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,200 @@
1
+ // Simulador de Git para as dinâmicas ao vivo (duel, terminals): duas pessoas, um remoto (origin/main), um arquivo.
2
+ // Quem escreve o slide só diz o que cada um FAZ (edita, commita, dá push, pull, resolve); o simulador calcula o
3
+ // resto: diff, push rejeitado, fast-forward, merge automático, conflito (merge a três por linha) e o grafo.
4
+ //
5
+ // base: "texto do arquivo" people: [Ana, Beto]
6
+ // turns: [{ who: 1, edit: {2: "nova linha 2", 5: null}, commit: "mensagem" }, { who: 1, push: true },
7
+ // { who: 2, pull: true }, { who: 2, resolve: "ours" | "theirs" | "both" | {2: "linha"}, commit: "…" },
8
+ // { who: 1, log: true }, { who: 2, say: "texto livre" }]
9
+
10
+ // linhas novas de b em relação a a (diff por subsequência comum mais longa: inserir uma linha não marca as de baixo)
11
+ function added(a, b) {
12
+ const n = a.length, m = b.length, L = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
13
+ for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) L[i][j] = a[i] === b[j] ? L[i + 1][j + 1] + 1 : Math.max(L[i + 1][j], L[i][j + 1]);
14
+ const out = [];
15
+ let i = 0, j = 0;
16
+ while (j < m) {
17
+ if (i < n && a[i] === b[j]) { i++; j++; }
18
+ else if (i < n && L[i + 1][j] >= L[i][j + 1]) i++;
19
+ else { out.push(j + 1); j++; }
20
+ }
21
+ return out;
22
+ }
23
+ const short = (n) => ((n * 2654435761) >>> 0).toString(16).padStart(8, "0").slice(0, 7); // hash estável e curto
24
+ const lines = (t) => String(t ?? "").replace(/\r/g, "").replace(/\n$/, "").split("\n");
25
+
26
+ export function simulateGit(spec = {}) {
27
+ const people = [0, 1].map((i) => {
28
+ const p = (spec.people || [])[i];
29
+ return String((typeof p === "object" ? p?.name : p) || (i ? "Pessoa B" : "Pessoa A"));
30
+ });
31
+ const commits = [];
32
+ const mk = (msg, author, parents, file, lane) => {
33
+ const c = { id: short(commits.length + 7), n: commits.length, msg: String(msg), author, parents, file: [...file], lane };
34
+ commits.push(c);
35
+ return c;
36
+ };
37
+ const root = mk(spec.initial || "primeiro commit", -1, [], lines(spec.base), "main");
38
+ const remote = { head: root };
39
+ const P = people.map((name, i) => ({ i, name, file: [...root.file], head: root, changed: new Set(), conflict: null, ahead: 0 }));
40
+ const ancestors = (c) => { const s = new Set(); const st = [c]; while (st.length) { const x = st.pop(); if (s.has(x)) continue; s.add(x); x.parents.forEach((q) => st.push(q)); } return s; };
41
+ const isAnc = (a, b) => ancestors(b).has(a); // a é ancestral de b (ou igual)
42
+ const base3 = (a, b) => { const A = ancestors(a); let best = null; for (const x of ancestors(b)) if (A.has(x) && (!best || x.n > best.n)) best = x; return best || root; };
43
+
44
+ // merge a três por linha (as edições do slide trocam, apagam ou acrescentam linhas: o alinhamento é pelo número)
45
+ function merge3(base, ours, theirs) {
46
+ const n = Math.max(base.length, ours.length, theirs.length);
47
+ const out = [], conflicts = [];
48
+ for (let i = 0; i < n; i++) {
49
+ const b = base[i], o = ours[i], t = theirs[i];
50
+ if (o === t) out.push({ v: o, i });
51
+ else if (o === b) out.push({ v: t, i, from: "theirs" });
52
+ else if (t === b) out.push({ v: o, i, from: "ours" });
53
+ else { out.push({ conflict: true, o, t, i }); conflicts.push(i + 1); }
54
+ }
55
+ return { out, conflicts };
56
+ }
57
+ const flat = (m) => m.out.flatMap((x) => (x.conflict ? [] : x.v === undefined || x.v === null ? [] : [x.v]));
58
+
59
+ const frames = [];
60
+ let touched = [[], []]; // linhas que mudaram neste turno, por pessoa (edição, pull, resolução)
61
+ const shown = (p) => (p.conflict ? p.conflict.view : p.file);
62
+ const state = (extra) => ({
63
+ people: P.map((p, i) => ({ name: p.name, file: p.conflict ? p.conflict.view : [...p.file], changed: [...(touched[i] || [])], conflict: p.conflict ? p.conflict.lines : [],
64
+ head: p.head.id, status: p.conflict ? "conflito" : p.changed.size ? "mudou sem commit" : aheadOf(p) ? `${aheadOf(p)} ${aheadOf(p) > 1 ? "commits" : "commit"} à frente` : behindOf(p) ? "desatualizado" : "em dia" })),
65
+ remote: remote.head.id,
66
+ heads: P.map((p) => p.head.id),
67
+ commits: commits.map((c) => ({ id: c.id, msg: c.msg, author: c.author, parents: c.parents.map((q) => q.id), lane: c.lane })),
68
+ ...extra,
69
+ });
70
+ const aheadOf = (p) => [...ancestors(p.head)].filter((c) => !ancestors(remote.head).has(c)).length;
71
+ const behindOf = (p) => [...ancestors(remote.head)].filter((c) => !ancestors(p.head).has(c)).length;
72
+ frames.push(state({ who: -1, cmd: "", out: "", text: `Os dois clonaram o repositório: ${spec.file || "arquivo"} igual para todo mundo.`, event: "start" }));
73
+
74
+ for (const [k, t] of (spec.turns || []).entries()) {
75
+ const who = Math.min(1, Math.max(0, (Number(t.who) || 1) - 1)), p = P[who];
76
+ const before = P.map((q) => [...shown(q)]);
77
+ let cmd = "", out = "", text = "", event = ""; // out = o que o terminal mostra; text = a explicação para a plateia
78
+ const edits = (map) => {
79
+ for (const [key, val] of Object.entries(map || {})) {
80
+ const i = Number(key) - 1;
81
+ if (!Number.isInteger(i) || i < 0) throw new Error(`turno ${k + 1}: linha "${key}" inválida (use o número da linha)`);
82
+ while (p.file.length <= i) p.file.push("");
83
+ if (val === null) p.file[i] = null; else p.file[i] = String(val);
84
+ p.changed.add(i + 1);
85
+ }
86
+ const kept = [];
87
+ p.file.forEach((l, i) => { if (l !== null) kept.push(l); });
88
+ if (kept.length !== p.file.length) { p.changed = new Set([...p.changed].filter((n) => n <= kept.length)); p.file = kept; }
89
+ };
90
+ if (t.edit) {
91
+ if (p.conflict) throw new Error(`turno ${k + 1}: ${p.name} está com conflito; use resolve: antes de editar`);
92
+ edits(t.edit);
93
+ const ls = [...p.changed].sort((a, b) => a - b);
94
+ text = `${p.name} mudou a ${ls.length > 1 ? "s linhas" : "linha"} ${ls.join(", ")}.`; event = "edit";
95
+ }
96
+ if (t.resolve != null) {
97
+ if (!p.conflict) throw new Error(`turno ${k + 1}: ${p.name} não tem conflito para resolver`);
98
+ const m = p.conflict.merge, r = t.resolve;
99
+ const merged = m.out.flatMap((x) => {
100
+ if (!x.conflict) return x.v == null ? [] : [x.v];
101
+ if (r === "ours") return x.o == null ? [] : [x.o];
102
+ if (r === "theirs") return x.t == null ? [] : [x.t];
103
+ if (r === "both") return [x.o, x.t].filter((v) => v != null);
104
+ const v = typeof r === "object" ? r[x.i + 1] : undefined;
105
+ if (v === undefined) throw new Error(`turno ${k + 1}: faltou resolver a linha ${x.i + 1} (resolve: {${x.i + 1}: "…"})`);
106
+ return v === null ? [] : [String(v)];
107
+ });
108
+ p.file = merged; p.changed = new Set(p.conflict.lines);
109
+ const theirs = p.conflict.theirs;
110
+ p.conflict = null; p.pendingMerge = theirs;
111
+ cmd = `git add ${spec.file || "arquivo"}`; text = `${p.name} resolveu o conflito: tirou os marcadores e ficou com a versão final.`; event = "resolve";
112
+ }
113
+ if (t.commit) {
114
+ if (p.conflict) throw new Error(`turno ${k + 1}: ${p.name} ainda está com conflito`);
115
+ const parents = p.pendingMerge ? [p.head, p.pendingMerge] : [p.head];
116
+ const c = mk(t.commit, who, parents, p.file, who === 0 ? "a" : "b");
117
+ p.head = c; p.pendingMerge = null; p.changed = new Set();
118
+ cmd = cmd ? `${cmd} && git commit -m "${t.commit}"` : `git commit -am "${t.commit}"`;
119
+ out = `[main ${c.id}] ${t.commit}\n 1 file changed`; event = parents.length > 1 ? "merge-commit" : "commit";
120
+ if (!text) text = parents.length > 1 ? `${p.name} fechou o merge com um commit que tem dois pais.` : `${p.name} guardou a mudança num commit, só na máquina dele(a).`;
121
+ }
122
+ if (t.push) {
123
+ cmd = "git push";
124
+ if (p.conflict || p.changed.size) { out = "Everything up-to-date"; text = "Nada foi enviado: a mudança ainda não virou commit."; event = "noop"; }
125
+ else if (isAnc(remote.head, p.head) && remote.head !== p.head) { out = `To origin\n ${remote.head.id}..${p.head.id} main -> main`; remote.head = p.head; text = `O remoto agora tem o que ${p.name} fez.`; event = "push"; }
126
+ else if (remote.head === p.head) { out = "Everything up-to-date"; event = "noop"; }
127
+ else { out = "! [rejected] main -> main (fetch first)\nerror: failed to push some refs to 'origin'"; text = `Recusado: o remoto tem commits que ${p.name} não tem. Primeiro pull.`; event = "rejected"; }
128
+ }
129
+ if (t.pull) {
130
+ cmd = "git pull";
131
+ if (p.changed.size) throw new Error(`turno ${k + 1}: ${p.name} tem mudanças sem commit; faça commit antes do pull`);
132
+ if (isAnc(remote.head, p.head)) { out = "Already up to date."; event = "noop"; }
133
+ else if (isAnc(p.head, remote.head)) { out = `Updating ${p.head.id}..${remote.head.id}\nFast-forward`; text = `${p.name} só estava atrás: o Git avançou o ponteiro, sem merge.`; p.head = remote.head; p.file = [...remote.head.file]; event = "ff"; }
134
+ else {
135
+ const b = base3(p.head, remote.head), m = merge3(b.file, p.head.file, remote.head.file);
136
+ if (!m.conflicts.length) {
137
+ p.file = flat(m);
138
+ const c = mk(`Merge de origin/main`, who, [p.head, remote.head], p.file, who === 0 ? "a" : "b");
139
+ p.head = c; event = "merge"; out = `Auto-merging ${spec.file || "arquivo"}\nMerge made by the 'ort' strategy.`; text = "As mudanças não se tocam: o Git juntou sozinho, num commit de merge.";
140
+ } else {
141
+ const view = [], clines = [];
142
+ m.out.forEach((x) => {
143
+ if (!x.conflict) { if (x.v != null) view.push(x.v); return; }
144
+ clines.push(view.length + 1); view.push(`<<<<<<< ${p.name} (seu)`);
145
+ if (x.o != null) { clines.push(view.length + 1); view.push(x.o); }
146
+ clines.push(view.length + 1); view.push("=======");
147
+ if (x.t != null) { clines.push(view.length + 1); view.push(x.t); }
148
+ clines.push(view.length + 1); view.push(">>>>>>> origin/main");
149
+ });
150
+ p.conflict = { merge: m, view, lines: clines, theirs: remote.head };
151
+ out = `Auto-merging ${spec.file || "arquivo"}\nCONFLICT (content): Merge conflict in ${spec.file || "arquivo"}\nAutomatic merge failed; fix conflicts and then commit the result.`;
152
+ text = `Os dois mudaram a linha ${m.conflicts.join(", ")}: o Git não escolhe por você.`; event = "conflict";
153
+ }
154
+ }
155
+ }
156
+ if (t.log) { cmd = "git log --graph --oneline --all"; out = logGraph(commits, P, remote); event = event || "log"; }
157
+ if (t.say) text = String(t.say);
158
+ touched = P.map((q, i) => added(before[i], shown(q)));
159
+ frames.push(state({ who, cmd, out, text, event, note: t.note ? String(t.note) : "" }));
160
+ }
161
+ return { people, frames, file: spec.file || "arquivo" };
162
+ }
163
+
164
+ // saída de "git log --graph --oneline --all" do estado atual (do mais novo para o mais velho)
165
+ export function logGraph(commits, P, remote) {
166
+ const tags = (c) => {
167
+ const t = [];
168
+ P.forEach((p) => { if (p.head === c) t.push(`${p.name}/HEAD`); });
169
+ if (remote.head === c) t.push("origin/main");
170
+ return t.length ? ` (${t.join(", ")})` : "";
171
+ };
172
+ return [...commits].reverse().map((c) => `${c.parents.length > 1 ? "* " : c.lane === "b" ? "| * " : "* "}${c.id} ${c.msg}${tags(c)}`).join("\n");
173
+ }
174
+
175
+ // grafo em SVG: três trilhas (pessoa A em cima, main no meio, pessoa B embaixo), um nó por commit
176
+ export function graphSVG(frame, people, { h = 240 } = {}) {
177
+ const lanes = { a: 58, main: h / 2, b: h - 58 };
178
+ const cs = frame.commits, gap = 170, w = Math.max(560, 120 + gap * Math.max(1, cs.length - 1)); // o grafo cresce com os commits
179
+ const pos = new Map(cs.map((c, i) => [c.id, { x: 60 + i * gap, y: lanes[c.lane] ?? lanes.main, c }]));
180
+ // commit que já está no remoto sobe para a trilha main
181
+ const onRemote = new Set(); const st = [frame.remote];
182
+ while (st.length) { const id = st.pop(); if (onRemote.has(id)) continue; onRemote.add(id); cs.find((c) => c.id === id)?.parents.forEach((q) => st.push(q)); }
183
+ for (const [id, p] of pos) if (onRemote.has(id)) p.y = lanes.main;
184
+ let g = `<text x="0" y="${lanes.a + 7}" class="gg-lane f-label">${people[0]}</text><text x="0" y="${lanes.main + 7}" class="gg-lane f-label">main</text><text x="0" y="${lanes.b + 7}" class="gg-lane f-label">${people[1]}</text>`;
185
+ g = g.replace(/<text x="0"/g, '<text x="-6" text-anchor="end"');
186
+ cs.forEach((c) => c.parents.forEach((q) => {
187
+ const a = pos.get(q), b = pos.get(c.id);
188
+ g += a.y === b.y ? `<line x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" class="gg-edge"/>`
189
+ : `<path d="M${a.x} ${a.y} C${a.x + gap * 0.55} ${a.y} ${b.x - gap * 0.55} ${b.y} ${b.x} ${b.y}" class="gg-edge" fill="none"/>`;
190
+ }));
191
+ const last = cs.at(-1)?.id;
192
+ cs.forEach((c) => {
193
+ const p = pos.get(c.id), who = c.author < 0 ? "gg-root" : c.author === 0 ? "gg-a" : "gg-b";
194
+ g += `<g class="gg-node ${who}${c.id === last ? " gg-new" : ""}${c.parents.length > 1 ? " gg-merge" : ""}"><circle cx="${p.x}" cy="${p.y}" r="${c.parents.length > 1 ? 15 : 13}"/><text x="${p.x}" y="${p.y > h / 2 ? p.y + 40 : p.y - 24}" text-anchor="middle" class="gg-msg f-body">${esc(c.msg.length > 16 ? c.msg.slice(0, 15) + "…" : c.msg)}</text></g>`;
195
+ });
196
+ const rp = pos.get(frame.remote);
197
+ if (rp) g += `<g class="gg-tag"><rect x="${rp.x - 64}" y="${rp.y + 20}" width="128" height="30" rx="15"/><text x="${rp.x}" y="${rp.y + 41}" text-anchor="middle" class="f-label">origin/main</text></g>`;
198
+ return `<svg class="git-graph" viewBox="-110 0 ${w + 190} ${h}" preserveAspectRatio="xMinYMid meet" aria-label="Grafo de commits">${g}</svg>`;
199
+ }
200
+ const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -0,0 +1,112 @@
1
+ // Dinâmicas ao vivo entre duas pessoas. Cada clique da apresentação é um quadro (o mesmo mecanismo do Código
2
+ // guiado: data-lesson + data-lesson-panel), então funciona com o controle remoto, o modo apresentador e o PDF
3
+ // (que mostra o último quadro).
4
+ // duel — Duelo de commits: dois editores, o comando da vez e o grafo crescendo (simulador em git-sim.js)
5
+ // terminals — dois terminais lado a lado com o comando sendo digitado (roteiro livre ou o mesmo do duelo)
6
+ // turns — turnos genéricos: code review, cliente × servidor, debate, role-play, pair programming
7
+ import { md, esc } from "../markup.js";
8
+ import { poll } from "../elements.js";
9
+ import { highlightCode as hlRaw } from "../code-highlight.js";
10
+ import { resolveCodeLanguage } from "../code-language.js";
11
+ import { simulateGit, graphSVG } from "./git-sim.js";
12
+
13
+ const langOf = (file, lang) => resolveCodeLanguage(lang, file);
14
+ // coloração das linguagens que o motor conhece; as outras saem só escapadas
15
+ const plainLines = (src) => src.split("\n").map(esc);
16
+ const highlightCode = (src, language) => { try { return language ? hlRaw(src, language) : plainLines(src); } catch { return plainLines(src); } };
17
+ const initial = (name) => esc(String(name || "?").trim().charAt(0).toUpperCase() || "?");
18
+ const personOf = (p, i) => (typeof p === "object" && p ? { name: String(p.name || `Pessoa ${i + 1}`), role: p.role ? String(p.role) : "" } : { name: String(p || (i ? "Pessoa B" : "Pessoa A")), role: "" });
19
+ const panel = (i, inner, cls = "") => `<section class="lesson-panel dyn-frame ${cls}${i === 0 ? " active" : ""}" data-lesson-panel="${i}">${inner}</section>`;
20
+ const counter = (i, n) => `<div class="dyn-count f-label">${String(i + 1).padStart(2, "0")} / ${String(n).padStart(2, "0")}</div>`;
21
+
22
+ function codeLines(file, { changed = [], conflict = [] } = {}, language) {
23
+ const hl = highlightCode(file.join("\n"), language);
24
+ const ch = new Set(changed), cf = new Set(conflict);
25
+ return `<ol class="dyn-code dyn-mono">${file.map((l, i) => {
26
+ const marker = /^(<<<<<<<|=======|>>>>>>>)/.test(l);
27
+ const cls = [cf.has(i + 1) ? (marker ? "cf cf-mark" : "cf") : "", ch.has(i + 1) && !cf.has(i + 1) ? "chg" : ""].filter(Boolean).join(" ");
28
+ return `<li${cls ? ` class="${cls}"` : ""}><span class="ln">${i + 1}</span><span class="lc">${marker ? esc(l) : hl[i] || " "}</span></li>`;
29
+ }).join("")}</ol>`;
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------------------------------- duel
33
+ export function duelHTML(s, ctx, head) {
34
+ let sim;
35
+ try { sim = simulateGit(s); }
36
+ catch (e) { ctx?.warnings?.push(`duelo de commits: ${e.message}`); return `<div class="L-duel">${head(s)}<div class="dyn-error f-body">Roteiro do duelo com erro: ${esc(e.message)}</div></div>`; }
37
+ const language = langOf(s.file, s.language);
38
+ const frames = sim.frames.map((f) => ({ f }));
39
+ // aposta: uma enquete antes do primeiro pull que decide (conflito ou merge); o quadro seguinte revela
40
+ if (s.bet) {
41
+ const at = frames.findIndex(({ f }) => ["conflict", "merge", "ff"].includes(f.event));
42
+ if (at > 0) {
43
+ frames[at].reveal = frames[at].f.event === "conflict" ? "Deu conflito!" : "Sem conflito: o Git juntou sozinho.";
44
+ frames.splice(at, 0, { bet: true, f: frames[at - 1].f });
45
+ }
46
+ }
47
+ const n = frames.length;
48
+ const editor = (p, i, f) => `<div class="dyn-ed who-${i ? "b" : "a"}${f.who === i ? " turn" : ""}${p.status === "conflito" ? " has-conflict" : ""}">
49
+ <div class="dyn-ed-top"><span class="dyn-av">${initial(p.name)}</span><b class="f-heading">${esc(p.name)}</b><span class="dyn-file dyn-mono">${esc(sim.file)}</span><span class="dyn-status f-label st-${p.status.replace(/\W+/g, "-")}">${esc(p.status)}</span></div>
50
+ ${codeLines(p.file, p, language)}</div>`;
51
+ const body = frames.map(({ f, bet, reveal }, i) => {
52
+ if (bet) {
53
+ const q = typeof s.bet === "string" ? s.bet : "Vai dar conflito?";
54
+ const pollEl = poll({ poll: `${s.id || "duelo"}-aposta`, options: s.betOptions || ["Vai dar conflito", "O Git resolve sozinho"], hint: "Votem antes do pull" }, ctx);
55
+ return panel(i, `${counter(i, n)}<div class="dyn-bet"><div class="dyn-bet-q t f-display">${md(q)}</div>${pollEl}</div>`, "dyn-bet-frame");
56
+ }
57
+ const who = f.who >= 0 ? sim.people[f.who] : "";
58
+ const term = `<div class="dyn-term">${f.cmd ? `<div class="dyn-cmd dyn-mono"><span class="dyn-prompt">${esc(who || "$")} $</span> ${esc(f.cmd)}</div>` : ""}${f.out ? `<pre class="dyn-out dyn-mono ev-${f.event}">${esc(f.out)}</pre>` : ""}${f.text ? `<p class="dyn-say t f-body">${md(f.text)}</p>` : ""}</div>`;
59
+ return panel(i, `${counter(i, n)}<div class="dyn-row">${f.people.map((p, k) => editor(p, k, f)).join("")}</div>
60
+ <div class="dyn-bottom">${term}<div class="dyn-graph">${graphSVG(f, sim.people)}</div></div>
61
+ ${reveal ? `<div class="dyn-reveal t f-display ${f.event === "conflict" ? "bad" : "good"}">${esc(reveal)}</div>` : ""}${f.note ? `<div class="dyn-note t f-body">${md(f.note)}</div>` : ""}`, `ev-${f.event}`);
62
+ }).join("");
63
+ return `<div class="L-duel dyn" data-lesson="duel" data-lesson-count="${n}">${head(s)}<div class="dyn-frames">${body}</div></div>`;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------------------------- terminals
67
+ export function terminalsHTML(s, ctx, head) {
68
+ let people, steps;
69
+ if (s.base != null && Array.isArray(s.turns)) { // mesmo roteiro do duelo: os comandos e a saída vêm do simulador
70
+ try {
71
+ const sim = simulateGit(s);
72
+ people = sim.people.map((name) => ({ name }));
73
+ steps = sim.frames.slice(1).filter((f) => f.cmd).map((f) => ({ who: f.who + 1, cmd: f.cmd, out: f.out, event: f.event }));
74
+ } catch (e) { ctx?.warnings?.push(`terminais: ${e.message}`); return `<div class="L-terminals">${head(s)}<div class="dyn-error f-body">Roteiro com erro: ${esc(e.message)}</div></div>`; }
75
+ } else {
76
+ people = [0, 1].map((i) => personOf((s.panes || s.people || [])[i], i));
77
+ steps = (s.steps || []).map((x) => (typeof x === "string" ? { who: 1, cmd: x } : x || {}));
78
+ }
79
+ const n = Math.max(1, steps.length);
80
+ const MAX = s.keep || 6; // comandos visíveis por terminal (os mais velhos saem por cima)
81
+ const body = Array.from({ length: n }, (_, i) => {
82
+ const pane = (p, k) => {
83
+ const mine = steps.slice(0, i + 1).map((x, j) => ({ ...x, j })).filter((x) => (Number(x.who) || 1) - 1 === k).slice(-MAX);
84
+ const rows = mine.map((x) => {
85
+ const last = x.j === i;
86
+ const cmd = String(x.cmd ?? "");
87
+ return `<div class="tm-entry${last ? " tm-new" : ""}"><div class="tm-line"><span class="tm-ps">${esc(p.name.toLowerCase().replace(/\s+/g, ""))}@${esc(s.host || "dev")} $</span> <span class="tm-cmd"${last ? ` style="--n:${Math.max(1, cmd.length)}"` : ""}>${esc(cmd)}</span></div>${x.out ? `<pre class="tm-out${x.event ? ` ev-${esc(x.event)}` : ""}">${esc(x.out)}</pre>` : ""}</div>`;
88
+ }).join("");
89
+ return `<div class="tm-pane who-${k ? "b" : "a"}${(Number(steps[i]?.who) || 1) - 1 === k ? " turn" : ""}"><div class="tm-bar"><i></i><i></i><i></i><b class="f-label">${esc(p.name)}${p.role ? ` · ${esc(p.role)}` : ""}</b></div><div class="tm-body dyn-mono">${rows || '<div class="tm-line tm-idle"><span class="tm-ps">$</span> <span class="tm-caret"></span></div>'}</div></div>`;
90
+ };
91
+ return panel(i, `${counter(i, n)}<div class="tm-row">${people.map(pane).join("")}</div>${steps[i]?.note ? `<div class="dyn-note t f-body">${md(steps[i].note)}</div>` : ""}`);
92
+ }).join("");
93
+ return `<div class="L-terminals dyn" data-lesson="terminals" data-lesson-count="${n}">${head(s)}<div class="dyn-frames">${body}</div></div>`;
94
+ }
95
+
96
+ // -------------------------------------------------------------------------------------------------- turns
97
+ export function turnsHTML(s, ctx, head) {
98
+ const people = [0, 1].map((i) => personOf((s.people || [])[i], i));
99
+ const turns = (s.turns || []).map((x) => (typeof x === "string" ? { who: 1, text: x } : x || {}));
100
+ const n = Math.max(1, turns.length);
101
+ const MAX = s.keep || 4; // falas visíveis ao mesmo tempo (as mais velhas sobem e somem)
102
+ const bubble = (t, j, last) => {
103
+ const k = Math.min(1, Math.max(0, (Number(t.who) || 1) - 1));
104
+ const p = people[k];
105
+ const req = t.method || t.url ? `<div class="tn-req dyn-mono"><b>${esc(String(t.method || "GET").toUpperCase())}</b> ${esc(t.url || "")}${t.status ? ` <span class="tn-status s${String(t.status).charAt(0)}">${esc(t.status)}</span>` : ""}</div>` : "";
106
+ const code = t.code ? `<pre class="tn-code dyn-mono">${highlightCode(String(t.code), resolveCodeLanguage(t.language, "")).join("\n")}</pre>` : "";
107
+ return `<div class="tn-turn who-${k ? "b" : "a"}${last ? " tn-new" : ""}"><span class="dyn-av">${initial(p.name)}</span><div class="tn-bubble">${t.tag ? `<span class="tn-tag f-label">${esc(t.tag)}</span>` : ""}${req}${t.text ? `<div class="tn-text t f-body">${md(t.text)}</div>` : ""}${code}</div></div>`;
108
+ };
109
+ const top = `<div class="tn-cast">${people.map((p, k) => `<div class="tn-who who-${k ? "b" : "a"}"><span class="dyn-av">${initial(p.name)}</span><div><b class="f-heading">${esc(p.name)}</b>${p.role ? `<small class="f-label">${esc(p.role)}</small>` : ""}</div></div>`).join('<span class="tn-vs f-label">×</span>')}</div>`;
110
+ const body = Array.from({ length: n }, (_, i) => panel(i, `<div class="tn-feed">${turns.slice(Math.max(0, i + 1 - MAX), i + 1).map((t, j, arr) => bubble(t, j, j === arr.length - 1)).join("")}</div>`)).join("");
111
+ return `<div class="L-turns dyn" data-lesson="turns" data-lesson-count="${n}">${head(s)}${top}<div class="dyn-frames">${body}</div></div>`;
112
+ }