primocode 8.42.2 → 9.0.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 (102) hide show
  1. package/README.md +41 -37
  2. package/bin/primocode.js +598 -360
  3. package/lib/acervo.js +8 -1
  4. package/lib/act.js +205 -114
  5. package/lib/api.js +35 -4
  6. package/lib/boas-vindas.js +31 -192
  7. package/lib/catalogo.js +60 -60
  8. package/lib/config.js +1 -1
  9. package/lib/conta.js +3 -24
  10. package/lib/diff.js +161 -0
  11. package/lib/diretor.js +37 -47
  12. package/lib/entrada.js +545 -0
  13. package/lib/especialistas.js +2 -2
  14. package/lib/estudio/assets.js +343 -0
  15. package/lib/estudio/identidade.js +560 -0
  16. package/lib/estudio/index.js +565 -0
  17. package/lib/estudio/plano.js +327 -0
  18. package/lib/estudio/projeto.js +246 -0
  19. package/lib/estudio/remotion.js +606 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +13 -1
  23. package/lib/marca.js +4 -1
  24. package/lib/marketplace.js +189 -0
  25. package/lib/permissao.js +137 -0
  26. package/lib/registro.js +229 -0
  27. package/lib/skills.js +329 -0
  28. package/lib/tools.js +51 -181
  29. package/lib/ui.js +7 -3
  30. package/lib/voz.js +239 -0
  31. package/package.json +12 -3
  32. package/skills/acessibilidade/SKILL.md +28 -0
  33. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  34. package/skills/depurar-de-verdade/SKILL.md +29 -0
  35. package/skills/editar-meu-video/SKILL.md +59 -0
  36. package/skills/escrever-em-portugues/SKILL.md +28 -0
  37. package/skills/git-limpo/SKILL.md +28 -0
  38. package/skills/motion-de-primeira/SKILL.md +62 -0
  39. package/skills/pagina-que-converte/SKILL.md +28 -0
  40. package/skills/performance-web/SKILL.md +28 -0
  41. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  42. package/skills/slides-que-prendem/SKILL.md +40 -0
  43. package/studio/edge.py +16 -66
  44. package/studio/narrar.py +274 -0
  45. package/studio/pessoa.py +167 -0
  46. package/voz/primo_voz/__init__.py +0 -0
  47. package/voz/primo_voz/__main__.py +325 -0
  48. package/voz/primo_voz/escuta.py +235 -0
  49. package/voz/primo_voz/orbe.py +216 -0
  50. package/lib/audio-livre.js +0 -177
  51. package/lib/chaves.js +0 -149
  52. package/lib/claude-engine.js +0 -631
  53. package/lib/codex-engine.js +0 -208
  54. package/lib/remotion.js +0 -663
  55. package/lib/studio.js +0 -960
  56. package/studio/PRIMOCODE.md +0 -58
  57. package/studio/README.md +0 -154
  58. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  59. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  60. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  61. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  62. package/studio/ferramentas/00-protocolo.md +0 -125
  63. package/studio/ferramentas/01-modelos.md +0 -270
  64. package/studio/ferramentas/README.md +0 -32
  65. package/studio/ferramentas/corte.md +0 -919
  66. package/studio/ferramentas/grade.md +0 -92
  67. package/studio/ferramentas/prisma.md +0 -107
  68. package/studio/ferramentas/prosa.md +0 -86
  69. package/studio/ferramentas/tela.md +0 -93
  70. package/studio/ferramentas/traco.md +0 -79
  71. package/studio/server.py +0 -2254
  72. package/studio/studio.log +0 -57
  73. package/studio/voz.py +0 -388
  74. package/studio/web/analise.html +0 -52
  75. package/studio/web/core.css +0 -1167
  76. package/studio/web/fontes/bebas.woff2 +0 -0
  77. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  78. package/studio/web/fontes/inter-400.woff2 +0 -0
  79. package/studio/web/fontes/inter-700.woff2 +0 -0
  80. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  81. package/studio/web/fontes/playfair-700.woff2 +0 -0
  82. package/studio/web/fontes.css +0 -23
  83. package/studio/web/home.html +0 -217
  84. package/studio/web/js/analise.js +0 -510
  85. package/studio/web/js/audio.js +0 -607
  86. package/studio/web/js/editor.js +0 -1802
  87. package/studio/web/js/exportar.js +0 -222
  88. package/studio/web/js/ferramentas.js +0 -108
  89. package/studio/web/js/icones.js +0 -120
  90. package/studio/web/js/midia.js +0 -157
  91. package/studio/web/js/narracao.js +0 -325
  92. package/studio/web/js/player.js +0 -903
  93. package/studio/web/js/render-grade.js +0 -243
  94. package/studio/web/js/render-prosa.js +0 -104
  95. package/studio/web/js/render.js +0 -1852
  96. package/studio/web/js/sfx.js +0 -233
  97. package/studio/web/js/video.js +0 -464
  98. package/studio/web/js/voz-neural.js +0 -152
  99. package/studio/web/render.css +0 -1352
  100. package/studio/web/tf.html +0 -15
  101. package/studio/web/tool.html +0 -78
  102. package/studio/web/view.html +0 -27
@@ -1,1802 +0,0 @@
1
- /* ==========================================================================
2
- editor.js — o estúdio manual. Uma pessoa mexe aqui no que a IA criou.
3
- ========================================================================== */
4
-
5
- (function () {
6
- "use strict";
7
-
8
- var $ = function (s) { return document.querySelector(s); };
9
- var partes = location.pathname.split("/");
10
- var SLUG = partes[2], ID = partes[3];
11
- var F = FERR[SLUG] || FERR.prisma;
12
-
13
- var ARQ = null, doc = null;
14
- var iCena = 0, iAba = 0, sel = null; // sel = índice do elemento/bloco
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
- // 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"]],
33
- transicao: [["", "Nenhuma (corte seco)"], ["fade", "Suave"], ["deslizar", "Deslizar para o lado"],
34
- ["subir", "Subir"], ["zoom", "Aproximar"], ["limpar", "Varrer"],
35
- ["persiana", "Persiana"], ["flash", "Flash"]],
36
- fundo: [["", "Sem fundo"], ["malha", "Malha"], ["brilho", "Brilho"], ["grade", "Grade"],
37
- ["pontos", "Pontos"], ["suave", "Suave"], ["vinheta", "Vinheta"], ["aurora", "Aurora"],
38
- ["nevoa", "Névoa"], ["ondas", "Ondas"], ["raios", "Raios"], ["xadrez", "Xadrez"]],
39
- acabamento: [["", "Nenhum"], ["grao", "Granulado"], ["vinheta", "Vinheta"],
40
- ["scanlines", "Linhas de TV"], ["luz", "Vazamento de luz"], ["brilho", "Brilho"],
41
- ["moldura", "Moldura"]],
42
- animacao: [["", "Nenhuma"], ["subir", "Subir"], ["surgir", "Surgir"],
43
- ["esquerda", "Da esquerda"], ["direita", "Da direita"], ["zoom", "Aproximar"],
44
- ["desfoque", "Sair do desfoque"], ["cair", "Cair"], ["girar", "Girar"],
45
- ["saltar", "Saltar"], ["revelar", "Revelar"], ["cortina", "Cortina"],
46
- ["elastico", "Elástico"], ["palavra", "Palavra por palavra"],
47
- ["nenhuma", "Travada (sem entrada)"]],
48
- };
49
-
50
- /* Menu com nome legível. Aceita "valor" ou ["valor", "Nome"]. */
51
- function menu(opcoes, atual, aoMudar) {
52
- var s = el("select", "campo");
53
- opcoes.forEach(function (o) {
54
- var valor = Array.isArray(o) ? o[0] : o;
55
- var nome = Array.isArray(o) ? o[1] : String(o);
56
- var op = el("option", "", nome);
57
- op.value = valor;
58
- if (String(valor) === String(atual == null ? "" : atual)) op.selected = true;
59
- s.appendChild(op);
60
- });
61
- s.onchange = function () { aoMudar(s.value); };
62
- return s;
63
- }
64
-
65
- /* ---------------------------------------------------------------- arrastar
66
- Reordenar não existia. As camadas e as cenas só respondiam a clique, e
67
- quem tentava arrastar — que é o gesto óbvio — não acontecia nada. Este
68
- é o mesmo comportamento para as duas listas: pega, mostra onde vai cair,
69
- solta, reordena e salva. */
70
- function arrastavel(no, indice, mover) {
71
- no.draggable = true;
72
- no.addEventListener("dragstart", function (ev) {
73
- ev.dataTransfer.effectAllowed = "move";
74
- ev.dataTransfer.setData("text/plain", String(indice));
75
- no.classList.add("arrastando");
76
- });
77
- no.addEventListener("dragend", function () {
78
- no.classList.remove("arrastando");
79
- Array.prototype.forEach.call(document.querySelectorAll(".alvo-solta"),
80
- function (n) { n.classList.remove("alvo-solta"); });
81
- });
82
- no.addEventListener("dragover", function (ev) {
83
- ev.preventDefault();
84
- ev.dataTransfer.dropEffect = "move";
85
- no.classList.add("alvo-solta");
86
- });
87
- no.addEventListener("dragleave", function () { no.classList.remove("alvo-solta"); });
88
- no.addEventListener("drop", function (ev) {
89
- ev.preventDefault();
90
- ev.stopPropagation();
91
- no.classList.remove("alvo-solta");
92
- var de = parseInt(ev.dataTransfer.getData("text/plain"), 10);
93
- if (isNaN(de) || de === indice) return;
94
- mover(de, indice);
95
- });
96
- }
97
-
98
- /* Tira o item de uma posição e põe na outra, na mesma lista. */
99
- function reordenar(lista, de, para) {
100
- var item = lista.splice(de, 1)[0];
101
- lista.splice(para, 0, item);
102
- }
103
-
104
- function nomeDaOpcao(opcoes, valor) {
105
- for (var i = 0; i < opcoes.length; i++) {
106
- if (String(opcoes[i][0]) === String(valor)) return opcoes[i][1];
107
- }
108
- return String(valor);
109
- }
110
-
111
- /* ----------------------------------------------------------------- modelos
112
- Cena pronta, com texto de exemplo, para pôr e trocar as palavras.
113
-
114
- "Nova cena" dava uma cena VAZIA, e cena vazia é a tela em branco: quem
115
- abre não sabe por onde começar e o resultado sai desalinhado do resto.
116
- Estes modelos são os mesmos arranjos que a spec ensina à IA — a pessoa
117
- agora alcança os mesmos, com um clique. */
118
- var MODELOS = [
119
- { id: "capa", nome: "Abertura", para: ["corte", "prisma", "tela"],
120
- cena: { layout: "capa", duracao: 4, fundo: "malha", transicao: "fade", elementos: [
121
- { tipo: "texto", papel: "chapeu", texto: "NOME DA EMPRESA", an: "surgir" },
122
- { tipo: "texto", papel: "titulo", texto: "O título que abre", an: "subir" },
123
- { tipo: "texto", papel: "apoio", texto: "Uma frase que explica do que se trata.", an: "subir" }] } },
124
- { id: "titulo-lista", nome: "Título e tópicos", para: ["corte", "prisma"],
125
- cena: { layout: "titulo", duracao: 6, transicao: "deslizar", elementos: [
126
- { tipo: "texto", papel: "titulo", texto: "Uma ideia por cena" },
127
- { tipo: "lista", itens: ["Primeiro ponto", "Segundo ponto", "Terceiro ponto"],
128
- revelar: true, intervalo: 0.9 }] } },
129
- { id: "numeros", nome: "Números que impressionam", para: ["corte", "prisma", "tela"],
130
- cena: { layout: "centro", duracao: 5, fundo: "brilho", transicao: "zoom", elementos: [
131
- { tipo: "numeros", itens: [
132
- { valor: "3x", rotulo: "mais rápido" },
133
- { valor: "40%", rotulo: "menos custo" },
134
- { valor: "24h", rotulo: "no ar" }], an: "saltar" }] } },
135
- { id: "citacao", nome: "Citação", para: ["corte", "prisma", "tela"],
136
- cena: { layout: "citacao", duracao: 5, tema: "papel", transicao: "limpar", elementos: [
137
- { tipo: "citacao", texto: "A frase que alguém disse e vale repetir.",
138
- autor: "Quem disse", an: "desfoque" }] } },
139
- { id: "cartoes", nome: "Três cartões", para: ["corte", "prisma"],
140
- cena: { layout: "titulo", duracao: 6, transicao: "subir", elementos: [
141
- { tipo: "texto", papel: "titulo", texto: "O que fazemos" },
142
- { tipo: "cartoes", itens: [
143
- { titulo: "Primeiro", texto: "Uma linha explicando." },
144
- { titulo: "Segundo", texto: "Uma linha explicando." },
145
- { titulo: "Terceiro", texto: "Uma linha explicando." }], revelar: true }] } },
146
- { id: "secao", nome: "Divisor de seção", para: ["corte", "prisma"],
147
- cena: { layout: "secao", duracao: 2.5, fundo: "aurora", transicao: "flash", elementos: [
148
- { tipo: "texto", papel: "titulo", texto: "Parte dois", an: "zoom" }] } },
149
- { id: "foto", nome: "Foto com frase", para: ["corte", "prisma", "tela"],
150
- cena: { layout: "capa", duracao: 5, acabamento: "vinheta", transicao: "fade",
151
- fundo: "vinheta", elementos: [
152
- { tipo: "texto", papel: "titulo", texto: "A frase por cima da imagem", an: "subir" }] } },
153
- { id: "grafico", nome: "Gráfico", para: ["corte", "prisma"],
154
- cena: { layout: "titulo", duracao: 6, elementos: [
155
- { tipo: "texto", papel: "titulo", texto: "Como evoluiu" },
156
- { tipo: "grafico", grafico: "barras",
157
- dados: [["Jan", 30], ["Fev", 52], ["Mar", 71], ["Abr", 96]] }] } },
158
- { id: "fecho", nome: "Fecho / chamada", para: ["corte", "prisma", "tela"],
159
- cena: { layout: "centro", duracao: 4, fundo: "raios", transicao: "zoom", elementos: [
160
- { tipo: "texto", papel: "titulo", texto: "Vamos conversar", an: "zoom" },
161
- { tipo: "texto", papel: "apoio", texto: "seu@email.com", an: "surgir" }] } },
162
- ];
163
-
164
- function modelosDaFerramenta() {
165
- return MODELOS.filter(function (m) { return m.para.indexOf(SLUG) !== -1; });
166
- }
167
-
168
- /* A galeria: cada modelo desenhado de verdade, com o renderizador do
169
- produto. Miniatura falsa mentiria sobre o resultado. */
170
- function abrirModelos() {
171
- var fundo = el("div", "folha on");
172
- var caixa = el("div", "caixa larga");
173
- caixa.appendChild(el("h3", "", "Escolha um começo"));
174
- caixa.appendChild(el("p", "sub",
175
- "Cada um entra como cena nova, com texto de exemplo para você trocar."));
176
-
177
- var grade = el("div", "modelos");
178
- var W = doc.largura || 1920, H = doc.altura || 1080;
179
- var pendentes = [];
180
-
181
- // Primeiro a cena em branco: quem quer começar do zero não deve ter de
182
- // procurar.
183
- var vazio = el("button", "modelo");
184
- var cxV = el("div", "caixa-mini vazia", svgIcone("mais"));
185
- vazio.appendChild(cxV);
186
- vazio.appendChild(el("span", "", "Cena em branco"));
187
- vazio.onclick = function () { fechar(); novaCena(null); };
188
- grade.appendChild(vazio);
189
-
190
- modelosDaFerramenta().forEach(function (m) {
191
- var b = el("button", "modelo");
192
- var cx = el("div", "caixa-mini");
193
- var mini = renderCena(m.cena, doc, {});
194
- cx.appendChild(mini);
195
- b.appendChild(cx);
196
- b.appendChild(el("span", "", m.nome));
197
- b.onclick = function () { fechar(); novaCena(m); };
198
- grade.appendChild(b);
199
- pendentes.push([cx, mini]);
200
- });
201
- caixa.appendChild(grade);
202
-
203
- var rodape = el("div", "rodape");
204
- var bFechar = el("button", "btn", "Fechar");
205
- bFechar.onclick = fechar;
206
- rodape.appendChild(bFechar);
207
- caixa.appendChild(rodape);
208
- fundo.appendChild(caixa);
209
- document.body.appendChild(fundo);
210
-
211
- // A escala só dá para calcular depois que o nó está no documento e tem
212
- // largura de verdade.
213
- pendentes.forEach(function (p) {
214
- var larg = p[0].clientWidth || 220;
215
- p[0].style.height = Math.round(larg * H / W) + "px";
216
- p[1].style.transform = "scale(" + (larg / W) + ")";
217
- });
218
-
219
- function fechar() { fundo.remove(); document.removeEventListener("keydown", esc); }
220
- function esc(ev) { if (ev.key === "Escape") fechar(); }
221
- document.addEventListener("keydown", esc);
222
- fundo.onclick = function (ev) { if (ev.target === fundo) fechar(); };
223
- }
224
-
225
- function novaCena(modelo) {
226
- var base = modelo && modelo.cena
227
- ? JSON.parse(JSON.stringify(modelo.cena))
228
- : { layout: F.layoutPadrao, duracao: 4, elementos: [] };
229
- if (!F.tempo) delete base.duracao;
230
- mudar(function () {
231
- doc.cenas.splice(iCena + 1, 0, base);
232
- iCena++; sel = null;
233
- });
234
- }
235
-
236
- /* ------------------------------------------------------------ utilidades */
237
-
238
- function el(tag, cls, html) {
239
- var n = document.createElement(tag);
240
- if (cls) n.className = cls;
241
- if (html != null) n.innerHTML = html;
242
- return n;
243
- }
244
-
245
- function cena() { return (doc.cenas || [])[iCena] || { elementos: [] }; }
246
- function itens() {
247
- if (F.modelo === "cena") return cena().elementos || (cena().elementos = []);
248
- if (F.modelo === "documento") return doc.blocos || (doc.blocos = []);
249
- return [];
250
- }
251
- function aba() { return (doc.abas || [])[iAba] || { colunas: [], linhas: [] }; }
252
-
253
- function instantaneo() {
254
- historico.push(JSON.stringify(doc));
255
- if (historico.length > 60) historico.shift();
256
- futuro.length = 0;
257
- }
258
-
259
- function mudar(fn) {
260
- instantaneo();
261
- fn();
262
- desenhar();
263
- agendarSalvar();
264
- }
265
-
266
- function desfazer() {
267
- if (!historico.length) return;
268
- futuro.push(JSON.stringify(doc));
269
- doc = JSON.parse(historico.pop());
270
- ARQ.doc = doc;
271
- sel = null;
272
- desenhar();
273
- agendarSalvar();
274
- }
275
-
276
- function refazer() {
277
- if (!futuro.length) return;
278
- historico.push(JSON.stringify(doc));
279
- doc = JSON.parse(futuro.pop());
280
- ARQ.doc = doc;
281
- sel = null;
282
- desenhar();
283
- agendarSalvar();
284
- }
285
-
286
- /* ---------------------------------------------------------------- salvar */
287
-
288
- function marcar(t, ok) {
289
- var e = $("#estado");
290
- e.textContent = t;
291
- e.classList.toggle("ok", !!ok);
292
- }
293
-
294
- function salvar(saindo) {
295
- if (!sujo) return;
296
- sujo = false;
297
- marcar("salvando…");
298
- fetch("/api/arquivos/" + ID, {
299
- method: "PUT",
300
- keepalive: !!saindo,
301
- headers: { "Content-Type": "application/json" },
302
- body: JSON.stringify({ titulo: $("#titulo").value, doc: doc }),
303
- }).then(function () { if (!sujo) marcar("salvo", true); });
304
- }
305
-
306
- function agendarSalvar() {
307
- sujo = true;
308
- marcar("editando…");
309
- clearTimeout(tSalvar);
310
- tSalvar = setTimeout(function () { salvar(); }, 600);
311
- }
312
-
313
- /* ----------------------------------------------------------------- carga */
314
-
315
- fetch("/api/arquivos/" + ID)
316
- .then(function (r) { return r.json(); })
317
- .then(function (a) {
318
- if (a.erro) { document.body.innerHTML = "<p style='padding:40px'>Arquivo não encontrado.</p>"; return; }
319
- ARQ = a;
320
- doc = a.doc || {};
321
- $("#titulo").value = a.titulo || "";
322
- document.title = (a.titulo || "Editor") + " · " + F.nome;
323
- $("#marca-ferr").textContent = F.nome;
324
- $("#marca-ferr").style.color = F.cor;
325
- montarTrilho();
326
- montarTopo();
327
- desenhar();
328
- marcar("salvo", true);
329
- });
330
-
331
- /* ---------------------------------------------------------------- trilho */
332
-
333
- function montarTrilho() {
334
- var t = $("#trilho");
335
- t.innerHTML = "";
336
- (F.trilho || []).forEach(function (b) {
337
- var btn = el("button", "", svgIcone(b.i));
338
- btn.title = b.r;
339
- btn.onclick = function () { inserir(b); };
340
- t.appendChild(btn);
341
- });
342
- if (F.modelo !== "planilha") {
343
- t.appendChild(el("div", "esp"));
344
- var bm = el("button", "", svgIcone("pasta"));
345
- bm.title = "Imagem do computador";
346
- bm.onclick = inserirImagem;
347
- t.appendChild(bm);
348
- if (F.tempo) {
349
- var bs = el("button", "", svgIcone("play2"));
350
- bs.title = "Trilha sonora";
351
- bs.onclick = function () { $("#folha-trilha").classList.add("on"); desenharTrilha(); };
352
- t.appendChild(bs);
353
- }
354
- }
355
-
356
- t.appendChild(el("div", "esp"));
357
- var und = el("button", "", svgIcone("voltar")); und.title = "Desfazer (⌘Z)"; und.onclick = desfazer;
358
- var red = el("button", "", svgIcone("refazer")); red.title = "Refazer"; red.onclick = refazer;
359
- t.appendChild(und); t.appendChild(red);
360
- }
361
-
362
- /* insere uma imagem do computador na cena ou no bloco atual */
363
- function inserirImagem() {
364
- Midia.escolher({ tipo: "imagem" }).then(function (m) {
365
- if (!m) return;
366
- mudar(function () {
367
- if (F.modelo === "documento") {
368
- itens().push({ tipo: "imagem", src: m.url, legenda: "" });
369
- } else if (cena().layout === "livre") {
370
- itens().push({ tipo: "imagem", src: m.url, x: 120, y: 120, l: 720, a: 480 });
371
- } else {
372
- itens().push({ tipo: "imagem", src: m.url, ajuste: "cobrir" });
373
- }
374
- sel = itens().length - 1;
375
- });
376
- });
377
- }
378
-
379
- function inserir(b) {
380
- if (SLUG === "grade") return inserirGrade(b.k);
381
- mudar(function () {
382
- var novo = JSON.parse(JSON.stringify(b.novo));
383
- itens().push(novo);
384
- sel = itens().length - 1;
385
- });
386
- }
387
-
388
- function inserirGrade(k) {
389
- mudar(function () {
390
- var a = aba();
391
- if (k === "linha") a.linhas.push(a.colunas.map(function () { return ""; }));
392
- if (k === "coluna") {
393
- a.colunas.push("Coluna " + (a.colunas.length + 1));
394
- a.linhas.forEach(function (l) { l.push(""); });
395
- }
396
- if (k === "total") {
397
- var n = a.linhas.length;
398
- a.linhas.push(a.colunas.map(function (_, i) {
399
- return i === 0 ? "Total" : "=SOMA(" + String.fromCharCode(65 + i) + "1:" +
400
- String.fromCharCode(65 + i) + n + ")";
401
- }));
402
- }
403
- if (k === "grafico") {
404
- a.grafico = a.grafico ? null : { tipo: "barras", rotulo: a.colunas[0], valor: a.colunas[1] };
405
- }
406
- });
407
- }
408
-
409
- /* ------------------------------------------------------------------ topo */
410
-
411
- function montarTopo() {
412
- var cx = $("#topo-extra");
413
- cx.innerHTML = "";
414
-
415
- if (F.modelo !== "planilha") {
416
- var tema = el("select", "campo");
417
- tema.style.width = "128px";
418
- TEMAS.forEach(function (t) {
419
- var o = el("option", "", t); o.value = t;
420
- if ((doc.tema || "meia-noite") === t) o.selected = true;
421
- tema.appendChild(o);
422
- });
423
- tema.onchange = function () { mudar(function () { doc.tema = tema.value; }); };
424
- cx.appendChild(tema);
425
- }
426
-
427
- if (F.modelo === "cena" && (F.layouts || []).length > 1) {
428
- var lay = el("select", "campo");
429
- lay.id = "sel-layout";
430
- lay.style.width = "148px";
431
- F.layouts.forEach(function (l) {
432
- var o = el("option", "", l); o.value = l;
433
- lay.appendChild(o);
434
- });
435
- lay.onchange = function () { mudar(function () { cena().layout = lay.value; }); };
436
- cx.appendChild(lay);
437
- }
438
- }
439
-
440
- /* --------------------------------------------------------------- desenho */
441
-
442
- function desenhar() {
443
- desenharPainel();
444
- desenharPalco();
445
- desenharInspetor();
446
- if (F.tempo) desenharTempo();
447
- var lay = $("#sel-layout");
448
- if (lay) lay.value = cena().layout || F.layoutPadrao;
449
- }
450
-
451
- /* painel esquerdo ------------------------------------------------------- */
452
-
453
- function desenharPainel() {
454
- var lista = $("#painel-lista");
455
- lista.innerHTML = "";
456
-
457
- if (F.modelo === "cena") {
458
- // No Corte as cenas já vivem na tira do rodapé. Repetir a mesma lista
459
- // aqui era o pior tipo de duplicação: duas coisas iguais, lado a lado,
460
- // sem dizer qual manda. Aqui ficam só as camadas — que é o que faltava
461
- // ter espaço.
462
- var soCamadas = !!F.tempo;
463
- if (!soCamadas) {
464
- $("#painel-rotulo").textContent = SLUG === "prisma" ? "Slides" : "Pranchas";
465
- var W = doc.largura || 1920, H = doc.altura || 1080;
466
- var minis = [];
467
- (doc.cenas || []).forEach(function (c, i) {
468
- var m = el("div", "miniatura" + (i === iCena ? " ativa" : ""));
469
- var caixa = el("div", "caixa");
470
- var mini = renderCena(c, doc, {});
471
- caixa.appendChild(mini);
472
- m.appendChild(caixa);
473
- m.appendChild(el("div", "num", String(i + 1)));
474
- m.onclick = function () { iCena = i; sel = null; desenhar(); };
475
- arrastavel(m, i, function (de, para) {
476
- mudar(function () { reordenar(doc.cenas, de, para); });
477
- iCena = para; sel = null; desenhar();
478
- });
479
- lista.appendChild(m);
480
- minis.push([caixa, mini]);
481
- });
482
- // a escala vem da largura real da caixa, que depende do painel
483
- minis.forEach(function (par) {
484
- var larg = par[0].clientWidth || 160;
485
- par[0].style.height = Math.round(larg * H / W) + "px";
486
- par[1].style.transform = "scale(" + (larg / W) + ")";
487
- });
488
- }
489
-
490
- var els = cena().elementos || [];
491
- $("#painel-rotulo").textContent = soCamadas ? "Camadas da cena" : $("#painel-rotulo").textContent;
492
- if (!soCamadas && els.length) {
493
- var tit = el("div", "rotulo");
494
- tit.style.cssText = "padding:14px 4px 6px";
495
- tit.textContent = "Camadas";
496
- lista.appendChild(tit);
497
- }
498
- if (!els.length) {
499
- lista.appendChild(el("div", "nada", soCamadas
500
- ? "Cena vazia. Use a barra da esquerda para pôr texto, lista, número ou imagem."
501
- : "Nada nesta prancha ainda."));
502
- }
503
- // De cima para baixo na lista = da frente para trás na tela, que é como
504
- // todo editor de camada se comporta. O documento guarda o contrário.
505
- els.slice().reverse().forEach(function (e2, ordem) {
506
- var i = els.length - 1 - ordem;
507
- var c = el("div", "camada" + (i === sel ? " ativa" : ""),
508
- svgIcone(iconeDe(e2)) + "<span>" + rotuloDe(e2) + "</span>");
509
- c.appendChild(botoesDaCamada(els, i));
510
- c.onclick = function () { sel = i; desenhar(); };
511
- arrastavel(c, i, function (de, para) {
512
- mudar(function () { reordenar(els, de, para); });
513
- sel = para; desenhar();
514
- });
515
- lista.appendChild(c);
516
- });
517
- return;
518
- }
519
-
520
- if (F.modelo === "documento") {
521
- $("#painel-rotulo").textContent = "Blocos";
522
- var blocos = doc.blocos || [];
523
- blocos.forEach(function (b, i) {
524
- var c = el("div", "camada" + (i === sel ? " ativa" : ""),
525
- svgIcone(iconeDe(b)) + "<span>" + rotuloDe(b) + "</span>");
526
- c.appendChild(botoesDaCamada(blocos, i));
527
- c.onclick = function () { sel = i; desenhar(); };
528
- arrastavel(c, i, function (de, para) {
529
- mudar(function () { reordenar(blocos, de, para); });
530
- sel = para; desenhar();
531
- });
532
- lista.appendChild(c);
533
- });
534
- return;
535
- }
536
-
537
- $("#painel-rotulo").textContent = "Abas";
538
- (doc.abas || []).forEach(function (a, i) {
539
- var c = el("div", "camada" + (i === iAba ? " ativa" : ""),
540
- svgIcone("tabela") + "<span>" + (a.nome || "Planilha") + "</span>");
541
- c.onclick = function () { iAba = i; sel = null; desenhar(); };
542
- lista.appendChild(c);
543
- });
544
- }
545
-
546
- /* Subir, descer, duplicar e apagar direto na camada.
547
-
548
- Arrastar é o gesto natural, mas não é o único: num item pequeno o
549
- arrasto erra o alvo, e teclado não arrasta. Os botões fazem o mesmo
550
- trabalho sem depender de pontaria. */
551
- function botoesDaCamada(lista, i) {
552
- var caixa = el("div", "camada-acoes");
553
- function bt(icone, titulo, fn) {
554
- var b = el("button", "", svgIcone(icone));
555
- b.title = titulo;
556
- b.onclick = function (ev) { ev.stopPropagation(); fn(); };
557
- caixa.appendChild(b);
558
- return b;
559
- }
560
- bt("subir", "Trazer para a frente", function () {
561
- if (i >= lista.length - 1) return;
562
- mudar(function () { reordenar(lista, i, i + 1); });
563
- sel = i + 1; desenhar();
564
- }).disabled = i >= lista.length - 1;
565
- bt("descer", "Mandar para trás", function () {
566
- if (i <= 0) return;
567
- mudar(function () { reordenar(lista, i, i - 1); });
568
- sel = i - 1; desenhar();
569
- }).disabled = i <= 0;
570
- bt("duplicar", "Duplicar", function () {
571
- mudar(function () { lista.splice(i + 1, 0, JSON.parse(JSON.stringify(lista[i]))); });
572
- sel = i + 1; desenhar();
573
- });
574
- bt("lixeira", "Apagar", function () {
575
- mudar(function () { lista.splice(i, 1); });
576
- sel = null; desenhar();
577
- });
578
- return caixa;
579
- }
580
-
581
- function iconeDe(e) {
582
- return { texto: "texto", lista: "lista", cartoes: "cartao", numeros: "numero",
583
- tabela: "tabela", codigo: "codigo", citacao: "citacao", forma: "quadrado",
584
- imagem: "imagem", icone: "icone", grafico: "grafico", no: "no",
585
- conector: "conector", titulo: "titulo", secao: "texto", subsecao: "texto",
586
- paragrafo: "texto", numerada: "numero", destaque: "cartao",
587
- divisor: "linha" }[e.tipo] || "quadrado";
588
- }
589
-
590
- function rotuloDe(e) {
591
- var t = e.texto || (e.itens && e.itens[0]) || (e.linhas && e.linhas[0] && e.linhas[0][0]) ||
592
- e.nome || e.src || e.tipo;
593
- t = String(t).replace(/\s+/g, " ").trim();
594
- return (t.length > 22 ? t.slice(0, 22) + "…" : t) || e.tipo;
595
- }
596
-
597
- /* palco ---------------------------------------------------------------- */
598
-
599
- function desenharPalco() {
600
- var wrap = $("#palco-wrap");
601
- wrap.innerHTML = "";
602
-
603
- if (F.modelo === "planilha") return desenharPlanilha(wrap);
604
- if (F.modelo === "documento") return desenharDocumento(wrap);
605
-
606
- var W = doc.largura || 1920, H = doc.altura || 1080;
607
- var disp = wrap.getBoundingClientRect();
608
- escala = Math.min((disp.width - 52) / W, (disp.height - 52) / H, 1);
609
-
610
- var quadro = el("div", "palco");
611
- quadro.id = "quadro";
612
- quadro.style.cssText = "width:" + W * escala + "px;height:" + H * escala + "px;position:relative";
613
-
614
- var cn = renderCena(cena(), doc, {});
615
- cn.style.transform = "scale(" + escala + ")";
616
- cn.style.transformOrigin = "top left";
617
- cn.style.position = "absolute";
618
- quadro.appendChild(cn);
619
- wrap.appendChild(quadro);
620
-
621
- /* seleção e interação */
622
- // Índice do documento -> nó na tela, pelo carimbo que o renderizador
623
- // deixa em data-i.
624
- //
625
- // Antes isto era deduzido pela ordem: o k-ésimo nó era o k-ésimo
626
- // elemento. A dedução quebra em todo layout de fluxo, porque eles
627
- // desenham o cabeçalho (chapéu e título) antes do corpo — a ordem na
628
- // tela não é a do documento. Quem clicava num texto via o editor
629
- // selecionar outro, e editava o errado. Foi medido: nas três camadas da
630
- // cena de teste, as três apontavam para o elemento vizinho.
631
- var lista = cena().elementos || [];
632
- var mapa = lista.map(function () { return null; });
633
- Array.prototype.forEach.call(cn.querySelectorAll(".el[data-i]"), function (no) {
634
- var i = +no.dataset.i;
635
- if (i >= 0 && i < mapa.length && !mapa[i]) mapa[i] = no;
636
- });
637
-
638
- mapa.forEach(function (no, i) {
639
- if (!no) return;
640
- no.style.cursor = "move";
641
- no.addEventListener("mousedown", function (ev) {
642
- ev.stopPropagation();
643
- sel = i;
644
- desenharPainel(); desenharInspetor(); marcarSelecao(quadro, no);
645
- if (lista[i].x != null || lista[i].y != null) arrastar(ev, i, quadro);
646
- });
647
- no.addEventListener("dblclick", function (ev) {
648
- ev.stopPropagation();
649
- editarNoLugar(no, i);
650
- });
651
- });
652
-
653
- quadro.addEventListener("mousedown", function () { sel = null; desenhar(); });
654
- if (sel != null && mapa[sel]) marcarSelecao(quadro, mapa[sel]);
655
-
656
- barraPalco(W, H);
657
- }
658
-
659
- function marcarSelecao(quadro, no) {
660
- var antigos = quadro.querySelectorAll(".selecao,.alca");
661
- Array.prototype.forEach.call(antigos, function (n) { n.remove(); });
662
-
663
- var r = no.getBoundingClientRect(), q = quadro.getBoundingClientRect();
664
- var x = r.left - q.left, y = r.top - q.top;
665
-
666
- var caixa = el("div", "selecao");
667
- caixa.style.cssText = "left:" + x + "px;top:" + y + "px;width:" + r.width + "px;height:" + r.height + "px";
668
- quadro.appendChild(caixa);
669
-
670
- var e2 = (cena().elementos || [])[sel];
671
- if (!e2 || (e2.x == null && e2.y == null)) return;
672
-
673
- [["nw", 0, 0], ["ne", 1, 0], ["sw", 0, 1], ["se", 1, 1]].forEach(function (h) {
674
- var a = el("div", "alca " + h[0]);
675
- a.style.left = (x + h[1] * r.width - 5) + "px";
676
- a.style.top = (y + h[2] * r.height - 5) + "px";
677
- a.addEventListener("mousedown", function (ev) {
678
- ev.stopPropagation(); ev.preventDefault();
679
- redimensionar(ev, sel, h[0], quadro);
680
- });
681
- quadro.appendChild(a);
682
- });
683
- }
684
-
685
- function arrastar(ev, i, quadro) {
686
- ev.preventDefault();
687
- var e2 = (cena().elementos || [])[i];
688
- var x0 = ev.clientX, y0 = ev.clientY;
689
- var ix = e2.x || 0, iy = e2.y || 0;
690
- var no = quadro.querySelectorAll(".el")[i];
691
- instantaneo();
692
-
693
- function mover(m) {
694
- e2.x = Math.round(ix + (m.clientX - x0) / escala);
695
- e2.y = Math.round(iy + (m.clientY - y0) / escala);
696
- if (no) { no.style.left = e2.x + "px"; no.style.top = e2.y + "px"; }
697
- marcarSelecao(quadro, no);
698
- }
699
- function soltar() {
700
- document.removeEventListener("mousemove", mover);
701
- document.removeEventListener("mouseup", soltar);
702
- desenhar(); agendarSalvar();
703
- }
704
- document.addEventListener("mousemove", mover);
705
- document.addEventListener("mouseup", soltar);
706
- }
707
-
708
- function redimensionar(ev, i, canto, quadro) {
709
- var e2 = (cena().elementos || [])[i];
710
- var x0 = ev.clientX, y0 = ev.clientY;
711
- var ix = e2.x || 0, iy = e2.y || 0, il = e2.l || 200, ia = e2.a || 100;
712
- instantaneo();
713
-
714
- function mover(m) {
715
- var dx = (m.clientX - x0) / escala, dy = (m.clientY - y0) / escala;
716
- if (canto.indexOf("e") >= 0) e2.l = Math.max(24, Math.round(il + dx));
717
- if (canto.indexOf("w") >= 0) { e2.l = Math.max(24, Math.round(il - dx)); e2.x = Math.round(ix + dx); }
718
- if (canto.charAt(0) === "s") e2.a = Math.max(24, Math.round(ia + dy));
719
- if (canto.charAt(0) === "n") { e2.a = Math.max(24, Math.round(ia - dy)); e2.y = Math.round(iy + dy); }
720
- desenharPalco();
721
- }
722
- function soltar() {
723
- document.removeEventListener("mousemove", mover);
724
- document.removeEventListener("mouseup", soltar);
725
- desenhar(); agendarSalvar();
726
- }
727
- document.addEventListener("mousemove", mover);
728
- document.addEventListener("mouseup", soltar);
729
- }
730
-
731
- function editarNoLugar(no, i) {
732
- var e2 = itens()[i];
733
- if (!e2 || (e2.tipo !== "texto" && e2.tipo !== "no" && e2.tipo !== "paragrafo" &&
734
- e2.tipo !== "titulo" && e2.tipo !== "secao" && e2.tipo !== "subsecao")) return;
735
- no.contentEditable = "true";
736
- no.style.outline = "2px solid var(--primo)";
737
- no.focus();
738
- document.execCommand && document.execCommand("selectAll", false, null);
739
- no.addEventListener("blur", function () {
740
- no.contentEditable = "false";
741
- no.style.outline = "";
742
- mudar(function () { e2.texto = no.innerText.trim(); });
743
- }, { once: true });
744
- no.addEventListener("keydown", function (ev) {
745
- if (ev.key === "Escape" || (ev.key === "Enter" && !ev.shiftKey)) { ev.preventDefault(); no.blur(); }
746
- ev.stopPropagation();
747
- });
748
- }
749
-
750
- function barraPalco(W, H) {
751
- var b = $("#palco-barra");
752
- b.innerHTML = "";
753
- b.appendChild(el("span", "", F.modelo === "cena"
754
- ? W + " × " + H + " · " + Math.round(escala * 100) + "%"
755
- : ""));
756
- var esp = el("div"); esp.style.flex = "1"; b.appendChild(esp);
757
-
758
- if (F.modelo === "cena") {
759
- var addC = el("button", "btn primo", svgIcone("mais") + " Nova cena");
760
- addC.onclick = abrirModelos;
761
- var dup = el("button", "btn", svgIcone("duplicar"));
762
- dup.title = "Duplicar cena";
763
- dup.onclick = function () {
764
- mudar(function () {
765
- doc.cenas.splice(iCena + 1, 0, JSON.parse(JSON.stringify(cena())));
766
- iCena++;
767
- });
768
- };
769
- var rem = el("button", "btn perigo", svgIcone("lixeira"));
770
- rem.title = "Remover cena";
771
- rem.onclick = function () {
772
- if ((doc.cenas || []).length < 2) return;
773
- mudar(function () { doc.cenas.splice(iCena, 1); iCena = Math.max(0, iCena - 1); sel = null; });
774
- };
775
- var png = el("button", "btn", svgIcone("imagem") + " PNG");
776
- png.title = "Exportar esta cena como imagem";
777
- png.onclick = function () {
778
- var alvo = document.querySelector("#quadro .cena");
779
- if (!alvo) return;
780
- png.textContent = "gerando…";
781
- Exportar.imagem(alvo, {
782
- largura: doc.largura || 1920, altura: doc.altura || 1080,
783
- escala: 2, nome: ($("#titulo").value || "arte") + "-" + (iCena + 1),
784
- }).then(function () {
785
- png.innerHTML = svgIcone("imagem") + " PNG";
786
- }).catch(function (e) {
787
- png.innerHTML = svgIcone("imagem") + " PNG";
788
- alert("não consegui gerar o PNG: " + e.message);
789
- });
790
- };
791
-
792
- b.appendChild(addC); b.appendChild(dup); b.appendChild(rem); b.appendChild(png);
793
- }
794
- }
795
-
796
- /* documento ------------------------------------------------------------ */
797
-
798
- function desenharDocumento(wrap) {
799
- var quadro = el("div", "palco");
800
- quadro.id = "quadro";
801
- var folha = renderProsa(doc);
802
- var disp = wrap.getBoundingClientRect();
803
- escala = Math.min((disp.width - 52) / 794, 1);
804
- folha.style.transform = "scale(" + escala + ")";
805
- folha.style.transformOrigin = "top left";
806
- quadro.style.cssText = "width:" + 794 * escala + "px;position:relative";
807
- quadro.appendChild(folha);
808
- wrap.appendChild(quadro);
809
- wrap.style.alignContent = "start";
810
-
811
- var filhos = Array.prototype.slice.call(folha.children);
812
- var deslocamento = doc.capa ? 2 : 0;
813
- (doc.blocos || []).forEach(function (b, i) {
814
- var no = filhos[i + deslocamento];
815
- if (!no) return;
816
- no.style.cursor = "pointer";
817
- if (i === sel) no.style.outline = "2px solid var(--primo)";
818
- no.addEventListener("click", function (ev) { ev.stopPropagation(); sel = i; desenhar(); });
819
- no.addEventListener("dblclick", function (ev) { ev.stopPropagation(); editarNoLugar(no, i); });
820
- });
821
-
822
- var texto = (doc.blocos || []).map(function (x) {
823
- return [x.texto, (x.itens || []).join(" "), (x.linhas || []).join(" "), x.titulo]
824
- .filter(Boolean).join(" ");
825
- }).join(" ");
826
- var palavras = texto.trim().split(/\s+/).filter(Boolean).length;
827
- var minutos = Math.max(1, Math.round(palavras / 200));
828
-
829
- var b2 = $("#palco-barra");
830
- b2.innerHTML = "";
831
- b2.appendChild(el("span", "", (doc.blocos || []).length + " blocos · " +
832
- palavras.toLocaleString("pt-BR") + " palavras · ~" + minutos + " min de leitura · A4"));
833
- }
834
-
835
- /* planilha ------------------------------------------------------------- */
836
-
837
- function desenharPlanilha(wrap) {
838
- wrap.style.alignContent = "start";
839
- var caixa = el("div");
840
- caixa.style.cssText = "width:100%;max-width:1180px;overflow:auto;border:1px solid var(--linha);border-radius:12px";
841
- var tabela = renderGrade(doc, { aba: iAba });
842
- caixa.appendChild(tabela);
843
- wrap.appendChild(caixa);
844
-
845
- Array.prototype.forEach.call(tabela.querySelectorAll("td[data-l]"), function (td) {
846
- td.contentEditable = "true";
847
- td.addEventListener("focus", function () {
848
- var l = +td.dataset.l, c = +td.dataset.c;
849
- td.textContent = (aba().linhas[l] || [])[c] != null ? aba().linhas[l][c] : "";
850
- });
851
- td.addEventListener("blur", function () {
852
- var l = +td.dataset.l, c = +td.dataset.c;
853
- var v = td.textContent.trim();
854
- var atual = aba().linhas[l][c];
855
- if (String(atual) === v) { desenharPalco(); return; }
856
- mudar(function () {
857
- aba().linhas[l][c] = /^-?[\d.,]+$/.test(v) && v !== "" ? parseFloat(v.replace(",", ".")) : v;
858
- });
859
- });
860
- td.addEventListener("keydown", function (ev) {
861
- if (ev.key === "Enter") { ev.preventDefault(); td.blur(); }
862
- ev.stopPropagation();
863
- });
864
- });
865
-
866
- var b = $("#palco-barra");
867
- b.innerHTML = "";
868
- b.appendChild(el("span", "", aba().linhas.length + " linhas × " + aba().colunas.length + " colunas"));
869
- var esp = el("div"); esp.style.flex = "1"; b.appendChild(esp);
870
- var nova = el("button", "btn", svgIcone("mais") + " Aba");
871
- nova.onclick = function () {
872
- mudar(function () {
873
- doc.abas.push({ nome: "Planilha " + (doc.abas.length + 1), colunas: ["Coluna A", "Coluna B"], linhas: [["", ""]] });
874
- iAba = doc.abas.length - 1;
875
- });
876
- };
877
- b.appendChild(nova);
878
- }
879
-
880
- /* linha do tempo (Corte) ------------------------------------------------ */
881
-
882
- function desenharTempo() {
883
- var t = $("#tempo");
884
- t.style.display = "flex";
885
- t.innerHTML = "";
886
- var total = 0;
887
- var W = doc.largura || 1920, H = doc.altura || 1080;
888
- (doc.cenas || []).forEach(function (c, i) {
889
- var dur = c.duracao || 4;
890
- total += dur;
891
- var larg = Math.max(70, dur * 26);
892
- var clipe = el("div", "clipe" + (i === iCena ? " ativo" : ""));
893
- clipe.style.width = larg + "px";
894
- var caixa = el("div", "caixa");
895
- var mini = renderCena(c, doc, {});
896
- // cobre o clipe inteiro sem distorcer
897
- mini.style.transform = "scale(" + Math.max(larg / W, 62 / H) + ")";
898
- caixa.appendChild(mini);
899
- clipe.appendChild(caixa);
900
- clipe.appendChild(el("div", "dur", dur + "s"));
901
- // A transição aparece NA tira, entre um clipe e outro, que é onde ela
902
- // acontece. Escondida num menu do inspetor, ninguém achava.
903
- if (c.transicao && c.transicao !== "corte") {
904
- clipe.appendChild(el("div", "tr-marca", nomeDaOpcao(OPC.transicao, c.transicao)));
905
- }
906
- clipe.onclick = function () { iCena = i; sel = null; desenhar(); };
907
- clipe.ondblclick = function () { Player && Player.tocar && Player.tocar(); };
908
- arrastavel(clipe, i, function (de, para) {
909
- mudar(function () { reordenar(doc.cenas, de, para); });
910
- iCena = para; sel = null; desenhar();
911
- });
912
- t.appendChild(clipe);
913
- });
914
-
915
- var mais = el("button", "clipe-mais", svgIcone("mais"));
916
- mais.title = "Nova cena";
917
- mais.onclick = abrirModelos;
918
- t.appendChild(mais);
919
-
920
- var info = el("div");
921
- info.style.cssText = "margin-left:12px;font-size:12px;color:var(--fraco2);white-space:nowrap";
922
- info.textContent = "total " + total.toFixed(1) + "s · arraste para reordenar";
923
- t.appendChild(info);
924
- }
925
-
926
- /* trilha sonora (Corte) -------------------------------------------------- */
927
-
928
- var previa = null;
929
-
930
- function pararPrevia() {
931
- if (previa) { previa.parar(); previa = null; }
932
- }
933
-
934
- function desenharTrilha() {
935
- var caixa = $("#trilha-caixa");
936
- var t = doc.trilha || {};
937
- caixa.innerHTML = "";
938
- caixa.appendChild(el("h3", "", "Trilha sonora"));
939
-
940
- var nota = el("p", "", "Música gerada aqui mesmo, ou um arquivo seu. " +
941
- "Não baixo faixa de YouTube nem Spotify — use áudio que você tem direito de usar.");
942
- nota.style.cssText = "color:var(--fraco);font-size:12.5px;margin:0 0 18px;line-height:1.6";
943
- caixa.appendChild(nota);
944
-
945
- /* geradas */
946
- caixa.appendChild(el("div", "rotulo", "Geradas pelo estúdio"));
947
- var grade = el("div", "trilha-grade");
948
- nomesEstilosTrilha().forEach(function (nome) {
949
- var E = ESTILOS_TRILHA[nome];
950
- var c = el("div", "trilha-item" + (t.tipo === "gerada" && t.estilo === nome ? " ativo" : ""));
951
- c.innerHTML = "<b>" + E.rotulo + "</b><span>" + E.bpm + " BPM</span>" +
952
- '<button class="ouvir" title="Ouvir">' + svgIcone("play") + "</button>";
953
- c.onclick = function (ev) {
954
- if (ev.target.closest(".ouvir")) {
955
- ev.stopPropagation();
956
- pararPrevia();
957
- previa = criarTrilha({ tipo: "gerada", estilo: nome, volume: 0.6 });
958
- if (previa) { previa.tocar(); setTimeout(pararPrevia, 9000); }
959
- return;
960
- }
961
- mudar(function () {
962
- doc.trilha = { tipo: "gerada", estilo: nome, volume: t.volume != null ? t.volume : 0.7 };
963
- });
964
- desenharTrilha();
965
- };
966
- grade.appendChild(c);
967
- });
968
- caixa.appendChild(grade);
969
-
970
- /* arquivo */
971
- var r2 = el("div", "rotulo", "Do seu computador");
972
- r2.style.marginTop = "22px";
973
- caixa.appendChild(r2);
974
-
975
- var linhaArq = el("div");
976
- linhaArq.style.cssText = "display:flex;gap:9px;align-items:center;margin-top:10px;flex-wrap:wrap";
977
- var bSub = el("button", "btn primo", "Escolher áudio");
978
- bSub.onclick = function () {
979
- Midia.escolher({ tipo: "audio" }).then(function (m) {
980
- if (!m) return;
981
- mudar(function () {
982
- doc.trilha = { tipo: "arquivo", src: m.url, nome: m.nome,
983
- volume: t.volume != null ? t.volume : 0.7, inicio: 0 };
984
- });
985
- desenharTrilha();
986
- });
987
- };
988
- linhaArq.appendChild(bSub);
989
-
990
- var urlDireta = el("input", "campo");
991
- urlDireta.placeholder = "…ou cole a URL de um arquivo de áudio";
992
- urlDireta.style.cssText = "flex:1;min-width:200px";
993
- urlDireta.value = t.tipo === "arquivo" && t.src && t.src.indexOf("/midia/") !== 0 ? t.src : "";
994
- urlDireta.onchange = function () {
995
- if (!urlDireta.value.trim()) return;
996
- mudar(function () {
997
- doc.trilha = { tipo: "arquivo", src: urlDireta.value.trim(), volume: 0.7, inicio: 0 };
998
- });
999
- desenharTrilha();
1000
- };
1001
- linhaArq.appendChild(urlDireta);
1002
- caixa.appendChild(linhaArq);
1003
-
1004
- /* estado atual */
1005
- var atual = el("div");
1006
- atual.style.cssText = "margin-top:22px;padding-top:18px;border-top:1px solid var(--linha)";
1007
- if (!t.tipo) {
1008
- atual.innerHTML = '<span style="color:var(--fraco2);font-size:13px">Sem trilha por enquanto.</span>';
1009
- } else {
1010
- atual.innerHTML = '<div class="rotulo" style="margin-bottom:10px">Em uso</div>' +
1011
- '<div style="font-size:13.5px;margin-bottom:12px">' +
1012
- (t.tipo === "gerada" ? "Gerada · " + (ESTILOS_TRILHA[t.estilo] || {}).rotulo
1013
- : "Arquivo · " + (t.nome || t.src)) + "</div>";
1014
-
1015
- var vol = el("div", "linha-campo");
1016
- vol.appendChild(el("span", "", "Volume"));
1017
- var sl = el("input");
1018
- sl.type = "range"; sl.min = 0; sl.max = 100;
1019
- sl.value = Math.round((t.volume != null ? t.volume : 0.7) * 100);
1020
- sl.oninput = function () { doc.trilha.volume = sl.value / 100; agendarSalvar(); };
1021
- vol.appendChild(sl);
1022
- atual.appendChild(vol);
1023
-
1024
- var acoes = el("div");
1025
- acoes.style.cssText = "display:flex;gap:9px;margin-top:14px";
1026
- var bAjustar = el("button", "btn", "Ajustar cenas ao áudio");
1027
- bAjustar.title = "Distribui a duração das cenas para preencher a faixa";
1028
- bAjustar.onclick = ajustarAoAudio;
1029
- var bTirar = el("button", "btn perigo", "Remover trilha");
1030
- bTirar.onclick = function () {
1031
- mudar(function () { delete doc.trilha; });
1032
- desenharTrilha();
1033
- };
1034
- acoes.appendChild(bAjustar);
1035
- acoes.appendChild(bTirar);
1036
- atual.appendChild(acoes);
1037
- }
1038
- caixa.appendChild(atual);
1039
-
1040
- var rodape = el("div", "rodape");
1041
- var bFechar = el("button", "btn", "Fechar");
1042
- bFechar.onclick = function () { pararPrevia(); $("#folha-trilha").classList.remove("on"); };
1043
- rodape.appendChild(bFechar);
1044
- caixa.appendChild(rodape);
1045
- }
1046
-
1047
- function ajustarAoAudio() {
1048
- var t = doc.trilha || {};
1049
- if (t.tipo === "gerada") {
1050
- return alert("A trilha gerada não tem fim — ela acompanha o vídeo. " +
1051
- "Ajuste as durações das cenas como quiser.");
1052
- }
1053
- var som = new Audio(t.src);
1054
- som.addEventListener("loadedmetadata", function () {
1055
- var d = som.duration;
1056
- if (!isFinite(d) || !d) return alert("não consegui ler a duração do áudio");
1057
- var n = (doc.cenas || []).length || 1;
1058
- mudar(function () {
1059
- doc.cenas.forEach(function (c) { c.duracao = Math.round((d / n) * 10) / 10; });
1060
- });
1061
- desenharTrilha();
1062
- });
1063
- som.addEventListener("error", function () { alert("não consegui carregar o áudio"); });
1064
- }
1065
-
1066
- /* inspetor -------------------------------------------------------------- */
1067
-
1068
- function grupo(titulo) {
1069
- var g = el("div", "grupo");
1070
- g.appendChild(el("div", "rotulo", titulo));
1071
- return g;
1072
- }
1073
-
1074
- function linha(rotulo, controle) {
1075
- var l = el("div", "linha-campo");
1076
- l.appendChild(el("span", "", rotulo));
1077
- l.appendChild(controle);
1078
- return l;
1079
- }
1080
-
1081
- function inp(valor, aoMudar, tipo) {
1082
- var i = el("input", "campo");
1083
- i.type = tipo || "text";
1084
- i.value = valor == null ? "" : valor;
1085
- i.oninput = function () { aoMudar(i.type === "number" ? (i.value === "" ? null : +i.value) : i.value); };
1086
- return i;
1087
- }
1088
-
1089
- function area(valor, aoMudar, linhas) {
1090
- var t = el("textarea", "campo");
1091
- t.rows = linhas || 4;
1092
- t.value = valor == null ? "" : valor;
1093
- t.oninput = function () { aoMudar(t.value); };
1094
- return t;
1095
- }
1096
-
1097
- /* O menu escreve em português.
1098
-
1099
- Antes ele mostrava o valor cru do documento — "grao", "kb-aproxima",
1100
- "left" — e uma opção vazia no topo, sem texto nenhum. Três caixas em
1101
- branco no inspetor, e nada dizendo o que havia dentro delas.
1102
-
1103
- O dicionário traduz na hora de mostrar; o valor gravado continua o
1104
- mesmo, porque é ele que o render.css entende. `vazio` é o texto da
1105
- opção "", que muda com o contexto: em Fundo é "Sem fundo", em
1106
- Acabamento é "Nenhum". */
1107
- var NOMES = {
1108
- malha: "Malha", brilho: "Brilho", grade: "Grade", pontos: "Pontos", suave: "Suave",
1109
- vinheta: "Vinheta", aurora: "Aurora", raios: "Raios", ondas: "Ondas", ruido: "Ruído",
1110
- grao: "Granulado", scanlines: "Linhas de TV", luz: "Vazamento de luz", moldura: "Moldura",
1111
- horizontal: "Na horizontal", vertical: "Na vertical",
1112
- chapeu: "Chapéu", titulo: "Título", subtitulo: "Subtítulo", apoio: "Apoio",
1113
- corpo: "Corpo", legenda: "Legenda",
1114
- left: "À esquerda", center: "Ao centro", right: "À direita",
1115
- cobrir: "Preencher", conter: "Caber inteira",
1116
- cinza: "Preto e branco", sepia: "Sépia", contraste: "Contraste", desbotado: "Desbotado",
1117
- aproximar: "Aproximar", afastar: "Afastar", esquerda: "Para a esquerda",
1118
- direita: "Para a direita", cima: "Para cima",
1119
- retangulo: "Retângulo", circulo: "Círculo", triangulo: "Triângulo",
1120
- linha: "Linha", seta: "Seta",
1121
- barras: "Barras", area: "Área", pizza: "Pizza", rosca: "Rosca",
1122
- curva: "Curva", reta: "Reta", cotovelo: "Cotovelo",
1123
- texto: "Texto", inteiro: "Número inteiro", decimal: "Decimal",
1124
- moeda: "Dinheiro", percentual: "Porcentagem",
1125
- "não": "Não", sim: "Sim",
1126
- 300: "Fino", 400: "Normal", 500: "Médio", 600: "Semi-negrito",
1127
- 700: "Negrito", 800: "Extra-negrito",
1128
- };
1129
-
1130
- function selecao(valores, atual, aoMudar, vazio) {
1131
- var s = el("select", "campo");
1132
- valores.forEach(function (v) {
1133
- var rotulo = v === "" ? (vazio || "Automático") : (NOMES[v] || String(v));
1134
- var o = el("option", "", rotulo);
1135
- o.value = v;
1136
- if (String(v) === String(atual)) o.selected = true;
1137
- s.appendChild(o);
1138
- });
1139
- s.onchange = function () { aoMudar(s.value); };
1140
- return s;
1141
- }
1142
-
1143
- /* As amostras e o campo hex na MESMA faixa.
1144
-
1145
- Empilhados, cada cor ocupava três linhas do painel; com dois seletores
1146
- de cor na mesma tela, sobrava painel só para eles. Agora as amostras
1147
- rolam numa faixa e o hex fica ao lado — e a amostra escolhida aparece
1148
- marcada, coisa que antes não dava para saber. */
1149
- function seletorCor(valor, aoMudar) {
1150
- var caixa = el("div", "cor-faixa");
1151
- var p = el("div", "cores");
1152
- var limpar = el("button", "sem-cor");
1153
- limpar.title = "Sem cor";
1154
- limpar.onclick = function () { aoMudar(""); };
1155
- p.appendChild(limpar);
1156
- PALETA.forEach(function (c) {
1157
- var b = el("button");
1158
- b.style.background = c;
1159
- if (String(valor).toLowerCase() === String(c).toLowerCase()) b.classList.add("ativa");
1160
- b.title = c;
1161
- b.onclick = function () { aoMudar(c); };
1162
- p.appendChild(b);
1163
- });
1164
- caixa.appendChild(p);
1165
- var hex = inp(valor, aoMudar);
1166
- hex.placeholder = "#hex";
1167
- hex.className = "campo hex";
1168
- caixa.appendChild(hex);
1169
- return caixa;
1170
- }
1171
-
1172
- function edita(campo, valor) {
1173
- var alvo = itens()[sel];
1174
- if (!alvo) return;
1175
- mudar(function () {
1176
- if (valor === "" || valor === null) delete alvo[campo];
1177
- else alvo[campo] = valor;
1178
- });
1179
- }
1180
-
1181
- function desenharInspetor() {
1182
- var ins = $("#inspetor");
1183
- ins.innerHTML = "";
1184
-
1185
- /* propriedades do arquivo */
1186
- var g0 = grupo(F.modelo === "cena" ? "Cena" : (F.modelo === "documento" ? "Documento" : "Planilha"));
1187
-
1188
- if (F.modelo === "cena") {
1189
- var c = cena();
1190
- g0.appendChild(linha("Layout", selecao(F.layouts, c.layout || F.layoutPadrao,
1191
- function (v) { mudar(function () { c.layout = v; }); })));
1192
- // UM controle para o fundo, não três.
1193
- //
1194
- // Havia "Fundo", "Fundo (extra)" e "Cor fundo", lado a lado, e os três
1195
- // gravavam na MESMA propriedade: escolher num apagava o outro, e dois
1196
- // deles apareciam vazios porque só um podia estar preenchido por vez.
1197
- // Era a parte mais confusa do inspetor. Agora é uma lista só, com tudo
1198
- // que existe, e a cor entra por baixo quando é cor que se quer.
1199
- // A lista vem de OPC, não repetida aqui: era a duplicação que deixava
1200
- // o menu com "ruido" (que não existe) enquanto o CSS tinha "nevoa" e
1201
- // "xadrez" que ninguém alcançava.
1202
- var fundoAtual = typeof c.fundo === "string" && c.fundo.charAt(0) !== "#" ? c.fundo : "";
1203
- g0.appendChild(linha("Fundo", menu(OPC.fundo, fundoAtual, function (v) {
1204
- mudar(function () { if (v) c.fundo = v; else delete c.fundo; });
1205
- })));
1206
- g0.appendChild(linha("Cor do fundo", seletorCor(
1207
- typeof c.fundo === "string" && c.fundo.charAt(0) === "#" ? c.fundo : "",
1208
- function (v) { mudar(function () { if (v) c.fundo = v; else delete c.fundo; }); })));
1209
-
1210
- var bFoto = el("button", "btn", svgIcone("imagem") + " Foto de fundo");
1211
- bFoto.style.width = "100%";
1212
- bFoto.onclick = function () {
1213
- Midia.escolher({ tipo: "imagem" }).then(function (m) {
1214
- if (m) mudar(function () { c.fundo = { imagem: m.url, escurecer: 0.55 }; });
1215
- });
1216
- };
1217
- g0.appendChild(linha("Imagem", bFoto));
1218
-
1219
- g0.appendChild(linha("Acabamento", menu(OPC.acabamento, c.acabamento || "",
1220
- function (v) { mudar(function () { if (v) c.acabamento = v; else delete c.acabamento; }); })));
1221
- g0.appendChild(linha("Cor de destaque", seletorCor(c.tom || "",
1222
- function (v) { mudar(function () { if (v) c.tom = v; else delete c.tom; }); })));
1223
- g0.appendChild(linha("Tema da cena", selecao([""].concat(TEMAS), c.tema || "",
1224
- function (v) { mudar(function () { if (v) c.tema = v; else delete c.tema; }); }, "Igual ao documento")));
1225
-
1226
- if (F.tempo) {
1227
- g0.appendChild(linha("Duração (s)", inp(c.duracao || 4, function (v) { mudar(function () { c.duracao = v || 4; }); }, "number")));
1228
- g0.appendChild(linha("Como entra", menu(OPC.transicao, c.transicao || "",
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; }); })));
1236
- }
1237
- if (SLUG === "traco") {
1238
- g0.appendChild(linha("Arranjo", selecao(["", "horizontal", "vertical"], c.auto || "",
1239
- function (v) { mudar(function () { if (v) c.auto = v; else delete c.auto; }); })));
1240
- }
1241
- var dim = el("div", "dois");
1242
- dim.appendChild(inp(doc.largura || 1920, function (v) { mudar(function () { doc.largura = v; }); }, "number"));
1243
- dim.appendChild(inp(doc.altura || 1080, function (v) { mudar(function () { doc.altura = v; }); }, "number"));
1244
- g0.appendChild(linha("Tela", dim));
1245
- if (F.tamanhos) {
1246
- g0.appendChild(linha("Preset", selecao([""].concat(F.tamanhos.map(function (t) { return t[0]; })), "",
1247
- function (v) {
1248
- var t = F.tamanhos.filter(function (x) { return x[0] === v; })[0];
1249
- if (t) mudar(function () { doc.largura = t[1]; doc.altura = t[2]; });
1250
- })));
1251
- }
1252
- } else if (F.modelo === "planilha") {
1253
- g0.appendChild(linha("Nome", inp(aba().nome, function (v) { mudar(function () { aba().nome = v; }); })));
1254
- g0.appendChild(linha("Tema", selecao(TEMAS, doc.tema || "claro", function (v) { mudar(function () { doc.tema = v; }); })));
1255
- } else {
1256
- g0.appendChild(linha("Tema", selecao(TEMAS, doc.tema || "papel", function (v) { mudar(function () { doc.tema = v; }); })));
1257
- }
1258
- ins.appendChild(g0);
1259
-
1260
- if (F.modelo === "planilha") { colunasInspetor(ins); return; }
1261
- if (F.modelo === "cena" && (SLUG === "corte" || SLUG === "prisma")) narracaoInspetor(ins);
1262
-
1263
- var e = itens()[sel];
1264
- if (!e) {
1265
- ins.appendChild(el("div", "nada", "Clique em um elemento do palco<br>para editar as propriedades."));
1266
- return;
1267
- }
1268
-
1269
- /* ações do elemento */
1270
- var ga = grupo(e.tipo);
1271
- var ac = el("div", "opcoes");
1272
- [["subir", "Subir", -1], ["descer", "Descer", 1]].forEach(function (a) {
1273
- var b = el("button", "", svgIcone(a[0]));
1274
- b.title = a[1];
1275
- b.onclick = function () {
1276
- var lista = itens(), j = sel + a[2];
1277
- if (j < 0 || j >= lista.length) return;
1278
- mudar(function () { var t = lista[sel]; lista[sel] = lista[j]; lista[j] = t; sel = j; });
1279
- };
1280
- ac.appendChild(b);
1281
- });
1282
- var bd = el("button", "", svgIcone("duplicar")); bd.title = "Duplicar";
1283
- bd.onclick = function () { mudar(function () { itens().splice(sel + 1, 0, JSON.parse(JSON.stringify(e))); sel++; }); };
1284
- var bx = el("button", "", svgIcone("lixeira")); bx.title = "Remover";
1285
- bx.onclick = function () { mudar(function () { itens().splice(sel, 1); sel = null; }); };
1286
- ac.appendChild(bd); ac.appendChild(bx);
1287
- ga.appendChild(ac);
1288
- ins.appendChild(ga);
1289
-
1290
- camposDoTipo(ins, e);
1291
-
1292
- /* posição, para elementos livres */
1293
- if (F.modelo === "cena") {
1294
- var gp = grupo("Posição");
1295
- var xy = el("div", "dois");
1296
- xy.appendChild(inp(e.x, function (v) { edita("x", v); }, "number"));
1297
- xy.appendChild(inp(e.y, function (v) { edita("y", v); }, "number"));
1298
- gp.appendChild(linha("X / Y", xy));
1299
- var la = el("div", "dois");
1300
- la.appendChild(inp(e.l, function (v) { edita("l", v); }, "number"));
1301
- la.appendChild(inp(e.a, function (v) { edita("a", v); }, "number"));
1302
- gp.appendChild(linha("L / A", la));
1303
- gp.appendChild(linha("Girar (graus)", inp(e.rotacao || 0, function (v) { edita("rotacao", v); }, "number")));
1304
- gp.appendChild(linha("Opacidade", inp(e.opacidade != null ? e.opacidade : 1, function (v) { edita("opacidade", v); }, "number")));
1305
- gp.appendChild(linha("Como entra", menu(OPC.animacao, e.anim || "",
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")));
1312
- if (e.tipo === "numeros") {
1313
- gp.appendChild(linha("Contar", selecao(["não", "sim"], e.contar ? "sim" : "não",
1314
- function (v) { edita("contar", v === "sim" ? true : ""); })));
1315
- }
1316
- ins.appendChild(gp);
1317
- if (e.x == null && e.y == null) {
1318
- var dica = el("div", "nada", "Este elemento segue o layout da cena.<br>Defina X e Y para posicionar à mão.");
1319
- dica.style.padding = "10px 16px";
1320
- ins.appendChild(dica);
1321
- }
1322
- if (F.tempo) chavesInspetor(ins, e);
1323
- }
1324
- }
1325
-
1326
- /* ─────────────────────────────────────────────────────────── chaves ─────
1327
- Antes um elemento só sabia ENTRAR: escolhia-se um efeito e acabava ali.
1328
- Nada percorria a cena, crescia devagar nem sumia no fim.
1329
-
1330
- Uma chave é um instante: onde o elemento está aos 0%, aos 50%, aos
1331
- 100% da cena. Duas ou mais viram animação de verdade. A régua embaixo
1332
- mostra as chaves na linha do tempo da cena — não é enfeite, é o único
1333
- jeito de ver que existem três chaves e que a do meio está adiantada. */
1334
- var PRESETS_CHAVE = [
1335
- { nome: "Atravessar", chaves: [{ t: 0, x: -220, opacidade: 0 }, { t: 0.18, x: 0, opacidade: 1 },
1336
- { t: 0.82, x: 0, opacidade: 1 }, { t: 1, x: 220, opacidade: 0 }] },
1337
- { nome: "Crescer devagar", chaves: [{ t: 0, escala: 1 }, { t: 1, escala: 1.18 }] },
1338
- { nome: "Aparecer e sumir", chaves: [{ t: 0, opacidade: 0 }, { t: 0.15, opacidade: 1 },
1339
- { t: 0.85, opacidade: 1 }, { t: 1, opacidade: 0 }] },
1340
- { nome: "Flutuar", chaves: [{ t: 0, y: 0 }, { t: 0.5, y: -22 }, { t: 1, y: 0 }] },
1341
- { nome: "Sair do desfoque", chaves: [{ t: 0, desfoque: 18, opacidade: 0, escala: 1.06 },
1342
- { t: 0.35, desfoque: 0, opacidade: 1, escala: 1 }, { t: 1, desfoque: 0, opacidade: 1, escala: 1 }] },
1343
- ];
1344
-
1345
- function chavesInspetor(ins, e) {
1346
- var g = grupo("Animação por chaves");
1347
- var chaves = e.chaves || [];
1348
-
1349
- if (!chaves.length) {
1350
- g.appendChild(el("div", "nada",
1351
- "Sem chaves: o elemento fica parado depois de entrar."));
1352
- var pr = el("div", "presets");
1353
- PRESETS_CHAVE.forEach(function (p) {
1354
- var b = el("button", "", p.nome);
1355
- b.onclick = function () {
1356
- edita("chaves", JSON.parse(JSON.stringify(p.chaves)));
1357
- };
1358
- pr.appendChild(b);
1359
- });
1360
- g.appendChild(pr);
1361
- ins.appendChild(g);
1362
- return;
1363
- }
1364
-
1365
- // A régua: cada chave é um pino na duração da cena.
1366
- var regua = el("div", "regua-chaves");
1367
- var trilho = el("div", "trilho-chaves");
1368
- regua.appendChild(trilho);
1369
- chaves.forEach(function (k, i) {
1370
- var pino = el("button", "pino" + (i === chaveSel ? " ativo" : ""));
1371
- pino.style.left = (Math.max(0, Math.min(1, k.t || 0)) * 100) + "%";
1372
- pino.title = ((k.t || 0) * (cena().duracao || 4)).toFixed(1) + "s";
1373
- pino.onclick = function () { chaveSel = i; desenharInspetor(); };
1374
- trilho.appendChild(pino);
1375
- });
1376
- g.appendChild(regua);
1377
- g.appendChild(el("div", "sub-regua",
1378
- "0s" + " · " + (cena().duracao || 4) + "s no fim"));
1379
-
1380
- if (chaveSel >= chaves.length) chaveSel = chaves.length - 1;
1381
- var k = chaves[chaveSel] || chaves[0];
1382
-
1383
- var nav = el("div", "opcoes");
1384
- chaves.forEach(function (kk, i) {
1385
- var b = el("button", i === chaveSel ? "ativo" : "", String(i + 1));
1386
- b.onclick = function () { chaveSel = i; desenharInspetor(); };
1387
- nav.appendChild(b);
1388
- });
1389
- var bMais = el("button", "", svgIcone("mais"));
1390
- bMais.title = "Nova chave no fim";
1391
- bMais.onclick = function () {
1392
- mudar(function () {
1393
- var ultima = chaves[chaves.length - 1] || {};
1394
- var nova = JSON.parse(JSON.stringify(ultima));
1395
- nova.t = Math.min(1, (ultima.t || 0) + 0.25);
1396
- chaves.push(nova);
1397
- chaves.sort(function (a, b2) { return (a.t || 0) - (b2.t || 0); });
1398
- });
1399
- chaveSel = chaves.length - 1;
1400
- desenharInspetor();
1401
- };
1402
- nav.appendChild(bMais);
1403
- g.appendChild(nav);
1404
-
1405
- function campoChave(rotulo, campo, padrao, passo) {
1406
- var i = inp(k[campo] != null ? k[campo] : padrao, function (v) {
1407
- mudar(function () {
1408
- if (v == null || v === "") delete k[campo];
1409
- else k[campo] = v;
1410
- });
1411
- }, "number");
1412
- if (passo) i.step = passo;
1413
- g.appendChild(linha(rotulo, i));
1414
- }
1415
-
1416
- var tSeg = inp(+((k.t || 0) * (cena().duracao || 4)).toFixed(2), function (v) {
1417
- mudar(function () {
1418
- k.t = Math.max(0, Math.min(1, (v || 0) / (cena().duracao || 4)));
1419
- chaves.sort(function (a, b2) { return (a.t || 0) - (b2.t || 0); });
1420
- });
1421
- }, "number");
1422
- tSeg.step = "0.1";
1423
- g.appendChild(linha("Momento (s)", tSeg));
1424
-
1425
- campoChave("Deslocar X", "x", 0);
1426
- campoChave("Deslocar Y", "y", 0);
1427
- campoChave("Escala", "escala", 1, "0.05");
1428
- campoChave("Girar (graus)", "giro", 0);
1429
- campoChave("Opacidade", "opacidade", 1, "0.1");
1430
- campoChave("Desfoque (px)", "desfoque", 0);
1431
-
1432
- var acoes = el("div", "opcoes");
1433
- var bRep = el("button", e.repetir ? "ativo" : "", svgIcone("refazer"));
1434
- bRep.title = "Repetir sem parar";
1435
- bRep.onclick = function () { edita("repetir", e.repetir ? "" : true); };
1436
- var bLin = el("button", e.suavizar === false ? "ativo" : "", svgIcone("linha"));
1437
- bLin.title = "Movimento constante (sem suavizar)";
1438
- bLin.onclick = function () { edita("suavizar", e.suavizar === false ? "" : false); };
1439
- var bDel = el("button", "", svgIcone("lixeira"));
1440
- bDel.title = "Apagar esta chave";
1441
- bDel.onclick = function () {
1442
- mudar(function () { chaves.splice(chaveSel, 1); if (!chaves.length) delete e.chaves; });
1443
- chaveSel = 0; desenharInspetor();
1444
- };
1445
- acoes.appendChild(bRep); acoes.appendChild(bLin); acoes.appendChild(bDel);
1446
- g.appendChild(acoes);
1447
-
1448
- ins.appendChild(g);
1449
- }
1450
-
1451
- function camposDoTipo(ins, e) {
1452
- var g = grupo("Conteúdo");
1453
-
1454
- if (e.tipo === "texto" || e.tipo === "no" || ["titulo", "secao", "subsecao", "paragrafo", "apoio", "chapeu"].indexOf(e.tipo) >= 0) {
1455
- g.appendChild(linha("Texto", area(e.texto, function (v) { edita("texto", v); }, 3)));
1456
- }
1457
- if (e.tipo === "texto") {
1458
- g.appendChild(linha("Papel", selecao(["chapeu", "titulo", "subtitulo", "apoio", "corpo", "legenda"],
1459
- e.papel || "corpo", function (v) { edita("papel", v); })));
1460
- g.appendChild(linha("Tamanho", inp(e.tamanho, function (v) { edita("tamanho", v); }, "number")));
1461
- g.appendChild(linha("Peso", selecao(["", 300, 400, 500, 600, 700, 800], e.peso || "", function (v) { edita("peso", v ? +v : ""); })));
1462
- g.appendChild(linha("Alinhar", selecao(["", "left", "center", "right"], e.alinhar || "", function (v) { edita("alinhar", v); })));
1463
- g.appendChild(linha("Estilo", selecao(
1464
- ["", "gradiente", "contorno", "contorno-tom", "sombra", "brilho", "relevo", "marcador", "fita", "caixa"],
1465
- e.estilo || "", function (v) { edita("estilo", v); })));
1466
- g.appendChild(linha("Cor", seletorCor(e.cor, function (v) { edita("cor", v); })));
1467
- g.appendChild(linha("Maiúsc.", selecao(["não", "sim"], e.maiusculas ? "sim" : "não",
1468
- function (v) { edita("maiusculas", v === "sim" ? true : ""); })));
1469
- }
1470
- if (e.tipo === "lista" || e.tipo === "numerada") {
1471
- g.appendChild(linha("Itens", area((e.itens || []).join("\n"),
1472
- function (v) { edita("itens", v.split("\n").filter(function (x) { return x.trim(); })); }, 6)));
1473
- }
1474
- if (e.tipo === "cartoes" || e.tipo === "numeros") {
1475
- g.appendChild(linha("Itens", area((e.itens || []).map(function (i) {
1476
- return typeof i === "string" ? i : [i.titulo || i.valor, i.texto || i.rotulo].join(" | ");
1477
- }).join("\n"), function (v) { edita("itens", v.split("\n").filter(function (x) { return x.trim(); })); }, 6)));
1478
- g.appendChild(linha("Colunas", inp(e.colunas, function (v) { edita("colunas", v); }, "number")));
1479
- }
1480
- if (e.tipo === "tabela") {
1481
- g.appendChild(linha("Linhas", area((e.linhas || []).map(function (l) { return l.join(" | "); }).join("\n"),
1482
- function (v) {
1483
- edita("linhas", v.split("\n").filter(function (x) { return x.trim(); })
1484
- .map(function (l) { return l.split("|").map(function (c) { return c.trim(); }); }));
1485
- }, 7)));
1486
- }
1487
- if (e.tipo === "codigo") g.appendChild(linha("Código", area(e.texto, function (v) { edita("texto", v); }, 8)));
1488
- if (e.tipo === "citacao") {
1489
- g.appendChild(linha("Frase", area(e.texto, function (v) { edita("texto", v); }, 4)));
1490
- g.appendChild(linha("Autor", inp(e.autor, function (v) { edita("autor", v); })));
1491
- }
1492
- if (e.tipo === "destaque") {
1493
- g.appendChild(linha("Título", inp(e.titulo, function (v) { edita("titulo", v); })));
1494
- g.appendChild(linha("Texto", area(e.texto, function (v) { edita("texto", v); }, 3)));
1495
- }
1496
- if (e.tipo === "imagem") {
1497
- var bTrocar = el("button", "btn", svgIcone("pasta") + " Do computador");
1498
- bTrocar.style.width = "100%";
1499
- bTrocar.onclick = function () {
1500
- Midia.escolher({ tipo: "imagem" }).then(function (m) { if (m) edita("src", m.url); });
1501
- };
1502
- g.appendChild(linha("Arquivo", bTrocar));
1503
- g.appendChild(linha("URL", inp(e.src, function (v) { edita("src", v); })));
1504
- g.appendChild(linha("Ajuste", selecao(["cobrir", "conter"], e.ajuste || "cobrir", function (v) { edita("ajuste", v); })));
1505
- g.appendChild(linha("Filtro", selecao(["", "cinza", "sepia", "contraste", "desbotado"],
1506
- e.filtro || "", function (v) { edita("filtro", v); })));
1507
- g.appendChild(linha("Movimento", selecao(["", "aproximar", "afastar", "esquerda", "direita", "cima"],
1508
- e.movimento || "", function (v) { edita("movimento", v); })));
1509
- g.appendChild(linha("Raio", inp(e.raio, function (v) { edita("raio", v); }, "number")));
1510
- g.appendChild(linha("Legenda", inp(e.legenda, function (v) { edita("legenda", v); })));
1511
- }
1512
- if (e.tipo === "icone") {
1513
- g.appendChild(linha("Ícone", selecao(nomesIcones(), e.nome || "estrela", function (v) { edita("nome", v); })));
1514
- g.appendChild(linha("Tamanho", inp(e.tamanho || 90, function (v) { edita("tamanho", v); }, "number")));
1515
- g.appendChild(linha("Cor", seletorCor(e.cor, function (v) { edita("cor", v); })));
1516
- }
1517
- if (e.tipo === "forma") {
1518
- g.appendChild(linha("Forma", selecao(["retangulo", "circulo", "triangulo", "linha", "seta"],
1519
- e.forma || "retangulo", function (v) { edita("forma", v); })));
1520
- g.appendChild(linha("Cor", seletorCor(e.preenchimento, function (v) { edita("preenchimento", v); })));
1521
- g.appendChild(linha("Raio", inp(e.raio, function (v) { edita("raio", v); }, "number")));
1522
- }
1523
- if (e.tipo === "grafico") {
1524
- g.appendChild(linha("Tipo", selecao(["barras", "linha", "area", "pizza", "rosca"],
1525
- e.grafico || "barras", function (v) { edita("grafico", v); })));
1526
- g.appendChild(linha("Dados", area((e.dados || []).map(function (d) { return d[0] + " | " + d[1]; }).join("\n"),
1527
- function (v) {
1528
- edita("dados", v.split("\n").filter(function (x) { return x.trim(); }).map(function (l) {
1529
- var p = l.split("|");
1530
- return [(p[0] || "").trim(), parseFloat(p[1]) || 0];
1531
- }));
1532
- }, 6)));
1533
- }
1534
- if (e.tipo === "no") {
1535
- g.appendChild(linha("Id", inp(e.id, function (v) { edita("id", v); })));
1536
- g.appendChild(linha("Destaque", selecao(["não", "sim"], e.destaque ? "sim" : "não",
1537
- function (v) { edita("destaque", v === "sim" ? true : ""); })));
1538
- g.appendChild(linha("Formato", selecao(["", "circulo"], e.formato || "", function (v) { edita("formato", v); })));
1539
- }
1540
- if (e.tipo === "conector") {
1541
- var ids = (cena().elementos || []).filter(function (x) { return x.id; }).map(function (x) { return x.id; });
1542
- g.appendChild(linha("De", selecao([""].concat(ids), e.de || "", function (v) { edita("de", v); })));
1543
- g.appendChild(linha("Para", selecao([""].concat(ids), e.para || "", function (v) { edita("para", v); })));
1544
- g.appendChild(linha("Estilo", selecao(["curva", "reta", "cotovelo"], e.estilo || "curva", function (v) { edita("estilo", v); })));
1545
- g.appendChild(linha("Rótulo", inp(e.texto, function (v) { edita("texto", v); })));
1546
- }
1547
-
1548
- if (g.children.length > 1) ins.appendChild(g);
1549
- }
1550
-
1551
- function narracaoInspetor(ins) {
1552
- var c = cena();
1553
- var gn = grupo(SLUG === "corte" ? "Narração" : "Narração e notas");
1554
-
1555
- /* --- 1. sua própria voz: sempre melhor que qualquer sintetizada ------ */
1556
-
1557
- if (c.narracaoAudio) {
1558
- var usada = el("div");
1559
- usada.style.cssText = "display:flex;gap:6px;align-items:center;margin-bottom:10px";
1560
- var bTocar = el("button", "btn", svgIcone("play"));
1561
- bTocar.title = "Ouvir a gravação";
1562
- bTocar.onclick = function () { new Audio(c.narracaoAudio).play(); };
1563
- var rot = el("span", "", "voz gravada");
1564
- rot.style.cssText = "flex:1;font-size:12px;color:#4ade80";
1565
- var bX = el("button", "btn perigo", svgIcone("lixeira"));
1566
- bX.title = "Remover a gravação";
1567
- bX.onclick = function () { mudar(function () { delete c.narracaoAudio; }); };
1568
- usada.appendChild(bTocar); usada.appendChild(rot); usada.appendChild(bX);
1569
- gn.appendChild(usada);
1570
- } else if (Narracao.podeGravar()) {
1571
- var bGravar = el("button", "btn", svgIcone("play2") + " Gravar minha voz");
1572
- bGravar.style.cssText = "width:100%;margin-bottom:10px";
1573
- bGravar.onclick = function () {
1574
- if (Narracao.gravando()) {
1575
- Narracao.pararGravacao("narracao-" + ID + "-" + (iCena + 1)).then(function (m) {
1576
- mudar(function () { c.narracaoAudio = m.url; });
1577
- }).catch(function (e) { alert("erro ao salvar: " + e.message); });
1578
- return;
1579
- }
1580
- Narracao.gravar().then(function () {
1581
- bGravar.innerHTML = svgIcone("pausa") + " Parar e salvar";
1582
- bGravar.classList.add("primo");
1583
- }).catch(function (e) { alert("não consegui abrir o microfone: " + e.message); });
1584
- };
1585
- gn.appendChild(bGravar);
1586
- }
1587
-
1588
- /* --- 2. texto para a voz sintetizada -------------------------------- */
1589
-
1590
- gn.appendChild(linha("Falar", area(c.narracao, function (v) {
1591
- mudar(function () { if (v) c.narracao = v; else delete c.narracao; });
1592
- }, 3)));
1593
-
1594
- var acoes = el("div", "opcoes");
1595
- var bOuvir = el("button", "", svgIcone("play"));
1596
- bOuvir.title = "Ouvir";
1597
- bOuvir.onclick = function () {
1598
- if (!Narracao.disponivel()) return alert("este navegador não tem síntese de voz");
1599
- Narracao.falar(c.narracao || "", doc.voz || {});
1600
- };
1601
- var bParar = el("button", "", svgIcone("pausa"));
1602
- bParar.title = "Parar";
1603
- bParar.onclick = function () { Narracao.parar(); };
1604
- acoes.appendChild(bOuvir);
1605
- acoes.appendChild(bParar);
1606
-
1607
- if (F.tempo) {
1608
- var bMedir = el("button", "", svgIcone("relogio"));
1609
- bMedir.title = "Ajustar a duração desta cena à narração";
1610
- bMedir.onclick = function () {
1611
- if (!c.narracao) return;
1612
- bMedir.style.opacity = ".4";
1613
- Narracao.medir(c.narracao, doc.voz || {}).then(function (s) {
1614
- bMedir.style.opacity = "";
1615
- mudar(function () { c.duracao = Math.max(2, Math.round((s + 0.6) * 10) / 10); });
1616
- });
1617
- };
1618
- acoes.appendChild(bMedir);
1619
- }
1620
- gn.appendChild(acoes);
1621
-
1622
- if (SLUG === "prisma") {
1623
- gn.appendChild(linha("Notas", area(c.notas, function (v) {
1624
- mudar(function () { if (v) c.notas = v; else delete c.notas; });
1625
- }, 3)));
1626
- }
1627
-
1628
- /* --- 3. escolha da voz, já ranqueada -------------------------------- */
1629
-
1630
- // As vozes vêm do servidor, que é quem sintetiza. Cada uma traz a
1631
- // frequência MEDIDA — quem quer voz de locução escolhe pelo número, não
1632
- // pelo palpite.
1633
- var voz = doc.voz || {};
1634
- var sel2 = el("select", "campo");
1635
- function encherVozes(lista) {
1636
- sel2.innerHTML = "";
1637
- var opAuto = el("option", "", "Padrão (a mais grave)");
1638
- opAuto.value = "";
1639
- sel2.appendChild(opAuto);
1640
- lista.forEach(function (v) {
1641
- var o = el("option", "", v.nome + " · " + v.tom
1642
- + (v.hz ? " · " + v.hz + " Hz" : "")
1643
- + (v.instalada ? "" : " · baixa ao usar"));
1644
- o.value = v.id;
1645
- if (v.id === voz.voz) o.selected = true;
1646
- sel2.appendChild(o);
1647
- });
1648
- }
1649
- encherVozes(VozNeural.vozes());
1650
- VozNeural.status().then(function () { encherVozes(VozNeural.vozes()); });
1651
- sel2.onchange = function () {
1652
- mudar(function () { doc.voz = doc.voz || {}; doc.voz.voz = sel2.value; });
1653
- Narracao.falar("Assim que esta voz vai narrar o seu vídeo.",
1654
- { voz: sel2.value, grave: voz.grave, velocidade: voz.velocidade });
1655
- };
1656
- gn.appendChild(linha("Voz", sel2));
1657
-
1658
- var bGrave = el("button", "btn" + (voz.grave ? " primo" : ""),
1659
- (voz.grave ? "✓ " : "") + "Registro grave, de cinema");
1660
- bGrave.style.cssText = "width:100%;margin:2px 0 8px";
1661
- bGrave.title = "Abaixa o tom uns 10% sem mudar a duração";
1662
- bGrave.onclick = function () {
1663
- mudar(function () { doc.voz = doc.voz || {}; doc.voz.grave = !voz.grave; });
1664
- };
1665
- gn.appendChild(bGrave);
1666
-
1667
- gn.appendChild(linha("Velocidade", inp(voz.velocidade != null ? voz.velocidade : 1,
1668
- function (v) { mudar(function () { doc.voz = doc.voz || {}; doc.voz.velocidade = v || 1; }); },
1669
- "number")));
1670
-
1671
- if (F.tempo) {
1672
- var bTodas = el("button", "btn", "Ajustar todas as cenas");
1673
- bTodas.style.cssText = "width:100%;margin-top:8px";
1674
- bTodas.onclick = ajustarTudoNarracao;
1675
- gn.appendChild(bTodas);
1676
- }
1677
-
1678
- /* --- 4. o estado real do motor -------------------------------------- */
1679
-
1680
- // Isto aqui mandava o usuário em "Ajustes do Sistema → Acessibilidade →
1681
- // ... baixe as vozes da Apple". Era verdade quando a narração dependia
1682
- // do TTS do sistema. Hoje a síntese é neural e roda no servidor, então o
1683
- // texto virou instrução para o lugar errado — e num Windows, para um
1684
- // lugar que nem existe. O que fica é o estado de verdade do motor.
1685
- var aviso = el("div", "dica-voz");
1686
- aviso.style.display = "none";
1687
- gn.appendChild(aviso);
1688
- VozNeural.status(true).then(function (s) {
1689
- var p = s.piper || {};
1690
- if (p.pronta) return;
1691
- aviso.style.display = "";
1692
- if (p.baixando && p.baixando.ativo) {
1693
- aviso.innerHTML = "<b>Preparando a voz neural…</b><p>" + p.baixando.etapa
1694
- + " · " + p.baixando.pct + "%. Enquanto isso a narração sai por uma voz "
1695
- + "mais simples; o vídeo não fica mudo.</p>";
1696
- setTimeout(function () { if (document.body.contains(aviso)) desenharInspetor(); }, 3000);
1697
- } else if (!p.suportado) {
1698
- aviso.innerHTML = "<b>Sem voz neural para este sistema.</b>"
1699
- + "<p>A narração sai pela voz de reserva. Gravar a sua própria voz, no botão "
1700
- + "acima, continua sendo o melhor resultado.</p>";
1701
- } else {
1702
- aviso.innerHTML = "<b>A voz neural ainda não foi baixada.</b>"
1703
- + "<p>São ~90 MB, uma vez só, e depois funciona sem internet.</p>";
1704
- var bJa = el("button", "btn primo", "Baixar agora");
1705
- bJa.style.cssText = "width:100%;margin-top:8px";
1706
- bJa.onclick = function () {
1707
- bJa.textContent = "baixando…";
1708
- VozNeural.instalar(voz.voz).then(function () { desenharInspetor(); });
1709
- };
1710
- aviso.appendChild(bJa);
1711
- }
1712
- });
1713
-
1714
- ins.appendChild(gn);
1715
- }
1716
-
1717
- /* mede cada narração e ajusta a duração da cena a ela, uma de cada vez */
1718
- function ajustarTudoNarracao() {
1719
- var lista = (doc.cenas || []).map(function (c, i) { return { c: c, i: i }; })
1720
- .filter(function (x) { return x.c.narracao; });
1721
- if (!lista.length) return alert("nenhuma cena tem narração ainda");
1722
-
1723
- marcar("medindo narração…");
1724
- (function proxima(k) {
1725
- if (k >= lista.length) {
1726
- desenhar();
1727
- agendarSalvar();
1728
- return;
1729
- }
1730
- Narracao.medir(lista[k].c.narracao, doc.voz || {}).then(function (s) {
1731
- lista[k].c.duracao = Math.max(2, Math.round((s + 0.6) * 10) / 10);
1732
- marcar("medindo " + (k + 1) + "/" + lista.length + "…");
1733
- proxima(k + 1);
1734
- });
1735
- })(0);
1736
- }
1737
-
1738
- function colunasInspetor(ins) {
1739
- var g = grupo("Colunas");
1740
- (aba().colunas || []).forEach(function (c, i) {
1741
- g.appendChild(linha(String.fromCharCode(65 + i), inp(c, function (v) {
1742
- mudar(function () { aba().colunas[i] = v; });
1743
- })));
1744
- });
1745
- ins.appendChild(g);
1746
-
1747
- var gf = grupo("Formato por coluna");
1748
- (aba().colunas || []).forEach(function (c) {
1749
- gf.appendChild(linha(c.slice(0, 8), selecao(["", "texto", "inteiro", "decimal", "moeda", "percentual"],
1750
- (aba().formatos || {})[c] || "", function (v) {
1751
- mudar(function () {
1752
- aba().formatos = aba().formatos || {};
1753
- if (v) aba().formatos[c] = v; else delete aba().formatos[c];
1754
- });
1755
- })));
1756
- });
1757
- ins.appendChild(gf);
1758
-
1759
- var gh = grupo("Fórmulas");
1760
- gh.appendChild(el("div", "nada",
1761
- "Escreva na célula:<br><code>=SOMA(B1:B9)</code><br><code>=MEDIA(C1:C4)</code><br><code>=B2*1.2</code>"));
1762
- ins.appendChild(gh);
1763
- }
1764
-
1765
- /* ------------------------------------------------------------- ações topo */
1766
-
1767
- $("#titulo").addEventListener("input", agendarSalvar);
1768
-
1769
- $("#btn-codigo").onclick = function () {
1770
- $("#codigo-txt").value = JSON.stringify(doc, null, 2);
1771
- $("#folha").classList.add("on");
1772
- };
1773
- $("#codigo-aplicar").onclick = function () {
1774
- try {
1775
- var novo = JSON.parse($("#codigo-txt").value);
1776
- mudar(function () { doc = novo; ARQ.doc = doc; iCena = 0; sel = null; });
1777
- $("#folha").classList.remove("on");
1778
- } catch (err) {
1779
- alert("JSON inválido: " + err.message);
1780
- }
1781
- };
1782
- $("#codigo-fechar").onclick = function () { $("#folha").classList.remove("on"); };
1783
-
1784
- $("#btn-ver").href = "/v/" + ID;
1785
- $("#btn-baixar").href = "/api/arquivos/" + ID + "/export";
1786
- $("#btn-pdf").onclick = function () { window.open("/v/" + ID + "#imprimir", "_blank"); };
1787
-
1788
- document.addEventListener("keydown", function (ev) {
1789
- if (/input|textarea/i.test(ev.target.tagName) || ev.target.isContentEditable) return;
1790
- if ((ev.metaKey || ev.ctrlKey) && ev.key === "z") { ev.preventDefault(); ev.shiftKey ? refazer() : desfazer(); return; }
1791
- if (ev.key === "Delete" || ev.key === "Backspace") {
1792
- if (sel != null) { ev.preventDefault(); mudar(function () { itens().splice(sel, 1); sel = null; }); }
1793
- }
1794
- if (F.modelo === "cena") {
1795
- if (ev.key === "ArrowDown" && !sel) { iCena = Math.min((doc.cenas || []).length - 1, iCena + 1); desenhar(); }
1796
- if (ev.key === "ArrowUp" && !sel) { iCena = Math.max(0, iCena - 1); desenhar(); }
1797
- }
1798
- });
1799
-
1800
- window.addEventListener("resize", function () { if (doc) desenharPalco(); });
1801
- window.addEventListener("beforeunload", function () { clearTimeout(tSalvar); salvar(true); });
1802
- })();