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,248 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** Health of one appended blip. */
|
|
3
|
+
export type PulseStatus = 'ok' | 'degraded' | 'down';
|
|
4
|
+
|
|
5
|
+
export interface GraphPulseProps {
|
|
6
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
7
|
+
title: string;
|
|
8
|
+
/** Append a live blip every interval. Default true. Reduced motion freezes a pre-filled window. */
|
|
9
|
+
animated?: boolean;
|
|
10
|
+
/** Window size in blips, newest at the right. Default 60. */
|
|
11
|
+
length?: number;
|
|
12
|
+
/** Milliseconds between appends. Default 1000. */
|
|
13
|
+
intervalMs?: number;
|
|
14
|
+
/** Chance an appended blip is ok; about 4% of the rest degrade, the rest go down. Default 0.94. */
|
|
15
|
+
okRate?: number;
|
|
16
|
+
/** Replace the built-in generator with your own stream of statuses. */
|
|
17
|
+
feed?: () => PulseStatus;
|
|
18
|
+
/** Seed for the reproducible generator. Default 11. */
|
|
19
|
+
seedNum?: number;
|
|
20
|
+
/** Label at the left edge of the caption row. Default 'now'. */
|
|
21
|
+
from?: string;
|
|
22
|
+
class?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Deterministic mulberry32 PRNG so a seed replays the same window. */
|
|
26
|
+
function mulberry32(seed: number): () => number {
|
|
27
|
+
let a = seed >>> 0;
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
31
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
32
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
33
|
+
|
|
34
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const MARK: Record<PulseStatus, string> = { ok: '█', degraded: '▒', down: '·' };
|
|
39
|
+
const TONE: Record<PulseStatus, string> = { ok: 'up', degraded: 'slow', down: 'down' };
|
|
40
|
+
|
|
41
|
+
interface Seg {
|
|
42
|
+
text: string;
|
|
43
|
+
cls: string;
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<script lang="ts">
|
|
48
|
+
import Graph from '../frame/Graph.svelte';
|
|
49
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
50
|
+
import { formatAgo, graphNow } from '../frame/clock';
|
|
51
|
+
|
|
52
|
+
let {
|
|
53
|
+
title,
|
|
54
|
+
animated = true,
|
|
55
|
+
length = 60,
|
|
56
|
+
intervalMs = 1000,
|
|
57
|
+
okRate = 0.94,
|
|
58
|
+
feed,
|
|
59
|
+
seedNum = 11,
|
|
60
|
+
from = 'now',
|
|
61
|
+
class: className = ''
|
|
62
|
+
}: GraphPulseProps = $props();
|
|
63
|
+
|
|
64
|
+
// svelte-ignore state_referenced_locally
|
|
65
|
+
const rand = mulberry32(seedNum);
|
|
66
|
+
|
|
67
|
+
function draw(): PulseStatus {
|
|
68
|
+
if (feed) {
|
|
69
|
+
return feed();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const r = rand();
|
|
73
|
+
|
|
74
|
+
if (r < okRate) {
|
|
75
|
+
return 'ok';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return r < okRate + 0.04 ? 'degraded' : 'down';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function prefill(): PulseStatus[] {
|
|
82
|
+
const out: PulseStatus[] = [];
|
|
83
|
+
const size = Math.max(1, length);
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < size; i++) {
|
|
86
|
+
out.push(draw());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// svelte-ignore state_referenced_locally
|
|
93
|
+
let blips = $state<PulseStatus[]>(prefill());
|
|
94
|
+
let lastCheckAt = $state(Date.now());
|
|
95
|
+
|
|
96
|
+
const time = graphNow();
|
|
97
|
+
|
|
98
|
+
const moving = $derived(
|
|
99
|
+
animated &&
|
|
100
|
+
(typeof window === 'undefined' ||
|
|
101
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
$effect(() => {
|
|
105
|
+
if (!moving) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const timer = window.setInterval(() => {
|
|
110
|
+
blips = [...blips.slice(1), draw()];
|
|
111
|
+
lastCheckAt = Date.now();
|
|
112
|
+
}, intervalMs);
|
|
113
|
+
|
|
114
|
+
return () => window.clearInterval(timer);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const segments = $derived.by((): Seg[] => {
|
|
118
|
+
const segs: Seg[] = [];
|
|
119
|
+
|
|
120
|
+
for (const blip of blips) {
|
|
121
|
+
const cls = TONE[blip];
|
|
122
|
+
const last = segs[segs.length - 1];
|
|
123
|
+
|
|
124
|
+
if (last && last.cls === cls) {
|
|
125
|
+
last.text += MARK[blip];
|
|
126
|
+
} else {
|
|
127
|
+
segs.push({ text: MARK[blip], cls });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return segs;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const percent = $derived.by(() => {
|
|
135
|
+
if (blips.length === 0) {
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const ok = blips.filter((blip) => blip === 'ok').length;
|
|
140
|
+
|
|
141
|
+
return Math.round((ok / blips.length) * 100);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const ago = $derived(
|
|
145
|
+
$time == null ? '0s ago' : formatAgo(Math.max(0, $time - lastCheckAt))
|
|
146
|
+
);
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<Graph {title} class={className}>
|
|
150
|
+
<GraphBody>
|
|
151
|
+
<div class="pulse">
|
|
152
|
+
<div class="viewport">
|
|
153
|
+
<pre class="art" aria-hidden="true"><code>{#each segments as seg, i (i)}{#if seg.cls}<span class={seg.cls}>{seg.text}</span>{:else}{seg.text}{/if}{/each}</code></pre>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="meta">
|
|
156
|
+
<p class="legend">
|
|
157
|
+
<span><span class="lg up" aria-hidden="true">█</span> up</span>
|
|
158
|
+
<span><span class="lg slow" aria-hidden="true">▒</span> slow</span>
|
|
159
|
+
<span><span class="lg down" aria-hidden="true">·</span> down</span>
|
|
160
|
+
</p>
|
|
161
|
+
<p class="pct">{percent}%</p>
|
|
162
|
+
</div>
|
|
163
|
+
<p class="caption"><span>{from}</span><span>last check {ago}</span></p>
|
|
164
|
+
<span class="sr-only">{percent} percent uptime over {blips.length} checks</span>
|
|
165
|
+
</div>
|
|
166
|
+
</GraphBody>
|
|
167
|
+
</Graph>
|
|
168
|
+
|
|
169
|
+
<style>
|
|
170
|
+
.pulse {
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-direction: column;
|
|
173
|
+
gap: 1rem;
|
|
174
|
+
min-width: 0;
|
|
175
|
+
font-size: 0.875rem;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.viewport {
|
|
179
|
+
overflow-x: auto;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.art {
|
|
183
|
+
margin: 0;
|
|
184
|
+
font-size: 0.85rem;
|
|
185
|
+
line-height: 1.4;
|
|
186
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
187
|
+
white-space: pre;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.up {
|
|
191
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.slow {
|
|
195
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.down {
|
|
199
|
+
color: var(--graph-faint, oklch(0.3 0 0));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.meta {
|
|
203
|
+
display: flex;
|
|
204
|
+
flex-wrap: wrap;
|
|
205
|
+
align-items: baseline;
|
|
206
|
+
justify-content: space-between;
|
|
207
|
+
gap: 0.75rem;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.legend {
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-wrap: wrap;
|
|
213
|
+
margin: 0;
|
|
214
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
215
|
+
gap: 0.25rem 1rem;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.lg {
|
|
219
|
+
user-select: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.pct {
|
|
223
|
+
margin: 0 0 0 auto;
|
|
224
|
+
text-align: right;
|
|
225
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.caption {
|
|
229
|
+
display: flex;
|
|
230
|
+
justify-content: space-between;
|
|
231
|
+
gap: 1rem;
|
|
232
|
+
margin: 0;
|
|
233
|
+
font-size: 0.8rem;
|
|
234
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.sr-only {
|
|
238
|
+
position: absolute;
|
|
239
|
+
width: 1px;
|
|
240
|
+
height: 1px;
|
|
241
|
+
padding: 0;
|
|
242
|
+
margin: -1px;
|
|
243
|
+
overflow: hidden;
|
|
244
|
+
clip-path: inset(50%);
|
|
245
|
+
white-space: nowrap;
|
|
246
|
+
border-width: 0;
|
|
247
|
+
}
|
|
248
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Health of one appended blip. */
|
|
2
|
+
export type PulseStatus = 'ok' | 'degraded' | 'down';
|
|
3
|
+
export interface GraphPulseProps {
|
|
4
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Append a live blip every interval. Default true. Reduced motion freezes a pre-filled window. */
|
|
7
|
+
animated?: boolean;
|
|
8
|
+
/** Window size in blips, newest at the right. Default 60. */
|
|
9
|
+
length?: number;
|
|
10
|
+
/** Milliseconds between appends. Default 1000. */
|
|
11
|
+
intervalMs?: number;
|
|
12
|
+
/** Chance an appended blip is ok; about 4% of the rest degrade, the rest go down. Default 0.94. */
|
|
13
|
+
okRate?: number;
|
|
14
|
+
/** Replace the built-in generator with your own stream of statuses. */
|
|
15
|
+
feed?: () => PulseStatus;
|
|
16
|
+
/** Seed for the reproducible generator. Default 11. */
|
|
17
|
+
seedNum?: number;
|
|
18
|
+
/** Label at the left edge of the caption row. Default 'now'. */
|
|
19
|
+
from?: string;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
declare const GraphPulse: import("svelte").Component<GraphPulseProps, {}, "">;
|
|
23
|
+
type GraphPulse = ReturnType<typeof GraphPulse>;
|
|
24
|
+
export default GraphPulse;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface GraphRainProps {
|
|
3
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Animate the rain. Default true. Reduced motion freezes a single frame. */
|
|
6
|
+
animated?: boolean;
|
|
7
|
+
/** Width in character columns. Default 56. */
|
|
8
|
+
cols?: number;
|
|
9
|
+
/** Height in rows. Default 16. */
|
|
10
|
+
rows?: number;
|
|
11
|
+
/** Milliseconds per tick. Default 110. */
|
|
12
|
+
speedMs?: number;
|
|
13
|
+
/** Trail length behind each head. Default 8. */
|
|
14
|
+
trail?: number;
|
|
15
|
+
/** Seed for the reproducible rain. Default 5. */
|
|
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 rain. */
|
|
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
|
+
/** Stream characters: ASCII lowercase + digits mixed with shade glyphs. */
|
|
44
|
+
const GLYPHS = 'abcdefghijklmnopqrstuvwxyz0123456789░▒▓░▒';
|
|
45
|
+
|
|
46
|
+
function pickGlyph(rand: () => number): string {
|
|
47
|
+
return GLYPHS[Math.floor(rand() * GLYPHS.length)] ?? ' ';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function newStream(rand: () => number, trail: number): string[] {
|
|
51
|
+
const stream: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < trail; i++) {
|
|
54
|
+
stream.push(pickGlyph(rand));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return stream;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface Drop {
|
|
61
|
+
/** Head row; may sit above the frame (negative) while the stream enters. */
|
|
62
|
+
y: number;
|
|
63
|
+
/** Ticks between head advances, 1–3. */
|
|
64
|
+
advanceEvery: number;
|
|
65
|
+
step: number;
|
|
66
|
+
stream: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface Seg {
|
|
70
|
+
text: string;
|
|
71
|
+
cls?: string;
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<script lang="ts">
|
|
76
|
+
import Graph from '../frame/Graph.svelte';
|
|
77
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
78
|
+
|
|
79
|
+
let {
|
|
80
|
+
title,
|
|
81
|
+
animated = true,
|
|
82
|
+
cols = 56,
|
|
83
|
+
rows = 16,
|
|
84
|
+
speedMs = 110,
|
|
85
|
+
trail = 8,
|
|
86
|
+
seedNum = 5,
|
|
87
|
+
label = 'glyph rain',
|
|
88
|
+
class: className = ''
|
|
89
|
+
}: GraphRainProps = $props();
|
|
90
|
+
|
|
91
|
+
// svelte-ignore state_referenced_locally
|
|
92
|
+
const rand = mulberry32(seedNum);
|
|
93
|
+
// svelte-ignore state_referenced_locally
|
|
94
|
+
const width = Math.max(1, cols);
|
|
95
|
+
// svelte-ignore state_referenced_locally
|
|
96
|
+
const height = Math.max(1, rows);
|
|
97
|
+
|
|
98
|
+
function span(): number {
|
|
99
|
+
return Math.max(1, trail);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function spawn(): Drop {
|
|
103
|
+
return {
|
|
104
|
+
y: -Math.floor(rand() * height * 2),
|
|
105
|
+
advanceEvery: 1 + Math.floor(rand() * 3),
|
|
106
|
+
step: 0,
|
|
107
|
+
stream: newStream(rand, span())
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function initialDrops(): Drop[] {
|
|
112
|
+
const out: Drop[] = [];
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < width; i++) {
|
|
115
|
+
out.push(spawn());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// svelte-ignore state_referenced_locally
|
|
122
|
+
let drops = $state<Drop[]>(initialDrops());
|
|
123
|
+
|
|
124
|
+
/** Advance every column; heads step on their own cadence and respawn once clear. */
|
|
125
|
+
function tickRain() {
|
|
126
|
+
const trailLen = span();
|
|
127
|
+
|
|
128
|
+
for (const d of drops) {
|
|
129
|
+
d.step += 1;
|
|
130
|
+
|
|
131
|
+
if (d.step >= d.advanceEvery) {
|
|
132
|
+
d.step = 0;
|
|
133
|
+
d.y += 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (d.y - trailLen >= height) {
|
|
137
|
+
d.y = -Math.floor(rand() * 3);
|
|
138
|
+
d.advanceEvery = 1 + Math.floor(rand() * 3);
|
|
139
|
+
d.step = 0;
|
|
140
|
+
d.stream = newStream(rand, trailLen);
|
|
141
|
+
} else if (rand() < 0.08) {
|
|
142
|
+
d.stream[Math.floor(rand() * d.stream.length)] = pickGlyph(rand);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// svelte-ignore state_referenced_locally
|
|
148
|
+
const preTicks = animated && !prefersReduced() ? height * 2 : height * 4;
|
|
149
|
+
|
|
150
|
+
// Streams stagger in when animated; a settled mid-fall frame when frozen.
|
|
151
|
+
for (let i = 0; i < preTicks; i++) {
|
|
152
|
+
tickRain();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const moving = $derived(
|
|
156
|
+
animated &&
|
|
157
|
+
(typeof window === 'undefined' ||
|
|
158
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
$effect(() => {
|
|
162
|
+
if (!moving) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const timer = window.setInterval(() => {
|
|
167
|
+
tickRain();
|
|
168
|
+
}, speedMs);
|
|
169
|
+
|
|
170
|
+
return () => window.clearInterval(timer);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const view = $derived.by((): Seg[][] => {
|
|
174
|
+
const trailLen = span();
|
|
175
|
+
const bright = Math.max(1, Math.round(trailLen / 3));
|
|
176
|
+
const dim = Math.max(bright + 1, Math.round((trailLen * 2) / 3));
|
|
177
|
+
const out: Seg[][] = [];
|
|
178
|
+
|
|
179
|
+
for (let r = 0; r < height; r++) {
|
|
180
|
+
const segs: Seg[] = [];
|
|
181
|
+
let buf = '';
|
|
182
|
+
let cls: string | undefined;
|
|
183
|
+
|
|
184
|
+
const flush = () => {
|
|
185
|
+
if (buf.length > 0) {
|
|
186
|
+
segs.push({ text: buf, cls });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
buf = '';
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
for (let c = 0; c < width; c++) {
|
|
193
|
+
const d = drops[c];
|
|
194
|
+
const dist = d ? d.y - r : -1;
|
|
195
|
+
let ch = ' ';
|
|
196
|
+
let next: string | undefined;
|
|
197
|
+
|
|
198
|
+
if (d && dist === 0) {
|
|
199
|
+
ch = '█';
|
|
200
|
+
next = 'hot';
|
|
201
|
+
} else if (d && dist >= 1 && dist <= trailLen) {
|
|
202
|
+
ch = d.stream[dist - 1] ?? ' ';
|
|
203
|
+
|
|
204
|
+
if (dist > dim) {
|
|
205
|
+
next = 'faint';
|
|
206
|
+
} else if (dist > bright) {
|
|
207
|
+
next = 'mid';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (next === cls) {
|
|
212
|
+
buf += ch;
|
|
213
|
+
} else {
|
|
214
|
+
flush();
|
|
215
|
+
cls = next;
|
|
216
|
+
buf = ch;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
flush();
|
|
221
|
+
out.push(segs);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return out;
|
|
225
|
+
});
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<Graph {title} class={className}>
|
|
229
|
+
<GraphBody>
|
|
230
|
+
<div class="rain">
|
|
231
|
+
<div class="viewport">
|
|
232
|
+
<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>
|
|
233
|
+
</div>
|
|
234
|
+
{#if label}
|
|
235
|
+
<p class="caption">{label}</p>
|
|
236
|
+
{/if}
|
|
237
|
+
</div>
|
|
238
|
+
</GraphBody>
|
|
239
|
+
</Graph>
|
|
240
|
+
|
|
241
|
+
<style>
|
|
242
|
+
.rain {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
gap: 1rem;
|
|
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.15;
|
|
258
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
259
|
+
white-space: pre;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.hot {
|
|
263
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.mid {
|
|
268
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.faint {
|
|
272
|
+
color: var(--graph-faint, oklch(0.3 0 0));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.caption {
|
|
276
|
+
margin: 0;
|
|
277
|
+
font-size: 0.8rem;
|
|
278
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
279
|
+
}
|
|
280
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GraphRainProps {
|
|
2
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
3
|
+
title: string;
|
|
4
|
+
/** Animate the rain. Default true. Reduced motion freezes a single frame. */
|
|
5
|
+
animated?: boolean;
|
|
6
|
+
/** Width in character columns. Default 56. */
|
|
7
|
+
cols?: number;
|
|
8
|
+
/** Height in rows. Default 16. */
|
|
9
|
+
rows?: number;
|
|
10
|
+
/** Milliseconds per tick. Default 110. */
|
|
11
|
+
speedMs?: number;
|
|
12
|
+
/** Trail length behind each head. Default 8. */
|
|
13
|
+
trail?: number;
|
|
14
|
+
/** Seed for the reproducible rain. Default 5. */
|
|
15
|
+
seedNum?: number;
|
|
16
|
+
/** Short caption under the art; the art itself is decorative. */
|
|
17
|
+
label?: string;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
declare const GraphRain: import("svelte").Component<GraphRainProps, {}, "">;
|
|
21
|
+
type GraphRain = ReturnType<typeof GraphRain>;
|
|
22
|
+
export default GraphRain;
|