@cleocode/animations 2026.5.29 → 2026.5.34
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/CHANGELOG.md +77 -0
- package/README.md +290 -0
- package/dist/src/animate-context.d.ts +115 -0
- package/dist/src/animate-context.d.ts.map +1 -0
- package/dist/src/animate-context.js +85 -0
- package/dist/src/animate-context.js.map +1 -0
- package/dist/src/braille.d.ts +32 -0
- package/dist/src/braille.d.ts.map +1 -1
- package/dist/src/braille.js +52 -0
- package/dist/src/braille.js.map +1 -1
- package/dist/src/index.d.ts +18 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +23 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/progress.d.ts +50 -0
- package/dist/src/progress.d.ts.map +1 -0
- package/dist/src/progress.js +121 -0
- package/dist/src/progress.js.map +1 -0
- package/dist/src/spark.d.ts +47 -0
- package/dist/src/spark.d.ts.map +1 -0
- package/dist/src/spark.js +115 -0
- package/dist/src/spark.js.map +1 -0
- package/dist/src/spinner-handle.d.ts +97 -0
- package/dist/src/spinner-handle.d.ts.map +1 -0
- package/dist/src/spinner-handle.js +177 -0
- package/dist/src/spinner-handle.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +20 -2
- package/scripts/demo.cjs +208 -51
- package/scripts/demo.html +807 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>@cleocode/animations · Workshop Vitrine</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* ──────────────────────────────────────────────────────────────────────
|
|
9
|
+
Cleo Workshop palette — dark default, light alt theme.
|
|
10
|
+
Colours map onto canon systems so the page reads as a workshop tour:
|
|
11
|
+
NEXUS slate, LOOM amber, BRAIN teal, CANT magenta, CONDUIT moss,
|
|
12
|
+
AWAKEN ember.
|
|
13
|
+
────────────────────────────────────────────────────────────────────── */
|
|
14
|
+
:root[data-theme="dark"] {
|
|
15
|
+
--bg: #07090d;
|
|
16
|
+
--surface: #11161e;
|
|
17
|
+
--surface-2: #161c26;
|
|
18
|
+
--border: #1f2733;
|
|
19
|
+
--border-hi: #2a3548;
|
|
20
|
+
--text: #b8c2cf;
|
|
21
|
+
--text-hi: #e5eaf1;
|
|
22
|
+
--text-dim: #5a6573;
|
|
23
|
+
--code-bg: #0d1117;
|
|
24
|
+
--kw: #d3869b;
|
|
25
|
+
--str: #a9b665;
|
|
26
|
+
--cm: #5a6573;
|
|
27
|
+
--num: #d8a657;
|
|
28
|
+
--nexus: #4f93b8;
|
|
29
|
+
--loom: #d8a657;
|
|
30
|
+
--brain: #7daea3;
|
|
31
|
+
--cant: #d3869b;
|
|
32
|
+
--conduit: #a9b665;
|
|
33
|
+
--awaken: #e78a4e;
|
|
34
|
+
}
|
|
35
|
+
:root[data-theme="light"] {
|
|
36
|
+
--bg: #fafaf7;
|
|
37
|
+
--surface: #ffffff;
|
|
38
|
+
--surface-2: #f3f3ee;
|
|
39
|
+
--border: #e6e4dd;
|
|
40
|
+
--border-hi: #cdcbc2;
|
|
41
|
+
--text: #2d2a26;
|
|
42
|
+
--text-hi: #14110d;
|
|
43
|
+
--text-dim: #6a6660;
|
|
44
|
+
--code-bg: #f7f6f1;
|
|
45
|
+
--kw: #a3464d;
|
|
46
|
+
--str: #4f6b1b;
|
|
47
|
+
--cm: #8a857c;
|
|
48
|
+
--num: #8b6914;
|
|
49
|
+
--nexus: #2c6d8f;
|
|
50
|
+
--loom: #8b6914;
|
|
51
|
+
--brain: #3d6e64;
|
|
52
|
+
--cant: #a3464d;
|
|
53
|
+
--conduit: #4f6b1b;
|
|
54
|
+
--awaken: #b85e2c;
|
|
55
|
+
}
|
|
56
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
57
|
+
html, body {
|
|
58
|
+
background: var(--bg);
|
|
59
|
+
color: var(--text);
|
|
60
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
61
|
+
-webkit-font-smoothing: antialiased;
|
|
62
|
+
transition: background .25s, color .25s;
|
|
63
|
+
}
|
|
64
|
+
body {
|
|
65
|
+
min-height: 100vh;
|
|
66
|
+
background:
|
|
67
|
+
radial-gradient(circle at 12% 0%, rgba(79,147,184,0.10), transparent 55%),
|
|
68
|
+
radial-gradient(circle at 88% 100%, rgba(216,166,87,0.08), transparent 50%),
|
|
69
|
+
var(--bg);
|
|
70
|
+
background-attachment: fixed;
|
|
71
|
+
}
|
|
72
|
+
.mono {
|
|
73
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', 'Menlo', ui-monospace, monospace;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ── Top controls ────────────────────────────────────────────────────── */
|
|
77
|
+
.theme-toggle {
|
|
78
|
+
position: fixed; top: 16px; right: 24px; z-index: 100;
|
|
79
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
|
|
80
|
+
padding: 6px 12px; cursor: pointer; font-size: 12px; font-weight: 500;
|
|
81
|
+
color: var(--text-dim); display: flex; align-items: center; gap: 6px;
|
|
82
|
+
transition: border-color .2s, color .2s;
|
|
83
|
+
}
|
|
84
|
+
.theme-toggle:hover { border-color: var(--text-dim); color: var(--text); }
|
|
85
|
+
|
|
86
|
+
/* ── Layout shell ────────────────────────────────────────────────────── */
|
|
87
|
+
main {
|
|
88
|
+
max-width: 920px;
|
|
89
|
+
margin: 0 auto;
|
|
90
|
+
padding: 64px 28px 96px;
|
|
91
|
+
}
|
|
92
|
+
header.crest {
|
|
93
|
+
text-align: center;
|
|
94
|
+
border-bottom: 1px solid var(--border);
|
|
95
|
+
padding-bottom: 28px;
|
|
96
|
+
margin-bottom: 36px;
|
|
97
|
+
}
|
|
98
|
+
.crest-tag {
|
|
99
|
+
color: var(--text-dim);
|
|
100
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
101
|
+
font-size: 11px;
|
|
102
|
+
letter-spacing: 0.22em;
|
|
103
|
+
text-transform: uppercase;
|
|
104
|
+
margin-bottom: 10px;
|
|
105
|
+
}
|
|
106
|
+
.crest-tag .dot {
|
|
107
|
+
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
|
108
|
+
background: var(--awaken); margin-right: 8px;
|
|
109
|
+
box-shadow: 0 0 10px var(--awaken);
|
|
110
|
+
animation: crestPulse 2.4s ease-in-out infinite;
|
|
111
|
+
vertical-align: 1px;
|
|
112
|
+
}
|
|
113
|
+
@keyframes crestPulse { 0%,100%{opacity:.4} 50%{opacity:1} }
|
|
114
|
+
h1 {
|
|
115
|
+
font-size: 30px; font-weight: 600; letter-spacing: -0.02em;
|
|
116
|
+
color: var(--text-hi);
|
|
117
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
118
|
+
}
|
|
119
|
+
h1 .at { color: var(--text-dim); }
|
|
120
|
+
h1 .sigil { color: var(--loom); }
|
|
121
|
+
.crest-sub {
|
|
122
|
+
margin-top: 8px;
|
|
123
|
+
color: var(--text-dim);
|
|
124
|
+
font-size: 14px;
|
|
125
|
+
}
|
|
126
|
+
.install {
|
|
127
|
+
text-align: center;
|
|
128
|
+
margin: 22px 0 4px;
|
|
129
|
+
}
|
|
130
|
+
.install code {
|
|
131
|
+
background: var(--surface);
|
|
132
|
+
border: 1px solid var(--border);
|
|
133
|
+
border-radius: 8px;
|
|
134
|
+
padding: 8px 16px;
|
|
135
|
+
color: var(--text);
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ── Table-of-contents nav ───────────────────────────────────────────── */
|
|
140
|
+
nav.toc {
|
|
141
|
+
background: var(--surface);
|
|
142
|
+
border: 1px solid var(--border);
|
|
143
|
+
border-radius: 8px;
|
|
144
|
+
padding: 14px 20px;
|
|
145
|
+
margin: 28px 0 40px;
|
|
146
|
+
display: flex; flex-wrap: wrap; gap: 6px 18px;
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
}
|
|
149
|
+
nav.toc a {
|
|
150
|
+
color: var(--text-dim); text-decoration: none;
|
|
151
|
+
padding: 2px 4px; border-radius: 3px;
|
|
152
|
+
transition: color .15s, background .15s;
|
|
153
|
+
}
|
|
154
|
+
nav.toc a:hover { color: var(--text); background: var(--surface-2); }
|
|
155
|
+
nav.toc a strong { color: var(--text); font-weight: 500; }
|
|
156
|
+
|
|
157
|
+
/* ── Sections ────────────────────────────────────────────────────────── */
|
|
158
|
+
section { margin-top: 56px; }
|
|
159
|
+
.section-label {
|
|
160
|
+
font-size: 11px;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
text-transform: uppercase;
|
|
163
|
+
letter-spacing: 0.18em;
|
|
164
|
+
color: var(--text-dim);
|
|
165
|
+
margin-bottom: 8px;
|
|
166
|
+
}
|
|
167
|
+
h2 {
|
|
168
|
+
font-size: 22px; font-weight: 500; letter-spacing: -0.01em;
|
|
169
|
+
color: var(--text-hi);
|
|
170
|
+
margin: 4px 0 6px;
|
|
171
|
+
}
|
|
172
|
+
.blurb {
|
|
173
|
+
color: var(--text-dim);
|
|
174
|
+
font-size: 14px;
|
|
175
|
+
line-height: 1.7;
|
|
176
|
+
margin: 6px 0 22px;
|
|
177
|
+
max-width: 740px;
|
|
178
|
+
}
|
|
179
|
+
.blurb code, .prose code, .ref-table code {
|
|
180
|
+
background: var(--code-bg);
|
|
181
|
+
border: 1px solid var(--border);
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
padding: 1px 6px;
|
|
184
|
+
font-size: 12px;
|
|
185
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* ── List rows (spinners, sparks) ────────────────────────────────────── */
|
|
189
|
+
.row-list {
|
|
190
|
+
display: grid;
|
|
191
|
+
grid-template-columns: 1fr 1fr;
|
|
192
|
+
gap: 0;
|
|
193
|
+
background: var(--surface);
|
|
194
|
+
border: 1px solid var(--border);
|
|
195
|
+
border-radius: 8px;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
}
|
|
198
|
+
@media (max-width: 600px) { .row-list { grid-template-columns: 1fr; } }
|
|
199
|
+
.row {
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 12px;
|
|
203
|
+
padding: 12px 18px;
|
|
204
|
+
border-bottom: 1px solid var(--border);
|
|
205
|
+
transition: background .15s;
|
|
206
|
+
}
|
|
207
|
+
.row:hover { background: var(--surface-2); }
|
|
208
|
+
.row .frame {
|
|
209
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
210
|
+
font-size: 17px;
|
|
211
|
+
width: 5.5rem;
|
|
212
|
+
text-align: center;
|
|
213
|
+
flex-shrink: 0;
|
|
214
|
+
color: var(--nexus);
|
|
215
|
+
white-space: nowrap;
|
|
216
|
+
}
|
|
217
|
+
.row.canon .frame { color: var(--loom); }
|
|
218
|
+
.row.spark .frame { color: var(--awaken); }
|
|
219
|
+
.row .name {
|
|
220
|
+
font-weight: 500;
|
|
221
|
+
font-size: 13px;
|
|
222
|
+
color: var(--text);
|
|
223
|
+
white-space: nowrap;
|
|
224
|
+
}
|
|
225
|
+
.row .meta {
|
|
226
|
+
font-size: 11px;
|
|
227
|
+
color: var(--text-dim);
|
|
228
|
+
margin-left: auto;
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
231
|
+
}
|
|
232
|
+
.row .alias {
|
|
233
|
+
font-size: 11px;
|
|
234
|
+
color: var(--brain);
|
|
235
|
+
margin-left: auto;
|
|
236
|
+
white-space: nowrap;
|
|
237
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
238
|
+
}
|
|
239
|
+
.row .alias em { color: var(--text-dim); font-style: normal; }
|
|
240
|
+
|
|
241
|
+
/* ── Progress strip ──────────────────────────────────────────────────── */
|
|
242
|
+
.progress-strip {
|
|
243
|
+
background: var(--surface);
|
|
244
|
+
border: 1px solid var(--border);
|
|
245
|
+
border-radius: 8px;
|
|
246
|
+
padding: 8px;
|
|
247
|
+
display: flex;
|
|
248
|
+
flex-direction: column;
|
|
249
|
+
gap: 0;
|
|
250
|
+
}
|
|
251
|
+
.progress-row {
|
|
252
|
+
display: grid;
|
|
253
|
+
grid-template-columns: 110px 1fr 56px;
|
|
254
|
+
gap: 14px;
|
|
255
|
+
align-items: center;
|
|
256
|
+
padding: 14px 14px;
|
|
257
|
+
border-bottom: 1px solid var(--border);
|
|
258
|
+
}
|
|
259
|
+
.progress-row:last-child { border-bottom: none; }
|
|
260
|
+
.progress-row .style {
|
|
261
|
+
font-weight: 500; font-size: 13px; color: var(--text);
|
|
262
|
+
}
|
|
263
|
+
.progress-row .style em {
|
|
264
|
+
display: block; font-style: normal; color: var(--text-dim); font-size: 11px;
|
|
265
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
266
|
+
}
|
|
267
|
+
.progress-row .bar {
|
|
268
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
269
|
+
font-size: 18px;
|
|
270
|
+
color: var(--nexus);
|
|
271
|
+
white-space: pre;
|
|
272
|
+
overflow: hidden;
|
|
273
|
+
letter-spacing: 0;
|
|
274
|
+
}
|
|
275
|
+
.progress-row.cascade .bar { color: var(--loom); }
|
|
276
|
+
.progress-row.refinery .bar { color: var(--brain); }
|
|
277
|
+
.progress-row .pct {
|
|
278
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
279
|
+
font-size: 12px;
|
|
280
|
+
color: var(--text-dim);
|
|
281
|
+
text-align: right;
|
|
282
|
+
}
|
|
283
|
+
@media (max-width: 600px) {
|
|
284
|
+
.progress-row { grid-template-columns: 90px 1fr; }
|
|
285
|
+
.progress-row .pct { display: none; }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* ── Code blocks (syntax-highlit) ────────────────────────────────────── */
|
|
289
|
+
pre.code {
|
|
290
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
291
|
+
font-size: 13px;
|
|
292
|
+
line-height: 1.6;
|
|
293
|
+
background: var(--code-bg);
|
|
294
|
+
border: 1px solid var(--border);
|
|
295
|
+
border-radius: 8px;
|
|
296
|
+
padding: 16px 18px;
|
|
297
|
+
overflow-x: auto;
|
|
298
|
+
color: var(--text);
|
|
299
|
+
margin: 10px 0;
|
|
300
|
+
}
|
|
301
|
+
pre.code .kw { color: var(--kw); }
|
|
302
|
+
pre.code .str { color: var(--str); }
|
|
303
|
+
pre.code .cm { color: var(--cm); font-style: italic; }
|
|
304
|
+
pre.code .num { color: var(--num); }
|
|
305
|
+
|
|
306
|
+
/* ── Reference tables ────────────────────────────────────────────────── */
|
|
307
|
+
table.ref {
|
|
308
|
+
width: 100%; border-collapse: collapse;
|
|
309
|
+
font-size: 13px;
|
|
310
|
+
background: var(--surface);
|
|
311
|
+
border: 1px solid var(--border);
|
|
312
|
+
border-radius: 8px;
|
|
313
|
+
overflow: hidden;
|
|
314
|
+
margin: 12px 0;
|
|
315
|
+
}
|
|
316
|
+
table.ref th {
|
|
317
|
+
text-align: left;
|
|
318
|
+
font-size: 11px;
|
|
319
|
+
text-transform: uppercase;
|
|
320
|
+
letter-spacing: 0.12em;
|
|
321
|
+
color: var(--text-dim);
|
|
322
|
+
padding: 10px 14px;
|
|
323
|
+
border-bottom: 1px solid var(--border);
|
|
324
|
+
background: var(--surface-2);
|
|
325
|
+
font-weight: 600;
|
|
326
|
+
}
|
|
327
|
+
table.ref td {
|
|
328
|
+
padding: 10px 14px;
|
|
329
|
+
border-bottom: 1px solid var(--border);
|
|
330
|
+
color: var(--text);
|
|
331
|
+
}
|
|
332
|
+
table.ref tr:last-child td { border-bottom: none; }
|
|
333
|
+
table.ref code { font-size: 12px; }
|
|
334
|
+
|
|
335
|
+
.sub-h {
|
|
336
|
+
font-size: 13px; font-weight: 600;
|
|
337
|
+
color: var(--text); margin: 22px 0 8px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* ── Footer ──────────────────────────────────────────────────────────── */
|
|
341
|
+
footer.foot {
|
|
342
|
+
margin-top: 80px; padding-top: 24px;
|
|
343
|
+
border-top: 1px solid var(--border);
|
|
344
|
+
color: var(--text-dim);
|
|
345
|
+
font-size: 12px; line-height: 1.7;
|
|
346
|
+
text-align: center;
|
|
347
|
+
}
|
|
348
|
+
footer.foot a { color: var(--brain); text-decoration: none; }
|
|
349
|
+
footer.foot a:hover { color: var(--text-hi); text-decoration: underline; }
|
|
350
|
+
footer .palette {
|
|
351
|
+
display: flex; gap: 14px; flex-wrap: wrap; justify-content: center;
|
|
352
|
+
margin-top: 14px; font-size: 11px;
|
|
353
|
+
font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
|
|
354
|
+
}
|
|
355
|
+
footer .palette span { display: inline-flex; align-items: center; gap: 6px; }
|
|
356
|
+
footer .palette i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
357
|
+
</style>
|
|
358
|
+
</head>
|
|
359
|
+
<body>
|
|
360
|
+
|
|
361
|
+
<button class="theme-toggle" id="themeToggle" aria-label="toggle theme">
|
|
362
|
+
<span id="themeIcon">☀</span><span id="themeLabel">Light</span>
|
|
363
|
+
</button>
|
|
364
|
+
|
|
365
|
+
<main>
|
|
366
|
+
|
|
367
|
+
<header class="crest">
|
|
368
|
+
<div class="crest-tag"><span class="dot"></span>The Workshop · Vitrine</div>
|
|
369
|
+
<h1><span class="at">@</span>cleocode<span class="sigil">/</span>animations</h1>
|
|
370
|
+
<div class="crest-sub">Unicode terminal animations woven on the LOOM — for the cleo CLI and CleoOS.</div>
|
|
371
|
+
<div class="install"><code class="mono">pnpm add @cleocode/animations</code></div>
|
|
372
|
+
</header>
|
|
373
|
+
|
|
374
|
+
<nav class="toc">
|
|
375
|
+
<a href="#spinners-generic"><strong>1.</strong> 18 generic braille spinners</a>
|
|
376
|
+
<a href="#spinners-canon"><strong>2.</strong> 9 canon spinner aliases</a>
|
|
377
|
+
<a href="#progress"><strong>3.</strong> 3 progress styles</a>
|
|
378
|
+
<a href="#sparks"><strong>4.</strong> 4 sparks</a>
|
|
379
|
+
<a href="#animate-context"><strong>5.</strong> AnimateContext</a>
|
|
380
|
+
<a href="#guide"><strong>6.</strong> Usage guide</a>
|
|
381
|
+
<a href="#api"><strong>7.</strong> API reference</a>
|
|
382
|
+
</nav>
|
|
383
|
+
|
|
384
|
+
<!-- ───────────────────────── 1. Generic spinners ───────────────────── -->
|
|
385
|
+
<section id="spinners-generic">
|
|
386
|
+
<div class="section-label">1 · Spinners · Generic</div>
|
|
387
|
+
<h2>18 braille loaders</h2>
|
|
388
|
+
<p class="blurb">
|
|
389
|
+
Frame-cycled spinners indexed by their generic name. Ported verbatim from
|
|
390
|
+
<a href="https://github.com/gunnargray-dev/unicode-animations" target="_blank">unicode-animations</a> (MIT, Gunnar Gray).
|
|
391
|
+
Each entry is <code>{ frames: string[], interval: number }</code> — pure data, no I/O.
|
|
392
|
+
</p>
|
|
393
|
+
<div class="row-list" id="rows-generic"></div>
|
|
394
|
+
</section>
|
|
395
|
+
|
|
396
|
+
<!-- ───────────────────────── 2. Canon spinners ─────────────────────── -->
|
|
397
|
+
<section id="spinners-canon">
|
|
398
|
+
<div class="section-label">2 · Spinners · Canon</div>
|
|
399
|
+
<h2>9 workshop names</h2>
|
|
400
|
+
<p class="blurb">
|
|
401
|
+
Aliases drawn from CLEO canon (<code>CLEO-VISION.md</code> · <code>NEXUS-CORE-ASPECTS.md</code>).
|
|
402
|
+
Same Spinner objects as the generic registry — references, not copies — so renaming
|
|
403
|
+
a generic spinner automatically updates the canon view.
|
|
404
|
+
</p>
|
|
405
|
+
<div class="row-list" id="rows-canon"></div>
|
|
406
|
+
</section>
|
|
407
|
+
|
|
408
|
+
<!-- ───────────────────────── 3. Progress ───────────────────────────── -->
|
|
409
|
+
<section id="progress">
|
|
410
|
+
<div class="section-label">3 · Progress · Canon Styles</div>
|
|
411
|
+
<h2>3 woven gauges</h2>
|
|
412
|
+
<p class="blurb">
|
|
413
|
+
Fixed-width segmented bars. <strong>Tapestry</strong> uses coarse Unicode
|
|
414
|
+
blocks (<code>░▒▓█</code>) for a woven cloth feel. <strong>Cascade</strong> uses
|
|
415
|
+
1/8 gradient steps (<code>▏▎▍▌▋▊▉█</code>) for a smooth waterfall edge.
|
|
416
|
+
<strong>Refinery</strong> uses braille block-fill stages, evoking the BRAIN
|
|
417
|
+
memory promotion pipeline filling level-by-level.
|
|
418
|
+
</p>
|
|
419
|
+
<div class="progress-strip" id="progress-strip"></div>
|
|
420
|
+
</section>
|
|
421
|
+
|
|
422
|
+
<!-- ───────────────────────── 4. Sparks ─────────────────────────────── -->
|
|
423
|
+
<section id="sparks">
|
|
424
|
+
<div class="section-label">4 · Sparks · One-Shot</div>
|
|
425
|
+
<h2>4 canon flares</h2>
|
|
426
|
+
<p class="blurb">
|
|
427
|
+
Sparks are finite frame sequences. They play once and decay back to empty —
|
|
428
|
+
not loops. <code>awaken</code> at first dream / cleo init; <code>sweep</code>
|
|
429
|
+
on BRAIN integrity sweep complete; <code>cascade</code> on release-shipped /
|
|
430
|
+
task-complete; <code>weave</code> on playbook stage transitions and CANT
|
|
431
|
+
directive acceptance. (On this page they auto-replay every ~1.4s so you can
|
|
432
|
+
see the motion.)
|
|
433
|
+
</p>
|
|
434
|
+
<div class="row-list" id="rows-sparks"></div>
|
|
435
|
+
</section>
|
|
436
|
+
|
|
437
|
+
<!-- ───────────────────────── 5. AnimateContext ─────────────────────── -->
|
|
438
|
+
<section id="animate-context">
|
|
439
|
+
<div class="section-label">5 · LAFS Render Gate</div>
|
|
440
|
+
<h2>AnimateContext</h2>
|
|
441
|
+
<p class="blurb">
|
|
442
|
+
Every primitive in this package routes its output through an
|
|
443
|
+
<code>AnimateContext</code>. The context is <strong>pure data</strong> — no I/O, no
|
|
444
|
+
timers — derived from the LAFS <code>FlagResolution</code> plus environment
|
|
445
|
+
signals. When silent, primitives return no-op handles so callers don't branch
|
|
446
|
+
on output mode.
|
|
447
|
+
</p>
|
|
448
|
+
<table class="ref">
|
|
449
|
+
<thead><tr><th>Signal</th><th>Source</th><th>Effect</th><th>Reason code</th></tr></thead>
|
|
450
|
+
<tbody>
|
|
451
|
+
<tr><td><code>format !== 'human'</code></td><td>LAFS flags</td><td>Disable animations (machine output)</td><td><code>format-json</code></td></tr>
|
|
452
|
+
<tr><td><code>quiet === true</code></td><td>LAFS flags</td><td>Disable animations (script-friendly)</td><td><code>quiet</code></td></tr>
|
|
453
|
+
<tr><td><code>!isTTY</code></td><td><code>process.stdout.isTTY</code></td><td>Disable animations (piped/redirected)</td><td><code>no-tty</code></td></tr>
|
|
454
|
+
<tr><td><code>NO_COLOR</code> set</td><td><code>process.env.NO_COLOR</code></td><td>Disable animations (no-color.org)</td><td><code>no-color</code></td></tr>
|
|
455
|
+
</tbody>
|
|
456
|
+
</table>
|
|
457
|
+
<p class="blurb">Construct it once at command entry, thread it everywhere downstream:</p>
|
|
458
|
+
<pre class="code"><span class="kw">import</span> { resolveOutputFormat } <span class="kw">from</span> <span class="str">'@cleocode/lafs'</span>;
|
|
459
|
+
<span class="kw">import</span> { createAnimateContext } <span class="kw">from</span> <span class="str">'@cleocode/animations'</span>;
|
|
460
|
+
|
|
461
|
+
<span class="kw">const</span> flags = resolveOutputFormat({ humanFlag: <span class="kw">true</span> });
|
|
462
|
+
<span class="kw">const</span> ctx = createAnimateContext({ flagResolution: flags });
|
|
463
|
+
|
|
464
|
+
<span class="kw">if</span> (ctx.enabled) <span class="cm">/* render spinner */</span>;
|
|
465
|
+
<span class="kw">else</span> console.log(<span class="str">`silent: ${ctx.reason}`</span>);
|
|
466
|
+
<span class="cm">// → "enabled" | "format-json" | "quiet" | "no-tty" | "no-color"</span></pre>
|
|
467
|
+
</section>
|
|
468
|
+
|
|
469
|
+
<!-- ───────────────────────── 6. Guide ──────────────────────────────── -->
|
|
470
|
+
<section id="guide">
|
|
471
|
+
<div class="section-label">6 · Usage Guide</div>
|
|
472
|
+
<h2>How to render</h2>
|
|
473
|
+
|
|
474
|
+
<p class="blurb">
|
|
475
|
+
<strong>Lint rule:</strong> any <code>process.stdout.write(...)</code> of a string starting with
|
|
476
|
+
<code>\r</code> outside <code>@cleocode/animations</code> is a contract violation.
|
|
477
|
+
Always route through <code>createSpinnerHandle</code> — it owns the timer, the cursor,
|
|
478
|
+
the exit handler, and the LAFS gate.
|
|
479
|
+
</p>
|
|
480
|
+
|
|
481
|
+
<div class="sub-h">Spinner during async work — the canonical pattern</div>
|
|
482
|
+
<pre class="code"><span class="kw">import</span> { resolveOutputFormat } <span class="kw">from</span> <span class="str">'@cleocode/lafs'</span>;
|
|
483
|
+
<span class="kw">import</span> { createAnimateContext, createSpinnerHandle } <span class="kw">from</span> <span class="str">'@cleocode/animations'</span>;
|
|
484
|
+
|
|
485
|
+
<span class="kw">const</span> ctx = createAnimateContext({
|
|
486
|
+
flagResolution: resolveOutputFormat({ humanFlag: <span class="kw">true</span> }),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
<span class="kw">const</span> spinner = createSpinnerHandle(ctx, <span class="str">'looming'</span>, <span class="str">'Weaving tasks…'</span>);
|
|
490
|
+
spinner.start();
|
|
491
|
+
<span class="kw">try</span> {
|
|
492
|
+
<span class="kw">await</span> doWork();
|
|
493
|
+
spinner.stop(<span class="str">'✔ Tapestry complete.'</span>);
|
|
494
|
+
} <span class="kw">catch</span> (err) {
|
|
495
|
+
spinner.stop();
|
|
496
|
+
<span class="kw">throw</span> err;
|
|
497
|
+
}</pre>
|
|
498
|
+
|
|
499
|
+
<div class="sub-h">Multi-step pipeline with label updates</div>
|
|
500
|
+
<pre class="code"><span class="kw">const</span> spinner = createSpinnerHandle(ctx, <span class="str">'weaving'</span>, <span class="str">'Connecting to database…'</span>);
|
|
501
|
+
spinner.start();
|
|
502
|
+
|
|
503
|
+
<span class="kw">const</span> db = <span class="kw">await</span> connect();
|
|
504
|
+
spinner.update(<span class="str">`Running ${migrations.length} migrations…`</span>);
|
|
505
|
+
|
|
506
|
+
<span class="kw">await</span> db.migrate(migrations);
|
|
507
|
+
spinner.stop(<span class="str">'✔ Database ready.'</span>);</pre>
|
|
508
|
+
|
|
509
|
+
<div class="sub-h">Progress bar with a known ratio</div>
|
|
510
|
+
<pre class="code"><span class="kw">import</span> { renderProgressBar } <span class="kw">from</span> <span class="str">'@cleocode/animations'</span>;
|
|
511
|
+
|
|
512
|
+
<span class="kw">function</span> tick(done, total) {
|
|
513
|
+
<span class="kw">const</span> ratio = done / total;
|
|
514
|
+
<span class="kw">const</span> bar = renderProgressBar(<span class="str">'refinery'</span>, ratio, <span class="num">36</span>);
|
|
515
|
+
<span class="cm">// (progress bars are not yet routed through a handle —</span>
|
|
516
|
+
<span class="cm">// gate manually with ctx.enabled until ProgressBarHandle ships)</span>
|
|
517
|
+
<span class="kw">if</span> (ctx.enabled) process.stdout.write(<span class="str">`\r\x1B[2K ${bar} ${done}/${total}`</span>);
|
|
518
|
+
}</pre>
|
|
519
|
+
|
|
520
|
+
<div class="sub-h">One-shot spark on success</div>
|
|
521
|
+
<pre class="code"><span class="kw">import</span> { sparks, sparkDurationMs } <span class="kw">from</span> <span class="str">'@cleocode/animations'</span>;
|
|
522
|
+
|
|
523
|
+
<span class="kw">async function</span> playSpark(name) {
|
|
524
|
+
<span class="kw">if</span> (!ctx.enabled) <span class="kw">return</span>;
|
|
525
|
+
<span class="kw">const</span> { frames, interval } = sparks[name];
|
|
526
|
+
<span class="kw">for</span> (<span class="kw">const</span> f <span class="kw">of</span> frames) {
|
|
527
|
+
process.stdout.write(<span class="str">`\r\x1B[2K ${f}`</span>);
|
|
528
|
+
<span class="kw">await</span> <span class="kw">new</span> Promise(r => setTimeout(r, interval));
|
|
529
|
+
}
|
|
530
|
+
process.stdout.write(<span class="str">'\n'</span>);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
<span class="kw">await</span> shipRelease();
|
|
534
|
+
<span class="kw">await</span> playSpark(<span class="str">'cascade'</span>); <span class="cm">// total: ~980ms via sparkDurationMs('cascade')</span></pre>
|
|
535
|
+
</section>
|
|
536
|
+
|
|
537
|
+
<!-- ───────────────────────── 7. API reference ──────────────────────── -->
|
|
538
|
+
<section id="api">
|
|
539
|
+
<div class="section-label">7 · API Reference</div>
|
|
540
|
+
<h2>Exports from <code class="mono">@cleocode/animations</code></h2>
|
|
541
|
+
|
|
542
|
+
<div class="sub-h">Spinners</div>
|
|
543
|
+
<table class="ref">
|
|
544
|
+
<thead><tr><th>Export</th><th>Type / Shape</th></tr></thead>
|
|
545
|
+
<tbody>
|
|
546
|
+
<tr><td><code>spinners</code></td><td><code>Record<BrailleSpinnerName, Spinner></code> — 18 entries</td></tr>
|
|
547
|
+
<tr><td><code>canonSpinners</code></td><td><code>Record<CanonSpinnerName, Spinner></code> — 9 aliases</td></tr>
|
|
548
|
+
<tr><td><code>CANON_TO_GENERIC</code></td><td><code>Record<CanonSpinnerName, BrailleSpinnerName></code></td></tr>
|
|
549
|
+
<tr><td><code>resolveSpinner(name)</code></td><td><code>(string) => Spinner | undefined</code></td></tr>
|
|
550
|
+
<tr><td><code>gridToBraille(grid)</code></td><td><code>(boolean[][]) => string</code></td></tr>
|
|
551
|
+
<tr><td><code>makeGrid(rows, cols)</code></td><td><code>(number, number) => boolean[][]</code></td></tr>
|
|
552
|
+
<tr><td><code>Spinner</code></td><td>TS interface: <code>{ frames: readonly string[]; interval: number }</code></td></tr>
|
|
553
|
+
<tr><td><code>BrailleSpinnerName</code> · <code>CanonSpinnerName</code></td><td>TS string-literal unions</td></tr>
|
|
554
|
+
</tbody>
|
|
555
|
+
</table>
|
|
556
|
+
|
|
557
|
+
<div class="sub-h">AnimateContext</div>
|
|
558
|
+
<table class="ref">
|
|
559
|
+
<thead><tr><th>Export</th><th>Type / Shape</th></tr></thead>
|
|
560
|
+
<tbody>
|
|
561
|
+
<tr><td><code>createAnimateContext(input)</code></td><td><code>(AnimateContextInput) => AnimateContext</code></td></tr>
|
|
562
|
+
<tr><td><code>SILENT_CONTEXT</code></td><td>Frozen <code>AnimateContext</code> — always disabled</td></tr>
|
|
563
|
+
<tr><td><code>AnimateContext</code></td><td><code>{ enabled, reason, inputs }</code></td></tr>
|
|
564
|
+
<tr><td><code>AnimateContextInput</code></td><td><code>{ flagResolution, isTTY?, noColor? }</code></td></tr>
|
|
565
|
+
<tr><td><code>FlagResolutionLike</code></td><td><code>{ format: 'json'|'human'; quiet: boolean }</code></td></tr>
|
|
566
|
+
</tbody>
|
|
567
|
+
</table>
|
|
568
|
+
|
|
569
|
+
<div class="sub-h">SpinnerHandle (canonical <code>\r</code> owner)</div>
|
|
570
|
+
<table class="ref">
|
|
571
|
+
<thead><tr><th>Export</th><th>Type / Shape</th></tr></thead>
|
|
572
|
+
<tbody>
|
|
573
|
+
<tr><td><code>createSpinnerHandle(ctx, name, label, options?)</code></td><td><code>(AnimateContext, name, string, SpinnerHandleOptions?) => SpinnerHandle</code></td></tr>
|
|
574
|
+
<tr><td><code>SpinnerHandle</code></td><td><code>{ start(); stop(finalLine?); update(label); enabled: boolean }</code></td></tr>
|
|
575
|
+
<tr><td><code>SpinnerHandleOptions</code></td><td><code>{ delayMs?: number }</code> — defaults to <code>150</code></td></tr>
|
|
576
|
+
</tbody>
|
|
577
|
+
</table>
|
|
578
|
+
|
|
579
|
+
<div class="sub-h">Progress & Sparks</div>
|
|
580
|
+
<table class="ref">
|
|
581
|
+
<thead><tr><th>Export</th><th>Type / Shape</th></tr></thead>
|
|
582
|
+
<tbody>
|
|
583
|
+
<tr><td><code>progressBars</code></td><td><code>Record<ProgressBarStyle, ProgressBarRenderer></code></td></tr>
|
|
584
|
+
<tr><td><code>renderProgressBar(style, ratio, width)</code></td><td><code>(style, number, number) => string</code></td></tr>
|
|
585
|
+
<tr><td><code>ProgressBarStyle</code></td><td><code>'tapestry' | 'cascade' | 'refinery'</code></td></tr>
|
|
586
|
+
<tr><td><code>sparks</code></td><td><code>Record<SparkName, Spark></code></td></tr>
|
|
587
|
+
<tr><td><code>sparkDurationMs(name)</code></td><td><code>(SparkName) => number</code></td></tr>
|
|
588
|
+
<tr><td><code>SparkName</code></td><td><code>'awaken' | 'sweep' | 'cascade' | 'weave'</code></td></tr>
|
|
589
|
+
</tbody>
|
|
590
|
+
</table>
|
|
591
|
+
|
|
592
|
+
<div class="sub-h">Canon mapping</div>
|
|
593
|
+
<table class="ref">
|
|
594
|
+
<thead><tr><th>Canon name</th><th>Generic name</th><th>Cleo lore role</th></tr></thead>
|
|
595
|
+
<tbody>
|
|
596
|
+
<tr><td><code>looming</code></td><td><code>helix</code></td><td>Twin strands weaving — task on the LOOM</td></tr>
|
|
597
|
+
<tr><td><code>weaving</code></td><td><code>braillewave</code></td><td>Pattern threading across columns</td></tr>
|
|
598
|
+
<tr><td><code>heartbeat</code></td><td><code>breathe</code></td><td>Organic in-out pulse — Hearth presence</td></tr>
|
|
599
|
+
<tr><td><code>awakening</code></td><td><code>pulse</code></td><td>Radial bloom — first dream / cleo init</td></tr>
|
|
600
|
+
<tr><td><code>sweeping</code></td><td><code>scan</code></td><td>Left→right beam — BRAIN integrity Sweep</td></tr>
|
|
601
|
+
<tr><td><code>watching</code></td><td><code>orbit</code></td><td>Circular sentinel — sentient daemon tick</td></tr>
|
|
602
|
+
<tr><td><code>cascade</code></td><td><code>cascade</code></td><td>Diagonal fall — command-success accent</td></tr>
|
|
603
|
+
<tr><td><code>tapestry</code></td><td><code>waverows</code></td><td>Multi-row sinusoidal — wave of tasks shipping</td></tr>
|
|
604
|
+
<tr><td><code>refinery</code></td><td><code>columns</code></td><td>Filling stages — memory promotion pipeline</td></tr>
|
|
605
|
+
</tbody>
|
|
606
|
+
</table>
|
|
607
|
+
</section>
|
|
608
|
+
|
|
609
|
+
<footer class="foot">
|
|
610
|
+
<div>
|
|
611
|
+
Forked from
|
|
612
|
+
<a href="https://github.com/gunnargray-dev/unicode-animations" target="_blank" rel="noopener">gunnargray-dev/unicode-animations</a>
|
|
613
|
+
(MIT) · Canon vocabulary from <code>docs/concepts/CLEO-VISION.md</code> +
|
|
614
|
+
<code>docs/concepts/NEXUS-CORE-ASPECTS.md</code> ·
|
|
615
|
+
<a href="https://github.com/kryptobaseddev/cleo/tree/main/packages/animations" target="_blank">source on GitHub</a>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="palette">
|
|
618
|
+
<span><i style="background:var(--nexus)"></i>NEXUS</span>
|
|
619
|
+
<span><i style="background:var(--loom)"></i>LOOM</span>
|
|
620
|
+
<span><i style="background:var(--brain)"></i>BRAIN</span>
|
|
621
|
+
<span><i style="background:var(--cant)"></i>CANT</span>
|
|
622
|
+
<span><i style="background:var(--conduit)"></i>CONDUIT</span>
|
|
623
|
+
<span><i style="background:var(--awaken)"></i>AWAKEN</span>
|
|
624
|
+
</div>
|
|
625
|
+
</footer>
|
|
626
|
+
|
|
627
|
+
</main>
|
|
628
|
+
|
|
629
|
+
<script>
|
|
630
|
+
/* ──────────────────────────────────────────────────────────────────────
|
|
631
|
+
Vendored frame data — kept in sync with src/braille.ts + src/spark.ts.
|
|
632
|
+
The page is intentionally self-contained: open in a browser without
|
|
633
|
+
any build step, no node_modules, no fetch. If upstream frames change,
|
|
634
|
+
re-paste from src/__snapshots__/braille.test.ts.snap.
|
|
635
|
+
────────────────────────────────────────────────────────────────────── */
|
|
636
|
+
const SPINNERS = {
|
|
637
|
+
braille: { frames: ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'], interval: 80 },
|
|
638
|
+
braillewave: { frames: ['⠁⠂⠄⡀','⠂⠄⡀⢀','⠄⡀⢀⠠','⡀⢀⠠⠐','⢀⠠⠐⠈','⠠⠐⠈⠁','⠐⠈⠁⠂','⠈⠁⠂⠄'], interval: 100 },
|
|
639
|
+
dna: { frames: ['⠋⠉⠙⠚','⠉⠙⠚⠒','⠙⠚⠒⠂','⠚⠒⠂⠂','⠒⠂⠂⠒','⠂⠂⠒⠲','⠂⠒⠲⠴','⠒⠲⠴⠤','⠲⠴⠤⠄','⠴⠤⠄⠋','⠤⠄⠋⠉','⠄⠋⠉⠙'], interval: 80 },
|
|
640
|
+
scan: { frames: ['⠀⠀⠀⠀','⡇⠀⠀⠀','⣿⠀⠀⠀','⢸⡇⠀⠀','⠀⣿⠀⠀','⠀⢸⡇⠀','⠀⠀⣿⠀','⠀⠀⢸⡇','⠀⠀⠀⣿','⠀⠀⠀⢸'], interval: 70 },
|
|
641
|
+
rain: { frames: ['⢁⠂⠔⠈','⠂⠌⡠⠐','⠄⡐⢀⠡','⡈⠠⠀⢂','⠐⢀⠁⠄','⠠⠁⠊⡀','⢁⠂⠔⠈','⠂⠌⡠⠐','⠄⡐⢀⠡','⡈⠠⠀⢂','⠐⢀⠁⠄','⠠⠁⠊⡀'], interval: 100 },
|
|
642
|
+
scanline: { frames: ['⠉⠉⠉','⠓⠓⠓','⠦⠦⠦','⣄⣄⣄','⠦⠦⠦','⠓⠓⠓'], interval: 120 },
|
|
643
|
+
pulse: { frames: ['⠀⠶⠀','⠰⣿⠆','⢾⣉⡷','⣏⠀⣹','⡁⠀⢈'], interval: 180 },
|
|
644
|
+
snake: { frames: ['⣁⡀','⣉⠀','⡉⠁','⠉⠉','⠈⠙','⠀⠛','⠐⠚','⠒⠒','⠖⠂','⠶⠀','⠦⠄','⠤⠤','⠠⢤','⠀⣤','⢀⣠','⣀⣀'], interval: 80 },
|
|
645
|
+
sparkle: { frames: ['⡡⠊⢔⠡','⠊⡰⡡⡘','⢔⢅⠈⢢','⡁⢂⠆⡍','⢔⠨⢑⢐','⠨⡑⡠⠊'], interval: 150 },
|
|
646
|
+
cascade: { frames: ['⠀⠀⠀⠀','⠀⠀⠀⠀','⠁⠀⠀⠀','⠋⠀⠀⠀','⠞⠁⠀⠀','⡴⠋⠀⠀','⣠⠞⠁⠀','⢀⡴⠋⠀','⠀⣠⠞⠁','⠀⢀⡴⠋','⠀⠀⣠⠞','⠀⠀⢀⡴','⠀⠀⠀⣠','⠀⠀⠀⢀'], interval: 60 },
|
|
647
|
+
columns: { frames: ['⡀⠀⠀','⡄⠀⠀','⡆⠀⠀','⡇⠀⠀','⣇⠀⠀','⣧⠀⠀','⣷⠀⠀','⣿⠀⠀','⣿⡀⠀','⣿⡄⠀','⣿⡆⠀','⣿⡇⠀','⣿⣇⠀','⣿⣧⠀','⣿⣷⠀','⣿⣿⠀','⣿⣿⡀','⣿⣿⡄','⣿⣿⡆','⣿⣿⡇','⣿⣿⣇','⣿⣿⣧','⣿⣿⣷','⣿⣿⣿','⣿⣿⣿','⠀⠀⠀'], interval: 60 },
|
|
648
|
+
orbit: { frames: ['⠃','⠉','⠘','⠰','⢠','⣀','⡄','⠆'], interval: 100 },
|
|
649
|
+
breathe: { frames: ['⠀','⠂','⠌','⡑','⢕','⢝','⣫','⣟','⣿','⣟','⣫','⢝','⢕','⡑','⠌','⠂','⠀'], interval: 100 },
|
|
650
|
+
waverows: { frames: ['⠖⠉⠉⠑','⡠⠖⠉⠉','⣠⡠⠖⠉','⣄⣠⡠⠖','⠢⣄⣠⡠','⠙⠢⣄⣠','⠉⠙⠢⣄','⠊⠉⠙⠢','⠜⠊⠉⠙','⡤⠜⠊⠉','⣀⡤⠜⠊','⢤⣀⡤⠜','⠣⢤⣀⡤','⠑⠣⢤⣀','⠉⠑⠣⢤','⠋⠉⠑⠣'], interval: 90 },
|
|
651
|
+
checkerboard: { frames: ['⢕⢕⢕','⡪⡪⡪','⢊⠔⡡','⡡⢊⠔'], interval: 250 },
|
|
652
|
+
helix: { frames: ['⢌⣉⢎⣉','⣉⡱⣉⡱','⣉⢎⣉⢎','⡱⣉⡱⣉','⢎⣉⢎⣉','⣉⡱⣉⡱','⣉⢎⣉⢎','⡱⣉⡱⣉','⢎⣉⢎⣉','⣉⡱⣉⡱','⣉⢎⣉⢎','⡱⣉⡱⣉','⢎⣉⢎⣉','⣉⡱⣉⡱','⣉⢎⣉⢎','⡱⣉⡱⣉'], interval: 80 },
|
|
653
|
+
fillsweep: { frames: ['⣀⣀','⣤⣤','⣶⣶','⣿⣿','⣿⣿','⣿⣿','⣶⣶','⣤⣤','⣀⣀','⠀⠀','⠀⠀'], interval: 100 },
|
|
654
|
+
diagswipe: { frames: ['⠁⠀','⠋⠀','⠟⠁','⡿⠋','⣿⠟','⣿⡿','⣿⣿','⣿⣿','⣾⣿','⣴⣿','⣠⣾','⢀⣴','⠀⣠','⠀⢀','⠀⠀','⠀⠀'], interval: 60 },
|
|
655
|
+
};
|
|
656
|
+
const CANON_TO_GENERIC = {
|
|
657
|
+
looming: 'helix', weaving: 'braillewave', heartbeat: 'breathe',
|
|
658
|
+
awakening: 'pulse', sweeping: 'scan', watching: 'orbit',
|
|
659
|
+
cascade: 'cascade', tapestry: 'waverows', refinery: 'columns',
|
|
660
|
+
};
|
|
661
|
+
const SPARKS = {
|
|
662
|
+
awaken: { frames: ['⠀','⠂','⠒','⠶','⣶','⣷','⣿','⣷','⣶','⠶','⠒','⠂','⠀'], interval: 90 },
|
|
663
|
+
sweep: { frames: ['⠀⠀⠀⠀','⡇⠀⠀⠀','⢸⡇⠀⠀','⠀⢸⡇⠀','⠀⠀⢸⡇','⠀⠀⠀⢸','⠀⠀⠀⠀'], interval: 80 },
|
|
664
|
+
cascade: { frames: ['⠀⠀⠀⠀','⠁⠀⠀⠀','⠋⠀⠀⠀','⠞⠁⠀⠀','⡴⠋⠀⠀','⣠⠞⠁⠀','⢀⡴⠋⠀','⠀⣠⠞⠁','⠀⢀⡴⠋','⠀⠀⣠⠞','⠀⠀⢀⡴','⠀⠀⠀⣠','⠀⠀⠀⢀','⠀⠀⠀⠀'], interval: 70 },
|
|
665
|
+
weave: { frames: ['⠀⠀⠀⠀','⠁⠀⠀⠀','⠁⠈⠀⠀','⠉⠈⠁⠀','⠉⠉⠁⠈','⡉⠉⠉⠈','⡏⠉⠉⠉','⡏⡏⠉⠉','⡿⡏⡏⠉','⣿⡿⡏⡏','⣿⣿⡿⡏','⣿⣿⣿⡿','⣿⣿⣿⣿','⣷⣷⣷⣷','⣧⣧⣧⣧','⡇⡇⡇⡇','⠁⠁⠁⠁','⠀⠀⠀⠀'], interval: 70 },
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
/* Progress renderers — direct ports of src/progress.ts */
|
|
669
|
+
function clamp(r) { return Number.isNaN(r) ? 0 : r < 0 ? 0 : r > 1 ? 1 : r; }
|
|
670
|
+
function renderTapestry(r, w) {
|
|
671
|
+
r = clamp(r); w = Math.max(0, w|0); if (!w) return '';
|
|
672
|
+
const exact = r * w, full = Math.floor(exact), frac = exact - full;
|
|
673
|
+
let edge = ''; if (full < w) edge = frac >= 0.75 ? '▓' : frac >= 0.25 ? '▒' : '░';
|
|
674
|
+
return '█'.repeat(full) + edge + '░'.repeat(Math.max(0, w - full - (edge ? 1 : 0)));
|
|
675
|
+
}
|
|
676
|
+
const CASCADE_GRAD = ['', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'];
|
|
677
|
+
function renderCascade(r, w) {
|
|
678
|
+
r = clamp(r); w = Math.max(0, w|0); if (!w) return '';
|
|
679
|
+
const filled = Math.round(r * w * 8);
|
|
680
|
+
const full = Math.floor(filled / 8), partial = filled - full * 8;
|
|
681
|
+
const edge = partial === 0 ? '' : CASCADE_GRAD[partial];
|
|
682
|
+
return '█'.repeat(full) + edge + ' '.repeat(Math.max(0, w - full - (edge ? 1 : 0)));
|
|
683
|
+
}
|
|
684
|
+
const REFINERY_STAGES = ['⠀','⡀','⡄','⡆','⡇','⣇','⣧','⣷','⣿'];
|
|
685
|
+
function renderRefinery(r, w) {
|
|
686
|
+
r = clamp(r); w = Math.max(0, w|0); if (!w) return '';
|
|
687
|
+
const filled = Math.round(r * w * 8);
|
|
688
|
+
const full = Math.floor(filled / 8), partial = filled - full * 8;
|
|
689
|
+
const edge = partial === 0 ? '' : REFINERY_STAGES[partial];
|
|
690
|
+
return '⣿'.repeat(full) + edge + '⠀'.repeat(Math.max(0, w - full - (edge ? 1 : 0)));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* ──────────────────────────────────────────────────────────────────────
|
|
694
|
+
Render the page.
|
|
695
|
+
────────────────────────────────────────────────────────────────────── */
|
|
696
|
+
function makeRow(parent, cls, frame, name, meta, alias) {
|
|
697
|
+
const row = document.createElement('div');
|
|
698
|
+
row.className = `row${cls ? ' ' + cls : ''}`;
|
|
699
|
+
const f = document.createElement('span'); f.className = 'frame'; f.textContent = frame;
|
|
700
|
+
const n = document.createElement('span'); n.className = 'name'; n.textContent = name;
|
|
701
|
+
row.append(f, n);
|
|
702
|
+
if (alias) {
|
|
703
|
+
const a = document.createElement('span');
|
|
704
|
+
a.className = 'alias';
|
|
705
|
+
a.innerHTML = `→ <em>${alias}</em>`;
|
|
706
|
+
row.append(a);
|
|
707
|
+
} else {
|
|
708
|
+
const m = document.createElement('span'); m.className = 'meta'; m.textContent = meta;
|
|
709
|
+
row.append(m);
|
|
710
|
+
}
|
|
711
|
+
parent.append(row);
|
|
712
|
+
return f;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const genericFrames = {};
|
|
716
|
+
const rowsGeneric = document.getElementById('rows-generic');
|
|
717
|
+
for (const [name, sp] of Object.entries(SPINNERS)) {
|
|
718
|
+
genericFrames[name] = makeRow(
|
|
719
|
+
rowsGeneric, '', sp.frames[0], name, `${sp.frames.length}f · ${sp.interval}ms`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const canonFrames = {};
|
|
724
|
+
const rowsCanon = document.getElementById('rows-canon');
|
|
725
|
+
for (const [canon, generic] of Object.entries(CANON_TO_GENERIC)) {
|
|
726
|
+
const sp = SPINNERS[generic];
|
|
727
|
+
canonFrames[canon] = makeRow(rowsCanon, 'canon', sp.frames[0], canon, '', generic);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const sparkFrames = {};
|
|
731
|
+
const rowsSparks = document.getElementById('rows-sparks');
|
|
732
|
+
for (const [name, sp] of Object.entries(SPARKS)) {
|
|
733
|
+
sparkFrames[name] = makeRow(
|
|
734
|
+
rowsSparks, 'spark', sp.frames[0], name, `${sp.frames.length}f · ${sp.interval}ms · once`,
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/* Animate spinners — group by interval, single setInterval per group (upstream pattern) */
|
|
739
|
+
const groupsByInterval = {};
|
|
740
|
+
for (const [name, sp] of Object.entries(SPINNERS)) {
|
|
741
|
+
if (!groupsByInterval[sp.interval]) groupsByInterval[sp.interval] = [];
|
|
742
|
+
groupsByInterval[sp.interval].push({ key: name, frames: sp.frames, i: 0 });
|
|
743
|
+
}
|
|
744
|
+
for (const [interval, group] of Object.entries(groupsByInterval)) {
|
|
745
|
+
setInterval(() => {
|
|
746
|
+
for (const s of group) {
|
|
747
|
+
s.i = (s.i + 1) % s.frames.length;
|
|
748
|
+
const generic = s.key;
|
|
749
|
+
if (genericFrames[generic]) genericFrames[generic].textContent = s.frames[s.i];
|
|
750
|
+
// canon aliases share the same interval so update them in lockstep
|
|
751
|
+
for (const [canon, g] of Object.entries(CANON_TO_GENERIC)) {
|
|
752
|
+
if (g === generic && canonFrames[canon]) canonFrames[canon].textContent = s.frames[s.i];
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}, Number(interval));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/* Sparks — replay each on a long beat so the page is alive */
|
|
759
|
+
for (const [name, sp] of Object.entries(SPARKS)) {
|
|
760
|
+
const target = sparkFrames[name];
|
|
761
|
+
let i = 0;
|
|
762
|
+
const loop = () => {
|
|
763
|
+
if (i < sp.frames.length) { target.textContent = sp.frames[i++]; setTimeout(loop, sp.interval); }
|
|
764
|
+
else { i = 0; setTimeout(loop, 1400); }
|
|
765
|
+
};
|
|
766
|
+
loop();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* Progress strip — sinusoidal sweep, distinct phase per row */
|
|
770
|
+
const strip = document.getElementById('progress-strip');
|
|
771
|
+
const PROG_W = 36;
|
|
772
|
+
const styles = [
|
|
773
|
+
{ name: 'tapestry', renderer: renderTapestry, blurb: 'block fill (░▒▓█)' },
|
|
774
|
+
{ name: 'cascade', renderer: renderCascade, blurb: '1/8 gradient steps' },
|
|
775
|
+
{ name: 'refinery', renderer: renderRefinery, blurb: 'braille block stages' },
|
|
776
|
+
];
|
|
777
|
+
const progRows = styles.map(s => {
|
|
778
|
+
const row = document.createElement('div');
|
|
779
|
+
row.className = `progress-row ${s.name}`;
|
|
780
|
+
row.innerHTML = `<div class="style">${s.name}<em>${s.blurb}</em></div><div class="bar mono"></div><div class="pct"></div>`;
|
|
781
|
+
strip.append(row);
|
|
782
|
+
return { ...s, bar: row.querySelector('.bar'), pct: row.querySelector('.pct') };
|
|
783
|
+
});
|
|
784
|
+
let t = 0;
|
|
785
|
+
setInterval(() => {
|
|
786
|
+
for (let i = 0; i < progRows.length; i++) {
|
|
787
|
+
const phase = (t + i * 8) * 0.05;
|
|
788
|
+
const ratio = (Math.sin(phase) + 1) / 2;
|
|
789
|
+
progRows[i].bar.textContent = progRows[i].renderer(ratio, PROG_W);
|
|
790
|
+
progRows[i].pct.textContent = `${Math.round(ratio * 100)}%`;
|
|
791
|
+
}
|
|
792
|
+
t++;
|
|
793
|
+
}, 80);
|
|
794
|
+
|
|
795
|
+
/* Theme toggle */
|
|
796
|
+
const tbtn = document.getElementById('themeToggle');
|
|
797
|
+
const ticon = document.getElementById('themeIcon');
|
|
798
|
+
const tlbl = document.getElementById('themeLabel');
|
|
799
|
+
tbtn.addEventListener('click', () => {
|
|
800
|
+
const dark = document.documentElement.dataset.theme === 'dark';
|
|
801
|
+
document.documentElement.dataset.theme = dark ? 'light' : 'dark';
|
|
802
|
+
ticon.textContent = dark ? '☾' : '☀';
|
|
803
|
+
tlbl.textContent = dark ? 'Dark' : 'Light';
|
|
804
|
+
});
|
|
805
|
+
</script>
|
|
806
|
+
</body>
|
|
807
|
+
</html>
|