chalk-talk 0.17.0 → 0.17.2

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.
package/README.md CHANGED
@@ -10,7 +10,7 @@ Hand-drawn lessons that draw themselves, from a small JSON **recipe** — the pl
10
10
  <meta charset="utf-8">
11
11
  <title>My lesson</title>
12
12
  <script type="application/json" id="chalk-recipe">{ "id": "my-lesson", "title": "…", "width": 900, "height": 560, "steps": [ … ] }</script>
13
- <script src="https://cdn.jsdelivr.net/npm/chalk-talk@0.17.0/player.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/chalk-talk@0.17.2/player.js"></script>
14
14
  ```
15
15
 
16
16
  The player builds the page around the recipe — chapters, Next / Back, values to move, the check, downloads — and needs
@@ -2,6 +2,7 @@
2
2
 
3
3
  The six animations here — celebrate (🎉 U+1F389), think (🤔 U+1F914), surprised (😮 U+1F62E), ok (👍 U+1F44D),
4
4
  idea (💡 U+1F4A1) and time (⏰ U+23F0) — are **Animated Noto Emoji by Google**, licensed under
5
- [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), not under chalk-talk's MIT license. Source:
6
- https://googlefonts.github.io/noto-emoji-files/ (the Lottie files of fonts.gstatic.com/s/e/notoemoji/latest/). They are
7
- unchanged. A lesson that plays one shows the credit «Animated emoji: Noto Animated Emoji by Google, CC BY 4.0».
5
+ [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), not under chalk-talk's MIT license. The animations come
6
+ from https://googlefonts.github.io/noto-emoji-animation/ (the Lottie files at fonts.gstatic.com/s/e/notoemoji/latest/);
7
+ the license is stated at https://googlefonts.github.io/noto-emoji-files/ («Animated Noto Emoji is licensed under CC BY
8
+ 4.0»). They are unchanged. A lesson that plays one shows the credit «Animated emoji: Noto Animated Emoji by Google, CC BY 4.0».
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chalk-talk",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Hand-drawn lessons that draw themselves: a player for chalk-talk recipes (one script for the browser or a CDN), a lesson you can mount inside any element, and a command that checks a recipe and builds one HTML file.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,6 +42,7 @@
42
42
  "markdown.d.ts",
43
43
  "scripts/",
44
44
  "kit/",
45
+ "references/",
45
46
  "assets/player.html",
46
47
  "assets/lottie/",
47
48
  "assets/lottie_light.js",
@@ -0,0 +1,128 @@
1
+ {
2
+ "id": "platao-caverna",
3
+ "title": "Platão ↔ por dentro da caverna",
4
+ "category": "Filosofia",
5
+ "width": 900,
6
+ "height": 660,
7
+ "background": "plain",
8
+ "params": {
9
+ "afastamento": { "label": "afastamento do fogo (%)", "min": 0, "max": 100, "step": 10, "value": 50 }
10
+ },
11
+ "vars": {
12
+ "distancia": "100 + afastamento * 0.8",
13
+ "objeto": "555 - distancia",
14
+ "projecao": "410 / distancia",
15
+ "sombraY": "350 - 28 * projecao",
16
+ "sombraR": "6 * projecao"
17
+ },
18
+ "learning": {
19
+ "goal": "Entre na cena: descubra o que produz as sombras e por que aprender pode mudar nossa maneira de ver.",
20
+ "question": "Uma imagem muito convincente basta para saber o que aconteceu? O que você investigaria antes de acreditar?",
21
+ "answer": "Uma imagem pode ser uma pista, não uma explicação completa. Investigar a origem, o contexto e outras evidências ajuda a revisar a primeira interpretação.",
22
+ "caveat": "A alegoria está na República, livro VII. O desenho é um corte esquemático, não uma reconstrução histórica: luz pontual e projeção simplificada ajudam a visualizar as sombras. A saída remete à educação e à ideia do Bem, não apenas a trocar uma opinião por outra; retornar e educar também faz parte da alegoria."
23
+ },
24
+ "steps": [
25
+ { "type": "text", "chapter": "Um mundo de sombras", "at": [40, 59], "text": "e se tudo o que você conhecesse fosse uma sombra?", "size": 39, "duration": 900, "say": "Imagine conhecer o mundo inteiro apenas pelas sombras de uma parede. Vamos entrar na caverna de Platão." },
26
+
27
+ { "type": "polygon", "points": [[620, 105], [857, 105], [857, 479], [590, 479]], "style": { "color": "#F4EACB", "fill": "#F4EACB", "fillStyle": "solid", "roughness": 0.6 }, "duration": 90 },
28
+ { "type": "polygon", "points": [[45, 136], [110, 108], [213, 116], [299, 94], [410, 112], [492, 99], [564, 129], [615, 169], [644, 237], [654, 338], [686, 436], [620, 478], [45, 478]], "style": { "color": "#DDD7CE", "fill": "#E7E1D8", "fillStyle": "solid", "roughness": 0.8, "width": 1.6 }, "duration": 180 },
29
+ { "type": "polygon", "points": [[46, 136], [110, 108], [213, 116], [299, 94], [410, 112], [492, 99], [564, 129], [615, 169], [644, 237], [654, 338], [686, 436], [660, 426], [625, 331], [610, 245], [582, 190], [535, 160], [489, 145], [405, 151], [299, 136], [215, 156], [112, 149], [46, 177]], "style": { "color": "#8D8478", "fill": "#C6BDAF", "fillStyle": "solid", "width": 1.5, "roughness": 0.9 }, "duration": 220 },
30
+ { "type": "polygon", "points": [[45, 431], [173, 419], [270, 445], [356, 431], [470, 450], [579, 428], [654, 409], [686, 436], [857, 451], [857, 482], [45, 482]], "style": { "color": "#B5A68E", "fill": "#D6C8AF", "fillStyle": "solid", "width": 1.2 }, "duration": 150 },
31
+ { "type": "polyline", "points": [[102, 121], [122, 135], [116, 148]], "style": { "color": "#8D8478", "width": 1.2 }, "duration": 80 },
32
+ { "type": "polyline", "points": [[286, 104], [300, 122], [331, 128]], "style": { "color": "#8D8478", "width": 1.2 }, "duration": 80 },
33
+ { "type": "polyline", "points": [[480, 109], [493, 130], [520, 135]], "style": { "color": "#8D8478", "width": 1.2 }, "duration": 80 },
34
+ { "type": "polyline", "points": [[588, 156], [581, 174], [602, 205]], "style": { "color": "#8D8478", "width": 1.2 }, "duration": 80 },
35
+ { "type": "polygon", "points": [[194, 151], [202, 177], [211, 155]], "style": { "color": "#A79C8B", "fill": "#C6BDAF", "fillStyle": "solid", "width": 1 }, "duration": 70 },
36
+ { "type": "polygon", "points": [[445, 149], [458, 181], [466, 147]], "style": { "color": "#A79C8B", "fill": "#C6BDAF", "fillStyle": "solid", "width": 1 }, "duration": 70 },
37
+
38
+ { "type": "polygon", "points": [[66, 197], [174, 170], [174, 409], [66, 439]], "style": { "color": "#9B9387", "fill": "#F0E7D7", "fillStyle": "solid", "width": 1.8, "roughness": 0.7 }, "duration": 200, "say": "Aqui está a parede. Para quem está preso e só olha para ela, as sombras parecem ser as próprias coisas." },
39
+ { "type": "line", "from": [75, 208], "to": [75, 425], "style": { "color": "#D3C9B9", "width": 1.2 }, "duration": 100 },
40
+ { "type": "line", "from": [83, 429], "to": [167, 406], "style": { "color": "#C3B8A7", "width": 1.2 }, "duration": 80 },
41
+ { "type": "ellipse", "id": "sombra-cabeca", "center": [123, "sombraY - 5 * projecao"], "rx": "sombraR", "ry": "sombraR * 1.1", "style": { "color": "#59616D", "fill": "#59616D", "fillStyle": "solid", "roughness": 0.6, "opacity": 0.8, "width": 1 }, "duration": 160 },
42
+ { "type": "polygon", "points": [[123, "sombraY + projecao"], ["123 - 8 * projecao", "sombraY + 13 * projecao"], ["123 - 4 * projecao", "sombraY + 15 * projecao"], ["123 + 8 * projecao", "sombraY + 15 * projecao"], ["123 + 5 * projecao", "sombraY + 4 * projecao"]], "style": { "color": "#59616D", "fill": "#59616D", "fillStyle": "solid", "roughness": 0.7, "opacity": 0.8, "width": 1 }, "duration": 150 },
43
+ { "type": "ellipse", "center": [123, "sombraY + 18 * projecao"], "rx": "10 * projecao", "ry": "2 * projecao", "style": { "color": "#59616D", "fill": "#59616D", "fillStyle": "solid", "roughness": 0.5, "opacity": 0.8, "width": 1 }, "duration": 100 },
44
+ { "type": "text", "at": [120, 471], "text": "a parede", "anchor": "middle", "size": 25, "duration": 280, "style": { "color": "#5E5A52" } },
45
+
46
+ { "type": "ellipse", "center": [232, 427], "rx": 42, "ry": 9, "style": { "color": "#B7AA93", "fill": "#B7AA93", "fillStyle": "solid", "width": 1 }, "duration": 80 },
47
+ { "type": "polygon", "points": [[228, 362], [248, 369], [253, 397], [239, 407], [214, 398], [214, 380]], "style": { "color": "#4D5663", "fill": "#657489", "fillStyle": "solid", "width": 1.6 }, "duration": 130, "say": "As pessoas estão voltadas para a parede. Elas não veem a fonte de luz nem os objetos que passam atrás." },
48
+ { "type": "circle", "center": [231, 347], "r": 14, "style": { "color": "#4D5663", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.6 }, "duration": 110 },
49
+ { "type": "curve", "points": [[218, 343], [222, 332], [237, 333], [245, 341]], "style": { "color": "#4D5663", "width": 4 }, "duration": 90 },
50
+ { "type": "polyline", "points": [[217, 376], [198, 395], [219, 401]], "style": { "color": "#4D5663", "width": 6 }, "duration": 100 },
51
+ { "type": "polyline", "points": [[241, 400], [215, 414], [191, 421]], "style": { "color": "#4D5663", "width": 8 }, "duration": 100 },
52
+ { "type": "polyline", "points": [[228, 401], [241, 420], [220, 427]], "style": { "color": "#4D5663", "width": 7 }, "duration": 100 },
53
+ { "type": "line", "from": [218, 347], "to": [211, 351], "style": { "color": "#4D5663", "width": 1.8 }, "duration": 70 },
54
+
55
+ { "type": "ellipse", "center": [304, 446], "rx": 40, "ry": 8, "style": { "color": "#B7AA93", "fill": "#B7AA93", "fillStyle": "solid", "width": 1 }, "duration": 80 },
56
+ { "type": "polygon", "points": [[295, 377], [316, 382], [321, 415], [307, 424], [285, 416], [286, 394]], "style": { "color": "#4D5663", "fill": "#8B8A81", "fillStyle": "solid", "width": 1.6 }, "duration": 120 },
57
+ { "type": "circle", "center": [300, 361], "r": 14, "style": { "color": "#4D5663", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.6 }, "duration": 100 },
58
+ { "type": "curve", "points": [[288, 357], [291, 347], [305, 347], [313, 355]], "style": { "color": "#4D5663", "width": 4 }, "duration": 90 },
59
+ { "type": "polyline", "points": [[287, 394], [273, 413], [289, 419]], "style": { "color": "#4D5663", "width": 6 }, "duration": 100 },
60
+ { "type": "polyline", "points": [[307, 419], [282, 434], [264, 440]], "style": { "color": "#4D5663", "width": 8 }, "duration": 100 },
61
+ { "type": "polyline", "points": [[295, 420], [311, 438], [293, 445]], "style": { "color": "#4D5663", "width": 7 }, "duration": 100 },
62
+ { "type": "line", "from": [287, 361], "to": [280, 365], "style": { "color": "#4D5663", "width": 1.8 }, "duration": 60 },
63
+
64
+ { "type": "circle", "center": [253, 408], "r": 4, "style": { "color": "#8C8578", "width": 1.5 }, "duration": 70 },
65
+ { "type": "circle", "center": [260, 410], "r": 4, "style": { "color": "#8C8578", "width": 1.5 }, "duration": 70 },
66
+ { "type": "circle", "center": [267, 413], "r": 4, "style": { "color": "#8C8578", "width": 1.5 }, "duration": 70 },
67
+ { "type": "circle", "center": [274, 415], "r": 4, "style": { "color": "#8C8578", "width": 1.5 }, "duration": 70 },
68
+ { "type": "polygon", "points": [[180, 445], [194, 438], [206, 447], [202, 455], [183, 455]], "style": { "color": "#A69883", "fill": "#C6B79E", "fillStyle": "solid", "width": 1 }, "duration": 80 },
69
+ { "type": "text", "at": [290, 512], "text": "o único mundo que conhecem", "anchor": "middle", "size": 25, "duration": 350, "style": { "color": "#5E5A52" } },
70
+
71
+ { "type": "polygon", "chapter": "O que a parede não mostra", "points": [[555, 350], [174, "sombraY - 14 * projecao"], [66, "sombraY + 23 * projecao"]], "style": { "color": "#E9B952", "fill": "#F3CB62", "fillStyle": "solid", "opacity": 0.15, "width": 0, "roughness": 0 }, "duration": 200, "say": "Agora olhe por trás. O fogo ilumina uma estatueta. Entre a luz e a parede, ela produz a sombra que as pessoas enxergam." },
72
+ { "type": "polygon", "points": [[348, 375], [484, 368], [484, 424], [348, 432]], "style": { "color": "#928778", "fill": "#B8AD9C", "fillStyle": "solid", "width": 1.5 }, "duration": 140 },
73
+ { "type": "polygon", "points": [[344, 367], [480, 360], [491, 369], [349, 380]], "style": { "color": "#928778", "fill": "#D4C9B7", "fillStyle": "solid", "width": 1.3 }, "duration": 120 },
74
+ { "type": "polyline", "points": [[350, 401], [393, 398], [393, 427]], "style": { "color": "#A29583", "width": 1.3 }, "duration": 90 },
75
+ { "type": "polyline", "points": [[434, 374], [434, 396], [481, 393]], "style": { "color": "#A29583", "width": 1.3 }, "duration": 90 },
76
+ { "type": "line", "from": ["objeto", 347], "to": ["objeto", 389], "style": { "color": "#6B5845", "width": 3 }, "duration": 110 },
77
+ { "type": "circle", "id": "estatueta-cabeca", "center": ["objeto", 317], "r": 6, "style": { "color": "#6B5845", "fill": "#A88760", "fillStyle": "solid", "roughness": 0.5, "width": 1.5 }, "duration": 100 },
78
+ { "type": "polygon", "points": [["objeto", 323], ["objeto - 8", 335], ["objeto - 4", 337], ["objeto + 8", 337], ["objeto + 5", 326]], "style": { "color": "#6B5845", "fill": "#A88760", "fillStyle": "solid", "roughness": 0.5, "width": 1.3 }, "duration": 130 },
79
+ { "type": "ellipse", "center": ["objeto", 340], "rx": 10, "ry": 2, "style": { "color": "#6B5845", "fill": "#A88760", "fillStyle": "solid", "width": 1 }, "duration": 80 },
80
+ { "type": "ellipse", "center": [553, 424], "rx": 42, "ry": 9, "style": { "color": "#B9A58B", "fill": "#B9A58B", "fillStyle": "solid", "width": 1 }, "duration": 80 },
81
+ { "type": "polyline", "points": [[524, 417], [574, 409]], "style": { "color": "#795D43", "width": 8 }, "duration": 90 },
82
+ { "type": "polyline", "points": [[532, 407], [575, 420]], "style": { "color": "#795D43", "width": 7 }, "duration": 90 },
83
+ { "type": "polygon", "points": [[534, 404], [529, 389], [539, 372], [538, 355], [550, 366], [557, 340], [562, 367], [576, 384], [572, 405], [553, 412]], "style": { "color": "#B96C36", "fill": "#D98B45", "fillStyle": "solid", "roughness": 1, "width": 1.5 }, "duration": 220 },
84
+ { "type": "polygon", "points": [[543, 403], [541, 389], [552, 372], [555, 387], [564, 380], [566, 399], [556, 407]], "style": { "color": "#E9BC55", "fill": "#F1CE72", "fillStyle": "solid", "roughness": 0.8, "width": 1 }, "duration": 150 },
85
+ { "type": "curve", "points": [[546, 330], [542, 319], [546, 307]], "style": { "color": "#A69883", "width": 1.5, "opacity": 0.5 }, "duration": 120 },
86
+ { "type": "curve", "points": [[566, 326], [571, 313], [567, 301]], "style": { "color": "#A69883", "width": 1.5, "opacity": 0.5 }, "duration": 120 },
87
+ { "type": "text", "at": ["objeto + 14", 322], "text": "objeto", "anchor": "start", "size": 25, "duration": 260, "style": { "color": "#6B5845" } },
88
+ { "type": "text", "at": [553, 461], "text": "fogo", "anchor": "middle", "size": 25, "duration": 250, "style": { "color": "#9F6535" } },
89
+ { "type": "text", "at": [450, 566], "text": "a sombra é real. mas ela não é o objeto.", "anchor": "middle", "size": 34, "duration": 650, "say": "Mova a estatueta com o controle abaixo. A mesma coisa pode projetar uma sombra maior ou menor. O que aparece não conta toda a história." },
90
+
91
+ { "type": "circle", "chapter": "Reaprender a ver", "center": [782, 167], "r": 30, "style": { "color": "#C8A04F", "fill": "#F2D47B", "fillStyle": "solid", "roughness": 0.7, "width": 1.8 }, "duration": 200, "say": "Uma pessoa consegue sair. A luz primeiro ofusca: compreender de outro jeito exige tempo, não apenas trocar uma imagem por outra." },
92
+ { "type": "line", "from": [782, 124], "to": [782, 112], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
93
+ { "type": "line", "from": [822, 139], "to": [832, 132], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
94
+ { "type": "line", "from": [826, 173], "to": [840, 175], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
95
+ { "type": "line", "from": [815, 204], "to": [823, 214], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
96
+ { "type": "line", "from": [775, 209], "to": [774, 224], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
97
+ { "type": "line", "from": [740, 192], "to": [730, 200], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
98
+ { "type": "line", "from": [739, 154], "to": [725, 150], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
99
+ { "type": "line", "from": [757, 130], "to": [750, 118], "style": { "color": "#C8A04F", "width": 1.8 }, "duration": 70 },
100
+
101
+ { "type": "curve", "points": [[625, 445], [662, 432], [699, 411], [746, 411], [795, 441]], "style": { "color": "#BAAB8F", "width": 12, "roughness": 0.9 }, "duration": 150 },
102
+ { "type": "curve", "points": [[663, 435], [710, 425], [753, 427], [787, 447]], "style": { "color": "#F6E8C8", "width": 7, "roughness": 0.5 }, "duration": 120 },
103
+ { "type": "polygon", "points": [[686, 439], [692, 426], [699, 435], [703, 420], [711, 440]], "style": { "color": "#758563", "fill": "#8C9B75", "fillStyle": "solid", "width": 1 }, "duration": 90 },
104
+ { "type": "polygon", "points": [[822, 443], [827, 432], [832, 437], [838, 424], [843, 444]], "style": { "color": "#758563", "fill": "#8C9B75", "fillStyle": "solid", "width": 1 }, "duration": 90 },
105
+ { "type": "polygon", "points": [[815, 382], [820, 320], [828, 319], [827, 382]], "style": { "color": "#8B785C", "fill": "#AA9370", "fillStyle": "solid", "width": 1 }, "duration": 110 },
106
+ { "type": "line", "from": [824, 342], "to": [806, 326], "style": { "color": "#8B785C", "width": 3 }, "duration": 80 },
107
+ { "type": "ellipse", "center": [811, 303], "rx": 29, "ry": 24, "style": { "color": "#77885F", "fill": "#B1BD8E", "fillStyle": "solid", "width": 1.3 }, "duration": 120 },
108
+ { "type": "ellipse", "center": [834, 293], "rx": 22, "ry": 30, "style": { "color": "#77885F", "fill": "#A5B581", "fillStyle": "solid", "width": 1.3 }, "duration": 120 },
109
+ { "type": "ellipse", "center": [829, 319], "rx": 26, "ry": 19, "style": { "color": "#77885F", "fill": "#BCC79A", "fillStyle": "solid", "width": 1.3 }, "duration": 120 },
110
+ { "type": "ellipse", "center": [823, 383], "rx": 27, "ry": 5, "style": { "color": "#C6BDA0", "fill": "#C6BDA0", "fillStyle": "solid", "width": 1 }, "duration": 70 },
111
+
112
+ { "type": "ellipse", "center": [725, 418], "rx": 31, "ry": 6, "style": { "color": "#C6BDA0", "fill": "#C6BDA0", "fillStyle": "solid", "width": 1 }, "duration": 70 },
113
+ { "type": "polygon", "points": [[710, 345], [730, 344], [738, 378], [717, 383], [708, 365]], "style": { "color": "#4D5663", "fill": "#657489", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
114
+ { "type": "circle", "center": [720, 328], "r": 13, "style": { "color": "#4D5663", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.5 }, "duration": 100 },
115
+ { "type": "curve", "points": [[708, 326], [709, 315], [722, 315], [731, 321]], "style": { "color": "#4D5663", "width": 4 }, "duration": 90 },
116
+ { "type": "polyline", "points": [[717, 380], [714, 399], [703, 415]], "style": { "color": "#4D5663", "width": 7 }, "duration": 110 },
117
+ { "type": "polyline", "points": [[731, 379], [734, 399], [743, 414]], "style": { "color": "#4D5663", "width": 7 }, "duration": 110 },
118
+ { "type": "polyline", "points": [[730, 350], [743, 335], [735, 316]], "style": { "color": "#4D5663", "width": 5 }, "duration": 130 },
119
+ { "type": "line", "from": [735, 316], "to": [725, 311], "style": { "color": "#4D5663", "width": 4 }, "duration": 90 },
120
+ { "type": "polyline", "points": [[711, 352], [703, 369], [709, 377]], "style": { "color": "#4D5663", "width": 5 }, "duration": 110 },
121
+ { "type": "text", "at": [754, 512], "text": "a luz primeiro incomoda", "anchor": "middle", "size": 24, "duration": 350 },
122
+
123
+ { "type": "curve", "chapter": "Voltar com outra pergunta", "points": [[626, 466], [607, 498], [536, 504], [484, 490]], "style": { "color": "#657489", "width": 2, "dashed": true, "roughness": 0.8 }, "duration": 450, "say": "Na alegoria, quem aprende também retorna à caverna. O desafio não é só sair: é conversar com quem ainda vê apenas as sombras." },
124
+ { "type": "arrow", "from": [484, 490], "to": [465, 478], "style": { "color": "#657489", "width": 2 }, "duration": 150 },
125
+ { "type": "highlight", "from": [95, 600], "to": [806, 600], "thickness": 24, "duration": 300 },
126
+ { "type": "text", "at": [450, 608], "text": "o que está por trás daquilo que eu vejo?", "anchor": "middle", "size": 38, "duration": 750, "say": "Leve essa pergunta para uma notícia, uma foto ou uma certeza sua: o que produz essa aparência? O que eu ainda não consigo ver?" }
127
+ ]
128
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "id": "estoicismo-onibus",
3
+ "title": "Estoicismo ↔ esperando o ônibus",
4
+ "category": "Filosofia",
5
+ "width": 900,
6
+ "height": 660,
7
+ "background": "plain",
8
+ "learning": {
9
+ "goal": "Separar aquilo que depende de nossa escolha daquilo que não está inteiramente sob nosso controle.",
10
+ "question": "Uma entrevista foi adiada sem aviso. O que você pode escolher fazer, sem fingir que o problema não importa?",
11
+ "answer": "Você pode pedir informações, explicar o impacto, reorganizar a agenda ou contestar o tratamento. Não pode garantir a resposta da outra pessoa. Agir é diferente de controlar o resultado.",
12
+ "caveat": "A cena parte do Manual de Epicteto, §1. A distinção estoica não manda reprimir emoções, aceitar injustiças ou abandonar a ação coletiva. Nossos recursos e condições também limitam as ações possíveis."
13
+ },
14
+ "steps": [
15
+ { "type": "text", "chapter": "O imprevisto", "at": [40, 59], "text": "o ônibus atrasou. e agora?", "size": 44, "duration": 800, "say": "Você saiu no horário, mas o ônibus não chegou. O atraso é real. A irritação também. Vamos olhar para a situação." },
16
+ { "type": "polygon", "points": [[45, 431], [853, 431], [853, 493], [45, 493]], "style": { "color": "#D2CEC5", "fill": "#E6E2D9", "fillStyle": "solid", "width": 1 }, "duration": 100 },
17
+ { "type": "line", "from": [45, 444], "to": [853, 444], "style": { "color": "#B1AA9D", "width": 2 }, "duration": 100 },
18
+ { "type": "line", "from": [450, 475], "to": [830, 475], "style": { "color": "#B1AA9D", "width": 2, "dashed": true }, "duration": 100 },
19
+ { "type": "polygon", "points": [[60, 262], [333, 262], [355, 280], [48, 280]], "style": { "color": "#647581", "fill": "#91A3A9", "fillStyle": "solid", "width": 1.8 }, "duration": 150 },
20
+ { "type": "line", "from": [72, 280], "to": [72, 429], "style": { "color": "#647581", "width": 5 }, "duration": 110 },
21
+ { "type": "line", "from": [329, 280], "to": [329, 429], "style": { "color": "#647581", "width": 5 }, "duration": 110 },
22
+ { "type": "polygon", "points": [[87, 293], [190, 293], [190, 366], [87, 366]], "style": { "color": "#C1BEB4", "fill": "#ECE9DE", "fillStyle": "solid", "width": 1.3 }, "duration": 100 },
23
+ { "type": "polyline", "points": [[100, 350], [115, 325], [145, 337], [177, 309]], "style": { "color": "#7D9187", "width": 3 }, "duration": 150 },
24
+ { "type": "circle", "center": [100, 350], "r": 3, "style": { "color": "#607267", "fill": "#607267", "fillStyle": "solid", "width": 1 }, "duration": 60 },
25
+ { "type": "circle", "center": [177, 309], "r": 3, "style": { "color": "#607267", "fill": "#607267", "fillStyle": "solid", "width": 1 }, "duration": 60 },
26
+ { "type": "polygon", "points": [[94, 390], [185, 390], [194, 401], [87, 401]], "style": { "color": "#927C61", "fill": "#B99F7A", "fillStyle": "solid", "width": 1.4 }, "duration": 110 },
27
+ { "type": "line", "from": [104, 401], "to": [104, 427], "style": { "color": "#647581", "width": 4 }, "duration": 80 },
28
+ { "type": "line", "from": [177, 401], "to": [177, 427], "style": { "color": "#647581", "width": 4 }, "duration": 80 },
29
+ { "type": "line", "from": [53, 241], "to": [53, 428], "style": { "color": "#647581", "width": 4 }, "duration": 110 },
30
+ { "type": "circle", "center": [53, 221], "r": 20, "style": { "color": "#647581", "fill": "#DCE7E5", "fillStyle": "solid", "width": 2 }, "duration": 120 },
31
+ { "type": "polygon", "points": [[43, 210], [63, 210], [63, 228], [43, 228]], "style": { "color": "#647581", "width": 1.7, "roughness": 0.6 }, "duration": 100 },
32
+ { "type": "line", "from": [45, 218], "to": [61, 218], "style": { "color": "#647581", "width": 1.4 }, "duration": 60 },
33
+ { "type": "circle", "center": [46, 231], "r": 2, "style": { "color": "#647581", "fill": "#647581", "fillStyle": "solid" }, "duration": 60 },
34
+ { "type": "circle", "center": [60, 231], "r": 2, "style": { "color": "#647581", "fill": "#647581", "fillStyle": "solid" }, "duration": 60 },
35
+ { "type": "ellipse", "center": [252, 428], "rx": 32, "ry": 6, "style": { "color": "#C8C0B0", "fill": "#C8C0B0", "fillStyle": "solid", "width": 1 }, "duration": 70 },
36
+ { "type": "polygon", "points": [[240, 344], [263, 344], [273, 386], [237, 386]], "style": { "color": "#4D5663", "fill": "#A8AF95", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
37
+ { "type": "circle", "center": [252, 326], "r": 14, "style": { "color": "#4D5663", "fill": "#DBBB9D", "fillStyle": "solid", "roughness": 0.7, "width": 1.5 }, "duration": 100 },
38
+ { "type": "curve", "points": [[239, 322], [243, 312], [257, 312], [265, 322]], "style": { "color": "#4D5663", "width": 4 }, "duration": 90 },
39
+ { "type": "polyline", "points": [[246, 386], [243, 411], [234, 426]], "style": { "color": "#4D5663", "width": 7 }, "duration": 100 },
40
+ { "type": "polyline", "points": [[262, 386], [264, 412], [275, 426]], "style": { "color": "#4D5663", "width": 7 }, "duration": 100 },
41
+ { "type": "polyline", "points": [[240, 351], [227, 370], [237, 379]], "style": { "color": "#4D5663", "width": 5 }, "duration": 100 },
42
+ { "type": "polyline", "points": [[265, 352], [281, 366], [289, 353]], "style": { "color": "#4D5663", "width": 5 }, "duration": 100 },
43
+ { "type": "circle", "center": [300, 305], "r": 20, "style": { "color": "#8D8D82", "fill": "#FBF7EE", "fillStyle": "solid", "width": 1.6 }, "duration": 130, "say": "Você olha o relógio. Esperar mais um pouco já tem um custo: alguém está esperando por você." },
44
+ { "type": "polyline", "points": [[300, 292], [300, 305], [310, 311]], "style": { "color": "#647581", "width": 2 }, "duration": 100 },
45
+ { "type": "text", "at": [203, 483], "text": "o atraso e a irritação são reais.", "anchor": "middle", "size": 25, "duration": 450 },
46
+
47
+ { "type": "ellipse", "chapter": "O que não depende só de mim", "center": [613, 170], "rx": 48, "ry": 22, "style": { "color": "#9BA6AC", "fill": "#DEE2E2", "fillStyle": "solid", "width": 1.2 }, "duration": 120, "say": "O tempo, o trânsito e a decisão de outras pessoas não obedecem à sua vontade. Você pode tentar influenciar a situação, mas não garantir o resultado." },
48
+ { "type": "ellipse", "center": [654, 153], "rx": 35, "ry": 30, "style": { "color": "#9BA6AC", "fill": "#DEE2E2", "fillStyle": "solid", "width": 1.2 }, "duration": 110 },
49
+ { "type": "ellipse", "center": [692, 172], "rx": 41, "ry": 22, "style": { "color": "#9BA6AC", "fill": "#DEE2E2", "fillStyle": "solid", "width": 1.2 }, "duration": 110 },
50
+ { "type": "line", "from": [593, 207], "to": [585, 224], "style": { "color": "#9BA6AC", "width": 2 }, "duration": 70 },
51
+ { "type": "line", "from": [626, 212], "to": [618, 229], "style": { "color": "#9BA6AC", "width": 2 }, "duration": 70 },
52
+ { "type": "line", "from": [661, 207], "to": [653, 224], "style": { "color": "#9BA6AC", "width": 2 }, "duration": 70 },
53
+ { "type": "line", "from": [696, 212], "to": [688, 229], "style": { "color": "#9BA6AC", "width": 2 }, "duration": 70 },
54
+ { "type": "line", "from": [722, 203], "to": [714, 220], "style": { "color": "#9BA6AC", "width": 2 }, "duration": 70 },
55
+ { "type": "ellipse", "center": [676, 439], "rx": 151, "ry": 11, "style": { "color": "#C5BFB2", "fill": "#C5BFB2", "fillStyle": "solid", "width": 1 }, "duration": 100 },
56
+ { "type": "polygon", "points": [[531, 329], [543, 285], [786, 285], [816, 319], [816, 418], [531, 418]], "style": { "color": "#73858A", "fill": "#A8B9B8", "fillStyle": "solid", "width": 2 }, "duration": 200 },
57
+ { "type": "polygon", "points": [[547, 301], [593, 301], [593, 348], [538, 348]], "style": { "color": "#73858A", "fill": "#E7E9DE", "fillStyle": "solid", "width": 1.3 }, "duration": 100 },
58
+ { "type": "polygon", "points": [[605, 301], [655, 301], [655, 348], [605, 348]], "style": { "color": "#73858A", "fill": "#E7E9DE", "fillStyle": "solid", "width": 1.3 }, "duration": 100 },
59
+ { "type": "polygon", "points": [[668, 301], [718, 301], [718, 348], [668, 348]], "style": { "color": "#73858A", "fill": "#E7E9DE", "fillStyle": "solid", "width": 1.3 }, "duration": 100 },
60
+ { "type": "polygon", "points": [[731, 302], [778, 302], [797, 323], [797, 392], [731, 392]], "style": { "color": "#73858A", "fill": "#DCE3DC", "fillStyle": "solid", "width": 1.3 }, "duration": 130 },
61
+ { "type": "line", "from": [763, 306], "to": [763, 391], "style": { "color": "#73858A", "width": 1.5 }, "duration": 90 },
62
+ { "type": "line", "from": [538, 367], "to": [724, 367], "style": { "color": "#E5E5D3", "width": 9 }, "duration": 100 },
63
+ { "type": "circle", "center": [580, 420], "r": 21, "style": { "color": "#4D5663", "fill": "#4D5663", "fillStyle": "solid", "width": 1.4 }, "duration": 110 },
64
+ { "type": "circle", "center": [580, 420], "r": 10, "style": { "color": "#B2B4AB", "fill": "#B2B4AB", "fillStyle": "solid", "width": 1 }, "duration": 80 },
65
+ { "type": "circle", "center": [765, 420], "r": 21, "style": { "color": "#4D5663", "fill": "#4D5663", "fillStyle": "solid", "width": 1.4 }, "duration": 110 },
66
+ { "type": "circle", "center": [765, 420], "r": 10, "style": { "color": "#B2B4AB", "fill": "#B2B4AB", "fillStyle": "solid", "width": 1 }, "duration": 80 },
67
+ { "type": "text", "at": [668, 469], "text": "não controlo a chegada.", "anchor": "middle", "size": 22, "duration": 400, "style": { "color": "#5E5A52" } },
68
+
69
+ { "type": "polygon", "chapter": "Uma ação possível", "points": [[286, 333], [306, 331], [310, 359], [289, 362]], "style": { "color": "#46638B", "fill": "#6C8AAC", "fillStyle": "solid", "width": 1.4 }, "duration": 130, "say": "O celular está na sua mão. Uma ação possível é avisar do atraso. Outra é consultar uma rota alternativa ou cobrar informação sobre o serviço." },
70
+ { "type": "polygon", "points": [[290, 337], [302, 336], [305, 353], [292, 355]], "style": { "color": "#C9DFD8", "fill": "#DDEDE3", "fillStyle": "solid", "width": 1 }, "duration": 100 },
71
+ { "type": "line", "from": [294, 342], "to": [301, 341], "style": { "color": "#46638B", "width": 1.3 }, "duration": 80 },
72
+ { "type": "line", "from": [294, 347], "to": [302, 346], "style": { "color": "#46638B", "width": 1.3 }, "duration": 80 },
73
+ { "type": "highlight", "from": [206, 536], "to": [692, 536], "thickness": 19, "duration": 200 },
74
+ { "type": "text", "at": [450, 544], "text": "posso avisar, buscar uma rota, pedir informação.", "anchor": "middle", "size": 31, "duration": 600 },
75
+ { "type": "highlight", "chapter": "Agir sem controlar tudo", "from": [91, 603], "to": [811, 603], "thickness": 24, "duration": 250 },
76
+ { "type": "text", "at": [450, 611], "text": "agir com cuidado não é mandar no resultado.", "anchor": "middle", "size": 36, "duration": 700, "say": "Epicteto não pede que você finja que não sente nada. A distinção ajuda a escolher como agir, sem prometer controle sobre tudo. Você ainda pode reclamar e cobrar melhorias." }
77
+ ]
78
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "id": "arquitetura-fila",
3
+ "title": "Filas ↔ uma manhã na cafeteria",
4
+ "category": "Sistemas",
5
+ "width": 900,
6
+ "height": 660,
7
+ "background": "plain",
8
+ "params": {
9
+ "chegadas": { "label": "pedidos que chegam por minuto", "min": 1, "max": 8, "step": 1, "value": 6 },
10
+ "capacidade": { "label": "pedidos atendidos por minuto", "min": 1, "max": 8, "step": 1, "value": 3 }
11
+ },
12
+ "vars": {
13
+ "saldo": "chegadas - capacidade",
14
+ "acumulo": "max(0, saldo)",
15
+ "t0": "min(1, max(0, acumulo))",
16
+ "t1": "min(1, max(0, acumulo - 1))",
17
+ "t2": "min(1, max(0, acumulo - 2))",
18
+ "t3": "min(1, max(0, acumulo - 3))",
19
+ "t4": "min(1, max(0, acumulo - 4))",
20
+ "t5": "min(1, max(0, acumulo - 5))",
21
+ "t6": "min(1, max(0, acumulo - 6))"
22
+ },
23
+ "learning": {
24
+ "goal": "Entender que uma fila desacopla chegada e processamento, mas não cria capacidade.",
25
+ "question": "Se um consumidor concluir uma tarefa, mas falhar antes de confirmar a mensagem, o que pode acontecer?",
26
+ "answer": "Em uma fila com entrega pelo menos uma vez, a mensagem pode reaparecer. O consumidor precisa tolerar repetição, por exemplo registrando uma chave da operação e evitando aplicar o mesmo efeito duas vezes.",
27
+ "caveat": "Cada comanda pendurada representa um pedido ainda esperando após um minuto: o desenho mostra um saldo simplificado, não uma simulação em tempo real. Ele assume taxas constantes, fila inicialmente vazia e ausência de falhas. O exemplo adota entrega pelo menos uma vez; ordenação e novas tentativas variam por tecnologia. Receber um protocolo não significa concluir o trabalho."
28
+ },
29
+ "steps": [
30
+ { "type": "text", "chapter": "O pedido chega ao caixa", "at": [40, 59], "text": "pedir um café não é o mesmo que preparar um café.", "size": 39, "duration": 850, "say": "Numa manhã cheia, o caixa anota pedidos enquanto o barista prepara os cafés. Uma coisa não precisa esperar a outra terminar." },
31
+ { "type": "line", "from": [46, 448], "to": [852, 448], "style": { "color": "#C6BAA5", "width": 2 }, "duration": 100 },
32
+ { "type": "polygon", "points": [[52, 415], [840, 415], [840, 439], [52, 439]], "style": { "color": "#AA9270", "fill": "#CBB58F", "fillStyle": "solid", "width": 1.6 }, "duration": 130 },
33
+ { "type": "polygon", "points": [[45, 400], [837, 400], [852, 414], [52, 420]], "style": { "color": "#AA9270", "fill": "#E2D0AC", "fillStyle": "solid", "width": 1.5 }, "duration": 130 },
34
+ { "type": "line", "from": [174, 421], "to": [174, 437], "style": { "color": "#B29A77", "width": 1.2 }, "duration": 70 },
35
+ { "type": "line", "from": [352, 421], "to": [352, 437], "style": { "color": "#B29A77", "width": 1.2 }, "duration": 70 },
36
+ { "type": "line", "from": [530, 421], "to": [530, 437], "style": { "color": "#B29A77", "width": 1.2 }, "duration": 70 },
37
+ { "type": "line", "from": [708, 421], "to": [708, 437], "style": { "color": "#B29A77", "width": 1.2 }, "duration": 70 },
38
+ { "type": "polygon", "points": [[122, 337], [148, 337], [160, 394], [111, 394]], "style": { "color": "#59616D", "fill": "#94A89E", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
39
+ { "type": "circle", "center": [135, 318], "r": 14, "style": { "color": "#59616D", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.5 }, "duration": 100 },
40
+ { "type": "curve", "points": [[123, 314], [126, 303], [140, 303], [148, 313]], "style": { "color": "#59616D", "width": 4 }, "duration": 90 },
41
+ { "type": "polygon", "points": [[124, 357], [146, 357], [152, 391], [117, 391]], "style": { "color": "#748577", "fill": "#D1DAC2", "fillStyle": "solid", "width": 1.1 }, "duration": 100 },
42
+ { "type": "polyline", "points": [[147, 344], [166, 363], [186, 351]], "style": { "color": "#59616D", "width": 5 }, "duration": 110 },
43
+ { "type": "polygon", "points": [[180, 375], [239, 375], [252, 398], [173, 398]], "style": { "color": "#667B7C", "fill": "#99B0AC", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
44
+ { "type": "polygon", "points": [[190, 313], [236, 313], [241, 349], [194, 349]], "style": { "color": "#667B7C", "fill": "#99B0AC", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
45
+ { "type": "polygon", "points": [[197, 320], [230, 320], [234, 342], [200, 342]], "style": { "color": "#667B7C", "fill": "#E2EBDA", "fillStyle": "solid", "width": 1 }, "duration": 100 },
46
+ { "type": "line", "from": [210, 351], "to": [210, 376], "style": { "color": "#667B7C", "width": 5 }, "duration": 80 },
47
+ { "type": "line", "from": [205, 329], "to": [225, 329], "style": { "color": "#8DA08A", "width": 2 }, "duration": 80 },
48
+ { "type": "line", "from": [205, 335], "to": [218, 335], "style": { "color": "#8DA08A", "width": 1.5 }, "duration": 80 },
49
+ { "type": "line", "from": [185, 385], "to": [235, 385], "style": { "color": "#6B8580", "width": 2, "dashed": true }, "duration": 100 },
50
+ { "type": "text", "at": [154, 475], "text": "caixa: recebe o pedido", "anchor": "middle", "size": 24, "duration": 350, "style": { "color": "#46638B" } },
51
+
52
+ { "type": "arrow", "chapter": "As comandas esperam", "from": [245, 306], "to": [292, 256], "style": { "color": "#46638B", "width": 2.2 }, "duration": 300, "say": "O caixa é o produtor. Ele envia o trabalho para uma fila. Aqui, cada comanda pendurada será um pedido ainda esperando após um minuto." },
53
+ { "type": "text", "at": [462, 190], "text": "comandas pendentes", "anchor": "middle", "size": 27, "duration": 350 },
54
+ { "type": "polygon", "points": [[295, 211], [628, 211], [628, 223], [295, 223]], "style": { "color": "#9F8B65", "fill": "#BEA77E", "fillStyle": "solid", "width": 1.4 }, "duration": 110 },
55
+
56
+ { "type": "polygon", "id": "comanda-1", "points": [[310, 223], [342, 223], [342, "223 + 60*t0"], [326, "223 + 54*t0"], [310, "223 + 60*t0"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
57
+ { "type": "line", "from": [316, "223 + 14*t0"], "to": [336, "223 + 14*t0"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
58
+ { "type": "line", "from": [316, "223 + 24*t0"], "to": [332, "223 + 24*t0"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
59
+ { "type": "polygon", "id": "comanda-2", "points": [[354, 223], [386, 223], [386, "223 + 60*t1"], [370, "223 + 54*t1"], [354, "223 + 60*t1"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
60
+ { "type": "line", "from": [360, "223 + 14*t1"], "to": [380, "223 + 14*t1"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
61
+ { "type": "line", "from": [360, "223 + 24*t1"], "to": [376, "223 + 24*t1"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
62
+ { "type": "polygon", "id": "comanda-3", "points": [[398, 223], [430, 223], [430, "223 + 60*t2"], [414, "223 + 54*t2"], [398, "223 + 60*t2"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
63
+ { "type": "line", "from": [404, "223 + 14*t2"], "to": [424, "223 + 14*t2"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
64
+ { "type": "line", "from": [404, "223 + 24*t2"], "to": [420, "223 + 24*t2"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
65
+ { "type": "polygon", "id": "comanda-4", "points": [[442, 223], [474, 223], [474, "223 + 60*t3"], [458, "223 + 54*t3"], [442, "223 + 60*t3"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
66
+ { "type": "line", "from": [448, "223 + 14*t3"], "to": [468, "223 + 14*t3"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
67
+ { "type": "line", "from": [448, "223 + 24*t3"], "to": [464, "223 + 24*t3"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
68
+ { "type": "polygon", "id": "comanda-5", "points": [[486, 223], [518, 223], [518, "223 + 60*t4"], [502, "223 + 54*t4"], [486, "223 + 60*t4"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
69
+ { "type": "line", "from": [492, "223 + 14*t4"], "to": [512, "223 + 14*t4"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
70
+ { "type": "line", "from": [492, "223 + 24*t4"], "to": [508, "223 + 24*t4"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
71
+ { "type": "polygon", "id": "comanda-6", "points": [[530, 223], [562, 223], [562, "223 + 60*t5"], [546, "223 + 54*t5"], [530, "223 + 60*t5"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
72
+ { "type": "line", "from": [536, "223 + 14*t5"], "to": [556, "223 + 14*t5"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
73
+ { "type": "line", "from": [536, "223 + 24*t5"], "to": [552, "223 + 24*t5"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
74
+ { "type": "polygon", "id": "comanda-7", "points": [[574, 223], [606, 223], [606, "223 + 60*t6"], [590, "223 + 54*t6"], [574, "223 + 60*t6"]], "style": { "color": "#9F8B65", "fill": "#FFF4D8", "fillStyle": "solid", "width": 1.2, "roughness": 0.4 }, "duration": 100 },
75
+ { "type": "line", "from": [580, "223 + 14*t6"], "to": [600, "223 + 14*t6"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
76
+ { "type": "line", "from": [580, "223 + 24*t6"], "to": [596, "223 + 24*t6"], "style": { "color": "#9F8B65", "width": 1.2, "roughness": 0.4 }, "duration": 60 },
77
+
78
+ { "type": "arrow", "chapter": "O preparo tem um ritmo", "from": [633, 256], "to": [684, 306], "style": { "color": "#617C61", "width": 2.2 }, "duration": 300, "say": "O barista é o consumidor. Ele pega uma comanda, prepara o café e confirma o trabalho. A fila separa o ritmo de chegada do ritmo de preparo." },
79
+ { "type": "polygon", "points": [[649, 337], [675, 337], [688, 394], [639, 394]], "style": { "color": "#59616D", "fill": "#A7AA8C", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
80
+ { "type": "circle", "center": [662, 318], "r": 14, "style": { "color": "#59616D", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.5 }, "duration": 100 },
81
+ { "type": "curve", "points": [[650, 314], [653, 303], [667, 303], [675, 313]], "style": { "color": "#59616D", "width": 4 }, "duration": 90 },
82
+ { "type": "polygon", "points": [[652, 356], [675, 356], [680, 391], [645, 391]], "style": { "color": "#748577", "fill": "#D1DAC2", "fillStyle": "solid", "width": 1.1 }, "duration": 100 },
83
+ { "type": "polyline", "points": [[675, 344], [701, 366], [725, 355]], "style": { "color": "#59616D", "width": 5 }, "duration": 110 },
84
+ { "type": "polygon", "points": [[728, 299], [815, 299], [831, 314], [831, 393], [728, 393]], "style": { "color": "#647B7A", "fill": "#9EAFAB", "fillStyle": "solid", "width": 1.6 }, "duration": 140 },
85
+ { "type": "polygon", "points": [[728, 293], [815, 293], [831, 307], [742, 307]], "style": { "color": "#647B7A", "fill": "#C5CEBD", "fillStyle": "solid", "width": 1.3 }, "duration": 110 },
86
+ { "type": "polygon", "points": [[738, 337], [820, 337], [820, 383], [738, 383]], "style": { "color": "#647B7A", "fill": "#E1E4D4", "fillStyle": "solid", "width": 1.2 }, "duration": 110 },
87
+ { "type": "circle", "center": [751, 320], "r": 5, "style": { "color": "#647B7A", "fill": "#E1E4D4", "fillStyle": "solid", "width": 1.2 }, "duration": 70 },
88
+ { "type": "circle", "center": [772, 320], "r": 5, "style": { "color": "#647B7A", "fill": "#E1E4D4", "fillStyle": "solid", "width": 1.2 }, "duration": 70 },
89
+ { "type": "line", "from": [769, 338], "to": [769, 352], "style": { "color": "#647B7A", "width": 4 }, "duration": 80 },
90
+ { "type": "line", "from": [754, 346], "to": [777, 346], "style": { "color": "#647B7A", "width": 4 }, "duration": 80 },
91
+ { "type": "polygon", "points": [[756, 360], [784, 360], [781, 379], [761, 379]], "style": { "color": "#9F8B65", "fill": "#FAF2DF", "fillStyle": "solid", "width": 1.3 }, "duration": 110 },
92
+ { "type": "curve", "points": [[784, 364], [791, 365], [790, 371], [783, 374]], "style": { "color": "#9F8B65", "width": 1.8 }, "duration": 100 },
93
+ { "type": "line", "from": [741, 388], "to": [818, 388], "style": { "color": "#647B7A", "width": 2, "dashed": true }, "duration": 90 },
94
+ { "type": "curve", "points": [[796, 282], [791, 269], [796, 257]], "style": { "color": "#A1A89A", "width": 1.6 }, "duration": 110 },
95
+ { "type": "curve", "points": [[811, 281], [816, 267], [811, 254]], "style": { "color": "#A1A89A", "width": 1.6 }, "duration": 110 },
96
+ { "type": "text", "at": [740, 475], "text": "barista: faz e confirma", "anchor": "middle", "size": 24, "duration": 350, "style": { "color": "#617C61" } },
97
+ { "type": "text", "at": [450, 520], "text": "chegam {chegadas}/min · capacidade: {capacidade}/min", "anchor": "middle", "size": 30, "duration": 450, "say": "Mexa nos valores abaixo. Mais pedidos que capacidade? Sobram comandas penduradas. Com capacidade suficiente, o saldo pendente fica em zero." },
98
+ { "type": "text", "at": [450, 561], "text": "após 1 min: {acumulo} pedido(s) ainda esperando", "anchor": "middle", "size": 28, "duration": 450 },
99
+ { "type": "highlight", "chapter": "A fila não prepara café", "from": [65, 611], "to": [800, 611], "thickness": 24, "duration": 250 },
100
+ { "type": "text", "at": [450, 619], "text": "a fila absorve picos. ela não prepara mais café.", "anchor": "middle", "size": 35, "duration": 700, "say": "Uma fila compra tempo para absorver um pico, mas não resolve sobrecarga contínua. E, se uma comanda for entregue de novo após uma falha, o sistema precisa evitar cobrar ou preparar o mesmo pedido duas vezes." }
101
+ ]
102
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "id": "juros-fatura",
3
+ "title": "Juros compostos ↔ a fatura",
4
+ "category": "Matemática",
5
+ "width": 900,
6
+ "height": 560,
7
+ "background": "grid",
8
+ "params": {
9
+ "taxa": { "label": "juros do rotativo (% ao mês)", "min": 1, "max": 15, "step": 1, "value": 12 }
10
+ },
11
+ "vars": {
12
+ "r": "taxa / 100",
13
+ "bx": 90,
14
+ "by": 130,
15
+ "bw": 720,
16
+ "bh": 340,
17
+ "ymax": 6000,
18
+ "final": "1000 * (1 + r)^12",
19
+ "simples": "1000 * (1 + r*12)",
20
+ "ex": "bx + bw",
21
+ "ey": "by + bh - min(final, ymax) / ymax * bh"
22
+ },
23
+ "steps": [
24
+ { "type": "text", "at": [40, 72], "text": "por que a dívida do rotativo dispara?", "size": 42,
25
+ "say": "Você deixou R$ 1.000 no rotativo do cartão. O que acontece em um ano?" },
26
+
27
+ { "type": "polyline", "points": [["bx", "by - 10"], ["bx", "by + bh"], ["bx + bw + 10", "by + bh"]],
28
+ "style": { "width": 2.4 } },
29
+ { "type": "text", "at": ["bx + bw", "by + bh + 38"], "text": "meses →", "anchor": "end", "size": 26, "style": { "color": "#5E5A52" } },
30
+ { "type": "text", "at": ["bx - 10", "by - 18"], "text": "R$", "anchor": "middle", "size": 26, "style": { "color": "#5E5A52" } },
31
+
32
+ { "type": "plot", "fn": "1000 * (1 + r*x)", "domain": [0, 12], "range": [0, "ymax"],
33
+ "box": { "x": "bx", "y": "by", "w": "bw", "h": "bh" },
34
+ "style": { "color": "#8C8578", "width": 2.4, "dashed": true },
35
+ "say": "Se os juros fossem simples, a dívida subiria em linha reta." },
36
+ { "type": "text", "at": ["ex - 8", "min(by + bh - min(simples, ymax)/ymax*bh + 62, by + bh - 8)"], "text": "simples: R$ {simples:0}", "anchor": "end",
37
+ "size": 26, "style": { "color": "#5E5A52" } },
38
+
39
+ { "type": "pause", "ms": 400 },
40
+
41
+ { "type": "plot", "fn": "1000 * (1 + r)^x", "domain": [0, 12], "range": [0, "ymax"],
42
+ "box": { "x": "bx", "y": "by", "w": "bw", "h": "bh" },
43
+ "style": { "color": "#C8442C", "width": 3.4 },
44
+ "say": "Mas no rotativo o juro cobra juro do juro. A curva entorta para cima." },
45
+
46
+ { "type": "arrow", "from": ["ex - 230", "ey - 48"], "to": ["ex - 10", "ey - 6"], "style": { "width": 2.4 } },
47
+ { "type": "text", "at": ["ex - 240", "ey - 56"], "text": "12 meses: R$ {final:0}", "anchor": "end", "size": 36,
48
+ "style": { "color": "#C8442C" },
49
+ "say": "Em doze meses, os mil reais viram isso. Mexa na taxa e veja a curva mudar." }
50
+ ]
51
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "id": "pitagoras-telhado",
3
+ "title": "Pitágoras ↔ o telhado",
4
+ "category": "Matemática",
5
+ "width": 900,
6
+ "height": 560,
7
+ "background": "dots",
8
+ "params": {
9
+ "a": { "label": "altura do topo (m)", "min": 1, "max": 6, "step": 1, "value": 3 },
10
+ "b": { "label": "meia largura da casa (m)", "min": 1, "max": 8, "step": 1, "value": 4 }
11
+ },
12
+ "vars": {
13
+ "k": 45,
14
+ "x0": 450,
15
+ "y0": 470,
16
+ "xl": "x0 - b*k",
17
+ "xb": "x0 + b*k",
18
+ "ya": "y0 - a*k",
19
+ "c": "sqrt(a^2 + b^2)"
20
+ },
21
+ "steps": [
22
+ { "type": "text", "at": [40, 72], "text": "por que a telha tem esse comprimento?", "size": 42,
23
+ "say": "Um telhado visto de frente. Quanto mede a telha, do topo até a beirada?" },
24
+
25
+ { "type": "polygon", "points": [["xl", "y0"], ["xb", "y0"], ["xb", "y0 + 52"], ["xl", "y0 + 52"]],
26
+ "style": { "color": "#8C8578", "width": 2, "fill": "#EDE5D6", "fillStyle": "hachure" },
27
+ "say": "Primeiro, a casa." },
28
+
29
+ { "type": "polyline", "points": [["xl", "y0"], ["x0", "ya"], ["xb", "y0"]],
30
+ "style": { "width": 3 }, "say": "Depois, o telhado." },
31
+
32
+ { "type": "pause", "ms": 400 },
33
+
34
+ { "type": "line", "from": ["x0", "ya"], "to": ["x0", "y0"],
35
+ "style": { "color": "#C8442C", "width": 3, "dashed": true },
36
+ "say": "Agora a altura, do topo até a laje. Vamos chamar de a." },
37
+ { "type": "text", "at": ["x0", "ya - 16"], "text": "a = {a} m", "anchor": "middle", "size": 32,
38
+ "style": { "color": "#C8442C" } },
39
+
40
+ { "type": "line", "from": ["x0", "y0"], "to": ["xb", "y0"],
41
+ "style": { "color": "#2F5BEA", "width": 3.4 },
42
+ "say": "E metade da largura da casa. Essa é a b." },
43
+ { "type": "text", "at": ["(x0 + xb)/2", "y0 + 80"], "text": "b = {b} m", "anchor": "middle", "size": 28,
44
+ "style": { "color": "#2F5BEA" } },
45
+
46
+ { "type": "polyline", "points": [["x0", "y0 - 20"], ["x0 + 20", "y0 - 20"], ["x0 + 20", "y0"]],
47
+ "style": { "width": 2 } },
48
+
49
+ { "type": "highlight", "from": ["x0", "ya"], "to": ["xb", "y0"], "thickness": 20,
50
+ "say": "Sobrou um triângulo retângulo. A telha é o lado maior dele." },
51
+ { "type": "line", "from": ["x0", "ya"], "to": ["xb", "y0"], "style": { "width": 4 } },
52
+ { "type": "text", "at": ["(x0 + xb)/2 + 18", "(ya + y0)/2 - 8"], "text": "telha = ?", "size": 34 },
53
+
54
+ { "type": "pause", "ms": 500 },
55
+
56
+ { "type": "text", "at": [620, 125], "text": "a² + b² = c²", "size": 50,
57
+ "say": "Pitágoras resolve: a ao quadrado mais b ao quadrado é igual a c ao quadrado." },
58
+ { "type": "text", "at": [620, 168], "text": "{a}² + {b}² = {a^2 + b^2}", "size": 32, "style": { "color": "#5E5A52" } },
59
+ { "type": "text", "at": [620, 222], "text": "telha ≈ {c:1} m", "size": 40,
60
+ "say": "Com só duas medidas, o pedreiro já sabe quanto cortar." },
61
+ { "type": "ellipse", "center": [742, 214], "rx": 138, "ry": 34,
62
+ "style": { "color": "#C8442C", "width": 2.6, "roughness": 1.6 } }
63
+ ]
64
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "id": "arquitetura-requisicao",
3
+ "title": "Arquitetura web ↔ da mesa à cozinha",
4
+ "category": "Sistemas",
5
+ "width": 900,
6
+ "height": 660,
7
+ "background": "plain",
8
+ "learning": {
9
+ "goal": "Acompanhar uma requisição e distinguir interface, regras do serviço e persistência.",
10
+ "question": "Se alguém esconder o botão de editar no navegador, os dados já estão protegidos? Onde a permissão deve ser conferida?",
11
+ "answer": "Não. O cliente pode ser modificado ou ignorado. O serviço precisa autenticar, autorizar e validar cada operação antes de acessar os dados. Esconder o botão só melhora a interface.",
12
+ "caveat": "O restaurante ajuda a separar responsabilidades, mas pessoas não são servidores. Este é um exemplo cliente–API–banco: sistemas reais podem ter caches, múltiplos serviços e outros fluxos. As setas indicam chamadas lógicas, não uma topologia de rede."
13
+ },
14
+ "steps": [
15
+ { "type": "text", "chapter": "Um pedido na mesa", "at": [40, 59], "text": "clicou em salvar. por onde passa o pedido?", "size": 41, "duration": 850, "say": "Num restaurante, escolher no cardápio não faz a comida aparecer. Vamos acompanhar o pedido da mesa até a cozinha e o registro." },
16
+ { "type": "line", "from": [45, 444], "to": [852, 444], "style": { "color": "#C6BAA5", "width": 2 }, "duration": 110 },
17
+ { "type": "ellipse", "center": [171, 437], "rx": 117, "ry": 8, "style": { "color": "#E1D9CA", "fill": "#E1D9CA", "fillStyle": "solid", "width": 1 }, "duration": 100 },
18
+ { "type": "polygon", "points": [[72, 348], [101, 348], [101, 405], [72, 405]], "style": { "color": "#9A8261", "fill": "#C5AD86", "fillStyle": "solid", "width": 1.4 }, "duration": 100 },
19
+ { "type": "line", "from": [77, 403], "to": [77, 436], "style": { "color": "#9A8261", "width": 5 }, "duration": 80 },
20
+ { "type": "line", "from": [97, 404], "to": [97, 436], "style": { "color": "#9A8261", "width": 5 }, "duration": 80 },
21
+ { "type": "polygon", "points": [[100, 324], [120, 326], [132, 369], [101, 378], [92, 350]], "style": { "color": "#4D5663", "fill": "#8FA6AD", "fillStyle": "solid", "width": 1.5 }, "duration": 120 },
22
+ { "type": "circle", "center": [107, 306], "r": 14, "style": { "color": "#4D5663", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.5 }, "duration": 100 },
23
+ { "type": "curve", "points": [[95, 303], [98, 292], [113, 293], [120, 300]], "style": { "color": "#4D5663", "width": 4 }, "duration": 80 },
24
+ { "type": "polyline", "points": [[119, 373], [142, 393], [139, 429]], "style": { "color": "#4D5663", "width": 7 }, "duration": 100 },
25
+ { "type": "polyline", "points": [[105, 376], [117, 402], [113, 431]], "style": { "color": "#4D5663", "width": 7 }, "duration": 100 },
26
+ { "type": "polyline", "points": [[120, 335], [140, 347], [157, 326]], "style": { "color": "#4D5663", "width": 5 }, "duration": 100 },
27
+ { "type": "line", "from": [168, 357], "to": [165, 436], "style": { "color": "#9A8261", "width": 5 }, "duration": 90 },
28
+ { "type": "line", "from": [259, 357], "to": [267, 436], "style": { "color": "#9A8261", "width": 5 }, "duration": 90 },
29
+ { "type": "ellipse", "center": [214, 349], "rx": 79, "ry": 20, "style": { "color": "#A28B68", "fill": "#D8C29C", "fillStyle": "solid", "width": 1.6 }, "duration": 140 },
30
+ { "type": "ellipse", "center": [242, 346], "rx": 24, "ry": 8, "style": { "color": "#A8AAA0", "fill": "#F8F5E9", "fillStyle": "solid", "width": 1.2 }, "duration": 100 },
31
+ { "type": "ellipse", "center": [242, 346], "rx": 17, "ry": 5, "style": { "color": "#C4C5B7", "width": 1 }, "duration": 70 },
32
+ { "type": "polygon", "points": [[160, 287], [188, 298], [209, 287], [210, 334], [188, 344], [160, 331]], "style": { "color": "#667B7C", "fill": "#DCE6DC", "fillStyle": "solid", "width": 1.6 }, "duration": 140, "say": "O cardápio é a interface: mostra opções e recebe a intenção. A tela coleta o pedido, mas não decide sozinha se ele é permitido." },
33
+ { "type": "line", "from": [188, 301], "to": [188, 339], "style": { "color": "#667B7C", "width": 1.3 }, "duration": 80 },
34
+ { "type": "line", "from": [167, 307], "to": [181, 312], "style": { "color": "#8DA098", "width": 1.3 }, "duration": 70 },
35
+ { "type": "line", "from": [167, 317], "to": [181, 322], "style": { "color": "#8DA098", "width": 1.3 }, "duration": 70 },
36
+ { "type": "text", "at": [183, 487], "text": "interface: recebe a intenção", "anchor": "middle", "size": 24, "duration": 400, "style": { "color": "#46638B" } },
37
+
38
+ { "type": "arrow", "chapter": "Na cozinha: conferir e agir", "from": [259, 245], "to": [359, 245], "style": { "color": "#46638B", "width": 2.4 }, "duration": 350, "say": "A requisição leva o pedido ao serviço. Na nossa cozinha, antes de preparar, é preciso conferir se o pedido é válido e se pode ser atendido." },
39
+ { "type": "text", "at": [308, 221], "text": "pedido", "anchor": "middle", "size": 25, "duration": 300, "style": { "color": "#46638B" } },
40
+ { "type": "polygon", "points": [[281, 256], [316, 256], [316, 281], [281, 281]], "style": { "color": "#46638B", "fill": "#EDF0E6", "fillStyle": "solid", "width": 1.3 }, "duration": 100 },
41
+ { "type": "polyline", "points": [[282, 257], [299, 269], [315, 257]], "style": { "color": "#46638B", "width": 1.2 }, "duration": 100 },
42
+ { "type": "polygon", "points": [[355, 354], [593, 354], [593, 433], [355, 433]], "style": { "color": "#8F9184", "fill": "#D1D1BF", "fillStyle": "solid", "width": 1.5 }, "duration": 130 },
43
+ { "type": "polygon", "points": [[347, 346], [585, 346], [603, 358], [355, 365]], "style": { "color": "#8F9184", "fill": "#E8E6D7", "fillStyle": "solid", "width": 1.3 }, "duration": 100 },
44
+ { "type": "line", "from": [479, 368], "to": [479, 431], "style": { "color": "#A3A494", "width": 1.2 }, "duration": 80 },
45
+ { "type": "circle", "center": [465, 381], "r": 3, "style": { "color": "#8F9184", "fill": "#8F9184", "fillStyle": "solid" }, "duration": 70 },
46
+ { "type": "circle", "center": [493, 381], "r": 3, "style": { "color": "#8F9184", "fill": "#8F9184", "fillStyle": "solid" }, "duration": 70 },
47
+ { "type": "polygon", "points": [[409, 280], [439, 280], [454, 340], [398, 340]], "style": { "color": "#59616D", "fill": "#EEEBDD", "fillStyle": "solid", "width": 1.5 }, "duration": 130 },
48
+ { "type": "circle", "center": [425, 262], "r": 15, "style": { "color": "#59616D", "fill": "#D8B99B", "fillStyle": "solid", "roughness": 0.7, "width": 1.5 }, "duration": 110 },
49
+ { "type": "ellipse", "center": [411, 240], "rx": 11, "ry": 10, "style": { "color": "#A2A393", "fill": "#FAF7EC", "fillStyle": "solid", "width": 1.2 }, "duration": 100 },
50
+ { "type": "ellipse", "center": [425, 234], "rx": 13, "ry": 13, "style": { "color": "#A2A393", "fill": "#FAF7EC", "fillStyle": "solid", "width": 1.2 }, "duration": 100 },
51
+ { "type": "ellipse", "center": [439, 240], "rx": 11, "ry": 10, "style": { "color": "#A2A393", "fill": "#FAF7EC", "fillStyle": "solid", "width": 1.2 }, "duration": 100 },
52
+ { "type": "polygon", "points": [[411, 243], [439, 243], [438, 252], [411, 252]], "style": { "color": "#A2A393", "fill": "#FAF7EC", "fillStyle": "solid", "width": 1.1 }, "duration": 90 },
53
+ { "type": "polygon", "points": [[414, 297], [436, 297], [441, 336], [408, 336]], "style": { "color": "#A2A393", "fill": "#DCE1CF", "fillStyle": "solid", "width": 1.1 }, "duration": 100 },
54
+ { "type": "polyline", "points": [[440, 290], [467, 318], [495, 315]], "style": { "color": "#59616D", "width": 5 }, "duration": 120 },
55
+ { "type": "ellipse", "center": [541, 342], "rx": 32, "ry": 8, "style": { "color": "#677571", "fill": "#B9C2B2", "fillStyle": "solid", "width": 1.4 }, "duration": 110 },
56
+ { "type": "polygon", "points": [[510, 318], [572, 318], [565, 342], [517, 342]], "style": { "color": "#677571", "fill": "#BAC3B5", "fillStyle": "solid", "width": 1.4 }, "duration": 110 },
57
+ { "type": "ellipse", "center": [541, 316], "rx": 32, "ry": 7, "style": { "color": "#677571", "fill": "#E7E7D7", "fillStyle": "solid", "width": 1.2 }, "duration": 100 },
58
+ { "type": "line", "from": [571, 324], "to": [588, 318], "style": { "color": "#677571", "width": 4 }, "duration": 90 },
59
+ { "type": "curve", "points": [[529, 300], [523, 288], [529, 277]], "style": { "color": "#A1A89A", "width": 1.6 }, "duration": 110 },
60
+ { "type": "curve", "points": [[547, 297], [553, 285], [548, 272]], "style": { "color": "#A1A89A", "width": 1.6 }, "duration": 110 },
61
+ { "type": "polyline", "points": [[399, 411], [411, 422], [435, 396]], "style": { "color": "#617C61", "width": 4 }, "duration": 180, "say": "No sistema real, o serviço autentica, autoriza, valida os dados e aplica as regras. Só esconder uma opção na tela não protege a operação." },
62
+ { "type": "text", "at": [477, 487], "text": "serviço: confere e executa", "anchor": "middle", "size": 24, "duration": 400 },
63
+
64
+ { "type": "arrow", "chapter": "O registro que permanece", "from": [610, 245], "to": [688, 245], "style": { "color": "#46638B", "width": 2.4 }, "duration": 300, "say": "O serviço consulta ou grava o estado. O livro representa o banco de dados: o registro precisa continuar existindo depois que a conversa acaba." },
65
+ { "type": "text", "at": [649, 221], "text": "gravar", "anchor": "middle", "size": 25, "duration": 300, "style": { "color": "#46638B" } },
66
+ { "type": "ellipse", "center": [762, 429], "rx": 83, "ry": 9, "style": { "color": "#E1D9CA", "fill": "#E1D9CA", "fillStyle": "solid", "width": 1 }, "duration": 80 },
67
+ { "type": "line", "from": [704, 372], "to": [699, 430], "style": { "color": "#9A8261", "width": 5 }, "duration": 90 },
68
+ { "type": "line", "from": [824, 372], "to": [830, 430], "style": { "color": "#9A8261", "width": 5 }, "duration": 90 },
69
+ { "type": "polygon", "points": [[686, 361], [826, 361], [841, 375], [693, 380]], "style": { "color": "#A28B68", "fill": "#D8C29C", "fillStyle": "solid", "width": 1.3 }, "duration": 110 },
70
+ { "type": "polygon", "points": [[698, 307], [761, 320], [824, 306], [824, 355], [761, 368], [698, 354]], "style": { "color": "#7C8C72", "fill": "#AAB795", "fillStyle": "solid", "width": 1.5 }, "duration": 130 },
71
+ { "type": "polygon", "points": [[701, 298], [761, 310], [821, 297], [821, 346], [761, 359], [701, 345]], "style": { "color": "#B7B6A2", "fill": "#F6F0DB", "fillStyle": "solid", "width": 1.1 }, "duration": 120 },
72
+ { "type": "line", "from": [761, 312], "to": [761, 354], "style": { "color": "#B7B6A2", "width": 1.2 }, "duration": 100 },
73
+ { "type": "line", "from": [710, 314], "to": [749, 322], "style": { "color": "#A2A38D", "width": 1.3 }, "duration": 90 },
74
+ { "type": "line", "from": [710, 325], "to": [749, 333], "style": { "color": "#A2A38D", "width": 1.3 }, "duration": 90 },
75
+ { "type": "line", "from": [774, 322], "to": [811, 314], "style": { "color": "#A2A38D", "width": 1.3 }, "duration": 90 },
76
+ { "type": "line", "from": [774, 334], "to": [811, 326], "style": { "color": "#A2A38D", "width": 1.3 }, "duration": 90 },
77
+ { "type": "polygon", "points": [[783, 302], [791, 300], [791, 350], [787, 346], [783, 352]], "style": { "color": "#B78E65", "fill": "#CDA77A", "fillStyle": "solid", "width": 1 }, "duration": 100 },
78
+ { "type": "text", "at": [759, 487], "text": "banco: guarda o estado", "anchor": "middle", "size": 24, "duration": 400, "style": { "color": "#617C61" } },
79
+
80
+ { "type": "arrow", "chapter": "A resposta volta pelo serviço", "from": [744, 549], "to": [502, 549], "style": { "color": "#617C61", "width": 2.5 }, "duration": 350 },
81
+ { "type": "arrow", "from": [451, 549], "to": [192, 549], "style": { "color": "#617C61", "width": 2.5 }, "duration": 350, "say": "O resultado volta pelo serviço até a interface. Pode ser uma confirmação ou uma falha. A tela não deve prometer sucesso antes da resposta." },
82
+ { "type": "text", "at": [450, 528], "text": "resposta", "anchor": "middle", "size": 25, "duration": 300, "style": { "color": "#617C61" } },
83
+ { "type": "highlight", "from": [90, 611], "to": [808, 611], "thickness": 24, "duration": 250 },
84
+ { "type": "text", "at": [450, 619], "text": "a tela pede. o serviço decide. o banco guarda.", "anchor": "middle", "size": 35, "duration": 700 }
85
+ ]
86
+ }
@@ -0,0 +1,16 @@
1
+ <!-- GENERATED by tools/assets.mjs. Do not edit. -->
2
+ # Choosing the hook
3
+
4
+ The hook is the thing of everyday life the idea is tied to. It is the first thing drawn and the question the drawing
5
+ opens with.
6
+
7
+ A good hook:
8
+ - is something the audience has seen or done (a card bill, a bus stop, a café queue), not something they have to imagine;
9
+ - already holds the idea: taking it apart reveals the idea without forcing it;
10
+ - can be drawn with few shapes (a person, a house, a cart, a table);
11
+ - has a number in it only when the idea is about a number.
12
+
13
+ Offer up to three, best first, each with one line saying why it works for this audience. Avoid:
14
+ - hooks that need a fact the person may not know to make sense;
15
+ - hooks tied to one country, brand or person (unless the person asked);
16
+ - sad or delicate situations (illness, debt shame, accidents) unless the person brought them.
@@ -0,0 +1,17 @@
1
+ <!-- GENERATED by tools/assets.mjs. Do not edit. -->
2
+ # The six molds
3
+
4
+ Choose one before the outline and say which. The mold decides what the middle of the drawing is.
5
+
6
+ | Mold | When it fits | The middle of the drawing | A value to move? |
7
+ |---|---|---|---|
8
+ | Measure and formula | A quantity that follows a rule (interest, Pythagoras, speed) | The real thing measured, then the rule drawn over it | Yes: the quantity |
9
+ | Thought experiment | "What if…" ideas (Plato's cave, the trolley) | The situation, then the twist that shows the idea | Rarely |
10
+ | Argument | A claim with reasons and an objection (stoicism, a policy) | Premises → conclusion, the objection beside it | No |
11
+ | System | Parts that pass something along (a web request, a queue) | One request followed end to end, failures included | Sometimes: load, capacity |
12
+ | Timeline | Things that happen in order (a process, a history) | The line with the moments that matter | Rarely |
13
+ | Cycle | Something that comes back (water, a feedback loop) | The loop, and what speeds it up or slows it down | Sometimes |
14
+
15
+ Examples of each, already drawn, in references/examples/: the recipes (juros-fatura and pitagoras-telhado are measure and formula;
16
+ platao-caverna is a thought experiment; estoicismo-onibus is an argument; arquitetura-requisicao and arquitetura-fila
17
+ are systems).
@@ -0,0 +1,81 @@
1
+ <!-- GENERATED by tools/assets.mjs. Do not edit. -->
2
+ # Writing the recipe
3
+
4
+ You write hand-drawn visual explanations. The player draws them, stroke by stroke, with a pencil following the line, from a **recipe** in JSON. Your job is to write that recipe for the topic the person asked. Every text the person reads (title, texts on the drawing, narration, learning, labels) is in the person's language, conversational, short and without jargon. Facts and numbers must be correct.
5
+
6
+ ## The shape of every scene
7
+
8
+ 1. **A real-world hook**: a question about something concrete ("why is the roof tile this long?").
9
+ 2. **Take it apart**: draw the real thing simply and reveal, step by step, the shape or pattern hidden in it.
10
+ 3. **The idea**: a relation, a distinction, an argument, a flow or a formula. Do not force numbers where they do not help.
11
+ 4. **The close**: a highlight (a circle, an arrow, the yellow marker) on the result, tying it back to the real thing.
12
+
13
+ Put `chapter` (a short title) on the first step of each of these four parts. `say` is one short sentence of narration when an idea changes; do not narrate every detail.
14
+
15
+ ## Beyond maths
16
+
17
+ - **Philosophy**: start from a lived situation, make the question explicit, compare ideas and give a situated reading (author and work, when it applies). Say in the narration what an arrow means ("produces", "contrasts", "leads to ask"). Never give percentages to beliefs nor treat an analogy as proof. Include an objection or a limit.
18
+ - **Systems architecture**: give a concrete scenario, separate responsibilities and follow a request or message. Name the parts and say what each arrow means, including answers and failures. Say what was simplified. Never promise exactly-once delivery, global order or endless queue capacity.
19
+ - Controls (`params`) only when there is a real quantity and a checkable relation; a scene with no `params` is fine. Prefer drawing the idea over boxes of text: use `card` only for a concept that really is a labelled box.
20
+ - `theme`: "chalkboard" draws in chalk on a green board (good for a class); "paper" by default.
21
+ - `defs` (optional): figures the recipe makes itself, when no prop is the thing — {"name": {"shapes": [...]}}, up to 8, each up to 24 shapes. A shape is {"kind": "polygon" | "polyline" | "curve", "points": [[x, y], ...], "fill": "#hex"} or {"kind": "circle", "c": [x, y], "r": n, "fill": ...}, on a grid of **whole numbers from 0 to 100**, y down like the paper; the figure stands at 50,100 (its bottom centre). The name (lowercase, not a prop's or a step's) is then used as a prop. E.g. {"xicara": {"shapes": [{"kind": "polygon", "points": [[24,48],[76,48],[70,90],[30,90]], "fill": "#FFFFFF"}, {"kind": "curve", "points": [[74,56],[90,62],[72,80]]}]}}.
22
+ - `kind` (optional): what the drawing is for — `lesson` (the default; what you write here), or `pitch`, `arch`, `decision`, `data`, `timeline`, `story` (back-of-napkin's drawings). `source` (optional): where the numbers come from, expected in a `data` drawing.
23
+ - `quiz` (optional, for a class; only a `lesson` has one): {"question", "options": [{"text", "right": true or false, "why"}]} — 2 to 4 options, exactly one right, each `why` one short sentence. It appears when the last chapter is drawn.
24
+ - Always include `learning`: `goal` (what the person will understand), `question` (a new situation to think through with the idea), `answer` (one grounded possibility and its reasoning, not a correction of opinion) and `caveat` (where the analogy stops working, what was simplified, the reference when there is one).
25
+
26
+ ## Format
27
+
28
+ {"id": "slug", "title": "Idea ↔ thing of the world", "width": 900, "height": 560, "background": "dots" | "grid" | "plain", "category": "the area, in the person's language", "theme": "paper" | "chalkboard", "locale": "the tag of the texts' language, e.g. pt-BR or en-US", "learning": {"goal": "...", "question": "...", "answer": "...", "caveat": "..."}, "params": {"a": {"label": "...", "min": 1, "max": 6, "step": 1, "value": 3}}, "switches": {"atraso": {"label": "...", "on": false}}, "vars": {"k": 45, "c": "sqrt(a^2 + b^2)"}, "defs": {...}, "steps": [...]}
29
+
30
+ `locale` is the language tag of the texts ("pt-BR", "en-US", "es-ES"…): numbers shown in texts and counters are written its way (3,5 or 3.5). Coordinates have their origin at the top left. Coordinates, radii, card sizes and plot fields may be **expressions as text** over params and vars: `+ - * / ^ ( )`, the functions sqrt abs sin cos tan min max round floor ceil pow log exp and the constants pi and e. Use them so the drawing redraws when a control moves. Texts take `{expr}` and `{expr:digits}` to show a value. `vars` are computed in order. Names are letters, digits and `_`. Conditions: `> < >= <= == !=`, `and`, `or`, `not` (true is 1, false is 0). `switches` are yes/no the person toggles beside the values; in expressions they are 1 or 0.
31
+
32
+ Step types (every step also takes `style`, `say`, `chapter`, `duration`, `id`; a step the person moved on the paper carries `nudge`: [dx, dy], added to its place after the expressions — keep it as it is when you fix a recipe):
33
+ - `card`: `at` (top-left corner), `w`, `h`, `title`, `lines` (up to 6 short texts). The title is size 30 and lines 22; keep `h` at least 70 + 28 per line and 24 px of side room; there is no line wrapping.
34
+ - `line` and `arrow`: `from`, `to`.
35
+ - `polyline` (open), `polygon` (closed, 3 points or more), `curve` (smooth through the points): `points`.
36
+ - `circle`: `center`, `r`. `ellipse`: `center`, `rx`, `ry` (good to circle a result).
37
+ - `plot`: `fn` (an expression of `var`, x by default), `domain` [from, to], `range` [low, high], `box` {x, y, w, h}, `samples`.
38
+ - `text`: `at`, `text`, `size`, `font` ("hand" or "body"), `anchor` ("start", "middle", "end"), `rotate`.
39
+ - `highlight`: `from`, `to`, `thickness` — a marker stroke behind the ink.
40
+ - `pause`: `ms` — a breath between ideas. With `ask` (a short question), the lesson stops on it until the person asks to see the rest: put it, with the `chapter`, as the first step of the part that answers it.
41
+ - `prop`: `name`, `at` (its bottom centre, where it stands), `size` (its box, 80 by default), `label` (text under it), `flip` (mirror). Names: person, person-sitting, person-pointing, house, car, bus, coin, phone, document, server, bank, cloud, queue, desk, computer, clock, lock, money-arrow, tree, school, hospital, factory, truck, cart, safe, bulb, gear, database, robot; logistics: ship, container, warehouse, box, pallet, shop, shelf, field, cup; cloud: function, api-gateway, cache, cluster; school: board, book, pencil. A person can show a `mood`: happy, sad, surprised, worried or idea (a lit bulb above the head). E.g. {"type":"prop","name":"person","at":[150,300],"label":"Ana","id":"ana"}.
42
+ - `repeat`: `times` (number or expression, up to 50) and one `step`; `i` (0 to n-1) is a value in that step. E.g. {"type":"repeat","times":"n","step":{"type":"prop","name":"coin","at":["100 + i*50",400],"size":40}}. Inside, an `id` x becomes x-0, x-1…
43
+ - `when`: `if` (a condition) and `steps`, drawn only when it is true. E.g. {"type":"when","if":"atraso","steps":[...]}. Steps inside repeat and when take no `say`, `chapter`, `duration` nor `pause`.
44
+ - `move`: `target` (an earlier `id`), `to`, `along` ("line" or "curve"), `ms`: the element walks until its `at` (or `center`, or `from`) is at `to`. E.g. {"type":"move","target":"pedido","to":[600,200],"along":"curve"}.
45
+ - `animate`: `target` (an earlier `id`) and `effect` — spin, drive, steam (wisps over it), talk, pulse, float, or stop — and `speed` ("slow" or "fast"): the element keeps moving in place from this step on (only a prop's drawing, not its label). Use it sparingly, where motion says something (the truck leaves, the gear turns, steam over the cup); one or two per drawing. E.g. {"type":"animate","target":"caminhao","effect":"drive"}.
46
+ - `link`: `from` and `to` (earlier `id`s), `label`: an arrow from the edge of one to the edge of the other, no coordinates. E.g. {"type":"link","from":"ana","to":"banco","label":"pede"}.
47
+ - `counter`: `at`, `value` (expression), `format` with `{v}` (e.g. "R$ {v:0}"), `size`, `anchor`: the number counts from 0 while its beat is drawn.
48
+
49
+ Placing without coordinates (prefer these to working out positions by hand):
50
+ - `place` on a `text`, `prop`, `card`, `counter`, `circle` or `ellipse`: {"to": an earlier `id`, "side": "below" | "above" | "left" | "right", "gap": 24, "align": "center" | "start" | "end"}. The step's box sits on that side of the other element; its `at`/`center` can then be left out. E.g. {"type":"text","text":"asks","place":{"to":"ana","side":"right","gap":16}}.
51
+ - `chain`: `at` (the top-left corner) and `steps` — 2 to 12 props, texts, cards, counters, circles or ellipses (give them an `id` and a `label`) — in a line spread over the paper, each joined to the next by an arrow, no coordinates. `forward` and `back` add a long arrow each way under the line, with that label (a flow down, and what comes back up); `gap` ("auto" or a number), `direction` ("right" or "down"), `wrap` (more lines when they do not fit), `size` (of the props). Use it whenever things pass something along in order (a supply chain, a request through systems). E.g. {"type":"chain","at":[40,190],"forward":"o café desce","back":"pedidos sobem","steps":[{"type":"prop","name":"field","id":"fazenda","label":"fazenda"},{"type":"prop","name":"cup","id":"cafe","label":"cafeteria"}]}.
52
+ - `row` and `column`: `at` (the top-left corner), `gap`, `align` and `steps` — texts, props, cards, counters, circles, ellipses or a `repeat` of one — set side by side or one under the other, measured; the steps inside need no coordinates. `grid`: the same with `columns`. E.g. {"type":"row","at":[480,120],"gap":14,"steps":[{"type":"repeat","times":"n","step":{"type":"prop","name":"coin","size":44}}]}.
53
+ - `axis`: `from`, `to`, `domain` [low, high], `ticks` (how many parts, or a list of values), `label`, `format` with `{v}`: an arrow with marks and their numbers, written the recipe's `locale` way.
54
+ - `timeline`: `from`, `to`, `domain` [first, last], `marks` [{"at", "label" above, "text" under}]: a line of time with each mark in its place.
55
+ Steps inside `row`, `column` and `grid` take no `say`, `chapter`, `duration` nor `place`.
56
+
57
+ More pieces:
58
+ - `balloon`: `to` (the `id` of who speaks), `text` (under 300 characters, wrapped), `side` (above by default; another side is used when it does not fit), `kind` ("speech" or "thought"), `width` (240 by default). Prefer it to a caption when a person says or thinks something.
59
+ - `brace`: `from`, `to`, `label`: a curly brace grouping what lies along it, bulging under a line that runs across (left of one that runs up), the other way with `flip`.
60
+ - `note`: `at`, `text`, and `to` (an `id`) or `point`: a handwritten blue note with a curved arrow — «look here».
61
+ - `table`: `at`, `rows` (up to 12 rows of up to 6 short cells; the first is the header unless `header` is false; cells take `{expr}`), `size`.
62
+ - `bars`: `box` {x, y, w, h}, `values` (numbers or expressions, up to 12), `labels`, `max`, `format` with `{v}`: bars with their value on top and label under.
63
+
64
+ `style`: `color`, `width`, `roughness` (0 ruler, 1.6 default, 3 scribble), `bowing`, `fill`, `fillStyle` ("hachure", "solid", "zigzag", "cross-hatch", "dots"), `dashed` (true or false), `opacity` (0 to 1).
65
+
66
+ The player groups steps into beats: each `say`, `chapter` or `pause` starts one, and the steps after it appear together. `duration` on a beat's first step suggests its pace, 1000 to 1600 ms. Keep the whole scene under 15 seconds.
67
+
68
+ ## Rules of good drawing
69
+
70
+ - Palette: graphite #2B2B2B (default), pen blue #2F5BEA, correction red #C8442C, green #2E8F62, pencil grey #8C8578, marker #FFE45C. At most 3 accent colours.
71
+ - Title text at the top (size 40 to 44). Labels 26 to 34. Nothing under 22.
72
+ - Leave room: a text, prop, card or counter that would leave the paper is brought back in, and a text that would cover another moves a little, but plan the space anyway: nothing closer than 30 px to the edge; texts never touch lines; nothing leaves the drawing at the controls' extreme values.
73
+ - 8 to 40 steps, read from top to bottom, from the real thing to the idea. Draw the real thing recognisably: use a `prop` for people and everyday things instead of drawing them by hand, and give it an `id` when an arrow or a move needs it.
74
+ - Numbers are literals in `size`, `duration`, `ms`, `rotate`, `samples`, `params` and `style`. Colours are hex or plain names; never URLs, HTML, scripts or links. Text is text, not markup.
75
+
76
+ An example (compound interest and a credit card bill):
77
+ {"id":"juros-fatura","title":"Juros compostos ↔ a fatura","category":"Matemática","width":900,"height":560,"background":"grid","params":{"taxa":{"label":"juros do rotativo (% ao mês)","min":1,"max":15,"step":1,"value":12}},"vars":{"r":"taxa / 100","bx":90,"by":130,"bw":720,"bh":340,"ymax":6000,"final":"1000 * (1 + r)^12","simples":"1000 * (1 + r*12)","ex":"bx + bw","ey":"by + bh - min(final, ymax) / ymax * bh"},"steps":[{"type":"text","at":[40,72],"text":"por que a dívida do rotativo dispara?","size":42,"say":"Você deixou R$ 1.000 no rotativo do cartão. O que acontece em um ano?"},{"type":"polyline","points":[["bx","by - 10"],["bx","by + bh"],["bx + bw + 10","by + bh"]],"style":{"width":2.4}},{"type":"text","at":["bx + bw","by + bh + 38"],"text":"meses →","anchor":"end","size":26,"style":{"color":"#5E5A52"}},{"type":"text","at":["bx - 10","by - 18"],"text":"R$","anchor":"middle","size":26,"style":{"color":"#5E5A52"}},{"type":"plot","fn":"1000 * (1 + r*x)","domain":[0,12],"range":[0,"ymax"],"box":{"x":"bx","y":"by","w":"bw","h":"bh"},"style":{"color":"#8C8578","width":2.4,"dashed":true},"say":"Se os juros fossem simples, a dívida subiria em linha reta."},{"type":"text","at":["ex - 8","by + bh - min(simples, ymax)/ymax*bh + 34"],"text":"simples: R$ {simples:0}","anchor":"end","size":26,"style":{"color":"#5E5A52"}},{"type":"pause","ms":400},{"type":"plot","fn":"1000 * (1 + r)^x","domain":[0,12],"range":[0,"ymax"],"box":{"x":"bx","y":"by","w":"bw","h":"bh"},"style":{"color":"#C8442C","width":3.4},"say":"Mas no rotativo o juro cobra juro do juro. A curva entorta para cima."},{"type":"arrow","from":["ex - 230","ey - 48"],"to":["ex - 10","ey - 6"],"style":{"width":2.4}},{"type":"text","at":["ex - 240","ey - 56"],"text":"12 meses: R$ {final:0}","anchor":"end","size":36,"style":{"color":"#C8442C"},"say":"Em doze meses, os mil reais viram isso. Mexa na taxa e veja a curva mudar."}]}
78
+
79
+ When a previous recipe and a problem are given, fix that recipe: keep what is right and change only what the problem asks.
80
+
81
+ Save the recipe as a .json file and run scripts/build.mjs on it.
@@ -0,0 +1,115 @@
1
+ # Writing the recipe as text (.chalk)
2
+
3
+ The same recipe as `references/recipe.md`, one step per line. `scripts/build.mjs` reads it (and says the line of any
4
+ mistake). Save it as `<id>.chalk`. What each step draws, the palette and the rules of good drawing are in
5
+ `references/recipe.md`; this page is only how to write them.
6
+
7
+ ```
8
+ title Juntar moedas ↔ o cofrinho
9
+ id coin-jar
10
+ theme chalkboard
11
+ locale pt-BR
12
+ param n "moedas por mês" 1..8 = 3
13
+
14
+ # O gancho
15
+ > A Ana guarda moedas todo mês.
16
+ person ana at 150,330 "Ana" mood=idea
17
+ text "quer poupar" right-of ana gap 16 align=start
18
+
19
+ # Desmontar
20
+ > Cada mês, n moedas vão para o banco.
21
+ row at 470,150 gap 14
22
+ repeat n: coin size 44
23
+ bank banco at 740,450 "banco"
24
+ ana -> banco "deposita"
25
+
26
+ # A ideia
27
+ pause ask "E em um ano, quanto junta?"
28
+ counter at 470,270 "12*n" "{v:0} moedas num ano"
29
+
30
+ learning goal "…"
31
+ learning question "…"
32
+ learning answer "…"
33
+ learning caveat "…"
34
+
35
+ quiz "Se n dobra, o total do ano…"
36
+ * dobra -- 12 × n cresce em linha reta
37
+ - quadruplica -- não há n² aqui
38
+ ```
39
+
40
+ ## The rules
41
+
42
+ - **Header lines** (anywhere, usually on top): `title …`, `id …`, `category …`, `theme paper|chalkboard`,
43
+ `locale en-US`, `background dots|grid|plain`, `size 900x560`, `param name "label" min..max [step=1] = value`,
44
+ `switch name "label" on|off`, `var name = value`, `learning goal|question|answer|caveat "…"`.
45
+ - `# Title` opens a chapter and `> sentence` is the narration: both go to the **next step**.
46
+ - **Numbers or expressions:** a number as it is; an expression in quotes (`"12*n"`, `"x0 + b*k",300`); a bare name
47
+ (`n`) is a value too. A point is `x,y` (no space).
48
+ - **Attributes** after a step: `key=value` (`size=44`, `color=blue`, `id=box`, `stroke=3`, `opacity=0.5`,
49
+ `nudge=4,-2`) or the flags `dashed`, `flip`. `gap`, `size`, `r`, `rx`, `ry`, `w`, `h`, `columns`, `ms` and `thickness`
50
+ can also go with a space (`size 44`).
51
+ - **Placing** instead of `at x,y`: `right-of <id>`, `left-of`, `above`, `below`, then `gap 16` and `align=start|end`.
52
+ - **Indented lines** are the steps inside `row`, `column`, `grid` and `when`, the marks of a `timeline`, the rows of a
53
+ `table` and the options of a `quiz`.
54
+
55
+ ## Every step
56
+
57
+ | Text | Step |
58
+ |---|---|
59
+ | `person ana at 150,330 "Ana" mood=idea` | a prop (any prop name), with an optional id and label |
60
+ | `text "…" at 40,60 size=40 anchor=middle` | text |
61
+ | `card at 10,10 w=220 h=130 "Title" "line 1" "line 2"` | card |
62
+ | `arrow 100,200 -> 300,200` · `line …` · `highlight … thickness=18` | from → to |
63
+ | `polyline 0,0 10,0 10,10` · `polygon …` · `curve …` | points |
64
+ | `circle at 100,100 r=40` · `ellipse at 100,100 rx=60 ry=30` | circle, ellipse |
65
+ | `plot "x^2" domain 0..10 range 0..100 box 50,50,300,200` | a curve of a formula |
66
+ | `pause` · `pause 400` · `pause ask "E agora?"` | a breath; with `ask`, the lesson waits for «Show me» |
67
+ | `row at 470,150 gap 14` · `column …` · `grid at 300,300 columns=3` | + indented steps |
68
+ | `repeat n: coin size 44` | one step repeated (`i` counts from 0) |
69
+ | `when atraso:` | + indented steps drawn when the condition holds |
70
+ | `ana -> banco "deposita"` | an arrow between two ids |
71
+ | `ana says "posso?"` · `ana thinks "hmm"` | a balloon (`side=right`, `width=200`) |
72
+ | `move ana to 300,200 along=curve` | the element walks there |
73
+ | `counter at 470,270 "12*n" "{v:0} moedas"` | a number that counts up |
74
+ | `axis 80,470 -> 420,470 domain 0..10 ticks=5 label="anos"` | axis |
75
+ | `timeline 100,400 -> 800,400 domain 1440..1520` + `mark 1450 "1450" "the press"` | timeline |
76
+ | `brace 60,560 -> 290,560 "two years"` | curly brace |
77
+ | `note at 500,100 "look here" -> ana` | a note with a curved arrow (or `-> x,y`) |
78
+ | `table at 60,380` + `\| a \| b \|` lines (`noheader`) | table |
79
+ | `bars box 400,400,300,200 values 1,2,"3*n" labels a,b,c format="{v}%"` | bars |
80
+ | `json {"type": …}` | any step as JSON, for anything the text has no short form for |
81
+
82
+ ## A Mermaid diagram
83
+
84
+ **Any Mermaid diagram** (flowchart, sequence, class, state, ER, pie, timeline, journey, gantt, xychart, quadrant,
85
+ mindmap, kanban, gitGraph, sankey, block, packet, treemap, radar, requirement, C4, architecture, ishikawa, venn,
86
+ treeView, cynefin, eventmodeling, railroad, swimlanes, wardley — every kind of Mermaid 11.17 but zenuml) is also a recipe: save it as `<id>.mmd` (or put it in a
87
+ ```mermaid block of a `.md`) and build it. Add `%% title …`, `%% # Chapter` and `%% > narration` lines — comments for
88
+ Mermaid, so the diagram still shows as usual on GitHub. A chapter starts at the arrow (message, relation) after it. The
89
+ drawing follows the order of the arrows, each box before the first arrow that reaches it; a sequence goes top to bottom.
90
+ Node shapes are kept (`([…])`, `[(…)]`, `{{…}}`, `[/…/]`, `>…]`, `[[…]]`, `(((…)))`, `@{ shape: … }`). In the other
91
+ kinds a `%% #` goes before a section, period, task, slice, series, point, branch, column or git command; with none,
92
+ each section, column, branch or git branch is a chapter of its own. A kind it does not draw yet makes the build say
93
+ which ones it draws.
94
+
95
+ ```
96
+ flowchart LR
97
+ %% title Um café, do pedido à xícara
98
+ %% # O pedido
99
+ %% > Você pede um café no balcão.
100
+ A([Você]) -->|pede| B[Caixa]
101
+ %% # A cozinha
102
+ %% > O caixa passa a comanda para o barista.
103
+ B -->|comanda| C[Barista]
104
+ C -->|café pronto| A
105
+ ```
106
+
107
+ ```
108
+ sequenceDiagram
109
+ %% title Um café no balcão
110
+ %% # O pedido
111
+ %% > Você pede um café.
112
+ Você->>Caixa: um café, por favor
113
+ %% # A conta
114
+ Caixa-->>Você: são cinco reais
115
+ ```
package/scripts/build.mjs CHANGED
@@ -59,10 +59,12 @@ let checking
59
59
  const chalk = (text) => parseChalk((reading = text), PROPS)
60
60
 
61
61
  /** The recipes a file holds: one for .json, .chalk and .mmd; one per ```chalk-talk / ```mermaid block in a .md. */
62
+ const idOf = (file) => basename(file).replace(/\.[^.]+$/, '').replace(/[^a-z0-9-]+/gi, '-').slice(0, 60) || 'drawing'
62
63
  async function recipesOf(file) {
63
64
  const text = readFileSync(resolve(file), 'utf8')
64
65
  if (file.endsWith('.json')) return [JSON.parse(text)]
65
- if (file.endsWith('.chalk')) return [chalk(text)]
66
+ // A .chalk file with no `id` line is named after the file (drawing.chalk → drawing.html), not the reader's default.
67
+ if (file.endsWith('.chalk')) return [/^id\s/m.test(text) ? chalk(text) : { ...chalk(text), id: idOf(file) }]
66
68
  if (file.endsWith('.mmd')) return [await mermaid(text)]
67
69
  const blocks = [...text.matchAll(/^```(chalk-talk|mermaid)[^\n]*\n([\s\S]*?)^```/gm)]
68
70
  if (!blocks.length) throw new Error('no ```chalk-talk or ```mermaid block in this file.')
@@ -156,6 +158,9 @@ scenes.forEach((scene, k) => {
156
158
  const values = Object.keys(scene.params ?? {}).length + Object.keys(scene.switches ?? {}).length
157
159
  const who = course ? ` (${k + 1}) ${scene.title}: ` : ' '
158
160
  console.log(`✓ contract${who}${scene.steps.length} steps, ${chaptersFor(scene).length} chapters, ${values} values`)
161
+ // one ready-made animation per drawing reads better (it is polished, not chalk): said, not an error
162
+ const lotties = JSON.stringify(scene.steps).match(/"type":"lottie"/g)?.length ?? 0
163
+ if (lotties > 1) console.log(`• lottie ${course ? `(${k + 1}) ` : ''}${lotties} animations in one drawing: one reads better (keep the others only if the person asked for them)`)
159
164
  for (const w of purposeWarnings(scene)) {
160
165
  console.log(`! ${scene.kind.padEnd(9)}${course ? ` (${k + 1})` : ''} ${w.text}`)
161
166
  warned = true