primocode 8.12.0 → 8.13.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/package.json +2 -2
- package/studio/ferramentas/corte.md +27 -0
- package/studio/web/core.css +133 -2
- package/studio/web/js/editor.js +614 -99
- package/studio/web/js/ferramentas.js +4 -1
- package/studio/web/js/render.js +114 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.13.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. 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"
|
|
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"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
@@ -185,6 +185,33 @@ Na cena, `transicao` define como ela **entra**:
|
|
|
185
185
|
|
|
186
186
|
Vale o mesmo conselho das animações: varie. Tudo em `fade` parece slide, não vídeo.
|
|
187
187
|
|
|
188
|
+
## Chaves de animação
|
|
189
|
+
|
|
190
|
+
`anim` faz o elemento **entrar** e parar por ali. Para ele se mexer ao longo da
|
|
191
|
+
cena — atravessar, crescer devagar, sumir no fim — use `chaves`: uma lista de
|
|
192
|
+
instantes, com `t` de 0 (começo da cena) a 1 (fim).
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{ "tipo": "texto", "texto": "Atravessa a tela",
|
|
196
|
+
"chaves": [
|
|
197
|
+
{ "t": 0, "x": -400, "opacidade": 0 },
|
|
198
|
+
{ "t": 0.25, "x": 0, "opacidade": 1 },
|
|
199
|
+
{ "t": 0.75, "x": 0, "opacidade": 1 },
|
|
200
|
+
{ "t": 1, "x": 400, "opacidade": 0 }
|
|
201
|
+
] }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Campos de uma chave, todos opcionais: `x` e `y` (**deslocamento** em pixels a
|
|
205
|
+
partir de onde o elemento está — não é posição absoluta), `escala` (1 = tamanho
|
|
206
|
+
normal), `giro` (graus), `opacidade` (0 a 1) e `desfoque` (pixels).
|
|
207
|
+
|
|
208
|
+
No elemento: `"repetir": true` roda sem parar, e `"suavizar": false` dá
|
|
209
|
+
movimento constante em vez de acelerar e frear.
|
|
210
|
+
|
|
211
|
+
Duas chaves já bastam. Quem tem `chaves` ignora `anim` — as duas disputariam o
|
|
212
|
+
mesmo movimento, e a chave é mais específica. Use em **um ou dois** elementos
|
|
213
|
+
por vídeo: tudo se mexendo ao mesmo tempo cansa e esconde a mensagem.
|
|
214
|
+
|
|
188
215
|
## Acabamento
|
|
189
216
|
|
|
190
217
|
Textura por cima da cena inteira — é o que tira a cara de template:
|
package/studio/web/core.css
CHANGED
|
@@ -381,9 +381,18 @@ input.titulo-arq:focus { outline: none; border-color: var(--primo); background:
|
|
|
381
381
|
}
|
|
382
382
|
.grupo { border-bottom: 1px solid var(--linha); padding: 14px; }
|
|
383
383
|
.grupo > .rotulo { display: block; margin-bottom: 11px; }
|
|
384
|
-
|
|
384
|
+
/* Rótulo em cima, controle embaixo.
|
|
385
|
+
|
|
386
|
+
A coluna do rótulo tinha 62px fixos. "Acabamento" não cabia e saía
|
|
387
|
+
cortado; "Cor do fundo" e "Tema da cena" quebravam em duas linhas e
|
|
388
|
+
desalinhavam a altura de tudo. Era metade da sensação de "estranho" no
|
|
389
|
+
painel. Empilhado, o nome cabe inteiro e o controle usa a largura toda. */
|
|
390
|
+
.linha-campo { display: grid; gap: 4px; margin-bottom: 11px; }
|
|
385
391
|
.linha-campo:last-child { margin-bottom: 0; }
|
|
386
|
-
.linha-campo > span {
|
|
392
|
+
.linha-campo > span {
|
|
393
|
+
font-size: 10.5px; color: var(--fraco2);
|
|
394
|
+
letter-spacing: .04em; text-transform: uppercase;
|
|
395
|
+
}
|
|
387
396
|
.dois { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
388
397
|
.tres { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
|
|
389
398
|
.opcoes { display: flex; gap: 4px; }
|
|
@@ -721,3 +730,125 @@ body:has(.transporte) .aviso { bottom: 80px; }
|
|
|
721
730
|
.topo { gap: 6px; padding: 0 8px; }
|
|
722
731
|
.marca em { display: none; }
|
|
723
732
|
}
|
|
733
|
+
|
|
734
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
735
|
+
Reordenar, modelos e ações de camada
|
|
736
|
+
|
|
737
|
+
Três coisas que faltavam e faziam o editor parecer travado: arrastar não
|
|
738
|
+
dava retorno nenhum, camada não tinha ação à mão, e "nova cena" abria uma
|
|
739
|
+
tela em branco. O visual aqui é de propósito discreto — a peça que a
|
|
740
|
+
pessoa está fazendo é que tem de chamar atenção, não a ferramenta.
|
|
741
|
+
═════════════════════════════════════════════════════════════════════════ */
|
|
742
|
+
|
|
743
|
+
/* enquanto arrasta: o item sai de cena, o alvo se anuncia */
|
|
744
|
+
.arrastando { opacity: .35; }
|
|
745
|
+
.camada.alvo-solta, .miniatura.alvo-solta, .clipe.alvo-solta {
|
|
746
|
+
outline: 2px solid var(--primo);
|
|
747
|
+
outline-offset: 1px;
|
|
748
|
+
}
|
|
749
|
+
.camada, .miniatura, .clipe { -webkit-user-drag: element; }
|
|
750
|
+
.camada:active { cursor: grabbing; }
|
|
751
|
+
|
|
752
|
+
/* ações que aparecem ao passar o mouse na camada */
|
|
753
|
+
.camada { position: relative; }
|
|
754
|
+
.camada-acoes {
|
|
755
|
+
display: none; gap: 1px; margin-left: auto; flex: 0 0 auto;
|
|
756
|
+
}
|
|
757
|
+
.camada:hover .camada-acoes, .camada.ativa .camada-acoes { display: flex; }
|
|
758
|
+
.camada-acoes button {
|
|
759
|
+
background: none; border: 0; padding: 3px;
|
|
760
|
+
border-radius: 5px; cursor: pointer; color: var(--fraco2);
|
|
761
|
+
display: grid; place-items: center;
|
|
762
|
+
}
|
|
763
|
+
.camada-acoes button:hover { background: var(--sup3, rgba(255,255,255,.08)); color: var(--texto); }
|
|
764
|
+
.camada-acoes button:disabled { opacity: .25; cursor: default; }
|
|
765
|
+
.camada-acoes button:disabled:hover { background: none; color: var(--fraco2); }
|
|
766
|
+
.camada-acoes svg { width: 12px; height: 12px; opacity: 1; }
|
|
767
|
+
|
|
768
|
+
/* a transição marcada no próprio clipe da linha do tempo */
|
|
769
|
+
.clipe .tr-marca {
|
|
770
|
+
position: absolute; left: 4px; top: 4px;
|
|
771
|
+
background: rgba(6, 6, 8, .72);
|
|
772
|
+
color: #cbd5e1;
|
|
773
|
+
font-size: 9.5px; letter-spacing: .02em;
|
|
774
|
+
padding: 2px 5px; border-radius: 4px;
|
|
775
|
+
pointer-events: none;
|
|
776
|
+
max-width: calc(100% - 8px);
|
|
777
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/* o "+" no fim da tira */
|
|
781
|
+
.clipe-mais {
|
|
782
|
+
flex: 0 0 auto;
|
|
783
|
+
width: 40px; height: 62px;
|
|
784
|
+
border: 1px dashed var(--linha2);
|
|
785
|
+
border-radius: 8px;
|
|
786
|
+
background: none; color: var(--fraco2);
|
|
787
|
+
cursor: pointer;
|
|
788
|
+
display: grid; place-items: center;
|
|
789
|
+
}
|
|
790
|
+
.clipe-mais:hover { border-color: var(--primo); color: var(--primo); }
|
|
791
|
+
.clipe-mais svg { width: 15px; height: 15px; }
|
|
792
|
+
|
|
793
|
+
/* galeria de modelos */
|
|
794
|
+
.folha .caixa.larga { width: min(1040px, 100%); }
|
|
795
|
+
.folha .sub { color: var(--fraco); font-size: 12.5px; margin: -8px 0 18px; }
|
|
796
|
+
.modelos {
|
|
797
|
+
display: grid;
|
|
798
|
+
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
|
|
799
|
+
gap: 14px;
|
|
800
|
+
}
|
|
801
|
+
.modelo {
|
|
802
|
+
background: none; border: 0; padding: 0;
|
|
803
|
+
cursor: pointer; text-align: left;
|
|
804
|
+
display: grid; gap: 7px;
|
|
805
|
+
}
|
|
806
|
+
.modelo .caixa-mini {
|
|
807
|
+
position: relative; width: 100%; aspect-ratio: 16/9;
|
|
808
|
+
overflow: hidden; border-radius: 9px;
|
|
809
|
+
border: 1px solid var(--linha);
|
|
810
|
+
background: var(--sup2);
|
|
811
|
+
transition: border-color .12s, transform .12s;
|
|
812
|
+
}
|
|
813
|
+
.modelo .caixa-mini .cena { position: absolute; top: 0; left: 0; transform-origin: top left; }
|
|
814
|
+
.modelo .caixa-mini.vazia { display: grid; place-items: center; border-style: dashed; }
|
|
815
|
+
.modelo .caixa-mini.vazia svg { width: 22px; height: 22px; color: var(--fraco2); }
|
|
816
|
+
.modelo:hover .caixa-mini { border-color: var(--primo); transform: translateY(-2px); }
|
|
817
|
+
.modelo span { font-size: 12.5px; color: var(--fraco); }
|
|
818
|
+
.modelo:hover span { color: var(--texto); }
|
|
819
|
+
|
|
820
|
+
/* seletor de cor compacto: amostras numa faixa, hex ao lado */
|
|
821
|
+
.cor-faixa { display: grid; grid-template-columns: 1fr 62px; gap: 6px; align-items: center; }
|
|
822
|
+
.cor-faixa .cores { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px; }
|
|
823
|
+
.cor-faixa .cores::-webkit-scrollbar { height: 0; }
|
|
824
|
+
.cor-faixa .cores button { width: 19px; height: 19px; flex: 0 0 auto; }
|
|
825
|
+
.cor-faixa .cores button.ativa { outline: 2px solid var(--texto); outline-offset: 1px; }
|
|
826
|
+
.cor-faixa .sem-cor {
|
|
827
|
+
background: repeating-linear-gradient(45deg, var(--sup2) 0 4px, var(--linha2) 4px 8px);
|
|
828
|
+
}
|
|
829
|
+
.campo.hex { font-size: 11px; padding-left: 6px; padding-right: 6px; }
|
|
830
|
+
|
|
831
|
+
/* chaves de animação: a régua mostra QUANDO cada uma acontece */
|
|
832
|
+
.regua-chaves { position: relative; height: 26px; margin: 2px 0 2px; }
|
|
833
|
+
.trilho-chaves {
|
|
834
|
+
position: absolute; left: 5px; right: 5px; top: 11px; height: 3px;
|
|
835
|
+
background: var(--sup3); border-radius: 2px;
|
|
836
|
+
}
|
|
837
|
+
.trilho-chaves .pino {
|
|
838
|
+
position: absolute; top: 50%; width: 11px; height: 11px;
|
|
839
|
+
margin: -5.5px 0 0 -5.5px; padding: 0;
|
|
840
|
+
border-radius: 50%; border: 2px solid var(--fundo);
|
|
841
|
+
background: var(--fraco); cursor: pointer;
|
|
842
|
+
}
|
|
843
|
+
.trilho-chaves .pino:hover { background: var(--texto); }
|
|
844
|
+
.trilho-chaves .pino.ativo { background: var(--primo); transform: scale(1.25); }
|
|
845
|
+
.sub-regua { font-size: 10.5px; color: var(--fraco2); margin-bottom: 10px; }
|
|
846
|
+
.opcoes button.ativo { border-color: var(--primo); color: var(--primo); background: var(--primo-suave); }
|
|
847
|
+
.presets { display: grid; gap: 5px; margin-top: 9px; }
|
|
848
|
+
.presets button {
|
|
849
|
+
height: 30px; padding: 0 10px; text-align: left;
|
|
850
|
+
border: 1px solid var(--linha); border-radius: 8px;
|
|
851
|
+
background: var(--sup2); color: var(--fraco);
|
|
852
|
+
font: 500 12px/1 var(--sans); cursor: pointer;
|
|
853
|
+
}
|
|
854
|
+
.presets button:hover { background: var(--sup3); color: var(--texto); border-color: var(--primo); }
|
package/studio/web/js/editor.js
CHANGED
|
@@ -13,6 +13,220 @@
|
|
|
13
13
|
var ARQ = null, doc = null;
|
|
14
14
|
var iCena = 0, iAba = 0, sel = null; // sel = índice do elemento/bloco
|
|
15
15
|
var escala = 1, historico = [], futuro = [], tSalvar = null, sujo = false;
|
|
16
|
+
var chaveSel = 0; // chave de animação em edição
|
|
17
|
+
|
|
18
|
+
/* ------------------------------------------------------------- vocabulário
|
|
19
|
+
Os menus mostravam o valor cru do documento: "grao", "kb-aproxima", e uma
|
|
20
|
+
opção vazia no topo que não dizia nada. Quem abria o editor via três
|
|
21
|
+
caixas em branco e não tinha como adivinhar o que havia dentro. Cada
|
|
22
|
+
opção agora é [valor, nome em português], e o vazio virou uma frase.
|
|
23
|
+
|
|
24
|
+
Os valores NÃO mudam — são os mesmos que o render.css entende. O que
|
|
25
|
+
muda é só o que a pessoa lê. */
|
|
26
|
+
var OPC = {
|
|
27
|
+
transicao: [["", "Nenhuma (corte seco)"], ["fade", "Suave"], ["deslizar", "Deslizar para o lado"],
|
|
28
|
+
["subir", "Subir"], ["zoom", "Aproximar"], ["limpar", "Varrer"],
|
|
29
|
+
["persiana", "Persiana"], ["flash", "Flash"]],
|
|
30
|
+
fundo: [["", "Sem fundo"], ["malha", "Malha"], ["aurora", "Aurora"], ["raios", "Raios"],
|
|
31
|
+
["brilho", "Brilho"], ["ondas", "Ondas"], ["grade", "Grade"], ["pontos", "Pontos"],
|
|
32
|
+
["vinheta", "Vinheta"], ["ruido", "Ruído"]],
|
|
33
|
+
acabamento: [["", "Nenhum"], ["grao", "Granulado"], ["vinheta", "Vinheta"],
|
|
34
|
+
["scanlines", "Linhas de TV"], ["luz", "Vazamento de luz"], ["brilho", "Brilho"],
|
|
35
|
+
["moldura", "Moldura"]],
|
|
36
|
+
animacao: [["", "Nenhuma"], ["subir", "Subir"], ["surgir", "Surgir"], ["esq", "Da esquerda"],
|
|
37
|
+
["dir", "Da direita"], ["zoom", "Aproximar"], ["desfoque", "Sair do desfoque"],
|
|
38
|
+
["cair", "Cair"], ["girar", "Girar"], ["saltar", "Saltar"], ["revelar", "Revelar"],
|
|
39
|
+
["cortina", "Cortina"], ["elastico", "Elástico"]],
|
|
40
|
+
kenburns: [["", "Parado"], ["aproxima", "Aproximar"], ["afasta", "Afastar"],
|
|
41
|
+
["esquerda", "Correr para a esquerda"], ["direita", "Correr para a direita"],
|
|
42
|
+
["cima", "Subir devagar"]],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/* Menu com nome legível. Aceita "valor" ou ["valor", "Nome"]. */
|
|
46
|
+
function menu(opcoes, atual, aoMudar) {
|
|
47
|
+
var s = el("select", "campo");
|
|
48
|
+
opcoes.forEach(function (o) {
|
|
49
|
+
var valor = Array.isArray(o) ? o[0] : o;
|
|
50
|
+
var nome = Array.isArray(o) ? o[1] : String(o);
|
|
51
|
+
var op = el("option", "", nome);
|
|
52
|
+
op.value = valor;
|
|
53
|
+
if (String(valor) === String(atual == null ? "" : atual)) op.selected = true;
|
|
54
|
+
s.appendChild(op);
|
|
55
|
+
});
|
|
56
|
+
s.onchange = function () { aoMudar(s.value); };
|
|
57
|
+
return s;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ---------------------------------------------------------------- arrastar
|
|
61
|
+
Reordenar não existia. As camadas e as cenas só respondiam a clique, e
|
|
62
|
+
quem tentava arrastar — que é o gesto óbvio — não acontecia nada. Este
|
|
63
|
+
é o mesmo comportamento para as duas listas: pega, mostra onde vai cair,
|
|
64
|
+
solta, reordena e salva. */
|
|
65
|
+
function arrastavel(no, indice, mover) {
|
|
66
|
+
no.draggable = true;
|
|
67
|
+
no.addEventListener("dragstart", function (ev) {
|
|
68
|
+
ev.dataTransfer.effectAllowed = "move";
|
|
69
|
+
ev.dataTransfer.setData("text/plain", String(indice));
|
|
70
|
+
no.classList.add("arrastando");
|
|
71
|
+
});
|
|
72
|
+
no.addEventListener("dragend", function () {
|
|
73
|
+
no.classList.remove("arrastando");
|
|
74
|
+
Array.prototype.forEach.call(document.querySelectorAll(".alvo-solta"),
|
|
75
|
+
function (n) { n.classList.remove("alvo-solta"); });
|
|
76
|
+
});
|
|
77
|
+
no.addEventListener("dragover", function (ev) {
|
|
78
|
+
ev.preventDefault();
|
|
79
|
+
ev.dataTransfer.dropEffect = "move";
|
|
80
|
+
no.classList.add("alvo-solta");
|
|
81
|
+
});
|
|
82
|
+
no.addEventListener("dragleave", function () { no.classList.remove("alvo-solta"); });
|
|
83
|
+
no.addEventListener("drop", function (ev) {
|
|
84
|
+
ev.preventDefault();
|
|
85
|
+
ev.stopPropagation();
|
|
86
|
+
no.classList.remove("alvo-solta");
|
|
87
|
+
var de = parseInt(ev.dataTransfer.getData("text/plain"), 10);
|
|
88
|
+
if (isNaN(de) || de === indice) return;
|
|
89
|
+
mover(de, indice);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Tira o item de uma posição e põe na outra, na mesma lista. */
|
|
94
|
+
function reordenar(lista, de, para) {
|
|
95
|
+
var item = lista.splice(de, 1)[0];
|
|
96
|
+
lista.splice(para, 0, item);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function nomeDaOpcao(opcoes, valor) {
|
|
100
|
+
for (var i = 0; i < opcoes.length; i++) {
|
|
101
|
+
if (String(opcoes[i][0]) === String(valor)) return opcoes[i][1];
|
|
102
|
+
}
|
|
103
|
+
return String(valor);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ----------------------------------------------------------------- modelos
|
|
107
|
+
Cena pronta, com texto de exemplo, para pôr e trocar as palavras.
|
|
108
|
+
|
|
109
|
+
"Nova cena" dava uma cena VAZIA, e cena vazia é a tela em branco: quem
|
|
110
|
+
abre não sabe por onde começar e o resultado sai desalinhado do resto.
|
|
111
|
+
Estes modelos são os mesmos arranjos que a spec ensina à IA — a pessoa
|
|
112
|
+
agora alcança os mesmos, com um clique. */
|
|
113
|
+
var MODELOS = [
|
|
114
|
+
{ id: "capa", nome: "Abertura", para: ["corte", "prisma", "tela"],
|
|
115
|
+
cena: { layout: "capa", duracao: 4, fundo: "malha", transicao: "fade", elementos: [
|
|
116
|
+
{ tipo: "texto", papel: "chapeu", texto: "NOME DA EMPRESA", an: "surgir" },
|
|
117
|
+
{ tipo: "texto", papel: "titulo", texto: "O título que abre", an: "subir" },
|
|
118
|
+
{ tipo: "texto", papel: "apoio", texto: "Uma frase que explica do que se trata.", an: "subir" }] } },
|
|
119
|
+
{ id: "titulo-lista", nome: "Título e tópicos", para: ["corte", "prisma"],
|
|
120
|
+
cena: { layout: "titulo", duracao: 6, transicao: "deslizar", elementos: [
|
|
121
|
+
{ tipo: "texto", papel: "titulo", texto: "Uma ideia por cena" },
|
|
122
|
+
{ tipo: "lista", itens: ["Primeiro ponto", "Segundo ponto", "Terceiro ponto"],
|
|
123
|
+
revelar: true, intervalo: 0.9 }] } },
|
|
124
|
+
{ id: "numeros", nome: "Números que impressionam", para: ["corte", "prisma", "tela"],
|
|
125
|
+
cena: { layout: "centro", duracao: 5, fundo: "brilho", transicao: "zoom", elementos: [
|
|
126
|
+
{ tipo: "numeros", itens: [
|
|
127
|
+
{ valor: "3x", rotulo: "mais rápido" },
|
|
128
|
+
{ valor: "40%", rotulo: "menos custo" },
|
|
129
|
+
{ valor: "24h", rotulo: "no ar" }], an: "saltar" }] } },
|
|
130
|
+
{ id: "citacao", nome: "Citação", para: ["corte", "prisma", "tela"],
|
|
131
|
+
cena: { layout: "citacao", duracao: 5, tema: "papel", transicao: "limpar", elementos: [
|
|
132
|
+
{ tipo: "citacao", texto: "A frase que alguém disse e vale repetir.",
|
|
133
|
+
autor: "Quem disse", an: "desfoque" }] } },
|
|
134
|
+
{ id: "cartoes", nome: "Três cartões", para: ["corte", "prisma"],
|
|
135
|
+
cena: { layout: "titulo", duracao: 6, transicao: "subir", elementos: [
|
|
136
|
+
{ tipo: "texto", papel: "titulo", texto: "O que fazemos" },
|
|
137
|
+
{ tipo: "cartoes", itens: [
|
|
138
|
+
{ titulo: "Primeiro", texto: "Uma linha explicando." },
|
|
139
|
+
{ titulo: "Segundo", texto: "Uma linha explicando." },
|
|
140
|
+
{ titulo: "Terceiro", texto: "Uma linha explicando." }], revelar: true }] } },
|
|
141
|
+
{ id: "secao", nome: "Divisor de seção", para: ["corte", "prisma"],
|
|
142
|
+
cena: { layout: "secao", duracao: 2.5, fundo: "aurora", transicao: "flash", elementos: [
|
|
143
|
+
{ tipo: "texto", papel: "titulo", texto: "Parte dois", an: "zoom" }] } },
|
|
144
|
+
{ id: "foto", nome: "Foto com frase", para: ["corte", "prisma", "tela"],
|
|
145
|
+
cena: { layout: "capa", duracao: 5, acabamento: "vinheta", transicao: "fade",
|
|
146
|
+
fundo: "vinheta", kenburns: "aproxima", elementos: [
|
|
147
|
+
{ tipo: "texto", papel: "titulo", texto: "A frase por cima da imagem", an: "subir" }] } },
|
|
148
|
+
{ id: "grafico", nome: "Gráfico", para: ["corte", "prisma"],
|
|
149
|
+
cena: { layout: "titulo", duracao: 6, elementos: [
|
|
150
|
+
{ tipo: "texto", papel: "titulo", texto: "Como evoluiu" },
|
|
151
|
+
{ tipo: "grafico", grafico: "barras",
|
|
152
|
+
dados: [["Jan", 30], ["Fev", 52], ["Mar", 71], ["Abr", 96]] }] } },
|
|
153
|
+
{ id: "fecho", nome: "Fecho / chamada", para: ["corte", "prisma", "tela"],
|
|
154
|
+
cena: { layout: "centro", duracao: 4, fundo: "raios", transicao: "zoom", elementos: [
|
|
155
|
+
{ tipo: "texto", papel: "titulo", texto: "Vamos conversar", an: "zoom" },
|
|
156
|
+
{ tipo: "texto", papel: "apoio", texto: "seu@email.com", an: "surgir" }] } },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
function modelosDaFerramenta() {
|
|
160
|
+
return MODELOS.filter(function (m) { return m.para.indexOf(SLUG) !== -1; });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* A galeria: cada modelo desenhado de verdade, com o renderizador do
|
|
164
|
+
produto. Miniatura falsa mentiria sobre o resultado. */
|
|
165
|
+
function abrirModelos() {
|
|
166
|
+
var fundo = el("div", "folha on");
|
|
167
|
+
var caixa = el("div", "caixa larga");
|
|
168
|
+
caixa.appendChild(el("h3", "", "Escolha um começo"));
|
|
169
|
+
caixa.appendChild(el("p", "sub",
|
|
170
|
+
"Cada um entra como cena nova, com texto de exemplo para você trocar."));
|
|
171
|
+
|
|
172
|
+
var grade = el("div", "modelos");
|
|
173
|
+
var W = doc.largura || 1920, H = doc.altura || 1080;
|
|
174
|
+
var pendentes = [];
|
|
175
|
+
|
|
176
|
+
// Primeiro a cena em branco: quem quer começar do zero não deve ter de
|
|
177
|
+
// procurar.
|
|
178
|
+
var vazio = el("button", "modelo");
|
|
179
|
+
var cxV = el("div", "caixa-mini vazia", svgIcone("mais"));
|
|
180
|
+
vazio.appendChild(cxV);
|
|
181
|
+
vazio.appendChild(el("span", "", "Cena em branco"));
|
|
182
|
+
vazio.onclick = function () { fechar(); novaCena(null); };
|
|
183
|
+
grade.appendChild(vazio);
|
|
184
|
+
|
|
185
|
+
modelosDaFerramenta().forEach(function (m) {
|
|
186
|
+
var b = el("button", "modelo");
|
|
187
|
+
var cx = el("div", "caixa-mini");
|
|
188
|
+
var mini = renderCena(m.cena, doc, {});
|
|
189
|
+
cx.appendChild(mini);
|
|
190
|
+
b.appendChild(cx);
|
|
191
|
+
b.appendChild(el("span", "", m.nome));
|
|
192
|
+
b.onclick = function () { fechar(); novaCena(m); };
|
|
193
|
+
grade.appendChild(b);
|
|
194
|
+
pendentes.push([cx, mini]);
|
|
195
|
+
});
|
|
196
|
+
caixa.appendChild(grade);
|
|
197
|
+
|
|
198
|
+
var rodape = el("div", "rodape");
|
|
199
|
+
var bFechar = el("button", "btn", "Fechar");
|
|
200
|
+
bFechar.onclick = fechar;
|
|
201
|
+
rodape.appendChild(bFechar);
|
|
202
|
+
caixa.appendChild(rodape);
|
|
203
|
+
fundo.appendChild(caixa);
|
|
204
|
+
document.body.appendChild(fundo);
|
|
205
|
+
|
|
206
|
+
// A escala só dá para calcular depois que o nó está no documento e tem
|
|
207
|
+
// largura de verdade.
|
|
208
|
+
pendentes.forEach(function (p) {
|
|
209
|
+
var larg = p[0].clientWidth || 220;
|
|
210
|
+
p[0].style.height = Math.round(larg * H / W) + "px";
|
|
211
|
+
p[1].style.transform = "scale(" + (larg / W) + ")";
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
function fechar() { fundo.remove(); document.removeEventListener("keydown", esc); }
|
|
215
|
+
function esc(ev) { if (ev.key === "Escape") fechar(); }
|
|
216
|
+
document.addEventListener("keydown", esc);
|
|
217
|
+
fundo.onclick = function (ev) { if (ev.target === fundo) fechar(); };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function novaCena(modelo) {
|
|
221
|
+
var base = modelo && modelo.cena
|
|
222
|
+
? JSON.parse(JSON.stringify(modelo.cena))
|
|
223
|
+
: { layout: F.layoutPadrao, duracao: 4, elementos: [] };
|
|
224
|
+
if (!F.tempo) delete base.duracao;
|
|
225
|
+
mudar(function () {
|
|
226
|
+
doc.cenas.splice(iCena + 1, 0, base);
|
|
227
|
+
iCena++; sel = null;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
16
230
|
|
|
17
231
|
/* ------------------------------------------------------------ utilidades */
|
|
18
232
|
|
|
@@ -236,48 +450,80 @@
|
|
|
236
450
|
lista.innerHTML = "";
|
|
237
451
|
|
|
238
452
|
if (F.modelo === "cena") {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
453
|
+
// No Corte as cenas já vivem na tira do rodapé. Repetir a mesma lista
|
|
454
|
+
// aqui era o pior tipo de duplicação: duas coisas iguais, lado a lado,
|
|
455
|
+
// sem dizer qual manda. Aqui ficam só as camadas — que é o que faltava
|
|
456
|
+
// ter espaço.
|
|
457
|
+
var soCamadas = !!F.tempo;
|
|
458
|
+
if (!soCamadas) {
|
|
459
|
+
$("#painel-rotulo").textContent = SLUG === "prisma" ? "Slides" : "Pranchas";
|
|
460
|
+
var W = doc.largura || 1920, H = doc.altura || 1080;
|
|
461
|
+
var minis = [];
|
|
462
|
+
(doc.cenas || []).forEach(function (c, i) {
|
|
463
|
+
var m = el("div", "miniatura" + (i === iCena ? " ativa" : ""));
|
|
464
|
+
var caixa = el("div", "caixa");
|
|
465
|
+
var mini = renderCena(c, doc, {});
|
|
466
|
+
caixa.appendChild(mini);
|
|
467
|
+
m.appendChild(caixa);
|
|
468
|
+
m.appendChild(el("div", "num", String(i + 1)));
|
|
469
|
+
m.onclick = function () { iCena = i; sel = null; desenhar(); };
|
|
470
|
+
arrastavel(m, i, function (de, para) {
|
|
471
|
+
mudar(function () { reordenar(doc.cenas, de, para); });
|
|
472
|
+
iCena = para; sel = null; desenhar();
|
|
473
|
+
});
|
|
474
|
+
lista.appendChild(m);
|
|
475
|
+
minis.push([caixa, mini]);
|
|
476
|
+
});
|
|
477
|
+
// a escala vem da largura real da caixa, que depende do painel
|
|
478
|
+
minis.forEach(function (par) {
|
|
479
|
+
var larg = par[0].clientWidth || 160;
|
|
480
|
+
par[0].style.height = Math.round(larg * H / W) + "px";
|
|
481
|
+
par[1].style.transform = "scale(" + (larg / W) + ")";
|
|
482
|
+
});
|
|
483
|
+
}
|
|
259
484
|
|
|
260
|
-
|
|
485
|
+
var els = cena().elementos || [];
|
|
486
|
+
$("#painel-rotulo").textContent = soCamadas ? "Camadas da cena" : $("#painel-rotulo").textContent;
|
|
487
|
+
if (!soCamadas && els.length) {
|
|
261
488
|
var tit = el("div", "rotulo");
|
|
262
|
-
tit.style.cssText = "padding:
|
|
489
|
+
tit.style.cssText = "padding:14px 4px 6px";
|
|
263
490
|
tit.textContent = "Camadas";
|
|
264
491
|
lista.appendChild(tit);
|
|
265
|
-
(cena().elementos || []).forEach(function (e2, i) {
|
|
266
|
-
var c = el("div", "camada" + (i === sel ? " ativa" : ""),
|
|
267
|
-
svgIcone(iconeDe(e2)) + "<span>" + rotuloDe(e2) + "</span>");
|
|
268
|
-
c.onclick = function () { sel = i; desenhar(); };
|
|
269
|
-
lista.appendChild(c);
|
|
270
|
-
});
|
|
271
492
|
}
|
|
493
|
+
if (!els.length) {
|
|
494
|
+
lista.appendChild(el("div", "nada", soCamadas
|
|
495
|
+
? "Cena vazia. Use a barra da esquerda para pôr texto, lista, número ou imagem."
|
|
496
|
+
: "Nada nesta prancha ainda."));
|
|
497
|
+
}
|
|
498
|
+
// De cima para baixo na lista = da frente para trás na tela, que é como
|
|
499
|
+
// todo editor de camada se comporta. O documento guarda o contrário.
|
|
500
|
+
els.slice().reverse().forEach(function (e2, ordem) {
|
|
501
|
+
var i = els.length - 1 - ordem;
|
|
502
|
+
var c = el("div", "camada" + (i === sel ? " ativa" : ""),
|
|
503
|
+
svgIcone(iconeDe(e2)) + "<span>" + rotuloDe(e2) + "</span>");
|
|
504
|
+
c.appendChild(botoesDaCamada(els, i));
|
|
505
|
+
c.onclick = function () { sel = i; desenhar(); };
|
|
506
|
+
arrastavel(c, i, function (de, para) {
|
|
507
|
+
mudar(function () { reordenar(els, de, para); });
|
|
508
|
+
sel = para; desenhar();
|
|
509
|
+
});
|
|
510
|
+
lista.appendChild(c);
|
|
511
|
+
});
|
|
272
512
|
return;
|
|
273
513
|
}
|
|
274
514
|
|
|
275
515
|
if (F.modelo === "documento") {
|
|
276
516
|
$("#painel-rotulo").textContent = "Blocos";
|
|
277
|
-
|
|
517
|
+
var blocos = doc.blocos || [];
|
|
518
|
+
blocos.forEach(function (b, i) {
|
|
278
519
|
var c = el("div", "camada" + (i === sel ? " ativa" : ""),
|
|
279
520
|
svgIcone(iconeDe(b)) + "<span>" + rotuloDe(b) + "</span>");
|
|
521
|
+
c.appendChild(botoesDaCamada(blocos, i));
|
|
280
522
|
c.onclick = function () { sel = i; desenhar(); };
|
|
523
|
+
arrastavel(c, i, function (de, para) {
|
|
524
|
+
mudar(function () { reordenar(blocos, de, para); });
|
|
525
|
+
sel = para; desenhar();
|
|
526
|
+
});
|
|
281
527
|
lista.appendChild(c);
|
|
282
528
|
});
|
|
283
529
|
return;
|
|
@@ -292,6 +538,41 @@
|
|
|
292
538
|
});
|
|
293
539
|
}
|
|
294
540
|
|
|
541
|
+
/* Subir, descer, duplicar e apagar direto na camada.
|
|
542
|
+
|
|
543
|
+
Arrastar é o gesto natural, mas não é o único: num item pequeno o
|
|
544
|
+
arrasto erra o alvo, e teclado não arrasta. Os botões fazem o mesmo
|
|
545
|
+
trabalho sem depender de pontaria. */
|
|
546
|
+
function botoesDaCamada(lista, i) {
|
|
547
|
+
var caixa = el("div", "camada-acoes");
|
|
548
|
+
function bt(icone, titulo, fn) {
|
|
549
|
+
var b = el("button", "", svgIcone(icone));
|
|
550
|
+
b.title = titulo;
|
|
551
|
+
b.onclick = function (ev) { ev.stopPropagation(); fn(); };
|
|
552
|
+
caixa.appendChild(b);
|
|
553
|
+
return b;
|
|
554
|
+
}
|
|
555
|
+
bt("subir", "Trazer para a frente", function () {
|
|
556
|
+
if (i >= lista.length - 1) return;
|
|
557
|
+
mudar(function () { reordenar(lista, i, i + 1); });
|
|
558
|
+
sel = i + 1; desenhar();
|
|
559
|
+
}).disabled = i >= lista.length - 1;
|
|
560
|
+
bt("descer", "Mandar para trás", function () {
|
|
561
|
+
if (i <= 0) return;
|
|
562
|
+
mudar(function () { reordenar(lista, i, i - 1); });
|
|
563
|
+
sel = i - 1; desenhar();
|
|
564
|
+
}).disabled = i <= 0;
|
|
565
|
+
bt("duplicar", "Duplicar", function () {
|
|
566
|
+
mudar(function () { lista.splice(i + 1, 0, JSON.parse(JSON.stringify(lista[i]))); });
|
|
567
|
+
sel = i + 1; desenhar();
|
|
568
|
+
});
|
|
569
|
+
bt("lixeira", "Apagar", function () {
|
|
570
|
+
mudar(function () { lista.splice(i, 1); });
|
|
571
|
+
sel = null; desenhar();
|
|
572
|
+
});
|
|
573
|
+
return caixa;
|
|
574
|
+
}
|
|
575
|
+
|
|
295
576
|
function iconeDe(e) {
|
|
296
577
|
return { texto: "texto", lista: "lista", cartoes: "cartao", numeros: "numero",
|
|
297
578
|
tabela: "tabela", codigo: "codigo", citacao: "citacao", forma: "quadrado",
|
|
@@ -333,13 +614,20 @@
|
|
|
333
614
|
wrap.appendChild(quadro);
|
|
334
615
|
|
|
335
616
|
/* seleção e interação */
|
|
336
|
-
|
|
617
|
+
// Índice do documento -> nó na tela, pelo carimbo que o renderizador
|
|
618
|
+
// deixa em data-i.
|
|
619
|
+
//
|
|
620
|
+
// Antes isto era deduzido pela ordem: o k-ésimo nó era o k-ésimo
|
|
621
|
+
// elemento. A dedução quebra em todo layout de fluxo, porque eles
|
|
622
|
+
// desenham o cabeçalho (chapéu e título) antes do corpo — a ordem na
|
|
623
|
+
// tela não é a do documento. Quem clicava num texto via o editor
|
|
624
|
+
// selecionar outro, e editava o errado. Foi medido: nas três camadas da
|
|
625
|
+
// cena de teste, as três apontavam para o elemento vizinho.
|
|
337
626
|
var lista = cena().elementos || [];
|
|
338
|
-
var mapa =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
mapa.push(els[k++] || null);
|
|
627
|
+
var mapa = lista.map(function () { return null; });
|
|
628
|
+
Array.prototype.forEach.call(cn.querySelectorAll(".el[data-i]"), function (no) {
|
|
629
|
+
var i = +no.dataset.i;
|
|
630
|
+
if (i >= 0 && i < mapa.length && !mapa[i]) mapa[i] = no;
|
|
343
631
|
});
|
|
344
632
|
|
|
345
633
|
mapa.forEach(function (no, i) {
|
|
@@ -463,13 +751,8 @@
|
|
|
463
751
|
var esp = el("div"); esp.style.flex = "1"; b.appendChild(esp);
|
|
464
752
|
|
|
465
753
|
if (F.modelo === "cena") {
|
|
466
|
-
var addC = el("button", "btn", svgIcone("mais") + " Nova cena");
|
|
467
|
-
addC.onclick =
|
|
468
|
-
mudar(function () {
|
|
469
|
-
doc.cenas.splice(iCena + 1, 0, { layout: F.layoutPadrao, elementos: [] });
|
|
470
|
-
iCena++; sel = null;
|
|
471
|
-
});
|
|
472
|
-
};
|
|
754
|
+
var addC = el("button", "btn primo", svgIcone("mais") + " Nova cena");
|
|
755
|
+
addC.onclick = abrirModelos;
|
|
473
756
|
var dup = el("button", "btn", svgIcone("duplicar"));
|
|
474
757
|
dup.title = "Duplicar cena";
|
|
475
758
|
dup.onclick = function () {
|
|
@@ -610,12 +893,28 @@
|
|
|
610
893
|
caixa.appendChild(mini);
|
|
611
894
|
clipe.appendChild(caixa);
|
|
612
895
|
clipe.appendChild(el("div", "dur", dur + "s"));
|
|
896
|
+
// A transição aparece NA tira, entre um clipe e outro, que é onde ela
|
|
897
|
+
// acontece. Escondida num menu do inspetor, ninguém achava.
|
|
898
|
+
if (c.transicao && c.transicao !== "corte") {
|
|
899
|
+
clipe.appendChild(el("div", "tr-marca", nomeDaOpcao(OPC.transicao, c.transicao)));
|
|
900
|
+
}
|
|
613
901
|
clipe.onclick = function () { iCena = i; sel = null; desenhar(); };
|
|
902
|
+
clipe.ondblclick = function () { Player && Player.tocar && Player.tocar(); };
|
|
903
|
+
arrastavel(clipe, i, function (de, para) {
|
|
904
|
+
mudar(function () { reordenar(doc.cenas, de, para); });
|
|
905
|
+
iCena = para; sel = null; desenhar();
|
|
906
|
+
});
|
|
614
907
|
t.appendChild(clipe);
|
|
615
908
|
});
|
|
909
|
+
|
|
910
|
+
var mais = el("button", "clipe-mais", svgIcone("mais"));
|
|
911
|
+
mais.title = "Nova cena";
|
|
912
|
+
mais.onclick = abrirModelos;
|
|
913
|
+
t.appendChild(mais);
|
|
914
|
+
|
|
616
915
|
var info = el("div");
|
|
617
916
|
info.style.cssText = "margin-left:12px;font-size:12px;color:var(--fraco2);white-space:nowrap";
|
|
618
|
-
info.textContent = "total " + total.toFixed(1) + "s";
|
|
917
|
+
info.textContent = "total " + total.toFixed(1) + "s · arraste para reordenar";
|
|
619
918
|
t.appendChild(info);
|
|
620
919
|
}
|
|
621
920
|
|
|
@@ -790,10 +1089,44 @@
|
|
|
790
1089
|
return t;
|
|
791
1090
|
}
|
|
792
1091
|
|
|
793
|
-
|
|
1092
|
+
/* O menu escreve em português.
|
|
1093
|
+
|
|
1094
|
+
Antes ele mostrava o valor cru do documento — "grao", "kb-aproxima",
|
|
1095
|
+
"left" — e uma opção vazia no topo, sem texto nenhum. Três caixas em
|
|
1096
|
+
branco no inspetor, e nada dizendo o que havia dentro delas.
|
|
1097
|
+
|
|
1098
|
+
O dicionário traduz na hora de mostrar; o valor gravado continua o
|
|
1099
|
+
mesmo, porque é ele que o render.css entende. `vazio` é o texto da
|
|
1100
|
+
opção "", que muda com o contexto: em Fundo é "Sem fundo", em
|
|
1101
|
+
Acabamento é "Nenhum". */
|
|
1102
|
+
var NOMES = {
|
|
1103
|
+
malha: "Malha", brilho: "Brilho", grade: "Grade", pontos: "Pontos", suave: "Suave",
|
|
1104
|
+
vinheta: "Vinheta", aurora: "Aurora", raios: "Raios", ondas: "Ondas", ruido: "Ruído",
|
|
1105
|
+
grao: "Granulado", scanlines: "Linhas de TV", luz: "Vazamento de luz", moldura: "Moldura",
|
|
1106
|
+
horizontal: "Na horizontal", vertical: "Na vertical",
|
|
1107
|
+
chapeu: "Chapéu", titulo: "Título", subtitulo: "Subtítulo", apoio: "Apoio",
|
|
1108
|
+
corpo: "Corpo", legenda: "Legenda",
|
|
1109
|
+
left: "À esquerda", center: "Ao centro", right: "À direita",
|
|
1110
|
+
cobrir: "Preencher", conter: "Caber inteira",
|
|
1111
|
+
cinza: "Preto e branco", sepia: "Sépia", contraste: "Contraste", desbotado: "Desbotado",
|
|
1112
|
+
aproximar: "Aproximar", afastar: "Afastar", esquerda: "Para a esquerda",
|
|
1113
|
+
direita: "Para a direita", cima: "Para cima",
|
|
1114
|
+
retangulo: "Retângulo", circulo: "Círculo", triangulo: "Triângulo",
|
|
1115
|
+
linha: "Linha", seta: "Seta",
|
|
1116
|
+
barras: "Barras", area: "Área", pizza: "Pizza", rosca: "Rosca",
|
|
1117
|
+
curva: "Curva", reta: "Reta", cotovelo: "Cotovelo",
|
|
1118
|
+
texto: "Texto", inteiro: "Número inteiro", decimal: "Decimal",
|
|
1119
|
+
moeda: "Dinheiro", percentual: "Porcentagem",
|
|
1120
|
+
"não": "Não", sim: "Sim",
|
|
1121
|
+
300: "Fino", 400: "Normal", 500: "Médio", 600: "Semi-negrito",
|
|
1122
|
+
700: "Negrito", 800: "Extra-negrito",
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
function selecao(valores, atual, aoMudar, vazio) {
|
|
794
1126
|
var s = el("select", "campo");
|
|
795
1127
|
valores.forEach(function (v) {
|
|
796
|
-
var
|
|
1128
|
+
var rotulo = v === "" ? (vazio || "Automático") : (NOMES[v] || String(v));
|
|
1129
|
+
var o = el("option", "", rotulo);
|
|
797
1130
|
o.value = v;
|
|
798
1131
|
if (String(v) === String(atual)) o.selected = true;
|
|
799
1132
|
s.appendChild(o);
|
|
@@ -802,18 +1135,32 @@
|
|
|
802
1135
|
return s;
|
|
803
1136
|
}
|
|
804
1137
|
|
|
1138
|
+
/* As amostras e o campo hex na MESMA faixa.
|
|
1139
|
+
|
|
1140
|
+
Empilhados, cada cor ocupava três linhas do painel; com dois seletores
|
|
1141
|
+
de cor na mesma tela, sobrava painel só para eles. Agora as amostras
|
|
1142
|
+
rolam numa faixa e o hex fica ao lado — e a amostra escolhida aparece
|
|
1143
|
+
marcada, coisa que antes não dava para saber. */
|
|
805
1144
|
function seletorCor(valor, aoMudar) {
|
|
806
|
-
var caixa = el("div");
|
|
807
|
-
caixa.style.cssText = "display:grid;gap:6px";
|
|
1145
|
+
var caixa = el("div", "cor-faixa");
|
|
808
1146
|
var p = el("div", "cores");
|
|
1147
|
+
var limpar = el("button", "sem-cor");
|
|
1148
|
+
limpar.title = "Sem cor";
|
|
1149
|
+
limpar.onclick = function () { aoMudar(""); };
|
|
1150
|
+
p.appendChild(limpar);
|
|
809
1151
|
PALETA.forEach(function (c) {
|
|
810
1152
|
var b = el("button");
|
|
811
1153
|
b.style.background = c;
|
|
1154
|
+
if (String(valor).toLowerCase() === String(c).toLowerCase()) b.classList.add("ativa");
|
|
1155
|
+
b.title = c;
|
|
812
1156
|
b.onclick = function () { aoMudar(c); };
|
|
813
1157
|
p.appendChild(b);
|
|
814
1158
|
});
|
|
815
1159
|
caixa.appendChild(p);
|
|
816
|
-
|
|
1160
|
+
var hex = inp(valor, aoMudar);
|
|
1161
|
+
hex.placeholder = "#hex";
|
|
1162
|
+
hex.className = "campo hex";
|
|
1163
|
+
caixa.appendChild(hex);
|
|
817
1164
|
return caixa;
|
|
818
1165
|
}
|
|
819
1166
|
|
|
@@ -837,13 +1184,21 @@
|
|
|
837
1184
|
var c = cena();
|
|
838
1185
|
g0.appendChild(linha("Layout", selecao(F.layouts, c.layout || F.layoutPadrao,
|
|
839
1186
|
function (v) { mudar(function () { c.layout = v; }); })));
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1187
|
+
// UM controle para o fundo, não três.
|
|
1188
|
+
//
|
|
1189
|
+
// Havia "Fundo", "Fundo (extra)" e "Cor fundo", lado a lado, e os três
|
|
1190
|
+
// gravavam na MESMA propriedade: escolher num apagava o outro, e dois
|
|
1191
|
+
// deles apareciam vazios porque só um podia estar preenchido por vez.
|
|
1192
|
+
// Era a parte mais confusa do inspetor. Agora é uma lista só, com tudo
|
|
1193
|
+
// que existe, e a cor entra por baixo quando é cor que se quer.
|
|
1194
|
+
var padroes = ["", "malha", "brilho", "grade", "pontos", "suave", "vinheta",
|
|
1195
|
+
"aurora", "raios", "ondas", "nevoa", "xadrez"];
|
|
1196
|
+
var fundoAtual = typeof c.fundo === "string" && c.fundo.charAt(0) !== "#" ? c.fundo : "";
|
|
1197
|
+
g0.appendChild(linha("Fundo", selecao(padroes, fundoAtual, function (v) {
|
|
1198
|
+
mudar(function () { if (v) c.fundo = v; else delete c.fundo; });
|
|
1199
|
+
}, "Sem fundo")));
|
|
1200
|
+
g0.appendChild(linha("Cor do fundo", seletorCor(
|
|
1201
|
+
typeof c.fundo === "string" && c.fundo.charAt(0) === "#" ? c.fundo : "",
|
|
847
1202
|
function (v) { mudar(function () { if (v) c.fundo = v; else delete c.fundo; }); })));
|
|
848
1203
|
|
|
849
1204
|
var bFoto = el("button", "btn", svgIcone("imagem") + " Foto de fundo");
|
|
@@ -855,16 +1210,17 @@
|
|
|
855
1210
|
};
|
|
856
1211
|
g0.appendChild(linha("Imagem", bFoto));
|
|
857
1212
|
|
|
858
|
-
g0.appendChild(linha("
|
|
859
|
-
c.acabamento || "", function (v) { mudar(function () { if (v) c.acabamento = v; else delete c.acabamento; }); })));
|
|
860
|
-
g0.appendChild(linha("
|
|
1213
|
+
g0.appendChild(linha("Acabamento", selecao(["", "grao", "vinheta", "scanlines", "luz", "brilho", "moldura"],
|
|
1214
|
+
c.acabamento || "", function (v) { mudar(function () { if (v) c.acabamento = v; else delete c.acabamento; }); }, "Nenhum")));
|
|
1215
|
+
g0.appendChild(linha("Cor de destaque", seletorCor(c.tom || "",
|
|
861
1216
|
function (v) { mudar(function () { if (v) c.tom = v; else delete c.tom; }); })));
|
|
1217
|
+
g0.appendChild(linha("Tema da cena", selecao([""].concat(TEMAS), c.tema || "",
|
|
1218
|
+
function (v) { mudar(function () { if (v) c.tema = v; else delete c.tema; }); }, "Igual ao documento")));
|
|
862
1219
|
|
|
863
1220
|
if (F.tempo) {
|
|
864
|
-
g0.appendChild(linha("Duração", inp(c.duracao || 4, function (v) { mudar(function () { c.duracao = v || 4; }); }, "number")));
|
|
865
|
-
g0.appendChild(linha("
|
|
866
|
-
|
|
867
|
-
c.transicao || "", function (v) { mudar(function () { if (v) c.transicao = v; else delete c.transicao; }); })));
|
|
1221
|
+
g0.appendChild(linha("Duração (s)", inp(c.duracao || 4, function (v) { mudar(function () { c.duracao = v || 4; }); }, "number")));
|
|
1222
|
+
g0.appendChild(linha("Como entra", menu(OPC.transicao, c.transicao || "",
|
|
1223
|
+
function (v) { mudar(function () { if (v) c.transicao = v; else delete c.transicao; }); })));
|
|
868
1224
|
}
|
|
869
1225
|
if (SLUG === "traco") {
|
|
870
1226
|
g0.appendChild(linha("Arranjo", selecao(["", "horizontal", "vertical"], c.auto || "",
|
|
@@ -932,12 +1288,10 @@
|
|
|
932
1288
|
la.appendChild(inp(e.l, function (v) { edita("l", v); }, "number"));
|
|
933
1289
|
la.appendChild(inp(e.a, function (v) { edita("a", v); }, "number"));
|
|
934
1290
|
gp.appendChild(linha("L / A", la));
|
|
935
|
-
gp.appendChild(linha("Girar", inp(e.rotacao || 0, function (v) { edita("rotacao", v); }, "number")));
|
|
936
|
-
gp.appendChild(linha("
|
|
937
|
-
gp.appendChild(linha("
|
|
938
|
-
|
|
939
|
-
"girar", "saltar", "revelar", "cortina", "elastico", "palavra", "nenhuma"],
|
|
940
|
-
e.anim || "", function (v) { edita("anim", v); })));
|
|
1291
|
+
gp.appendChild(linha("Girar (graus)", inp(e.rotacao || 0, function (v) { edita("rotacao", v); }, "number")));
|
|
1292
|
+
gp.appendChild(linha("Opacidade", inp(e.opacidade != null ? e.opacidade : 1, function (v) { edita("opacidade", v); }, "number")));
|
|
1293
|
+
gp.appendChild(linha("Como entra", menu(OPC.animacao, e.anim || "",
|
|
1294
|
+
function (v) { edita("anim", v); })));
|
|
941
1295
|
if (e.tipo === "numeros") {
|
|
942
1296
|
gp.appendChild(linha("Contar", selecao(["não", "sim"], e.contar ? "sim" : "não",
|
|
943
1297
|
function (v) { edita("contar", v === "sim" ? true : ""); })));
|
|
@@ -948,7 +1302,133 @@
|
|
|
948
1302
|
dica.style.padding = "10px 16px";
|
|
949
1303
|
ins.appendChild(dica);
|
|
950
1304
|
}
|
|
1305
|
+
if (F.tempo) chavesInspetor(ins, e);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/* ─────────────────────────────────────────────────────────── chaves ─────
|
|
1310
|
+
Antes um elemento só sabia ENTRAR: escolhia-se um efeito e acabava ali.
|
|
1311
|
+
Nada percorria a cena, crescia devagar nem sumia no fim.
|
|
1312
|
+
|
|
1313
|
+
Uma chave é um instante: onde o elemento está aos 0%, aos 50%, aos
|
|
1314
|
+
100% da cena. Duas ou mais viram animação de verdade. A régua embaixo
|
|
1315
|
+
mostra as chaves na linha do tempo da cena — não é enfeite, é o único
|
|
1316
|
+
jeito de ver que existem três chaves e que a do meio está adiantada. */
|
|
1317
|
+
var PRESETS_CHAVE = [
|
|
1318
|
+
{ nome: "Atravessar", chaves: [{ t: 0, x: -220, opacidade: 0 }, { t: 0.18, x: 0, opacidade: 1 },
|
|
1319
|
+
{ t: 0.82, x: 0, opacidade: 1 }, { t: 1, x: 220, opacidade: 0 }] },
|
|
1320
|
+
{ nome: "Crescer devagar", chaves: [{ t: 0, escala: 1 }, { t: 1, escala: 1.18 }] },
|
|
1321
|
+
{ nome: "Aparecer e sumir", chaves: [{ t: 0, opacidade: 0 }, { t: 0.15, opacidade: 1 },
|
|
1322
|
+
{ t: 0.85, opacidade: 1 }, { t: 1, opacidade: 0 }] },
|
|
1323
|
+
{ nome: "Flutuar", chaves: [{ t: 0, y: 0 }, { t: 0.5, y: -22 }, { t: 1, y: 0 }] },
|
|
1324
|
+
{ nome: "Sair do desfoque", chaves: [{ t: 0, desfoque: 18, opacidade: 0, escala: 1.06 },
|
|
1325
|
+
{ t: 0.35, desfoque: 0, opacidade: 1, escala: 1 }, { t: 1, desfoque: 0, opacidade: 1, escala: 1 }] },
|
|
1326
|
+
];
|
|
1327
|
+
|
|
1328
|
+
function chavesInspetor(ins, e) {
|
|
1329
|
+
var g = grupo("Animação por chaves");
|
|
1330
|
+
var chaves = e.chaves || [];
|
|
1331
|
+
|
|
1332
|
+
if (!chaves.length) {
|
|
1333
|
+
g.appendChild(el("div", "nada",
|
|
1334
|
+
"Sem chaves: o elemento fica parado depois de entrar."));
|
|
1335
|
+
var pr = el("div", "presets");
|
|
1336
|
+
PRESETS_CHAVE.forEach(function (p) {
|
|
1337
|
+
var b = el("button", "", p.nome);
|
|
1338
|
+
b.onclick = function () {
|
|
1339
|
+
edita("chaves", JSON.parse(JSON.stringify(p.chaves)));
|
|
1340
|
+
};
|
|
1341
|
+
pr.appendChild(b);
|
|
1342
|
+
});
|
|
1343
|
+
g.appendChild(pr);
|
|
1344
|
+
ins.appendChild(g);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// A régua: cada chave é um pino na duração da cena.
|
|
1349
|
+
var regua = el("div", "regua-chaves");
|
|
1350
|
+
var trilho = el("div", "trilho-chaves");
|
|
1351
|
+
regua.appendChild(trilho);
|
|
1352
|
+
chaves.forEach(function (k, i) {
|
|
1353
|
+
var pino = el("button", "pino" + (i === chaveSel ? " ativo" : ""));
|
|
1354
|
+
pino.style.left = (Math.max(0, Math.min(1, k.t || 0)) * 100) + "%";
|
|
1355
|
+
pino.title = ((k.t || 0) * (cena().duracao || 4)).toFixed(1) + "s";
|
|
1356
|
+
pino.onclick = function () { chaveSel = i; desenharInspetor(); };
|
|
1357
|
+
trilho.appendChild(pino);
|
|
1358
|
+
});
|
|
1359
|
+
g.appendChild(regua);
|
|
1360
|
+
g.appendChild(el("div", "sub-regua",
|
|
1361
|
+
"0s" + " · " + (cena().duracao || 4) + "s no fim"));
|
|
1362
|
+
|
|
1363
|
+
if (chaveSel >= chaves.length) chaveSel = chaves.length - 1;
|
|
1364
|
+
var k = chaves[chaveSel] || chaves[0];
|
|
1365
|
+
|
|
1366
|
+
var nav = el("div", "opcoes");
|
|
1367
|
+
chaves.forEach(function (kk, i) {
|
|
1368
|
+
var b = el("button", i === chaveSel ? "ativo" : "", String(i + 1));
|
|
1369
|
+
b.onclick = function () { chaveSel = i; desenharInspetor(); };
|
|
1370
|
+
nav.appendChild(b);
|
|
1371
|
+
});
|
|
1372
|
+
var bMais = el("button", "", svgIcone("mais"));
|
|
1373
|
+
bMais.title = "Nova chave no fim";
|
|
1374
|
+
bMais.onclick = function () {
|
|
1375
|
+
mudar(function () {
|
|
1376
|
+
var ultima = chaves[chaves.length - 1] || {};
|
|
1377
|
+
var nova = JSON.parse(JSON.stringify(ultima));
|
|
1378
|
+
nova.t = Math.min(1, (ultima.t || 0) + 0.25);
|
|
1379
|
+
chaves.push(nova);
|
|
1380
|
+
chaves.sort(function (a, b2) { return (a.t || 0) - (b2.t || 0); });
|
|
1381
|
+
});
|
|
1382
|
+
chaveSel = chaves.length - 1;
|
|
1383
|
+
desenharInspetor();
|
|
1384
|
+
};
|
|
1385
|
+
nav.appendChild(bMais);
|
|
1386
|
+
g.appendChild(nav);
|
|
1387
|
+
|
|
1388
|
+
function campoChave(rotulo, campo, padrao, passo) {
|
|
1389
|
+
var i = inp(k[campo] != null ? k[campo] : padrao, function (v) {
|
|
1390
|
+
mudar(function () {
|
|
1391
|
+
if (v == null || v === "") delete k[campo];
|
|
1392
|
+
else k[campo] = v;
|
|
1393
|
+
});
|
|
1394
|
+
}, "number");
|
|
1395
|
+
if (passo) i.step = passo;
|
|
1396
|
+
g.appendChild(linha(rotulo, i));
|
|
951
1397
|
}
|
|
1398
|
+
|
|
1399
|
+
var tSeg = inp(+((k.t || 0) * (cena().duracao || 4)).toFixed(2), function (v) {
|
|
1400
|
+
mudar(function () {
|
|
1401
|
+
k.t = Math.max(0, Math.min(1, (v || 0) / (cena().duracao || 4)));
|
|
1402
|
+
chaves.sort(function (a, b2) { return (a.t || 0) - (b2.t || 0); });
|
|
1403
|
+
});
|
|
1404
|
+
}, "number");
|
|
1405
|
+
tSeg.step = "0.1";
|
|
1406
|
+
g.appendChild(linha("Momento (s)", tSeg));
|
|
1407
|
+
|
|
1408
|
+
campoChave("Deslocar X", "x", 0);
|
|
1409
|
+
campoChave("Deslocar Y", "y", 0);
|
|
1410
|
+
campoChave("Escala", "escala", 1, "0.05");
|
|
1411
|
+
campoChave("Girar (graus)", "giro", 0);
|
|
1412
|
+
campoChave("Opacidade", "opacidade", 1, "0.1");
|
|
1413
|
+
campoChave("Desfoque (px)", "desfoque", 0);
|
|
1414
|
+
|
|
1415
|
+
var acoes = el("div", "opcoes");
|
|
1416
|
+
var bRep = el("button", e.repetir ? "ativo" : "", svgIcone("refazer"));
|
|
1417
|
+
bRep.title = "Repetir sem parar";
|
|
1418
|
+
bRep.onclick = function () { edita("repetir", e.repetir ? "" : true); };
|
|
1419
|
+
var bLin = el("button", e.suavizar === false ? "ativo" : "", svgIcone("linha"));
|
|
1420
|
+
bLin.title = "Movimento constante (sem suavizar)";
|
|
1421
|
+
bLin.onclick = function () { edita("suavizar", e.suavizar === false ? "" : false); };
|
|
1422
|
+
var bDel = el("button", "", svgIcone("lixeira"));
|
|
1423
|
+
bDel.title = "Apagar esta chave";
|
|
1424
|
+
bDel.onclick = function () {
|
|
1425
|
+
mudar(function () { chaves.splice(chaveSel, 1); if (!chaves.length) delete e.chaves; });
|
|
1426
|
+
chaveSel = 0; desenharInspetor();
|
|
1427
|
+
};
|
|
1428
|
+
acoes.appendChild(bRep); acoes.appendChild(bLin); acoes.appendChild(bDel);
|
|
1429
|
+
g.appendChild(acoes);
|
|
1430
|
+
|
|
1431
|
+
ins.appendChild(g);
|
|
952
1432
|
}
|
|
953
1433
|
|
|
954
1434
|
function camposDoTipo(ins, e) {
|
|
@@ -1130,27 +1610,45 @@
|
|
|
1130
1610
|
|
|
1131
1611
|
/* --- 3. escolha da voz, já ranqueada -------------------------------- */
|
|
1132
1612
|
|
|
1613
|
+
// As vozes vêm do servidor, que é quem sintetiza. Cada uma traz a
|
|
1614
|
+
// frequência MEDIDA — quem quer voz de locução escolhe pelo número, não
|
|
1615
|
+
// pelo palpite.
|
|
1133
1616
|
var voz = doc.voz || {};
|
|
1134
|
-
var boas = Narracao.vozes();
|
|
1135
1617
|
var sel2 = el("select", "campo");
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1618
|
+
function encherVozes(lista) {
|
|
1619
|
+
sel2.innerHTML = "";
|
|
1620
|
+
var opAuto = el("option", "", "Padrão (a mais grave)");
|
|
1621
|
+
opAuto.value = "";
|
|
1622
|
+
sel2.appendChild(opAuto);
|
|
1623
|
+
lista.forEach(function (v) {
|
|
1624
|
+
var o = el("option", "", v.nome + " · " + v.tom
|
|
1625
|
+
+ (v.hz ? " · " + v.hz + " Hz" : "")
|
|
1626
|
+
+ (v.instalada ? "" : " · baixa ao usar"));
|
|
1627
|
+
o.value = v.id;
|
|
1628
|
+
if (v.id === voz.voz) o.selected = true;
|
|
1629
|
+
sel2.appendChild(o);
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
encherVozes(VozNeural.vozes());
|
|
1633
|
+
VozNeural.status().then(function () { encherVozes(VozNeural.vozes()); });
|
|
1145
1634
|
sel2.onchange = function () {
|
|
1146
1635
|
mudar(function () { doc.voz = doc.voz || {}; doc.voz.voz = sel2.value; });
|
|
1147
1636
|
Narracao.falar("Assim que esta voz vai narrar o seu vídeo.",
|
|
1148
|
-
{ voz: sel2.value, velocidade: voz.velocidade });
|
|
1637
|
+
{ voz: sel2.value, grave: voz.grave, velocidade: voz.velocidade });
|
|
1149
1638
|
};
|
|
1150
1639
|
gn.appendChild(linha("Voz", sel2));
|
|
1151
1640
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1641
|
+
var bGrave = el("button", "btn" + (voz.grave ? " primo" : ""),
|
|
1642
|
+
(voz.grave ? "✓ " : "") + "Registro grave, de cinema");
|
|
1643
|
+
bGrave.style.cssText = "width:100%;margin:2px 0 8px";
|
|
1644
|
+
bGrave.title = "Abaixa o tom uns 10% sem mudar a duração";
|
|
1645
|
+
bGrave.onclick = function () {
|
|
1646
|
+
mudar(function () { doc.voz = doc.voz || {}; doc.voz.grave = !voz.grave; });
|
|
1647
|
+
};
|
|
1648
|
+
gn.appendChild(bGrave);
|
|
1649
|
+
|
|
1650
|
+
gn.appendChild(linha("Velocidade", inp(voz.velocidade != null ? voz.velocidade : 1,
|
|
1651
|
+
function (v) { mudar(function () { doc.voz = doc.voz || {}; doc.voz.velocidade = v || 1; }); },
|
|
1154
1652
|
"number")));
|
|
1155
1653
|
|
|
1156
1654
|
if (F.tempo) {
|
|
@@ -1160,24 +1658,41 @@
|
|
|
1160
1658
|
gn.appendChild(bTodas);
|
|
1161
1659
|
}
|
|
1162
1660
|
|
|
1163
|
-
/* --- 4.
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
"
|
|
1179
|
-
|
|
1180
|
-
|
|
1661
|
+
/* --- 4. o estado real do motor -------------------------------------- */
|
|
1662
|
+
|
|
1663
|
+
// Isto aqui mandava o usuário em "Ajustes do Sistema → Acessibilidade →
|
|
1664
|
+
// ... baixe as vozes da Apple". Era verdade quando a narração dependia
|
|
1665
|
+
// do TTS do sistema. Hoje a síntese é neural e roda no servidor, então o
|
|
1666
|
+
// texto virou instrução para o lugar errado — e num Windows, para um
|
|
1667
|
+
// lugar que nem existe. O que fica é o estado de verdade do motor.
|
|
1668
|
+
var aviso = el("div", "dica-voz");
|
|
1669
|
+
aviso.style.display = "none";
|
|
1670
|
+
gn.appendChild(aviso);
|
|
1671
|
+
VozNeural.status(true).then(function (s) {
|
|
1672
|
+
var p = s.piper || {};
|
|
1673
|
+
if (p.pronta) return;
|
|
1674
|
+
aviso.style.display = "";
|
|
1675
|
+
if (p.baixando && p.baixando.ativo) {
|
|
1676
|
+
aviso.innerHTML = "<b>Preparando a voz neural…</b><p>" + p.baixando.etapa
|
|
1677
|
+
+ " · " + p.baixando.pct + "%. Enquanto isso a narração sai por uma voz "
|
|
1678
|
+
+ "mais simples; o vídeo não fica mudo.</p>";
|
|
1679
|
+
setTimeout(function () { if (document.body.contains(aviso)) desenharInspetor(); }, 3000);
|
|
1680
|
+
} else if (!p.suportado) {
|
|
1681
|
+
aviso.innerHTML = "<b>Sem voz neural para este sistema.</b>"
|
|
1682
|
+
+ "<p>A narração sai pela voz de reserva. Gravar a sua própria voz, no botão "
|
|
1683
|
+
+ "acima, continua sendo o melhor resultado.</p>";
|
|
1684
|
+
} else {
|
|
1685
|
+
aviso.innerHTML = "<b>A voz neural ainda não foi baixada.</b>"
|
|
1686
|
+
+ "<p>São ~90 MB, uma vez só, e depois funciona sem internet.</p>";
|
|
1687
|
+
var bJa = el("button", "btn primo", "Baixar agora");
|
|
1688
|
+
bJa.style.cssText = "width:100%;margin-top:8px";
|
|
1689
|
+
bJa.onclick = function () {
|
|
1690
|
+
bJa.textContent = "baixando…";
|
|
1691
|
+
VozNeural.instalar(voz.voz).then(function () { desenharInspetor(); });
|
|
1692
|
+
};
|
|
1693
|
+
aviso.appendChild(bJa);
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1181
1696
|
|
|
1182
1697
|
ins.appendChild(gn);
|
|
1183
1698
|
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
ferramentas.js — o que cada ferramenta oferece na interface.
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Os dez que o render.css define. A lista tinha seis: quatro temas existiam
|
|
6
|
+
// no CSS, a IA podia usá-los, e o editor não os oferecia a ninguém.
|
|
7
|
+
var TEMAS = ["meia-noite", "claro", "papel", "neon", "brasa", "floresta",
|
|
8
|
+
"retro", "oceano", "doce", "mono"];
|
|
6
9
|
|
|
7
10
|
var PALETA = ["#ff5a36", "#ffb03a", "#f4e04d", "#2fbf7f", "#1f8fe0", "#8b5cf6",
|
|
8
11
|
"#f472b6", "#ffffff", "#9aa4b2", "#111318"];
|
package/studio/web/js/render.js
CHANGED
|
@@ -70,13 +70,23 @@ function aplicarFundo(no, fundo) {
|
|
|
70
70
|
/* --------------------------------------------------------------- gráficos */
|
|
71
71
|
|
|
72
72
|
function svgGrafico(e, l, a) {
|
|
73
|
-
|
|
73
|
+
/* Os dados podem chegar como "dados" ou como "itens", e o tipo como
|
|
74
|
+
"grafico", "estilo" ou "tipo".
|
|
75
|
+
|
|
76
|
+
O nome certo é `dados` + `grafico`, e é o que a spec ensina. Mas quem
|
|
77
|
+
escreve um gráfico — a IA ou uma pessoa — chuta `itens`, porque é o
|
|
78
|
+
campo de TODOS os outros elementos da mesma spec (lista, cartoes,
|
|
79
|
+
numeros usam `itens`). Com o nome trocado esta função devolvia string
|
|
80
|
+
vazia: o gráfico simplesmente não existia na tela, sem erro nenhum.
|
|
81
|
+
Aceitar as duas grafias custa uma linha e evita a falha muda. */
|
|
82
|
+
var bruto = e.dados || e.itens || [];
|
|
83
|
+
var dados = bruto.map(function (d) {
|
|
74
84
|
return Array.isArray(d) ? { r: d[0], v: +d[1] || 0 } : { r: d.rotulo, v: +d.valor || 0 };
|
|
75
85
|
});
|
|
76
86
|
if (!dados.length) return "";
|
|
77
87
|
var cores = e.cores || ["var(--c-tom)", "var(--c-tom2)", "#7c9cff", "#2fbf7f", "#f472b6", "#facc15"];
|
|
78
88
|
var max = Math.max.apply(null, dados.map(function (d) { return d.v; })) || 1;
|
|
79
|
-
var tipo = e.grafico || "barras";
|
|
89
|
+
var tipo = e.grafico || e.estilo || e.tipoGrafico || "barras";
|
|
80
90
|
var pad = { e: 8, d: 8, t: 14, b: 42 };
|
|
81
91
|
var iw = l - pad.e - pad.d, ih = a - pad.t - pad.b;
|
|
82
92
|
var p = ['<svg viewBox="0 0 ' + l + " " + a + '" width="' + l + '" height="' + a + '">'];
|
|
@@ -140,9 +150,79 @@ function svgGrafico(e, l, a) {
|
|
|
140
150
|
var TIPOS_CONHECIDOS = { texto: 1, lista: 1, cartoes: 1, numeros: 1, tabela: 1, codigo: 1,
|
|
141
151
|
citacao: 1, forma: 1, imagem: 1, icone: 1, grafico: 1, no: 1, conector: 1 };
|
|
142
152
|
|
|
153
|
+
/* ── chaves de animação ──────────────────────────────────────────────────
|
|
154
|
+
Transforma a lista de instantes de um elemento numa regra @keyframes de
|
|
155
|
+
verdade e prende ela no elemento.
|
|
156
|
+
|
|
157
|
+
Os nomes precisam ser únicos por página: o mesmo documento aparece várias
|
|
158
|
+
vezes ao mesmo tempo (palco, miniaturas do painel, clipes da tira), e
|
|
159
|
+
dois @keyframes de mesmo nome com conteúdo diferente se atropelariam —
|
|
160
|
+
a última regra injetada venceria em TODAS as cópias. */
|
|
161
|
+
var _nChave = 0;
|
|
162
|
+
|
|
163
|
+
function _numero(v, padrao) {
|
|
164
|
+
return typeof v === "number" && isFinite(v) ? v : padrao;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function aplicarChaves(no, e, ctx) {
|
|
168
|
+
var chaves = e.chaves.slice().sort(function (a, b) {
|
|
169
|
+
return _numero(a.t, 0) - _numero(b.t, 0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
var nome = "ch" + (++_nChave);
|
|
173
|
+
var passos = chaves.map(function (k) {
|
|
174
|
+
var pct = Math.max(0, Math.min(1, _numero(k.t, 0))) * 100;
|
|
175
|
+
var mov = [];
|
|
176
|
+
// x e y de uma chave são DESLOCAMENTO a partir de onde o elemento está.
|
|
177
|
+
// Assim a chave continua valendo se a pessoa arrastar o elemento no
|
|
178
|
+
// palco depois — o contrário obrigaria a refazer todas as chaves.
|
|
179
|
+
var dx = _numero(k.x, 0), dy = _numero(k.y, 0);
|
|
180
|
+
if (dx || dy) mov.push("translate(" + dx + "px," + dy + "px)");
|
|
181
|
+
var esc = _numero(k.escala, 1);
|
|
182
|
+
if (esc !== 1) mov.push("scale(" + esc + ")");
|
|
183
|
+
var giro = _numero(k.giro, 0);
|
|
184
|
+
if (giro) mov.push("rotate(" + giro + "deg)");
|
|
185
|
+
|
|
186
|
+
var linhas = ["transform:" + (mov.length ? mov.join(" ") : "none")];
|
|
187
|
+
var op = _numero(k.opacidade, 1);
|
|
188
|
+
linhas.push("opacity:" + op);
|
|
189
|
+
if (typeof k.desfoque === "number" && k.desfoque > 0) {
|
|
190
|
+
linhas.push("filter:blur(" + k.desfoque + "px)");
|
|
191
|
+
}
|
|
192
|
+
return pct.toFixed(3) + "%{" + linhas.join(";") + "}";
|
|
193
|
+
}).join("");
|
|
194
|
+
|
|
195
|
+
// A regra vai na RAIZ da cena, não dentro do elemento.
|
|
196
|
+
//
|
|
197
|
+
// Presa no próprio elemento ela era apagada segundos depois: quem desenha
|
|
198
|
+
// o conteúdo (texto, lista, número) escreve o innerHTML e leva junto tudo
|
|
199
|
+
// que estivesse ali. O elemento continuava com `animation-name: ch1`
|
|
200
|
+
// apontando para uma regra que não existia mais — a animação simplesmente
|
|
201
|
+
// não acontecia, sem erro nenhum no console. A cena só recebe filhos, e
|
|
202
|
+
// por isso a regra sobrevive lá.
|
|
203
|
+
var folha = document.createElement("style");
|
|
204
|
+
folha.textContent = "@keyframes " + nome + "{" + passos + "}";
|
|
205
|
+
(ctx.raiz || no).appendChild(folha);
|
|
206
|
+
|
|
207
|
+
var dur = _numero(ctx.duracao, 4);
|
|
208
|
+
no.style.animationName = nome;
|
|
209
|
+
no.style.animationDuration = dur + "s";
|
|
210
|
+
no.style.animationTimingFunction = e.suavizar === false ? "linear" : "cubic-bezier(.4,0,.2,1)";
|
|
211
|
+
no.style.animationFillMode = "both";
|
|
212
|
+
no.style.animationIterationCount = e.repetir ? "infinite" : "1";
|
|
213
|
+
}
|
|
214
|
+
|
|
143
215
|
function renderElemento(e, ctx) {
|
|
144
216
|
var no = document.createElement("div");
|
|
145
217
|
var tipo = e.tipo || "texto";
|
|
218
|
+
/* Qual elemento do documento é este nó.
|
|
219
|
+
|
|
220
|
+
O editor precisava saber, e antes deduzia pela ordem dos nós na tela.
|
|
221
|
+
A dedução estava errada: os layouts de fluxo separam cabeçalho de corpo
|
|
222
|
+
e desenham o cabeçalho primeiro, então a ordem na tela NÃO é a ordem do
|
|
223
|
+
documento. O efeito era clicar num texto e o editor selecionar outro —
|
|
224
|
+
e mexer no elemento errado. O índice vem carimbado daqui. */
|
|
225
|
+
if (ctx && ctx.ordem && ctx.ordem.has(e)) no.dataset.i = ctx.ordem.get(e);
|
|
146
226
|
/* Tipo inventado ("titulo", "subtitulo"...) caía num default sem estilo:
|
|
147
227
|
texto de 16px num canvas de 1920 — invisível — ou div vazio quando o
|
|
148
228
|
conteúdo estava noutro campo. O servidor novo recusa; documentos velhos
|
|
@@ -178,11 +258,27 @@ function renderElemento(e, ctx) {
|
|
|
178
258
|
|
|
179
259
|
/* animação */
|
|
180
260
|
var an = e.anim || ctx.animPadrao;
|
|
181
|
-
if (an && an !== "nenhuma") {
|
|
261
|
+
if (an && an !== "nenhuma" && !(e.chaves && e.chaves.length > 1)) {
|
|
182
262
|
no.setAttribute("data-an", typeof an === "string" ? an : (an.entrada || "subir"));
|
|
183
263
|
no.style.setProperty("--atraso", (typeof an === "object" && an.atraso != null ? an.atraso : ctx.i * 0.09 + 0.1));
|
|
184
264
|
}
|
|
185
265
|
|
|
266
|
+
/* ── chaves de animação ────────────────────────────────────────────────
|
|
267
|
+
Antes um elemento só sabia ENTRAR: escolhia-se um efeito de entrada e
|
|
268
|
+
acabava ali. Não dava para fazer nada percorrer a cena, crescer devagar
|
|
269
|
+
ou sumir no fim — que é o que separa animação de slide com efeito.
|
|
270
|
+
|
|
271
|
+
Uma chave é um instante: `{t: 0.5, x: 200, escala: 1.2}` diz onde o
|
|
272
|
+
elemento está na metade da cena. Duas ou mais viram uma animação CSS de
|
|
273
|
+
verdade, gerada aqui, com a duração da cena. O navegador interpola;
|
|
274
|
+
nós só descrevemos os pontos.
|
|
275
|
+
|
|
276
|
+
Quem tem chaves não recebe animação de entrada: as duas brigariam pelo
|
|
277
|
+
mesmo transform, e a chave é a mais específica das duas. */
|
|
278
|
+
if (ctx.animar && e.chaves && e.chaves.length > 1) {
|
|
279
|
+
aplicarChaves(no, e, ctx);
|
|
280
|
+
}
|
|
281
|
+
|
|
186
282
|
switch (tipo) {
|
|
187
283
|
case "texto": {
|
|
188
284
|
var papel = PAPEIS[e.papel] || PAPEIS.corpo;
|
|
@@ -560,7 +656,21 @@ function renderCena(cena, doc, op) {
|
|
|
560
656
|
// tela vertical pede tipografia maior: os padrões são calibrados em 1920x1080
|
|
561
657
|
var escalaTipo = H > W ? 1.35 : 1;
|
|
562
658
|
no.style.setProperty("--esc", escalaTipo);
|
|
563
|
-
var ctx = {
|
|
659
|
+
var ctx = {
|
|
660
|
+
i: 0, escalaTipo: escalaTipo,
|
|
661
|
+
animPadrao: op.animar ? (cena.anim || "subir") : null,
|
|
662
|
+
// as chaves precisam saber quanto dura a cena para virar segundos
|
|
663
|
+
animar: !!op.animar,
|
|
664
|
+
duracao: cena.duracao || 4,
|
|
665
|
+
raiz: no,
|
|
666
|
+
// objeto do elemento -> posição dele em cena.elementos, tirada ANTES de
|
|
667
|
+
// qualquer separação por layout
|
|
668
|
+
ordem: (function () {
|
|
669
|
+
var m = new Map();
|
|
670
|
+
(cena.elementos || []).forEach(function (e, i) { m.set(e, i); });
|
|
671
|
+
return m;
|
|
672
|
+
})(),
|
|
673
|
+
};
|
|
564
674
|
|
|
565
675
|
if (layout === "livre") {
|
|
566
676
|
fluxo.style.padding = "0";
|