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,262 @@
1
+ <script module lang="ts">
2
+ export interface GraphMandalaProps {
3
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
4
+ title: string;
5
+ /** Animate the bloom. Default true. Reduced motion freezes a fully bloomed frame. */
6
+ animated?: boolean;
7
+ /** Width of the scene in characters; at least 38 so the outermost ring fits. Default 48. */
8
+ cols?: number;
9
+ /** Height of the scene in rows; at least 20 so the circle fits the cell aspect. Default 24. */
10
+ rows?: number;
11
+ /** Rotational folds (K). One cell of the canonical petal is rotated K times. Default 8. */
12
+ folds?: number;
13
+ /** Number of concentric lotus-petal rings. Default 5. */
14
+ rings?: number;
15
+ /** Milliseconds per tick. Default 130. */
16
+ speedMs?: number;
17
+ /** Seed for the per-ring radius and phase perturbation. Default 11. */
18
+ seedNum?: number;
19
+ /** Short caption under the art; the art itself is decorative. */
20
+ label?: string;
21
+ class?: string;
22
+ }
23
+
24
+ /** Deterministic mulberry32 PRNG so a seed replays the same mandala. */
25
+ function mulberry32(seed: number): () => number {
26
+ let a = seed >>> 0;
27
+
28
+ return () => {
29
+ a = (a + 0x6d2b79f5) | 0;
30
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
31
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
32
+
33
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
34
+ };
35
+ }
36
+
37
+ interface Seg {
38
+ text: string;
39
+ cls?: string;
40
+ }
41
+ </script>
42
+
43
+ <script lang="ts">
44
+ import Graph from '../frame/Graph.svelte';
45
+ import GraphBody from '../frame/GraphBody.svelte';
46
+
47
+ let {
48
+ title,
49
+ animated = true,
50
+ cols = 48,
51
+ rows = 24,
52
+ folds = 8,
53
+ rings = 5,
54
+ speedMs = 130,
55
+ seedNum = 11,
56
+ label = 'the circle that holds',
57
+ class: className = ''
58
+ }: GraphMandalaProps = $props();
59
+
60
+ // Character cells are ~2x taller than wide; aspect-correct so the lotus reads round.
61
+ const aspect = 2.1;
62
+
63
+ // svelte-ignore state_referenced_locally
64
+ const width = Math.max(38, cols);
65
+ // svelte-ignore state_referenced_locally
66
+ const height = Math.max(20, rows);
67
+ const cx = width / 2;
68
+ const cy = height / 2;
69
+ // svelte-ignore state_referenced_locally
70
+ const K = Math.max(3, Math.min(16, Math.round(folds)));
71
+ // svelte-ignore state_referenced_locally
72
+ const N = Math.max(3, Math.min(7, Math.round(rings)));
73
+
74
+ // svelte-ignore state_referenced_locally
75
+ const rand = mulberry32(seedNum);
76
+
77
+ // Per-ring radii; seeded perturbation so every seed is a different mandala.
78
+ interface Ring {
79
+ r_in: number;
80
+ r_out: number;
81
+ phase: number;
82
+ }
83
+
84
+ // svelte-ignore state_referenced_locally
85
+ const ringData: Ring[] = (() => {
86
+ const maxR = Math.min(width / 2 - 0.5, (height / 2) * aspect - 0.5);
87
+ const baseR = maxR / (N - 0.2);
88
+ const out: Ring[] = [];
89
+
90
+ for (let i = 0; i < N; i++) {
91
+ const center = (i + 0.5) * baseR;
92
+ const half = 0.38 * baseR * (0.85 + 0.3 * rand());
93
+ const r_in = Math.max(0, center - half);
94
+ const r_out = Math.min(maxR, center + half);
95
+ const phase = (rand() - 0.5) * 0.5;
96
+ out.push({ r_in, r_out, phase });
97
+ }
98
+
99
+ return out;
100
+ })();
101
+
102
+ const ringTicks = 14;
103
+ const maxBloom = N * ringTicks;
104
+ const phaseSpeed = 0.02;
105
+
106
+ // svelte-ignore state_referenced_locally
107
+ const initialTick = animated ? Math.floor(maxBloom * 0.65) : maxBloom;
108
+
109
+ // svelte-ignore state_referenced_locally
110
+ let tick = $state(initialTick);
111
+
112
+ const moving = $derived(
113
+ animated &&
114
+ (typeof window === 'undefined' ||
115
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches)
116
+ );
117
+
118
+ $effect(() => {
119
+ if (!moving) {
120
+ return;
121
+ }
122
+
123
+ const timer = window.setInterval(() => {
124
+ tick += 1;
125
+ }, speedMs);
126
+
127
+ return () => window.clearInterval(timer);
128
+ });
129
+
130
+ const view = $derived.by((): Seg[][] => {
131
+ const phase = tick * phaseSpeed;
132
+ const out: Seg[][] = [];
133
+
134
+ for (let y = 0; y < height; y++) {
135
+ const segs: Seg[] = [];
136
+ let buf = '';
137
+ let cls: string | undefined;
138
+
139
+ const flush = () => {
140
+ if (buf.length > 0) {
141
+ segs.push({ text: buf, cls });
142
+ }
143
+
144
+ buf = '';
145
+ };
146
+
147
+ for (let x = 0; x < width; x++) {
148
+ const dx = x - cx;
149
+ const dy = (y - cy) * aspect;
150
+ const d = Math.sqrt(dx * dx + dy * dy);
151
+ // Angle in [-π, π]; subtract phase so the whole figure slowly rotates.
152
+ const a = Math.atan2(dy, dx) - phase;
153
+ // Find the petal this cell belongs to: nearest of K divisions of the circle.
154
+ const kFloat = (a + Math.PI) * (K / (Math.PI * 2));
155
+ const k = Math.round(kFloat);
156
+ const localA = a - k * ((Math.PI * 2) / K) + Math.PI;
157
+ const localAbs = Math.abs(localA);
158
+
159
+ let ch = ' ';
160
+ let next: string | undefined;
161
+
162
+ for (let i = 0; i < N; i++) {
163
+ const r = ringData[i]!;
164
+ if (d < r.r_in || d > r.r_out) {
165
+ continue;
166
+ }
167
+ // t goes 0 (inner edge of ring) to 1 (outer edge).
168
+ const t = (d - r.r_in) / (r.r_out - r.r_in);
169
+ // Lotus petal: half-width peaks at the middle, narrows at both ends.
170
+ const halfWidth = (Math.PI / K) * Math.sin(Math.PI * t);
171
+ if (localAbs > halfWidth) {
172
+ continue;
173
+ }
174
+ // Per-ring bloom progress: 0 → 1 over `ringTicks`, then stays 1.
175
+ const growth = Math.min(1, Math.max(0, (tick - i * ringTicks) / ringTicks));
176
+ if (t > growth) {
177
+ break; // not yet reached at this radius; nothing deeper
178
+ }
179
+ // Glyph by ring: bindu ring hot, middle rings mid, outer rings faint.
180
+ if (i === 0) {
181
+ ch = '█';
182
+ next = 'hot';
183
+ } else if (i < N / 2) {
184
+ ch = '▓';
185
+ next = 'mid';
186
+ } else {
187
+ ch = '▒';
188
+ next = 'faint';
189
+ }
190
+ break;
191
+ }
192
+
193
+ if (next === cls) {
194
+ buf += ch;
195
+ } else {
196
+ flush();
197
+ cls = next;
198
+ buf = ch;
199
+ }
200
+ }
201
+
202
+ flush();
203
+ out.push(segs);
204
+ }
205
+
206
+ return out;
207
+ });
208
+ </script>
209
+
210
+ <Graph {title} class={className}>
211
+ <GraphBody>
212
+ <div class="mandala">
213
+ <div class="viewport">
214
+ <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>
215
+ </div>
216
+ {#if label}
217
+ <p class="caption">{label}</p>
218
+ {/if}
219
+ </div>
220
+ </GraphBody>
221
+ </Graph>
222
+
223
+ <style>
224
+ .mandala {
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: 1rem;
228
+ min-width: 0;
229
+ font-size: 0.875rem;
230
+ }
231
+
232
+ .viewport {
233
+ overflow-x: auto;
234
+ }
235
+
236
+ .art {
237
+ margin: 0;
238
+ font-size: 0.85rem;
239
+ line-height: 1.15;
240
+ color: var(--graph-foreground, oklch(0.93 0 0));
241
+ white-space: pre;
242
+ }
243
+
244
+ .hot {
245
+ color: var(--graph-accent, oklch(0.78 0.17 155));
246
+ font-weight: 600;
247
+ }
248
+
249
+ .mid {
250
+ color: var(--graph-muted, oklch(0.62 0 0));
251
+ }
252
+
253
+ .faint {
254
+ color: var(--graph-faint, oklch(0.3 0 0));
255
+ }
256
+
257
+ .caption {
258
+ margin: 0;
259
+ font-size: 0.8rem;
260
+ color: var(--graph-muted, oklch(0.62 0 0));
261
+ }
262
+ </style>
@@ -0,0 +1,24 @@
1
+ export interface GraphMandalaProps {
2
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
3
+ title: string;
4
+ /** Animate the bloom. Default true. Reduced motion freezes a fully bloomed frame. */
5
+ animated?: boolean;
6
+ /** Width of the scene in characters; at least 38 so the outermost ring fits. Default 48. */
7
+ cols?: number;
8
+ /** Height of the scene in rows; at least 20 so the circle fits the cell aspect. Default 24. */
9
+ rows?: number;
10
+ /** Rotational folds (K). One cell of the canonical petal is rotated K times. Default 8. */
11
+ folds?: number;
12
+ /** Number of concentric lotus-petal rings. Default 5. */
13
+ rings?: number;
14
+ /** Milliseconds per tick. Default 130. */
15
+ speedMs?: number;
16
+ /** Seed for the per-ring radius and phase perturbation. Default 11. */
17
+ seedNum?: number;
18
+ /** Short caption under the art; the art itself is decorative. */
19
+ label?: string;
20
+ class?: string;
21
+ }
22
+ declare const GraphMandala: import("svelte").Component<GraphMandalaProps, {}, "">;
23
+ type GraphMandala = ReturnType<typeof GraphMandala>;
24
+ export default GraphMandala;
@@ -0,0 +1,290 @@
1
+ <script module lang="ts">
2
+ /** ASCII Mandelbrot with a slow cinematic zoom into seahorse valley. */
3
+ export interface GraphMandelProps {
4
+ /** Caption drawn on the top edge as `[ TITLE ]`. */
5
+ title: string;
6
+ /** Columns of the grid. Default 66. */
7
+ cols?: number;
8
+ /** Rows of the grid. Default 20. */
9
+ rows?: number;
10
+ /** Escape count -> glyph, low to high. Keep it ASCII for grid alignment. */
11
+ charset?: string;
12
+ /** Milliseconds per zoom tick. Default 140. */
13
+ speedMs?: number;
14
+ /** Window shrink factor per tick. Default 1.06. */
15
+ zoomPerTick?: number;
16
+ /** Starting iteration budget; grows +2 per tick, capped at 300. Default 90. */
17
+ maxIter?: number;
18
+ /** Start playing on mount. Default true. Reduced motion starts paused. */
19
+ animated?: boolean;
20
+ /** Show the playback controls. Default true. */
21
+ showControls?: boolean;
22
+ /** Character at each corner of the frame. Default "+". */
23
+ corner?: string;
24
+ class?: string;
25
+ }
26
+
27
+ /** The classic seahorse valley point the zoom converges toward. */
28
+ const TARGET_X = -0.743643887037151;
29
+ const TARGET_Y = 0.131825904205330;
30
+ const START_X = -0.7;
31
+ const START_Y = 0;
32
+ /** Half of the re span [-2.2, 0.9]. */
33
+ const START_HALF_RE = 1.55;
34
+ const ITER_CAP = 300;
35
+ /** Character cells are roughly twice as tall as they are wide. */
36
+ const CELL_ASPECT = 2;
37
+ /** Glyphs this far from the end of the charset render with the accent 'hot' tint. */
38
+ const HOT_FROM_END = 3;
39
+
40
+ /** Iterations before |z| escapes the radius-2 disc; `budget` means inside the set. */
41
+ function escapeCount(re0: number, im0: number, budget: number): number {
42
+ let zr = 0;
43
+ let zi = 0;
44
+ let count = 0;
45
+
46
+ while (count < budget) {
47
+ const zr2 = zr * zr;
48
+ const zi2 = zi * zi;
49
+
50
+ if (zr2 + zi2 > 4) {
51
+ return count;
52
+ }
53
+
54
+ zi = 2 * zr * zi + im0;
55
+ zr = zr2 - zi2 + re0;
56
+ count++;
57
+ }
58
+
59
+ return count;
60
+ }
61
+ </script>
62
+
63
+ <script lang="ts">
64
+ import Graph from '../frame/Graph.svelte';
65
+ import GraphBody from '../frame/GraphBody.svelte';
66
+
67
+ let {
68
+ title,
69
+ cols = 66,
70
+ rows = 20,
71
+ charset = ' .:-=+*#%@',
72
+ speedMs = 140,
73
+ zoomPerTick = 1.06,
74
+ maxIter = 90,
75
+ animated = true,
76
+ showControls = true,
77
+ corner,
78
+ class: className = ''
79
+ }: GraphMandelProps = $props();
80
+
81
+ interface ArtSeg {
82
+ text: string;
83
+ cls?: string;
84
+ }
85
+
86
+ // svelte-ignore state_referenced_locally
87
+ let cx = $state(START_X);
88
+ let cy = $state(START_Y);
89
+ let halfRe = $state(START_HALF_RE);
90
+ // svelte-ignore state_referenced_locally
91
+ let iters = $state(Math.max(1, maxIter));
92
+ // svelte-ignore state_referenced_locally
93
+ let isPlaying = $state(animated);
94
+
95
+ $effect(() => {
96
+ // Reduced motion never auto-plays. Explicit play by the user is still fine.
97
+ const query = window.matchMedia('(prefers-reduced-motion: reduce)');
98
+ if (query.matches) {
99
+ isPlaying = false;
100
+ }
101
+
102
+ const onChange = (event: MediaQueryListEvent) => {
103
+ if (event.matches) {
104
+ isPlaying = false;
105
+ }
106
+ };
107
+
108
+ query.addEventListener('change', onChange);
109
+ return () => query.removeEventListener('change', onChange);
110
+ });
111
+
112
+ $effect(() => {
113
+ if (!isPlaying) {
114
+ return;
115
+ }
116
+
117
+ const timer = window.setInterval(tick, Math.max(30, speedMs));
118
+
119
+ return () => window.clearInterval(timer);
120
+ });
121
+
122
+ function tick(): void {
123
+ const factor = Math.max(1.0001, zoomPerTick);
124
+ // Close the same fraction of the remaining distance to the target each tick,
125
+ // so the seahorse valley point stays at a fixed offset inside the window.
126
+ const drift = 1 - 1 / factor;
127
+
128
+ cx += (TARGET_X - cx) * drift;
129
+ cy += (TARGET_Y - cy) * drift;
130
+ halfRe /= factor;
131
+ iters = Math.min(ITER_CAP, iters + 2);
132
+ }
133
+
134
+ function handleToggle() {
135
+ isPlaying = !isPlaying;
136
+ }
137
+
138
+ function handleReset() {
139
+ cx = START_X;
140
+ cy = START_Y;
141
+ halfRe = START_HALF_RE;
142
+ iters = Math.max(1, maxIter);
143
+ }
144
+
145
+ const halfIm = $derived((halfRe * rows * CELL_ASPECT) / cols);
146
+ const zoom = $derived(START_HALF_RE / halfRe);
147
+ const zoomLabel = $derived(zoom >= 100 ? String(Math.round(zoom)) : zoom.toFixed(1));
148
+ const iterLabel = $derived(String(iters).padStart(3, '0'));
149
+
150
+ // Every row is padded to exactly `cols` cells so the grid width never shifts.
151
+ const art = $derived.by((): ArtSeg[][] => {
152
+ const glyphs = Array.from(charset.length > 0 ? charset : ' ');
153
+ const last = glyphs.length - 1;
154
+ const hotFloor = Math.max(0, glyphs.length - HOT_FROM_END);
155
+ const out: ArtSeg[][] = [];
156
+
157
+ for (let r = 0; r < rows; r++) {
158
+ const im0 = cy + halfIm - ((r + 0.5) / rows) * 2 * halfIm;
159
+ const row: ArtSeg[] = [];
160
+ let buf = '';
161
+ let hot = false;
162
+
163
+ const flush = () => {
164
+ if (buf.length > 0) {
165
+ row.push(hot ? { text: buf, cls: 'hot' } : { text: buf });
166
+ buf = '';
167
+ }
168
+ };
169
+
170
+ for (let c = 0; c < cols; c++) {
171
+ const re0 = cx - halfRe + ((c + 0.5) / cols) * 2 * halfRe;
172
+ const count = escapeCount(re0, im0, iters);
173
+ const idx = Math.min(last, Math.floor((count / iters) * glyphs.length));
174
+ const isHot = idx >= hotFloor;
175
+
176
+ if (isHot !== hot) {
177
+ flush();
178
+ hot = isHot;
179
+ }
180
+
181
+ buf += glyphs[idx] ?? ' ';
182
+ }
183
+
184
+ flush();
185
+ out.push(row);
186
+ }
187
+
188
+ return out;
189
+ });
190
+ </script>
191
+
192
+ <Graph {title} {corner} class={className}>
193
+ <GraphBody>
194
+ <div class="mandel">
195
+ <div class="viewport">
196
+ <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>
197
+ </div>
198
+
199
+ <p class="caption">zoom {zoomLabel}x · iter {iterLabel}</p>
200
+
201
+ {#if showControls}
202
+ <div class="controls">
203
+ <div class="buttons">
204
+ <button
205
+ class="ctrl primary"
206
+ type="button"
207
+ onclick={handleToggle}
208
+ aria-label={isPlaying ? 'Pause zoom' : 'Play zoom'}
209
+ >
210
+ {isPlaying ? '❚❚ pause' : '▶ play'}
211
+ </button>
212
+ <button class="ctrl" type="button" onclick={handleReset} aria-label="Reset zoom">
213
+ reset
214
+ </button>
215
+ </div>
216
+ </div>
217
+ {/if}
218
+ </div>
219
+ </GraphBody>
220
+ </Graph>
221
+
222
+ <style>
223
+ .mandel {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 0.875rem;
227
+ min-width: 0;
228
+ font-size: 0.875rem;
229
+ }
230
+
231
+ .viewport {
232
+ overflow-x: auto;
233
+ }
234
+
235
+ .art {
236
+ margin: 0;
237
+ font-size: 0.85rem;
238
+ line-height: 1.2;
239
+ color: var(--graph-muted, oklch(0.62 0 0));
240
+ white-space: pre;
241
+ }
242
+
243
+ .hot {
244
+ color: var(--graph-accent, oklch(0.78 0.17 155));
245
+ font-weight: 600;
246
+ }
247
+
248
+ .caption {
249
+ margin: 0;
250
+ font-size: 0.75rem;
251
+ color: var(--graph-muted, oklch(0.62 0 0));
252
+ }
253
+
254
+ .controls {
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: space-between;
258
+ flex-wrap: wrap;
259
+ gap: 1rem;
260
+ padding-top: 0.875rem;
261
+ border-top: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
262
+ }
263
+
264
+ .buttons {
265
+ display: flex;
266
+ align-items: center;
267
+ gap: 0.35rem;
268
+ }
269
+
270
+ .ctrl {
271
+ padding: 0.25rem 0.6rem;
272
+ font-size: 0.75rem;
273
+ color: var(--graph-muted, oklch(0.62 0 0));
274
+ background: none;
275
+ border: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
276
+ border-radius: 0;
277
+ cursor: pointer;
278
+ }
279
+
280
+ .ctrl:hover {
281
+ color: var(--graph-foreground, oklch(0.93 0 0));
282
+ border-color: var(--graph-muted, oklch(0.62 0 0));
283
+ }
284
+
285
+ .primary {
286
+ color: var(--graph-accent, oklch(0.78 0.17 155));
287
+ border-color: var(--graph-accent, oklch(0.78 0.17 155));
288
+ font-weight: 600;
289
+ }
290
+ </style>
@@ -0,0 +1,27 @@
1
+ /** ASCII Mandelbrot with a slow cinematic zoom into seahorse valley. */
2
+ export interface GraphMandelProps {
3
+ /** Caption drawn on the top edge as `[ TITLE ]`. */
4
+ title: string;
5
+ /** Columns of the grid. Default 66. */
6
+ cols?: number;
7
+ /** Rows of the grid. Default 20. */
8
+ rows?: number;
9
+ /** Escape count -> glyph, low to high. Keep it ASCII for grid alignment. */
10
+ charset?: string;
11
+ /** Milliseconds per zoom tick. Default 140. */
12
+ speedMs?: number;
13
+ /** Window shrink factor per tick. Default 1.06. */
14
+ zoomPerTick?: number;
15
+ /** Starting iteration budget; grows +2 per tick, capped at 300. Default 90. */
16
+ maxIter?: number;
17
+ /** Start playing on mount. Default true. Reduced motion starts paused. */
18
+ animated?: boolean;
19
+ /** Show the playback controls. Default true. */
20
+ showControls?: boolean;
21
+ /** Character at each corner of the frame. Default "+". */
22
+ corner?: string;
23
+ class?: string;
24
+ }
25
+ declare const GraphMandel: import("svelte").Component<GraphMandelProps, {}, "">;
26
+ type GraphMandel = ReturnType<typeof GraphMandel>;
27
+ export default GraphMandel;