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,450 @@
1
+ // Desenho da execução rastreada (layout algo com program:, ou um algoritmo do catálogo). Cada passo vira um quadro:
2
+ // à esquerda as estruturas, desenhadas conforme o tipo do dado (lista de números → barras, texto e listas → casas,
3
+ // lista de listas → grade, dicionário → tabela, dicionário de vizinhos → grafo, objeto com filhos → árvore ou lista
4
+ // ligada), com os ponteiros (i, j, atual…) e o que foi lido, escrito e comparado aceso; à direita o código com a linha
5
+ // atual e o painel de variáveis de cada chamada, como num depurador.
6
+ import { runProgram, PyError } from "./pytrace.js";
7
+ import { CATALOG } from "./algo-catalog.js";
8
+ import { esc } from "./markup.js";
9
+
10
+ const MAX_STRUCTS = 4;
11
+ const isRef = (v) => v !== null && typeof v === "object" && "ref" in v;
12
+ const normView = (v) => (Array.isArray(v) ? v.map((e) => (typeof e === "string" ? { var: e } : e)).filter((e) => e && e.var) : null);
13
+
14
+ // o que rodar: programa do slide (program + call) ou do catálogo (algorithm: kmp …)
15
+ const EXEMPLO = { program: ["def soma(v):", " total = 0", " for x in v:", " total += x # soma {x}: total = {total}", " return total"].join("\n"), call: "soma([3, 1, 4, 1, 5])" };
16
+ export function tracePlan(s) {
17
+ if (!s.program && s.algorithm === "program") return { ...EXEMPLO, view: normView(s.view ?? s.show), name: s.name || "" };
18
+ if (s.program) return { program: String(s.program), call: s.call ? String(s.call) : null, view: normView(s.view ?? s.show), name: s.name || "" };
19
+ const cat = CATALOG[String(s.algorithm || "")];
20
+ if (cat) return { program: cat.program, call: cat.call(s), view: normView(s.view ?? s.show ?? cat.view), name: s.name || cat.name };
21
+ return null;
22
+ }
23
+
24
+ const fmtNum = (n) => (n === Infinity ? "∞" : n === -Infinity ? "-∞" : Number.isInteger(n) ? String(n) : String(+n.toFixed(4)));
25
+ function short(v, heap, max = 36, seen = new Set()) {
26
+ let s;
27
+ if (v === null || v === undefined) s = "None";
28
+ else if (v === true) s = "True"; else if (v === false) s = "False";
29
+ else if (typeof v === "number") s = fmtNum(v);
30
+ else if (typeof v === "string") s = `'${v}'`;
31
+ else if (v.repr) s = v.repr;
32
+ else if (seen.has(v.ref)) s = "…";
33
+ else {
34
+ const n = heap[v.ref]; seen = new Set([...seen, v.ref]);
35
+ const r = (x) => short(x, heap, max, seen);
36
+ if (!n) s = "?";
37
+ else if (n.t === "list") s = `[${n.items.map(r).join(", ")}]`;
38
+ else if (n.t === "tuple") s = `(${n.items.map(r).join(", ")})`;
39
+ else if (n.t === "deque") s = `deque([${n.items.map(r).join(", ")}])`;
40
+ else if (n.t === "set") s = n.items.length ? `{${n.items.map(r).join(", ")}}` : "set()";
41
+ else if (n.t === "dict") s = `{${n.entries.map(([k, x]) => `${r(k)}: ${r(x)}`).join(", ")}}`;
42
+ else s = `${n.cls}(${n.label === undefined ? "" : r(n.label)})`;
43
+ }
44
+ return s.length > max ? s.slice(0, max - 1) + "…" : s;
45
+ }
46
+ const cellText = (v, heap) => (typeof v === "string" ? v : short(v, heap, 12));
47
+
48
+ function findVar(st, name, from) {
49
+ const fr = st.frames;
50
+ for (let k = fr.length - 1; k >= 0; k--) if ((!from || fr[k].name === from) && name in fr[k].vars) return { v: fr[k].vars[name], k };
51
+ return null;
52
+ }
53
+ // ids alcançáveis a partir de um valor (para não desenhar de novo o que já está dentro de outra estrutura)
54
+ function reach(v, heap, acc = new Set()) {
55
+ if (!isRef(v) || acc.has(v.ref)) return acc;
56
+ acc.add(v.ref);
57
+ const n = heap[v.ref];
58
+ if (!n) return acc;
59
+ (n.items || []).forEach((x) => reach(x, heap, acc));
60
+ (n.entries || []).forEach(([k, x]) => { reach(k, heap, acc); reach(x, heap, acc); });
61
+ Object.values(n.attrs || {}).forEach((x) => reach(x, heap, acc));
62
+ return acc;
63
+ }
64
+ function isAdjacency(n, heap) {
65
+ if (n.t !== "dict" || n.entries.length < 2) return false;
66
+ const keys = new Set(n.entries.map(([k]) => k));
67
+ if ([...keys].some((k) => k !== null && typeof k === "object")) return false;
68
+ let hits = 0;
69
+ for (const [, v] of n.entries) {
70
+ if (!isRef(v)) return false;
71
+ const c = heap[v.ref];
72
+ if (!c || !["list", "set", "tuple", "dict"].includes(c.t)) return false;
73
+ for (const nb of neighborsOf(c, heap)) if (keys.has(nb.to)) hits++;
74
+ }
75
+ return hits > 0;
76
+ }
77
+ function neighborsOf(c, heap) {
78
+ if (c.t === "dict") return c.entries.map(([k, w]) => ({ to: k, w }));
79
+ return c.items.map((x) => {
80
+ if (isRef(x)) { const p = heap[x.ref]; if (p && (p.t === "tuple" || p.t === "list") && p.items.length === 2 && !isRef(p.items[0])) return { to: p.items[0], w: p.items[1] }; return { to: short(x, heap) }; }
81
+ return { to: x };
82
+ });
83
+ }
84
+
85
+ function kindOf(v, heap, as, indexed) {
86
+ if (as) return as;
87
+ if (typeof v === "string") return "cells";
88
+ const n = heap[v.ref];
89
+ if (!n) return "cells";
90
+ if (n.t === "list" || n.t === "tuple" || n.t === "deque") {
91
+ if (n.t === "list" && n.items.length && n.items.every((x) => isRef(x) && heap[x.ref]?.t === "list")) return "grid";
92
+ if (n.t === "list" && indexed.has(v.ref) && n.items.length >= 2 && n.items.length <= 24 && n.items.every((x) => typeof x === "number" && x >= 0 && Number.isFinite(x))) return "bars";
93
+ return "cells";
94
+ }
95
+ if (n.t === "set") return "cells";
96
+ if (n.t === "dict") return isAdjacency(n, heap) ? "graph" : "table";
97
+ return n.cls ? "tree" : "cells";
98
+ }
99
+
100
+ // ponteiros aprendidos: a variável inteira cujo valor bate com o índice lido/escrito numa estrutura quase sempre
101
+ function learnPointers(steps) {
102
+ const hits = new Map(), tot = new Map();
103
+ const bump = (m, k, n) => { const x = m.get(k) || new Map(); x.set(n, (x.get(n) || 0) + 1); m.set(k, x); };
104
+ for (const st of steps) {
105
+ const top = st.frames.at(-1)?.vars || {};
106
+ const ints = Object.entries(top).filter(([, v]) => typeof v === "number" && Number.isInteger(v));
107
+ for (const r of [...st.reads, ...st.writes]) {
108
+ const key = r[0] === "s" ? "s:" + r[1] : "r:" + r[0], idx = r[0] === "s" ? r[2] : r[1];
109
+ if (typeof idx !== "number") continue;
110
+ for (const [name, val] of ints) { bump(tot, key, name); if (val === idx) bump(hits, key, name); }
111
+ }
112
+ }
113
+ const out = new Map();
114
+ for (const [key, m] of hits) {
115
+ const names = [...m].filter(([n, h]) => h >= 2 && h / tot.get(key).get(n) >= 0.5).map(([n]) => n);
116
+ if (names.length) out.set(key, names);
117
+ }
118
+ return out;
119
+ }
120
+
121
+ // ------------------------------------------------------------------------------------------------ estruturas
122
+ function structuresFor(st, plan, ctxT) {
123
+ const out = [];
124
+ if (plan.view) {
125
+ for (const e of plan.view) {
126
+ const f = findVar(st, e.var, e.from);
127
+ if (!f) continue;
128
+ out.push({ name: e.label || e.var, v: f.v, k: f.k, e });
129
+ }
130
+ return out;
131
+ }
132
+ const covered = new Set();
133
+ for (let k = 0; k < st.frames.length && out.length < MAX_STRUCTS; k++) {
134
+ for (const [name, v] of Object.entries(st.frames[k].vars)) {
135
+ if (out.length >= MAX_STRUCTS) break;
136
+ if (isRef(v)) {
137
+ const n = st.heap[v.ref];
138
+ if (!n || covered.has(v.ref) || n.t === "tuple") continue;
139
+ reach(v, st.heap, covered);
140
+ out.push({ name, v, k, e: {} });
141
+ } else if (typeof v === "string" && ctxT.indexedStrings.has(v) && !covered.has("s:" + v)) {
142
+ covered.add("s:" + v);
143
+ out.push({ name, v, k, e: {} });
144
+ }
145
+ }
146
+ }
147
+ return out;
148
+ }
149
+
150
+ function hlFor(st, key) {
151
+ const m = new Map();
152
+ const add = (i, c) => { const s = m.get(i) || new Set(); s.add(c); m.set(i, s); };
153
+ const keyOfRead = (r) => (r[0] === "s" ? "s:" + r[1] : "r:" + r[0]);
154
+ const idxOf = (r) => (r[0] === "s" ? r[2] : r[1]);
155
+ for (const r of st.reads) if (keyOfRead(r) === key) add(idxOf(r), "cmp");
156
+ for (const w of st.writes) if ("r:" + w[0] === key) add(w[1], "wr");
157
+ for (const mt of st.matches) for (const r of mt.reads) if (keyOfRead(r) === key) add(idxOf(r), mt.eq ? "eq" : "ne");
158
+ return m;
159
+ }
160
+ const clsOf = (m, i) => { const s = m.get(i); if (!s) return ""; if (s.has("ne")) return " ne"; if (s.has("eq")) return " eq"; if (s.has("wr")) return " wr"; return " cmp"; };
161
+
162
+ function pointerMap(st, s, keyStr, learned, len) {
163
+ const map = new Map();
164
+ const add = (i, name) => { if (!Number.isInteger(i) || i < 0 || i > len) return; const a = map.get(i) || []; if (!a.includes(name)) a.push(name); map.set(i, a); };
165
+ if (s.e.pointers) for (const p of s.e.pointers) { const v = st.watch?.[p]; if (typeof v === "number") add(v, p.replace(/\s+/g, "")); }
166
+ else {
167
+ const top = st.frames.at(-1).vars;
168
+ for (const n of learned.get(keyStr) || []) if (typeof top[n] === "number") add(top[n], n);
169
+ }
170
+ return map;
171
+ }
172
+
173
+ function seqItems(v, heap) {
174
+ if (typeof v === "string") return [...v];
175
+ const n = heap[v.ref];
176
+ return n.items || n.entries?.map(([k]) => k) || [];
177
+ }
178
+
179
+ // linha de casas (texto, lista, fila…), com as de baixo alinhadas quando o slide pede (padrão sob o texto)
180
+ function cellsBlock(st, group, prevSt, T) {
181
+ const [base, ...unders] = group;
182
+ const heap = st.heap;
183
+ const baseItems = seqItems(base.v, heap);
184
+ const rows = [{ s: base, items: baseItems, off: 0 }];
185
+ for (const u of unders) {
186
+ const off = st.watch?.[u.e.offset];
187
+ rows.push({ s: u, items: seqItems(u.v, heap), off: typeof off === "number" ? off : 0 });
188
+ }
189
+ const ptrs = rows.map((r) => pointerMap(st, r.s, keyStrOf(r.s.v), T.learned, r.items.length));
190
+ let C = Math.max(1, ...rows.map((r) => r.off + r.items.length), ...ptrs.map((m, k) => Math.max(0, ...[...m.keys()].map((i) => i + rows[k].off + 1))));
191
+ C = Math.min(C, Math.max(...rows.map((r) => r.off + r.items.length)) + 1);
192
+ const html = rows.map((r, k) => {
193
+ const key = keyStrOf(r.s.v), hl = hlFor(st, key);
194
+ const cells = r.items.map((x, i) => `<span class="tr-cell${clsOf(hl, i)}${k ? " under" : ""}" style="grid-column:${r.off + i + 1}">${esc(cellText(x, heap))}</span>`).join("");
195
+ const idx = k === 0 ? `<div class="tr-grid-row tr-idx" style="--c:${C}">${r.items.map((_, i) => `<span style="grid-column:${i + 1}">${i}</span>`).join("")}</div>` : "";
196
+ const pm = ptrs[k];
197
+ const tags = pm.size ? `<div class="tr-grid-row tr-ptrs" style="--c:${C}">${[...pm].filter(([i]) => r.off + i < C).map(([i, names]) => `<span class="tr-ptr" style="grid-column:${r.off + i + 1}">${esc(names.join(" · "))}</span>`).join("")}</div>` : "";
198
+ const empty = r.items.length ? "" : `<span class="tr-empty">vazio</span>`;
199
+ return `<div class="tr-seq"><span class="tr-lab t f-label">${esc(r.s.name)}</span><div class="tr-seq-body"><div class="tr-grid-row tr-cells" style="--c:${C}">${cells}${empty}</div>${idx}${tags}</div></div>`;
200
+ }).join("");
201
+ void prevSt;
202
+ const len = Math.max(1, ...rows.flatMap((r) => r.items.map((x) => cellText(x, heap).length)));
203
+ return `<div class="tr-struct tr-k-cells" style="--c:${C};--len:${len};--cw:${Math.max(92, len * 17)}px">${html}</div>`;
204
+ }
205
+ const keyStrOf = (v) => (typeof v === "string" ? "s:" + v : "r:" + v.ref);
206
+
207
+ function barsBlock(st, s, prevSt, T) {
208
+ const heap = st.heap, n = heap[s.v.ref], items = n.items;
209
+ const key = keyStrOf(s.v), hl = hlFor(st, key), ptrs = pointerMap(st, s, key, T.learned, items.length);
210
+ const top = Math.max(1, ...items);
211
+ // troca: duas posições escritas cujos valores se inverteram desde o passo anterior
212
+ const prev = prevSt && (() => { const f = findVar(prevSt, s.e.var || s.name, s.e.from); return f && isRef(f.v) && f.v.ref === s.v.ref ? prevSt.heap[f.v.ref]?.items : null; })();
213
+ const w = st.writes.filter((x) => "r:" + x[0] === key).map((x) => x[1]);
214
+ const swap = prev && w.length === 2 && prev[w[0]] === items[w[1]] && prev[w[1]] === items[w[0]] && w[0] !== w[1] ? w : null;
215
+ return `<div class="tr-struct tr-k-bars"><span class="tr-lab t f-label">${esc(s.name)}</span><div class="algo-bars" style="--n:${items.length}">${items.map((v, i) => {
216
+ const c = clsOf(hl, i).trim();
217
+ const from = swap && swap.includes(i) ? (i === swap[0] ? swap[1] - swap[0] : swap[0] - swap[1]) : 0;
218
+ const cls = [c === "wr" && swap ? "swp" : c, c === "eq" ? "cmp" : ""].filter(Boolean).join(" ");
219
+ const tag = ptrs.get(i) ? `<span class="tr-ptr">${esc(ptrs.get(i).join(" · "))}</span>` : "";
220
+ return `<div class="algo-bar ${cls}" style="--h:${(v / top) * 100}%;--from:${from}"><span class="algo-val t f-display">${esc(fmtNum(v))}</span><i></i><span class="algo-idx t f-label">${i}</span>${tag}</div>`;
221
+ }).join("")}</div></div>`;
222
+ }
223
+
224
+ function gridBlock(st, s) {
225
+ const heap = st.heap, rows = heap[s.v.ref].items.map((r) => ({ id: r.ref, items: heap[r.ref].items }));
226
+ const cols = Math.max(1, ...rows.map((r) => r.items.length));
227
+ const body = rows.map((r, i) => {
228
+ const hl = hlFor(st, "r:" + r.id);
229
+ return `<tr><th>${i}</th>${r.items.map((x, j) => `<td class="tr-cell${clsOf(hl, j)}">${esc(cellText(x, heap))}</td>`).join("")}</tr>`;
230
+ }).join("");
231
+ return `<div class="tr-struct tr-k-grid"><span class="tr-lab t f-label">${esc(s.name)}</span><table class="tr-table"><thead><tr><th></th>${Array.from({ length: cols }, (_, j) => `<th>${j}</th>`).join("")}</tr></thead><tbody>${body}</tbody></table></div>`;
232
+ }
233
+
234
+ function tableBlock(st, s) {
235
+ const heap = st.heap, n = heap[s.v.ref], key = keyStrOf(s.v), hl = hlFor(st, key);
236
+ const k2 = (k) => (typeof k === "boolean" ? Number(k) : k);
237
+ const ents = n.entries;
238
+ if (!ents.length) return `<div class="tr-struct tr-k-table"><span class="tr-lab t f-label">${esc(s.name)}</span><span class="tr-empty">vazio</span></div>`;
239
+ const cell = (k, v) => `<td class="tr-cell${clsOf(hl, k2(k))}">${esc(cellText(v, heap))}</td>`;
240
+ if (ents.length <= 12) {
241
+ return `<div class="tr-struct tr-k-table"><span class="tr-lab t f-label">${esc(s.name)}</span><table class="tr-table tr-horiz"><tr>${ents.map(([k]) => `<th>${esc(cellText(k, heap))}</th>`).join("")}</tr><tr>${ents.map(([k, v]) => cell(k, v)).join("")}</tr></table></div>`;
242
+ }
243
+ return `<div class="tr-struct tr-k-table"><span class="tr-lab t f-label">${esc(s.name)}</span><table class="tr-table">${ents.map(([k, v]) => `<tr><th>${esc(cellText(k, heap))}</th>${cell(k, v)}</tr>`).join("")}</table></div>`;
244
+ }
245
+
246
+ // árvore (ou lista ligada): filhos são os atributos que apontam para outro objeto; o vazio de um lado ocupa meia casa
247
+ function treeBlock(st, s, prevSt) {
248
+ const heap = st.heap;
249
+ const all = reach(s.v, heap);
250
+ const childAttrs = new Set();
251
+ for (const id of all) { const n = heap[id]; if (n?.attrs) for (const [k, v] of Object.entries(n.attrs)) if (isRef(v) && (heap[v.ref]?.cls || (heap[v.ref]?.t === "list" && heap[v.ref].items.every((x) => isRef(x) && heap[x.ref]?.cls)))) childAttrs.add(k); }
252
+ const slotsOf = (id) => {
253
+ const n = heap[id];
254
+ if (!n?.attrs) return [];
255
+ const out = [];
256
+ for (const k of Object.keys(n.attrs)) {
257
+ if (!childAttrs.has(k)) continue;
258
+ const v = n.attrs[k];
259
+ if (v === null) out.push(null);
260
+ else if (isRef(v) && heap[v.ref]?.cls) out.push(v.ref);
261
+ else if (isRef(v) && heap[v.ref]?.t === "list") heap[v.ref].items.forEach((x) => out.push(x.ref));
262
+ }
263
+ return out;
264
+ };
265
+ const chain = [...all].every((id) => !heap[id]?.cls || slotsOf(id).length <= 1);
266
+ const pos = new Map(); const edges = [];
267
+ let maxD = 0, maxX = 0;
268
+ const seen = new Set();
269
+ const width = (id, stack = new Set()) => {
270
+ if (stack.has(id)) return 1;
271
+ const sl = slotsOf(id);
272
+ if (!sl.some((x) => x !== null)) return 1;
273
+ const st2 = new Set([...stack, id]);
274
+ return Math.max(1, sl.reduce((a, c) => a + (c === null ? 0.5 : width(c, st2)), 0));
275
+ };
276
+ const place = (id, x0, d) => {
277
+ if (seen.has(id)) return; seen.add(id);
278
+ maxD = Math.max(maxD, d);
279
+ if (chain) { pos.set(id, { x: d, y: 0 }); maxX = Math.max(maxX, d); const c = slotsOf(id).find((x) => x !== null); if (c) { edges.push([id, c]); place(c, 0, d + 1); } return; }
280
+ const w = width(id);
281
+ pos.set(id, { x: x0 + w / 2, y: d }); maxX = Math.max(maxX, x0 + w);
282
+ let x = x0;
283
+ for (const c of slotsOf(id)) { if (c === null) { x += 0.5; continue; } edges.push([id, c]); place(c, x, d + 1); x += width(c); }
284
+ };
285
+ if (isRef(s.v) && heap[s.v.ref]?.cls) place(s.v.ref, 0, 0);
286
+ const W = chain ? (maxX + 1) * 170 : Math.max(1, maxX) * 120, H = chain ? 160 : (maxD + 1) * 120;
287
+ const px = (p) => (chain ? 85 + p.x * 170 : p.x * 120), py = (p) => (chain ? 80 : 60 + p.y * 120);
288
+ const reads = new Set(st.reads.map((r) => r[0])), writes = new Set(st.writes.map((w) => w[0]));
289
+ const prevHeap = prevSt?.heap || {};
290
+ const top = st.frames.at(-1).vars;
291
+ const tags = new Map();
292
+ for (const [name, v] of Object.entries(top)) if (isRef(v) && pos.has(v.ref)) tags.set(v.ref, [...(tags.get(v.ref) || []), name]);
293
+ const edgeSVG = edges.map(([a, b]) => { const p = pos.get(a), q = pos.get(b); return `<line class="tr-edge" x1="${px(p)}" y1="${py(p)}" x2="${px(q)}" y2="${py(q)}"${chain ? ' marker-end="url(#tr-arrow)"' : ""}/>`; }).join("");
294
+ const nodeSVG = [...pos].map(([id, p]) => {
295
+ const n = heap[id];
296
+ const cls = [reads.has(Number(id)) && "cmp", writes.has(Number(id)) && "wr", !prevHeap[id] && prevSt && "new"].filter(Boolean).join(" ");
297
+ const tag = tags.get(id) ? `<text class="tr-node-tag" x="${px(p)}" y="${py(p) - 50}">${esc(tags.get(id).join(" · "))}</text>` : "";
298
+ return `<g class="tr-node ${cls}"><circle cx="${px(p)}" cy="${py(p)}" r="38"/><text x="${px(p)}" y="${py(p)}">${esc(cellText(n.label, heap))}</text>${tag}</g>`;
299
+ }).join("");
300
+ const vw = W + (chain ? 0 : 120), vh = H + 20;
301
+ const svg = pos.size ? `<svg class="tr-svg" viewBox="${chain ? 0 : -60} -10 ${vw} ${vh}" style="max-width:${vw}px;max-height:${vh}px" preserveAspectRatio="xMidYMid meet"><defs><marker id="tr-arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="currentColor"/></marker></defs>${edgeSVG.replace(/x2="([\d.]+)"/g, (m, x) => (chain ? `x2="${Number(x) - 40}"` : m))}${nodeSVG}</svg>` : `<span class="tr-empty">vazia (None)</span>`;
302
+ return `<div class="tr-struct tr-k-tree"><span class="tr-lab t f-label">${esc(s.name)}</span>${svg}</div>`;
303
+ }
304
+
305
+ function graphBlock(st, s) {
306
+ const heap = st.heap, n = heap[s.v.ref];
307
+ const keys = n.entries.map(([k]) => k);
308
+ const nodes = [...keys];
309
+ const edges = [];
310
+ for (const [k, v] of n.entries) for (const nb of neighborsOf(heap[v.ref], heap)) { if (!nodes.includes(nb.to)) nodes.push(nb.to); edges.push({ a: k, b: nb.to, w: nb.w }); }
311
+ // camadas a partir do primeiro nó (como a busca em largura enxerga); dentro da camada, perto dos vizinhos de cima
312
+ const adj = new Map(nodes.map((k) => [k, new Set()]));
313
+ for (const e of edges) { adj.get(e.a).add(e.b); adj.get(e.b).add(e.a); }
314
+ const layer = new Map(), layers = [];
315
+ for (const root of nodes) {
316
+ if (layer.has(root)) continue;
317
+ let frontier = [root]; layer.set(root, layers.length); layers.push([root]);
318
+ for (;;) {
319
+ const nextL = [];
320
+ for (const u of frontier) for (const w of adj.get(u)) if (!layer.has(w)) { layer.set(w, layers.length); nextL.push(w); }
321
+ if (!nextL.length) break;
322
+ const prevIdx = new Map(layers.at(-1).map((k, i) => [k, i]));
323
+ const bary = (k) => { const ps = [...adj.get(k)].filter((w) => prevIdx.has(w)).map((w) => prevIdx.get(w)); return ps.length ? ps.reduce((a, b) => a + b, 0) / ps.length : 0; };
324
+ nextL.sort((a, b) => bary(a) - bary(b) || nodes.indexOf(a) - nodes.indexOf(b));
325
+ layers.push(nextL); frontier = nextL;
326
+ }
327
+ }
328
+ const GW = Math.max(520, Math.max(...layers.map((l) => l.length)) * 190), GH = layers.length * 170 + 40;
329
+ const pos = new Map();
330
+ layers.forEach((l, d) => l.forEach((k, i) => pos.set(k, { x: (GW * (i + 1)) / (l.length + 1), y: 105 + d * 170, d, i })));
331
+ const top = st.frames.at(-1).vars;
332
+ const vals = (v) => (isRef(v) ? (heap[v.ref]?.items || heap[v.ref]?.entries?.map(([k]) => k) || []).flatMap((x) => (isRef(x) ? heap[x.ref]?.items || [] : [x])) : []);
333
+ const ok = new Set(), queued = new Set(), tags = new Map();
334
+ for (const [name, v] of Object.entries(top)) {
335
+ if (isRef(v) && v.ref === s.v.ref) continue;
336
+ if (isRef(v) && heap[v.ref]?.t === "set") vals(v).forEach((x) => ok.add(x));
337
+ else if (isRef(v) && ["deque", "list"].includes(heap[v.ref]?.t)) vals(v).forEach((x) => queued.add(x));
338
+ else if (!isRef(v) && pos.has(v)) tags.set(v, [...(tags.get(v) || []), name]);
339
+ }
340
+ const hotPairs = new Set();
341
+ const params = new Set(st.frames.at(-1).params || []);
342
+ const scal = Object.entries(top).filter(([k, v]) => !params.has(k) && !isRef(v) && pos.has(v)).map(([, v]) => v);
343
+ for (const a of scal) for (const b of scal) if (a !== b) hotPairs.add(`${a}\u0000${b}`);
344
+ const drawn = new Set();
345
+ const edgeSVG = edges.map((e) => {
346
+ const back = edges.some((f) => f.a === e.b && f.b === e.a);
347
+ const id = back ? [e.a, e.b].map(String).sort().join("\u0000") : `${e.a}\u0000${e.b}`;
348
+ if (drawn.has(id)) return ""; drawn.add(id);
349
+ const p = pos.get(e.a), q = pos.get(e.b);
350
+ const d = Math.hypot(q.x - p.x, q.y - p.y) || 1, ux = (q.x - p.x) / d, uy = (q.y - p.y) / d;
351
+ const hot = hotPairs.has(`${e.a}\u0000${e.b}`) ? " hot" : "";
352
+ const arrow = back ? "" : ' marker-end="url(#tr-garrow)"';
353
+ const hasW = e.w !== undefined && e.w !== null && !isRef(e.w);
354
+ // mesma camada e com gente no meio: arco por cima para não atravessar os nós
355
+ if (p.d === q.d && Math.abs(p.i - q.i) > 1) {
356
+ const mx = (p.x + q.x) / 2, my = p.y - 70 - 25 * Math.abs(p.i - q.i);
357
+ const wl = hasW ? `<text class="tr-weight" x="${mx}" y="${(p.y + my) / 2 - 8}">${esc(cellText(e.w, heap))}</text>` : "";
358
+ return `<path class="tr-edge${hot}" fill="none" d="M${p.x} ${p.y - 42}Q${mx} ${my} ${q.x} ${q.y - 42}"${arrow}/>${wl}`;
359
+ }
360
+ const wl = hasW ? `<text class="tr-weight" x="${(p.x + q.x) / 2 - uy * 26}" y="${(p.y + q.y) / 2 + ux * 26}">${esc(cellText(e.w, heap))}</text>` : "";
361
+ return `<line class="tr-edge${hot}" x1="${p.x + ux * 44}" y1="${p.y + uy * 44}" x2="${q.x - ux * 46}" y2="${q.y - uy * 46}"${arrow}/>${wl}`;
362
+ }).join("");
363
+ const reads = st.reads.filter((r) => r[0] === s.v.ref).map((r) => r[1]);
364
+ const nodeSVG = nodes.map((k) => {
365
+ const p = pos.get(k);
366
+ const cls = [ok.has(k) && "ok", queued.has(k) && "queued", tags.has(k) && "cur", reads.includes(k) && "cmp"].filter(Boolean).join(" ");
367
+ const tag = tags.get(k) ? `<text class="tr-node-tag" x="${p.x}" y="${p.y - 58}">${esc(tags.get(k).join(" · "))}</text>` : "";
368
+ return `<g class="tr-node ${cls}"><circle cx="${p.x}" cy="${p.y}" r="42"/><text x="${p.x}" y="${p.y}">${esc(cellText(k, heap))}</text>${tag}</g>`;
369
+ }).join("");
370
+ return `<div class="tr-struct tr-k-graph"><span class="tr-lab t f-label">${esc(s.name)}</span><svg class="tr-svg" viewBox="0 0 ${GW} ${GH}" style="max-width:${GW}px;max-height:${GH}px" preserveAspectRatio="xMidYMid meet"><defs><marker id="tr-garrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="currentColor"/></marker></defs>${edgeSVG}${nodeSVG}</svg></div>`;
371
+ }
372
+
373
+ function stageHTML(st, prevSt, plan, T) {
374
+ const structs = structuresFor(st, plan, T);
375
+ const parts = [];
376
+ const used = new Set();
377
+ for (const s of structs) {
378
+ if (used.has(s)) continue;
379
+ const kind = kindOf(s.v, st.heap, s.e.as, T.indexed);
380
+ if (kind === "cells") {
381
+ // quem se alinha embaixo desta linha (padrão sob o texto)
382
+ const unders = structs.filter((u) => u !== s && u.e.under && u.e.under === (s.e.var || s.name) && kindOf(u.v, st.heap, u.e.as, T.indexed) === "cells");
383
+ unders.forEach((u) => used.add(u));
384
+ used.add(s);
385
+ parts.push(cellsBlock(st, [s, ...unders], prevSt, T));
386
+ } else if (kind === "bars") parts.push(barsBlock(st, s, prevSt, T));
387
+ else if (kind === "grid") parts.push(gridBlock(st, s));
388
+ else if (kind === "table") parts.push(tableBlock(st, s));
389
+ else if (kind === "tree") parts.push(treeBlock(st, s, prevSt));
390
+ else if (kind === "graph") parts.push(graphBlock(st, s));
391
+ used.add(s);
392
+ }
393
+ return parts.join("");
394
+ }
395
+
396
+ function varsHTML(st, prevSt) {
397
+ const frames = st.frames.map((f, k) => ({ ...f, k })).filter((f) => f.k > 0 || Object.keys(f.vars).length).reverse().slice(0, 3);
398
+ const prevText = (k, name) => { const f = prevSt?.frames[k]; if (!f || !(name in f.vars) || prevSt.frames.length <= k || prevSt.frames[k].name !== st.frames[k].name) return undefined; return short(f.vars[name], prevSt.heap); };
399
+ return `<div class="tr-vars">${frames.map((f, i) => `<div class="tr-frame${i === 0 ? " now" : ""}${Object.keys(f.vars).length > 6 ? " many" : ""}"><div class="tr-fname t f-label">${esc(f.name === "<programa>" ? "programa" : f.name)}</div><dl>${Object.entries(f.vars).map(([n, v]) => {
400
+ const txt = short(v, st.heap), before = prevText(f.k, n);
401
+ return `<dt>${esc(n)}</dt><dd${prevSt && before !== txt ? ' class="chg"' : ""}>${esc(txt)}</dd>`;
402
+ }).join("") || "<dd class=\"tr-none\">sem variáveis</dd>"}</dl></div>`).join("")}</div>`;
403
+ }
404
+
405
+ function codeLines(res) {
406
+ return res.lines.map((l, i) => {
407
+ const c = res.comments[i + 1];
408
+ if (!c) return l;
409
+ const at = l.lastIndexOf(c), hash = l.lastIndexOf("#", at);
410
+ return hash >= 0 ? l.slice(0, hash).trimEnd() : l;
411
+ });
412
+ }
413
+ function codeHTML(lines, line) {
414
+ const first = lines.findIndex((l) => l.trim()), lastNon = lines.length - [...lines].reverse().findIndex((l) => l.trim());
415
+ let a = Math.max(0, first), b = lastNon;
416
+ const WIN = 18;
417
+ if (b - a > WIN) { const c = line > 0 ? line - 1 : a; a = Math.max(a, Math.min(c - 6, b - WIN)); b = a + WIN; }
418
+ return `<ol class="algo-code dyn-mono tr-code" style="counter-reset:ln ${a}">${lines.slice(a, b).map((l, i) => `<li${a + i + 1 === line ? ' class="now"' : ""}>${esc(l) || " "}</li>`).join("")}</ol>`;
419
+ }
420
+
421
+ function pickSteps(steps, comments, max, warn) {
422
+ if (steps.length <= max) return steps;
423
+ const important = (st, i) => i === 0 || i === steps.length - 1 || st.writes.length || st.matches.length || comments[st.line] || st.kind === "return" || st.kind === "done";
424
+ let keep = steps.filter(important);
425
+ if (keep.length > max) { warn(`${steps.length} passos; mostrando ${max} (use uma entrada menor ou maxSteps)`); keep = keep.slice(0, max - 1).concat(steps.slice(-1)); }
426
+ return keep;
427
+ }
428
+
429
+ export function traceHTML(s, plan, ctx, head, panel) {
430
+ const warn = (m) => ctx?.warnings?.push(`algoritmo: ${m}`);
431
+ const watch = [];
432
+ for (const e of plan.view || []) { (e.pointers || []).forEach((p) => watch.push(String(p))); if (e.offset) watch.push(String(e.offset)); }
433
+ let res;
434
+ try { res = runProgram(plan.program, { call: plan.call, watch }); }
435
+ catch (e) {
436
+ if (!(e instanceof PyError)) throw e;
437
+ warn(e.message);
438
+ return `<div class="L-algo">${head(s)}<div class="dyn-error f-body">${esc(e.message)}</div></div>`;
439
+ }
440
+ const all = res.steps;
441
+ const T = { indexed: new Set(), indexedStrings: new Set(), learned: learnPointers(all) };
442
+ for (const st of all) for (const r of st.reads) { if (r[0] === "s") T.indexedStrings.add(r[1]); else if (typeof r[1] === "number") T.indexed.add(r[0]); }
443
+ const steps = pickSteps(all, res.comments, Number(s.maxSteps) || 120, warn);
444
+ const lines = codeLines(res);
445
+ const printed = res.output.length > 0;
446
+ const n = steps.length;
447
+ const body = steps.map((st, k) => panel(k, `<div class="algo-row tr-row"><div class="algo-stage tr-stage"><div class="tr-structs">${stageHTML(st, steps[k - 1], plan, T)}</div><div class="algo-caption t f-body">${esc(st.text || "")}</div></div>
448
+ <aside class="algo-side tr-side"><div class="algo-name t f-label">${esc(plan.name || "Programa")}<span class="tr-count">${k}/${n - 1}</span></div>${codeHTML(lines, st.line)}${varsHTML(st, steps[k - 1])}${printed ? `<div class="tr-out dyn-mono"><span class="t f-label">saída</span>${st.out.map((l) => `<div>${esc(l)}</div>`).join("") || "<div></div>"}</div>` : ""}</aside></div>`)).join("");
449
+ return `<div class="L-algo L-trace dyn" data-lesson="algo" data-lesson-count="${n}" data-autoplay-ms="${Number(s.speed) || 900}">${head(s)}<button type="button" class="algo-play" data-autoplay aria-pressed="false" title="Tocar sozinho (clique de novo para pausar)"><span class="algo-play-ic" aria-hidden="true"></span><span>Tocar</span></button><div class="dyn-frames">${body}</div></div>`;
450
+ }
package/src/uid.js ADDED
@@ -0,0 +1,19 @@
1
+ // Identificador persistente de slide (uid): não depende do layout nem da posição na lista. Serve para casar o mesmo
2
+ // slide entre versões (junção do que a pessoa e a IA mexeram ao mesmo tempo), para marcas de revisão, cobertura da
3
+ // transformação e histórico. Decks antigos ganham uid na primeira gravação (migração sem perder nada).
4
+ export function newUid() {
5
+ const r = globalThis.crypto?.getRandomValues ? globalThis.crypto.getRandomValues(new Uint32Array(2)) : [Math.random() * 2 ** 32, Math.random() * 2 ** 32];
6
+ return `s${(r[0] >>> 0).toString(36)}${(r[1] >>> 0).toString(36)}`.slice(0, 12);
7
+ }
8
+ // garante um uid em cada slide (e troca uid repetido, que aparece quando alguém duplica um slide copiando o YAML)
9
+ export function ensureUids(spec) {
10
+ if (!spec || !Array.isArray(spec.slides)) return 0;
11
+ const seen = new Set();
12
+ let added = 0;
13
+ for (const s of spec.slides) {
14
+ if (!s || typeof s !== "object") continue;
15
+ if (typeof s.uid !== "string" || !s.uid || seen.has(s.uid)) { s.uid = newUid(); added++; }
16
+ seen.add(s.uid);
17
+ }
18
+ return added;
19
+ }
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { esc } from './markup.js';
4
+ import {iconSVG} from './figures/icons.js';
5
+
6
+ export function youtubeId(value) {
7
+ try {
8
+ const u = new URL(value), host = u.hostname.toLowerCase();
9
+ if (host === 'youtu.be') return /^[\w-]{11}$/.test(u.pathname.slice(1)) ? u.pathname.slice(1) : null;
10
+ if (!['youtube.com','www.youtube.com','m.youtube.com','youtube-nocookie.com','www.youtube-nocookie.com'].includes(host)) return null;
11
+ const id = u.searchParams.get('v') || u.pathname.match(/^\/(?:embed|shorts|live)\/([\w-]{11})(?:\/|$)/)?.[1];
12
+ return /^[\w-]{11}$/.test(id || '') ? id : null;
13
+ } catch { return null; }
14
+ }
15
+ export function videoPlayer(e, ctx = {}) {
16
+ const id = youtubeId(e.video), label = esc(e.label || 'Vídeo');
17
+ if (id) return `<div class="video-player"><iframe title="${label}" src="https://www.youtube-nocookie.com/embed/${id}?rel=0" referrerpolicy="strict-origin-when-cross-origin" allow="encrypted-media; picture-in-picture; fullscreen" allowfullscreen></iframe><small>YouTube · reprodução online</small></div>`;
18
+ let url = String(e.video || '');
19
+ if (!/^https?:|^data:/i.test(url) && /\.(mp4|webm|ogg)$/i.test(url)) {
20
+ const file = path.resolve(ctx.baseDir || process.cwd(), url);
21
+ if (fs.existsSync(file)) {
22
+ const mime = /\.mp4$/i.test(file) ? 'video/mp4' : /\.webm$/i.test(file) ? 'video/webm' : 'video/ogg';
23
+ url = `data:${mime};base64,${fs.readFileSync(file).toString('base64')}`;
24
+ } else { ctx.warnings?.push(`Vídeo não encontrado: ${url}`); return `<div class="fig-pending">${label}: arquivo não encontrado</div>`; }
25
+ }
26
+ if (/\.(mp4|webm|ogg)(?:\?|$)|^data:video\//i.test(url)) {
27
+ let poster=e.poster;
28
+ if(poster&&!/^(https?:|data:)/i.test(poster)){
29
+ const file=path.resolve(ctx.baseDir||process.cwd(),poster);
30
+ if(fs.existsSync(file)){const ext=path.extname(file).slice(1).toLowerCase();poster=`data:image/${ext==='jpg'?'jpeg':ext};base64,${fs.readFileSync(file).toString('base64')}`;}
31
+ else ctx.warnings?.push(`Poster não encontrado: ${poster}`);
32
+ }
33
+ // loop silencioso (fundo vivo, detalhe animado): sem controles, repete sozinho
34
+ const posterAttr=poster?` poster="${esc(poster)}"`:'';
35
+ if(e.start==='manual')return `<div class="video-player video-opening${e.composite==='screen'?' video-composite':''}" data-video-opening${e.controls==='stage'?' data-video-stage':''}${e.finish==='next'?' data-video-next':''}${e.finish==='reveal'?' data-video-reveal':''} data-video-fade="${Number.isFinite(e.fadeOut)?Math.max(0,e.fadeOut):0.4}"${Number.isFinite(e.handoffAt)&&e.handoffAt>=0?` data-video-handoff="${e.handoffAt}"`:''}><video title="${label}" src="${esc(url)}"${posterAttr} muted playsinline preload="auto" disablepictureinpicture></video><div class="video-opening-controls"><button data-video-start>${iconSVG('play',20)} Iniciar abertura</button><button data-video-advance>${iconSVG('arrow-right',20)} Avançar</button><output data-video-status aria-live="polite"></output></div></div>`;
36
+ if (e.loop) return `<div class="video-player video-loop"><video title="${label}" src="${esc(url)}"${posterAttr} autoplay muted loop playsinline preload="auto" disablepictureinpicture></video></div>`;
37
+ return `<div class="video-player"><video title="${label}" controls preload="metadata" playsinline src="${esc(url)}"></video></div>`;
38
+ }
39
+ return null;
40
+ }
@@ -0,0 +1,99 @@
1
+ // Identificadores determinísticos por classe; ajustes visuais valem no Studio e no HTML exportado.
2
+ const SVG_SHAPES = ['triangle','diamond','hexagon','star','arrow','chevron','bubble'].map(k=>`shape-${k}`);
3
+ // Propriedades do inspetor do Studio: só valores de uma lista ou números (nada de CSS vindo do deck direto)
4
+ const ALIGN = ['left','center','right','justify'], ANIM = ['fade','pop','left','right','down','zoom','none'];
5
+ const SHADOW = { suave: 'drop-shadow(0 8px 16px rgb(0 0 0 / .18))', forte: 'drop-shadow(0 18px 36px rgb(0 0 0 / .35))' };
6
+ const HEX = /^#[0-9a-f]{6}$/i;
7
+ // imagem trocada no Studio: caminho relativo da pasta do deck (ou imagem colada em data:), enquadramento e foco
8
+ const FOCUS = ['center','top','bottom','left','right','top left','top right','bottom left','bottom right'];
9
+ const IMG_PATH = /^(?![a-z]+:)(?!\/)(?!.*\.\.)[\w .\/-]+\.(png|jpe?g|webp|gif|svg)$/i, IMG_DATA = /^data:image\/(png|jpeg|webp|gif);base64,[a-z0-9+/=]+$/i;
10
+ // Chave de um objeto: o grupo (classes) + uma impressão do CONTEÚDO dele (o texto; na figura, a imagem ou o desenho),
11
+ // e não a posição: inserir, reordenar ou apagar outro objeto não passa o ajuste para quem não era. Objetos iguais
12
+ // no mesmo slide ganham ~2, ~3… A chave antiga (grupo-N, pela ordem) continua valendo para decks de antes; o Studio
13
+ // converte na primeira vez que o slide abre (data-vkey-old).
14
+ const hash = (s) => { let h = 0x811c9dc5; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); } return (h >>> 0).toString(36); };
15
+ function innerOf(html, from, name) {
16
+ const re = new RegExp(`<(/?)${name}\\b[^>]*?(/?)>`, 'gi');
17
+ re.lastIndex = from;
18
+ let depth = 1, m;
19
+ while ((m = re.exec(html))) {
20
+ if (m[1]) { if (--depth === 0) return html.slice(from, m.index); }
21
+ else if (!m[2]) depth++;
22
+ }
23
+ return html.slice(from, from + 2000);
24
+ }
25
+ function fingerprint(inner) {
26
+ const src = inner.match(/<img[^>]*\bsrc="([^"]{0,4000})/)?.[1];
27
+ if (src) return hash(`img:${src.length}:${src.slice(0, 600)}:${src.slice(-200)}`);
28
+ const text = inner.replace(/<svg[\s\S]*?<\/svg>/gi, (s) => ` svg${hash(s.replace(/\s+id="[^"]*"|url\(#[^)]*\)/g, ''))} `).replace(/<[^>]+>/g, ' ').replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ').trim();
29
+ return hash(text.slice(0, 400));
30
+ }
31
+ export function applyVisualEdits(html, edits = {}, { src = (p) => p } = {}) {
32
+ const counts = new Map(), seen = new Map(), imgs = [];
33
+ const out = html.replace(/<(div|span|p|h[1-6])\b([^>]*\bclass="([^"]+)"[^>]*)>/g, (tag,name,attrs,cls,offset) => {
34
+ const classes = cls.split(/\s+/);
35
+ // tbx / tbx-table / drw: texto, tabela e desenho que vieram de um PowerPoint (também se ajustam no Studio)
36
+ if (!classes.some(c=>['t','fig','shape','scene-sculpture','tbx','tbx-table','drw','tbl'].includes(c))) return tag;
37
+ const group = classes.filter(c=>c!=='e' && c!=='active').join('-').replace(/[^a-zA-Z0-9_-]/g,'');
38
+ const n = counts.get(group) || 0; counts.set(group,n+1);
39
+ const legacy = `${group}-${n}`;
40
+ const base = `${group}~${fingerprint(innerOf(html, offset + tag.length, name))}`;
41
+ const dup = (seen.get(base) || 0) + 1; seen.set(base, dup);
42
+ const key = dup > 1 ? `${base}~${dup}` : base;
43
+ const e = edits[key] || edits[legacy] || {};
44
+ const num = (v,f=0)=>Number.isFinite(Number(v))?Number(v):f;
45
+ let css = '';
46
+ if (e.hidden) css += 'display:none!important;';
47
+ if (e.dx || e.dy) css += `translate:${num(e.dx)}px ${num(e.dy)}px;`;
48
+ if (e.w != null) css += `width:${Math.max(20,num(e.w,200))}px!important;max-width:none;`;
49
+ if (e.h != null) css += `height:${Math.max(20,num(e.h,100))}px!important;`;
50
+ if (e.size != null) css += `font-size:${Math.max(10,num(e.size,36))}px!important;`;
51
+ if (HEX.test(e.color)) css += `color:${e.color}!important;`;
52
+ if ([400, 500, 600, 700, 800].includes(Number(e.weight))) css += `font-weight:${Number(e.weight)}!important;`;
53
+ if (e.italic === true) css += 'font-style:italic!important;';
54
+ if (ALIGN.includes(e.align)) css += `text-align:${e.align}!important;`;
55
+ if (e.lineHeight != null && Number.isFinite(Number(e.lineHeight))) css += `line-height:${Math.min(3, Math.max(0.7, Number(e.lineHeight)))}!important;`;
56
+ if (e.letterSpacing != null && Number.isFinite(Number(e.letterSpacing))) css += `letter-spacing:${Math.min(0.5, Math.max(-0.1, Number(e.letterSpacing)))}em!important;`;
57
+ if (e.uppercase === true) css += 'text-transform:uppercase!important;';
58
+ if (e.rotate) css += `rotate:${Math.round(num(e.rotate)) % 360}deg;`;
59
+ if (e.opacity != null && Number.isFinite(Number(e.opacity))) css += `opacity:${Math.min(1, Math.max(0, Number(e.opacity)))};`;
60
+ if (SHADOW[e.shadow]) css += `filter:${SHADOW[e.shadow]};`;
61
+ if (e.radius != null && Number.isFinite(Number(e.radius))) css += `border-radius:${Math.max(0, Math.round(Number(e.radius)))}px!important;overflow:hidden;`;
62
+ // contorno: desenho (SVG) lê --shape-stroke/--shape-sw; forma reta, imagem ou caixa ganham borda
63
+ if (HEX.test(e.stroke)) {
64
+ const w = Math.max(0, Math.min(40, num(e.strokeWidth, 4)));
65
+ css += classes.some(c=>SVG_SHAPES.includes(c)) ? `--shape-stroke:${e.stroke};--shape-sw:${w}px;` : `border:${w}px solid ${e.stroke}!important;`;
66
+ }
67
+ // Preenchimento de forma: desenho (SVG) usa --shape-fill; retângulo, elipse e pílula pintam o fundo; linha, a cor
68
+ if (/^#[0-9a-f]{6}$/i.test(e.fill) && classes.includes('shape')) css += classes.some(c=>SVG_SHAPES.includes(c)) ? `--shape-fill:${e.fill};` : `background:${e.fill}!important;`;
69
+ if (e.z != null) css += `${/position:absolute/.test(attrs) ? '' : 'position:relative;'}z-index:${Math.round(num(e.z))};`;
70
+ if (css) attrs = /\bstyle="/.test(attrs) ? attrs.replace(/style="([^"]*)"/,(_,old)=>`style="${old};${css}"`) : `${attrs} style="${css}"`;
71
+ // aparece no clique N e animação de entrada: atributos que o runtime já entende (trocam os do elemento)
72
+ let extra = '';
73
+ if (Number.isInteger(Number(e.step)) && Number(e.step) > 0) { attrs = attrs.replace(/\sdata-step="[^"]*"/, ''); extra += ` data-step="${Number(e.step)}"`; }
74
+ if (ANIM.includes(e.anim)) { attrs = attrs.replace(/\sdata-anim="[^"]*"/, ''); extra += ` data-anim="${e.anim}"`; }
75
+ // travado pela pessoa (no Studio, o clique atravessa); decoração do original já vem travada do motor
76
+ if (e.locked === true && !/\sdata-locked/.test(attrs)) extra += ' data-locked="1"';
77
+ if (e.locked === false) attrs = attrs.replace(/\sdata-locked="[^"]*"/, '');
78
+ // imagem trocada, foco e encaixe: valem na <img> de dentro (segunda passada, abaixo)
79
+ const img = { src: typeof e.image === 'string' && (IMG_PATH.test(e.image) || IMG_DATA.test(e.image)) ? src(e.image) : null, focus: FOCUS.includes(e.focus) ? e.focus : null, fit: ['cover','contain'].includes(e.fit) ? e.fit : null };
80
+ if (img.src || img.focus || img.fit) { extra += ` data-vimg="${imgs.length}"`; imgs.push(img); }
81
+ return `<${name}${attrs} data-vkey="${key}"${edits[legacy] && !edits[key] ? ` data-vkey-old="${legacy}"` : ''}${e.size != null ? ' data-vsize' : ''}${extra}>`;
82
+ });
83
+ if (!imgs.length) return out;
84
+ let res = out;
85
+ imgs.forEach((img, n) => {
86
+ const mark = ` data-vimg="${n}"`, at = res.indexOf(mark);
87
+ if (at < 0) return;
88
+ res = res.slice(0, at) + res.slice(at + mark.length);
89
+ const i = res.indexOf('<img', at), end = i < 0 ? -1 : res.indexOf('>', i);
90
+ if (end < 0) return;
91
+ let tag = res.slice(i, end);
92
+ const esc = (v) => String(v).replace(/"/g, '&quot;');
93
+ if (img.src) tag = /\ssrc="/.test(tag) ? tag.replace(/\ssrc="[^"]*"/, ` src="${esc(img.src)}"`) : `${tag} src="${esc(img.src)}"`;
94
+ const css = `${img.fit ? `object-fit:${img.fit};` : ''}${img.focus ? `object-position:${img.focus};` : ''}`;
95
+ if (css) tag = /\sstyle="/.test(tag) ? tag.replace(/\sstyle="([^"]*)"/, (_, old) => ` style="${old};${css}"`) : `${tag} style="${css}"`;
96
+ res = res.slice(0, i) + tag + res.slice(end);
97
+ });
98
+ return res;
99
+ }