sagadeck 1.2.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 (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +305 -0
  3. package/SKILL.md +148 -0
  4. package/bin/sagadeck.js +511 -0
  5. package/docs/ANDAMENTO.md +1255 -0
  6. package/docs/EXPERIENCIAS.md +39 -0
  7. package/docs/PLANO-EXPERIENCIAS.md +39 -0
  8. package/docs/PLANO-TESTE-AULAS-TECNICAS.md +44 -0
  9. package/docs/PLANO-TESTE-DESINFORMACAO.md +38 -0
  10. package/docs/REFERENCIA.md +1131 -0
  11. package/package.json +59 -0
  12. package/src/adaptive-layouts.js +18 -0
  13. package/src/ai/art-direction.js +8 -0
  14. package/src/ai/commands.js +130 -0
  15. package/src/ai/context.js +245 -0
  16. package/src/ai/deck-ai.js +1135 -0
  17. package/src/ai/directions.js +74 -0
  18. package/src/ai/document-materials.js +49 -0
  19. package/src/ai/document-visuals.js +215 -0
  20. package/src/ai/evaluation.js +31 -0
  21. package/src/ai/ground.js +55 -0
  22. package/src/ai/llm.js +292 -0
  23. package/src/ai/quality.js +51 -0
  24. package/src/ai/relay.js +45 -0
  25. package/src/ai/staged-generation.js +24 -0
  26. package/src/ai/transform.js +858 -0
  27. package/src/ai/usage.js +17 -0
  28. package/src/ai/variety.js +80 -0
  29. package/src/ai/video-frames.js +24 -0
  30. package/src/ai/video-generation.js +126 -0
  31. package/src/ai/web-operation.js +31 -0
  32. package/src/algo-catalog.js +153 -0
  33. package/src/algo-trace.js +139 -0
  34. package/src/api-client.js +514 -0
  35. package/src/api-collections.js +14 -0
  36. package/src/api-demo.js +222 -0
  37. package/src/aspect.js +40 -0
  38. package/src/build.js +323 -0
  39. package/src/carousel.js +56 -0
  40. package/src/chrome.js +65 -0
  41. package/src/code-highlight.js +68 -0
  42. package/src/code-lab-browser.js +22 -0
  43. package/src/code-lab.js +3 -0
  44. package/src/code-language.js +52 -0
  45. package/src/csv.js +74 -0
  46. package/src/decision-lab.js +20 -0
  47. package/src/deck-file.js +119 -0
  48. package/src/diagram/napkin-examples.js +13 -0
  49. package/src/diagram/napkin.js +539 -0
  50. package/src/docx.js +128 -0
  51. package/src/dynamics/git-sim.js +200 -0
  52. package/src/dynamics/layouts.js +112 -0
  53. package/src/elements.js +554 -0
  54. package/src/experiences.js +124 -0
  55. package/src/exploration.js +31 -0
  56. package/src/export/browser.js +53 -0
  57. package/src/export/estudo.js +67 -0
  58. package/src/export/pptx.js +475 -0
  59. package/src/export/roteiro.js +40 -0
  60. package/src/export/shots.js +182 -0
  61. package/src/figures/charts.js +383 -0
  62. package/src/figures/diagrams.js +117 -0
  63. package/src/figures/icons.js +166 -0
  64. package/src/figures/material-pictos.js +20 -0
  65. package/src/figures/pictos.js +255 -0
  66. package/src/figures/points.js +106 -0
  67. package/src/figures/qr.js +29 -0
  68. package/src/figures/ufmap.js +48 -0
  69. package/src/fiscal/autofix.js +377 -0
  70. package/src/fiscal/normalize.js +115 -0
  71. package/src/graph-lab.js +24 -0
  72. package/src/hologram.js +30 -0
  73. package/src/identity.js +86 -0
  74. package/src/import/crop.js +59 -0
  75. package/src/import/index.js +132 -0
  76. package/src/import/legacy-word.js +42 -0
  77. package/src/import/merge.js +45 -0
  78. package/src/import/office.js +123 -0
  79. package/src/import/omml.js +69 -0
  80. package/src/import/pdf-render.js +56 -0
  81. package/src/import/pptx.js +701 -0
  82. package/src/import/xml.js +42 -0
  83. package/src/infographic.js +401 -0
  84. package/src/layouts.js +723 -0
  85. package/src/lessons.js +171 -0
  86. package/src/library.js +399 -0
  87. package/src/markup.js +116 -0
  88. package/src/master.js +214 -0
  89. package/src/mcp/server.js +371 -0
  90. package/src/meeting.js +42 -0
  91. package/src/motion.js +56 -0
  92. package/src/package.js +116 -0
  93. package/src/playground.js +19 -0
  94. package/src/portal-scene.js +20 -0
  95. package/src/preferences.js +61 -0
  96. package/src/protect.js +51 -0
  97. package/src/purpose.js +24 -0
  98. package/src/pytrace.js +1092 -0
  99. package/src/research/duckduckgo.js +73 -0
  100. package/src/research/research.js +202 -0
  101. package/src/research/svg-image.js +17 -0
  102. package/src/research/visuals.js +52 -0
  103. package/src/runtime/api-collection-ui.js +5 -0
  104. package/src/runtime/api-collections.js +28 -0
  105. package/src/runtime/api-core.js +530 -0
  106. package/src/runtime/api-ui.js +966 -0
  107. package/src/runtime/base.css +1774 -0
  108. package/src/runtime/calc.js +139 -0
  109. package/src/runtime/code-lab.js +371 -0
  110. package/src/runtime/decision-lab.js +49 -0
  111. package/src/runtime/diagram.js +341 -0
  112. package/src/runtime/fit.js +268 -0
  113. package/src/runtime/fonts/caveat.css +2 -0
  114. package/src/runtime/fonts/comic-neue.css +3 -0
  115. package/src/runtime/fonts/fredoka.css +2 -0
  116. package/src/runtime/fonts/index.json +10 -0
  117. package/src/runtime/fonts/inter.css +2 -0
  118. package/src/runtime/fonts/patrick-hand.css +2 -0
  119. package/src/runtime/fonts/plus-jakarta-sans.css +2 -0
  120. package/src/runtime/fonts/press-start-2p.css +2 -0
  121. package/src/runtime/fonts/vt323.css +2 -0
  122. package/src/runtime/formula.js +148 -0
  123. package/src/runtime/graph-lab.js +72 -0
  124. package/src/runtime/meeting.css +7 -0
  125. package/src/runtime/meeting.js +10 -0
  126. package/src/runtime/motion.js +15 -0
  127. package/src/runtime/playground.js +11 -0
  128. package/src/runtime/points.js +19 -0
  129. package/src/runtime/portal-scene.css +6 -0
  130. package/src/runtime/portal-scene.js +22 -0
  131. package/src/runtime/runtime.js +827 -0
  132. package/src/runtime/science.js +89 -0
  133. package/src/runtime/skins/aluminio.css +31 -0
  134. package/src/runtime/skins/arcade.css +25 -0
  135. package/src/runtime/skins/aurora.css +17 -0
  136. package/src/runtime/skins/bauhaus.css +19 -0
  137. package/src/runtime/skins/editorial.css +19 -0
  138. package/src/runtime/skins/jornal.css +17 -0
  139. package/src/runtime/skins/manual-noite.css +14 -0
  140. package/src/runtime/skins/manual.css +14 -0
  141. package/src/runtime/skins/noite.css +21 -0
  142. package/src/runtime/skins/oceano.css +17 -0
  143. package/src/runtime/skins/pop.css +23 -0
  144. package/src/runtime/skins/prata.css +18 -0
  145. package/src/runtime/skins/prisma.css +28 -0
  146. package/src/runtime/skins/rabisco.css +18 -0
  147. package/src/runtime/skins/relevo.css +33 -0
  148. package/src/runtime/skins/sinal.css +16 -0
  149. package/src/runtime/skins/terminal.css +23 -0
  150. package/src/runtime/vendor/KATEX-LICENSE.txt +21 -0
  151. package/src/runtime/vendor/MERMAID-LICENSE.txt +21 -0
  152. package/src/runtime/vendor/PLOTLY-LICENSE.txt +21 -0
  153. package/src/runtime/vendor/katex.css +1 -0
  154. package/src/runtime/vendor/mermaid.min.js +3636 -0
  155. package/src/runtime/vendor/plotly.min.js +3620 -0
  156. package/src/runtime/video-player.js +35 -0
  157. package/src/science.js +162 -0
  158. package/src/studio/ai-response.js +36 -0
  159. package/src/studio/api-routes.js +152 -0
  160. package/src/studio/assets/contexto/leia-me.md +12 -0
  161. package/src/studio/assets/contexto/vendas.csv +7 -0
  162. package/src/studio/assets/imagens/colecao-arquitetura.png +0 -0
  163. package/src/studio/assets/imagens/colecao-cor.png +0 -0
  164. package/src/studio/assets/imagens/colecao-equipe.png +0 -0
  165. package/src/studio/assets/imagens/lavanda-cover.jpg +0 -0
  166. package/src/studio/code-routes.js +32 -0
  167. package/src/studio/command-approval.js +48 -0
  168. package/src/studio/demo-decks.js +98 -0
  169. package/src/studio/direction-routes.js +29 -0
  170. package/src/studio/errors.js +65 -0
  171. package/src/studio/exploration-demo.js +17 -0
  172. package/src/studio/exporting.js +105 -0
  173. package/src/studio/generation-report.js +10 -0
  174. package/src/studio/instance.js +48 -0
  175. package/src/studio/layout-samples.js +152 -0
  176. package/src/studio/project.js +371 -0
  177. package/src/studio/public/api-collections.js +28 -0
  178. package/src/studio/public/app.js +5265 -0
  179. package/src/studio/public/art-preview.js +10 -0
  180. package/src/studio/public/calc-fields.js +11 -0
  181. package/src/studio/public/chat-progress.js +47 -0
  182. package/src/studio/public/developer-fields.js +22 -0
  183. package/src/studio/public/explorer.js +443 -0
  184. package/src/studio/public/history.js +58 -0
  185. package/src/studio/public/index.html +867 -0
  186. package/src/studio/public/inspector.js +270 -0
  187. package/src/studio/public/lab-fields.js +5 -0
  188. package/src/studio/public/library.css +275 -0
  189. package/src/studio/public/library.html +55 -0
  190. package/src/studio/public/library.js +471 -0
  191. package/src/studio/public/merge-decks.js +155 -0
  192. package/src/studio/public/review-ui.js +100 -0
  193. package/src/studio/public/screenshot-editor.js +140 -0
  194. package/src/studio/public/share-ui.js +72 -0
  195. package/src/studio/public/slide-clipboard.js +23 -0
  196. package/src/studio/public/slide-form.js +1263 -0
  197. package/src/studio/public/studio-awareness.js +154 -0
  198. package/src/studio/public/studio-next.css +303 -0
  199. package/src/studio/public/style.css +1262 -0
  200. package/src/studio/public/ui-icons.js +2 -0
  201. package/src/studio/public/viewers.js +372 -0
  202. package/src/studio/public/visual-editor.js +326 -0
  203. package/src/studio/public/workspace.js +18 -0
  204. package/src/studio/public-files.js +1 -0
  205. package/src/studio/request-helpers.js +41 -0
  206. package/src/studio/server.js +1583 -0
  207. package/src/studio/share-routes.js +146 -0
  208. package/src/studio/slide-copy-routes.js +26 -0
  209. package/src/studio/slide-copy.js +30 -0
  210. package/src/studio/snapshot.js +160 -0
  211. package/src/studio/style-routes.js +103 -0
  212. package/src/studio/template-collections.js +97 -0
  213. package/src/studio/versions.js +95 -0
  214. package/src/studio/video-download.js +29 -0
  215. package/src/studio/visual-keys.js +52 -0
  216. package/src/studio/workspaces.js +39 -0
  217. package/src/table.js +93 -0
  218. package/src/templates/scaffold.js +372 -0
  219. package/src/themes.js +604 -0
  220. package/src/trace-view.js +450 -0
  221. package/src/uid.js +19 -0
  222. package/src/video-player.js +40 -0
  223. package/src/visual-edits.js +99 -0
  224. package/src/ws.js +134 -0
  225. package/templates/cenario/Texto no cen/303/241rio.yaml" +171 -0
  226. package/templates/cenario/imagens/cidade.svg +30 -0
  227. package/templates/cenario/imagens/pessoa.svg +15 -0
  228. package/templates/cenario-e-ciencia.yaml +47 -0
  229. package/templates/ensaio-api.yaml +160 -0
  230. package/templates/exemplo-alegre.yaml +75 -0
  231. package/templates/exemplo-keynote.yaml +90 -0
  232. package/templates/exemplo-napkin.yaml +139 -0
  233. package/templates/exemplo.yaml +228 -0
@@ -0,0 +1,49 @@
1
+ /* Laboratório didático: taxas hipotéticas homogêneas, nunca previsão de produção. */
2
+ (function(root) {
3
+ const defaults = { volume: 10000, errorRate: 2, reviewRate: 100, catchRate: 60, introducedRate: .2, seconds: 30 };
4
+ function normalize(input = {}) {
5
+ const result = {};
6
+ for (const [key, fallback] of Object.entries(defaults)) {
7
+ const n = Number(input[key] ?? fallback);
8
+ result[key] = Math.max(0, Math.min(key === 'volume' ? 10000000 : key === 'seconds' ? 3600 : 100, Number.isFinite(n) ? n : fallback));
9
+ }
10
+ return result;
11
+ }
12
+ function calculate(input) {
13
+ const s = normalize(input), p = s.errorRate / 100, r = s.reviewRate / 100;
14
+ const automatic = s.volume * p;
15
+ const caught = automatic * r * s.catchRate / 100;
16
+ const introduced = s.volume * (1-p) * r * s.introducedRate / 100;
17
+ return { ...s, automatic, caught, introduced, reviewed: automatic - caught + introduced,
18
+ hours: s.volume * r * s.seconds / 3600, delta: introduced - caught };
19
+ }
20
+ const fmt = n => n.toLocaleString('pt-BR', { maximumFractionDigits: 1 });
21
+ function mount(container = document) {
22
+ container.querySelectorAll('[data-decision-lab]').forEach(el => {
23
+ if (el.dataset.mounted) return;
24
+ el.dataset.mounted = 'true';
25
+ const initial = normalize(JSON.parse(el.dataset.decisionLab));
26
+ function update() {
27
+ const values = { ...initial };
28
+ el.querySelectorAll('input[data-param]').forEach(input => { values[input.dataset.param] = Number(input.value); });
29
+ const result = calculate(values);
30
+ el.querySelectorAll('[data-output]').forEach(out => { out.textContent = fmt(result[out.dataset.output]); });
31
+ const verdict = el.querySelector('[data-verdict]');
32
+ verdict.textContent = Math.abs(result.delta) < .000001 ? 'Mesmo total esperado de erros' : result.delta < 0 ? `${fmt(-result.delta)} erros a menos com revisão` : `${fmt(result.delta)} erros a mais com revisão`;
33
+ verdict.dataset.result = result.delta > 0 ? 'worse' : result.delta < 0 ? 'better' : 'equal';
34
+ }
35
+ el.addEventListener('input', update);
36
+ // As setas ajustam o controle, sem trocar o slide.
37
+ el.addEventListener('keydown', e => { if (e.target.matches('input,button')) e.stopPropagation(); });
38
+ el.querySelector('[data-reset]').addEventListener('click', () => {
39
+ el.querySelectorAll('input[data-param]').forEach(input => { input.value = initial[input.dataset.param]; }); update();
40
+ });
41
+ update();
42
+ });
43
+ }
44
+ root.SagaDecisionLab = { calculate, normalize, mount };
45
+ if (typeof document !== 'undefined') {
46
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => mount());
47
+ else mount();
48
+ }
49
+ })(globalThis);
@@ -0,0 +1,341 @@
1
+ // Diagramas (layout diagram): o Mermaid desenha, o sagadeck veste.
2
+ // - Famílias de cor tiradas do tema: cada grupo (subgraph) ganha um matiz e seus nós herdam esse matiz, com
3
+ // preenchimento claro e contorno da mesma cor mais escura; o grupo tem fundo bem claro e título na cor dele.
4
+ // O primeiro matiz é o destaque do tema; os outros são vizinhos harmônicos. Nada de preto puro: texto em grafite
5
+ // (ou no tom escuro da família), setas em cinza. Setas retas com cantos arredondados.
6
+ // - Ênfase só onde pedir: :::hi (tom forte do destaque), :::em (tom forte da ênfase), :::escuro, :::suave (cinza
7
+ // neutro: usuário, sistemas externos, bancos), :::vazado (tracejado: opcional, futuro).
8
+ // - Direção automática (fluxograma): desenha deitado (LR) e em pé (TB) e fica com o que deixa a letra maior na área
9
+ // do slide (autoDirection: false no slide mantém a do código).
10
+ // - Compacto: o desenho escala para ocupar a área do slide (sem vazios enormes).
11
+ // - Fonte do tema; ícones do sagadeck nos rótulos (":rocket:" vira o desenho, medido antes do layout).
12
+ // - Código errado ou desenho que precisou encolher demais: window.sagadeckDiagramErrors / sagadeckDiagramWarnings
13
+ // (o Studio devolve à IA para corrigir) e, no caso do erro, um aviso legível no slide.
14
+ (function () {
15
+ let loader, seq = 0;
16
+ window.sagadeckDiagramErrors = window.sagadeckDiagramErrors || [];
17
+ window.sagadeckDiagramWarnings = window.sagadeckDiagramWarnings || [];
18
+ function load() {
19
+ if (window.mermaid) return Promise.resolve();
20
+ return loader ||= new Promise((resolve, reject) => {
21
+ const s = document.createElement("script");
22
+ s.src = "mermaid.min.js"; s.onload = resolve;
23
+ s.onerror = () => { loader = null; s.remove(); reject(new Error("Não foi possível carregar o desenhador de diagramas.")); };
24
+ document.head.append(s);
25
+ });
26
+ }
27
+
28
+ // ---- cores -------------------------------------------------------------------------------------------------
29
+ const cv = document.createElement("canvas").getContext("2d");
30
+ // cor CSS qualquer (var, rgb, color-mix) -> #rrggbb
31
+ function hex(color, host) {
32
+ const i = document.createElement("i");
33
+ i.style.color = color; host.append(i);
34
+ const c = getComputedStyle(i).color; i.remove();
35
+ const m = c.match(/[\d.]+/g) || [0, 0, 0];
36
+ if (/^color\(srgb/.test(c)) return "#" + m.slice(0, 3).map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("");
37
+ cv.fillStyle = c; const f = cv.fillStyle;
38
+ return f.startsWith("#") ? f : "#" + m.slice(0, 3).map((v) => (+v).toString(16).padStart(2, "0")).join("");
39
+ }
40
+ const rgb = (h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));
41
+ const toHex = (a) => "#" + a.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")).join("");
42
+ const mix = (a, b, p) => toHex(rgb(a).map((v, i) => v * p + rgb(b)[i] * (1 - p)));
43
+ const lum = (h) => { const [r, g, b] = rgb(h).map((v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; };
44
+ const contrast = (a, b) => { const [p, q] = [lum(a), lum(b)].sort((m, n) => n - m); return (p + 0.05) / (q + 0.05); };
45
+ const on = (bgc, a, b) => (contrast(bgc, a) >= contrast(bgc, b) ? a : b);
46
+ function hueOf(h) {
47
+ const [r, g, b] = rgb(h).map((v) => v / 255), mx = Math.max(r, g, b), mn = Math.min(r, g, b), d = mx - mn;
48
+ if (!d) return { h: 220, s: 0 };
49
+ const hh = mx === r ? ((g - b) / d) % 6 : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
50
+ return { h: (hh * 60 + 360) % 360, s: d / (1 - Math.abs(mx + mn - 1)) };
51
+ }
52
+ function hsl(h, s, l) {
53
+ s /= 100; l /= 100;
54
+ const k = (n) => (n + h / 30) % 12, a = s * Math.min(l, 1 - l);
55
+ return toHex([0, 8, 4].map((n) => 255 * (l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))))));
56
+ }
57
+ const dist = (a, b) => { const d = Math.abs(a - b) % 360; return Math.min(d, 360 - d); };
58
+
59
+ // Uma família = um matiz em vários tons. Tema claro: fundo claro, contorno médio, letra escura da mesma cor.
60
+ // Tema escuro: o contrário. "forte" é a ênfase (:::hi/:::em), mais saturada, sem virar um bloco chapado.
61
+ function family(h, dark, sat = 1) {
62
+ const s = (v) => v * sat;
63
+ return dark ? {
64
+ fill: hsl(h, s(28), 21), stroke: hsl(h, s(48), 58), text: hsl(h, s(55), 88),
65
+ cbg: hsl(h, s(22), 12.5), cstroke: hsl(h, s(28), 28), ctext: hsl(h, s(50), 74),
66
+ strong: hsl(h, s(45), 36), strongStroke: hsl(h, s(60), 70), strongText: hsl(h, s(40), 95), line: hsl(h, s(40), 60),
67
+ } : {
68
+ fill: hsl(h, s(62), 91), stroke: hsl(h, s(42), 46), text: hsl(h, s(48), 23),
69
+ cbg: hsl(h, s(55), 97.5), cstroke: hsl(h, s(40), 83), ctext: hsl(h, s(45), 36),
70
+ strong: hsl(h, s(62), 79), strongStroke: hsl(h, s(48), 36), strongText: hsl(h, s(55), 17), line: hsl(h, s(40), 52),
71
+ };
72
+ }
73
+ // Família a partir de uma cor pronta (paletas de família: os parentes da cor da marca). Os tons saem da própria
74
+ // cor misturada com o fundo e com o texto, e a letra é a mais escura/clara que ainda lê bem sobre o fundo do nó.
75
+ // parente muito claro (ou muito escuro, no tema escuro) sumiria no fundo: vai em direção ao texto até aparecer
76
+ // (mantendo o matiz e a saturação: o rosa-claro vira um rosa médio, não um cinza)
77
+ function lightness(c) { const [r, g, b] = rgb(c).map((v) => v / 255); return (Math.max(r, g, b) + Math.min(r, g, b)) / 2 * 100; }
78
+ function visible(c, L) {
79
+ if (contrast(c, L.bg) >= 3) return c;
80
+ const { h, s } = hueOf(c), sat = Math.max(s * 100, 45);
81
+ let l = lightness(c);
82
+ for (let k = 0; k < 40; k++) { l += L.dark ? 2 : -2; const y = hsl(h, sat, l); if (contrast(y, L.bg) >= 3) return y; }
83
+ return hsl(h, sat, L.dark ? 70 : 40);
84
+ }
85
+ function familyOf(c0, L) {
86
+ const { bg, fg } = L, c = visible(c0, L);
87
+ const t = (x, base, min = 5.5) => { for (let p = 0.35; p <= 1; p += 0.05) { const y = mix(fg, x, p); if (contrast(y, base) >= min) return y; } return mix(fg, x, 0.92); };
88
+ const fill = L.dark ? mix(c, bg, 0.34) : mix(c, bg, 0.2), strong = L.dark ? mix(c, bg, 0.62) : mix(c, bg, 0.5);
89
+ return {
90
+ fill, stroke: L.dark ? mix(c, fg, 0.75) : mix(c, fg, 0.78), text: t(c, fill),
91
+ cbg: mix(c, bg, L.dark ? 0.12 : 0.055), cstroke: mix(c, bg, L.dark ? 0.35 : 0.3), ctext: t(c, mix(c, bg, L.dark ? 0.12 : 0.055), 4.5),
92
+ strong, strongStroke: L.dark ? mix(c, fg, 0.6) : mix(c, fg, 0.62), strongText: t(c, strong), line: mix(c, bg, 0.8),
93
+ };
94
+ }
95
+
96
+ // matizes: o destaque do tema, a ênfase (a do tema, ou a complementar se ela for parecida com o destaque ou
97
+ // cinza) e vizinhos harmônicos bem separados
98
+ const CANDIDATOS = [212, 272, 145, 175, 28, 335, 48, 195, 300, 100];
99
+ function hues(L) {
100
+ const out = [L.hueHi.h];
101
+ out.push(L.hueEm.s > 0.15 && dist(L.hueEm.h, out[0]) > 30 ? L.hueEm.h : (L.hueHi.h + 180) % 360);
102
+ for (const c of CANDIDATOS) if (out.every((h) => dist(h, c) > 32)) out.push(c);
103
+ return out;
104
+ }
105
+
106
+ function look(box) {
107
+ const slide = box.closest(".slide") || document.body, cs = getComputedStyle(slide);
108
+ const v = (n, fb) => hex(cs.getPropertyValue(n).trim() || fb, slide);
109
+ const bg = v("--bg", "#ffffff"), fg = v("--fg", "#111111"), hi = v("--hi", "#0f6cbd"), em = v("--em", "#d33a2c");
110
+ // a fonte do tema: um texto de corpo de mentira dentro do slide (o slide de diagrama pode não ter nenhum)
111
+ const probe = document.createElement("div");
112
+ probe.className = "t f-body"; probe.style.cssText = "position:absolute;visibility:hidden;pointer-events:none";
113
+ slide.append(probe);
114
+ const font = getComputedStyle(probe).fontFamily.replace(/"/g, "'"); // aspas simples: o Mermaid põe a fonte em atributos
115
+ probe.remove();
116
+ const dark = lum(bg) < 0.25;
117
+ const L = { bg, fg, hi, em, font, dark, hueHi: hueOf(hi), hueEm: hueOf(em) };
118
+ // grafite no lugar do preto (e um cinza claro no lugar do branco, no tema escuro); setas em cinza médio
119
+ L.ink = mix(fg, bg, 0.8); L.soft = mix(fg, bg, 0.6); L.arrow = mix(fg, bg, 0.5); L.faint = mix(fg, bg, 0.12);
120
+ // tema quase sem cor (preto e branco): as famílias ficam discretas
121
+ const sat = Math.max(0.35, Math.min(1, L.hueHi.s * 1.4));
122
+ // paleta de família (--c-f1…): destaque, ênfase e os parentes dela; senão, matizes harmônicos
123
+ const kin = [];
124
+ for (let i = 1; i <= 8; i++) { const c = cs.getPropertyValue("--c-f" + i).trim(); if (c) kin.push(hex(c, slide)); }
125
+ if (kin.length >= 2) {
126
+ // os parentes bem diferentes do destaque e entre si (o muito claro vira grupo; o destaque já é família 0)
127
+ const fams = [], seen = [visible(hi, L), visible(em, L)];
128
+ const differs = (c) => seen.every((x) => contrast(x, c) > 1.2 || dist(hueOf(x).h, hueOf(c).h) > 12);
129
+ for (const c of kin.map((k) => visible(k, L))) if (differs(c)) { fams.push(c); seen.push(c); }
130
+ // poucos parentes diferentes: variações da própria família (o matiz gira um pouco), nunca o arco-íris
131
+ // (giro pequeno do matiz e tons mais escuros/claros: o laranja vira cobre, não oliva)
132
+ for (const [turn, dl] of [[16, 0], [-16, 0], [0, L.dark ? 14 : -14], [0, L.dark ? -10 : 10], [16, L.dark ? 12 : -12]]) {
133
+ if (fams.length >= 4) break;
134
+ const v = visible(hi, L), b = hueOf(v), c = visible(hsl((b.h + turn + 360) % 360, Math.max(b.s * 100, 40), lightness(v) + dl), L);
135
+ if (differs(c)) { fams.push(c); seen.push(c); }
136
+ }
137
+ L.fams = [familyOf(hi, L), familyOf(em, L), ...fams.map((c) => familyOf(c, L))];
138
+ } else L.fams = hues(L).map((h) => family(h, dark, sat));
139
+ L.neutral = family(220, dark, 0.12);
140
+ L.hiFam = L.fams[0];
141
+ L.emFam = L.fams[1];
142
+ // grupos: nem a família do destaque (dos nós soltos) nem a da ênfase (a ênfase tem que se destacar do grupo)
143
+ L.groupFams = L.fams.slice(2);
144
+ // ramos do mapa mental, jornada, linha do tempo: também sem a ênfase (ela é o detalhe, não um ramo)
145
+ L.scaleFams = [L.fams[0], ...L.groupFams];
146
+ return L;
147
+ }
148
+
149
+ function config(L, curve) {
150
+ const F = L.hiFam;
151
+ const themeCSS = [
152
+ `.node rect,.node polygon,.node circle,.node ellipse,.node path{stroke-width:1.6px;filter:drop-shadow(0 1px 2px rgba(0,0,0,${L.dark ? 0.35 : 0.08}))}`,
153
+ `.node rect{rx:9px;ry:9px}`,
154
+ `.nodeLabel,.label,.edgeLabel,text,.messageText,.actor,.noteText,.cluster-label{font-family:${L.font}!important}`,
155
+ `.flowchart-link,.edgePath .path,.relation,.transition{stroke:${L.arrow}!important;stroke-width:1.7px!important}`,
156
+ `marker path,.marker,.arrowheadPath,.arrowMarkerPath{fill:${L.arrow}!important;stroke:${L.arrow}!important}`,
157
+ `.edgeLabel,.edgeLabel p,.edgeLabel span,.edgeLabel rect{background:${L.bg}!important;color:${L.soft}!important;font-size:17px;font-weight:600}`,
158
+ `.cluster rect{rx:14px!important;ry:14px!important;stroke-width:1.5px!important}`,
159
+ `.cluster-label .nodeLabel,.cluster-label span,.cluster-label p{font-weight:700;letter-spacing:.02em}`,
160
+ `.dgi{display:inline-flex;align-items:center;height:26px;vertical-align:-6px}.dgi svg{width:26px;height:26px;display:block;flex:none}`,
161
+ // o ícone fica dentro do nó: as regras de forma do Mermaid (.node path…) não podem pintá-lo
162
+ `.dgi svg *{stroke:currentColor!important;fill:none!important;filter:none!important;stroke-width:inherit!important}`,
163
+ // (nada de mudar peso/tamanho da letra dos nós aqui: o Mermaid já mediu o texto, e o rótulo cortaria)
164
+ `.actor{stroke-width:1.6px}.messageLine0,.messageLine1{stroke:${L.arrow}!important}`,
165
+ // números da sequência (autonumber): bolinha no tom forte, número legível
166
+ `[id$="-sequencenumber"],[id$="-sequencenumber"] circle{fill:${F.strongStroke}!important;stroke:${F.strongStroke}!important}.sequenceNumber{fill:${on(F.strongStroke, L.bg, L.ink)}!important}`,
167
+ // mapa mental, jornada e linha do tempo: uma família por ramo, com contorno e linha da própria cor
168
+ // (o Mermaid pinta o ramo N com a cor N+1 da escala: o contorno e a linha acompanham)
169
+ ...Array.from({ length: 11 }, (_, i) => L.scaleFams[(i + 1) % L.scaleFams.length]).map((f, i) => `.section-${i} rect,.section-${i} path,.section-${i} circle,.section-${i} polygon{stroke:${f.stroke};stroke-width:1.6px}.section-edge-${i}{stroke:${f.line}!important}`),
170
+ `.section-root rect,.section-root path,.section-root circle,.section-root polygon{fill:${F.strong}!important;stroke:${F.strongStroke}!important;stroke-width:2px}`,
171
+ ].join("\n");
172
+ const sizes = { useMaxWidth: false };
173
+ // 12 cores de escala sempre (com mais ramos que famílias, as famílias dão a volta; senão o Mermaid usa as dele)
174
+ const S = Array.from({ length: 12 }, (_, i) => L.scaleFams[i % L.scaleFams.length]);
175
+ const cs = Object.fromEntries(S.flatMap((f, i) => [["cScale" + i, f.fill], ["cScaleLabel" + i, f.text], ["cScalePeer" + i, f.stroke]]));
176
+ const N = L.neutral, E = L.emFam;
177
+ return {
178
+ startOnLoad: false, securityLevel: "antiscript", theme: "base", fontFamily: L.font, themeCSS,
179
+ themeVariables: {
180
+ fontFamily: L.font, fontSize: "22px", background: L.bg, textColor: L.ink, titleColor: L.ink, lineColor: L.arrow,
181
+ primaryColor: F.fill, primaryBorderColor: F.stroke, primaryTextColor: F.text,
182
+ secondaryColor: E.fill, secondaryBorderColor: E.stroke, secondaryTextColor: E.text,
183
+ tertiaryColor: N.fill, tertiaryBorderColor: N.stroke, tertiaryTextColor: N.text,
184
+ mainBkg: F.fill, nodeBorder: F.stroke, clusterBkg: N.cbg, clusterBorder: N.cstroke, edgeLabelBackground: L.bg,
185
+ actorBkg: F.fill, actorBorder: F.stroke, actorTextColor: F.text, actorLineColor: L.faint, signalColor: L.arrow, signalTextColor: L.ink,
186
+ labelBoxBkgColor: F.fill, labelBoxBorderColor: F.stroke, labelTextColor: F.text, loopTextColor: L.soft,
187
+ noteBkgColor: E.fill, noteBorderColor: E.stroke, noteTextColor: E.text,
188
+ activationBkgColor: F.strong, activationBorderColor: F.strongStroke, sequenceNumberColor: L.bg,
189
+ stateBkg: F.fill, stateLabelColor: F.text, compositeBackground: N.cbg, transitionColor: L.arrow, transitionLabelColor: L.soft,
190
+ classText: F.text, git0: F.strong, gitBranchLabel0: F.strongText, pie1: F.stroke, pie2: E.stroke, pie3: N.stroke, pie4: F.fill, ...cs,
191
+ },
192
+ flowchart: { ...sizes, htmlLabels: true, nodeSpacing: 34, rankSpacing: 50, padding: 14, diagramPadding: 6, curve: window.__dgCurve || curve || "basis", subGraphTitleMargin: { top: 6, bottom: 14 } },
193
+ sequence: { ...sizes, actorMargin: 44, boxMargin: 6, messageMargin: 28, mirrorActors: false, diagramMarginX: 6, diagramMarginY: 6 },
194
+ state: { ...sizes, padding: 10 }, class: sizes, er: sizes, journey: sizes, gantt: sizes, mindmap: sizes, timeline: sizes, block: sizes, requirement: sizes,
195
+ };
196
+ }
197
+
198
+ // ---- pintura depois do desenho: famílias por grupo -------------------------------------------------------
199
+ const ENFASE = ["hi", "em", "escuro", "suave", "vazado"];
200
+ function paint(svg, L) {
201
+ // mapa mental, jornada, linha do tempo…: as famílias vão por ramo (CSS .section-N), não por grupo
202
+ if (!/flowchart|state|class|block|er/i.test(svg.getAttribute("aria-roledescription") || "")) return;
203
+ const area = (r) => r.width * r.height;
204
+ const inside = (r, c) => c.x >= r.left && c.x <= r.right && c.y >= r.top && c.y <= r.bottom;
205
+ const center = (r) => ({ x: r.left + r.width / 2, y: r.top + r.height / 2 });
206
+ // grupos (subgraph / estado composto), na ordem em que aparecem: cada um ganha uma família
207
+ const clusters = [...svg.querySelectorAll("g.cluster")].map((g, i) => {
208
+ const rect = g.querySelector("rect");
209
+ return rect && { g, rect, box: rect.getBoundingClientRect(), fam: L.groupFams[i % L.groupFams.length] };
210
+ }).filter(Boolean);
211
+ const owner = (r) => clusters.filter((c) => inside(c.box, center(r))).sort((a, b) => area(a.box) - area(b.box))[0];
212
+ for (const c of clusters) {
213
+ c.rect.style.fill = c.fam.cbg; c.rect.style.stroke = c.fam.cstroke;
214
+ c.g.querySelectorAll(".cluster-label *, .cluster-label").forEach((t) => { t.style.color = c.fam.ctext; t.style.fill = c.fam.ctext; });
215
+ }
216
+ const set = (n, fill, stroke, text, dash) => {
217
+ n.querySelectorAll("rect, polygon, circle, ellipse, path").forEach((s) => {
218
+ if (s.closest(".label, .dgi, foreignObject")) return;
219
+ s.style.fill = fill; s.style.stroke = stroke;
220
+ if (dash) { s.style.strokeDasharray = "7 5"; s.style.filter = "none"; }
221
+ });
222
+ n.querySelectorAll(".nodeLabel, .nodeLabel *, span, p, text, tspan").forEach((t) => { if (!t.closest(".dgi")) { t.style.color = text; t.style.fill = text; } });
223
+ n.querySelectorAll(".dgi svg").forEach((s) => { s.style.color = text; s.style.stroke = text; });
224
+ };
225
+ svg.querySelectorAll("g.node").forEach((n) => {
226
+ const c = owner(n.getBoundingClientRect());
227
+ const F = c ? c.fam : L.hiFam;
228
+ const k = ENFASE.find((x) => n.classList.contains(x));
229
+ if (k === "hi") set(n, L.hiFam.strong, L.hiFam.strongStroke, L.hiFam.strongText);
230
+ else if (k === "em") set(n, L.emFam.strong, L.emFam.strongStroke, L.emFam.strongText);
231
+ else if (k === "escuro") set(n, L.ink, L.ink, L.bg);
232
+ else if (k === "suave") set(n, L.neutral.fill, L.neutral.stroke, L.neutral.text);
233
+ else if (k === "vazado") set(n, c ? c.fam.cbg : L.bg, F.stroke, F.text, true);
234
+ else set(n, F.fill, F.stroke, F.text);
235
+ });
236
+ // rótulo de seta dentro de um grupo: fundo do grupo (e não um retângulo da cor do slide)
237
+ svg.querySelectorAll(".edgeLabel").forEach((e) => {
238
+ const r = e.getBoundingClientRect(); if (!r.width) return;
239
+ const c = owner(r); if (!c) return;
240
+ e.querySelectorAll("*").forEach((x) => { x.style.setProperty("background", c.fam.cbg, "important"); });
241
+ e.querySelectorAll("rect").forEach((x) => { x.style.fill = c.fam.cbg; });
242
+ });
243
+ }
244
+
245
+ // ---- direção e tamanho -------------------------------------------------------------------------------------
246
+ const size = (svgText) => {
247
+ const m = svgText.match(/viewBox="[-\d.]+ [-\d.]+ ([\d.]+) ([\d.]+)"/);
248
+ return m ? { w: +m[1], h: +m[2] } : null;
249
+ };
250
+ const scaleFor = (box, s) => Math.min(box.clientWidth / s.w, box.clientHeight / s.h, 2.2);
251
+ // fluxograma: a mesma coisa deitada e em pé (as direções dentro dos grupos ficam como estão)
252
+ function flipped(code) {
253
+ const m = code.match(/^(\s*(?:flowchart|graph))(?:[ \t]+(LR|RL|TB|TD|BT))?[ \t]*(\r?\n|$)/);
254
+ if (!m) return null;
255
+ const dir = m[2] || "TB";
256
+ const other = /LR|RL/.test(dir) ? "TB" : "LR";
257
+ return code.replace(m[0], `${m[1]} ${other}${m[3]}`);
258
+ }
259
+
260
+ // o desenho ocupa a área do slide: pequeno cresce (até 2,2×), grande encolhe; devolve a escala usada
261
+ function fit(box, svg) {
262
+ const vb = svg.viewBox && svg.viewBox.baseVal;
263
+ const w = vb && vb.width ? vb.width : svg.getBBox().width, h = vb && vb.height ? vb.height : svg.getBBox().height;
264
+ const k = Math.min(box.clientWidth / w, box.clientHeight / h, 2.2);
265
+ svg.setAttribute("width", Math.floor(w * k));
266
+ svg.setAttribute("height", Math.floor(h * k));
267
+ svg.style.maxWidth = "none";
268
+ return k;
269
+ }
270
+
271
+ async function renderOne(box) {
272
+ const code = box.querySelector(".dg-src").textContent.trim();
273
+ const slide = box.closest(".slide");
274
+ const slideNo = slide ? Number(slide.dataset.idx) + 1 : null;
275
+ box.querySelectorAll("svg,.dg-error").forEach((e) => e.remove());
276
+ const id = "dg" + (++seq) + "-" + Math.random().toString(36).slice(2, 7);
277
+ try {
278
+ const L = look(box);
279
+ let icons = {};
280
+ try { icons = JSON.parse(box.dataset.dgIcons || "{}"); } catch {}
281
+ // Ícone: o Mermaid não conta imagem (nem espaço especial) na medida do rótulo, e o nó sairia estreito cortando o
282
+ // texto. Então, antes do desenho, no lugar do ícone vai um texto de verdade ("MM"), que ele mede com a fonte real;
283
+ // depois do desenho esse trecho vira o ícone, com a MESMA largura medida: o rótulo cabe no nó com qualquer fonte.
284
+ const withMarks = code.replace(/<i class=dgi-([a-z0-9-]+)><\/i>/g, (m, name) => (icons[name] ? `<span class=dgp-${name}>MM</span>` : ""));
285
+ window.mermaid.initialize(config(L, box.dataset.dgCurve));
286
+ let { svg } = await window.mermaid.render(id, withMarks);
287
+ // direção: fica com a que deixa a letra maior (a do código ganha no empate: só troca se for bem melhor)
288
+ const other = box.dataset.dgAuto !== "0" && flipped(withMarks);
289
+ if (other) {
290
+ try {
291
+ const alt = await window.mermaid.render(id + "b", other);
292
+ const a = size(svg), b = size(alt.svg);
293
+ if (a && b && scaleFor(box, b) > scaleFor(box, a) * 1.12) svg = alt.svg;
294
+ } catch {}
295
+ document.getElementById(id + "b")?.remove(); document.getElementById("d" + id + "b")?.remove();
296
+ }
297
+ box.insertAdjacentHTML("beforeend", svg);
298
+ const el = box.querySelector("svg");
299
+ el.removeAttribute("style");
300
+ el.querySelectorAll('span[class^="dgp-"]').forEach((ph) => {
301
+ const w = ph.offsetWidth || 33;
302
+ const span = document.createElement("span"); span.className = "dgi";
303
+ span.style.width = w + "px";
304
+ span.innerHTML = (icons[ph.className.slice(4)] || "").replace(/\swidth="\d+"/, ' width="26"').replace(/\sheight="\d+"/, ' height="26"');
305
+ ph.replaceWith(span);
306
+ });
307
+ const k = fit(box, el);
308
+ paint(el, L);
309
+ box.dataset.dgScale = k.toFixed(2);
310
+ // encolheu demais para caber: a letra fica pequena na tela (a IA recebe o aviso e reorganiza)
311
+ if (k < 0.62) window.sagadeckDiagramWarnings.push({ slide: slideNo, scale: +k.toFixed(2),
312
+ warning: `o diagrama precisou encolher para ${Math.round(k * 100)}% para caber (letra pequena), mesmo na melhor direção: use menos nós, rótulos mais curtos, agrupe em subgraph ou divida em dois slides (área ${box.clientWidth}×${box.clientHeight}).` });
313
+ box.dataset.dg = "ready";
314
+ } catch (e) {
315
+ document.getElementById(id)?.remove(); document.getElementById("d" + id)?.remove();
316
+ const msg = String((e && (e.str || e.message)) || e).replace(/\s+/g, " ").slice(0, 400);
317
+ const err = document.createElement("div");
318
+ err.className = "dg-error";
319
+ err.innerHTML = "<b>O diagrama tem um erro no código</b><span></span>";
320
+ err.querySelector("span").textContent = msg;
321
+ box.append(err);
322
+ box.dataset.dg = "error";
323
+ window.sagadeckDiagramErrors.push({ slide: slideNo, error: msg });
324
+ }
325
+ }
326
+
327
+ // um de cada vez (o Mermaid tem configuração global); devolve quando todos estiverem desenhados
328
+ let chain = Promise.resolve();
329
+ function mount(root = document) {
330
+ const boxes = [...root.querySelectorAll(".dg-box:not([data-dg])")];
331
+ if (!boxes.length) return chain;
332
+ boxes.forEach((b) => { b.dataset.dg = "pending"; });
333
+ // a fonte do tema (ou uma mais larga, de rabisco) pode chegar depois: o Mermaid mede o texto com a que estiver
334
+ // carregada, e o rótulo cortaria. Espera as fontes antes de desenhar.
335
+ chain = chain.then(load).then(() => document.fonts && document.fonts.ready).then(async () => { for (const b of boxes) await renderOne(b); })
336
+ .catch((e) => boxes.forEach((b) => { if (b.dataset.dg === "pending") { b.dataset.dg = "error"; b.insertAdjacentHTML("beforeend", `<div class="dg-error"><b>${e.message}</b></div>`); } }));
337
+ return chain;
338
+ }
339
+ window.SagaDiagrams = { mount, redraw(root = document) { root.querySelectorAll(".dg-box[data-dg]").forEach((b) => b.removeAttribute("data-dg")); return mount(root); } };
340
+ window.SagaDiagramsReady = mount();
341
+ })();