sketchmark 0.2.7 → 1.0.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 (64) hide show
  1. package/README.md +1195 -1066
  2. package/dist/animation/index.d.ts +54 -10
  3. package/dist/animation/index.d.ts.map +1 -1
  4. package/dist/ast/types.d.ts +16 -19
  5. package/dist/ast/types.d.ts.map +1 -1
  6. package/dist/config.d.ts +162 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/export/index.d.ts.map +1 -1
  9. package/dist/index.cjs +2127 -1374
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2127 -1374
  14. package/dist/index.js.map +1 -1
  15. package/dist/layout/entity-rect.d.ts +9 -0
  16. package/dist/layout/entity-rect.d.ts.map +1 -0
  17. package/dist/layout/index.d.ts.map +1 -1
  18. package/dist/markdown/parser.d.ts.map +1 -1
  19. package/dist/parser/index.d.ts.map +1 -1
  20. package/dist/parser/tokenizer.d.ts.map +1 -1
  21. package/dist/renderer/canvas/index.d.ts.map +1 -1
  22. package/dist/renderer/roughChart.d.ts.map +1 -1
  23. package/dist/renderer/shapes/box.d.ts +3 -0
  24. package/dist/renderer/shapes/box.d.ts.map +1 -0
  25. package/dist/renderer/shapes/circle.d.ts +3 -0
  26. package/dist/renderer/shapes/circle.d.ts.map +1 -0
  27. package/dist/renderer/shapes/cylinder.d.ts +3 -0
  28. package/dist/renderer/shapes/cylinder.d.ts.map +1 -0
  29. package/dist/renderer/shapes/diamond.d.ts +3 -0
  30. package/dist/renderer/shapes/diamond.d.ts.map +1 -0
  31. package/dist/renderer/shapes/hexagon.d.ts +3 -0
  32. package/dist/renderer/shapes/hexagon.d.ts.map +1 -0
  33. package/dist/renderer/shapes/icon.d.ts +3 -0
  34. package/dist/renderer/shapes/icon.d.ts.map +1 -0
  35. package/dist/renderer/shapes/image.d.ts +3 -0
  36. package/dist/renderer/shapes/image.d.ts.map +1 -0
  37. package/dist/renderer/shapes/index.d.ts +4 -0
  38. package/dist/renderer/shapes/index.d.ts.map +1 -0
  39. package/dist/renderer/shapes/line.d.ts +3 -0
  40. package/dist/renderer/shapes/line.d.ts.map +1 -0
  41. package/dist/renderer/shapes/note.d.ts +3 -0
  42. package/dist/renderer/shapes/note.d.ts.map +1 -0
  43. package/dist/renderer/shapes/parallelogram.d.ts +3 -0
  44. package/dist/renderer/shapes/parallelogram.d.ts.map +1 -0
  45. package/dist/renderer/shapes/path.d.ts +3 -0
  46. package/dist/renderer/shapes/path.d.ts.map +1 -0
  47. package/dist/renderer/shapes/registry.d.ts +5 -0
  48. package/dist/renderer/shapes/registry.d.ts.map +1 -0
  49. package/dist/renderer/shapes/text-shape.d.ts +3 -0
  50. package/dist/renderer/shapes/text-shape.d.ts.map +1 -0
  51. package/dist/renderer/shapes/triangle.d.ts +3 -0
  52. package/dist/renderer/shapes/triangle.d.ts.map +1 -0
  53. package/dist/renderer/shapes/types.d.ts +50 -0
  54. package/dist/renderer/shapes/types.d.ts.map +1 -0
  55. package/dist/renderer/shared.d.ts +26 -0
  56. package/dist/renderer/shared.d.ts.map +1 -0
  57. package/dist/renderer/svg/index.d.ts.map +1 -1
  58. package/dist/renderer/svg/roughChartSVG.d.ts.map +1 -1
  59. package/dist/renderer/typography.d.ts +27 -0
  60. package/dist/renderer/typography.d.ts.map +1 -0
  61. package/dist/scene/index.d.ts +7 -15
  62. package/dist/scene/index.d.ts.map +1 -1
  63. package/dist/sketchmark.iife.js +2127 -1374
  64. package/package.json +1 -1
package/README.md CHANGED
@@ -1,1066 +1,1195 @@
1
- # sketchmark
2
-
3
- A text-based diagram DSL that renders hand-drawn SVG and Canvas diagrams using [rough.js](https://roughjs.com). Write diagrams as plain text, get sketchy, expressive visuals with a full animation system.
4
-
5
- ```
6
- diagram
7
- title label="System Architecture"
8
-
9
- box client label="Client App" theme=primary
10
- box gateway label="API Gateway" theme=warning
11
- box db label="PostgreSQL" theme=success
12
-
13
- client --> gateway label="HTTPS"
14
- gateway --> db label="SQL"
15
-
16
- step highlight client
17
- step draw client-->gateway
18
- step highlight gateway
19
- step draw gateway-->db
20
- end
21
- ```
22
-
23
- ---
24
-
25
- ## Table of Contents
26
-
27
- - [Installation](#installation)
28
- - [Quick Start](#quick-start)
29
- - [DSL Reference](#dsl-reference)
30
- - [Diagram Header](#diagram-header)
31
- - [Node Shapes](#node-shapes)
32
- - [Icons](#icon-shape)
33
- - [Edges](#edges)
34
- - [Groups](#groups)
35
- - [Bare Groups](#bare-groups)
36
- - [Tables](#tables)
37
- - [Notes](#notes)
38
- - [Charts](#charts)
39
- - [Markdown Blocks](#markdown-blocks)
40
- - [Themes](#themes)
41
- - [Style Directive](#style-directive)
42
- - [Typography](#typography)
43
- - [Animation Steps](#animation-steps)
44
- - [Layout System](#layout-system)
45
- - [Animation System](#animation-system)
46
- - [Theme Palettes](#theme-palettes)
47
- - [Font System](#font-system)
48
- - [API Reference](#api-reference)
49
- - [Export](#export)
50
- - [Examples](#examples)
51
-
52
- ---
53
-
54
- ## Installation
55
-
56
- ```bash
57
- npm install sketchmark
58
- ```
59
-
60
- rough.js is a peer dependency — it must be available at runtime.
61
-
62
- ---
63
-
64
- ## Quick Start
65
-
66
- **With a bundler (Vite, webpack, Next.js):**
67
-
68
- ```typescript
69
- import { render } from 'sketchmark';
70
-
71
- const instance = render({
72
- container: document.getElementById('diagram'),
73
- dsl: `
74
- diagram
75
- box a label="Hello"
76
- box b label="World"
77
- a --> b label="connects"
78
- `,
79
- renderer: 'svg',
80
- svgOptions: { showTitle: true, interactive: true, transparent: true },
81
- });
82
-
83
- // Step through animation
84
- instance.anim.next();
85
- instance.anim.play(800);
86
- ```
87
-
88
- **CDN / no bundler with import map:**
89
-
90
- ```html
91
-
92
-
93
- { "imports": { "sketchmark": "https://unpkg.com/sketchmark/dist/index.js" } }
94
-
95
-
96
- import { render } from 'sketchmark';
97
- render({
98
- container: document.getElementById('diagram'),
99
- dsl: `diagram\nbox a label="Hello"\nbox b label="World"\na --> b`,
100
- });
101
-
102
- ```
103
-
104
- **CommonJS:**
105
-
106
- ```javascript
107
- const { parse, buildSceneGraph, layout } = require('sketchmark');
108
- ```
109
-
110
- ---
111
-
112
- ## DSL Reference
113
-
114
- Every diagram starts with `diagram` and ends with `end`.
115
-
116
- ```
117
- diagram
118
- title label="My Diagram"
119
- layout row
120
- config gap=60
121
-
122
- ... nodes, edges, groups, steps ...
123
-
124
- end
125
- ```
126
-
127
- ### Diagram Header
128
-
129
- | Keyword | Example | Description |
130
- |---|---|---|
131
- | `title` | `title label="My Diagram"` | Title shown above the diagram |
132
- | `description` | `description "A brief summary"` | Diagram description (metadata) |
133
- | `layout` | `layout row` | Root layout direction: `row`, `column`, `grid` |
134
- | `config gap` | `config gap=60` | Gap between root-level items (default: 80) |
135
- | `config margin` | `config margin=40` | Outer canvas margin (default: 60) |
136
- | `config theme` | `config theme=ocean` | Global palette (see [Theme Palettes](#theme-palettes)) |
137
- | `config font` | `config font=caveat` | Diagram-wide font (see [Font System](#font-system)) |
138
- | `config title-color` | `config title-color=#333` | Title text color |
139
- | `config title-size` | `config title-size=20` | Title font size in px |
140
- | `config title-weight` | `config title-weight=700` | Title font weight |
141
-
142
- ---
143
-
144
- ### Node Shapes
145
-
146
- ```
147
- box id label="..." [theme=X] [width=N] [height=N]
148
- circle id label="..."
149
- diamond id label="..."
150
- hexagon id label="..."
151
- triangle id label="..."
152
- cylinder id label="..."
153
- parallelogram id label="..."
154
- text id label="..."
155
- image id label="..." url="https://..."
156
- icon id label="..." name="prefix:name"
157
- ```
158
-
159
- **Common properties:**
160
-
161
- | Property | Example | Description |
162
- |---|---|---|
163
- | `label` | `label="API Gateway"` | Display text. Use `\n` for line breaks |
164
- | `theme` | `theme=primary` | Named theme |
165
- | `width` | `width=140` | Override auto-width in px |
166
- | `height` | `height=55` | Override auto-height in px |
167
- | `fill` | `fill="#e8f4ff"` | Background fill color |
168
- | `stroke` | `stroke="#0044cc"` | Border color |
169
- | `stroke-width` | `stroke-width=2` | Border thickness in px |
170
- | `stroke-dash` | `stroke-dash=5,3` | Dashed border pattern (dash, gap) |
171
- | `opacity` | `opacity=0.5` | Element opacity (0 to 1) |
172
- | `color` | `color="#003399"` | Text color |
173
- | `font` | `font=caveat` | Font family or built-in name |
174
- | `font-size` | `font-size=12` | Label font size in px |
175
- | `font-weight` | `font-weight=600` | Font weight |
176
- | `letter-spacing` | `letter-spacing=2` | Letter spacing in px |
177
- | `text-align` | `text-align=left` | `left`, `center`, `right` |
178
- | `vertical-align` | `vertical-align=top` | `top`, `middle`, `bottom` |
179
- | `line-height` | `line-height=1.6` | Line height multiplier |
180
-
181
- > **`text` shape:** No border or background. Long labels auto word-wrap. Use `width=` to control the wrap width.
182
-
183
- > **`image` shape:** Renders an image clipped to a rounded rect. Requires `url=` property. Label renders below the image. Border only shown when `stroke=` is set.
184
-
185
- > **`icon` shape:** Renders an icon from [Iconify](https://iconify.design/) (200,000+ open source icons). Requires `name=` property in `prefix:name` format (e.g. `mdi:database`). Defaults to `mdi` prefix if omitted. Use `color=` to tint the icon. Label renders below the icon. Border only shown when `stroke=` is set. Default size: 48x48.
186
-
187
- **Example:**
188
- ```
189
- box gateway label="API Gateway" theme=warning width=150 height=55
190
- circle user label="User" fill="#e8f4ff" stroke="#0044cc" color="#003399"
191
- cylinder db label="PostgreSQL" theme=success width=140 height=65
192
- image logo label="Logo" url="https://example.com/logo.png" width=80 height=80
193
- icon db label="Database" name="mdi:database" color="#1976D2"
194
- icon cloud name="mdi:cloud" width=64 height=64
195
- text caption label="This auto-wraps across multiple lines." width=300
196
- ```
197
-
198
- ---
199
-
200
- ### Icon Shape
201
-
202
- Render any of 200,000+ open source vector icons from [Iconify](https://iconify.design/).
203
-
204
- ```
205
- icon id [label="..."] name="prefix:name" [color="#hex"] [width=N] [height=N]
206
- ```
207
-
208
- | Property | Example | Description |
209
- |---|---|---|
210
- | `name` | `name="mdi:database"` | Icon identifier in `prefix:name` format. Defaults to `mdi` prefix if omitted |
211
- | `color` | `color="#1976D2"` | Icon tint color |
212
- | `stroke` | `stroke="#333"` | Optional border (not shown by default) |
213
- | `label` | `label="DB"` | Label shown below the icon (defaults to id) |
214
- | `width` | `width=64` | Icon width (default: 48) |
215
- | `height` | `height=64` | Icon height (default: 48) |
216
-
217
- Browse available icons at [icon-sets.iconify.design](https://icon-sets.iconify.design/). Common prefixes: `mdi` (Material Design), `lucide`, `heroicons`, `tabler`, `ph` (Phosphor), `ri` (Remix), `carbon`.
218
-
219
- **Example:**
220
- ```
221
- icon db label="Database" name="mdi:database" color="#1976D2"
222
- icon cloud label="Cloud" name="mdi:cloud-outline" color="#FF9800" width=64 height=64
223
- icon lock name="mdi:lock" color="#E53935"
224
- icon user name="lucide:user"
225
- ```
226
-
227
- ---
228
-
229
- ### Edges
230
-
231
- ```
232
- fromId connector toId [label="..."] [stroke="#color"] [stroke-width=N]
233
- ```
234
-
235
- **Connectors:**
236
-
237
- | Connector | Arrow | Line |
238
- |---|---|---|
239
- | `->` | end | solid |
240
- | `<-` | start | solid |
241
- | `<->` | both | solid |
242
- | `-->` | end | dashed |
243
- | `<-->` | both | dashed |
244
- | `--` | none | solid |
245
- | `---` | none | dashed |
246
-
247
- **Edge style properties:**
248
-
249
- | Property | Description |
250
- |---|---|
251
- | `label` | Text label on the edge |
252
- | `stroke` | Line and arrowhead color |
253
- | `stroke-width` | Line thickness |
254
- | `color` | Label text color |
255
- | `font` | Label font |
256
- | `font-size` | Label font size |
257
- | `theme` | Apply a named theme to the edge |
258
-
259
- **Example:**
260
- ```
261
- client --> gateway label="HTTPS"
262
- gateway <-> auth label="verify"
263
- a -- b
264
- db --- replica
265
- api --> db stroke="#0044cc" stroke-width=2 theme=primary
266
- ```
267
-
268
- ---
269
-
270
- ### Groups
271
-
272
- Groups are containers that arrange children using a flexbox-style layout.
273
-
274
- ```
275
- group id [label="..."] [layout=row|column|grid] [gap=N] [padding=N]
276
- [justify=start|center|end|space-between|space-around]
277
- [align=start|center|end]
278
- [columns=N] [width=N] [height=N] [theme=X]
279
- [font=X] [font-size=N] [letter-spacing=N]
280
- {
281
- ...children...
282
- }
283
- ```
284
-
285
- **Properties:**
286
-
287
- | Property | Default | Description |
288
- |---|---|---|
289
- | `label` | — | Label at top-left. Omit or `""` for no label and no reserved space |
290
- | `layout` | `column` | `row`, `column`, or `grid` |
291
- | `gap` | `10` | Space between children in px |
292
- | `padding` | `26` | Inner padding in px |
293
- | `justify` | `start` | Main-axis distribution |
294
- | `align` | `start` | Cross-axis alignment |
295
- | `columns` | `1` | Columns when `layout=grid` |
296
- | `width` | auto | Minimum width enables `justify` |
297
- | `height` | auto | Minimum height |
298
- | `theme` | | Named theme for border/background |
299
-
300
- > **`justify`** requires an explicit `width` larger than total child width to have a visible effect.
301
-
302
- **Example:**
303
- ```
304
- group services label="Microservices" layout=column gap=16 padding=30 theme=muted
305
- {
306
- box auth label="Auth Service" theme=primary width=140 height=55
307
- box billing label="Billing Service" theme=primary width=140 height=55
308
- }
309
- ```
310
-
311
- **Grid:**
312
- ```
313
- group icons layout=grid columns=3 gap=20 padding=24
314
- {
315
- box a label="A" width=100 height=60
316
- box b label="B" width=100 height=60
317
- box c label="C" width=100 height=60
318
- }
319
- ```
320
-
321
- **Space-between with explicit width:**
322
- ```
323
- group nav layout=row justify=space-between width=500 padding=20 gap=10
324
- {
325
- box home label="Home" width=80 height=40
326
- box about label="About" width=80 height=40
327
- box contact label="Contact" width=80 height=40
328
- }
329
- ```
330
-
331
- ---
332
-
333
- ### Bare Groups
334
-
335
- `bare` is a layout-only container no label, no border, no background, no padding by default. All group layout properties apply.
336
-
337
- ```
338
- bare id [layout=...] [gap=N] [padding=N] [justify=...] [align=...] [columns=N]
339
- {
340
- ...children...
341
- }
342
- ```
343
-
344
- Any explicitly set style overrides the bare defaults:
345
-
346
- ```
347
- # fully invisible layout container
348
- bare page layout=row gap=60
349
- {
350
- markdown intro width=340
351
- """
352
- # Title
353
- Some prose here.
354
- """
355
-
356
- group diagram layout=column gap=20 padding=30 theme=muted
357
- {
358
- box a label="A" theme=primary width=130 height=52
359
- }
360
- }
361
-
362
- # bare with a visible border
363
- bare wrapper layout=row gap=20 stroke="#cccccc" padding=16
364
- {
365
- box x label="X" width=100 height=50
366
- box y label="Y" width=100 height=50
367
- }
368
- ```
369
-
370
- ---
371
-
372
- ### Tables
373
-
374
- ```
375
- table id [label="..."] [theme=X] [font=X] [font-size=N] [text-align=left|center|right]
376
- {
377
- header Col1 Col2 Col3
378
- row val1 val2 val3
379
- }
380
- ```
381
-
382
- **Example:**
383
- ```
384
- table pricing label="Pricing Plans" font=dm-mono text-align=right
385
- {
386
- header Plan Price Requests
387
- row Free $0 1k/day
388
- row Pro $29 100k/day
389
- row Enterprise $299 Unlimited
390
- }
391
- ```
392
-
393
- ---
394
-
395
- ### Notes
396
-
397
- Sticky notes with a folded corner.
398
-
399
- ```
400
- note id label="Single line" [theme=X]
401
- note id label="Line one\nLine two\nLine three"
402
- note id label="..." [width=N] [height=N]
403
- [font=X] [font-size=N] [letter-spacing=N]
404
- [text-align=left|center|right]
405
- [vertical-align=top|middle|bottom]
406
- [line-height=1.4]
407
- ```
408
-
409
- **Example:**
410
- ```
411
- note n1 label="Rate limited\nto 1000 req/s\nper tenant"
412
- note n2 label="Postgres 16\nwith pg_vector" width=200 height=100 font-size=13
413
- note n3 label="Centered" text-align=center vertical-align=middle width=180 height=80
414
- ```
415
-
416
- ---
417
-
418
- ### Charts
419
-
420
- ```
421
- bar-chart id [label="..."] [width=N] [height=N] [theme=X]
422
- line-chart id ...
423
- area-chart id ...
424
- pie-chart id ...
425
- donut-chart id ...
426
- scatter-chart id ...
427
-
428
- data
429
- [
430
- ["Label", "Series1", "Series2"],
431
- ["Jan", 120, 80 ],
432
- ["Feb", 150, 95 ]
433
- ]
434
- ```
435
-
436
- **Pie / donut:**
437
- ```
438
- pie-chart revenue label="Revenue Split" width=280 height=240
439
- data
440
- [
441
- ["Product A", 42],
442
- ["Services", 30],
443
- ["Support", 25]
444
- ]
445
- ```
446
-
447
- ---
448
-
449
- ### Markdown Blocks
450
-
451
- Prose content with Markdown-style formatting, rendered inside the diagram layout.
452
-
453
- ```
454
- markdown id [width=N] [padding=N] [font=X]
455
- [text-align=left|center|right] [color=X]
456
- """
457
- # Heading 1
458
- ## Heading 2
459
- ### Heading 3
460
-
461
- Paragraph with **bold** and *italic* text.
462
-
463
- Another paragraph.
464
- """
465
- ```
466
-
467
- **Supported syntax:**
468
-
469
- | Syntax | Result |
470
- |---|---|
471
- | `# text` | H1 heading |
472
- | `## text` | H2 heading |
473
- | `### text` | H3 heading |
474
- | `**text**` | Bold |
475
- | `*text*` | Italic |
476
- | blank line | Vertical spacing |
477
-
478
- **Example:**
479
- ```
480
- bare page layout=row gap=60
481
- {
482
- markdown intro width=320 font=caveat padding=0
483
- """
484
- # Sketchmark
485
-
486
- A text-based diagram DSL that renders
487
- **hand-drawn** SVG diagrams.
488
-
489
- ## Animation
490
-
491
- Every element supports *step-by-step*
492
- animation **draw**, highlight, fade, move.
493
- """
494
-
495
- group diagram layout=column gap=20 padding=30 theme=muted
496
- {
497
- box parser label="Parser" theme=primary width=130 height=52
498
- box scene label="Scene" theme=success width=130 height=52
499
- }
500
- }
501
-
502
- parser --> scene label="AST"
503
-
504
- step draw intro
505
- step highlight parser
506
- step draw parser-->scene
507
- step highlight scene
508
- end
509
- ```
510
-
511
- ---
512
-
513
- ### Themes
514
-
515
- Define reusable style presets and apply them to any element.
516
-
517
- ```
518
- theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
519
- theme success fill="#e8ffe8" stroke="#007700" color="#004400"
520
- theme warning fill="#fff9e6" stroke="#f0a500" color="#7a5000"
521
- theme danger fill="#ffe8e8" stroke="#cc0000" color="#900000"
522
- theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
523
- ```
524
-
525
- Apply to any element: `box a theme=primary`, `group g theme=muted`, `note n theme=warning`, `a --> b theme=danger`
526
-
527
- ---
528
-
529
- ### Style Directive
530
-
531
- Apply styles to any element after it's defined, by targeting its id:
532
-
533
- ```
534
- box a label="Hello"
535
- style a fill="#ff0000" stroke="#cc0000" font-size=16
536
- ```
537
-
538
- This merges with any existing styles on the element. Useful for separating layout from styling.
539
-
540
- ---
541
-
542
- ### Typography
543
-
544
- Typography properties work on all text-bearing elements.
545
-
546
- | Property | Applies to | Description |
547
- |---|---|---|
548
- | `font` | all | Font family or built-in name |
549
- | `font-size` | all | Font size in px |
550
- | `font-weight` | all | `400`, `500`, `600`, `700` |
551
- | `letter-spacing` | all | Letter spacing in px |
552
- | `text-align` | nodes, notes, table cells, markdown | `left`, `center`, `right` |
553
- | `vertical-align` | nodes, notes | `top`, `middle`, `bottom` |
554
- | `line-height` | nodes, notes, markdown | Multiplier e.g. `1.4` |
555
-
556
- ```
557
- # diagram-wide font
558
- config font=caveat
559
-
560
- # per-element overrides
561
- box title label="Heading" font=playfair font-size=20 text-align=left
562
- box body label="Body text" font=system vertical-align=top
563
- note n label="Annotation" font=caveat font-size=13 line-height=1.6
564
- ```
565
-
566
- ---
567
-
568
- ### Animation Steps
569
-
570
- ```
571
- step action target [options]
572
- ```
573
-
574
- All actions work on **all element types** — nodes, groups, tables, notes, charts, and edges.
575
-
576
- **Actions:**
577
-
578
- | Action | Syntax | Description |
579
- |---|---|---|
580
- | `highlight` | `step highlight id` | Pulsing glow |
581
- | `fade` | `step fade id` | Fade to 22% opacity |
582
- | `unfade` | `step unfade id` | Restore full opacity |
583
- | `draw` | `step draw id` | Stroke-draw reveal |
584
- | `draw` | `step draw a-->b` | Animate edge in |
585
- | `erase` | `step erase id` | Fade to invisible |
586
- | `show` | `step show id` | Make hidden element visible |
587
- | `hide` | `step hide id` | Hide element |
588
- | `pulse` | `step pulse id` | Single brightness flash |
589
- | `color` | `step color id fill="#ff0000"` | Change fill color |
590
- | `move` | `step move id dx=50 dy=0` | Translate by dx/dy px |
591
- | `scale` | `step scale id factor=1.5` | Scale (absolute) |
592
- | `rotate` | `step rotate id deg=45` | Rotate (cumulative) |
593
-
594
- **Options:**
595
-
596
- | Option | Description |
597
- |---|---|
598
- | `duration=600` | Animation duration in ms |
599
- | `delay=100` | Delay before animation starts in ms |
600
- | `dx=100` | X offset for `move` |
601
- | `dy=-80` | Y offset for `move` |
602
- | `factor=1.5` | Scale multiplier |
603
- | `deg=45` | Rotation degrees |
604
-
605
- **Behaviour:**
606
- - `move` — cumulative. `dx=50` twice = 100px total
607
- - `scale` — absolute. `factor=1.0` always resets to normal
608
- - `rotate` — cumulative. `deg=-45` rotates back
609
- - `color` use `fill=` syntax: `step color id fill="#ff0000"`
610
-
611
- **Slide-in entrance:**
612
- ```
613
- step move id dx=0 dy=80 # snap below final position
614
- step draw id # reveal at offset
615
- step move id dx=0 dy=-80 # animate up into place
616
- ```
617
-
618
- **Wobble-and-fail:**
619
- ```
620
- step rotate id deg=8
621
- step rotate id deg=-8
622
- step rotate id deg=8
623
- step rotate id deg=25 # cumulative = 33°
624
- step fade id
625
- ```
626
-
627
- **Edge animations:**
628
- ```
629
- step highlight a-->b
630
- step color a-->b fill="#ff0000"
631
- step fade a-->b
632
- step move a-->b dx=0 dy=-20 duration=400
633
- ```
634
-
635
- ---
636
-
637
- ## Layout System
638
-
639
- ### Root layout
640
-
641
- ```
642
- layout row # items flow left to right (default)
643
- layout column # items flow top to bottom
644
- layout grid # grid — set columns with: config columns=N
645
- ```
646
-
647
- ### Group layout
648
-
649
- Each group is an independent flex container:
650
-
651
- ```
652
- group g layout=row justify=space-between width=500 gap=16 padding=20
653
- ```
654
-
655
- ### `justify` values
656
-
657
- | Value | Effect |
658
- |---|---|
659
- | `start` | Pack to start (default) |
660
- | `center` | Center in container |
661
- | `end` | Pack to end |
662
- | `space-between` | First at start, last at end, equal gaps between |
663
- | `space-around` | Equal space around each child |
664
-
665
- ### `align` values
666
-
667
- | Value | Effect |
668
- |---|---|
669
- | `start` | Cross-axis start (default) |
670
- | `center` | Cross-axis center |
671
- | `end` | Cross-axis end |
672
-
673
- ---
674
-
675
- ## Animation System
676
-
677
- ```typescript
678
- const { anim } = render({ container, dsl });
679
-
680
- anim.next(); // advance one step
681
- anim.prev(); // go back one step
682
- anim.reset(); // return to initial state
683
- anim.goTo(3); // jump to step index
684
- await anim.play(800); // auto-play, 800ms per step
685
-
686
- anim.currentStep // current index (-1 = not started)
687
- anim.total // total step count
688
- anim.canNext // boolean
689
- anim.canPrev // boolean
690
-
691
- anim.on((event) => {
692
- // event.type: 'step-change' | 'animation-reset' |
693
- // 'animation-start' | 'animation-end' | 'step-complete'
694
- });
695
- ```
696
-
697
- ---
698
-
699
- ## Theme Palettes
700
-
701
- | Name | Description |
702
- |---|---|
703
- | `light` | Warm parchment (default) |
704
- | `dark` | Dark warm background |
705
- | `ocean` | Cool blues |
706
- | `forest` | Greens |
707
- | `sunset` | Warm oranges and reds |
708
- | `slate` | Cool grays |
709
- | `rose` | Pinks and magentas |
710
- | `midnight` | GitHub-dark style blues |
711
- | `sketch` | Graphite pencil-on-paper |
712
-
713
- ```
714
- config theme=sketch
715
- ```
716
-
717
- List all palette names at runtime:
718
-
719
- ```typescript
720
- import { THEME_NAMES } from 'sketchmark';
721
- // ['light', 'dark', 'ocean', 'forest', 'sunset', 'slate', 'rose', 'midnight', 'sketch']
722
- ```
723
-
724
- ---
725
-
726
- ## Font System
727
-
728
- ### Built-in fonts
729
-
730
- | Name | Style |
731
- |---|---|
732
- | `caveat` | Hand-drawn, casual |
733
- | `handlee` | Hand-drawn, friendly |
734
- | `indie-flower` | Hand-drawn, playful |
735
- | `patrick-hand` | Hand-drawn, clean |
736
- | `dm-mono` | Monospace, refined |
737
- | `jetbrains` | Monospace, code-like |
738
- | `instrument` | Serif, editorial |
739
- | `playfair` | Serif, elegant |
740
- | `system` | System UI sans-serif |
741
- | `mono` | Courier New |
742
- | `serif` | Georgia |
743
-
744
- Built-in fonts load automatically from Google Fonts on first use.
745
-
746
- ```
747
- config font=caveat # diagram-wide
748
-
749
- box a label="Hand-drawn" font=caveat
750
- box b label="Code style" font=dm-mono font-size=11
751
- ```
752
-
753
- ### Custom fonts
754
-
755
- ```typescript
756
- import { registerFont } from 'sketchmark';
757
-
758
- // font already loaded in the page via or @import
759
- registerFont('brand', '"Brand Sans", sans-serif');
760
-
761
- // then use in DSL
762
- // config font=brand
763
- // box a font=brand
764
- ```
765
-
766
- Or pass a full CSS family directly in DSL (must be quoted):
767
-
768
- ```
769
- box a label="Hello" font="'Pacifico', cursive"
770
- ```
771
-
772
- ---
773
-
774
- ## API Reference
775
-
776
- ### `render(options): DiagramInstance`
777
-
778
- ```typescript
779
- import { render } from 'sketchmark';
780
-
781
- const instance = render({
782
- container: '#my-div',
783
- dsl: '...',
784
- renderer: 'svg', // 'svg' (default) | 'canvas'
785
- injectCSS: true,
786
- svgOptions: {
787
- showTitle: true,
788
- interactive: true,
789
- roughness: 1.3,
790
- bowing: 0.7,
791
- theme: 'light', // 'light' | 'dark' | 'auto'
792
- transparent: false, // remove background rect
793
- onNodeClick: (nodeId) => {},
794
- },
795
- canvasOptions: {
796
- scale: 2,
797
- roughness: 1.3,
798
- transparent: false,
799
- },
800
- onNodeClick: (nodeId) => {},
801
- onReady: (anim, svg) => {},
802
- });
803
- ```
804
-
805
- `theme: 'auto'` follows OS `prefers-color-scheme`. `transparent: true` removes the background so the diagram floats over the page.
806
-
807
- **`DiagramInstance`:**
808
-
809
- ```typescript
810
- instance.scene // SceneGraph
811
- instance.anim // AnimationController
812
- instance.svg // SVGSVGElement
813
- instance.canvas // HTMLCanvasElement
814
- instance.update(dsl) // re-render with new DSL
815
- instance.exportSVG() // download SVG
816
- instance.exportPNG() // download PNG
817
- ```
818
-
819
- ---
820
-
821
- ### Pipeline API
822
-
823
- ```typescript
824
- import { parse, buildSceneGraph, layout, renderToSVG } from 'sketchmark';
825
-
826
- const ast = parse(dslString);
827
- const scene = buildSceneGraph(ast);
828
- layout(scene);
829
- const svg = renderToSVG(scene, containerEl, options);
830
- ```
831
-
832
- ---
833
-
834
- ### `parse(dsl: string): DiagramAST`
835
-
836
- ```typescript
837
- import { parse, ParseError } from 'sketchmark';
838
-
839
- try {
840
- const ast = parse(dsl);
841
- } catch (e) {
842
- if (e instanceof ParseError) {
843
- console.error(`Line ${e.line}, Col ${e.col}: ${e.message}`);
844
- }
845
- }
846
- ```
847
-
848
- ---
849
-
850
- ## Export
851
-
852
- ```typescript
853
- import { exportSVG, exportPNG, exportHTML, getSVGBlob } from 'sketchmark';
854
-
855
- exportSVG(svgEl, { filename: 'diagram.svg' });
856
- await exportPNG(svgEl, { filename: 'diagram.png', scale: 2 });
857
- exportHTML(svgEl, dslSource, { filename: 'diagram.html' });
858
-
859
- const blob = getSVGBlob(svgEl);
860
- ```
861
-
862
- Via instance:
863
-
864
- ```typescript
865
- instance.exportSVG('diagram.svg');
866
- await instance.exportPNG('diagram.png');
867
- ```
868
-
869
- ---
870
-
871
- ## Examples
872
-
873
- ### Basic architecture
874
-
875
- ```
876
- diagram
877
- title label="System Architecture"
878
- layout row
879
- config gap=60
880
-
881
- theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
882
- theme success fill="#e8ffe8" stroke="#007700" color="#004400"
883
- theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
884
-
885
- box client label="Client App" theme=primary width=140 height=55
886
- box gateway label="API Gateway" theme=muted width=140 height=55
887
-
888
- group services label="Services" layout=column gap=16 padding=30 theme=muted
889
- {
890
- box auth label="Auth Service" theme=primary width=130 height=50
891
- box data label="Data Service" theme=primary width=130 height=50
892
- }
893
-
894
- cylinder db label="PostgreSQL" theme=success width=140 height=65
895
-
896
- client --> gateway label="HTTPS"
897
- gateway --> auth
898
- gateway --> data
899
- auth --> db label="SQL"
900
- data --> db label="SQL"
901
- end
902
- ```
903
-
904
- ---
905
-
906
- ### Animated deployment
907
-
908
- ```
909
- diagram
910
- title label="Blue-Green Deployment"
911
- layout row
912
- config gap=50
913
-
914
- theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
915
- theme success fill="#e8ffe8" stroke="#007700" color="#004400"
916
- theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
917
-
918
- box lb label="Load Balancer" theme=muted width=150 height=55
919
-
920
- group blue label="Blue (live)" layout=column gap=16 padding=26 theme=primary
921
- {
922
- box b1 label="API v1" theme=primary width=120 height=50
923
- box b2 label="Worker v1" theme=primary width=120 height=50
924
- }
925
-
926
- group green label="Green (new)" layout=column gap=16 padding=26 theme=success
927
- {
928
- box g1 label="API v2" theme=success width=120 height=50
929
- box g2 label="Worker v2" theme=success width=120 height=50
930
- }
931
-
932
- lb --> b1 label="100%"
933
- lb --> g1 label="0%"
934
-
935
- step highlight lb
936
- step draw lb-->b1
937
- step highlight b1
938
- step move g1 dx=0 dy=60
939
- step move g2 dx=0 dy=60
940
- step draw g1
941
- step move g1 dx=0 dy=-60 duration=500
942
- step draw g2
943
- step move g2 dx=0 dy=-60 duration=500
944
- step fade b1
945
- step fade b2
946
- step draw lb-->g1
947
- step highlight g1
948
- end
949
- ```
950
-
951
- ---
952
-
953
- ### Markdown with diagram
954
-
955
- ```
956
- diagram
957
- layout row
958
- config gap=60
959
-
960
- theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
961
- theme success fill="#e8ffe8" stroke="#007700" color="#004400"
962
- theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
963
-
964
- bare page layout=row gap=60
965
- {
966
- markdown intro width=320 font=caveat padding=0
967
- """
968
- # Sketchmark
969
-
970
- A text-based diagram DSL that renders
971
- **hand-drawn** SVG diagrams using rough.js.
972
-
973
- ## Animation
974
-
975
- Every element supports **step-by-step**
976
- animation draw, highlight, fade, move.
977
- """
978
-
979
- group diagram layout=column gap=20 padding=30 theme=muted
980
- {
981
- box parser label="Parser" theme=primary width=130 height=52
982
- box scene label="Scene" theme=success width=130 height=52
983
- box render label="Renderer" theme=muted width=130 height=52
984
- }
985
- }
986
-
987
- parser --> scene label="AST"
988
- scene --> render label="SceneGraph"
989
-
990
- step draw intro
991
- step highlight parser
992
- step draw parser-->scene
993
- step highlight scene
994
- step draw scene-->render
995
- step highlight render
996
- end
997
- ```
998
-
999
- ---
1000
-
1001
- ### Charts
1002
-
1003
- ```
1004
- diagram
1005
- layout row
1006
- config gap=40
1007
-
1008
- bar-chart revenue label="Monthly Revenue" width=340 height=240
1009
- data
1010
- [
1011
- ["Month", "2023", "2024"],
1012
- ["Jan", 42000, 58000 ],
1013
- ["Feb", 38000, 61000 ],
1014
- ["Mar", 51000, 67000 ],
1015
- ["Apr", 46000, 72000 ]
1016
- ]
1017
-
1018
- pie-chart share label="Market Share" width=280 height=240
1019
- data
1020
- [
1021
- ["Product A", 42],
1022
- ["Product B", 31],
1023
- ["Product C", 27]
1024
- ]
1025
- end
1026
- ```
1027
-
1028
- ---
1029
-
1030
- ### Sketch theme
1031
-
1032
- ```
1033
- diagram
1034
- title label="System Architecture"
1035
- config theme=sketch
1036
- config font=caveat
1037
- layout row
1038
- config gap=60
1039
-
1040
- group root layout=row gap=60 padding=0
1041
- {
1042
- box client label="Client App" width=140 height=55
1043
- box gateway label="API Gateway" width=140 height=55
1044
-
1045
- group services layout=column gap=16 padding=30
1046
- {
1047
- box auth label="Auth Service" width=140 height=55
1048
- box billing label="Billing Service" width=140 height=55
1049
- }
1050
-
1051
- cylinder db label="PostgreSQL" width=140 height=65
1052
- }
1053
-
1054
- client --> gateway label="HTTPS"
1055
- gateway --> auth
1056
- gateway --> billing
1057
- auth --> db label="SQL"
1058
- billing --> db label="SQL"
1059
- end
1060
- ```
1061
-
1062
- ---
1063
-
1064
- ## License
1065
-
1066
- MIT
1
+ # Sketchmark
2
+
3
+ > Hand-drawn, rough-style diagrams from a plain-text DSL.
4
+ > Live examples: **https://sketchmark.dev/examples**
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Installation](#installation)
11
+ 2. [Quick Start](#quick-start)
12
+ 3. [Framework Setup](#framework-setup)
13
+ - [Plain HTML (CDN)](#plain-html-cdn)
14
+ - [Vite / Vanilla TS](#vite--vanilla-ts)
15
+ - [Next.js (App Router)](#nextjs-app-router)
16
+ 4. [DSL Syntax Overview](#dsl-syntax-overview)
17
+ 5. [Nodes](#nodes)
18
+ - [Shapes](#shapes)
19
+ - [Node Properties](#node-properties)
20
+ 6. [Edges (Connectors)](#edges-connectors)
21
+ 7. [Groups](#groups)
22
+ 8. [Tables](#tables)
23
+ 9. [Charts](#charts)
24
+ 10. [Markdown Blocks](#markdown-blocks)
25
+ 11. [Themes](#themes)
26
+ 12. [Fonts](#fonts)
27
+ 13. [Animation System](#animation-system)
28
+ - [Narration](#narration)
29
+ - [Annotations](#annotations)
30
+ - [Parallel Steps (Beat)](#parallel-steps-beat)
31
+ - [Pace](#pace)
32
+ - [Text-to-Speech](#text-to-speech)
33
+ - [Pointer / Cursor](#pointer--cursor)
34
+ 14. [Config Options](#config-options)
35
+ 15. [Export](#export)
36
+ 16. [Supported vs Unsupported Features](#supported-vs-unsupported-features)
37
+ 17. [Full DSL Reference Table](#full-dsl-reference-table)
38
+ 18. [Complete Example](#complete-example)
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install sketchmark
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quick Start
51
+
52
+ ```javascript
53
+ import { render } from 'sketchmark';
54
+
55
+ const dsl = `
56
+ diagram
57
+ box a label="Hello"
58
+ box b label="World"
59
+ a --> b label="greets"
60
+ end
61
+ `.trim();
62
+
63
+ const instance = render({
64
+ container: document.getElementById('diagram'),
65
+ dsl,
66
+ renderer: 'svg',
67
+ svgOptions: { showTitle: true, theme: 'light', transparent: true },
68
+ });
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Framework Setup
74
+
75
+ ### Plain HTML (CDN)
76
+
77
+ ```html
78
+ <!DOCTYPE html>
79
+ <html>
80
+ <head>
81
+ <!-- rough.js MUST load before sketchmark -->
82
+ <script src="https://unpkg.com/roughjs@4.6.6/bundled/rough.js"></script>
83
+ </head>
84
+ <body>
85
+ <div id="diagram"></div>
86
+
87
+ <script type="module">
88
+ import { render } from 'https://unpkg.com/sketchmark/dist/index.js';
89
+
90
+ const dsl = `
91
+ diagram
92
+ box a label="Client"
93
+ box b label="Server"
94
+ a --> b label="HTTP"
95
+ end
96
+ `.trim();
97
+
98
+ const instance = render({
99
+ container: document.getElementById('diagram'),
100
+ dsl,
101
+ renderer: 'svg',
102
+ svgOptions: { showTitle: true, interactive: true, theme: 'light', transparent: true },
103
+ });
104
+
105
+ // Animation controls
106
+ const { anim } = instance;
107
+ document.getElementById('btn-next').addEventListener('click', () => anim.next());
108
+ document.getElementById('btn-play').addEventListener('click', () => anim.play(700));
109
+ </script>
110
+ </body>
111
+ </html>
112
+ ```
113
+
114
+ ---
115
+
116
+ ### Vite / Vanilla TS
117
+
118
+ **`src/main.ts`**:
119
+
120
+ ```typescript
121
+ import { render } from 'sketchmark';
122
+
123
+
124
+ const dsl = `
125
+ diagram
126
+ title label="My Architecture"
127
+ layout row
128
+ config gap=60
129
+
130
+ box client label="Client App" width=140 height=55
131
+ box server label="API Server" width=140 height=55
132
+ cylinder db label="PostgreSQL" width=140 height=65
133
+
134
+ client --> server label="HTTPS"
135
+ server --> db label="SQL"
136
+
137
+ step highlight client
138
+ step draw client-->server
139
+ step highlight server
140
+ step draw server-->db
141
+ end
142
+ `.trim();
143
+
144
+ const instance = render({
145
+ container: document.getElementById('diagram') as HTMLElement,
146
+ dsl,
147
+ renderer: 'svg',
148
+ svgOptions: { showTitle: true, interactive: true, theme: 'light', transparent: true },
149
+ });
150
+
151
+ const { anim } = instance;
152
+ // Wire up buttons
153
+ document.getElementById('btn-next')!.addEventListener('click', () => { anim.next(); });
154
+ document.getElementById('btn-play')!.addEventListener('click', async () => { await anim.play(700); });
155
+ document.getElementById('btn-reset')!.addEventListener('click', () => { anim.reset(); });
156
+ ```
157
+
158
+ ---
159
+
160
+ ### Next.js (App Router)
161
+
162
+ Three rules that **must** all be followed:
163
+
164
+ **Rule 1 Component must be `'use client'`**:
165
+
166
+ ```tsx
167
+ // src/components/SketchmarkDiagram.tsx
168
+ 'use client';
169
+
170
+ import { useEffect, useRef } from 'react';
171
+ import { render } from 'sketchmark';
172
+
173
+ interface Props {
174
+ dsl: string;
175
+ showTitle?: boolean;
176
+ showControls?: boolean;
177
+ theme?: 'light' | 'dark';
178
+ }
179
+
180
+ export default function SketchmarkDiagram({ dsl, showTitle = true, theme = 'light' }: Props) {
181
+ const containerRef = useRef<HTMLDivElement>(null);
182
+
183
+ useEffect(() => {
184
+ const el = containerRef.current;
185
+ if (!el) return;
186
+ el.innerHTML = '';
187
+ render({
188
+ container: el,
189
+ dsl,
190
+ renderer: 'svg',
191
+ svgOptions: { showTitle, theme, transparent: true, interactive: true },
192
+ });
193
+ }, [dsl, showTitle, theme]);
194
+
195
+ return <div ref={containerRef} />;
196
+ }
197
+ ```
198
+
199
+ **Rule 3 — DSL strings must never have leading whitespace**:
200
+
201
+ ```typescript
202
+ // Correct use .trim()
203
+ const dsl = `
204
+ diagram
205
+ box a label="Hello"
206
+ box b label="World"
207
+ a --> b
208
+ end
209
+ `.trim();
210
+
211
+ // Wrong leading spaces break the parser
212
+ const dsl = `
213
+ diagram
214
+ box a label="Hello"
215
+ `.trim();
216
+ ```
217
+ ---
218
+
219
+ ## DSL Syntax Overview
220
+
221
+ Every diagram follows this structure:
222
+
223
+ ```
224
+ diagram
225
+ [title label="My Title"]
226
+ [layout row|column|grid]
227
+ [config key=value ...]
228
+ [theme name fill="..." stroke="..." color="..."]
229
+
230
+ [nodes, edges, groups, tables, charts, markdown blocks]
231
+
232
+ [step action target ...]
233
+ end
234
+ ```
235
+
236
+ - Lines starting with `#` or `//` are comments.
237
+ - All key-value attributes use the `key=value` or `key="quoted value"` syntax.
238
+ - The DSL is **whitespace-sensitive** — do not indent lines.
239
+
240
+ ---
241
+
242
+ ## Nodes
243
+
244
+ ### Shapes
245
+
246
+ Every node has the form:
247
+ ```
248
+ <shape> <id> [label="..."] [property=value ...]
249
+ ```
250
+
251
+ | Shape | DSL Keyword | Description | Auto-sizes to |
252
+ |-------|-------------|-------------|---------------|
253
+ | Box (default) | `box` | Rectangle | label width |
254
+ | Circle | `circle` | Ellipse | label width |
255
+ | Diamond | `diamond` | Rhombus (decision) | label width + padding |
256
+ | Hexagon | `hexagon` | Six-sided polygon | label width + padding |
257
+ | Triangle | `triangle` | Triangle | label width + padding |
258
+ | Cylinder | `cylinder` | Database drum shape | label width, fixed height 66px |
259
+ | Parallelogram | `parallelogram` | Slanted rectangle | label width + skew |
260
+ | Text | `text` | Label only, no border | wraps to width |
261
+ | Image | `image` | URL-loaded image | label width |
262
+ | Icon | `icon` | Iconify icon | 48×48 + label |
263
+ | Line | `line` | Horizontal rule | label width |
264
+ | Path | `path` | Custom SVG path data | user-specified |
265
+ | Note | `note` | Sticky-note shape | line count × line height |
266
+
267
+ ```
268
+ # Examples of each shape
269
+ box myBox label="A Box" width=140 height=55
270
+ circle myCirc label="A Circle" width=100
271
+ diamond myDia label="Decision?" width=150
272
+ hexagon myHex label="Process"
273
+ triangle myTri label="Start"
274
+ cylinder myDb label="PostgreSQL" height=65
275
+ parallelogram myPara label="I/O"
276
+ text myTxt label="Some prose" width=300
277
+ image myImg label="Logo" url="https://example.com/logo.png" width=120 height=60
278
+ icon myIcon label="Settings" name="mdi:cog"
279
+ line myLine label="Section" width=200
280
+ path myPath value="M 0 0 L 50 50 L 100 0 Z" width=100 height=60
281
+ note myNote label="Remember this!"
282
+ ```
283
+
284
+ ---
285
+
286
+ ### Node Properties
287
+
288
+ | Property | Type | Description | Example |
289
+ |----------|------|-------------|---------|
290
+ | `label` | string | Display text (required) | `label="Hello World"` |
291
+ | `width` | number | Override auto-width (px) | `width=140` |
292
+ | `height` | number | Override auto-height (px) | `height=55` |
293
+ | `theme` | string | Named theme preset | `theme=primary` |
294
+ | `fill` | CSS color | Background fill color | `fill="#e8f4ff"` |
295
+ | `stroke` | CSS color | Border/outline color | `stroke="#0044cc"` |
296
+ | `stroke-width` | number | Border thickness | `stroke-width=2` |
297
+ | `color` | CSS color | Text color | `color="#003399"` |
298
+ | `opacity` | 0–1 | Element opacity | `opacity=0.5` |
299
+ | `font-size` | number | Text size in px | `font-size=16` |
300
+ | `font-weight` | number/string | Font weight | `font-weight=700` |
301
+ | `font` | string | Font name (see Fonts section) | `font=caveat` |
302
+ | `text-align` | left/center/right | Horizontal text alignment | `text-align=left` |
303
+ | `vertical-align` | top/middle/bottom | Vertical text alignment | `vertical-align=top` |
304
+ | `line-height` | number | Line height multiplier | `line-height=1.5` |
305
+ | `letter-spacing` | number | Letter spacing in px | `letter-spacing=2` |
306
+ | `padding` | number | Inner padding (px) | `padding=12` |
307
+ | `dash` | numbers | Stroke dash pattern | `dash="6,3"` |
308
+ | `url` | URL string | Image URL (for `image` shape) | `url="https://..."` |
309
+ | `name` | string | Iconify icon name (for `icon`) | `name="mdi:cog"` |
310
+ | `value` | string | SVG path data (for `path`) | `value="M 0 0 L 50 0"` |
311
+ | `deg` | number | Static rotation (degrees) | `deg=45` |
312
+ | `dx` | number | Static X translation (px) | `dx=20` |
313
+ | `dy` | number | Static Y translation (px) | `dy=-10` |
314
+ | `factor` | number | Static scale factor | `factor=1.2` |
315
+
316
+ ---
317
+
318
+ ## Edges (Connectors)
319
+
320
+ Edges connect two node/group IDs:
321
+
322
+ ```
323
+ <from> <connector> <to> [label="..."] [style properties]
324
+ ```
325
+
326
+ ### Connector Syntax
327
+
328
+ | Connector | Direction | Dashed | Arrowhead |
329
+ |-----------|-----------|--------|-----------|
330
+ | `->` | Forward | No | Single end |
331
+ | `-->` | Forward | Yes | Single end |
332
+ | `<-` | Backward | No | Single start |
333
+ | `<--` | Backward | Yes | Single start |
334
+ | `<->` | Bidirectional | No | Both ends |
335
+ | `<-->` | Bidirectional | Yes | Both ends |
336
+ | `--` | None | No | No arrow |
337
+ | `---` | None | Yes | No arrow |
338
+
339
+ ```
340
+ # Edge examples
341
+ a -> b
342
+ a --> b label="Async call"
343
+ a <-> b
344
+ a <--> b label="Sync"
345
+ a -- b # line, no arrow
346
+ a --- b # dashed line, no arrow
347
+
348
+ # With style overrides
349
+ a --> b label="HTTPS" stroke="#cc0000" stroke-width=2 color="#aa0000" font-size=10
350
+ ```
351
+
352
+ ### Edge Style Properties
353
+
354
+ | Property | Description |
355
+ |----------|-------------|
356
+ | `label` | Text label floating on the edge |
357
+ | `stroke` | Line color |
358
+ | `stroke-width` | Line thickness |
359
+ | `color` | Label text color |
360
+ | `font-size` | Label font size |
361
+ | `font` | Label font family |
362
+ | `letter-spacing` | Label letter spacing |
363
+
364
+ ---
365
+
366
+ ## Groups
367
+
368
+ Groups visually contain one or more nodes, tables, or nested groups.
369
+
370
+ ```
371
+ group <id> [label="..."] [layout=row|column|grid] [gap=N] [padding=N]
372
+ [columns=N] [align=start|center|end]
373
+ [justify=start|center|end|space-between|space-around]
374
+ [theme=...] [fill="..."] [stroke="..."] [width=N] [height=N]
375
+ {
376
+ box a label="Node A"
377
+ box b label="Node B"
378
+ # nested groups allowed:
379
+ group inner label="Inner Group" layout=row { box c label="C" }
380
+ }
381
+ ```
382
+
383
+ ### Group Properties
384
+
385
+ | Property | Type | Description |
386
+ |----------|------|-------------|
387
+ | `label` | string | Group title (shown at top) |
388
+ | `layout` | row / column / grid | Child arrangement direction |
389
+ | `gap` | number | Space between children (px) |
390
+ | `padding` | number | Inner padding (px) |
391
+ | `columns` | number | Column count (for `layout=grid`) |
392
+ | `align` | start/center/end | Cross-axis alignment (align-items) |
393
+ | `justify` | start/center/end/space-between/space-around | Main-axis alignment |
394
+ | `width` | number | Minimum width override |
395
+ | `height` | number | Minimum height override |
396
+ | `theme` | string | Named theme preset |
397
+ | `fill` | CSS color | Background color |
398
+ | `stroke` | CSS color | Border color |
399
+ | `stroke-width` | number | Border thickness |
400
+
401
+ #### `bare` keyword
402
+
403
+ `bare` is an alias for a group with no visible border or fill:
404
+
405
+ ```
406
+ bare myContainer {
407
+ box a label="Floating A"
408
+ box b label="Floating B"
409
+ }
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Tables
415
+
416
+ ```
417
+ table <id> [label="..."] [theme=...] [fill="..."] [stroke="..."]
418
+ {
419
+ header Col1 Col2 Col3
420
+ row "Value A" "Value B" "Value C"
421
+ row "Value D" "Value E" "Value F"
422
+ }
423
+ ```
424
+
425
+ - `header` rows get a shaded background and bold text.
426
+ - `row` rows use regular styling.
427
+ - `"value"` must use a double-quoted string literal.
428
+ - Column widths auto-size to content.
429
+ - Tables support `fill`, `stroke`, `color`, `font-size`, `font`, `text-align`, `letter-spacing`, `theme`, `opacity` style props (same as nodes).
430
+
431
+ ---
432
+
433
+ ## Charts
434
+
435
+ ```
436
+ <chart-type> <id> [label="Title"] [width=N] [height=N] [theme=...] [style props]
437
+ data
438
+ [["Category", "Series1", "Series2"],
439
+ ["Jan", 120, 80],
440
+ ["Feb", 150, 90]]
441
+ ```
442
+
443
+ ### Chart Types
444
+
445
+ | DSL Keyword | Chart | Notes |
446
+ |-------------|-------|-------|
447
+ | `bar-chart` | Grouped bar chart | Multiple series supported |
448
+ | `line-chart` | Line chart | Multiple series supported |
449
+ | `area-chart` | Area/filled line chart | Multiple series supported |
450
+ | `pie-chart` | Pie chart | `["Label", value]` rows |
451
+ | `donut-chart` | Donut chart | Same data as pie |
452
+ | `scatter-chart` | Scatter plot | `["Label", x, y]` rows |
453
+
454
+ ```
455
+ # Bar chart example
456
+ bar-chart sales label="Monthly Sales" width=400 height=280
457
+ data
458
+ [["Month", "Revenue", "Cost"],
459
+ ["Jan", 1200, 800],
460
+ ["Feb", 1500, 900],
461
+ ["Mar", 1100, 750]]
462
+
463
+ # Pie chart example
464
+ pie-chart share label="Market Share" width=300 height=240
465
+ data
466
+ [["Company", "Share"],
467
+ ["Alpha", 42],
468
+ ["Beta", 31],
469
+ ["Gamma", 27]]
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Markdown Blocks
475
+
476
+ Renders inline rich text with headings and bold/italic:
477
+
478
+ ```
479
+ markdown <id> [width=N] [height=N] [theme=...] [style props]
480
+ """
481
+ # Heading 1
482
+ ## Heading 2
483
+ ### Heading 3
484
+
485
+ Normal paragraph with **bold** and *italic* text.
486
+
487
+ Another paragraph here.
488
+ """
489
+ ```
490
+
491
+ - Triple-quote `"""` delimiters for the content block.
492
+ - Supported formatting: `# H1`, `## H2`, `### H3`, `**bold**`, `*italic*`, blank lines.
493
+ - Style props: `color`, `font`, `font-size`, `text-align`, `padding`, `fill`, `stroke`, `opacity`, `letter-spacing`.
494
+
495
+ ---
496
+
497
+ ## Themes
498
+
499
+ ### Global Palette Themes
500
+
501
+ Activate via `config theme=<name>` in the DSL.
502
+
503
+ | Theme Name | Description |
504
+ |------------|-------------|
505
+ | `light` | Warm parchment (default) |
506
+ | `dark` | Dark warm brown |
507
+ | `sketch` | Neutral grey pencil |
508
+ | `ocean` | Blue tones |
509
+ | `forest` | Green tones |
510
+ | `sunset` | Orange/red warm |
511
+ | `slate` | Cool blue-grey (like Tailwind) |
512
+ | `rose` | Pink/rose |
513
+ | `midnight` | GitHub dark-style |
514
+
515
+ ```
516
+ # Activate in DSL
517
+ config theme=ocean
518
+
519
+ # Or pass as render option
520
+ render({ ..., svgOptions: { theme: 'dark' } });
521
+ # 'auto' follows system prefers-color-scheme
522
+ render({ ..., svgOptions: { theme: 'auto' } });
523
+ ```
524
+
525
+ ### Named Custom Themes (per-element)
526
+
527
+ Define a named theme and apply it to any element with `theme=<name>`:
528
+
529
+ ```
530
+ theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
531
+ theme success fill="#e8ffe8" stroke="#007700" color="#004400"
532
+ theme warning fill="#fff9e6" stroke="#f0a500" color="#7a5000"
533
+ theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
534
+
535
+ box client label="Client" theme=primary
536
+ box server label="Server" theme=warning
537
+ cylinder db label="DB" theme=success
538
+ group services label="Services" theme=muted { ... }
539
+ ```
540
+
541
+ ---
542
+
543
+ ## Fonts
544
+
545
+ Set globally with `config font=<name>` or per-element with `font=<name>`.
546
+
547
+ | Font Name | Family | Type |
548
+ |-----------|--------|------|
549
+ | `caveat` | Caveat | Hand-drawn cursive |
550
+ | `handlee` | Handlee | Hand-drawn cursive |
551
+ | `indie-flower` | Indie Flower | Hand-drawn cursive |
552
+ | `patrick-hand` | Patrick Hand | Hand-drawn cursive |
553
+ | `dm-mono` | DM Mono | Monospace |
554
+ | `jetbrains` | JetBrains Mono | Monospace |
555
+ | `instrument` | Instrument Serif | Serif |
556
+ | `playfair` | Playfair Display | Serif |
557
+ | `system` | system-ui, sans-serif | System (default) |
558
+ | `mono` | Courier New | Monospace |
559
+ | `serif` | Georgia | Serif |
560
+
561
+ ```
562
+ # Global font
563
+ config font=caveat
564
+
565
+ # Per-element font
566
+ box a label="Handwritten" font=caveat
567
+ box b label="Mono" font=dm-mono
568
+ ```
569
+
570
+ You can also pass any valid CSS font-family string directly.
571
+
572
+ ---
573
+
574
+ ## Animation System
575
+
576
+ ### Step Syntax
577
+
578
+ ```
579
+ step <action> <target> [options]
580
+ ```
581
+
582
+ - `<target>` is a node/group/table/chart/markdown ID, or an edge in `from-->to` format.
583
+ - Steps play in sequence via `anim.next()` or `anim.play(msPerStep)`.
584
+
585
+ ### Animation Actions
586
+
587
+ | Action | Target | Options | Description |
588
+ |--------|--------|---------|-------------|
589
+ | `highlight` | node/edge/group | | Pulsing glow highlight (only one element at a time) |
590
+ | `draw` | node/edge/group/table/chart/markdown | `duration=N` | Animated stroke-drawing reveal with text writing effect |
591
+ | `fade` | node/edge/group | | Fade to 22% opacity |
592
+ | `unfade` | node/edge/group | | Restore from fade |
593
+ | `erase` | node/edge/group | `duration=N` | Fade to invisible (opacity 0) |
594
+ | `show` | node/edge/group | `duration=N` | Fade to visible |
595
+ | `hide` | node/edge/group | `duration=N` | Fade to hidden |
596
+ | `pulse` | node/edge/group | `duration=N` | One-shot brightness pulse |
597
+ | `move` | node | `dx=N dy=N duration=N` | Translate by (dx, dy) px |
598
+ | `scale` | node | `factor=N duration=N` | Scale to factor (absolute) |
599
+ | `rotate` | node | `deg=N duration=N` | Rotate by deg (cumulative) |
600
+ | `color` | node/edge | `fill="#..."` or `color="#..."` | Change fill/stroke color |
601
+ | `narrate` | | `"text"` | Show a caption with typing effect |
602
+ | `circle` | node | | Draw a rough circle annotation around element |
603
+ | `underline` | node | | Draw a rough underline below element |
604
+ | `crossout` | node | — | Draw rough diagonal cross-out lines |
605
+ | `bracket` | node node | — | Draw a rough curly brace spanning two elements |
606
+
607
+ ### Narration
608
+
609
+ Show captions like a teacher explaining on a blackboard. The caption appears as a floating bar at the bottom center of the viewport with a typing effect.
610
+
611
+ ```
612
+ step narrate "Plants need sunlight to make food"
613
+ step narrate "This is the most important step" pace=slow
614
+ ```
615
+
616
+ - Caption is rendered as a fixed-position `<div>` on `document.body` (independent of diagram pan/zoom).
617
+ - Access via `anim.captionElement` to reparent it anywhere.
618
+ - Supports built-in browser text-to-speech (see [TTS](#text-to-speech)).
619
+
620
+ ### Annotations
621
+
622
+ Hand-drawn annotation marks powered by rough.js. A clean guide path draws in first, then the rough sketch fades in. If `config pointer=chalk|dot|hand` is set, a pointer follows the annotation stroke.
623
+
624
+ ```
625
+ step circle leaf # circle around "leaf" node
626
+ step underline sun # underline below "sun" node
627
+ step crossout wrong # X through "wrong" node
628
+ step bracket sun leaf # curly brace spanning "sun" and "leaf"
629
+ ```
630
+
631
+ Annotations require rough.js to be loaded. They are drawn into a dedicated SVG layer on top of the diagram.
632
+
633
+ ### Parallel Steps (Beat)
634
+
635
+ Fire multiple steps simultaneously with `beat { }`:
636
+
637
+ ```
638
+ beat {
639
+ step draw sun
640
+ step draw co2
641
+ step narrate "Both appear at once"
642
+ }
643
+ ```
644
+
645
+ - All children fire at the same time.
646
+ - Playback waits for the longest child to finish before advancing.
647
+ - Beats can contain any step action including `narrate` and annotations.
648
+
649
+ ### Pace
650
+
651
+ Control timing per step with `pace=slow|fast|pause`:
652
+
653
+ ```
654
+ step draw sun pace=slow # 2× slower
655
+ step draw leaf pace=fast # 2× faster
656
+ step narrate "Key point" pace=pause # extra 1.5s hold after step
657
+ ```
658
+
659
+ | Pace | Effect |
660
+ |------|--------|
661
+ | `slow` | Duration × 2.0 |
662
+ | `fast` | Duration × 0.5 |
663
+ | `pause` | Adds 1500ms hold |
664
+
665
+ ### Step Options
666
+
667
+ | Option | Description | Default |
668
+ |--------|-------------|---------|
669
+ | `duration=N` | Animation duration in ms | varies by type |
670
+ | `delay=N` | Delay before this step fires (ms) | 0 |
671
+ | `pace=slow\|fast\|pause` | Timing modifier | — |
672
+ | `dx=N` | X translation for `move` | 0 |
673
+ | `dy=N` | Y translation for `move` | 0 |
674
+ | `factor=N` | Scale factor for `scale` | 1 |
675
+ | `deg=N` | Rotation degrees for `rotate` | 0 |
676
+ | `fill="..."` | New color for `color` action | — |
677
+ | `color="..."` | Alias for fill in `color` action | — |
678
+
679
+ ### Animation Examples
680
+
681
+ ```
682
+ # Draw edges incrementally
683
+ step draw client-->server
684
+ step highlight server
685
+ step draw server-->db
686
+ step highlight db
687
+
688
+ # Narration with pacing
689
+ step narrate "The client sends a request" pace=slow
690
+ step draw client-->server
691
+ step narrate "The server processes it"
692
+
693
+ # Annotations
694
+ step circle server
695
+ step underline db
696
+ step bracket server db
697
+
698
+ # Parallel steps
699
+ beat {
700
+ step draw sun
701
+ step draw moon
702
+ }
703
+
704
+ # Move a node
705
+ step move myBox dx=100 dy=0 duration=500
706
+
707
+ # Scale up then back
708
+ step scale myBox factor=1.5 duration=300
709
+ step scale myBox factor=1 duration=300
710
+
711
+ # Rotate
712
+ step rotate myBox deg=45 duration=400
713
+
714
+ # Change color
715
+ step color myBox fill="#ff0000"
716
+
717
+ # Show/hide
718
+ step hide myBox duration=400
719
+ step show myBox duration=400
720
+
721
+ # Fade background nodes
722
+ step fade nodeA
723
+ step unfade nodeA
724
+
725
+ # Pulse a node
726
+ step pulse myBox duration=600
727
+
728
+ # Delay before a step
729
+ step highlight server delay=500
730
+ ```
731
+
732
+ ### JavaScript Animation API
733
+
734
+ ```javascript
735
+ const { anim } = render({ ... });
736
+
737
+ // Properties
738
+ anim.total // number of steps
739
+ anim.currentStep // current step index (-1 = before start)
740
+ anim.canNext // boolean
741
+ anim.canPrev // boolean
742
+ anim.atEnd // boolean
743
+ anim.captionElement // HTMLDivElement | null — the narration caption element
744
+ anim.tts // boolean text-to-speech enabled/disabled
745
+
746
+ // Methods
747
+ anim.next() // advance one step (returns bool)
748
+ anim.prev() // go back one step (returns bool)
749
+ anim.reset() // reset to before step 0
750
+ anim.goTo(index) // jump to step N
751
+ await anim.play(700) // play all remaining steps (700ms between)
752
+ anim.destroy() // remove caption, annotations, pointer from DOM
753
+
754
+ // Toggle TTS programmatically
755
+ anim.tts = true; // enable browser speech
756
+ anim.tts = false; // disable (stops current speech)
757
+
758
+ // Reparent the narration caption to a custom container
759
+ myDiv.appendChild(anim.captionElement);
760
+
761
+ // Event listener
762
+ const unsub = anim.on((event) => {
763
+ console.log(event.type); // 'step-change' | 'animation-start' | 'animation-end' | 'animation-reset'
764
+ console.log(event.stepIndex); // number
765
+ console.log(event.step); // ASTStepItem (ASTStep | ASTBeat)
766
+ console.log(event.total); // total steps
767
+ });
768
+ unsub(); // unsubscribe
769
+ ```
770
+
771
+ ### Text-to-Speech
772
+
773
+ Enable browser-native speech synthesis for narrate steps:
774
+
775
+ ```
776
+ # In DSL
777
+ config tts=on
778
+ ```
779
+
780
+ ```javascript
781
+ // Or via API
782
+ anim.tts = true;
783
+
784
+ // Custom TTS (e.g. ElevenLabs) via event listener
785
+ anim.tts = false; // disable built-in
786
+ anim.on((e) => {
787
+ if (e.step?.kind === 'step' && e.step.action === 'narrate') {
788
+ myTTSService.speak(e.step.value);
789
+ }
790
+ });
791
+ ```
792
+
793
+ Speech cancels automatically on `reset()`, `prev()`, `destroy()`, or when a new narrate step fires.
794
+
795
+ ### Pointer / Cursor
796
+
797
+ Show a pointer that follows annotation strokes (circle, underline, crossout, bracket):
798
+
799
+ ```
800
+ config pointer=chalk # white dot with outline
801
+ config pointer=dot # colored dot
802
+ config pointer=hand # hand cursor
803
+ ```
804
+
805
+ The pointer only appears during annotation steps it follows the guide path as the annotation draws in, then fades out.
806
+
807
+ ### Pre-hidden Elements (Draw Targets)
808
+
809
+ Any element targeted by a `step draw` action starts **hidden** and only appears when that step fires. Elements NOT targeted by `draw` are always visible.
810
+
811
+ ---
812
+
813
+ ## Config Options
814
+
815
+ Set in DSL with `config key=value`:
816
+
817
+ | Key | Description | Default |
818
+ |-----|-------------|---------|
819
+ | `theme` | Global palette name | `light` |
820
+ | `font` | Global font name | `system` |
821
+ | `gap` | Space between root-level items (px) | `80` |
822
+ | `margin` | Canvas outer margin (px) | `60` |
823
+ | `columns` | Column count for `layout=grid` | `1` |
824
+ | `title-color` | Diagram title text color | palette default |
825
+ | `title-size` | Diagram title font size | `18` |
826
+ | `title-weight` | Diagram title font weight | `600` |
827
+ | `pointer` | Annotation pointer type | `none` |
828
+ | `tts` | Enable browser text-to-speech | `off` |
829
+
830
+ ```
831
+ diagram
832
+ title label="My System"
833
+ layout row
834
+ config gap=60
835
+ config margin=40
836
+ config theme=ocean
837
+ config font=caveat
838
+ config pointer=chalk
839
+ config tts=on
840
+ config title-size=24
841
+ config title-color="#001f5b"
842
+ ```
843
+
844
+ ---
845
+
846
+ ## Export
847
+
848
+ ### From JavaScript API
849
+
850
+ ```javascript
851
+ const instance = render({ ... });
852
+
853
+ // SVG file download
854
+ instance.exportSVG('my-diagram.svg');
855
+
856
+ // PNG file download
857
+ await instance.exportPNG('my-diagram.png');
858
+
859
+ // Advanced: get SVG string
860
+ import { getSVGString } from 'sketchmark';
861
+ const svgString = getSVGString(instance.svg);
862
+
863
+ // Advanced: get PNG data URL
864
+ import { svgToPNGDataURL } from 'sketchmark';
865
+ const dataUrl = await svgToPNGDataURL(instance.svg, { scale: 2, background: '#ffffff' });
866
+
867
+ // Self-contained HTML file
868
+ import { exportHTML } from 'sketchmark';
869
+ exportHTML(instance.svg, dslSource, { filename: 'diagram.html' });
870
+ ```
871
+
872
+ ### Export Options
873
+
874
+ | Option | Type | Description |
875
+ |--------|------|-------------|
876
+ | `filename` | string | Download filename |
877
+ | `scale` | number | PNG pixel density (default: 2 = @2x) |
878
+ | `background` | CSS color | PNG background color (default: `#f8f4ea`) |
879
+
880
+ ### Available Export Functions
881
+
882
+ | Function | Output | Status |
883
+ |----------|--------|--------|
884
+ | `exportSVG(svg, opts)` | `.svg` file download | ✅ Stable |
885
+ | `exportPNG(svg, opts)` | `.png` file download | ✅ Stable |
886
+ | `exportHTML(svg, dsl, opts)` | Self-contained `.html` | ✅ Stable |
887
+ | `exportCanvasPNG(canvas, opts)` | `.png` from canvas renderer | ✅ Stable |
888
+ | `getSVGString(svg)` | SVG string | ✅ Stable |
889
+ | `getSVGBlob(svg)` | SVG Blob | ✅ Stable |
890
+ | `svgToPNGDataURL(svg, opts)` | PNG data URL string | ✅ Stable |
891
+ | `exportGIF(frames, opts)` | GIF (requires gifshot) | ⚠️ Stub only |
892
+ | `exportMP4(canvas, dur, opts)` | WebM via MediaRecorder | ⚠️ Stub only |
893
+
894
+ ---
895
+
896
+ ## Supported vs Unsupported Features
897
+
898
+ ### Nodes
899
+
900
+ | Feature | Supported | Notes |
901
+ |---------|-----------|-------|
902
+ | box | ✅ | Default shape |
903
+ | circle | ✅ | |
904
+ | diamond | ✅ | |
905
+ | hexagon | ✅ | |
906
+ | triangle | ✅ | |
907
+ | cylinder | ✅ | |
908
+ | parallelogram | ✅ | |
909
+ | text | ✅ | Auto word-wraps |
910
+ | image (URL) | ✅ | Cross-origin images |
911
+ | icon (Iconify) | ✅ | Uses Iconify API |
912
+ | line | ✅ | Horizontal rule with label |
913
+ | path (SVG path data) | ✅ | Raw SVG `d` attribute |
914
+ | note | | Sticky-note shape |
915
+ | Multiline label (`\n`) | ✅ | Use `\n` in label strings |
916
+ | Per-node font override | ✅ | |
917
+ | Per-node opacity | ✅ | |
918
+ | Per-node static transform (deg/dx/dy/factor) | ✅ | Set at parse time |
919
+ | Rounded corners | ❌ | Not configurable |
920
+ | Custom SVG shapes | | Only `path` workaround |
921
+ | Rich text inside nodes | ❌ | Plain text only |
922
+
923
+ ### Edges
924
+
925
+ | Feature | Supported | Notes |
926
+ |---------|-----------|-------|
927
+ | Single arrow `->` / `-->` | ✅ | |
928
+ | Reverse arrow `<-` / `<--` | ✅ | |
929
+ | Bidirectional `<->` / `<-->` | ✅ | |
930
+ | No arrow `--` / `---` | ✅ | |
931
+ | Dashed lines (`--`, `---`, `<-->`) | ✅ | |
932
+ | Edge labels | ✅ | |
933
+ | Edge color/stroke override | ✅ | |
934
+ | Self-loops | ❌ | |
935
+ | Curved/bezier edges | ❌ | Straight lines only |
936
+ | Waypoints / routing control | ❌ | Auto-routed |
937
+ | Multiple edges between same nodes | ✅ | Stack visually |
938
+ | Edge from/to groups | ✅ | Uses group center |
939
+
940
+ ### Groups
941
+
942
+ | Feature | Supported | Notes |
943
+ |---------|-----------|-------|
944
+ | Nested groups | ✅ | Unlimited depth |
945
+ | Row / column / grid layout | ✅ | |
946
+ | justify-content variants | ✅ | start, center, end, space-between, space-around |
947
+ | align-items variants | ✅ | start, center, end |
948
+ | Fixed width/height | ✅ | Minimum size override |
949
+ | `bare` (invisible group) | ✅ | |
950
+ | Scrolling | ❌ | |
951
+
952
+ ### Charts
953
+
954
+ | Feature | Supported | Notes |
955
+ |---------|-----------|-------|
956
+ | Bar chart (grouped) | ✅ | Multiple series |
957
+ | Line chart | ✅ | Multiple series |
958
+ | Area chart | ✅ | Multiple series |
959
+ | Pie chart | ✅ | |
960
+ | Donut chart | ✅ | |
961
+ | Scatter plot | ✅ | |
962
+ | Axes and tick labels | ✅ | Auto-generated Y axis |
963
+ | Legend | ✅ | Auto-generated |
964
+ | X-axis labels | ✅ | |
965
+ | Interactive tooltips | ❌ | |
966
+ | Stacked bars | ❌ | |
967
+ | Custom colors per series | ❌ | Uses built-in palette |
968
+ | Logarithmic scale | ❌ | |
969
+
970
+ ### Animation
971
+
972
+ | Feature | Supported | Notes |
973
+ |---------|-----------|-------|
974
+ | highlight | ✅ | Pulsing glow |
975
+ | draw (nodes) | ✅ | Guide-path stroke reveal + text writing effect |
976
+ | draw (edges) | ✅ | Animated line draw |
977
+ | draw (groups) | ✅ | |
978
+ | draw (tables) | ✅ | |
979
+ | draw (charts) | | Fade-in |
980
+ | draw (markdown) | ✅ | Fade-in |
981
+ | fade / unfade | ✅ | |
982
+ | show / hide | ✅ | |
983
+ | erase | | |
984
+ | pulse | ✅ | |
985
+ | move | ✅ | CSS transform translate |
986
+ | scale | ✅ | CSS transform scale |
987
+ | rotate | ✅ | CSS transform rotate |
988
+ | color | ✅ | Dynamic fill/stroke color change |
989
+ | narrate | ✅ | Typing-effect captions |
990
+ | circle annotation | ✅ | Rough circle around element |
991
+ | underline annotation | ✅ | Rough underline below element |
992
+ | crossout annotation | ✅ | Rough X through element |
993
+ | bracket annotation | ✅ | Rough curly brace spanning two elements |
994
+ | pace (slow/fast/pause) | ✅ | Per-step timing control |
995
+ | parallel steps (beat) | ✅ | `beat { }` fires children simultaneously |
996
+ | pointer / cursor | ✅ | Follows annotation strokes (chalk/dot/hand) |
997
+ | text-to-speech | ✅ | Browser `speechSynthesis` API |
998
+ | text writing animation | ✅ | Left-to-right clipPath reveal on draw |
999
+ | delay per step | ✅ | `delay=N` ms |
1000
+ | custom duration | ✅ | `duration=N` ms |
1001
+ | Canvas renderer animation | ❌ | SVG renderer only |
1002
+ | Click-triggered steps | ❌ | Parsed but not implemented |
1003
+
1004
+ ### Export
1005
+
1006
+ | Feature | Supported |
1007
+ |---------|-----------|
1008
+ | SVG download | |
1009
+ | PNG download (via canvas) | ✅ |
1010
+ | HTML (self-contained) | ✅ |
1011
+ | SVG string / Blob | ✅ |
1012
+ | GIF | ❌ (stub) |
1013
+ | MP4/WebM | ❌ (stub) |
1014
+
1015
+ ---
1016
+
1017
+ ## Full DSL Reference Table
1018
+
1019
+ | Keyword | Category | Example |
1020
+ |---------|----------|---------|
1021
+ | `diagram` | Structure | `diagram` |
1022
+ | `end` | Structure | `end` |
1023
+ | `title` | Meta | `title label="My Diagram"` |
1024
+ | `description` | Meta | `description "Some text"` |
1025
+ | `layout` | Meta | `layout row` / `layout column` / `layout grid` |
1026
+ | `config` | Meta | `config gap=60` |
1027
+ | `theme` | Styling | `theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"` |
1028
+ | `style` | Styling | `style nodeId fill="#ff0" stroke="#000"` |
1029
+ | `box` | Node | `box myId label="Label" width=120 height=50` |
1030
+ | `circle` | Node | `circle myId label="Label"` |
1031
+ | `diamond` | Node | `diamond myId label="Decision?"` |
1032
+ | `hexagon` | Node | `hexagon myId label="Process"` |
1033
+ | `triangle` | Node | `triangle myId label="Start"` |
1034
+ | `cylinder` | Node | `cylinder myId label="DB" height=65` |
1035
+ | `parallelogram` | Node | `parallelogram myId label="I/O"` |
1036
+ | `text` | Node | `text myId label="Plain text" width=300` |
1037
+ | `image` | Node | `image myId label="Logo" url="https://..."` |
1038
+ | `icon` | Node | `icon myId label="Settings" name="mdi:cog"` |
1039
+ | `line` | Node | `line myId label="Divider" width=400` |
1040
+ | `path` | Node | `path myId value="M 0 0 L 100 0 L 50 80 Z"` |
1041
+ | `note` | Node | `note myId label="Sticky note text"` |
1042
+ | `->` | Edge | `a -> b label="call"` |
1043
+ | `-->` | Edge | `a --> b` |
1044
+ | `<-` | Edge | `a <- b` |
1045
+ | `<--` | Edge | `a <-- b` |
1046
+ | `<->` | Edge | `a <-> b` |
1047
+ | `<-->` | Edge | `a <--> b` |
1048
+ | `--` | Edge | `a -- b` |
1049
+ | `---` | Edge | `a --- b` |
1050
+ | `group` | Group | `group myGroup label="Services" layout=column { ... }` |
1051
+ | `bare` | Group | `bare myWrap { ... }` |
1052
+ | `table` | Table | `table myTable label="Users" { header Name Age }` |
1053
+ | `bar-chart` | Chart | `bar-chart sales label="Sales" data [...]` |
1054
+ | `line-chart` | Chart | `line-chart trend data [...]` |
1055
+ | `pie-chart` | Chart | `pie-chart share data [...]` |
1056
+ | `donut-chart` | Chart | `donut-chart share data [...]` |
1057
+ | `scatter-chart` | Chart | `scatter-chart pts data [...]` |
1058
+ | `area-chart` | Chart | `area-chart filled data [...]` |
1059
+ | `markdown` | Markdown | `markdown md1 """ # Title ... """` |
1060
+ | `step highlight` | Animation | `step highlight nodeId` |
1061
+ | `step draw` | Animation | `step draw nodeId` / `step draw a-->b` |
1062
+ | `step fade` | Animation | `step fade nodeId` |
1063
+ | `step unfade` | Animation | `step unfade nodeId` |
1064
+ | `step erase` | Animation | `step erase nodeId duration=400` |
1065
+ | `step show` | Animation | `step show nodeId duration=300` |
1066
+ | `step hide` | Animation | `step hide nodeId duration=300` |
1067
+ | `step pulse` | Animation | `step pulse nodeId duration=500` |
1068
+ | `step move` | Animation | `step move nodeId dx=100 dy=0 duration=400` |
1069
+ | `step scale` | Animation | `step scale nodeId factor=1.5 duration=300` |
1070
+ | `step rotate` | Animation | `step rotate nodeId deg=90 duration=400` |
1071
+ | `step color` | Animation | `step color nodeId fill="#ff0000"` |
1072
+ | `step narrate` | Animation | `step narrate "Caption text" pace=slow` |
1073
+ | `step circle` | Animation | `step circle nodeId` |
1074
+ | `step underline` | Animation | `step underline nodeId` |
1075
+ | `step crossout` | Animation | `step crossout nodeId` |
1076
+ | `step bracket` | Animation | `step bracket nodeId1 nodeId2` |
1077
+ | `beat` | Animation | `beat { step draw a \n step draw b }` |
1078
+ | `pace` | Animation | `step draw nodeId pace=slow\|fast\|pause` |
1079
+
1080
+ ---
1081
+
1082
+ ## Complete Example
1083
+
1084
+ This example demonstrates most features including narration, annotations, beats, pacing, and pointer:
1085
+
1086
+ ```
1087
+ diagram
1088
+ title label="How the Internet Delivers a Webpage"
1089
+ layout row
1090
+ config gap=50
1091
+ config pointer=chalk
1092
+ config tts=on
1093
+
1094
+ # Define named themes
1095
+ theme primary fill="#e8f4ff" stroke="#0044cc" color="#003399"
1096
+ theme success fill="#e8ffe8" stroke="#007700" color="#004400"
1097
+ theme warning fill="#fff9e6" stroke="#f0a500" color="#7a5000"
1098
+ theme muted fill="#f5f5f5" stroke="#999999" color="#444444"
1099
+
1100
+ box you label="You" theme=warning width=120 height=50
1101
+ box browser label="Browser" theme=primary width=120 height=50
1102
+ box dns label="DNS\nServer" theme=muted width=120 height=55
1103
+ box server label="Web\nServer" theme=success width=120 height=55
1104
+ box html label="HTML\nCSS JS" theme=primary width=120 height=55
1105
+ box screen label="Rendered\nPage" theme=warning width=120 height=55
1106
+
1107
+ you --> browser label="types URL"
1108
+ browser --> dns label="lookup"
1109
+ dns --> browser label="IP address"
1110
+ browser --> server label="request"
1111
+ server --> html label="responds"
1112
+ html --> screen label="renders"
1113
+
1114
+ # Animation with narration, annotations, beats, and pacing
1115
+ step narrate "You type a website address into your browser" pace=slow
1116
+ step draw you
1117
+ step draw browser
1118
+ step draw you-->browser
1119
+ step underline you
1120
+ step narrate "The browser asks a DNS server — the internet's phone book"
1121
+ step draw dns
1122
+ step draw browser-->dns
1123
+ step circle dns
1124
+ step narrate "DNS translates the domain name into an IP address"
1125
+ step draw dns-->browser
1126
+ step narrate "Now the browser knows WHERE to go" pace=slow
1127
+ beat {
1128
+ step draw server
1129
+ step draw browser-->server
1130
+ }
1131
+ step narrate "It sends a request to the web server at that address"
1132
+ step underline server
1133
+ step narrate "The server responds with HTML, CSS, and JavaScript" pace=slow
1134
+ beat {
1135
+ step draw html
1136
+ step draw server-->html
1137
+ }
1138
+ step circle html
1139
+ step narrate "The browser assembles everything into the page you see"
1140
+ step draw html-->screen
1141
+ step draw screen pace=slow
1142
+ step bracket html screen
1143
+ step narrate "All of this happens in under a second!" pace=pause
1144
+ end
1145
+ ```
1146
+
1147
+ ---
1148
+
1149
+ ## `render()` API Reference
1150
+
1151
+ ```typescript
1152
+ render(options: RenderOptions): DiagramInstance
1153
+
1154
+ interface RenderOptions {
1155
+ container: string | HTMLElement | SVGSVGElement; // CSS selector or element
1156
+ dsl: string; // DSL source text
1157
+ renderer?: 'svg' | 'canvas'; // default: 'svg'
1158
+ injectCSS?: boolean; // inject animation CSS (default: true)
1159
+ svgOptions?: SVGRendererOptions;
1160
+ canvasOptions?: CanvasRendererOptions;
1161
+ onNodeClick?: (nodeId: string) => void; // click handler
1162
+ onReady?: (anim, svg?) => void; // callback after render
1163
+ }
1164
+
1165
+ interface SVGRendererOptions {
1166
+ roughness?: number; // default 1.3
1167
+ bowing?: number; // default 0.7
1168
+ showTitle?: boolean; // show diagram title
1169
+ interactive?: boolean; // enable hover/click
1170
+ onNodeClick?: (id: string) => void;
1171
+ theme?: 'light' | 'dark' | 'auto';
1172
+ transparent?: boolean; // transparent background
1173
+ }
1174
+
1175
+ interface DiagramInstance {
1176
+ scene: SceneGraph;
1177
+ anim: AnimationController;
1178
+ svg?: SVGSVGElement;
1179
+ canvas?: HTMLCanvasElement;
1180
+ update: (dsl: string) => DiagramInstance; // re-render with new DSL
1181
+ exportSVG: (filename?: string) => void;
1182
+ exportPNG: (filename?: string) => Promise<void>;
1183
+ }
1184
+ ```
1185
+
1186
+ ---
1187
+
1188
+ ## Important Gotchas
1189
+
1190
+ 1. **DSL must not be indented** — every DSL line must start at column 0 (no leading spaces/tabs).
1191
+ 2. **Always call `.trim()`** on template literals to strip the leading newline from the opening backtick.
1192
+ 3. **Animation only works with SVG renderer** — the canvas renderer does not support animated steps.
1193
+ 4. **`step draw` makes elements start hidden** — any element you intend to `draw` will be invisible until its step fires.
1194
+ 5. **Node IDs must be unique** — duplicate IDs are silently deduplicated (only first definition kept).
1195
+ 6. **Group children inherit group's coordinate space** — edges can connect across group boundaries using the node/group ID directly.