markgraphy 0.1.1

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 (166) hide show
  1. package/LICENSE.txt +21 -0
  2. package/README.md +154 -0
  3. package/dist/animated/GraphAgni.svelte +430 -0
  4. package/dist/animated/GraphAgni.svelte.d.ts +22 -0
  5. package/dist/animated/GraphAum.svelte +294 -0
  6. package/dist/animated/GraphAum.svelte.d.ts +20 -0
  7. package/dist/animated/GraphBoot.svelte +325 -0
  8. package/dist/animated/GraphBoot.svelte.d.ts +28 -0
  9. package/dist/animated/GraphCron.svelte +476 -0
  10. package/dist/animated/GraphCron.svelte.d.ts +42 -0
  11. package/dist/animated/GraphDamru.svelte +341 -0
  12. package/dist/animated/GraphDamru.svelte.d.ts +18 -0
  13. package/dist/animated/GraphDeps.svelte +245 -0
  14. package/dist/animated/GraphDeps.svelte.d.ts +29 -0
  15. package/dist/animated/GraphDiya.svelte +330 -0
  16. package/dist/animated/GraphDiya.svelte.d.ts +22 -0
  17. package/dist/animated/GraphFire.svelte +246 -0
  18. package/dist/animated/GraphFire.svelte.d.ts +22 -0
  19. package/dist/animated/GraphFlame.svelte +258 -0
  20. package/dist/animated/GraphFlame.svelte.d.ts +14 -0
  21. package/dist/animated/GraphFlowPlayer.svelte +346 -0
  22. package/dist/animated/GraphFlowPlayer.svelte.d.ts +27 -0
  23. package/dist/animated/GraphGanga.svelte +305 -0
  24. package/dist/animated/GraphGanga.svelte.d.ts +20 -0
  25. package/dist/animated/GraphHash.svelte +251 -0
  26. package/dist/animated/GraphHash.svelte.d.ts +20 -0
  27. package/dist/animated/GraphJapa.svelte +253 -0
  28. package/dist/animated/GraphJapa.svelte.d.ts +20 -0
  29. package/dist/animated/GraphLife.svelte +312 -0
  30. package/dist/animated/GraphLife.svelte.d.ts +27 -0
  31. package/dist/animated/GraphMandala.svelte +262 -0
  32. package/dist/animated/GraphMandala.svelte.d.ts +24 -0
  33. package/dist/animated/GraphMandel.svelte +290 -0
  34. package/dist/animated/GraphMandel.svelte.d.ts +27 -0
  35. package/dist/animated/GraphPulse.svelte +248 -0
  36. package/dist/animated/GraphPulse.svelte.d.ts +24 -0
  37. package/dist/animated/GraphRain.svelte +280 -0
  38. package/dist/animated/GraphRain.svelte.d.ts +22 -0
  39. package/dist/animated/GraphScatter.svelte +395 -0
  40. package/dist/animated/GraphScatter.svelte.d.ts +31 -0
  41. package/dist/animated/GraphScope.svelte +197 -0
  42. package/dist/animated/GraphScope.svelte.d.ts +22 -0
  43. package/dist/animated/GraphSequence.svelte +247 -0
  44. package/dist/animated/GraphSequence.svelte.d.ts +18 -0
  45. package/dist/animated/GraphSpinners.svelte +109 -0
  46. package/dist/animated/GraphSpinners.svelte.d.ts +22 -0
  47. package/dist/animated/GraphState.svelte +270 -0
  48. package/dist/animated/GraphState.svelte.d.ts +14 -0
  49. package/dist/animated/GraphStream.svelte +223 -0
  50. package/dist/animated/GraphStream.svelte.d.ts +24 -0
  51. package/dist/animated/GraphSurya.svelte +268 -0
  52. package/dist/animated/GraphSurya.svelte.d.ts +22 -0
  53. package/dist/animated/GraphTerminal.svelte +238 -0
  54. package/dist/animated/GraphTerminal.svelte.d.ts +28 -0
  55. package/dist/animated/GraphTicker.svelte +163 -0
  56. package/dist/animated/GraphTicker.svelte.d.ts +21 -0
  57. package/dist/animated/GraphTypewriter.svelte +130 -0
  58. package/dist/animated/GraphTypewriter.svelte.d.ts +22 -0
  59. package/dist/animated/GraphWorkflow.svelte +570 -0
  60. package/dist/animated/GraphWorkflow.svelte.d.ts +29 -0
  61. package/dist/assets/favicon.svg +21 -0
  62. package/dist/diagram/AmplifierDiagram.svelte +206 -0
  63. package/dist/diagram/AmplifierDiagram.svelte.d.ts +12 -0
  64. package/dist/diagram/AsciiDiagram.svelte +212 -0
  65. package/dist/diagram/AsciiDiagram.svelte.d.ts +24 -0
  66. package/dist/diagram/DiagramEditor.svelte +414 -0
  67. package/dist/diagram/DiagramEditor.svelte.d.ts +3 -0
  68. package/dist/diagram/MetricsTableDiagram.svelte +125 -0
  69. package/dist/diagram/MetricsTableDiagram.svelte.d.ts +18 -0
  70. package/dist/diagram/NestedRadiiDiagram.svelte +218 -0
  71. package/dist/diagram/NestedRadiiDiagram.svelte.d.ts +12 -0
  72. package/dist/diagram/PromptLoopDiagram.svelte +331 -0
  73. package/dist/diagram/PromptLoopDiagram.svelte.d.ts +12 -0
  74. package/dist/diagram/ascii.d.ts +35 -0
  75. package/dist/diagram/ascii.js +83 -0
  76. package/dist/diagram/parser.d.ts +16 -0
  77. package/dist/diagram/parser.js +71 -0
  78. package/dist/frame/Graph.svelte +141 -0
  79. package/dist/frame/Graph.svelte.d.ts +14 -0
  80. package/dist/frame/GraphArrow.svelte +49 -0
  81. package/dist/frame/GraphArrow.svelte.d.ts +9 -0
  82. package/dist/frame/GraphBody.svelte +39 -0
  83. package/dist/frame/GraphBody.svelte.d.ts +10 -0
  84. package/dist/frame/GraphRule.svelte +21 -0
  85. package/dist/frame/GraphRule.svelte.d.ts +6 -0
  86. package/dist/frame/GraphTick.svelte +23 -0
  87. package/dist/frame/GraphTick.svelte.d.ts +9 -0
  88. package/dist/frame/GraphTrack.svelte +29 -0
  89. package/dist/frame/GraphTrack.svelte.d.ts +14 -0
  90. package/dist/frame/clock.d.ts +10 -0
  91. package/dist/frame/clock.js +62 -0
  92. package/dist/frame/glyphs.d.ts +22 -0
  93. package/dist/frame/glyphs.js +53 -0
  94. package/dist/frame/motion.css +27 -0
  95. package/dist/frame/motion.d.ts +24 -0
  96. package/dist/frame/motion.js +47 -0
  97. package/dist/frame/tone.d.ts +11 -0
  98. package/dist/frame/tone.js +64 -0
  99. package/dist/graphs/GraphActivity.svelte +387 -0
  100. package/dist/graphs/GraphActivity.svelte.d.ts +22 -0
  101. package/dist/graphs/GraphBars.svelte +194 -0
  102. package/dist/graphs/GraphBars.svelte.d.ts +20 -0
  103. package/dist/graphs/GraphBullet.svelte +208 -0
  104. package/dist/graphs/GraphBullet.svelte.d.ts +22 -0
  105. package/dist/graphs/GraphCalendar.svelte +203 -0
  106. package/dist/graphs/GraphCalendar.svelte.d.ts +20 -0
  107. package/dist/graphs/GraphCells.svelte +181 -0
  108. package/dist/graphs/GraphCells.svelte.d.ts +18 -0
  109. package/dist/graphs/GraphCompare.svelte +192 -0
  110. package/dist/graphs/GraphCompare.svelte.d.ts +18 -0
  111. package/dist/graphs/GraphCountdown.svelte +96 -0
  112. package/dist/graphs/GraphCountdown.svelte.d.ts +14 -0
  113. package/dist/graphs/GraphDiff.svelte +181 -0
  114. package/dist/graphs/GraphDiff.svelte.d.ts +18 -0
  115. package/dist/graphs/GraphFlow.svelte +137 -0
  116. package/dist/graphs/GraphFlow.svelte.d.ts +21 -0
  117. package/dist/graphs/GraphFunnel.svelte +190 -0
  118. package/dist/graphs/GraphFunnel.svelte.d.ts +21 -0
  119. package/dist/graphs/GraphGantt.svelte +258 -0
  120. package/dist/graphs/GraphGantt.svelte.d.ts +25 -0
  121. package/dist/graphs/GraphHeatmap.svelte +287 -0
  122. package/dist/graphs/GraphHeatmap.svelte.d.ts +22 -0
  123. package/dist/graphs/GraphInvoice.svelte +294 -0
  124. package/dist/graphs/GraphInvoice.svelte.d.ts +34 -0
  125. package/dist/graphs/GraphKpi.svelte +166 -0
  126. package/dist/graphs/GraphKpi.svelte.d.ts +17 -0
  127. package/dist/graphs/GraphLatency.svelte +340 -0
  128. package/dist/graphs/GraphLatency.svelte.d.ts +28 -0
  129. package/dist/graphs/GraphMeter.svelte +121 -0
  130. package/dist/graphs/GraphMeter.svelte.d.ts +16 -0
  131. package/dist/graphs/GraphPlot.svelte +226 -0
  132. package/dist/graphs/GraphPlot.svelte.d.ts +18 -0
  133. package/dist/graphs/GraphQuota.svelte +303 -0
  134. package/dist/graphs/GraphQuota.svelte.d.ts +27 -0
  135. package/dist/graphs/GraphRank.svelte +141 -0
  136. package/dist/graphs/GraphRank.svelte.d.ts +21 -0
  137. package/dist/graphs/GraphSlo.svelte +325 -0
  138. package/dist/graphs/GraphSlo.svelte.d.ts +31 -0
  139. package/dist/graphs/GraphSlope.svelte +197 -0
  140. package/dist/graphs/GraphSlope.svelte.d.ts +19 -0
  141. package/dist/graphs/GraphSpark.svelte +125 -0
  142. package/dist/graphs/GraphSpark.svelte.d.ts +15 -0
  143. package/dist/graphs/GraphSpec.svelte +69 -0
  144. package/dist/graphs/GraphSpec.svelte.d.ts +14 -0
  145. package/dist/graphs/GraphStack.svelte +266 -0
  146. package/dist/graphs/GraphStack.svelte.d.ts +25 -0
  147. package/dist/graphs/GraphStat.svelte +103 -0
  148. package/dist/graphs/GraphStat.svelte.d.ts +16 -0
  149. package/dist/graphs/GraphTable.svelte +133 -0
  150. package/dist/graphs/GraphTable.svelte.d.ts +15 -0
  151. package/dist/graphs/GraphTimeline.svelte +164 -0
  152. package/dist/graphs/GraphTimeline.svelte.d.ts +18 -0
  153. package/dist/graphs/GraphTimer.svelte +117 -0
  154. package/dist/graphs/GraphTimer.svelte.d.ts +15 -0
  155. package/dist/graphs/GraphTree.svelte +147 -0
  156. package/dist/graphs/GraphTree.svelte.d.ts +16 -0
  157. package/dist/graphs/GraphUptime.svelte +210 -0
  158. package/dist/graphs/GraphUptime.svelte.d.ts +18 -0
  159. package/dist/graphs/GraphWaffle.svelte +131 -0
  160. package/dist/graphs/GraphWaffle.svelte.d.ts +17 -0
  161. package/dist/graphs/GraphWaterfall.svelte +258 -0
  162. package/dist/graphs/GraphWaterfall.svelte.d.ts +22 -0
  163. package/dist/index.d.ts +147 -0
  164. package/dist/index.js +87 -0
  165. package/dist/themes.css +54 -0
  166. package/package.json +67 -0
@@ -0,0 +1,570 @@
1
+ <script module lang="ts">
2
+ export type WorkflowTone = 'default' | 'accent' | 'muted';
3
+
4
+ export interface WorkflowNode {
5
+ id: string;
6
+ label: string;
7
+ hint?: string;
8
+ tone?: WorkflowTone;
9
+ }
10
+
11
+ export interface WorkflowEdge {
12
+ from: string;
13
+ to: string;
14
+ label?: string;
15
+ tone?: WorkflowTone;
16
+ }
17
+
18
+ export interface GraphWorkflowProps {
19
+ title: string;
20
+ nodes: WorkflowNode[];
21
+ edges?: WorkflowEdge[];
22
+ path?: string[];
23
+ animated?: boolean;
24
+ autoPlay?: boolean;
25
+ speedMs?: number;
26
+ showControls?: boolean;
27
+ label?: string;
28
+ corner?: string;
29
+ class?: string;
30
+ }
31
+ </script>
32
+
33
+ <script lang="ts">
34
+ import Graph from '../frame/Graph.svelte';
35
+ import GraphBody from '../frame/GraphBody.svelte';
36
+
37
+ let {
38
+ title,
39
+ nodes,
40
+ edges,
41
+ path,
42
+ animated = true,
43
+ autoPlay = false,
44
+ speedMs = 1200,
45
+ showControls = true,
46
+ label,
47
+ corner,
48
+ class: className = ''
49
+ }: GraphWorkflowProps = $props();
50
+
51
+ const uid = $props.id();
52
+
53
+ const idIndex = $derived.by(() => {
54
+ const idx = new Map<string, number>();
55
+ for (let i = 0; i < nodes.length; i++) idx.set(nodes[i].id, i);
56
+ return idx;
57
+ });
58
+
59
+ const effectiveEdges = $derived.by(() => {
60
+ const known = (edges ?? []).filter(
61
+ (e) => idIndex.has(e.from) && idIndex.has(e.to) && e.from !== e.to
62
+ );
63
+ if (known.length > 0) return known;
64
+ const chain: WorkflowEdge[] = [];
65
+ for (let i = 0; i < nodes.length - 1; i++) {
66
+ chain.push({ from: nodes[i].id, to: nodes[i + 1].id });
67
+ }
68
+ return chain;
69
+ });
70
+
71
+ const ranks = $derived.by(() => {
72
+ const rank = new Map<string, number>();
73
+ for (const n of nodes) rank.set(n.id, 0);
74
+ const maxIter = nodes.length + 1;
75
+ for (let iter = 0; iter < maxIter; iter++) {
76
+ let changed = false;
77
+ for (const e of effectiveEdges) {
78
+ const rFrom = rank.get(e.from) ?? 0;
79
+ const rTo = rank.get(e.to) ?? 0;
80
+ if (rTo < rFrom + 1) {
81
+ rank.set(e.to, rFrom + 1);
82
+ changed = true;
83
+ }
84
+ }
85
+ if (!changed) break;
86
+ }
87
+ return rank;
88
+ });
89
+
90
+ const layers = $derived.by(() => {
91
+ const map = new Map<number, WorkflowNode[]>();
92
+ for (const n of nodes) {
93
+ const r = ranks.get(n.id) ?? 0;
94
+ if (!map.has(r)) map.set(r, []);
95
+ map.get(r)!.push(n);
96
+ }
97
+ return Array.from(map.entries())
98
+ .sort((a, b) => a[0] - b[0])
99
+ .map(([, list]) => list);
100
+ });
101
+
102
+ const NODE_GAP = 2;
103
+ const EDGE_GAP = 2;
104
+
105
+ function nodeInnerWidth(n: WorkflowNode): number {
106
+ const w = Math.max(n.label.length, n.hint?.length ?? 0);
107
+ return w + 2;
108
+ }
109
+ function nodeWidth(n: WorkflowNode): number {
110
+ return nodeInnerWidth(n) + 2;
111
+ }
112
+ function nodeHeight(n: WorkflowNode): number {
113
+ return n.hint ? 4 : 3;
114
+ }
115
+
116
+ const layout = $derived.by(() => {
117
+ const dims = new Map<string, { w: number; h: number }>();
118
+ for (const n of nodes) dims.set(n.id, { w: nodeWidth(n), h: nodeHeight(n) });
119
+
120
+ const layerWidths = layers.map(
121
+ (layer) =>
122
+ layer.reduce((s, n) => s + dims.get(n.id)!.w, 0) +
123
+ Math.max(0, layer.length - 1) * NODE_GAP
124
+ );
125
+ const width = Math.max(0, ...layerWidths);
126
+
127
+ const positions = new Map<string, { col: number; row: number }>();
128
+ let curRow = 0;
129
+ for (let i = 0; i < layers.length; i++) {
130
+ const layer = layers[i];
131
+ const layerWidth = layerWidths[i];
132
+ const leftPad = Math.max(0, Math.floor((width - layerWidth) / 2));
133
+ let curCol = leftPad;
134
+ let layerHeight = 0;
135
+ for (const n of layer) {
136
+ positions.set(n.id, { col: curCol, row: curRow });
137
+ curCol += dims.get(n.id)!.w + NODE_GAP;
138
+ layerHeight = Math.max(layerHeight, dims.get(n.id)!.h);
139
+ }
140
+ curRow += layerHeight + EDGE_GAP;
141
+ }
142
+ const height = Math.max(0, curRow - EDGE_GAP);
143
+
144
+ return { positions, dims, width, height };
145
+ });
146
+
147
+ function kahnPath(): string[] {
148
+ const inDeg = new Map<string, number>();
149
+ for (const n of nodes) inDeg.set(n.id, 0);
150
+ for (const e of effectiveEdges) {
151
+ if (idIndex.has(e.to)) inDeg.set(e.to, (inDeg.get(e.to) ?? 0) + 1);
152
+ }
153
+ const queue: string[] = [];
154
+ for (const [id, d] of inDeg) if (d === 0) queue.push(id);
155
+ const out: string[] = [];
156
+ while (queue.length > 0) {
157
+ const id = queue.shift()!;
158
+ out.push(id);
159
+ for (const e of effectiveEdges) {
160
+ if (e.from === id) {
161
+ const next = (inDeg.get(e.to) ?? 0) - 1;
162
+ inDeg.set(e.to, next);
163
+ if (next === 0) queue.push(e.to);
164
+ }
165
+ }
166
+ }
167
+ if (out.length < nodes.length) {
168
+ for (const n of nodes) if (!out.includes(n.id)) out.push(n.id);
169
+ }
170
+ return out;
171
+ }
172
+
173
+ const effectivePath = $derived.by(() => {
174
+ if (!path) return null;
175
+ const filtered = path.filter((id) => idIndex.has(id));
176
+ return filtered.length > 0 ? filtered : null;
177
+ });
178
+
179
+ const topologicalPath = $derived(effectivePath ?? kahnPath());
180
+
181
+ const pathIndex = $derived.by(() => {
182
+ const idx = new Map<string, number>();
183
+ topologicalPath.forEach((id, i) => idx.set(id, i));
184
+ return idx;
185
+ });
186
+
187
+ let step = $state(0);
188
+ let playing = $state(false);
189
+ // svelte-ignore state_referenced_locally
190
+ let intervalTime = $state(speedMs);
191
+
192
+ const count = $derived(topologicalPath.length);
193
+ const activeStep = $derived(count === 0 ? -1 : Math.min(step, count - 1));
194
+
195
+ const motionAllowed = $derived(
196
+ animated &&
197
+ (typeof window === 'undefined' ||
198
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches)
199
+ );
200
+
201
+ $effect(() => {
202
+ if (autoPlay && motionAllowed) {
203
+ playing = true;
204
+ }
205
+ });
206
+
207
+ $effect(() => {
208
+ if (!playing || count === 0) return;
209
+ const timer = window.setInterval(() => {
210
+ step = (activeStep + 1) % count;
211
+ }, Math.max(100, intervalTime));
212
+ return () => window.clearInterval(timer);
213
+ });
214
+
215
+ function handleTogglePlay() {
216
+ if (count === 0) return;
217
+ playing = !playing;
218
+ }
219
+ function handleNext() {
220
+ if (count === 0) return;
221
+ step = (activeStep + 1) % count;
222
+ }
223
+ function handlePrev() {
224
+ if (count === 0) return;
225
+ step = (activeStep - 1 + count) % count;
226
+ }
227
+
228
+ // --- Render: 2D char grid → list of (text, cls) segments per row ---
229
+
230
+ type Cell = { ch: string; cls?: string };
231
+ type Seg = { text: string; cls?: string };
232
+
233
+ class Grid {
234
+ rows: Cell[][];
235
+ width: number;
236
+ height: number;
237
+ constructor(width: number, height: number) {
238
+ this.width = width;
239
+ this.height = height;
240
+ this.rows = Array.from({ length: height }, () =>
241
+ Array.from({ length: width }, () => ({ ch: ' ' }))
242
+ );
243
+ }
244
+ set(col: number, row: number, ch: string, cls?: string): void {
245
+ if (col < 0 || col >= this.width || row < 0 || row >= this.height) return;
246
+ const cell = this.rows[row][col];
247
+ cell.ch = ch;
248
+ if (cls) cell.cls = cls;
249
+ }
250
+ rowToSegs(row: number): Seg[] {
251
+ const segs: Seg[] = [];
252
+ let run = '';
253
+ let runCls: string | undefined;
254
+ for (const cell of this.rows[row]) {
255
+ if (cell.cls === runCls) {
256
+ run += cell.ch;
257
+ } else {
258
+ if (run) segs.push({ text: run, cls: runCls });
259
+ run = cell.ch;
260
+ runCls = cell.cls;
261
+ }
262
+ }
263
+ if (run) segs.push({ text: run, cls: runCls });
264
+ return segs;
265
+ }
266
+ }
267
+
268
+ function drawBox(
269
+ grid: Grid,
270
+ n: WorkflowNode,
271
+ pos: { col: number; row: number },
272
+ dim: { w: number; h: number },
273
+ cls: string
274
+ ): void {
275
+ const x = pos.col;
276
+ const y = pos.row;
277
+ const w = dim.w;
278
+ const h = dim.h;
279
+ const innerW = w - 2;
280
+
281
+ grid.set(x, y, '┌', cls);
282
+ for (let c = 1; c < w - 1; c++) grid.set(x + c, y, '─', cls);
283
+ grid.set(x + w - 1, y, '┐', cls);
284
+
285
+ const labelText = ' ' + n.label.padEnd(Math.max(0, innerW), ' ');
286
+ for (let c = 0; c < w; c++) {
287
+ const ch = c === 0 || c === w - 1 ? '│' : labelText[c];
288
+ grid.set(x + c, y + 1, ch, cls);
289
+ }
290
+
291
+ if (h >= 4 && n.hint) {
292
+ const hintText = ' ' + n.hint.padEnd(Math.max(0, innerW), ' ');
293
+ for (let c = 0; c < w; c++) {
294
+ const ch = c === 0 || c === w - 1 ? '│' : hintText[c];
295
+ grid.set(x + c, y + 2, ch, cls);
296
+ }
297
+ }
298
+
299
+ const bRow = y + h - 1;
300
+ grid.set(x, bRow, '└', cls);
301
+ for (let c = 1; c < w - 1; c++) grid.set(x + c, bRow, '─', cls);
302
+ grid.set(x + w - 1, bRow, '┘', cls);
303
+ }
304
+
305
+ function drawEdge(
306
+ grid: Grid,
307
+ from: { col: number; row: number; w: number; h: number },
308
+ to: { col: number; row: number; w: number; h: number },
309
+ cls: string
310
+ ): void {
311
+ const sCx = from.col + Math.floor(from.w / 2);
312
+ const sBot = from.row + from.h;
313
+ const tCx = to.col + Math.floor(to.w / 2);
314
+ const tTop = to.row;
315
+
316
+ // Same row, or any case where the target is at or above the source's bottom:
317
+ // skip — drawing would overlap the source box.
318
+ if (sBot >= tTop) return;
319
+
320
+ if (sCx === tCx) {
321
+ for (let r = sBot; r < tTop - 1; r++) grid.set(sCx, r, '│', cls);
322
+ grid.set(sCx, tTop - 1, 'v', cls);
323
+ return;
324
+ }
325
+
326
+ const sCorner = sCx < tCx ? '└' : '┘';
327
+ const tCorner = sCx < tCx ? '┐' : '┌';
328
+
329
+ grid.set(sCx, sBot, sCorner, cls);
330
+ if (sCx < tCx) {
331
+ for (let c = sCx + 1; c < tCx; c++) grid.set(c, sBot, '─', cls);
332
+ } else {
333
+ for (let c = sCx - 1; c > tCx; c--) grid.set(c, sBot, '─', cls);
334
+ }
335
+ grid.set(tCx, sBot, tCorner, cls);
336
+
337
+ for (let r = sBot + 1; r < tTop - 1; r++) grid.set(tCx, r, '│', cls);
338
+ grid.set(tCx, tTop - 1, 'v', cls);
339
+ }
340
+
341
+ function nodeCls(n: WorkflowNode, state: 'active' | 'done' | 'pending'): string {
342
+ const base = `node ${state}`;
343
+ if (n.tone === 'accent') return `${base} explicit-accent`;
344
+ if (n.tone === 'muted') return `${base} explicit-muted`;
345
+ return base;
346
+ }
347
+
348
+ function edgeCls(e: WorkflowEdge, state: 'active' | 'done' | 'pending'): string {
349
+ const base = `edge ${state}`;
350
+ if (e.tone === 'accent') return `${base} explicit-accent`;
351
+ if (e.tone === 'muted') return `${base} explicit-muted`;
352
+ return base;
353
+ }
354
+
355
+ const art = $derived.by(() => {
356
+ const { positions, dims, width, height } = layout;
357
+ if (nodes.length === 0 || width === 0 || height === 0) {
358
+ return [[{ text: '( no nodes )', cls: 'detail' }]];
359
+ }
360
+
361
+ const grid = new Grid(width, height);
362
+
363
+ // Edges first so node boxes stamp on top.
364
+ for (const e of effectiveEdges) {
365
+ const fp = positions.get(e.from);
366
+ const tp = positions.get(e.to);
367
+ if (!fp || !tp) continue;
368
+ const fd = dims.get(e.from)!;
369
+ const td = dims.get(e.to)!;
370
+
371
+ const fromIdx = pathIndex.get(e.from) ?? -1;
372
+ const toIdx = pathIndex.get(e.to) ?? -1;
373
+
374
+ let state: 'active' | 'done' | 'pending';
375
+ if (fromIdx >= 0 && toIdx >= 0 && fromIdx < activeStep && toIdx <= activeStep) {
376
+ state = 'done';
377
+ } else if (toIdx === activeStep && fromIdx >= 0 && fromIdx < activeStep) {
378
+ state = 'active';
379
+ } else {
380
+ state = 'pending';
381
+ }
382
+ drawEdge(
383
+ grid,
384
+ { col: fp.col, row: fp.row, w: fd.w, h: fd.h },
385
+ { col: tp.col, row: tp.row, w: td.w, h: td.h },
386
+ edgeCls(e, state)
387
+ );
388
+ }
389
+
390
+ for (const n of nodes) {
391
+ const p = positions.get(n.id);
392
+ const d = dims.get(n.id);
393
+ if (!p || !d) continue;
394
+ const idx = pathIndex.get(n.id) ?? -1;
395
+ let state: 'active' | 'done' | 'pending';
396
+ if (activeStep < 0 || idx === -1) {
397
+ state = 'pending';
398
+ } else if (idx === activeStep) {
399
+ state = 'active';
400
+ } else if (idx < activeStep) {
401
+ state = 'done';
402
+ } else {
403
+ state = 'pending';
404
+ }
405
+ drawBox(grid, n, p, d, nodeCls(n, state));
406
+ }
407
+
408
+ return grid.rows.map((_, i) => grid.rowToSegs(i));
409
+ });
410
+ </script>
411
+
412
+ <Graph {title} {corner} class={className}>
413
+ <GraphBody>
414
+ <div class="workflow" class:anim={animated} class:playing>
415
+ <div class="viewport">
416
+ <pre class="art"><code>{#each art as line, i (i)}{#each line as seg, j (j)}{#if seg.cls}<span class={seg.cls}>{seg.text}</span>{:else}{seg.text}{/if}{/each}{#if i < art.length - 1}{'\n'}{/if}{/each}</code></pre>
417
+ {#if label}
418
+ <p class="caption">{label}</p>
419
+ {/if}
420
+ </div>
421
+
422
+ {#if showControls && count > 0}
423
+ <div class="controls">
424
+ <div class="buttons">
425
+ <button class="ctrl" type="button" onclick={handlePrev} aria-label="Previous step">◀</button>
426
+ <button class="ctrl primary" type="button" onclick={handleTogglePlay}>
427
+ {playing ? '❚❚ pause' : '▶ play'}
428
+ </button>
429
+ <button class="ctrl" type="button" onclick={handleNext} aria-label="Next step">▶</button>
430
+ </div>
431
+
432
+ <div class="dots">
433
+ {#each topologicalPath as _id, i (i)}
434
+ <button
435
+ class="dot"
436
+ class:on={activeStep === i}
437
+ type="button"
438
+ onclick={() => {
439
+ step = i;
440
+ playing = false;
441
+ }}
442
+ aria-label={`Step ${i + 1}`}
443
+ ></button>
444
+ {/each}
445
+ </div>
446
+
447
+ <label class="speed" for="{uid}-speed">
448
+ Speed {(intervalTime / 1000).toFixed(1)}s
449
+ <input id="{uid}-speed" type="range" min="400" max="2500" step="100" bind:value={intervalTime} />
450
+ </label>
451
+ </div>
452
+ {/if}
453
+ </div>
454
+ </GraphBody>
455
+ </Graph>
456
+
457
+ <style>
458
+ .workflow {
459
+ display: flex;
460
+ flex-direction: column;
461
+ gap: 1rem;
462
+ min-width: 0;
463
+ }
464
+
465
+ .viewport {
466
+ overflow-x: auto;
467
+ }
468
+
469
+ .art {
470
+ margin: 0;
471
+ font-size: 0.85rem;
472
+ line-height: 1.35;
473
+ color: var(--graph-foreground, oklch(0.93 0 0));
474
+ white-space: pre;
475
+ }
476
+
477
+ .caption {
478
+ margin: 0.5rem 0 0;
479
+ color: var(--graph-muted, oklch(0.62 0 0));
480
+ font-size: 0.75rem;
481
+ letter-spacing: 0.04em;
482
+ }
483
+
484
+ .node { color: var(--graph-foreground, oklch(0.93 0 0)); }
485
+ .node.active { color: var(--graph-foreground, oklch(0.93 0 0)); font-weight: 600; }
486
+ .node.done { color: var(--graph-accent, oklch(0.78 0.17 155)); }
487
+ .node.pending { color: var(--graph-muted, oklch(0.62 0 0)); }
488
+ .node.explicit-accent { color: var(--graph-accent, oklch(0.78 0.17 155)); }
489
+ .node.explicit-muted { color: var(--graph-faint, oklch(0.3 0 0)); }
490
+
491
+ .edge { color: var(--graph-frame, oklch(0.6 0 0 / 0.5)); }
492
+ .edge.active { color: var(--graph-accent, oklch(0.78 0.17 155)); }
493
+ .edge.done { color: var(--graph-accent, oklch(0.78 0.17 155)); }
494
+ .edge.pending { color: var(--graph-faint, oklch(0.3 0 0)); }
495
+ .edge.explicit-accent { color: var(--graph-accent, oklch(0.78 0.17 155)); }
496
+ .edge.explicit-muted { color: var(--graph-faint, oklch(0.3 0 0)); }
497
+
498
+ .detail { color: var(--graph-muted, oklch(0.62 0 0)); }
499
+
500
+ @media (prefers-reduced-motion: no-preference) {
501
+ .anim .node,
502
+ .anim .edge { transition: color 0.25s ease; }
503
+ .anim.playing .node.active { animation: pulse 1s infinite alternate; }
504
+ }
505
+
506
+ @keyframes pulse {
507
+ from { opacity: 0.7; }
508
+ to { opacity: 1; }
509
+ }
510
+
511
+ .controls {
512
+ display: flex;
513
+ align-items: center;
514
+ justify-content: space-between;
515
+ flex-wrap: wrap;
516
+ gap: 1rem;
517
+ padding-top: 0.875rem;
518
+ border-top: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
519
+ }
520
+
521
+ .buttons { display: flex; align-items: center; gap: 0.35rem; }
522
+
523
+ .ctrl {
524
+ padding: 0.25rem 0.6rem;
525
+ font-size: 0.75rem;
526
+ color: var(--graph-muted, oklch(0.62 0 0));
527
+ background: none;
528
+ border: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
529
+ border-radius: 0;
530
+ cursor: pointer;
531
+ }
532
+ .ctrl:hover {
533
+ color: var(--graph-foreground, oklch(0.93 0 0));
534
+ border-color: var(--graph-muted, oklch(0.62 0 0));
535
+ }
536
+ .primary {
537
+ color: var(--graph-accent, oklch(0.78 0.17 155));
538
+ border-color: var(--graph-accent, oklch(0.78 0.17 155));
539
+ font-weight: 600;
540
+ }
541
+
542
+ .dots { display: flex; align-items: center; gap: 0.4rem; }
543
+
544
+ .dot {
545
+ width: 8px;
546
+ height: 8px;
547
+ padding: 0;
548
+ background: var(--graph-faint, oklch(0.3 0 0));
549
+ border: none;
550
+ border-radius: 50%;
551
+ cursor: pointer;
552
+ }
553
+ .dot.on { background: var(--graph-accent, oklch(0.78 0.17 155)); }
554
+
555
+ .speed {
556
+ display: flex;
557
+ align-items: center;
558
+ gap: 0.5rem;
559
+ font-size: 0.75rem;
560
+ color: var(--graph-muted, oklch(0.62 0 0));
561
+ white-space: nowrap;
562
+ }
563
+
564
+ .speed input {
565
+ width: 80px;
566
+ height: 4px;
567
+ accent-color: var(--graph-accent, oklch(0.78 0.17 155));
568
+ cursor: pointer;
569
+ }
570
+ </style>
@@ -0,0 +1,29 @@
1
+ export type WorkflowTone = 'default' | 'accent' | 'muted';
2
+ export interface WorkflowNode {
3
+ id: string;
4
+ label: string;
5
+ hint?: string;
6
+ tone?: WorkflowTone;
7
+ }
8
+ export interface WorkflowEdge {
9
+ from: string;
10
+ to: string;
11
+ label?: string;
12
+ tone?: WorkflowTone;
13
+ }
14
+ export interface GraphWorkflowProps {
15
+ title: string;
16
+ nodes: WorkflowNode[];
17
+ edges?: WorkflowEdge[];
18
+ path?: string[];
19
+ animated?: boolean;
20
+ autoPlay?: boolean;
21
+ speedMs?: number;
22
+ showControls?: boolean;
23
+ label?: string;
24
+ corner?: string;
25
+ class?: string;
26
+ }
27
+ declare const GraphWorkflow: import("svelte").Component<GraphWorkflowProps, {}, "">;
28
+ type GraphWorkflow = ReturnType<typeof GraphWorkflow>;
29
+ export default GraphWorkflow;
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <title>markgraphy</title>
3
+ <rect width="32" height="32" fill="#101211" />
4
+ <rect
5
+ x="4.5"
6
+ y="4.5"
7
+ width="23"
8
+ height="23"
9
+ fill="none"
10
+ stroke="#949a96"
11
+ stroke-width="1"
12
+ stroke-dasharray="2 5"
13
+ />
14
+ <path
15
+ d="M16 10.5v11M10.5 16h11"
16
+ fill="none"
17
+ stroke="#47d2a7"
18
+ stroke-width="3"
19
+ stroke-linecap="square"
20
+ />
21
+ </svg>