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,330 @@
1
+ <script module lang="ts">
2
+ export interface GraphDiyaProps {
3
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
4
+ title: string;
5
+ /** Animate the lighting and the breathing. Default true. Reduced motion freezes a fully lit row. */
6
+ animated?: boolean;
7
+ /** Width of the scene in characters. Default 60. */
8
+ cols?: number;
9
+ /** Height of the scene in rows. Default 14. */
10
+ rows?: number;
11
+ /** Number of diyas in the row. Default 5. */
12
+ lamps?: number;
13
+ /** Milliseconds per tick. Default 110. */
14
+ speedMs?: number;
15
+ /** Seed for the per-lamp phase and gutter schedule. Default 13. */
16
+ seedNum?: number;
17
+ /** Short caption under the art; the art itself is decorative. */
18
+ label?: string;
19
+ class?: string;
20
+ }
21
+
22
+ /** Deterministic mulberry32 PRNG so a seed replays the same lighting. */
23
+ function mulberry32(seed: number): () => number {
24
+ let a = seed >>> 0;
25
+
26
+ return () => {
27
+ a = (a + 0x6d2b79f5) | 0;
28
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
29
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
30
+
31
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
32
+ };
33
+ }
34
+
35
+ interface Seg {
36
+ text: string;
37
+ cls?: string;
38
+ }
39
+ </script>
40
+
41
+ <script lang="ts">
42
+ import Graph from '../frame/Graph.svelte';
43
+ import GraphBody from '../frame/GraphBody.svelte';
44
+
45
+ let {
46
+ title,
47
+ animated = true,
48
+ cols = 60,
49
+ rows = 14,
50
+ lamps = 5,
51
+ speedMs = 110,
52
+ seedNum = 13,
53
+ label = 'row of light',
54
+ class: className = ''
55
+ }: GraphDiyaProps = $props();
56
+
57
+ // svelte-ignore state_referenced_locally
58
+ const width = Math.max(24, cols);
59
+ // svelte-ignore state_referenced_locally
60
+ const height = Math.max(10, rows);
61
+ // svelte-ignore state_referenced_locally
62
+ const N = Math.max(1, Math.min(11, Math.round(lamps)));
63
+
64
+ // svelte-ignore state_referenced_locally
65
+ const rand = mulberry32(seedNum);
66
+
67
+ // Spread lamps evenly across the row.
68
+ const margin = 4;
69
+ const usable = width - margin * 2;
70
+
71
+ // svelte-ignore state_referenced_locally
72
+ const lampCenters: number[] = (() => {
73
+ const out: number[] = [];
74
+
75
+ if (N === 1) {
76
+ out.push(Math.floor(width / 2));
77
+ } else {
78
+ for (let i = 0; i < N; i++) {
79
+ out.push(margin + Math.round((i * usable) / (N - 1)));
80
+ }
81
+ }
82
+
83
+ return out;
84
+ })();
85
+
86
+ const litTicks = 8;
87
+ const sequenceTicks = N * litTicks;
88
+ const gutterEvery = 40;
89
+
90
+ // svelte-ignore state_referenced_locally
91
+ const lampPhase: number[] = Array.from({ length: N }, () => rand());
92
+
93
+ // Gutter schedule: each lamp has its own list of (start, end) gutter intervals.
94
+ // svelte-ignore state_referenced_locally
95
+ const gutterSchedule: { start: number; end: number }[][] = Array.from(
96
+ { length: N },
97
+ () => {
98
+ const out: { start: number; end: number }[] = [];
99
+ let next = sequenceTicks + 16 + Math.floor(rand() * 24);
100
+
101
+ while (next < 1000) {
102
+ out.push({ start: next, end: next + 6 });
103
+ next += gutterEvery + Math.floor(rand() * 28);
104
+ }
105
+
106
+ return out;
107
+ }
108
+ );
109
+
110
+ function flameIntensity(i: number, tick: number): number {
111
+ const litAt = i * litTicks;
112
+ if (tick < litAt) {
113
+ return 0;
114
+ }
115
+
116
+ // Lighting phase
117
+ if (tick < litAt + litTicks) {
118
+ return (tick - litAt) / litTicks;
119
+ }
120
+
121
+ // Gutter / relight
122
+ for (const g of gutterSchedule[i]!) {
123
+ if (tick >= g.start && tick < g.end) {
124
+ return Math.max(0, 1 - (tick - g.start) / (g.end - g.start));
125
+ }
126
+
127
+ if (tick >= g.end && tick < g.end + litTicks) {
128
+ return (tick - g.end) / litTicks;
129
+ }
130
+ }
131
+
132
+ // Breathing — kept tight so the static frame still reads all-lit.
133
+ return 0.92 + 0.08 * Math.sin(tick / 3 + lampPhase[i]! * 6.2832);
134
+ }
135
+
136
+ // Pick the cell glyph and class for a single flame slot.
137
+ // row 0 = top tip, row 1 = mid, row 2 = base.
138
+ function flameSlot(row: 0 | 1 | 2, intensity: number): { ch: string; cls: string | undefined } {
139
+ if (intensity <= 0) {
140
+ return { ch: ' ', cls: undefined };
141
+ }
142
+
143
+ if (row === 0) {
144
+ if (intensity < 0.55) {
145
+ return { ch: '·', cls: 'faint' };
146
+ }
147
+ return { ch: '░', cls: 'faint' };
148
+ }
149
+
150
+ if (row === 1) {
151
+ return { ch: '▒', cls: 'mid' };
152
+ }
153
+
154
+ // row 2: hot base
155
+ if (intensity < 0.7) {
156
+ return { ch: '▓', cls: 'mid' };
157
+ }
158
+ return { ch: '█', cls: 'hot' };
159
+ }
160
+
161
+ // svelte-ignore state_referenced_locally
162
+ const initialTick = animated ? sequenceTicks : sequenceTicks;
163
+
164
+ // svelte-ignore state_referenced_locally
165
+ let tick = $state(initialTick);
166
+
167
+ const moving = $derived(
168
+ animated &&
169
+ (typeof window === 'undefined' ||
170
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches)
171
+ );
172
+
173
+ $effect(() => {
174
+ if (!moving) {
175
+ return;
176
+ }
177
+
178
+ const timer = window.setInterval(() => {
179
+ tick += 1;
180
+ }, speedMs);
181
+
182
+ return () => window.clearInterval(timer);
183
+ });
184
+
185
+ const view = $derived.by((): Seg[][] => {
186
+ const out: Seg[][] = [];
187
+ const intensities: number[] = Array.from({ length: N }, (_, i) =>
188
+ flameIntensity(i, tick)
189
+ );
190
+ const baseY = height - 1;
191
+ const wickY = baseY - 1;
192
+ const flameBaseY = wickY - 1; // row 2 of flame
193
+ const flameMidY = wickY - 2; // row 1
194
+ const flameTopY = wickY - 3; // row 0 (tip)
195
+
196
+ for (let y = 0; y < height; y++) {
197
+ const segs: Seg[] = [];
198
+ let buf = '';
199
+ let cls: string | undefined;
200
+
201
+ const flush = () => {
202
+ if (buf.length > 0) {
203
+ segs.push({ text: buf, cls });
204
+ }
205
+
206
+ buf = '';
207
+ };
208
+
209
+ for (let x = 0; x < width; x++) {
210
+ let ch = ' ';
211
+ let next: string | undefined;
212
+
213
+ for (let i = 0; i < N; i++) {
214
+ const cx = lampCenters[i]!;
215
+ const inten = intensities[i]!;
216
+
217
+ if (y === baseY) {
218
+ if (x >= cx - 1 && x <= cx + 1) {
219
+ ch = '▀';
220
+ next = 'mid';
221
+ break;
222
+ }
223
+ } else if (y === wickY) {
224
+ if (x === cx) {
225
+ ch = '║';
226
+ next = 'mid';
227
+ break;
228
+ }
229
+ } else if (y === flameBaseY) {
230
+ // 3-wide base
231
+ if (x === cx) {
232
+ const f = flameSlot(2, inten);
233
+ ch = f.ch;
234
+ next = f.cls;
235
+ break;
236
+ }
237
+ if (x === cx - 1 || x === cx + 1) {
238
+ const f = flameSlot(2, inten);
239
+ ch = f.ch;
240
+ next = f.cls;
241
+ break;
242
+ }
243
+ } else if (y === flameMidY) {
244
+ // 3-wide mid
245
+ if (x >= cx - 1 && x <= cx + 1) {
246
+ const f = flameSlot(1, inten);
247
+ ch = f.ch;
248
+ next = f.cls;
249
+ break;
250
+ }
251
+ } else if (y === flameTopY) {
252
+ if (x === cx) {
253
+ const f = flameSlot(0, inten);
254
+ ch = f.ch;
255
+ next = f.cls;
256
+ break;
257
+ }
258
+ }
259
+ }
260
+
261
+ if (next === cls) {
262
+ buf += ch;
263
+ } else {
264
+ flush();
265
+ cls = next;
266
+ buf = ch;
267
+ }
268
+ }
269
+
270
+ flush();
271
+ out.push(segs);
272
+ }
273
+
274
+ return out;
275
+ });
276
+ </script>
277
+
278
+ <Graph {title} class={className}>
279
+ <GraphBody>
280
+ <div class="diya">
281
+ <div class="viewport">
282
+ <pre class="art" aria-hidden="true"><code>{#each view 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 < view.length - 1}{'\n'}{/if}{/each}</code></pre>
283
+ </div>
284
+ {#if label}
285
+ <p class="caption">{label}</p>
286
+ {/if}
287
+ </div>
288
+ </GraphBody>
289
+ </Graph>
290
+
291
+ <style>
292
+ .diya {
293
+ display: flex;
294
+ flex-direction: column;
295
+ gap: 1rem;
296
+ min-width: 0;
297
+ font-size: 0.875rem;
298
+ }
299
+
300
+ .viewport {
301
+ overflow-x: auto;
302
+ }
303
+
304
+ .art {
305
+ margin: 0;
306
+ font-size: 0.85rem;
307
+ line-height: 1.15;
308
+ color: var(--graph-foreground, oklch(0.93 0 0));
309
+ white-space: pre;
310
+ }
311
+
312
+ .hot {
313
+ color: var(--graph-accent, oklch(0.78 0.17 155));
314
+ font-weight: 600;
315
+ }
316
+
317
+ .mid {
318
+ color: var(--graph-muted, oklch(0.62 0 0));
319
+ }
320
+
321
+ .faint {
322
+ color: var(--graph-faint, oklch(0.3 0 0));
323
+ }
324
+
325
+ .caption {
326
+ margin: 0;
327
+ font-size: 0.8rem;
328
+ color: var(--graph-muted, oklch(0.62 0 0));
329
+ }
330
+ </style>
@@ -0,0 +1,22 @@
1
+ export interface GraphDiyaProps {
2
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
3
+ title: string;
4
+ /** Animate the lighting and the breathing. Default true. Reduced motion freezes a fully lit row. */
5
+ animated?: boolean;
6
+ /** Width of the scene in characters. Default 60. */
7
+ cols?: number;
8
+ /** Height of the scene in rows. Default 14. */
9
+ rows?: number;
10
+ /** Number of diyas in the row. Default 5. */
11
+ lamps?: number;
12
+ /** Milliseconds per tick. Default 110. */
13
+ speedMs?: number;
14
+ /** Seed for the per-lamp phase and gutter schedule. Default 13. */
15
+ seedNum?: number;
16
+ /** Short caption under the art; the art itself is decorative. */
17
+ label?: string;
18
+ class?: string;
19
+ }
20
+ declare const GraphDiya: import("svelte").Component<GraphDiyaProps, {}, "">;
21
+ type GraphDiya = ReturnType<typeof GraphDiya>;
22
+ export default GraphDiya;
@@ -0,0 +1,246 @@
1
+ <script module lang="ts">
2
+ export interface GraphFireProps {
3
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
4
+ title: string;
5
+ /** Animate the burn. Default true. Reduced motion freezes a mid-burn frame. */
6
+ animated?: boolean;
7
+ /** Width of the fire in characters. Default 60. */
8
+ cols?: number;
9
+ /** Height of the fire in rows. Default 14. */
10
+ rows?: number;
11
+ /** Milliseconds per simulation step. Default 90. */
12
+ speedMs?: number;
13
+ /** Random decay strength; higher burns out sooner. Default 0.6. */
14
+ cooling?: number;
15
+ /** Seed for the reproducible burn. Default 3. */
16
+ seedNum?: number;
17
+ /** Short caption under the art; the art itself is decorative. */
18
+ label?: string;
19
+ class?: string;
20
+ }
21
+
22
+ /** Deterministic mulberry32 PRNG so a seed replays the same burn. */
23
+ function mulberry32(seed: number): () => number {
24
+ let a = seed >>> 0;
25
+
26
+ return () => {
27
+ a = (a + 0x6d2b79f5) | 0;
28
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
29
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
30
+
31
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
32
+ };
33
+ }
34
+
35
+ /** One-time reduced-motion check, for choosing the opening frame only. */
36
+ function prefersReduced(): boolean {
37
+ return (
38
+ typeof window !== 'undefined' &&
39
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches
40
+ );
41
+ }
42
+
43
+ interface Seg {
44
+ text: string;
45
+ cls?: string;
46
+ }
47
+
48
+ const HOT = 0.75;
49
+ const MID = 0.5;
50
+ const LOW = 0.3;
51
+ const EMBER = 0.12;
52
+
53
+ /** Run-length encode one row of heat into classed segments, never per-cell spans. */
54
+ function heatSegs(row: number[]): Seg[] {
55
+ const segs: Seg[] = [];
56
+ let buf = '';
57
+ let cls: string | undefined;
58
+
59
+ for (const heat of row) {
60
+ let ch = ' ';
61
+ let next: string | undefined;
62
+
63
+ if (heat >= HOT) {
64
+ ch = '█';
65
+ next = 'hot';
66
+ } else if (heat >= MID) {
67
+ ch = '▓';
68
+ } else if (heat >= LOW) {
69
+ ch = '▒';
70
+ next = 'mid';
71
+ } else if (heat >= EMBER) {
72
+ ch = '░';
73
+ next = 'faint';
74
+ }
75
+
76
+ if (next === cls) {
77
+ buf += ch;
78
+ } else {
79
+ if (buf.length > 0) {
80
+ segs.push({ text: buf, cls });
81
+ }
82
+
83
+ cls = next;
84
+ buf = ch;
85
+ }
86
+ }
87
+
88
+ if (buf.length > 0) {
89
+ segs.push({ text: buf, cls });
90
+ }
91
+
92
+ return segs;
93
+ }
94
+ </script>
95
+
96
+ <script lang="ts">
97
+ import Graph from '../frame/Graph.svelte';
98
+ import GraphBody from '../frame/GraphBody.svelte';
99
+
100
+ let {
101
+ title,
102
+ animated = true,
103
+ cols = 60,
104
+ rows = 14,
105
+ speedMs = 90,
106
+ cooling = 0.6,
107
+ seedNum = 3,
108
+ label = 'simulated fire',
109
+ class: className = ''
110
+ }: GraphFireProps = $props();
111
+
112
+ // svelte-ignore state_referenced_locally
113
+ const rand = mulberry32(seedNum);
114
+ // svelte-ignore state_referenced_locally
115
+ const width = Math.max(1, cols);
116
+ // svelte-ignore state_referenced_locally
117
+ const height = Math.max(1, rows);
118
+
119
+ function initialGrid(): number[][] {
120
+ const grid: number[][] = [];
121
+
122
+ for (let y = 0; y < height; y++) {
123
+ grid.push(new Array<number>(width).fill(0));
124
+ }
125
+
126
+ for (let x = 0; x < width; x++) {
127
+ grid[height - 1][x] = 1;
128
+ }
129
+
130
+ return grid;
131
+ }
132
+
133
+ /** Each cell takes heat from below minus random cooling; the base row stays max. */
134
+ function stepFire(grid: number[][]): number[][] {
135
+ const next: number[][] = [];
136
+
137
+ for (let y = 0; y < height; y++) {
138
+ next.push(new Array<number>(width).fill(0));
139
+ }
140
+
141
+ for (let y = 0; y < height - 1; y++) {
142
+ for (let x = 0; x < width; x++) {
143
+ const below =
144
+ (grid[y + 1][(x - 1 + width) % width] +
145
+ grid[y + 1][x] +
146
+ grid[y + 1][(x + 1) % width]) /
147
+ 3;
148
+
149
+ next[y][x] = Math.max(0, below * (1 - rand() * cooling * 0.5));
150
+ }
151
+ }
152
+
153
+ for (let x = 0; x < width; x++) {
154
+ next[height - 1][x] = 1;
155
+ }
156
+
157
+ return next;
158
+ }
159
+
160
+ const moving = $derived(
161
+ animated &&
162
+ (typeof window === 'undefined' ||
163
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches)
164
+ );
165
+
166
+ let sim = initialGrid();
167
+
168
+ // svelte-ignore state_referenced_locally
169
+ const preTicks = animated && !prefersReduced() ? 4 : height * 2 + 6;
170
+
171
+ // Just lit when animated; a developed mid-burn when frozen.
172
+ for (let i = 0; i < preTicks; i++) {
173
+ sim = stepFire(sim);
174
+ }
175
+
176
+ // svelte-ignore state_referenced_locally
177
+ let grid = $state<number[][]>(sim);
178
+
179
+ $effect(() => {
180
+ if (!moving) {
181
+ return;
182
+ }
183
+
184
+ const timer = window.setInterval(() => {
185
+ grid = stepFire(grid);
186
+ }, speedMs);
187
+
188
+ return () => window.clearInterval(timer);
189
+ });
190
+
191
+ const view = $derived.by((): Seg[][] => grid.map((row) => heatSegs(row)));
192
+ </script>
193
+
194
+ <Graph {title} class={className}>
195
+ <GraphBody>
196
+ <div class="fire">
197
+ <div class="viewport">
198
+ <pre class="art" aria-hidden="true"><code>{#each view 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 < view.length - 1}{'\n'}{/if}{/each}</code></pre>
199
+ </div>
200
+ {#if label}
201
+ <p class="caption">{label}</p>
202
+ {/if}
203
+ </div>
204
+ </GraphBody>
205
+ </Graph>
206
+
207
+ <style>
208
+ .fire {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 1rem;
212
+ min-width: 0;
213
+ font-size: 0.875rem;
214
+ }
215
+
216
+ .viewport {
217
+ overflow-x: auto;
218
+ }
219
+
220
+ .art {
221
+ margin: 0;
222
+ font-size: 0.85rem;
223
+ line-height: 1.15;
224
+ color: var(--graph-foreground, oklch(0.93 0 0));
225
+ white-space: pre;
226
+ }
227
+
228
+ .hot {
229
+ color: var(--graph-accent, oklch(0.78 0.17 155));
230
+ font-weight: 600;
231
+ }
232
+
233
+ .mid {
234
+ color: var(--graph-muted, oklch(0.62 0 0));
235
+ }
236
+
237
+ .faint {
238
+ color: var(--graph-faint, oklch(0.3 0 0));
239
+ }
240
+
241
+ .caption {
242
+ margin: 0;
243
+ font-size: 0.8rem;
244
+ color: var(--graph-muted, oklch(0.62 0 0));
245
+ }
246
+ </style>
@@ -0,0 +1,22 @@
1
+ export interface GraphFireProps {
2
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
3
+ title: string;
4
+ /** Animate the burn. Default true. Reduced motion freezes a mid-burn frame. */
5
+ animated?: boolean;
6
+ /** Width of the fire in characters. Default 60. */
7
+ cols?: number;
8
+ /** Height of the fire in rows. Default 14. */
9
+ rows?: number;
10
+ /** Milliseconds per simulation step. Default 90. */
11
+ speedMs?: number;
12
+ /** Random decay strength; higher burns out sooner. Default 0.6. */
13
+ cooling?: number;
14
+ /** Seed for the reproducible burn. Default 3. */
15
+ seedNum?: number;
16
+ /** Short caption under the art; the art itself is decorative. */
17
+ label?: string;
18
+ class?: string;
19
+ }
20
+ declare const GraphFire: import("svelte").Component<GraphFireProps, {}, "">;
21
+ type GraphFire = ReturnType<typeof GraphFire>;
22
+ export default GraphFire;