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.
- package/LICENSE.txt +21 -0
- package/README.md +154 -0
- package/dist/animated/GraphAgni.svelte +430 -0
- package/dist/animated/GraphAgni.svelte.d.ts +22 -0
- package/dist/animated/GraphAum.svelte +294 -0
- package/dist/animated/GraphAum.svelte.d.ts +20 -0
- package/dist/animated/GraphBoot.svelte +325 -0
- package/dist/animated/GraphBoot.svelte.d.ts +28 -0
- package/dist/animated/GraphCron.svelte +476 -0
- package/dist/animated/GraphCron.svelte.d.ts +42 -0
- package/dist/animated/GraphDamru.svelte +341 -0
- package/dist/animated/GraphDamru.svelte.d.ts +18 -0
- package/dist/animated/GraphDeps.svelte +245 -0
- package/dist/animated/GraphDeps.svelte.d.ts +29 -0
- package/dist/animated/GraphDiya.svelte +330 -0
- package/dist/animated/GraphDiya.svelte.d.ts +22 -0
- package/dist/animated/GraphFire.svelte +246 -0
- package/dist/animated/GraphFire.svelte.d.ts +22 -0
- package/dist/animated/GraphFlame.svelte +258 -0
- package/dist/animated/GraphFlame.svelte.d.ts +14 -0
- package/dist/animated/GraphFlowPlayer.svelte +346 -0
- package/dist/animated/GraphFlowPlayer.svelte.d.ts +27 -0
- package/dist/animated/GraphGanga.svelte +305 -0
- package/dist/animated/GraphGanga.svelte.d.ts +20 -0
- package/dist/animated/GraphHash.svelte +251 -0
- package/dist/animated/GraphHash.svelte.d.ts +20 -0
- package/dist/animated/GraphJapa.svelte +253 -0
- package/dist/animated/GraphJapa.svelte.d.ts +20 -0
- package/dist/animated/GraphLife.svelte +312 -0
- package/dist/animated/GraphLife.svelte.d.ts +27 -0
- package/dist/animated/GraphMandala.svelte +262 -0
- package/dist/animated/GraphMandala.svelte.d.ts +24 -0
- package/dist/animated/GraphMandel.svelte +290 -0
- package/dist/animated/GraphMandel.svelte.d.ts +27 -0
- package/dist/animated/GraphPulse.svelte +248 -0
- package/dist/animated/GraphPulse.svelte.d.ts +24 -0
- package/dist/animated/GraphRain.svelte +280 -0
- package/dist/animated/GraphRain.svelte.d.ts +22 -0
- package/dist/animated/GraphScatter.svelte +395 -0
- package/dist/animated/GraphScatter.svelte.d.ts +31 -0
- package/dist/animated/GraphScope.svelte +197 -0
- package/dist/animated/GraphScope.svelte.d.ts +22 -0
- package/dist/animated/GraphSequence.svelte +247 -0
- package/dist/animated/GraphSequence.svelte.d.ts +18 -0
- package/dist/animated/GraphSpinners.svelte +109 -0
- package/dist/animated/GraphSpinners.svelte.d.ts +22 -0
- package/dist/animated/GraphState.svelte +270 -0
- package/dist/animated/GraphState.svelte.d.ts +14 -0
- package/dist/animated/GraphStream.svelte +223 -0
- package/dist/animated/GraphStream.svelte.d.ts +24 -0
- package/dist/animated/GraphSurya.svelte +268 -0
- package/dist/animated/GraphSurya.svelte.d.ts +22 -0
- package/dist/animated/GraphTerminal.svelte +238 -0
- package/dist/animated/GraphTerminal.svelte.d.ts +28 -0
- package/dist/animated/GraphTicker.svelte +163 -0
- package/dist/animated/GraphTicker.svelte.d.ts +21 -0
- package/dist/animated/GraphTypewriter.svelte +130 -0
- package/dist/animated/GraphTypewriter.svelte.d.ts +22 -0
- package/dist/animated/GraphWorkflow.svelte +570 -0
- package/dist/animated/GraphWorkflow.svelte.d.ts +29 -0
- package/dist/assets/favicon.svg +21 -0
- package/dist/diagram/AmplifierDiagram.svelte +206 -0
- package/dist/diagram/AmplifierDiagram.svelte.d.ts +12 -0
- package/dist/diagram/AsciiDiagram.svelte +212 -0
- package/dist/diagram/AsciiDiagram.svelte.d.ts +24 -0
- package/dist/diagram/DiagramEditor.svelte +414 -0
- package/dist/diagram/DiagramEditor.svelte.d.ts +3 -0
- package/dist/diagram/MetricsTableDiagram.svelte +125 -0
- package/dist/diagram/MetricsTableDiagram.svelte.d.ts +18 -0
- package/dist/diagram/NestedRadiiDiagram.svelte +218 -0
- package/dist/diagram/NestedRadiiDiagram.svelte.d.ts +12 -0
- package/dist/diagram/PromptLoopDiagram.svelte +331 -0
- package/dist/diagram/PromptLoopDiagram.svelte.d.ts +12 -0
- package/dist/diagram/ascii.d.ts +35 -0
- package/dist/diagram/ascii.js +83 -0
- package/dist/diagram/parser.d.ts +16 -0
- package/dist/diagram/parser.js +71 -0
- package/dist/frame/Graph.svelte +141 -0
- package/dist/frame/Graph.svelte.d.ts +14 -0
- package/dist/frame/GraphArrow.svelte +49 -0
- package/dist/frame/GraphArrow.svelte.d.ts +9 -0
- package/dist/frame/GraphBody.svelte +39 -0
- package/dist/frame/GraphBody.svelte.d.ts +10 -0
- package/dist/frame/GraphRule.svelte +21 -0
- package/dist/frame/GraphRule.svelte.d.ts +6 -0
- package/dist/frame/GraphTick.svelte +23 -0
- package/dist/frame/GraphTick.svelte.d.ts +9 -0
- package/dist/frame/GraphTrack.svelte +29 -0
- package/dist/frame/GraphTrack.svelte.d.ts +14 -0
- package/dist/frame/clock.d.ts +10 -0
- package/dist/frame/clock.js +62 -0
- package/dist/frame/glyphs.d.ts +22 -0
- package/dist/frame/glyphs.js +53 -0
- package/dist/frame/motion.css +27 -0
- package/dist/frame/motion.d.ts +24 -0
- package/dist/frame/motion.js +47 -0
- package/dist/frame/tone.d.ts +11 -0
- package/dist/frame/tone.js +64 -0
- package/dist/graphs/GraphActivity.svelte +387 -0
- package/dist/graphs/GraphActivity.svelte.d.ts +22 -0
- package/dist/graphs/GraphBars.svelte +194 -0
- package/dist/graphs/GraphBars.svelte.d.ts +20 -0
- package/dist/graphs/GraphBullet.svelte +208 -0
- package/dist/graphs/GraphBullet.svelte.d.ts +22 -0
- package/dist/graphs/GraphCalendar.svelte +203 -0
- package/dist/graphs/GraphCalendar.svelte.d.ts +20 -0
- package/dist/graphs/GraphCells.svelte +181 -0
- package/dist/graphs/GraphCells.svelte.d.ts +18 -0
- package/dist/graphs/GraphCompare.svelte +192 -0
- package/dist/graphs/GraphCompare.svelte.d.ts +18 -0
- package/dist/graphs/GraphCountdown.svelte +96 -0
- package/dist/graphs/GraphCountdown.svelte.d.ts +14 -0
- package/dist/graphs/GraphDiff.svelte +181 -0
- package/dist/graphs/GraphDiff.svelte.d.ts +18 -0
- package/dist/graphs/GraphFlow.svelte +137 -0
- package/dist/graphs/GraphFlow.svelte.d.ts +21 -0
- package/dist/graphs/GraphFunnel.svelte +190 -0
- package/dist/graphs/GraphFunnel.svelte.d.ts +21 -0
- package/dist/graphs/GraphGantt.svelte +258 -0
- package/dist/graphs/GraphGantt.svelte.d.ts +25 -0
- package/dist/graphs/GraphHeatmap.svelte +287 -0
- package/dist/graphs/GraphHeatmap.svelte.d.ts +22 -0
- package/dist/graphs/GraphInvoice.svelte +294 -0
- package/dist/graphs/GraphInvoice.svelte.d.ts +34 -0
- package/dist/graphs/GraphKpi.svelte +166 -0
- package/dist/graphs/GraphKpi.svelte.d.ts +17 -0
- package/dist/graphs/GraphLatency.svelte +340 -0
- package/dist/graphs/GraphLatency.svelte.d.ts +28 -0
- package/dist/graphs/GraphMeter.svelte +121 -0
- package/dist/graphs/GraphMeter.svelte.d.ts +16 -0
- package/dist/graphs/GraphPlot.svelte +226 -0
- package/dist/graphs/GraphPlot.svelte.d.ts +18 -0
- package/dist/graphs/GraphQuota.svelte +303 -0
- package/dist/graphs/GraphQuota.svelte.d.ts +27 -0
- package/dist/graphs/GraphRank.svelte +141 -0
- package/dist/graphs/GraphRank.svelte.d.ts +21 -0
- package/dist/graphs/GraphSlo.svelte +325 -0
- package/dist/graphs/GraphSlo.svelte.d.ts +31 -0
- package/dist/graphs/GraphSlope.svelte +197 -0
- package/dist/graphs/GraphSlope.svelte.d.ts +19 -0
- package/dist/graphs/GraphSpark.svelte +125 -0
- package/dist/graphs/GraphSpark.svelte.d.ts +15 -0
- package/dist/graphs/GraphSpec.svelte +69 -0
- package/dist/graphs/GraphSpec.svelte.d.ts +14 -0
- package/dist/graphs/GraphStack.svelte +266 -0
- package/dist/graphs/GraphStack.svelte.d.ts +25 -0
- package/dist/graphs/GraphStat.svelte +103 -0
- package/dist/graphs/GraphStat.svelte.d.ts +16 -0
- package/dist/graphs/GraphTable.svelte +133 -0
- package/dist/graphs/GraphTable.svelte.d.ts +15 -0
- package/dist/graphs/GraphTimeline.svelte +164 -0
- package/dist/graphs/GraphTimeline.svelte.d.ts +18 -0
- package/dist/graphs/GraphTimer.svelte +117 -0
- package/dist/graphs/GraphTimer.svelte.d.ts +15 -0
- package/dist/graphs/GraphTree.svelte +147 -0
- package/dist/graphs/GraphTree.svelte.d.ts +16 -0
- package/dist/graphs/GraphUptime.svelte +210 -0
- package/dist/graphs/GraphUptime.svelte.d.ts +18 -0
- package/dist/graphs/GraphWaffle.svelte +131 -0
- package/dist/graphs/GraphWaffle.svelte.d.ts +17 -0
- package/dist/graphs/GraphWaterfall.svelte +258 -0
- package/dist/graphs/GraphWaterfall.svelte.d.ts +22 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +87 -0
- package/dist/themes.css +54 -0
- 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;
|