sagadeck 1.8.0 → 1.9.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 (92) hide show
  1. package/README.md +19 -8
  2. package/SKILL.md +10 -3
  3. package/bin/sagadeck.js +23 -0
  4. package/docs/ANDAMENTO.md +367 -7
  5. package/docs/PLANO-MELHORIAS.md +180 -0
  6. package/docs/REFERENCIA.md +131 -110
  7. package/package.json +33 -2
  8. package/src/ai/commands.js +2 -2
  9. package/src/ai/deck-ai.js +1 -1
  10. package/src/ai/ia-check.js +127 -0
  11. package/src/ai/ia-config.js +2 -2
  12. package/src/ai/llm.js +5 -1
  13. package/src/ai/map-analysis-run.js +1 -1
  14. package/src/ai/quality.js +1 -1
  15. package/src/ai/video-generation.js +70 -25
  16. package/src/build.js +46 -14
  17. package/src/elements.js +39 -1
  18. package/src/export/pptx.js +7 -2
  19. package/src/export/shots.js +7 -2
  20. package/src/fiscal/normalize.js +19 -0
  21. package/src/fs-retry.js +37 -2
  22. package/src/import/office.js +5 -4
  23. package/src/layouts.js +104 -99
  24. package/src/lessons.js +1 -1
  25. package/src/library.js +7 -7
  26. package/src/map-analysis.js +4 -1
  27. package/src/map-config.js +10 -4
  28. package/src/map.js +21 -8
  29. package/src/mcp/server.js +17 -0
  30. package/src/motion.js +20 -6
  31. package/src/purpose.js +1 -1
  32. package/src/runtime/base.css +88 -332
  33. package/src/runtime/fit.js +3 -2
  34. package/src/runtime/fonts/amatic-sc.css +3 -0
  35. package/src/runtime/fonts/bebas-neue.css +2 -0
  36. package/src/runtime/fonts/cabin-sketch.css +2 -0
  37. package/src/runtime/fonts/fraunces.css +3 -0
  38. package/src/runtime/fonts/gelasio.css +3 -0
  39. package/src/runtime/fonts/ibm-plex-mono.css +4 -0
  40. package/src/runtime/fonts/index.json +10 -1
  41. package/src/runtime/fonts/itim.css +2 -0
  42. package/src/runtime/fonts/montserrat.css +2 -0
  43. package/src/runtime/fonts/space-grotesk.css +2 -0
  44. package/src/runtime/map-background.js +91 -0
  45. package/src/runtime/map.js +41 -9
  46. package/src/runtime/runtime.js +81 -100
  47. package/src/runtime/skins/caderno.css +33 -0
  48. package/src/runtime/skins/cartaz.css +27 -0
  49. package/src/runtime/skins/infografico.css +29 -0
  50. package/src/runtime/skins/lousa.css +22 -0
  51. package/src/runtime/skins/musgo.css +42 -0
  52. package/src/runtime/vendor/MAPLIBRE-LEAFLET-LICENSE.txt +15 -0
  53. package/src/runtime/vendor/MAPLIBRE-LICENSE.txt +116 -0
  54. package/src/runtime/vendor/geo-contornos.json +1 -1
  55. package/src/runtime/vendor/maplibre-gl.css +1 -0
  56. package/src/runtime/vendor/maplibre-gl.js +825 -0
  57. package/src/runtime/vendor/maplibre-leaflet.js +234 -0
  58. package/src/runtime/vendor/maplibre-worker.js +10 -0
  59. package/src/studio/assets/imagens/tour-mapa-botao.jpg +0 -0
  60. package/src/studio/assets/imagens/tour-mapa-editor.jpg +0 -0
  61. package/src/studio/command-approval.js +2 -3
  62. package/src/studio/demo-decks.js +4 -2
  63. package/src/studio/export-jobs.js +86 -0
  64. package/src/studio/exporting.js +36 -18
  65. package/src/studio/feature-demos.js +144 -0
  66. package/src/studio/ia-routes.js +9 -0
  67. package/src/studio/layout-samples.js +9 -18
  68. package/src/studio/library-routes.js +0 -9
  69. package/src/studio/project.js +1 -0
  70. package/src/studio/public/ai-settings.js +2 -2
  71. package/src/studio/public/app.js +41 -51
  72. package/src/studio/public/export-status.js +154 -0
  73. package/src/studio/public/index.html +13 -10
  74. package/src/studio/public/library.css +12 -3
  75. package/src/studio/public/library.html +1 -0
  76. package/src/studio/public/library.js +36 -14
  77. package/src/studio/public/map-data.js +7 -3
  78. package/src/studio/public/map-editor.js +625 -236
  79. package/src/studio/public/map-settings.js +3 -1
  80. package/src/studio/public/slide-form.js +18 -15
  81. package/src/studio/public/style.css +54 -9
  82. package/src/studio/public/ui-icons.js +1 -1
  83. package/src/studio/server.js +27 -12
  84. package/src/studio/snapshot.js +8 -4
  85. package/src/studio/template-collections.js +1 -1
  86. package/src/studio/tour.js +89 -7
  87. package/src/themes.js +154 -0
  88. package/templates/cenario-e-ciencia.yaml +1 -21
  89. package/templates/ensaio-api.yaml +3 -3
  90. package/templates/cenario/Texto no cen/303/241rio.yaml" +0 -171
  91. package/templates/cenario/imagens/cidade.svg +0 -30
  92. package/templates/cenario/imagens/pessoa.svg +0 -15
@@ -71,13 +71,31 @@
71
71
  $("#ai-banner-btn").onclick = () => window.SagaAISettings.open(aiSaved);
72
72
  const openEditor = (id, present) => { location.href = `editor?deck=${encodeURIComponent(id)}${present ? "&present=1" : ""}`; };
73
73
 
74
+ // Demos oficiais (só leitura): as demos de um assunto só (src/studio/feature-demos.js: mapas, programação, APIs com
75
+ // curl) em primeiro, o Conheça o SagaDeck (src/studio/tour.js) e as demos de recursos da vitrine. Moram numa
76
+ // pasta própria da lateral e o Conheça aparece também entre as apresentações. Abrem em prévia (editor?model=…): navegar
77
+ // e apresentar não gravam nada; editar cria a cópia da pessoa em Modelos, como nos modelos. Sem mover, renomear nem
78
+ // excluir: a demo de fábrica nunca muda.
79
+ const officialDemos = () => GALLERY.filter(([kind]) => kind === "recurso");
80
+ const officialId = (key) => (key.startsWith("model-") ? key.slice(6) : key);
81
+ const openOfficial = (key, present) => { location.href = `editor?model=${encodeURIComponent(officialId(key))}&topic=Modelos${present ? "&present=1" : ""}`; };
82
+ function officialCard([, key, title, desc, sw]) {
83
+ return `<div class="card card-oficial" data-oficial="${esc(officialId(key))}" data-key="${esc(key)}" tabindex="0" title="Demo oficial do SagaDeck: só leitura (editar cria uma cópia sua em Modelos)">
84
+ <div class="thumb" style="background:linear-gradient(90deg,${sw.map(esc).join(",")})"><img loading="lazy" alt="" src="api/library/gallery-cover?key=${encodeURIComponent(key)}" onerror="this.remove()">
85
+ <div class="over"><button class="pill" data-present>${ic("play")}Apresentar</button><button class="pill" data-open>${ic("eye")}Abrir</button></div></div>
86
+ <div class="meta"><div class="t"><div class="name">${esc(title)}</div>
87
+ <div class="info"><span class="chip chip-oficial">${ic("lock")}demo oficial</span>·<span class="desc">${esc(desc)}</span></div></div></div>
88
+ </div>`;
89
+ }
90
+ const tourCard = () => officialCard(officialDemos().find(([, key]) => key === "model-conheca"));
91
+
74
92
  // ---------------------------------------------------------------- barra lateral
75
93
  function renderSide() {
76
94
  const loose = data.decks.filter((d) => !d.topic).length;
77
95
  $("#side").innerHTML = `
78
96
  <button class="nav ${view === "recentes" ? "active" : ""}" data-view="recentes">${ic("clock")}<span class="name">Recentes</span></button>
79
97
  <button class="nav ${view === "todas" ? "active" : ""}" data-view="todas">${ic("layout-grid")}<span class="name">Todas</span><span class="count">${data.decks.length || ""}</span></button>
80
- <button class="nav nav-tour" data-tour title="Tudo o que dá para fazer, um slide por recurso (abre em prévia)">${ic("compass")}<span class="name">Conheça o SagaDeck</span></button>
98
+ <button class="nav nav-oficiais ${view === "oficiais" ? "active" : ""}" data-view="oficiais" title="Apresentações do próprio SagaDeck, só leitura: o Conheça o SagaDeck e as demos (abrem em prévia)">${ic("lock")}<span class="name">Demos oficiais</span></button>
81
99
  <div class="side-title">TÓPICOS<button title="Novo tópico" id="new-topic" aria-label="Novo tópico">${ic("plus")}</button></div>
82
100
  ${data.topics.map((t) => `<button class="nav ${view === t.id ? "active" : ""}" data-view="${esc(t.id)}" data-topic="${esc(t.id)}"><span class="dot" style="background:${esc(t.color)}"></span><span class="name">${esc(t.name)}</span><span class="count">${t.count || ""}</span></button>`).join("")}
83
101
  ${loose ? `<button class="nav ${view === "" ? "active" : ""}" data-view="" title="Arquivos .yaml deixados direto na pasta da biblioteca"><span class="dot" style="background:${NO_TOPIC.color}"></span><span class="name">${NO_TOPIC.name}</span><span class="count">${loose}</span></button>` : ""}
@@ -88,8 +106,6 @@
88
106
  hydrate($("#side"));
89
107
  $("#side").querySelectorAll("[data-view]").forEach((b) => b.onclick = () => { view = b.dataset.view; store.set("libView", view); render(); });
90
108
  $("#new-topic").onclick = () => topicDialog();
91
- // a apresentação de recursos (src/studio/tour.js) abre como os modelos da vitrine: em prévia, sem criar arquivo
92
- $("#side [data-tour]").onclick = () => { location.href = "editor?model=conheca&topic=Modelos"; };
93
109
  $("#side").querySelectorAll("[data-topic]").forEach((b) => {
94
110
  b.ondragover = (e) => { if (e.dataTransfer.types.includes("application/x-sagadeck-deck")) { e.preventDefault(); b.classList.add("drop"); } };
95
111
  b.ondragleave = () => b.classList.remove("drop");
@@ -127,6 +143,10 @@
127
143
  (data.trash.length ? data.trash.map((t) => `<div class="trash-row"><div class="t"><b>${esc(t.title)}</b><div class="sub">Era de ${esc(t.topic || NO_TOPIC.name)} · ${t.slides || 0} slides · excluída ${ago(t.deleted)}</div></div>
128
144
  <button class="lib-btn ghost" data-restore="${esc(t.id)}">${ic("rotate-ccw")}Restaurar</button><button class="lib-btn ghost danger" data-purge="${esc(t.id)}">Excluir de vez</button></div>`).join("")
129
145
  : `<div class="empty"><div class="big">${ic("trash-2")}</div><h2>Lixeira vazia</h2></div>`);
146
+ } else if (view === "oficiais") {
147
+ const demos = officialDemos().filter(([, , title, desc]) => !q || `${title} ${desc}`.toLowerCase().includes(q));
148
+ main.innerHTML = `<div class="head"><div><h1>${ic("lock")} SagaDeck · demos oficiais</h1><div class="sub">Só leitura: abrem em prévia, sem criar arquivo. Gostou? Edite e nasce uma cópia sua em Modelos.</div></div></div>
149
+ ${demos.length ? `<div class="grid">${demos.map(officialCard).join("")}</div>` : `<div class="empty"><div class="big">${ic("search")}</div><h2>Nada encontrado</h2></div>`}`;
130
150
  } else {
131
151
  const t = view === "" ? NO_TOPIC : topicOf(view);
132
152
  const all = t ? data.decks.filter((d) => d.topic === t.id) : data.decks;
@@ -144,11 +164,13 @@
144
164
  } else if (!data.decks.length && !q) {
145
165
  main.innerHTML += `<div class="empty"><div class="big">${ic("library")}</div><h2>Sua biblioteca está vazia</h2>
146
166
  <p>Crie um tópico (ex.: Palestras, Trabalho) e comece uma apresentação — em branco, com IA ou importando um .sagadeck ou um PowerPoint (.pptx).</p>
147
- <div class="actions"><button class="lib-btn ghost" data-empty-topic>${ic("folder-plus")}Novo tópico</button><button class="lib-btn primary" data-empty-new>${ic("plus")}Nova apresentação</button></div></div>`;
167
+ <div class="actions"><button class="lib-btn ghost" data-empty-topic>${ic("folder-plus")}Novo tópico</button><button class="lib-btn primary" data-empty-new>${ic("plus")}Nova apresentação</button></div></div>
168
+ <div class="grid grid-oficial">${tourCard()}</div>`;
148
169
  } else if (!list.length) {
149
170
  main.innerHTML += `<div class="empty"><div class="big">${ic("search")}</div><h2>Nada encontrado</h2><p>Nenhuma apresentação com "${esc(q)}".</p></div>`;
150
171
  } else {
151
- main.innerHTML += `<div class="grid">${t && !q ? `<div class="card new-card" id="card-new" tabindex="0"><div class="thumb"><div class="plus">${ic("plus")}Nova em ${esc(t.name)}</div></div></div>` : ""}${items.map((d) => card(d, !t)).join("")}</div>`;
172
+ // em Recentes e Todas, o Conheça o SagaDeck (demo oficial) vem primeiro, para quem ainda não abriu a pasta
173
+ main.innerHTML += `<div class="grid">${t && !q ? `<div class="card new-card" id="card-new" tabindex="0"><div class="thumb"><div class="plus">${ic("plus")}Nova em ${esc(t.name)}</div></div></div>` : ""}${!t && !q ? tourCard() : ""}${items.map((d) => card(d, !t)).join("")}</div>`;
152
174
  }
153
175
  const sortSel = $("#sort");
154
176
  if (sortSel) { sortSel.value = sortBy; sortSel.onchange = () => { sortBy = sortSel.value; store.set("libSort", sortBy); render(); }; }
@@ -159,6 +181,10 @@
159
181
  }
160
182
 
161
183
  function wire(main) {
184
+ main.querySelectorAll(".card-oficial").forEach((c) => {
185
+ c.onclick = (e) => openOfficial(c.dataset.key, !!e.target.closest("[data-present]"));
186
+ c.onkeydown = (e) => { if (e.key === "Enter") openOfficial(c.dataset.key); };
187
+ });
162
188
  main.querySelectorAll(".card[data-id]").forEach((c) => {
163
189
  const id = c.dataset.id;
164
190
  c.ondragstart = (e) => { e.dataTransfer.setData("application/x-sagadeck-deck", id); c.classList.add("dragging"); };
@@ -228,8 +254,7 @@
228
254
  m.querySelectorAll("[data-move]").forEach((b) => b.onclick = () => { closeMenus(); act(() => api("api/library/decks/move", { id, topic: b.dataset.move }), `Movida para ${topicOf(b.dataset.move).name}`); });
229
255
  m.querySelectorAll("[data-dl]").forEach((b) => b.onclick = () => {
230
256
  closeMenus();
231
- if (b.dataset.dl !== "sagadeck") toast("Gerando… o download começa em alguns segundos.", 6000);
232
- location.href = `api/library/download?id=${encodeURIComponent(id)}&kind=${b.dataset.dl}${b.dataset.notas === "0" ? "&notas=0" : ""}`;
257
+ window.SagadeckExports.start({ id, kind: b.dataset.dl, notes: b.dataset.notas !== '0', title: d.title }).catch(err => toast('Não deu para gerar: ' + err.message, 8000));
233
258
  });
234
259
  }
235
260
 
@@ -281,16 +306,13 @@
281
306
  ["visual", "model-lavanda", "Estúdio lavanda", "Mosaicos, cápsulas e composições editoriais", ["#EFE9FB", "#5B3FA8", "#D8C8F5"]],
282
307
  ["visual", "model-executivo", "Relatório executivo", "Resumo, indicadores e plano de trabalho", ["#0F1115", "#E4B660", "#EDEBE6"]],
283
308
  ["visual", "model-workshop", "Workshop visual", "Perguntas, código guiado e consulta", ["#FFF7E6", "#1A1A1A", "#E8590C"]],
309
+ ["recurso", "model-mapas", "Mapas", "Pontos, bolhas, regiões, linhas e áreas; rota, alcance, raio e faixa calculados; a IA e o Editar mapa", ["#EAF4F8", "#0B2A3C", "#0E7CB8"]],
310
+ ["recurso", "model-programacao", "Programação", "Código destacado e guiado, código que a plateia roda, algoritmo animado, Git a dois e diagramas", ["#0C0F0B", "#F0B24A", "#A9C79B"]],
311
+ ["recurso", "model-apis", "APIs com curl", "A anatomia de um curl, chamadas ao vivo com o curl para copiar, polling, streaming e as opções do dia a dia", ["#170A10", "#F2506A", "#F3A6B5"]],
284
312
  ["recurso", "model-conheca", "Conheça o SagaDeck", "Um slide por recurso, com a frase para pedir cada um à IA", ["#F2F0EA", "#1A1A1A", "#F2B33D"]],
285
- ["recurso", "model-explorar", "Explore uma ideia", "Prever, comparar cenários, curvas conectadas e acompanhar o mesmo objeto entre cenas", ["#F4F1EA", "#1B1F3B", "#E8590C"]],
286
- ["recurso", "model-novidades", "Novidades", "Carrossel, gráfico do Excel e da planilha do projeto, fórmulas vivas, duelo de commits, terminais e zoom", ["#F4F1EA", "#1B1F3B", "#E8590C"]],
287
313
  ["recurso", "model-hidraulica", "Aula de hidráulica", "Navier–Stokes, curva que se mexe, calculadora de Reynolds ao vivo e exercício resolvido passo a passo", ["#EAF4F8", "#0B5C7A", "#1B998B"]],
288
314
  ["recurso", "model-algoritmos", "Aula de algoritmos", "Ordenação e busca rodando de verdade: barras, código, contadores e o botão Tocar", ["#0F1426", "#7C8CFF", "#F2B33D"]],
289
- ["recurso", "model-compacto", "Material de consulta", "Código completo, JSON e páginas compactas", ["#F7F6F2", "#20242B", "#0F6CBD"]],
290
- ["recurso", "model-avancado", "Recursos avançados", "Conteúdo denso, avisos, diagramas e tipografia cinética", ["#F7F6F2", "#20242B", "#E8590C"]],
291
315
  ["recurso", "model-diagramas", "Diagramas vivos", "Fluxo, sequência, estados e mapa mental", ["#F4F7FB", "#1F6FB2", "#5DBB86"]],
292
- ["recurso", "example-api", "Aula de APIs ao vivo", "Slides que executam requisições de verdade. Roda sem configurar nada", ["#111B2B", "#5B8DEF", "#ECF1FF"]],
293
- ["recurso", "example-cenario", "Texto no cenário", "As 13 composições: fundo, transparência, recorte na frente das letras", ["#0A0D17", "#00F2FE", "#FF007A"]],
294
316
  ];
295
317
  const GALLERY_KINDS = [["", "Todos"], ["visual", "Visuais para começar"], ["recurso", "Recursos do SagaDeck"]];
296
318
  async function galleryDialog(topic, kind = "") {
@@ -370,7 +392,7 @@
370
392
  let reading = 0;
371
393
  dialog(`<h3 id="dialog-heading">Nova apresentação com IA</h3>
372
394
  <label for="dlg-brief">Sobre o que é, para quem e com que objetivo? <span class="muted">Ou só anexe o material abaixo.</span></label>
373
- <textarea id="dlg-brief" placeholder="Ex.: palestra para gestores sobre golpes no Pix, tom leve, começando com um caso real. Ou: resumo deste relatório para a diretoria, focando nos números do ano."></textarea>
395
+ <textarea id="dlg-brief" placeholder="Ex.: palestra para gestores sobre trabalho híbrido, tom leve, começando com um caso real. Ou: resumo deste relatório para a diretoria, focando nos números do ano."></textarea>
374
396
  <div class="dlg-two">
375
397
  <div><label for="dlg-min">Quanto tempo você tem?</label><div class="dlg-min"><input type="number" id="dlg-min" min="1" max="120" value="10"><span>min</span><span class="hint" id="dlg-slides"></span></div></div>
376
398
  <div><label for="dlg-style">Estilo</label><select id="dlg-style"><option value="">Automático (a IA escolhe pelo assunto)</option>${STYLES.map(([v, l]) => `<option value="${v}">${esc(l)}</option>`).join("")}</select></div>
@@ -121,7 +121,7 @@ window.SagaMapData = (() => {
121
121
  // linha selecionada na planilha -> cartão do ponto no mapa da prévia
122
122
  const selectRow = (path, linha) => document.dispatchEvent(new CustomEvent("sagamap:linha", { detail: { path, linha } }));
123
123
 
124
- // botão "Editar no mapa" na prévia grande; o editor (map-editor.js) só carrega no primeiro clique
124
+ // botão "Editar mapa" na prévia grande; a tela do editor (map-editor.js) só carrega no primeiro clique
125
125
  let editorLoad;
126
126
  const loadEditor = () => (window.SagaMapEditor ? Promise.resolve() : (editorLoad ||= new Promise((res, rej) => {
127
127
  const s = document.createElement("script"); s.src = "map-editor.js"; s.onload = res;
@@ -137,12 +137,16 @@ window.SagaMapData = (() => {
137
137
  const host = document.getElementById("canvas-viewport") || box;
138
138
  host.querySelectorAll(":scope > .me-open").forEach((b) => b.remove());
139
139
  const btn = document.createElement("button");
140
- btn.type = "button"; btn.className = "me-open"; btn.innerHTML = '<i class="ic" data-ic="pencil"></i> Editar no mapa';
140
+ btn.type = "button"; btn.className = "me-open"; btn.innerHTML = '<i class="ic" data-ic="pencil"></i> Editar mapa';
141
+ btn.title = "Abre a tela do mapa: camadas, pontos, linhas, áreas e as propriedades de cada um (ou dê dois cliques no mapa)";
141
142
  for (const ev of ["pointerdown", "mousedown", "click", "dblclick"]) btn.addEventListener(ev, (e) => e.stopPropagation());
142
- btn.onclick = async () => {
143
+ const openEditor = async () => {
143
144
  try { await loadEditor(); btn.hidden = true; window.SagaMapEditor.start(box, slide, commit, ctx); }
144
145
  catch (e) { ctx.toast(e.message, 5000); }
145
146
  };
147
+ btn.onclick = openEditor;
148
+ // dois cliques no mapa da prévia também abrem a tela (o mapa pequeno, escalado, não serve para editar)
149
+ box.addEventListener("dblclick", (e) => { e.stopPropagation(); e.preventDefault(); openEditor(); });
146
150
  host.append(btn);
147
151
  ctx.hydrate?.(btn);
148
152
  // o slide redesenhou com o editor aberto (gravou algo): volta no mesmo modo