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,117 @@
1
+ // Diagramas gerados em SVG.
2
+ // { diagram: loop, nodes: [dados, modelo, decisão, ação], actor: in|on|out|none, at: 3 }
3
+ // { diagram: spectrum, stops: [HITL, HOTL, HOOTL], at: 1, left: "humano decide", right: "máquina decide" }
4
+ // { diagram: flow, steps: [a, b, c], highlight: 1 }
5
+ // { diagram: venn, a: "…", b: "…", both: "…" }
6
+ import { esc } from "../markup.js";
7
+ import { humanBody } from "./pictos.js";
8
+
9
+ const wrap = (box, inner, cls = "") =>
10
+ `<svg class="pic dia ${cls}" viewBox="${box.join(" ")}" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">${inner}</svg>`;
11
+
12
+ function arrowDefs(id) {
13
+ return `<defs><marker id="${id}" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" style="fill:var(--fg)"/></marker></defs>`;
14
+ }
15
+
16
+ let uid = 0;
17
+
18
+ export function loop(o = {}) {
19
+ const nodes = o.nodes || ["dados", "modelo", "decisão", "ação"];
20
+ const n = nodes.length, cx = 300, cy = 300, r = 190;
21
+ const id = `ar${++uid}`;
22
+ const ang = (i) => (-90 + (360 / n) * i) * Math.PI / 180;
23
+ const pt = (a, rr = r) => [cx + rr * Math.cos(a), cy + rr * Math.sin(a)];
24
+ let g = arrowDefs(id);
25
+ const actor = o.actor || "none";
26
+ const at = o.at ?? n - 1; // posição do humano quando "in": entre o nó at e at+1
27
+ for (let i = 0; i < n; i++) {
28
+ const a1 = ang(i) + 0.2, a2 = ang(i + 1) - 0.2;
29
+ const [x1, y1] = pt(a1), [x2, y2] = pt(a2);
30
+ if (actor === "in" && i === at) {
31
+ const mid = (ang(i) + ang(i + 1)) / 2;
32
+ const [mx1, my1] = pt(mid - 0.2), [mx2, my2] = pt(mid + 0.2);
33
+ g += `<path d="M${x1} ${y1} A${r} ${r} 0 0 1 ${mx1} ${my1}" fill="none" style="stroke:var(--fg)" stroke-width="10" stroke-linecap="round"/>`;
34
+ g += `<path d="M${mx2} ${my2} A${r} ${r} 0 0 1 ${x2} ${y2}" fill="none" style="stroke:var(--fg)" stroke-width="10" stroke-linecap="round" marker-end="url(#${id})"/>`;
35
+ } else {
36
+ g += `<path d="M${x1} ${y1} A${r} ${r} 0 0 1 ${x2} ${y2}" fill="none" style="stroke:var(--fg)" stroke-width="10" stroke-linecap="round" marker-end="url(#${id})"/>`;
37
+ }
38
+ }
39
+ nodes.forEach((label, i) => {
40
+ const a = ang(i);
41
+ const [x, y] = pt(a);
42
+ g += `<circle cx="${x}" cy="${y}" r="17" style="fill:var(--fg)"/>`;
43
+ const [lx, ly] = pt(a, r + 58);
44
+ const anchor = Math.abs(Math.cos(a)) < 0.3 ? "middle" : Math.cos(a) > 0 ? "start" : "end";
45
+ const dx = anchor === "start" ? -26 : anchor === "end" ? 26 : 0;
46
+ g += `<text x="${lx + dx}" y="${ly + 10}" text-anchor="${anchor}" class="f-label" font-size="${o.labelSize || 30}" style="fill:var(--fg)">${esc(label)}</text>`;
47
+ });
48
+ const person = (x, y, s, faded) =>
49
+ `<g opacity="${faded ? 0.38 : 1}"><circle cx="${x}" cy="${y}" r="${60 * s}" style="fill:var(--hi)"/><g transform="translate(${x - 60 * s * 0.62} ${y - 60 * s * 0.86}) scale(${s * 0.62})" style="--pc:var(--on-hi);--po:var(--on-hi)">${humanBody(faded ? "sleep" : o.actorPose || "stand")}</g></g>`;
50
+ let box = [0, 0, 600, 600];
51
+ if (actor === "in") {
52
+ const mid = (ang(at) + ang(at + 1)) / 2;
53
+ const [x, y] = pt(mid);
54
+ g += person(x, y, 1);
55
+ } else if (actor === "on") {
56
+ g += `<line x1="${cx}" y1="${cy - r - 40}" x2="${cx}" y2="${-40}" style="stroke:var(--fg)" stroke-width="6" stroke-dasharray="14 12"/>`;
57
+ g += person(cx, -110, 1);
58
+ box = [0, -200, 600, 800];
59
+ } else if (actor === "out") {
60
+ g += `<g opacity=".4"><circle cx="720" cy="40" r="64" style="fill:var(--hi)"/><g transform="translate(662 6) scale(0.64)" style="--pc:var(--on-hi);--po:var(--on-hi)">${humanBody("sleep")}</g></g>`;
61
+ box = [0, -60, 820, 680];
62
+ }
63
+ return wrap(box, g, "loop");
64
+ }
65
+
66
+ export function spectrum(o = {}) {
67
+ const stops = o.stops || ["HITL", "HOTL", "HOOTL"];
68
+ const W = 1200, y = 90, x0 = 60, x1 = W - 60;
69
+ const n = stops.length;
70
+ const X = (i) => x0 + ((x1 - x0) * i) / (n - 1);
71
+ let g = `<line x1="${x0}" y1="${y}" x2="${x1}" y2="${y}" style="stroke:var(--line)" stroke-width="14" stroke-linecap="round"/>`;
72
+ stops.forEach((s, i) => {
73
+ g += `<circle cx="${X(i)}" cy="${y}" r="16" style="fill:var(--fg)"/>`;
74
+ g += `<text x="${X(i)}" y="${y + 70}" text-anchor="middle" class="f-heading" font-size="44" style="fill:var(--fg)">${esc(s)}</text>`;
75
+ });
76
+ if (o.left) g += `<text x="${x0}" y="${y - 44}" class="f-label" font-size="24" style="fill:var(--muted)">${esc(o.left)}</text>`;
77
+ if (o.right) g += `<text x="${x1}" y="${y - 44}" text-anchor="end" class="f-label" font-size="24" style="fill:var(--muted)">${esc(o.right)}</text>`;
78
+ if (o.at != null) {
79
+ const x = typeof o.at === "number" && o.at <= 1 && !Number.isInteger(o.at) ? x0 + (x1 - x0) * o.at : X(o.at);
80
+ g += `<circle class="marker" cx="${x}" cy="${y}" r="34" style="fill:var(--hi);stroke:var(--fg)" stroke-width="6"/>`;
81
+ }
82
+ return wrap([0, 0, W, 190], g, "spectrum");
83
+ }
84
+
85
+ export function flow(o = {}) {
86
+ const steps = o.steps || [];
87
+ const n = steps.length, bw = 300, gap = 90, bh = 150;
88
+ const W = n * bw + (n - 1) * gap;
89
+ const id = `ar${++uid}`;
90
+ let g = arrowDefs(id);
91
+ steps.forEach((s, i) => {
92
+ const x = i * (bw + gap);
93
+ const hl = o.highlight === i;
94
+ g += `<rect x="${x}" y="0" width="${bw}" height="${bh}" rx="18" style="fill:var(${hl ? "--hi" : "--surface"});stroke:var(--fg)" stroke-width="${hl ? 0 : 4}"/>`;
95
+ g += `<foreignObject x="${x + 16}" y="10" width="${bw - 32}" height="${bh - 20}"><div xmlns="http://www.w3.org/1999/xhtml" class="f-heading" style="height:100%;display:flex;align-items:center;justify-content:center;text-align:center;font-size:36px;color:var(${hl ? "--on-hi" : "--fg"})">${esc(s)}</div></foreignObject>`;
96
+ if (i < n - 1) g += `<line x1="${x + bw + 12}" y1="${bh / 2}" x2="${x + bw + gap - 20}" y2="${bh / 2}" style="stroke:var(--fg)" stroke-width="8" marker-end="url(#${id})"/>`;
97
+ });
98
+ return wrap([-4, -4, W + 8, bh + 8], g, "flow");
99
+ }
100
+
101
+ export function venn(o = {}) {
102
+ let g = `<circle cx="300" cy="260" r="220" style="fill:var(--hi)" opacity=".85"/>
103
+ <circle cx="560" cy="260" r="220" style="fill:var(--fg)" opacity=".85"/>`;
104
+ g += `<text x="220" y="270" text-anchor="middle" class="f-heading" font-size="40" style="fill:var(--on-hi)">${esc(o.a || "")}</text>`;
105
+ g += `<text x="650" y="270" text-anchor="middle" class="f-heading" font-size="40" style="fill:var(--bg)">${esc(o.b || "")}</text>`;
106
+ if (o.both) g += `<text x="430" y="270" text-anchor="middle" class="f-label" font-size="26" style="fill:var(--bg)">${esc(o.both)}</text>`;
107
+ return wrap([60, 20, 740, 480], g, "venn");
108
+ }
109
+
110
+ export function diagram(o) {
111
+ const k = o.diagram;
112
+ if (k === "loop" || k === "cycle") return loop(o);
113
+ if (k === "spectrum") return spectrum(o);
114
+ if (k === "flow") return flow(o);
115
+ if (k === "venn") return venn(o);
116
+ throw new Error(`diagrama "${k}" desconhecido (loop | cycle | spectrum | flow | venn)`);
117
+ }
@@ -0,0 +1,166 @@
1
+ // Ícones: 2.100+ do Lucide (MIT), embutidos só os que o deck usa.
2
+ // Uso no YAML: { icon: gavel } { icon: car, size: 120, color: hi }
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { createRequire } from "node:module";
6
+
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
11
+ let DIR;
12
+ function dir() {
13
+ if (!DIR) {
14
+ // versão empacotada (pip) traz os ícones ao lado do motor; em desenvolvimento vêm do node_modules ou do engine
15
+ const candidates = [
16
+ path.join(HERE, "icons"),
17
+ path.join(HERE, "..", "..", "python", "sagadeck", "engine", "icons"),
18
+ path.join(HERE, "..", "python", "sagadeck", "engine", "icons")
19
+ ];
20
+ const found = candidates.find((p) => fs.existsSync(p));
21
+ if (found) {
22
+ DIR = found;
23
+ } else {
24
+ DIR = path.join(path.dirname(require.resolve("lucide-static/package.json")), "icons");
25
+ }
26
+ }
27
+ return DIR;
28
+ }
29
+
30
+ export function iconExists(name) {
31
+ return fs.existsSync(path.join(dir(), `${name}.svg`));
32
+ }
33
+
34
+ export function listIcons(filter) {
35
+ const all = fs.readdirSync(dir()).filter((f) => f.endsWith(".svg")).map((f) => f.slice(0, -4));
36
+ return filter ? all.filter((n) => n.includes(filter)) : all;
37
+ }
38
+
39
+ // Tabela determinística de sinônimos e termos em português para modelos de IA
40
+ const ICON_SYNONYMS = {
41
+ money: "banknote", dinheiro: "banknote", cash: "banknote", moeda: "coins", moedas: "coins",
42
+ dollar: "dollar-sign", real: "banknote", preco: "tag", preço: "tag", custo: "wallet",
43
+ ai: "sparkles", ia: "sparkles", "artificial-intelligence": "brain-circuit",
44
+ brain: "brain", cerebro: "brain", cérebro: "brain",
45
+ lamp: "lightbulb", lampada: "lightbulb", lâmpada: "lightbulb", idea: "lightbulb", ideia: "lightbulb",
46
+ speedometer: "gauge", velocidade: "gauge", rapido: "zap", rápido: "zap", speed: "gauge",
47
+ "rocket-ship": "rocket", foguete: "rocket", lancamento: "rocket", lançamento: "rocket", startup: "rocket",
48
+ people: "users", pessoas: "users", team: "users", time: "users", equipe: "users",
49
+ usuarios: "users", usuários: "users", person: "user", pessoa: "user", cliente: "user",
50
+ security: "shield-check", seguranca: "shield-check", segurança: "shield-check", protecao: "shield", proteção: "shield",
51
+ lock: "lock", cadeado: "lock", chave: "key", key: "key",
52
+ growth: "trending-up", crescimento: "trending-up", subida: "trending-up", lucro: "trending-up",
53
+ drop: "trending-down", queda: "trending-down", perda: "trending-down",
54
+ chart: "bar-chart-2", grafico: "bar-chart-3", gráfico: "bar-chart-3", barras: "bar-chart-2",
55
+ pie: "pie-chart", pizza: "pie-chart", rosca: "circle",
56
+ check: "check-circle-2", sucesso: "check-circle-2", aprovado: "check-check", ok: "check",
57
+ danger: "alert-triangle", perigo: "alert-triangle", alerta: "alert-triangle", aviso: "alert-circle",
58
+ error: "x-circle", falha: "x-circle", erro: "alert-triangle", cancelar: "x",
59
+ target: "target", alvo: "target", meta: "target", objetivo: "target",
60
+ star: "star", estrela: "star", favorito: "star",
61
+ heart: "heart", coracao: "heart", coração: "heart", amor: "heart", curtir: "heart",
62
+ trophy: "trophy", trofeu: "trophy", troféu: "trophy", premio: "award", prêmio: "award",
63
+ award: "award", medalha: "award", medal: "award",
64
+ zap: "zap", raio: "zap", energia: "zap", eletrico: "zap", elétrico: "zap",
65
+ cpu: "cpu", processador: "cpu", chip: "cpu", hardware: "cpu",
66
+ cloud: "cloud", nuvem: "cloud", servidor: "server", server: "server",
67
+ database: "database", banco: "database", dados: "database", data: "database",
68
+ code: "code-2", codigo: "code-2", código: "code-2", software: "code-2", dev: "code-2",
69
+ mobile: "smartphone", celular: "smartphone", telefone: "phone", phone: "phone",
70
+ laptop: "laptop", computador: "laptop", notebook: "laptop", pc: "monitor",
71
+ globe: "globe", mundo: "globe", global: "globe", terra: "globe", web: "globe", internet: "globe",
72
+ search: "search", busca: "search", pesquisa: "search", lupa: "search",
73
+ calendar: "calendar", calendario: "calendar", calendário: "calendar", agenda: "calendar", data_hora: "clock",
74
+ clock: "clock", tempo: "clock", relogio: "clock", relógio: "clock", hora: "clock",
75
+ message: "message-square", mensagem: "message-square", chat: "message-circle", conversa: "messages-square",
76
+ mail: "mail", email: "mail", correio: "mail",
77
+ eye: "eye", olho: "eye", visao: "eye", visão: "eye",
78
+ file: "file-text", arquivo: "file-text", documento: "file-text",
79
+ folder: "folder", pasta: "folder",
80
+ coffee: "coffee", cafe: "coffee", café: "coffee", pausa: "coffee",
81
+ tool: "wrench", ferramenta: "wrench", ferramentas: "wrench",
82
+ setting: "settings", settings: "settings", config: "settings", configuracao: "settings", configuração: "settings",
83
+ sparkle: "sparkles", sparkles: "sparkles", brilho: "sparkles", magica: "sparkles", mágica: "sparkles",
84
+ presentation: "presentation", slide: "presentation", palestra: "presentation",
85
+ book: "book-open", livro: "book-open", leitura: "book-open", educacao: "graduation-cap", educação: "graduation-cap",
86
+ music: "music", som: "volume-2", audio: "volume-2", áudio: "volume-2", microfone: "mic", mic: "mic",
87
+ video: "video", camera: "camera", câmera: "camera", filme: "film",
88
+ gift: "gift", presente: "gift",
89
+ car: "car-front", carro: "car-front", veiculo: "car-front", veículo: "car-front",
90
+ plane: "plane", aviao: "plane", avião: "plane", viagem: "plane",
91
+ map: "map", mapa: "map", local: "map-pin", pin: "map-pin", gps: "map-pin",
92
+ };
93
+
94
+ function levenshtein(a, b) {
95
+ const m = a.length, n = b.length;
96
+ const d = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
97
+ for (let i = 0; i <= m; i++) d[i][0] = i;
98
+ for (let j = 0; j <= n; j++) d[0][j] = j;
99
+ for (let i = 1; i <= m; i++) {
100
+ for (let j = 1; j <= n; j++) {
101
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
102
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
103
+ }
104
+ }
105
+ return d[m][n];
106
+ }
107
+
108
+ const warnedIcons = new Set();
109
+
110
+ export function resolveIconName(rawName) {
111
+ if (!rawName) return "sparkles";
112
+ const clean = String(rawName).toLowerCase().trim().replace(/_/g, "-");
113
+
114
+ // 1. Exato
115
+ if (iconExists(clean)) return clean;
116
+
117
+ // 2. Sinônimo / Tradução em português
118
+ if (ICON_SYNONYMS[clean] && iconExists(ICON_SYNONYMS[clean])) {
119
+ return ICON_SYNONYMS[clean];
120
+ }
121
+
122
+ // 3. Prefixos ou sufixos comuns (ex: icon-name, name-icon)
123
+ const stripped = clean.replace(/^(icon-|ico-)/, "").replace(/(-icon|-ico)$/, "");
124
+ if (iconExists(stripped)) return stripped;
125
+ if (ICON_SYNONYMS[stripped] && iconExists(ICON_SYNONYMS[stripped])) {
126
+ return ICON_SYNONYMS[stripped];
127
+ }
128
+
129
+ // 4. Distância de Levenshtein (encontra erro de digitação como 'shiled' -> 'shield')
130
+ const all = listIcons();
131
+ let bestMatch = null;
132
+ let minDistance = 3; // tolerância máxima de 2 caracteres trocados
133
+
134
+ for (const ic of all) {
135
+ if (Math.abs(ic.length - clean.length) > 2) continue;
136
+ const dist = levenshtein(clean, ic);
137
+ if (dist < minDistance) {
138
+ minDistance = dist;
139
+ bestMatch = ic;
140
+ if (dist === 1) break;
141
+ }
142
+ }
143
+
144
+ if (bestMatch) return bestMatch;
145
+
146
+ // 5. Fallback gracioso para evitar crash do build — mas avisa, senão o erro passa despercebido
147
+ if (!warnedIcons.has(clean)) {
148
+ warnedIcons.add(clean);
149
+ console.warn(`⚠ ícone "${rawName}" não existe; usei "sparkles". Procure o nome certo com: sagadeck icons <palavra>`);
150
+ }
151
+ return "sparkles";
152
+ }
153
+
154
+ // Retorna <svg> com stroke = currentColor, para herdar a cor do tom do slide.
155
+ export function iconSVG(name, { size = 96, stroke = 1.75, cls = "" } = {}) {
156
+ const resolved = resolveIconName(name);
157
+ const file = path.join(dir(), `${resolved}.svg`);
158
+
159
+ let svg = fs.readFileSync(file, "utf8").replace(/<!--[\s\S]*?-->/g, "").trim();
160
+ svg = svg
161
+ .replace(/\swidth="\d+"/, ` width="${size}"`)
162
+ .replace(/\sheight="\d+"/, ` height="${size}"`)
163
+ .replace(/stroke-width="[\d.]+"/, `stroke-width="${stroke}"`)
164
+ .replace(/class="[^"]*"/, `class="ico ${cls}"`);
165
+ return svg;
166
+ }
@@ -0,0 +1,20 @@
1
+ // GERADO por scripts/vendor-pictos.mjs — não edite à mão.
2
+ // Glifos: Material Symbols sharp (fill), @material-symbols/svg-400 0.47.5
3
+ // Copyright Google LLC · Licença Apache-2.0 (https://www.apache.org/licenses/LICENSE-2.0)
4
+ // Espaço de cada glifo: viewBox "0 -960 960 960".
5
+ export const MATERIAL_GLYPHS = {
6
+ "man": "M420-80v-280h-80v-313h280v313h-80v280H420Zm60.08-654q-30.08 0-51.58-21.42t-21.5-51.5q0-30.08 21.42-51.58t51.5-21.5q30.08 0 51.58 21.42t21.5 51.5q0 30.08-21.42 51.58t-51.5 21.5Z",
7
+ "directions_walk": "m278-40 116-586-101 43v133h-61v-173l192-81q14-6 29.5-7.5T484-710q17 3 29.5 11t20.5 20l42 66q31 48 77.5 75.5T753-510v60q-70-2-123.5-30.5T533-568l-38 152 92 83v293h-60v-240l-108-98-79 338h-62Zm210.5-735.5Q467-797 467-827t21.5-51.5Q510-900 540-900t51.5 21.5Q613-857 613-827t-21.5 51.5Q570-754 540-754t-51.5-21.5Z",
8
+ "directions_run": "M535-40v-239l-108-99-42 188-265-55 11-56 199 40 73-369-100 47v134h-60v-175l164-69q32-14 45.5-17.5T480-714q20 0 35.5 8.5T542-680l42 67q26 42 71 73t105 31v60q-67 0-119.5-31T543-573l-39 158 91 84v291h-60Zm-46.5-735.5Q467-797 467-827t21.5-51.5Q510-900 540-900t51.5 21.5Q613-857 613-827t-21.5 51.5Q570-754 540-754t-51.5-21.5Z",
9
+ "airline_seat_recline_normal": "M240-171v-521h60v461h320v60H240Zm198-547q-34 0-57.5-23.5T357-799q0-34 23.5-57.5T438-880q34 0 57.5 23.5T519-799q0 34-23.5 57.5T438-718ZM660-80v-191H340v-302q0-40.74 28.39-68.87Q396.77-670 437.89-670q41.11 0 69.61 28.13T536-573v202h184v291h-60Z",
10
+ "airline_seat_recline_extra": "M300-740q-29 0-49-20t-20-49q0-29 20-50t49-21q29 0 50 21t21 50q0 29-21 49t-50 20Zm-80 560L115-705h63l87 465h292v60H220ZM797-80 679-285H303l-57-303q-9-46 23-81.5t80-35.5q35 0 60 22.5t32 56.5l46 240h202l161 276-53 30Z",
11
+ "follow_the_signs": "M388-754q-30 0-51.5-21.5T315-827q0-30 21.5-51.5T388-900q30 0 51.5 21.5T461-827q0 30-21.5 51.5T388-754ZM126-40l117-589-103 46v133H80v-173l192-81q30-13 62-6.5t48 31.5l42 66q30 48 75.5 75.5T601-510v60q-66 0-119.5-30.5T384-572l-41 156 92 83v293h-60v-240l-108-98-79 338h-62Zm554 0v-565H530v-275h350v275H730v565h-50Zm43-610 92-92-92-92-36 36 31 31H587v50h131l-31 31 36 36Z",
12
+ "emoji_people": "M368-80v-538q-95-20-152.5-85.5T158-856h60q0 78 60.5 131T426-672h100q38 0 56 6.5t46 30.5l184 172-43 43-185-174v514h-60v-255h-96v255h-60Zm56.5-675.5Q403-777 403-807t21.5-51.5Q446-880 476-880t51.5 21.5Q549-837 549-807t-21.5 51.5Q506-734 476-734t-51.5-21.5Z",
13
+ "accessibility": "M428.5-755.42q-21.5-21.42-21.5-51.5t21.42-51.58q21.42-21.5 51.5-21.5t51.58 21.42q21.5 21.42 21.5 51.5t-21.42 51.58q-21.42 21.5-51.5 21.5t-51.58-21.42ZM372-80v-533H120v-60h720v60H588v533h-60v-260h-96v260h-60Z",
14
+ "accessibility_new": "M428.5-755.5Q407-777 407-807t21.5-51.5Q450-880 480-880t51.5 21.5Q553-837 553-807t-21.5 51.5Q510-734 480-734t-51.5-21.5ZM373-80v-533q-68-5-131.5-14T120-650l15-60q85 20 169 28.5t176 8.5q92 0 176-8.5T825-710l15 60q-58 14-121.5 23T587-613v533h-60v-260h-94v260h-60Z",
15
+ "hotel": "M40-200v-585h60v394h353v-309h467v500h-60v-131H100v131H40Zm154.5-279.5Q164-510 164-555t30.5-75.5Q225-661 270-661t75.5 30.5Q376-600 376-555t-30.5 75.5Q315-449 270-449t-75.5-30.5Zm108-43Q316-536 316-555t-13.5-32.5Q289-601 270-601t-32.5 13.5Q224-574 224-555t13.5 32.5Q251-509 270-509t32.5-13.5Zm-65 0Q224-536 224-555t13.5-32.5Q251-601 270-601t32.5 13.5Q316-574 316-555t-13.5 32.5Q289-509 270-509t-32.5-13.5Z",
16
+ "call": "M780-120q-108 0-225-56.5T336-335Q233-437 176.5-554T120-780v-60h215l40 189-117 119q28 45 57.5 83.5T377-378q36 36 77 67.5t88 57.5l117-121 181 39v215h-60Z",
17
+ "visibility": "M600.5-379.5Q650-429 650-500t-49.5-120.5Q551-670 480-670t-120.5 49.5Q310-571 310-500t49.5 120.5Q409-330 480-330t120.5-49.5Zm-200-41Q368-453 368-500t32.5-79.5Q433-612 480-612t79.5 32.5Q592-547 592-500t-32.5 79.5Q527-388 480-388t-79.5-32.5ZM216-283Q98-366 40-500q58-134 176-217t264-83q146 0 264 83t176 217q-58 134-176 217t-264 83q-146 0-264-83Z",
18
+ "question_mark": "M431-330q1-72 16.5-105t58.5-72q42-38 64.5-70.5T593-647q0-45-30-75t-84-30q-52 0-80 29.5T358-661l-84-37q22-59 74.5-100.5T479-840q100 0 154 55.5T687-651q0 48-20.5 87T601-482q-49 47-59 72t-11 80H431Zm48 250q-29 0-49.5-20.5T409-150q0-29 20.5-49.5T479-220q29 0 49.5 20.5T549-150q0 29-20.5 49.5T479-80Z",
19
+ "approval": "M160-80v-320h640v320H160Zm60-160h520v-100H220v100Zm260-160L281-680q0-83 58.21-141.5T480-880q82.58 0 140.79 58.5Q679-763 679-680L480-400Z"
20
+ };
@@ -0,0 +1,255 @@
1
+ // Pictogramas (estilo sinalização), gerados na hora em SVG. A pessoa (`human`, e dentro de crowd/scene)
2
+ // usa glifos do Material Symbols (Apache-2.0) — veja material-pictos.js e scripts/vendor-pictos.mjs.
3
+ // Cores seguem o tom do slide via variáveis CSS:
4
+ // --pc cor da figura (padrão: --fg) --po cor dos objetos (padrão: --fg)
5
+ // classes: pc (fill figura), pcs (stroke figura), po/pos (objetos), ph (fill --hi),
6
+ // pe (fill --em), pm (fill --muted), pb (fill --bg), pl (fill --line)
7
+ //
8
+ // YAML:
9
+ // { picto: human, pose: walk } poses: veja POSES
10
+ // { picto: human, pose: phone, sign: circle } placa atrás: circle|square|triangle
11
+ // { picto: machine }
12
+ // { picto: scene, name: car-top, driver: machine, passenger: human, back: sleep }
13
+ // { picto: scene, name: console, screen: "START" }
14
+ // { picto: crowd, count: 20, highlight: 3 }
15
+
16
+ import { MATERIAL_GLYPHS } from "./material-pictos.js";
17
+
18
+ // Poses de { picto: human }: cada uma é um glifo de pessoa do Material Symbols (material-pictos.js),
19
+ // às vezes com um objeto do mesmo estilo (prop) — o set não tem pessoa falando ao celular, olhando,
20
+ // pensando ou carimbando. prop: [glifo, x, y, lado] no espaço do glifo (viewBox 0 -960 960 960).
21
+ // box: quadro da pose (padrão 120 x 160; "sleep" é deitado).
22
+ export const POSES = {
23
+ stand: { glyph: "man" },
24
+ walk: { glyph: "directions_walk" },
25
+ run: { glyph: "directions_run" },
26
+ sit: { glyph: "airline_seat_recline_normal" },
27
+ drive: { glyph: "airline_seat_recline_extra" },
28
+ phone: { glyph: "man", prop: ["call", 590, -930, 230] },
29
+ watch: { glyph: "man", prop: ["visibility", 600, -1000, 270] },
30
+ point: { glyph: "follow_the_signs" },
31
+ raise: { glyph: "emoji_people" },
32
+ shrug: { glyph: "accessibility" },
33
+ think: { glyph: "man", prop: ["question_mark", 590, -1010, 260] },
34
+ stamp: { glyph: "emoji_people", prop: ["approval", 50, -975, 210] },
35
+ cheer: { glyph: "accessibility_new" },
36
+ sleep: { glyph: "hotel", box: [0, 0, 180, 110] },
37
+ };
38
+
39
+ // Nomes alternativos (português e sinônimos) -> pose. Comparação sem acento, com hífens.
40
+ const POSE_ALIASES = {
41
+ "em-pe": "stand", parado: "stand", "de-pe": "stand", standing: "stand",
42
+ andando: "walk", caminhando: "walk", walking: "walk",
43
+ correndo: "run", running: "run",
44
+ sentado: "sit", sentada: "sit", sitting: "sit", seated: "sit",
45
+ dirigindo: "drive", driving: "drive", motorista: "drive",
46
+ telefone: "phone", celular: "phone", ligando: "phone", "no-telefone": "phone", "no-celular": "phone", call: "phone",
47
+ olhando: "watch", observando: "watch", assistindo: "watch", vigiando: "watch", looking: "watch",
48
+ apontando: "point", pointing: "point",
49
+ "mao-levantada": "raise", "levantando-a-mao": "raise", acenando: "raise", wave: "raise", waving: "raise",
50
+ "dando-de-ombros": "shrug", "de-ombros": "shrug", "bracos-abertos": "shrug",
51
+ pensando: "think", duvida: "think", thinking: "think",
52
+ carimbando: "stamp", aprovando: "stamp", carimbo: "stamp",
53
+ comemorando: "cheer", celebrando: "cheer", vibrando: "cheer", celebrate: "cheer",
54
+ dormindo: "sleep", deitado: "sleep", sleeping: "sleep",
55
+ };
56
+ const warnedPoses = new Set();
57
+
58
+ // Nome de pose como veio do YAML -> pose conhecida. Desconhecida vira "stand", com aviso (uma vez por nome).
59
+ export function resolvePose(name) {
60
+ if (!name) return "stand";
61
+ if (POSES[name]) return name;
62
+ const key = String(name).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[\s_]+/g, "-");
63
+ if (POSES[key]) return key;
64
+ if (POSE_ALIASES[key]) return POSE_ALIASES[key];
65
+ if (!warnedPoses.has(name)) {
66
+ warnedPoses.add(name);
67
+ console.warn(`⚠ pose "${name}" não existe; usei "stand". Poses: ${Object.keys(POSES).join(", ")} (ou em português: sentado, andando, pensando…)`);
68
+ }
69
+ return "stand";
70
+ }
71
+
72
+ // Quadrado onde o glifo é desenhado, centralizado no quadro da pose.
73
+ export function glyphSquare(poseName = "stand") {
74
+ const P = POSES[resolvePose(poseName)];
75
+ const [x, y, w, h] = P.box || [0, -8, 120, 168];
76
+ const side = Math.max(w, h) * (P.box ? 1 : 0.95);
77
+ return [x + (w - side) / 2, y + (h - side) / 2, side, side];
78
+ }
79
+
80
+ export function humanBody(poseName = "stand") {
81
+ const P = POSES[resolvePose(poseName)];
82
+ const [x, y, w, h] = glyphSquare(poseName);
83
+ let inner = `<path class="pc" d="${MATERIAL_GLYPHS[P.glyph]}"/>`;
84
+ if (P.prop) {
85
+ const [name, px, py, side] = P.prop;
86
+ inner += `<svg x="${px}" y="${py}" width="${side}" height="${side}" viewBox="0 -960 960 960" overflow="visible"><path class="po" d="${MATERIAL_GLYPHS[name]}"/></svg>`;
87
+ }
88
+ return `<svg x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${w.toFixed(1)}" height="${h.toFixed(1)}" viewBox="0 -960 960 960" overflow="visible">${inner}</svg>`;
89
+ }
90
+
91
+ function signPlate(kind, box) {
92
+ const [x, y, w, h] = box;
93
+ const cx = x + w / 2, cy = y + h / 2, r = Math.max(w, h) * 0.62;
94
+ if (kind === "circle") return `<circle class="ph" cx="${cx}" cy="${cy}" r="${r}"/>`;
95
+ if (kind === "square") return `<rect class="ph" x="${cx - r}" y="${cy - r}" width="${2 * r}" height="${2 * r}" rx="${r * 0.18}"/>`;
96
+ if (kind === "triangle") {
97
+ const R = r * 1.3;
98
+ return `<path class="ph" d="M${cx} ${cy - R * 0.95} L${cx + R} ${cy + R * 0.78} L${cx - R} ${cy + R * 0.78} Z" stroke-linejoin="round" stroke-width="${R * 0.12}" style="stroke:var(--hi)"/><path d="M${cx} ${cy - R * 0.72} L${cx + R * 0.8} ${cy + R * 0.64} L${cx - R * 0.8} ${cy + R * 0.64} Z" fill="none" style="stroke:var(--pc)" stroke-width="${R * 0.07}" stroke-linejoin="round"/>`;
99
+ }
100
+ return "";
101
+ }
102
+
103
+ export function machineBody() {
104
+ return `<line class="pos" x1="60" y1="30" x2="60" y2="12" stroke-width="7" stroke-linecap="round"/>
105
+ <circle class="po" cx="60" cy="10" r="7"/>
106
+ <rect class="po" x="14" y="30" width="92" height="92" rx="22"/>
107
+ <circle class="pb" cx="60" cy="76" r="25"/>
108
+ <circle class="ph" cx="60" cy="76" r="11"/>
109
+ <rect class="po" x="30" y="128" width="60" height="14" rx="7"/>`;
110
+ }
111
+
112
+ function wrap(inner, box, { w, h, cls = "", style = "" } = {}) {
113
+ const [x, y, bw, bh] = box;
114
+ return `<svg class="pic ${cls}" viewBox="${x} ${y} ${bw} ${bh}" ${w ? `width="${w}"` : ""} ${h ? `height="${h}"` : ""} style="${style}" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">${inner}</svg>`;
115
+ }
116
+
117
+ export function colorStyle(o = {}) {
118
+ const role = (v, d) => {
119
+ if (!v) return `var(--${d})`;
120
+ if (/^[0-9a-f]{6}$/i.test(v)) return `#${v}`;
121
+ if (v.startsWith("#")) return v;
122
+ return `var(--${v})`;
123
+ };
124
+ return `--pc:${role(o.color, "fg")};--po:${role(o.object, "fg")};`;
125
+ }
126
+
127
+ export function human(o = {}) {
128
+ const box = glyphSquare(o.pose);
129
+ let inner = "";
130
+ if (o.sign) {
131
+ inner += signPlate(o.sign, box);
132
+ return wrap(inner + humanBody(o.pose), plateBox(o.sign, box), { style: colorStyle({ color: o.color || "on-hi", object: o.object || "on-hi" }) });
133
+ }
134
+ return wrap(humanBody(o.pose), box, { style: colorStyle(o) });
135
+ }
136
+
137
+ // caixa que contém a placa inteira (círculo/quadrado/triângulo)
138
+ function plateBox(kind, box) {
139
+ const [x, y, w, h] = box;
140
+ const cx = x + w / 2, cy = y + h / 2, r = Math.max(w, h) * 0.62;
141
+ const R = kind === "triangle" ? r * 1.3 * 1.08 : r * 1.02;
142
+ const top = kind === "triangle" ? cy - R * 0.95 : cy - R, bottom = kind === "triangle" ? cy + R * 0.8 : cy + R;
143
+ return [cx - R, top, 2 * R, bottom - top];
144
+ }
145
+
146
+ export function machine(o = {}) {
147
+ let inner = "";
148
+ const box = [0, 0, 120, 150];
149
+ if (o.sign) inner += signPlate(o.sign, box);
150
+ return wrap(inner + machineBody(), o.sign ? plateBox(o.sign, box) : box, { style: colorStyle(o.sign ? { color: "on-hi", object: o.object || "on-hi" } : o) });
151
+ }
152
+
153
+ // Vários bonequinhos em grade; `highlight` primeiros ficam na cor --hi (ou --em).
154
+ export function crowd(o = {}) {
155
+ const n = o.count || 20, cols = o.cols || Math.min(n, 10), hl = o.highlight || 0;
156
+ const rows = Math.ceil(n / cols), cw = 70, chh = 110;
157
+ let inner = "";
158
+ for (let i = 0; i < n; i++) {
159
+ const x = (i % cols) * cw, y = Math.floor(i / cols) * chh;
160
+ const on = i < hl;
161
+ inner += `<g transform="translate(${x} ${y}) scale(0.55)" class="${on ? "crowd-on" : "crowd-off"}" style="--pc:${on ? `var(--${o.hiColor || "hi"})` : "var(--muted)"}">${humanBody(o.pose || "stand")}</g>`;
162
+ }
163
+ return wrap(inner, [-4, -8, cols * cw, rows * chh], { style: colorStyle(o) });
164
+ }
165
+
166
+ // ---------- cenas ----------
167
+ const SCENES = {
168
+ // Mesa de controle com monitor (ex.: Petrov). screen: texto que aparece na tela.
169
+ console(o) {
170
+ const scr = o.screen ?? "";
171
+ return [[0, 0, 420, 280], `
172
+ <rect class="po" x="0" y="196" width="420" height="12" rx="4"/>
173
+ <rect class="po" x="30" y="208" width="12" height="70"/><rect class="po" x="378" y="208" width="12" height="70"/>
174
+ <rect class="po" x="196" y="30" width="200" height="140" rx="10"/>
175
+ <rect class="${o.alarm ? "blink" : "pb"}" ${o.alarm ? 'style="fill:var(--c-alert)"' : ""} x="208" y="42" width="176" height="116" rx="4"/>
176
+ ${scr ? `<text x="296" y="${108 + (o.screenSize || 30) / 3}" text-anchor="middle" class="f-display" font-size="${o.screenSize || 30}" style="letter-spacing:.06em;fill:${o.alarm ? "#fff" : "var(--pc)"}">${scr}</text>` : ""}
177
+ <rect class="po" x="286" y="170" width="20" height="28"/>
178
+ <g transform="translate(40 52) scale(1)">${humanBody("sit")}</g>`];
179
+ },
180
+ // Pessoa em uma mesa com papéis (analista)
181
+ desk(o) {
182
+ return [[0, 0, 300, 260], `
183
+ <rect class="po" x="110" y="150" width="190" height="12" rx="4"/>
184
+ <rect class="po" x="130" y="162" width="10" height="92"/><rect class="po" x="270" y="162" width="10" height="92"/>
185
+ <rect class="pm" x="${o.laptop === false ? 190 : 170}" y="118" width="80" height="30" rx="3" transform="skewX(-12)"/>
186
+ ${o.papers ? `<rect class="pl" x="236" y="128" width="46" height="22" rx="2"/><rect class="pl" x="240" y="120" width="46" height="22" rx="2"/>` : ""}
187
+ <g transform="translate(40 18)">${humanBody("sit")}</g>`];
188
+ },
189
+ // Carro visto de cima. driver/passenger: human|human-watch|human-phone|machine|none ; back: sleep|human|none ; button: true
190
+ "car-top"(o) {
191
+ const seat = (x, y, w, h) => `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="16" style="fill:var(--line)" opacity=".7"/>`;
192
+ const headTop = (cx, cy) => `<ellipse cx="${cx}" cy="${cy + 16}" rx="36" ry="17" style="fill:var(--fg)"/><circle cx="${cx}" cy="${cy}" r="23" style="fill:var(--hi);stroke:var(--fg)" stroke-width="5"/>`;
193
+ const machineTop = (cx, cy) => `<rect class="po" x="${cx - 32}" y="${cy - 32}" width="64" height="64" rx="15"/><circle class="pb" cx="${cx}" cy="${cy}" r="15"/><circle class="ph" cx="${cx}" cy="${cy}" r="6.5"/>`;
194
+ const arms = (cx, cy) => `<path d="M${cx - 28} ${cy + 6} L${cx - 22} ${cy - 56} M${cx + 28} ${cy + 6} L${cx + 22} ${cy - 56}" style="stroke:var(--fg)" stroke-width="10" stroke-linecap="round"/>`;
195
+ const occ = (who, cx, cy, isDriver) => {
196
+ if (who === "human") return (isDriver ? arms(cx, cy) : "") + headTop(cx, cy);
197
+ if (who === "human-watch") return `<path d="M${cx + 24} ${cy + 4} L${cx + 4} ${cy - 76}" style="stroke:var(--fg)" stroke-width="10" stroke-linecap="round"/>` + headTop(cx, cy);
198
+ if (who === "human-phone") return headTop(cx, cy) + `<rect x="${cx - 11}" y="${cy + 30}" width="22" height="30" rx="5" style="fill:var(--fg)"/>`;
199
+ if (who === "machine") return (isDriver ? arms(cx, cy) : "") + machineTop(cx, cy);
200
+ return "";
201
+ };
202
+ const back = o.back === "sleep"
203
+ ? `<ellipse cx="172" cy="352" rx="62" ry="19" style="fill:var(--fg)"/><circle cx="96" cy="350" r="23" style="fill:var(--hi);stroke:var(--fg)" stroke-width="5"/><text x="196" y="318" class="f-display" style="fill:var(--fg)" font-size="38">Z</text><text x="226" y="296" class="f-display" style="fill:var(--fg)" font-size="26">z</text>`
204
+ : o.back === "human" ? headTop(150, 345) : "";
205
+ return [[0, 0, 300, 470], `
206
+ <rect x="26" y="8" width="248" height="454" rx="84" style="fill:var(--surface);stroke:var(--fg)" stroke-width="9"/>
207
+ <path d="M64 100 Q150 76 236 100 L220 136 Q150 122 80 136 Z" style="fill:var(--fg)" opacity=".28"/>
208
+ ${seat(64, 196, 82, 84)}${seat(154, 196, 82, 84)}${seat(64, 306, 172, 96)}
209
+ <rect x="68" y="146" width="74" height="16" rx="8" style="fill:var(--fg)"/>
210
+ ${o.button ? `<circle cx="196" cy="146" r="17" style="fill:var(--c-alert, var(--em))"/>` : ""}
211
+ ${occ(o.driver, 105, 236, true)}${occ(o.passenger, 195, 236, false)}${back}`];
212
+ },
213
+ // Juiz atrás da bancada com martelo
214
+ judge(o) {
215
+ return [[0, 0, 320, 260], `
216
+ <g transform="translate(100 6)">${humanBody("sit")}</g>
217
+ <rect class="po" x="40" y="118" width="240" height="140" rx="6"/>
218
+ <rect class="pb" x="60" y="140" width="200" height="8" rx="4" opacity=".25"/>
219
+ <g transform="rotate(-30 236 96)"><rect class="po" x="222" y="70" width="34" height="22" rx="4"/><rect class="po" x="236" y="90" width="7" height="40" rx="3"/></g>`];
220
+ },
221
+ // Elevador com pessoa e botão
222
+ elevator(o) {
223
+ return [[0, 0, 300, 300], `
224
+ <rect x="20" y="10" width="200" height="280" rx="8" fill="none" style="stroke:var(--fg)" stroke-width="10"/>
225
+ <line x1="120" y1="16" x2="120" y2="284" style="stroke:var(--line)" stroke-width="4"/>
226
+ <g transform="translate(62 104) scale(1.05)">${humanBody("stand")}</g>
227
+ <rect x="236" y="96" width="50" height="120" rx="10" style="fill:var(--surface);stroke:var(--fg)" stroke-width="5"/>
228
+ <circle cx="261" cy="126" r="11" style="fill:var(--muted)"/><circle cx="261" cy="156" r="11" style="fill:var(--muted)"/>
229
+ <circle cx="261" cy="190" r="15" class="${o.button === false ? "pm" : "pe"}"/>`];
230
+ },
231
+ // Duas pessoas, lado a lado, cada uma numa mesa (comparação de julgamentos)
232
+ pair(o) {
233
+ return [[0, 0, 560, 260], `
234
+ <g transform="translate(0 0)">${SCENES.desk({ papers: true })[1]}</g>
235
+ <g transform="translate(270 0)">${SCENES.desk({ papers: true })[1]}</g>`];
236
+ },
237
+ };
238
+
239
+ export function scene(o = {}) {
240
+ const fn = SCENES[o.name];
241
+ if (!fn) throw new Error(`Cena "${o.name}" não existe. Disponíveis: ${Object.keys(SCENES).join(", ")}`);
242
+ const [box, inner] = fn(o);
243
+ return wrap(inner, box, { style: colorStyle(o) });
244
+ }
245
+
246
+ export function picto(o) {
247
+ const k = o.picto;
248
+ if (k === "human") return human(o);
249
+ if (k === "machine" || k === "robot") return machine(o);
250
+ if (k === "crowd") return crowd(o);
251
+ if (k === "scene") return scene(o);
252
+ throw new Error(`picto "${k}" desconhecido (use human | machine | crowd | scene)`);
253
+ }
254
+
255
+ export const SCENE_NAMES = Object.keys(SCENES);