primocode 7.18.0 → 8.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.
@@ -0,0 +1,1270 @@
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
+
17
+ /* ------------------------------------------------------------ utilidades */
18
+
19
+ function el(tag, cls, html) {
20
+ var n = document.createElement(tag);
21
+ if (cls) n.className = cls;
22
+ if (html != null) n.innerHTML = html;
23
+ return n;
24
+ }
25
+
26
+ function cena() { return (doc.cenas || [])[iCena] || { elementos: [] }; }
27
+ function itens() {
28
+ if (F.modelo === "cena") return cena().elementos || (cena().elementos = []);
29
+ if (F.modelo === "documento") return doc.blocos || (doc.blocos = []);
30
+ return [];
31
+ }
32
+ function aba() { return (doc.abas || [])[iAba] || { colunas: [], linhas: [] }; }
33
+
34
+ function instantaneo() {
35
+ historico.push(JSON.stringify(doc));
36
+ if (historico.length > 60) historico.shift();
37
+ futuro.length = 0;
38
+ }
39
+
40
+ function mudar(fn) {
41
+ instantaneo();
42
+ fn();
43
+ desenhar();
44
+ agendarSalvar();
45
+ }
46
+
47
+ function desfazer() {
48
+ if (!historico.length) return;
49
+ futuro.push(JSON.stringify(doc));
50
+ doc = JSON.parse(historico.pop());
51
+ ARQ.doc = doc;
52
+ sel = null;
53
+ desenhar();
54
+ agendarSalvar();
55
+ }
56
+
57
+ function refazer() {
58
+ if (!futuro.length) return;
59
+ historico.push(JSON.stringify(doc));
60
+ doc = JSON.parse(futuro.pop());
61
+ ARQ.doc = doc;
62
+ sel = null;
63
+ desenhar();
64
+ agendarSalvar();
65
+ }
66
+
67
+ /* ---------------------------------------------------------------- salvar */
68
+
69
+ function marcar(t, ok) {
70
+ var e = $("#estado");
71
+ e.textContent = t;
72
+ e.classList.toggle("ok", !!ok);
73
+ }
74
+
75
+ function salvar(saindo) {
76
+ if (!sujo) return;
77
+ sujo = false;
78
+ marcar("salvando…");
79
+ fetch("/api/arquivos/" + ID, {
80
+ method: "PUT",
81
+ keepalive: !!saindo,
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ titulo: $("#titulo").value, doc: doc }),
84
+ }).then(function () { if (!sujo) marcar("salvo", true); });
85
+ }
86
+
87
+ function agendarSalvar() {
88
+ sujo = true;
89
+ marcar("editando…");
90
+ clearTimeout(tSalvar);
91
+ tSalvar = setTimeout(function () { salvar(); }, 600);
92
+ }
93
+
94
+ /* ----------------------------------------------------------------- carga */
95
+
96
+ fetch("/api/arquivos/" + ID)
97
+ .then(function (r) { return r.json(); })
98
+ .then(function (a) {
99
+ if (a.erro) { document.body.innerHTML = "<p style='padding:40px'>Arquivo não encontrado.</p>"; return; }
100
+ ARQ = a;
101
+ doc = a.doc || {};
102
+ $("#titulo").value = a.titulo || "";
103
+ document.title = (a.titulo || "Editor") + " · " + F.nome;
104
+ $("#marca-ferr").textContent = F.nome;
105
+ $("#marca-ferr").style.color = F.cor;
106
+ montarTrilho();
107
+ montarTopo();
108
+ desenhar();
109
+ marcar("salvo", true);
110
+ });
111
+
112
+ /* ---------------------------------------------------------------- trilho */
113
+
114
+ function montarTrilho() {
115
+ var t = $("#trilho");
116
+ t.innerHTML = "";
117
+ (F.trilho || []).forEach(function (b) {
118
+ var btn = el("button", "", svgIcone(b.i));
119
+ btn.title = b.r;
120
+ btn.onclick = function () { inserir(b); };
121
+ t.appendChild(btn);
122
+ });
123
+ if (F.modelo !== "planilha") {
124
+ t.appendChild(el("div", "esp"));
125
+ var bm = el("button", "", svgIcone("pasta"));
126
+ bm.title = "Imagem do computador";
127
+ bm.onclick = inserirImagem;
128
+ t.appendChild(bm);
129
+ if (F.tempo) {
130
+ var bs = el("button", "", svgIcone("play2"));
131
+ bs.title = "Trilha sonora";
132
+ bs.onclick = function () { $("#folha-trilha").classList.add("on"); desenharTrilha(); };
133
+ t.appendChild(bs);
134
+ }
135
+ }
136
+
137
+ t.appendChild(el("div", "esp"));
138
+ var und = el("button", "", svgIcone("voltar")); und.title = "Desfazer (⌘Z)"; und.onclick = desfazer;
139
+ var red = el("button", "", svgIcone("refazer")); red.title = "Refazer"; red.onclick = refazer;
140
+ t.appendChild(und); t.appendChild(red);
141
+ }
142
+
143
+ /* insere uma imagem do computador na cena ou no bloco atual */
144
+ function inserirImagem() {
145
+ Midia.escolher({ tipo: "imagem" }).then(function (m) {
146
+ if (!m) return;
147
+ mudar(function () {
148
+ if (F.modelo === "documento") {
149
+ itens().push({ tipo: "imagem", src: m.url, legenda: "" });
150
+ } else if (cena().layout === "livre") {
151
+ itens().push({ tipo: "imagem", src: m.url, x: 120, y: 120, l: 720, a: 480 });
152
+ } else {
153
+ itens().push({ tipo: "imagem", src: m.url, ajuste: "cobrir" });
154
+ }
155
+ sel = itens().length - 1;
156
+ });
157
+ });
158
+ }
159
+
160
+ function inserir(b) {
161
+ if (SLUG === "grade") return inserirGrade(b.k);
162
+ mudar(function () {
163
+ var novo = JSON.parse(JSON.stringify(b.novo));
164
+ itens().push(novo);
165
+ sel = itens().length - 1;
166
+ });
167
+ }
168
+
169
+ function inserirGrade(k) {
170
+ mudar(function () {
171
+ var a = aba();
172
+ if (k === "linha") a.linhas.push(a.colunas.map(function () { return ""; }));
173
+ if (k === "coluna") {
174
+ a.colunas.push("Coluna " + (a.colunas.length + 1));
175
+ a.linhas.forEach(function (l) { l.push(""); });
176
+ }
177
+ if (k === "total") {
178
+ var n = a.linhas.length;
179
+ a.linhas.push(a.colunas.map(function (_, i) {
180
+ return i === 0 ? "Total" : "=SOMA(" + String.fromCharCode(65 + i) + "1:" +
181
+ String.fromCharCode(65 + i) + n + ")";
182
+ }));
183
+ }
184
+ if (k === "grafico") {
185
+ a.grafico = a.grafico ? null : { tipo: "barras", rotulo: a.colunas[0], valor: a.colunas[1] };
186
+ }
187
+ });
188
+ }
189
+
190
+ /* ------------------------------------------------------------------ topo */
191
+
192
+ function montarTopo() {
193
+ var cx = $("#topo-extra");
194
+ cx.innerHTML = "";
195
+
196
+ if (F.modelo !== "planilha") {
197
+ var tema = el("select", "campo");
198
+ tema.style.width = "128px";
199
+ TEMAS.forEach(function (t) {
200
+ var o = el("option", "", t); o.value = t;
201
+ if ((doc.tema || "meia-noite") === t) o.selected = true;
202
+ tema.appendChild(o);
203
+ });
204
+ tema.onchange = function () { mudar(function () { doc.tema = tema.value; }); };
205
+ cx.appendChild(tema);
206
+ }
207
+
208
+ if (F.modelo === "cena" && (F.layouts || []).length > 1) {
209
+ var lay = el("select", "campo");
210
+ lay.id = "sel-layout";
211
+ lay.style.width = "148px";
212
+ F.layouts.forEach(function (l) {
213
+ var o = el("option", "", l); o.value = l;
214
+ lay.appendChild(o);
215
+ });
216
+ lay.onchange = function () { mudar(function () { cena().layout = lay.value; }); };
217
+ cx.appendChild(lay);
218
+ }
219
+ }
220
+
221
+ /* --------------------------------------------------------------- desenho */
222
+
223
+ function desenhar() {
224
+ desenharPainel();
225
+ desenharPalco();
226
+ desenharInspetor();
227
+ if (F.tempo) desenharTempo();
228
+ var lay = $("#sel-layout");
229
+ if (lay) lay.value = cena().layout || F.layoutPadrao;
230
+ }
231
+
232
+ /* painel esquerdo ------------------------------------------------------- */
233
+
234
+ function desenharPainel() {
235
+ var lista = $("#painel-lista");
236
+ lista.innerHTML = "";
237
+
238
+ if (F.modelo === "cena") {
239
+ $("#painel-rotulo").textContent = SLUG === "corte" ? "Cenas" : (SLUG === "prisma" ? "Slides" : "Pranchas");
240
+ var W = doc.largura || 1920, H = doc.altura || 1080;
241
+ var minis = [];
242
+ (doc.cenas || []).forEach(function (c, i) {
243
+ var m = el("div", "miniatura" + (i === iCena ? " ativa" : ""));
244
+ var caixa = el("div", "caixa");
245
+ var mini = renderCena(c, doc, {});
246
+ caixa.appendChild(mini);
247
+ m.appendChild(caixa);
248
+ m.appendChild(el("div", "num", String(i + 1)));
249
+ m.onclick = function () { iCena = i; sel = null; desenhar(); };
250
+ lista.appendChild(m);
251
+ minis.push([caixa, mini]);
252
+ });
253
+ // a escala vem da largura real da caixa, que depende do painel
254
+ minis.forEach(function (par) {
255
+ var larg = par[0].clientWidth || 160;
256
+ par[0].style.height = Math.round(larg * H / W) + "px";
257
+ par[1].style.transform = "scale(" + (larg / W) + ")";
258
+ });
259
+
260
+ if (sel != null || (cena().elementos || []).length) {
261
+ var tit = el("div", "rotulo");
262
+ tit.style.cssText = "padding:12px 4px 6px";
263
+ tit.textContent = "Camadas";
264
+ 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
+ }
272
+ return;
273
+ }
274
+
275
+ if (F.modelo === "documento") {
276
+ $("#painel-rotulo").textContent = "Blocos";
277
+ (doc.blocos || []).forEach(function (b, i) {
278
+ var c = el("div", "camada" + (i === sel ? " ativa" : ""),
279
+ svgIcone(iconeDe(b)) + "<span>" + rotuloDe(b) + "</span>");
280
+ c.onclick = function () { sel = i; desenhar(); };
281
+ lista.appendChild(c);
282
+ });
283
+ return;
284
+ }
285
+
286
+ $("#painel-rotulo").textContent = "Abas";
287
+ (doc.abas || []).forEach(function (a, i) {
288
+ var c = el("div", "camada" + (i === iAba ? " ativa" : ""),
289
+ svgIcone("tabela") + "<span>" + (a.nome || "Planilha") + "</span>");
290
+ c.onclick = function () { iAba = i; sel = null; desenhar(); };
291
+ lista.appendChild(c);
292
+ });
293
+ }
294
+
295
+ function iconeDe(e) {
296
+ return { texto: "texto", lista: "lista", cartoes: "cartao", numeros: "numero",
297
+ tabela: "tabela", codigo: "codigo", citacao: "citacao", forma: "quadrado",
298
+ imagem: "imagem", icone: "icone", grafico: "grafico", no: "no",
299
+ conector: "conector", titulo: "titulo", secao: "texto", subsecao: "texto",
300
+ paragrafo: "texto", numerada: "numero", destaque: "cartao",
301
+ divisor: "linha" }[e.tipo] || "quadrado";
302
+ }
303
+
304
+ function rotuloDe(e) {
305
+ var t = e.texto || (e.itens && e.itens[0]) || (e.linhas && e.linhas[0] && e.linhas[0][0]) ||
306
+ e.nome || e.src || e.tipo;
307
+ t = String(t).replace(/\s+/g, " ").trim();
308
+ return (t.length > 22 ? t.slice(0, 22) + "…" : t) || e.tipo;
309
+ }
310
+
311
+ /* palco ---------------------------------------------------------------- */
312
+
313
+ function desenharPalco() {
314
+ var wrap = $("#palco-wrap");
315
+ wrap.innerHTML = "";
316
+
317
+ if (F.modelo === "planilha") return desenharPlanilha(wrap);
318
+ if (F.modelo === "documento") return desenharDocumento(wrap);
319
+
320
+ var W = doc.largura || 1920, H = doc.altura || 1080;
321
+ var disp = wrap.getBoundingClientRect();
322
+ escala = Math.min((disp.width - 52) / W, (disp.height - 52) / H, 1);
323
+
324
+ var quadro = el("div", "palco");
325
+ quadro.id = "quadro";
326
+ quadro.style.cssText = "width:" + W * escala + "px;height:" + H * escala + "px;position:relative";
327
+
328
+ var cn = renderCena(cena(), doc, {});
329
+ cn.style.transform = "scale(" + escala + ")";
330
+ cn.style.transformOrigin = "top left";
331
+ cn.style.position = "absolute";
332
+ quadro.appendChild(cn);
333
+ wrap.appendChild(quadro);
334
+
335
+ /* seleção e interação */
336
+ var els = Array.prototype.slice.call(cn.querySelectorAll(".el"));
337
+ var lista = cena().elementos || [];
338
+ var mapa = []; // nó -> índice no documento
339
+ var k = 0;
340
+ lista.forEach(function (e) {
341
+ if (e.tipo === "conector") { mapa.push(null); return; }
342
+ mapa.push(els[k++] || null);
343
+ });
344
+
345
+ mapa.forEach(function (no, i) {
346
+ if (!no) return;
347
+ no.style.cursor = "move";
348
+ no.addEventListener("mousedown", function (ev) {
349
+ ev.stopPropagation();
350
+ sel = i;
351
+ desenharPainel(); desenharInspetor(); marcarSelecao(quadro, no);
352
+ if (lista[i].x != null || lista[i].y != null) arrastar(ev, i, quadro);
353
+ });
354
+ no.addEventListener("dblclick", function (ev) {
355
+ ev.stopPropagation();
356
+ editarNoLugar(no, i);
357
+ });
358
+ });
359
+
360
+ quadro.addEventListener("mousedown", function () { sel = null; desenhar(); });
361
+ if (sel != null && mapa[sel]) marcarSelecao(quadro, mapa[sel]);
362
+
363
+ barraPalco(W, H);
364
+ }
365
+
366
+ function marcarSelecao(quadro, no) {
367
+ var antigos = quadro.querySelectorAll(".selecao,.alca");
368
+ Array.prototype.forEach.call(antigos, function (n) { n.remove(); });
369
+
370
+ var r = no.getBoundingClientRect(), q = quadro.getBoundingClientRect();
371
+ var x = r.left - q.left, y = r.top - q.top;
372
+
373
+ var caixa = el("div", "selecao");
374
+ caixa.style.cssText = "left:" + x + "px;top:" + y + "px;width:" + r.width + "px;height:" + r.height + "px";
375
+ quadro.appendChild(caixa);
376
+
377
+ var e2 = (cena().elementos || [])[sel];
378
+ if (!e2 || (e2.x == null && e2.y == null)) return;
379
+
380
+ [["nw", 0, 0], ["ne", 1, 0], ["sw", 0, 1], ["se", 1, 1]].forEach(function (h) {
381
+ var a = el("div", "alca " + h[0]);
382
+ a.style.left = (x + h[1] * r.width - 5) + "px";
383
+ a.style.top = (y + h[2] * r.height - 5) + "px";
384
+ a.addEventListener("mousedown", function (ev) {
385
+ ev.stopPropagation(); ev.preventDefault();
386
+ redimensionar(ev, sel, h[0], quadro);
387
+ });
388
+ quadro.appendChild(a);
389
+ });
390
+ }
391
+
392
+ function arrastar(ev, i, quadro) {
393
+ ev.preventDefault();
394
+ var e2 = (cena().elementos || [])[i];
395
+ var x0 = ev.clientX, y0 = ev.clientY;
396
+ var ix = e2.x || 0, iy = e2.y || 0;
397
+ var no = quadro.querySelectorAll(".el")[i];
398
+ instantaneo();
399
+
400
+ function mover(m) {
401
+ e2.x = Math.round(ix + (m.clientX - x0) / escala);
402
+ e2.y = Math.round(iy + (m.clientY - y0) / escala);
403
+ if (no) { no.style.left = e2.x + "px"; no.style.top = e2.y + "px"; }
404
+ marcarSelecao(quadro, no);
405
+ }
406
+ function soltar() {
407
+ document.removeEventListener("mousemove", mover);
408
+ document.removeEventListener("mouseup", soltar);
409
+ desenhar(); agendarSalvar();
410
+ }
411
+ document.addEventListener("mousemove", mover);
412
+ document.addEventListener("mouseup", soltar);
413
+ }
414
+
415
+ function redimensionar(ev, i, canto, quadro) {
416
+ var e2 = (cena().elementos || [])[i];
417
+ var x0 = ev.clientX, y0 = ev.clientY;
418
+ var ix = e2.x || 0, iy = e2.y || 0, il = e2.l || 200, ia = e2.a || 100;
419
+ instantaneo();
420
+
421
+ function mover(m) {
422
+ var dx = (m.clientX - x0) / escala, dy = (m.clientY - y0) / escala;
423
+ if (canto.indexOf("e") >= 0) e2.l = Math.max(24, Math.round(il + dx));
424
+ if (canto.indexOf("w") >= 0) { e2.l = Math.max(24, Math.round(il - dx)); e2.x = Math.round(ix + dx); }
425
+ if (canto.charAt(0) === "s") e2.a = Math.max(24, Math.round(ia + dy));
426
+ if (canto.charAt(0) === "n") { e2.a = Math.max(24, Math.round(ia - dy)); e2.y = Math.round(iy + dy); }
427
+ desenharPalco();
428
+ }
429
+ function soltar() {
430
+ document.removeEventListener("mousemove", mover);
431
+ document.removeEventListener("mouseup", soltar);
432
+ desenhar(); agendarSalvar();
433
+ }
434
+ document.addEventListener("mousemove", mover);
435
+ document.addEventListener("mouseup", soltar);
436
+ }
437
+
438
+ function editarNoLugar(no, i) {
439
+ var e2 = itens()[i];
440
+ if (!e2 || (e2.tipo !== "texto" && e2.tipo !== "no" && e2.tipo !== "paragrafo" &&
441
+ e2.tipo !== "titulo" && e2.tipo !== "secao" && e2.tipo !== "subsecao")) return;
442
+ no.contentEditable = "true";
443
+ no.style.outline = "2px solid var(--primo)";
444
+ no.focus();
445
+ document.execCommand && document.execCommand("selectAll", false, null);
446
+ no.addEventListener("blur", function () {
447
+ no.contentEditable = "false";
448
+ no.style.outline = "";
449
+ mudar(function () { e2.texto = no.innerText.trim(); });
450
+ }, { once: true });
451
+ no.addEventListener("keydown", function (ev) {
452
+ if (ev.key === "Escape" || (ev.key === "Enter" && !ev.shiftKey)) { ev.preventDefault(); no.blur(); }
453
+ ev.stopPropagation();
454
+ });
455
+ }
456
+
457
+ function barraPalco(W, H) {
458
+ var b = $("#palco-barra");
459
+ b.innerHTML = "";
460
+ b.appendChild(el("span", "", F.modelo === "cena"
461
+ ? W + " × " + H + " · " + Math.round(escala * 100) + "%"
462
+ : ""));
463
+ var esp = el("div"); esp.style.flex = "1"; b.appendChild(esp);
464
+
465
+ if (F.modelo === "cena") {
466
+ var addC = el("button", "btn", svgIcone("mais") + " Nova cena");
467
+ addC.onclick = function () {
468
+ mudar(function () {
469
+ doc.cenas.splice(iCena + 1, 0, { layout: F.layoutPadrao, elementos: [] });
470
+ iCena++; sel = null;
471
+ });
472
+ };
473
+ var dup = el("button", "btn", svgIcone("duplicar"));
474
+ dup.title = "Duplicar cena";
475
+ dup.onclick = function () {
476
+ mudar(function () {
477
+ doc.cenas.splice(iCena + 1, 0, JSON.parse(JSON.stringify(cena())));
478
+ iCena++;
479
+ });
480
+ };
481
+ var rem = el("button", "btn perigo", svgIcone("lixeira"));
482
+ rem.title = "Remover cena";
483
+ rem.onclick = function () {
484
+ if ((doc.cenas || []).length < 2) return;
485
+ mudar(function () { doc.cenas.splice(iCena, 1); iCena = Math.max(0, iCena - 1); sel = null; });
486
+ };
487
+ var png = el("button", "btn", svgIcone("imagem") + " PNG");
488
+ png.title = "Exportar esta cena como imagem";
489
+ png.onclick = function () {
490
+ var alvo = document.querySelector("#quadro .cena");
491
+ if (!alvo) return;
492
+ png.textContent = "gerando…";
493
+ Exportar.imagem(alvo, {
494
+ largura: doc.largura || 1920, altura: doc.altura || 1080,
495
+ escala: 2, nome: ($("#titulo").value || "arte") + "-" + (iCena + 1),
496
+ }).then(function () {
497
+ png.innerHTML = svgIcone("imagem") + " PNG";
498
+ }).catch(function (e) {
499
+ png.innerHTML = svgIcone("imagem") + " PNG";
500
+ alert("não consegui gerar o PNG: " + e.message);
501
+ });
502
+ };
503
+
504
+ b.appendChild(addC); b.appendChild(dup); b.appendChild(rem); b.appendChild(png);
505
+ }
506
+ }
507
+
508
+ /* documento ------------------------------------------------------------ */
509
+
510
+ function desenharDocumento(wrap) {
511
+ var quadro = el("div", "palco");
512
+ quadro.id = "quadro";
513
+ var folha = renderProsa(doc);
514
+ var disp = wrap.getBoundingClientRect();
515
+ escala = Math.min((disp.width - 52) / 794, 1);
516
+ folha.style.transform = "scale(" + escala + ")";
517
+ folha.style.transformOrigin = "top left";
518
+ quadro.style.cssText = "width:" + 794 * escala + "px;position:relative";
519
+ quadro.appendChild(folha);
520
+ wrap.appendChild(quadro);
521
+ wrap.style.alignContent = "start";
522
+
523
+ var filhos = Array.prototype.slice.call(folha.children);
524
+ var deslocamento = doc.capa ? 2 : 0;
525
+ (doc.blocos || []).forEach(function (b, i) {
526
+ var no = filhos[i + deslocamento];
527
+ if (!no) return;
528
+ no.style.cursor = "pointer";
529
+ if (i === sel) no.style.outline = "2px solid var(--primo)";
530
+ no.addEventListener("click", function (ev) { ev.stopPropagation(); sel = i; desenhar(); });
531
+ no.addEventListener("dblclick", function (ev) { ev.stopPropagation(); editarNoLugar(no, i); });
532
+ });
533
+
534
+ var texto = (doc.blocos || []).map(function (x) {
535
+ return [x.texto, (x.itens || []).join(" "), (x.linhas || []).join(" "), x.titulo]
536
+ .filter(Boolean).join(" ");
537
+ }).join(" ");
538
+ var palavras = texto.trim().split(/\s+/).filter(Boolean).length;
539
+ var minutos = Math.max(1, Math.round(palavras / 200));
540
+
541
+ var b2 = $("#palco-barra");
542
+ b2.innerHTML = "";
543
+ b2.appendChild(el("span", "", (doc.blocos || []).length + " blocos · " +
544
+ palavras.toLocaleString("pt-BR") + " palavras · ~" + minutos + " min de leitura · A4"));
545
+ }
546
+
547
+ /* planilha ------------------------------------------------------------- */
548
+
549
+ function desenharPlanilha(wrap) {
550
+ wrap.style.alignContent = "start";
551
+ var caixa = el("div");
552
+ caixa.style.cssText = "width:100%;max-width:1180px;overflow:auto;border:1px solid var(--linha);border-radius:12px";
553
+ var tabela = renderGrade(doc, { aba: iAba });
554
+ caixa.appendChild(tabela);
555
+ wrap.appendChild(caixa);
556
+
557
+ Array.prototype.forEach.call(tabela.querySelectorAll("td[data-l]"), function (td) {
558
+ td.contentEditable = "true";
559
+ td.addEventListener("focus", function () {
560
+ var l = +td.dataset.l, c = +td.dataset.c;
561
+ td.textContent = (aba().linhas[l] || [])[c] != null ? aba().linhas[l][c] : "";
562
+ });
563
+ td.addEventListener("blur", function () {
564
+ var l = +td.dataset.l, c = +td.dataset.c;
565
+ var v = td.textContent.trim();
566
+ var atual = aba().linhas[l][c];
567
+ if (String(atual) === v) { desenharPalco(); return; }
568
+ mudar(function () {
569
+ aba().linhas[l][c] = /^-?[\d.,]+$/.test(v) && v !== "" ? parseFloat(v.replace(",", ".")) : v;
570
+ });
571
+ });
572
+ td.addEventListener("keydown", function (ev) {
573
+ if (ev.key === "Enter") { ev.preventDefault(); td.blur(); }
574
+ ev.stopPropagation();
575
+ });
576
+ });
577
+
578
+ var b = $("#palco-barra");
579
+ b.innerHTML = "";
580
+ b.appendChild(el("span", "", aba().linhas.length + " linhas × " + aba().colunas.length + " colunas"));
581
+ var esp = el("div"); esp.style.flex = "1"; b.appendChild(esp);
582
+ var nova = el("button", "btn", svgIcone("mais") + " Aba");
583
+ nova.onclick = function () {
584
+ mudar(function () {
585
+ doc.abas.push({ nome: "Planilha " + (doc.abas.length + 1), colunas: ["Coluna A", "Coluna B"], linhas: [["", ""]] });
586
+ iAba = doc.abas.length - 1;
587
+ });
588
+ };
589
+ b.appendChild(nova);
590
+ }
591
+
592
+ /* linha do tempo (Corte) ------------------------------------------------ */
593
+
594
+ function desenharTempo() {
595
+ var t = $("#tempo");
596
+ t.style.display = "flex";
597
+ t.innerHTML = "";
598
+ var total = 0;
599
+ var W = doc.largura || 1920, H = doc.altura || 1080;
600
+ (doc.cenas || []).forEach(function (c, i) {
601
+ var dur = c.duracao || 4;
602
+ total += dur;
603
+ var larg = Math.max(70, dur * 26);
604
+ var clipe = el("div", "clipe" + (i === iCena ? " ativo" : ""));
605
+ clipe.style.width = larg + "px";
606
+ var caixa = el("div", "caixa");
607
+ var mini = renderCena(c, doc, {});
608
+ // cobre o clipe inteiro sem distorcer
609
+ mini.style.transform = "scale(" + Math.max(larg / W, 62 / H) + ")";
610
+ caixa.appendChild(mini);
611
+ clipe.appendChild(caixa);
612
+ clipe.appendChild(el("div", "dur", dur + "s"));
613
+ clipe.onclick = function () { iCena = i; sel = null; desenhar(); };
614
+ t.appendChild(clipe);
615
+ });
616
+ var info = el("div");
617
+ info.style.cssText = "margin-left:12px;font-size:12px;color:var(--fraco2);white-space:nowrap";
618
+ info.textContent = "total " + total.toFixed(1) + "s";
619
+ t.appendChild(info);
620
+ }
621
+
622
+ /* trilha sonora (Corte) -------------------------------------------------- */
623
+
624
+ var previa = null;
625
+
626
+ function pararPrevia() {
627
+ if (previa) { previa.parar(); previa = null; }
628
+ }
629
+
630
+ function desenharTrilha() {
631
+ var caixa = $("#trilha-caixa");
632
+ var t = doc.trilha || {};
633
+ caixa.innerHTML = "";
634
+ caixa.appendChild(el("h3", "", "Trilha sonora"));
635
+
636
+ var nota = el("p", "", "Música gerada aqui mesmo, ou um arquivo seu. " +
637
+ "Não baixo faixa de YouTube nem Spotify — use áudio que você tem direito de usar.");
638
+ nota.style.cssText = "color:var(--fraco);font-size:12.5px;margin:0 0 18px;line-height:1.6";
639
+ caixa.appendChild(nota);
640
+
641
+ /* geradas */
642
+ caixa.appendChild(el("div", "rotulo", "Geradas pelo estúdio"));
643
+ var grade = el("div", "trilha-grade");
644
+ nomesEstilosTrilha().forEach(function (nome) {
645
+ var E = ESTILOS_TRILHA[nome];
646
+ var c = el("div", "trilha-item" + (t.tipo === "gerada" && t.estilo === nome ? " ativo" : ""));
647
+ c.innerHTML = "<b>" + E.rotulo + "</b><span>" + E.bpm + " BPM</span>" +
648
+ '<button class="ouvir" title="Ouvir">' + svgIcone("play") + "</button>";
649
+ c.onclick = function (ev) {
650
+ if (ev.target.closest(".ouvir")) {
651
+ ev.stopPropagation();
652
+ pararPrevia();
653
+ previa = criarTrilha({ tipo: "gerada", estilo: nome, volume: 0.6 });
654
+ if (previa) { previa.tocar(); setTimeout(pararPrevia, 9000); }
655
+ return;
656
+ }
657
+ mudar(function () {
658
+ doc.trilha = { tipo: "gerada", estilo: nome, volume: t.volume != null ? t.volume : 0.7 };
659
+ });
660
+ desenharTrilha();
661
+ };
662
+ grade.appendChild(c);
663
+ });
664
+ caixa.appendChild(grade);
665
+
666
+ /* arquivo */
667
+ var r2 = el("div", "rotulo", "Do seu computador");
668
+ r2.style.marginTop = "22px";
669
+ caixa.appendChild(r2);
670
+
671
+ var linhaArq = el("div");
672
+ linhaArq.style.cssText = "display:flex;gap:9px;align-items:center;margin-top:10px;flex-wrap:wrap";
673
+ var bSub = el("button", "btn primo", "Escolher áudio");
674
+ bSub.onclick = function () {
675
+ Midia.escolher({ tipo: "audio" }).then(function (m) {
676
+ if (!m) return;
677
+ mudar(function () {
678
+ doc.trilha = { tipo: "arquivo", src: m.url, nome: m.nome,
679
+ volume: t.volume != null ? t.volume : 0.7, inicio: 0 };
680
+ });
681
+ desenharTrilha();
682
+ });
683
+ };
684
+ linhaArq.appendChild(bSub);
685
+
686
+ var urlDireta = el("input", "campo");
687
+ urlDireta.placeholder = "…ou cole a URL de um arquivo de áudio";
688
+ urlDireta.style.cssText = "flex:1;min-width:200px";
689
+ urlDireta.value = t.tipo === "arquivo" && t.src && t.src.indexOf("/midia/") !== 0 ? t.src : "";
690
+ urlDireta.onchange = function () {
691
+ if (!urlDireta.value.trim()) return;
692
+ mudar(function () {
693
+ doc.trilha = { tipo: "arquivo", src: urlDireta.value.trim(), volume: 0.7, inicio: 0 };
694
+ });
695
+ desenharTrilha();
696
+ };
697
+ linhaArq.appendChild(urlDireta);
698
+ caixa.appendChild(linhaArq);
699
+
700
+ /* estado atual */
701
+ var atual = el("div");
702
+ atual.style.cssText = "margin-top:22px;padding-top:18px;border-top:1px solid var(--linha)";
703
+ if (!t.tipo) {
704
+ atual.innerHTML = '<span style="color:var(--fraco2);font-size:13px">Sem trilha por enquanto.</span>';
705
+ } else {
706
+ atual.innerHTML = '<div class="rotulo" style="margin-bottom:10px">Em uso</div>' +
707
+ '<div style="font-size:13.5px;margin-bottom:12px">' +
708
+ (t.tipo === "gerada" ? "Gerada · " + (ESTILOS_TRILHA[t.estilo] || {}).rotulo
709
+ : "Arquivo · " + (t.nome || t.src)) + "</div>";
710
+
711
+ var vol = el("div", "linha-campo");
712
+ vol.appendChild(el("span", "", "Volume"));
713
+ var sl = el("input");
714
+ sl.type = "range"; sl.min = 0; sl.max = 100;
715
+ sl.value = Math.round((t.volume != null ? t.volume : 0.7) * 100);
716
+ sl.oninput = function () { doc.trilha.volume = sl.value / 100; agendarSalvar(); };
717
+ vol.appendChild(sl);
718
+ atual.appendChild(vol);
719
+
720
+ var acoes = el("div");
721
+ acoes.style.cssText = "display:flex;gap:9px;margin-top:14px";
722
+ var bAjustar = el("button", "btn", "Ajustar cenas ao áudio");
723
+ bAjustar.title = "Distribui a duração das cenas para preencher a faixa";
724
+ bAjustar.onclick = ajustarAoAudio;
725
+ var bTirar = el("button", "btn perigo", "Remover trilha");
726
+ bTirar.onclick = function () {
727
+ mudar(function () { delete doc.trilha; });
728
+ desenharTrilha();
729
+ };
730
+ acoes.appendChild(bAjustar);
731
+ acoes.appendChild(bTirar);
732
+ atual.appendChild(acoes);
733
+ }
734
+ caixa.appendChild(atual);
735
+
736
+ var rodape = el("div", "rodape");
737
+ var bFechar = el("button", "btn", "Fechar");
738
+ bFechar.onclick = function () { pararPrevia(); $("#folha-trilha").classList.remove("on"); };
739
+ rodape.appendChild(bFechar);
740
+ caixa.appendChild(rodape);
741
+ }
742
+
743
+ function ajustarAoAudio() {
744
+ var t = doc.trilha || {};
745
+ if (t.tipo === "gerada") {
746
+ return alert("A trilha gerada não tem fim — ela acompanha o vídeo. " +
747
+ "Ajuste as durações das cenas como quiser.");
748
+ }
749
+ var som = new Audio(t.src);
750
+ som.addEventListener("loadedmetadata", function () {
751
+ var d = som.duration;
752
+ if (!isFinite(d) || !d) return alert("não consegui ler a duração do áudio");
753
+ var n = (doc.cenas || []).length || 1;
754
+ mudar(function () {
755
+ doc.cenas.forEach(function (c) { c.duracao = Math.round((d / n) * 10) / 10; });
756
+ });
757
+ desenharTrilha();
758
+ });
759
+ som.addEventListener("error", function () { alert("não consegui carregar o áudio"); });
760
+ }
761
+
762
+ /* inspetor -------------------------------------------------------------- */
763
+
764
+ function grupo(titulo) {
765
+ var g = el("div", "grupo");
766
+ g.appendChild(el("div", "rotulo", titulo));
767
+ return g;
768
+ }
769
+
770
+ function linha(rotulo, controle) {
771
+ var l = el("div", "linha-campo");
772
+ l.appendChild(el("span", "", rotulo));
773
+ l.appendChild(controle);
774
+ return l;
775
+ }
776
+
777
+ function inp(valor, aoMudar, tipo) {
778
+ var i = el("input", "campo");
779
+ i.type = tipo || "text";
780
+ i.value = valor == null ? "" : valor;
781
+ i.oninput = function () { aoMudar(i.type === "number" ? (i.value === "" ? null : +i.value) : i.value); };
782
+ return i;
783
+ }
784
+
785
+ function area(valor, aoMudar, linhas) {
786
+ var t = el("textarea", "campo");
787
+ t.rows = linhas || 4;
788
+ t.value = valor == null ? "" : valor;
789
+ t.oninput = function () { aoMudar(t.value); };
790
+ return t;
791
+ }
792
+
793
+ function selecao(valores, atual, aoMudar) {
794
+ var s = el("select", "campo");
795
+ valores.forEach(function (v) {
796
+ var o = el("option", "", String(v));
797
+ o.value = v;
798
+ if (String(v) === String(atual)) o.selected = true;
799
+ s.appendChild(o);
800
+ });
801
+ s.onchange = function () { aoMudar(s.value); };
802
+ return s;
803
+ }
804
+
805
+ function seletorCor(valor, aoMudar) {
806
+ var caixa = el("div");
807
+ caixa.style.cssText = "display:grid;gap:6px";
808
+ var p = el("div", "cores");
809
+ PALETA.forEach(function (c) {
810
+ var b = el("button");
811
+ b.style.background = c;
812
+ b.onclick = function () { aoMudar(c); };
813
+ p.appendChild(b);
814
+ });
815
+ caixa.appendChild(p);
816
+ caixa.appendChild(inp(valor, aoMudar));
817
+ return caixa;
818
+ }
819
+
820
+ function edita(campo, valor) {
821
+ var alvo = itens()[sel];
822
+ if (!alvo) return;
823
+ mudar(function () {
824
+ if (valor === "" || valor === null) delete alvo[campo];
825
+ else alvo[campo] = valor;
826
+ });
827
+ }
828
+
829
+ function desenharInspetor() {
830
+ var ins = $("#inspetor");
831
+ ins.innerHTML = "";
832
+
833
+ /* propriedades do arquivo */
834
+ var g0 = grupo(F.modelo === "cena" ? "Cena" : (F.modelo === "documento" ? "Documento" : "Planilha"));
835
+
836
+ if (F.modelo === "cena") {
837
+ var c = cena();
838
+ g0.appendChild(linha("Layout", selecao(F.layouts, c.layout || F.layoutPadrao,
839
+ function (v) { mudar(function () { c.layout = v; }); })));
840
+ g0.appendChild(linha("Fundo", selecao(["", "malha", "brilho", "grade", "pontos", "suave", "vinheta"],
841
+ typeof c.fundo === "string" ? c.fundo : "", function (v) { mudar(function () { if (v) c.fundo = v; else delete c.fundo; }); })));
842
+ g0.appendChild(linha("Fundo (extra)", selecao(
843
+ ["", "aurora", "raios", "ondas", "nevoa", "xadrez"],
844
+ ["aurora", "raios", "ondas", "nevoa", "xadrez"].indexOf(c.fundo) >= 0 ? c.fundo : "",
845
+ function (v) { mudar(function () { if (v) c.fundo = v; }); })));
846
+ g0.appendChild(linha("Cor fundo", inp(typeof c.fundo === "string" && c.fundo.charAt(0) === "#" ? c.fundo : "",
847
+ function (v) { mudar(function () { if (v) c.fundo = v; else delete c.fundo; }); })));
848
+
849
+ var bFoto = el("button", "btn", svgIcone("imagem") + " Foto de fundo");
850
+ bFoto.style.width = "100%";
851
+ bFoto.onclick = function () {
852
+ Midia.escolher({ tipo: "imagem" }).then(function (m) {
853
+ if (m) mudar(function () { c.fundo = { imagem: m.url, escurecer: 0.55 }; });
854
+ });
855
+ };
856
+ g0.appendChild(linha("Imagem", bFoto));
857
+
858
+ g0.appendChild(linha("Acabam.", selecao(["", "grao", "vinheta", "scanlines", "luz", "brilho", "moldura"],
859
+ c.acabamento || "", function (v) { mudar(function () { if (v) c.acabamento = v; else delete c.acabamento; }); })));
860
+ g0.appendChild(linha("Destaque", seletorCor(c.tom || "",
861
+ function (v) { mudar(function () { if (v) c.tom = v; else delete c.tom; }); })));
862
+
863
+ 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("Transição", selecao(
866
+ ["", "corte", "fade", "deslizar", "subir", "zoom", "limpar", "persiana", "flash"],
867
+ c.transicao || "", function (v) { mudar(function () { if (v) c.transicao = v; else delete c.transicao; }); })));
868
+ }
869
+ if (SLUG === "traco") {
870
+ g0.appendChild(linha("Arranjo", selecao(["", "horizontal", "vertical"], c.auto || "",
871
+ function (v) { mudar(function () { if (v) c.auto = v; else delete c.auto; }); })));
872
+ }
873
+ var dim = el("div", "dois");
874
+ dim.appendChild(inp(doc.largura || 1920, function (v) { mudar(function () { doc.largura = v; }); }, "number"));
875
+ dim.appendChild(inp(doc.altura || 1080, function (v) { mudar(function () { doc.altura = v; }); }, "number"));
876
+ g0.appendChild(linha("Tela", dim));
877
+ if (F.tamanhos) {
878
+ g0.appendChild(linha("Preset", selecao([""].concat(F.tamanhos.map(function (t) { return t[0]; })), "",
879
+ function (v) {
880
+ var t = F.tamanhos.filter(function (x) { return x[0] === v; })[0];
881
+ if (t) mudar(function () { doc.largura = t[1]; doc.altura = t[2]; });
882
+ })));
883
+ }
884
+ } else if (F.modelo === "planilha") {
885
+ g0.appendChild(linha("Nome", inp(aba().nome, function (v) { mudar(function () { aba().nome = v; }); })));
886
+ g0.appendChild(linha("Tema", selecao(TEMAS, doc.tema || "claro", function (v) { mudar(function () { doc.tema = v; }); })));
887
+ } else {
888
+ g0.appendChild(linha("Tema", selecao(TEMAS, doc.tema || "papel", function (v) { mudar(function () { doc.tema = v; }); })));
889
+ }
890
+ ins.appendChild(g0);
891
+
892
+ if (F.modelo === "planilha") { colunasInspetor(ins); return; }
893
+ if (F.modelo === "cena" && (SLUG === "corte" || SLUG === "prisma")) narracaoInspetor(ins);
894
+
895
+ var e = itens()[sel];
896
+ if (!e) {
897
+ ins.appendChild(el("div", "nada", "Clique em um elemento do palco<br>para editar as propriedades."));
898
+ return;
899
+ }
900
+
901
+ /* ações do elemento */
902
+ var ga = grupo(e.tipo);
903
+ var ac = el("div", "opcoes");
904
+ [["subir", "Subir", -1], ["descer", "Descer", 1]].forEach(function (a) {
905
+ var b = el("button", "", svgIcone(a[0]));
906
+ b.title = a[1];
907
+ b.onclick = function () {
908
+ var lista = itens(), j = sel + a[2];
909
+ if (j < 0 || j >= lista.length) return;
910
+ mudar(function () { var t = lista[sel]; lista[sel] = lista[j]; lista[j] = t; sel = j; });
911
+ };
912
+ ac.appendChild(b);
913
+ });
914
+ var bd = el("button", "", svgIcone("duplicar")); bd.title = "Duplicar";
915
+ bd.onclick = function () { mudar(function () { itens().splice(sel + 1, 0, JSON.parse(JSON.stringify(e))); sel++; }); };
916
+ var bx = el("button", "", svgIcone("lixeira")); bx.title = "Remover";
917
+ bx.onclick = function () { mudar(function () { itens().splice(sel, 1); sel = null; }); };
918
+ ac.appendChild(bd); ac.appendChild(bx);
919
+ ga.appendChild(ac);
920
+ ins.appendChild(ga);
921
+
922
+ camposDoTipo(ins, e);
923
+
924
+ /* posição, para elementos livres */
925
+ if (F.modelo === "cena") {
926
+ var gp = grupo("Posição");
927
+ var xy = el("div", "dois");
928
+ xy.appendChild(inp(e.x, function (v) { edita("x", v); }, "number"));
929
+ xy.appendChild(inp(e.y, function (v) { edita("y", v); }, "number"));
930
+ gp.appendChild(linha("X / Y", xy));
931
+ var la = el("div", "dois");
932
+ la.appendChild(inp(e.l, function (v) { edita("l", v); }, "number"));
933
+ la.appendChild(inp(e.a, function (v) { edita("a", v); }, "number"));
934
+ 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("Opacid.", inp(e.opacidade != null ? e.opacidade : 1, function (v) { edita("opacidade", v); }, "number")));
937
+ gp.appendChild(linha("Entrada", selecao(
938
+ ["", "subir", "surgir", "esquerda", "direita", "zoom", "desfoque", "cair",
939
+ "girar", "saltar", "revelar", "cortina", "elastico", "palavra", "nenhuma"],
940
+ e.anim || "", function (v) { edita("anim", v); })));
941
+ if (e.tipo === "numeros") {
942
+ gp.appendChild(linha("Contar", selecao(["não", "sim"], e.contar ? "sim" : "não",
943
+ function (v) { edita("contar", v === "sim" ? true : ""); })));
944
+ }
945
+ ins.appendChild(gp);
946
+ if (e.x == null && e.y == null) {
947
+ var dica = el("div", "nada", "Este elemento segue o layout da cena.<br>Defina X e Y para posicionar à mão.");
948
+ dica.style.padding = "10px 16px";
949
+ ins.appendChild(dica);
950
+ }
951
+ }
952
+ }
953
+
954
+ function camposDoTipo(ins, e) {
955
+ var g = grupo("Conteúdo");
956
+
957
+ if (e.tipo === "texto" || e.tipo === "no" || ["titulo", "secao", "subsecao", "paragrafo", "apoio", "chapeu"].indexOf(e.tipo) >= 0) {
958
+ g.appendChild(linha("Texto", area(e.texto, function (v) { edita("texto", v); }, 3)));
959
+ }
960
+ if (e.tipo === "texto") {
961
+ g.appendChild(linha("Papel", selecao(["chapeu", "titulo", "subtitulo", "apoio", "corpo", "legenda"],
962
+ e.papel || "corpo", function (v) { edita("papel", v); })));
963
+ g.appendChild(linha("Tamanho", inp(e.tamanho, function (v) { edita("tamanho", v); }, "number")));
964
+ g.appendChild(linha("Peso", selecao(["", 300, 400, 500, 600, 700, 800], e.peso || "", function (v) { edita("peso", v ? +v : ""); })));
965
+ g.appendChild(linha("Alinhar", selecao(["", "left", "center", "right"], e.alinhar || "", function (v) { edita("alinhar", v); })));
966
+ g.appendChild(linha("Estilo", selecao(
967
+ ["", "gradiente", "contorno", "contorno-tom", "sombra", "brilho", "relevo", "marcador", "fita", "caixa"],
968
+ e.estilo || "", function (v) { edita("estilo", v); })));
969
+ g.appendChild(linha("Cor", seletorCor(e.cor, function (v) { edita("cor", v); })));
970
+ g.appendChild(linha("Maiúsc.", selecao(["não", "sim"], e.maiusculas ? "sim" : "não",
971
+ function (v) { edita("maiusculas", v === "sim" ? true : ""); })));
972
+ }
973
+ if (e.tipo === "lista" || e.tipo === "numerada") {
974
+ g.appendChild(linha("Itens", area((e.itens || []).join("\n"),
975
+ function (v) { edita("itens", v.split("\n").filter(function (x) { return x.trim(); })); }, 6)));
976
+ }
977
+ if (e.tipo === "cartoes" || e.tipo === "numeros") {
978
+ g.appendChild(linha("Itens", area((e.itens || []).map(function (i) {
979
+ return typeof i === "string" ? i : [i.titulo || i.valor, i.texto || i.rotulo].join(" | ");
980
+ }).join("\n"), function (v) { edita("itens", v.split("\n").filter(function (x) { return x.trim(); })); }, 6)));
981
+ g.appendChild(linha("Colunas", inp(e.colunas, function (v) { edita("colunas", v); }, "number")));
982
+ }
983
+ if (e.tipo === "tabela") {
984
+ g.appendChild(linha("Linhas", area((e.linhas || []).map(function (l) { return l.join(" | "); }).join("\n"),
985
+ function (v) {
986
+ edita("linhas", v.split("\n").filter(function (x) { return x.trim(); })
987
+ .map(function (l) { return l.split("|").map(function (c) { return c.trim(); }); }));
988
+ }, 7)));
989
+ }
990
+ if (e.tipo === "codigo") g.appendChild(linha("Código", area(e.texto, function (v) { edita("texto", v); }, 8)));
991
+ if (e.tipo === "citacao") {
992
+ g.appendChild(linha("Frase", area(e.texto, function (v) { edita("texto", v); }, 4)));
993
+ g.appendChild(linha("Autor", inp(e.autor, function (v) { edita("autor", v); })));
994
+ }
995
+ if (e.tipo === "destaque") {
996
+ g.appendChild(linha("Título", inp(e.titulo, function (v) { edita("titulo", v); })));
997
+ g.appendChild(linha("Texto", area(e.texto, function (v) { edita("texto", v); }, 3)));
998
+ }
999
+ if (e.tipo === "imagem") {
1000
+ var bTrocar = el("button", "btn", svgIcone("pasta") + " Do computador");
1001
+ bTrocar.style.width = "100%";
1002
+ bTrocar.onclick = function () {
1003
+ Midia.escolher({ tipo: "imagem" }).then(function (m) { if (m) edita("src", m.url); });
1004
+ };
1005
+ g.appendChild(linha("Arquivo", bTrocar));
1006
+ g.appendChild(linha("URL", inp(e.src, function (v) { edita("src", v); })));
1007
+ g.appendChild(linha("Ajuste", selecao(["cobrir", "conter"], e.ajuste || "cobrir", function (v) { edita("ajuste", v); })));
1008
+ g.appendChild(linha("Filtro", selecao(["", "cinza", "sepia", "contraste", "desbotado"],
1009
+ e.filtro || "", function (v) { edita("filtro", v); })));
1010
+ g.appendChild(linha("Movimento", selecao(["", "aproximar", "afastar", "esquerda", "direita", "cima"],
1011
+ e.movimento || "", function (v) { edita("movimento", v); })));
1012
+ g.appendChild(linha("Raio", inp(e.raio, function (v) { edita("raio", v); }, "number")));
1013
+ g.appendChild(linha("Legenda", inp(e.legenda, function (v) { edita("legenda", v); })));
1014
+ }
1015
+ if (e.tipo === "icone") {
1016
+ g.appendChild(linha("Ícone", selecao(nomesIcones(), e.nome || "estrela", function (v) { edita("nome", v); })));
1017
+ g.appendChild(linha("Tamanho", inp(e.tamanho || 90, function (v) { edita("tamanho", v); }, "number")));
1018
+ g.appendChild(linha("Cor", seletorCor(e.cor, function (v) { edita("cor", v); })));
1019
+ }
1020
+ if (e.tipo === "forma") {
1021
+ g.appendChild(linha("Forma", selecao(["retangulo", "circulo", "triangulo", "linha", "seta"],
1022
+ e.forma || "retangulo", function (v) { edita("forma", v); })));
1023
+ g.appendChild(linha("Cor", seletorCor(e.preenchimento, function (v) { edita("preenchimento", v); })));
1024
+ g.appendChild(linha("Raio", inp(e.raio, function (v) { edita("raio", v); }, "number")));
1025
+ }
1026
+ if (e.tipo === "grafico") {
1027
+ g.appendChild(linha("Tipo", selecao(["barras", "linha", "area", "pizza", "rosca"],
1028
+ e.grafico || "barras", function (v) { edita("grafico", v); })));
1029
+ g.appendChild(linha("Dados", area((e.dados || []).map(function (d) { return d[0] + " | " + d[1]; }).join("\n"),
1030
+ function (v) {
1031
+ edita("dados", v.split("\n").filter(function (x) { return x.trim(); }).map(function (l) {
1032
+ var p = l.split("|");
1033
+ return [(p[0] || "").trim(), parseFloat(p[1]) || 0];
1034
+ }));
1035
+ }, 6)));
1036
+ }
1037
+ if (e.tipo === "no") {
1038
+ g.appendChild(linha("Id", inp(e.id, function (v) { edita("id", v); })));
1039
+ g.appendChild(linha("Destaque", selecao(["não", "sim"], e.destaque ? "sim" : "não",
1040
+ function (v) { edita("destaque", v === "sim" ? true : ""); })));
1041
+ g.appendChild(linha("Formato", selecao(["", "circulo"], e.formato || "", function (v) { edita("formato", v); })));
1042
+ }
1043
+ if (e.tipo === "conector") {
1044
+ var ids = (cena().elementos || []).filter(function (x) { return x.id; }).map(function (x) { return x.id; });
1045
+ g.appendChild(linha("De", selecao([""].concat(ids), e.de || "", function (v) { edita("de", v); })));
1046
+ g.appendChild(linha("Para", selecao([""].concat(ids), e.para || "", function (v) { edita("para", v); })));
1047
+ g.appendChild(linha("Estilo", selecao(["curva", "reta", "cotovelo"], e.estilo || "curva", function (v) { edita("estilo", v); })));
1048
+ g.appendChild(linha("Rótulo", inp(e.texto, function (v) { edita("texto", v); })));
1049
+ }
1050
+
1051
+ if (g.children.length > 1) ins.appendChild(g);
1052
+ }
1053
+
1054
+ function narracaoInspetor(ins) {
1055
+ var c = cena();
1056
+ var gn = grupo(SLUG === "corte" ? "Narração" : "Narração e notas");
1057
+
1058
+ /* --- 1. sua própria voz: sempre melhor que qualquer sintetizada ------ */
1059
+
1060
+ if (c.narracaoAudio) {
1061
+ var usada = el("div");
1062
+ usada.style.cssText = "display:flex;gap:6px;align-items:center;margin-bottom:10px";
1063
+ var bTocar = el("button", "btn", svgIcone("play"));
1064
+ bTocar.title = "Ouvir a gravação";
1065
+ bTocar.onclick = function () { new Audio(c.narracaoAudio).play(); };
1066
+ var rot = el("span", "", "voz gravada");
1067
+ rot.style.cssText = "flex:1;font-size:12px;color:#4ade80";
1068
+ var bX = el("button", "btn perigo", svgIcone("lixeira"));
1069
+ bX.title = "Remover a gravação";
1070
+ bX.onclick = function () { mudar(function () { delete c.narracaoAudio; }); };
1071
+ usada.appendChild(bTocar); usada.appendChild(rot); usada.appendChild(bX);
1072
+ gn.appendChild(usada);
1073
+ } else if (Narracao.podeGravar()) {
1074
+ var bGravar = el("button", "btn", svgIcone("play2") + " Gravar minha voz");
1075
+ bGravar.style.cssText = "width:100%;margin-bottom:10px";
1076
+ bGravar.onclick = function () {
1077
+ if (Narracao.gravando()) {
1078
+ Narracao.pararGravacao("narracao-" + ID + "-" + (iCena + 1)).then(function (m) {
1079
+ mudar(function () { c.narracaoAudio = m.url; });
1080
+ }).catch(function (e) { alert("erro ao salvar: " + e.message); });
1081
+ return;
1082
+ }
1083
+ Narracao.gravar().then(function () {
1084
+ bGravar.innerHTML = svgIcone("pausa") + " Parar e salvar";
1085
+ bGravar.classList.add("primo");
1086
+ }).catch(function (e) { alert("não consegui abrir o microfone: " + e.message); });
1087
+ };
1088
+ gn.appendChild(bGravar);
1089
+ }
1090
+
1091
+ /* --- 2. texto para a voz sintetizada -------------------------------- */
1092
+
1093
+ gn.appendChild(linha("Falar", area(c.narracao, function (v) {
1094
+ mudar(function () { if (v) c.narracao = v; else delete c.narracao; });
1095
+ }, 3)));
1096
+
1097
+ var acoes = el("div", "opcoes");
1098
+ var bOuvir = el("button", "", svgIcone("play"));
1099
+ bOuvir.title = "Ouvir";
1100
+ bOuvir.onclick = function () {
1101
+ if (!Narracao.disponivel()) return alert("este navegador não tem síntese de voz");
1102
+ Narracao.falar(c.narracao || "", doc.voz || {});
1103
+ };
1104
+ var bParar = el("button", "", svgIcone("pausa"));
1105
+ bParar.title = "Parar";
1106
+ bParar.onclick = function () { Narracao.parar(); };
1107
+ acoes.appendChild(bOuvir);
1108
+ acoes.appendChild(bParar);
1109
+
1110
+ if (F.tempo) {
1111
+ var bMedir = el("button", "", svgIcone("relogio"));
1112
+ bMedir.title = "Ajustar a duração desta cena à narração";
1113
+ bMedir.onclick = function () {
1114
+ if (!c.narracao) return;
1115
+ bMedir.style.opacity = ".4";
1116
+ Narracao.medir(c.narracao, doc.voz || {}).then(function (s) {
1117
+ bMedir.style.opacity = "";
1118
+ mudar(function () { c.duracao = Math.max(2, Math.round((s + 0.6) * 10) / 10); });
1119
+ });
1120
+ };
1121
+ acoes.appendChild(bMedir);
1122
+ }
1123
+ gn.appendChild(acoes);
1124
+
1125
+ if (SLUG === "prisma") {
1126
+ gn.appendChild(linha("Notas", area(c.notas, function (v) {
1127
+ mudar(function () { if (v) c.notas = v; else delete c.notas; });
1128
+ }, 3)));
1129
+ }
1130
+
1131
+ /* --- 3. escolha da voz, já ranqueada -------------------------------- */
1132
+
1133
+ var voz = doc.voz || {};
1134
+ var boas = Narracao.vozes();
1135
+ var sel2 = el("select", "campo");
1136
+ var opAuto = el("option", "", "melhor disponível");
1137
+ opAuto.value = "";
1138
+ sel2.appendChild(opAuto);
1139
+ boas.forEach(function (v) {
1140
+ var o = el("option", "", Narracao.limpo(v.name) + " · " + v.lang);
1141
+ o.value = v.name;
1142
+ if (v.name === voz.voz) o.selected = true;
1143
+ sel2.appendChild(o);
1144
+ });
1145
+ sel2.onchange = function () {
1146
+ mudar(function () { doc.voz = doc.voz || {}; doc.voz.voz = sel2.value; });
1147
+ Narracao.falar("Assim que esta voz vai narrar o seu vídeo.",
1148
+ { voz: sel2.value, velocidade: voz.velocidade });
1149
+ };
1150
+ gn.appendChild(linha("Voz", sel2));
1151
+
1152
+ gn.appendChild(linha("Velocid.", inp(voz.velocidade != null ? voz.velocidade : 0.96,
1153
+ function (v) { mudar(function () { doc.voz = doc.voz || {}; doc.voz.velocidade = v || 0.96; }); },
1154
+ "number")));
1155
+
1156
+ if (F.tempo) {
1157
+ var bTodas = el("button", "btn", "Ajustar todas as cenas");
1158
+ bTodas.style.cssText = "width:100%;margin-top:8px";
1159
+ bTodas.onclick = ajustarTudoNarracao;
1160
+ gn.appendChild(bTodas);
1161
+ }
1162
+
1163
+ /* --- 4. diagnóstico honesto ----------------------------------------- */
1164
+
1165
+ var d = Narracao.diagnostico();
1166
+ if (!Narracao.disponivel()) {
1167
+ gn.appendChild(el("div", "nada", "Este navegador não tem síntese de voz."));
1168
+ } else if (!d.premium) {
1169
+ var dica = el("div", "dica-voz");
1170
+ dica.innerHTML =
1171
+ "<b>As vozes do sistema estão no modo compacto.</b>" +
1172
+ "<p>Só há " + d.boasEmPortugues + " voz útil em português aqui. As vozes " +
1173
+ "aprimoradas soam muito melhor e são download gratuito da Apple:</p>" +
1174
+ "<p class='caminho'>Ajustes do Sistema → Acessibilidade → Conteúdo Falado → " +
1175
+ "Voz do sistema → Gerenciar vozes → Português (Brasil) → baixe as marcadas " +
1176
+ "como <b>Premium</b> ou <b>Aprimorada</b></p>" +
1177
+ "<p>Depois recarregue esta página. Para qualidade de locução de verdade, " +
1178
+ "grave a sua voz no botão acima.</p>";
1179
+ gn.appendChild(dica);
1180
+ }
1181
+
1182
+ ins.appendChild(gn);
1183
+ }
1184
+
1185
+ /* mede cada narração e ajusta a duração da cena a ela, uma de cada vez */
1186
+ function ajustarTudoNarracao() {
1187
+ var lista = (doc.cenas || []).map(function (c, i) { return { c: c, i: i }; })
1188
+ .filter(function (x) { return x.c.narracao; });
1189
+ if (!lista.length) return alert("nenhuma cena tem narração ainda");
1190
+
1191
+ marcar("medindo narração…");
1192
+ (function proxima(k) {
1193
+ if (k >= lista.length) {
1194
+ desenhar();
1195
+ agendarSalvar();
1196
+ return;
1197
+ }
1198
+ Narracao.medir(lista[k].c.narracao, doc.voz || {}).then(function (s) {
1199
+ lista[k].c.duracao = Math.max(2, Math.round((s + 0.6) * 10) / 10);
1200
+ marcar("medindo " + (k + 1) + "/" + lista.length + "…");
1201
+ proxima(k + 1);
1202
+ });
1203
+ })(0);
1204
+ }
1205
+
1206
+ function colunasInspetor(ins) {
1207
+ var g = grupo("Colunas");
1208
+ (aba().colunas || []).forEach(function (c, i) {
1209
+ g.appendChild(linha(String.fromCharCode(65 + i), inp(c, function (v) {
1210
+ mudar(function () { aba().colunas[i] = v; });
1211
+ })));
1212
+ });
1213
+ ins.appendChild(g);
1214
+
1215
+ var gf = grupo("Formato por coluna");
1216
+ (aba().colunas || []).forEach(function (c) {
1217
+ gf.appendChild(linha(c.slice(0, 8), selecao(["", "texto", "inteiro", "decimal", "moeda", "percentual"],
1218
+ (aba().formatos || {})[c] || "", function (v) {
1219
+ mudar(function () {
1220
+ aba().formatos = aba().formatos || {};
1221
+ if (v) aba().formatos[c] = v; else delete aba().formatos[c];
1222
+ });
1223
+ })));
1224
+ });
1225
+ ins.appendChild(gf);
1226
+
1227
+ var gh = grupo("Fórmulas");
1228
+ gh.appendChild(el("div", "nada",
1229
+ "Escreva na célula:<br><code>=SOMA(B1:B9)</code><br><code>=MEDIA(C1:C4)</code><br><code>=B2*1.2</code>"));
1230
+ ins.appendChild(gh);
1231
+ }
1232
+
1233
+ /* ------------------------------------------------------------- ações topo */
1234
+
1235
+ $("#titulo").addEventListener("input", agendarSalvar);
1236
+
1237
+ $("#btn-codigo").onclick = function () {
1238
+ $("#codigo-txt").value = JSON.stringify(doc, null, 2);
1239
+ $("#folha").classList.add("on");
1240
+ };
1241
+ $("#codigo-aplicar").onclick = function () {
1242
+ try {
1243
+ var novo = JSON.parse($("#codigo-txt").value);
1244
+ mudar(function () { doc = novo; ARQ.doc = doc; iCena = 0; sel = null; });
1245
+ $("#folha").classList.remove("on");
1246
+ } catch (err) {
1247
+ alert("JSON inválido: " + err.message);
1248
+ }
1249
+ };
1250
+ $("#codigo-fechar").onclick = function () { $("#folha").classList.remove("on"); };
1251
+
1252
+ $("#btn-ver").href = "/v/" + ID;
1253
+ $("#btn-baixar").href = "/api/arquivos/" + ID + "/export";
1254
+ $("#btn-pdf").onclick = function () { window.open("/v/" + ID + "#imprimir", "_blank"); };
1255
+
1256
+ document.addEventListener("keydown", function (ev) {
1257
+ if (/input|textarea/i.test(ev.target.tagName) || ev.target.isContentEditable) return;
1258
+ if ((ev.metaKey || ev.ctrlKey) && ev.key === "z") { ev.preventDefault(); ev.shiftKey ? refazer() : desfazer(); return; }
1259
+ if (ev.key === "Delete" || ev.key === "Backspace") {
1260
+ if (sel != null) { ev.preventDefault(); mudar(function () { itens().splice(sel, 1); sel = null; }); }
1261
+ }
1262
+ if (F.modelo === "cena") {
1263
+ if (ev.key === "ArrowDown" && !sel) { iCena = Math.min((doc.cenas || []).length - 1, iCena + 1); desenhar(); }
1264
+ if (ev.key === "ArrowUp" && !sel) { iCena = Math.max(0, iCena - 1); desenhar(); }
1265
+ }
1266
+ });
1267
+
1268
+ window.addEventListener("resize", function () { if (doc) desenharPalco(); });
1269
+ window.addEventListener("beforeunload", function () { clearTimeout(tSalvar); salvar(true); });
1270
+ })();