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,253 @@
1
+ <script module lang="ts">
2
+ export interface GraphJapaProps {
3
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
4
+ title: string;
5
+ /** Animate the marker around the mala. Default true. Reduced motion freezes a mid-mala frame. */
6
+ animated?: boolean;
7
+ /** Width of the scene in characters. Default 60. */
8
+ cols?: number;
9
+ /** Height of the scene in rows. Default 22. */
10
+ rows?: number;
11
+ /** Total beads per mala. The mala is 108; the counter shows current/total. Default 108. */
12
+ beads?: number;
13
+ /** Milliseconds per bead. Default 110. */
14
+ speedMs?: number;
15
+ /** Short caption under the art; the art itself is decorative. */
16
+ label?: string;
17
+ class?: string;
18
+ }
19
+
20
+ interface Seg {
21
+ text: string;
22
+ cls?: string;
23
+ }
24
+ </script>
25
+
26
+ <script lang="ts">
27
+ import Graph from '../frame/Graph.svelte';
28
+ import GraphBody from '../frame/GraphBody.svelte';
29
+
30
+ let {
31
+ title,
32
+ animated = true,
33
+ cols = 60,
34
+ rows = 22,
35
+ beads = 108,
36
+ speedMs = 110,
37
+ label = 'a breath for every bead',
38
+ class: className = ''
39
+ }: GraphJapaProps = $props();
40
+
41
+ // Character cells are ~2x taller than wide; correct so the mala reads round.
42
+ const aspect = 2.1;
43
+
44
+ // svelte-ignore state_referenced_locally
45
+ const width = Math.max(28, cols);
46
+ // svelte-ignore state_referenced_locally
47
+ const height = Math.max(14, rows);
48
+ // svelte-ignore state_referenced_locally
49
+ const N = Math.max(8, Math.min(216, Math.round(beads)));
50
+
51
+ // svelte-ignore state_referenced_locally
52
+ const cx = width / 2;
53
+ // svelte-ignore state_referenced_locally
54
+ const cy = (height - 2) / 2 + 1; // leave two rows for the counter at the bottom
55
+
56
+ // Radius: prefer the horizontal budget, fall back to the vertical (aspect-corrected).
57
+ const r = Math.min(cx - 3, (cy - 1.5) * aspect);
58
+
59
+ // Bead positions, in display cells. Multiple beads may map to the same cell at
60
+ // the top/bottom of the circle (aspect squeezes them); the map keeps the first index.
61
+ interface Bead {
62
+ x: number;
63
+ y: number;
64
+ i: number;
65
+ }
66
+
67
+ // svelte-ignore state_referenced_locally
68
+ const beads$: Bead[] = (() => {
69
+ const out: Bead[] = [];
70
+
71
+ for (let i = 0; i < N; i++) {
72
+ const a = (i / N) * Math.PI * 2 - Math.PI / 2;
73
+ const x = Math.round(cx + r * Math.cos(a));
74
+ const y = Math.round(cy + (r * Math.sin(a)) / aspect);
75
+ out.push({ x, y, i });
76
+ }
77
+
78
+ return out;
79
+ })();
80
+
81
+ // svelte-ignore state_referenced_locally
82
+ const beadCells = new Map<number, number[]>(
83
+ beads$.map((b) => {
84
+ const key = b.y * width + b.x;
85
+ return [key, [b.i]];
86
+ }).reduce((m, [k, v]) => {
87
+ const existing = m.get(k as number);
88
+ if (existing) {
89
+ existing.push(...(v as number[]));
90
+ } else {
91
+ m.set(k as number, v as number[]);
92
+ }
93
+ return m;
94
+ }, new Map<number, number[]>())
95
+ );
96
+
97
+ // svelte-ignore state_referenced_locally
98
+ const initialTick = animated ? Math.floor(N * 0.55) : Math.floor(N * 0.5);
99
+
100
+ // svelte-ignore state_referenced_locally
101
+ let tick = $state(initialTick);
102
+
103
+ const moving = $derived(
104
+ animated &&
105
+ (typeof window === 'undefined' ||
106
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches)
107
+ );
108
+
109
+ $effect(() => {
110
+ if (!moving) {
111
+ return;
112
+ }
113
+
114
+ const timer = window.setInterval(() => {
115
+ tick += 1;
116
+ }, speedMs);
117
+
118
+ return () => window.clearInterval(timer);
119
+ });
120
+
121
+ const TAIL = 5;
122
+ const SHIMMER_TICKS = 6;
123
+
124
+ const view = $derived.by((): Seg[][] => {
125
+ const marker = ((tick % N) + N) % N;
126
+ const inMala = marker; // 0..N-1
127
+ const shimmering = tick > 0 && inMala < SHIMMER_TICKS;
128
+
129
+ // Tail indices (the TAIL beads behind the marker, mod N).
130
+ const tailSet = new Set<number>();
131
+
132
+ for (let k = 1; k <= TAIL; k++) {
133
+ tailSet.add((marker - k + N) % N);
134
+ }
135
+
136
+ const out: Seg[][] = [];
137
+ const counter = `${inMala + 1} / ${N}`;
138
+ const counterRow = height - 1;
139
+ const counterStart = width - counter.length;
140
+
141
+ for (let y = 0; y < height; y++) {
142
+ const segs: Seg[] = [];
143
+ let buf = '';
144
+ let cls: string | undefined;
145
+
146
+ const flush = () => {
147
+ if (buf.length > 0) {
148
+ segs.push({ text: buf, cls });
149
+ }
150
+
151
+ buf = '';
152
+ };
153
+
154
+ for (let x = 0; x < width; x++) {
155
+ let ch = ' ';
156
+ let next: string | undefined;
157
+
158
+ // Counter on the bottom row, right-aligned.
159
+ if (y === counterRow && x >= counterStart) {
160
+ ch = counter[x - counterStart]!;
161
+ } else {
162
+ const indices = beadCells.get(y * width + x);
163
+
164
+ if (indices && indices.length > 0) {
165
+ const isMarker = indices.includes(marker);
166
+ const isTail = !isMarker && indices.some((i) => tailSet.has(i));
167
+
168
+ if (isMarker) {
169
+ ch = '█';
170
+ next = 'hot';
171
+ } else if (shimmering) {
172
+ ch = '▓';
173
+ next = 'mid';
174
+ } else if (isTail) {
175
+ ch = '▒';
176
+ next = 'faint';
177
+ } else {
178
+ ch = '·';
179
+ next = 'faint';
180
+ }
181
+ }
182
+ }
183
+
184
+ if (next === cls) {
185
+ buf += ch;
186
+ } else {
187
+ flush();
188
+ cls = next;
189
+ buf = ch;
190
+ }
191
+ }
192
+
193
+ flush();
194
+ out.push(segs);
195
+ }
196
+
197
+ return out;
198
+ });
199
+ </script>
200
+
201
+ <Graph {title} class={className}>
202
+ <GraphBody>
203
+ <div class="japa">
204
+ <div class="viewport">
205
+ <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>
206
+ </div>
207
+ {#if label}
208
+ <p class="caption">{label}</p>
209
+ {/if}
210
+ </div>
211
+ </GraphBody>
212
+ </Graph>
213
+
214
+ <style>
215
+ .japa {
216
+ display: flex;
217
+ flex-direction: column;
218
+ gap: 1rem;
219
+ min-width: 0;
220
+ font-size: 0.875rem;
221
+ }
222
+
223
+ .viewport {
224
+ overflow-x: auto;
225
+ }
226
+
227
+ .art {
228
+ margin: 0;
229
+ font-size: 0.85rem;
230
+ line-height: 1.15;
231
+ color: var(--graph-foreground, oklch(0.93 0 0));
232
+ white-space: pre;
233
+ }
234
+
235
+ .hot {
236
+ color: var(--graph-accent, oklch(0.78 0.17 155));
237
+ font-weight: 600;
238
+ }
239
+
240
+ .mid {
241
+ color: var(--graph-muted, oklch(0.62 0 0));
242
+ }
243
+
244
+ .faint {
245
+ color: var(--graph-faint, oklch(0.3 0 0));
246
+ }
247
+
248
+ .caption {
249
+ margin: 0;
250
+ font-size: 0.8rem;
251
+ color: var(--graph-muted, oklch(0.62 0 0));
252
+ }
253
+ </style>
@@ -0,0 +1,20 @@
1
+ export interface GraphJapaProps {
2
+ /** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
3
+ title: string;
4
+ /** Animate the marker around the mala. Default true. Reduced motion freezes a mid-mala frame. */
5
+ animated?: boolean;
6
+ /** Width of the scene in characters. Default 60. */
7
+ cols?: number;
8
+ /** Height of the scene in rows. Default 22. */
9
+ rows?: number;
10
+ /** Total beads per mala. The mala is 108; the counter shows current/total. Default 108. */
11
+ beads?: number;
12
+ /** Milliseconds per bead. Default 110. */
13
+ speedMs?: number;
14
+ /** Short caption under the art; the art itself is decorative. */
15
+ label?: string;
16
+ class?: string;
17
+ }
18
+ declare const GraphJapa: import("svelte").Component<GraphJapaProps, {}, "">;
19
+ type GraphJapa = ReturnType<typeof GraphJapa>;
20
+ export default GraphJapa;
@@ -0,0 +1,312 @@
1
+ <script module lang="ts">
2
+ /** Conway's Game of Life in block glyphs, framed. Alive is accent, the dead leave a faint trail. */
3
+ export interface GraphLifeProps {
4
+ /** Caption drawn on the top edge as `[ TITLE ]`. */
5
+ title: string;
6
+ /** Columns of the board. Default 44. */
7
+ cols?: number;
8
+ /** Rows of the board. Default 14. */
9
+ rows?: number;
10
+ /** Random fill probability when no `seed` is given. Default 0.28. */
11
+ density?: number;
12
+ /** Explicit initial board. Padded/cropped to `cols` x `rows`. */
13
+ seed?: boolean[][];
14
+ /** PRNG seed for the random board, reproducible. Default 7. */
15
+ seedNum?: number;
16
+ /** Milliseconds per generation. Default 220. */
17
+ speedMs?: 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
+ /** Deterministic PRNG so `seedNum` reproduces the same board. */
28
+ function mulberry32(seedValue: number): () => number {
29
+ let a = seedValue >>> 0;
30
+ return () => {
31
+ a = (a + 0x6d2b79f5) | 0;
32
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
33
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
34
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
35
+ };
36
+ }
37
+ </script>
38
+
39
+ <script lang="ts">
40
+ import Graph from '../frame/Graph.svelte';
41
+ import GraphBody from '../frame/GraphBody.svelte';
42
+
43
+ let {
44
+ title,
45
+ cols = 44,
46
+ rows = 14,
47
+ density = 0.28,
48
+ seed,
49
+ seedNum = 7,
50
+ speedMs = 220,
51
+ animated = true,
52
+ showControls = true,
53
+ corner,
54
+ class: className = ''
55
+ }: GraphLifeProps = $props();
56
+
57
+ interface ArtSeg {
58
+ text: string;
59
+ cls?: string;
60
+ }
61
+
62
+ function initialBoard(): boolean[][] {
63
+ if (seed) {
64
+ return Array.from({ length: rows }, (_, r) =>
65
+ Array.from({ length: cols }, (_, c) => seed[r]?.[c] === true)
66
+ );
67
+ }
68
+
69
+ const rng = mulberry32(seedNum);
70
+
71
+ return Array.from({ length: rows }, () => Array.from({ length: cols }, () => rng() < density));
72
+ }
73
+
74
+ function emptyGrid(): boolean[][] {
75
+ return Array.from({ length: rows }, () => Array.from({ length: cols }, () => false));
76
+ }
77
+
78
+ function emptyTrail(): number[][] {
79
+ return Array.from({ length: rows }, () => Array.from({ length: cols }, () => 0));
80
+ }
81
+
82
+ // svelte-ignore state_referenced_locally
83
+ let grid = $state(initialBoard());
84
+ // svelte-ignore state_referenced_locally
85
+ let trail = $state(emptyTrail());
86
+ let gen = $state(0);
87
+ // svelte-ignore state_referenced_locally
88
+ let isPlaying = $state(animated);
89
+
90
+ $effect(() => {
91
+ // Reduced motion never auto-plays. Explicit play/step by the user is still fine.
92
+ const query = window.matchMedia('(prefers-reduced-motion: reduce)');
93
+ if (query.matches) {
94
+ isPlaying = false;
95
+ }
96
+
97
+ const onChange = (event: MediaQueryListEvent) => {
98
+ if (event.matches) {
99
+ isPlaying = false;
100
+ }
101
+ };
102
+
103
+ query.addEventListener('change', onChange);
104
+ return () => query.removeEventListener('change', onChange);
105
+ });
106
+
107
+ $effect(() => {
108
+ if (!isPlaying) {
109
+ return;
110
+ }
111
+
112
+ const timer = window.setInterval(step, Math.max(30, speedMs));
113
+
114
+ return () => window.clearInterval(timer);
115
+ });
116
+
117
+ function step(): void {
118
+ const nextGrid = emptyGrid();
119
+ const nextTrail = emptyTrail();
120
+
121
+ for (let r = 0; r < rows; r++) {
122
+ const up = (r - 1 + rows) % rows;
123
+ const down = (r + 1) % rows;
124
+
125
+ for (let c = 0; c < cols; c++) {
126
+ const left = (c - 1 + cols) % cols;
127
+ const right = (c + 1) % cols;
128
+ const neighbors =
129
+ (grid[up][left] ? 1 : 0) +
130
+ (grid[up][c] ? 1 : 0) +
131
+ (grid[up][right] ? 1 : 0) +
132
+ (grid[r][left] ? 1 : 0) +
133
+ (grid[r][right] ? 1 : 0) +
134
+ (grid[down][left] ? 1 : 0) +
135
+ (grid[down][c] ? 1 : 0) +
136
+ (grid[down][right] ? 1 : 0);
137
+
138
+ const wasAlive = grid[r][c];
139
+ const lives = wasAlive ? neighbors === 2 || neighbors === 3 : neighbors === 3;
140
+ nextGrid[r][c] = lives;
141
+
142
+ if (wasAlive && !lives) {
143
+ nextTrail[r][c] = 2;
144
+ } else if (!lives) {
145
+ nextTrail[r][c] = Math.max(0, trail[r][c] - 1);
146
+ }
147
+ }
148
+ }
149
+
150
+ grid = nextGrid;
151
+ trail = nextTrail;
152
+ gen += 1;
153
+ }
154
+
155
+ function handleToggle() {
156
+ isPlaying = !isPlaying;
157
+ }
158
+
159
+ function handleStep() {
160
+ step();
161
+ }
162
+
163
+ function handleReset() {
164
+ grid = initialBoard();
165
+ trail = emptyTrail();
166
+ gen = 0;
167
+ }
168
+
169
+ const pop = $derived(grid.flat().filter((cell) => cell).length);
170
+ const genLabel = $derived(String(gen).padStart(3, '0'));
171
+ const popLabel = $derived(String(pop).padStart(3, '0'));
172
+
173
+ // Every row is padded to exactly `cols` cells so the grid width never shifts.
174
+ const art = $derived.by((): ArtSeg[][] => {
175
+ const out: ArtSeg[][] = [];
176
+
177
+ for (let r = 0; r < rows; r++) {
178
+ const segs: ArtSeg[] = [];
179
+ let buf = '';
180
+ let mode: 'plain' | 'on' | 'trail' = 'plain';
181
+
182
+ const flush = () => {
183
+ if (buf.length > 0) {
184
+ segs.push(mode === 'plain' ? { text: buf } : { text: buf, cls: mode });
185
+ buf = '';
186
+ }
187
+ };
188
+
189
+ for (let c = 0; c < cols; c++) {
190
+ const next = grid[r][c] ? 'on' : trail[r][c] > 0 ? 'trail' : 'plain';
191
+
192
+ if (next !== mode) {
193
+ flush();
194
+ mode = next;
195
+ }
196
+
197
+ buf += grid[r][c] ? '█' : trail[r][c] > 0 ? '▒' : ' ';
198
+ }
199
+
200
+ flush();
201
+ out.push(segs);
202
+ }
203
+
204
+ return out;
205
+ });
206
+ </script>
207
+
208
+ <Graph {title} {corner} class={className}>
209
+ <GraphBody>
210
+ <div class="life">
211
+ <div class="viewport">
212
+ <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>
213
+ </div>
214
+
215
+ <p class="caption">gen {genLabel} · pop {popLabel}</p>
216
+
217
+ {#if showControls}
218
+ <div class="controls">
219
+ <div class="buttons">
220
+ <button
221
+ class="ctrl primary"
222
+ type="button"
223
+ onclick={handleToggle}
224
+ aria-label={isPlaying ? 'Pause simulation' : 'Play simulation'}
225
+ >
226
+ {isPlaying ? '❚❚ pause' : '▶ play'}
227
+ </button>
228
+ <button class="ctrl" type="button" onclick={handleStep} aria-label="Advance one generation">
229
+ step >
230
+ </button>
231
+ <button class="ctrl" type="button" onclick={handleReset} aria-label="Reset simulation">
232
+ reset
233
+ </button>
234
+ </div>
235
+ </div>
236
+ {/if}
237
+ </div>
238
+ </GraphBody>
239
+ </Graph>
240
+
241
+ <style>
242
+ .life {
243
+ display: flex;
244
+ flex-direction: column;
245
+ gap: 0.875rem;
246
+ min-width: 0;
247
+ font-size: 0.875rem;
248
+ }
249
+
250
+ .viewport {
251
+ overflow-x: auto;
252
+ }
253
+
254
+ .art {
255
+ margin: 0;
256
+ font-size: 0.85rem;
257
+ line-height: 1.2;
258
+ color: var(--graph-faint, oklch(0.3 0 0));
259
+ white-space: pre;
260
+ }
261
+
262
+ .on {
263
+ color: var(--graph-accent, oklch(0.78 0.17 155));
264
+ }
265
+
266
+ .trail {
267
+ color: var(--graph-faint, oklch(0.3 0 0));
268
+ }
269
+
270
+ .caption {
271
+ margin: 0;
272
+ font-size: 0.75rem;
273
+ color: var(--graph-muted, oklch(0.62 0 0));
274
+ }
275
+
276
+ .controls {
277
+ display: flex;
278
+ align-items: center;
279
+ justify-content: space-between;
280
+ flex-wrap: wrap;
281
+ gap: 1rem;
282
+ padding-top: 0.875rem;
283
+ border-top: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
284
+ }
285
+
286
+ .buttons {
287
+ display: flex;
288
+ align-items: center;
289
+ gap: 0.35rem;
290
+ }
291
+
292
+ .ctrl {
293
+ padding: 0.25rem 0.6rem;
294
+ font-size: 0.75rem;
295
+ color: var(--graph-muted, oklch(0.62 0 0));
296
+ background: none;
297
+ border: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
298
+ border-radius: 0;
299
+ cursor: pointer;
300
+ }
301
+
302
+ .ctrl:hover {
303
+ color: var(--graph-foreground, oklch(0.93 0 0));
304
+ border-color: var(--graph-muted, oklch(0.62 0 0));
305
+ }
306
+
307
+ .primary {
308
+ color: var(--graph-accent, oklch(0.78 0.17 155));
309
+ border-color: var(--graph-accent, oklch(0.78 0.17 155));
310
+ font-weight: 600;
311
+ }
312
+ </style>
@@ -0,0 +1,27 @@
1
+ /** Conway's Game of Life in block glyphs, framed. Alive is accent, the dead leave a faint trail. */
2
+ export interface GraphLifeProps {
3
+ /** Caption drawn on the top edge as `[ TITLE ]`. */
4
+ title: string;
5
+ /** Columns of the board. Default 44. */
6
+ cols?: number;
7
+ /** Rows of the board. Default 14. */
8
+ rows?: number;
9
+ /** Random fill probability when no `seed` is given. Default 0.28. */
10
+ density?: number;
11
+ /** Explicit initial board. Padded/cropped to `cols` x `rows`. */
12
+ seed?: boolean[][];
13
+ /** PRNG seed for the random board, reproducible. Default 7. */
14
+ seedNum?: number;
15
+ /** Milliseconds per generation. Default 220. */
16
+ speedMs?: 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 GraphLife: import("svelte").Component<GraphLifeProps, {}, "">;
26
+ type GraphLife = ReturnType<typeof GraphLife>;
27
+ export default GraphLife;