unicode-animations 0.2.1 → 1.0.0
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/README.md +2 -11
- package/dist/braille.cjs +2 -18
- package/dist/braille.d.cts +1 -1
- package/dist/braille.d.ts +1 -1
- package/dist/braille.global.js +385 -0
- package/dist/braille.js +1 -1
- package/dist/{chunk-JW3PMLWA.js → chunk-F2BWZODB.js} +2 -18
- package/dist/index.cjs +2 -18
- package/dist/index.js +1 -1
- package/package.json +33 -8
- package/scripts/demo.cjs +16 -2
- package/scripts/demo.html +254 -52
- package/scripts/postinstall.cjs +108 -56
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Unicode spinner animations as raw frame data — no dependencies, works everywhe
|
|
|
4
4
|
|
|
5
5
|
## Demo
|
|
6
6
|
|
|
7
|
-
See all
|
|
7
|
+
See all 18 spinners animating live:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx unicode-animations --web # open browser demo
|
|
@@ -171,15 +171,6 @@ el.textContent = '✔ Synced';
|
|
|
171
171
|
| `fillsweep` | 11 | 100ms |
|
|
172
172
|
| `diagswipe` | 16 | 60ms |
|
|
173
173
|
|
|
174
|
-
### Non-braille classics
|
|
175
|
-
|
|
176
|
-
| Name | Preview | Interval |
|
|
177
|
-
|------|---------|----------|
|
|
178
|
-
| `arc` | `◜ ◠ ◝ ◞ ◡ ◟` | 100ms |
|
|
179
|
-
| `halfmoon` | `◐ ◓ ◑ ◒` | 180ms |
|
|
180
|
-
| `line` | `\| / — \` | 100ms |
|
|
181
|
-
| `blocks` | `▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▇ ▆ ▅ ▄ ▃ ▂` | 100ms |
|
|
182
|
-
|
|
183
174
|
## Custom spinners
|
|
184
175
|
|
|
185
176
|
Create your own braille spinners using the grid utilities:
|
|
@@ -218,7 +209,7 @@ interface Spinner {
|
|
|
218
209
|
| `gridToBraille(grid)` | `(boolean[][]) => string` |
|
|
219
210
|
| `makeGrid(rows, cols)` | `(number, number) => boolean[][]` |
|
|
220
211
|
| `Spinner` | TypeScript interface |
|
|
221
|
-
| `BrailleSpinnerName` | Union type of all
|
|
212
|
+
| `BrailleSpinnerName` | Union type of all 18 spinner names |
|
|
222
213
|
|
|
223
214
|
### Exports from `'unicode-animations/braille'`
|
|
224
215
|
|
package/dist/braille.cjs
CHANGED
|
@@ -56,6 +56,7 @@ function gridToBraille(grid) {
|
|
|
56
56
|
return result;
|
|
57
57
|
}
|
|
58
58
|
function makeGrid(rows, cols) {
|
|
59
|
+
if (rows <= 0 || cols <= 0) return [];
|
|
59
60
|
return Array.from({ length: rows }, () => Array(cols).fill(false));
|
|
60
61
|
}
|
|
61
62
|
function genScan() {
|
|
@@ -377,24 +378,7 @@ var spinners = {
|
|
|
377
378
|
checkerboard: { frames: genCheckerboard(), interval: 250 },
|
|
378
379
|
helix: { frames: genHelix(), interval: 80 },
|
|
379
380
|
fillsweep: { frames: genFillSweep(), interval: 100 },
|
|
380
|
-
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
381
|
-
// === Non-braille classics ===
|
|
382
|
-
arc: {
|
|
383
|
-
frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"],
|
|
384
|
-
interval: 100
|
|
385
|
-
},
|
|
386
|
-
halfmoon: {
|
|
387
|
-
frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"],
|
|
388
|
-
interval: 180
|
|
389
|
-
},
|
|
390
|
-
line: {
|
|
391
|
-
frames: ["|", "/", "\u2014", "\\"],
|
|
392
|
-
interval: 100
|
|
393
|
-
},
|
|
394
|
-
blocks: {
|
|
395
|
-
frames: ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588", "\u2587", "\u2586", "\u2585", "\u2584", "\u2583", "\u2582"],
|
|
396
|
-
interval: 100
|
|
397
|
-
}
|
|
381
|
+
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
398
382
|
};
|
|
399
383
|
var braille_default = spinners;
|
|
400
384
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/braille.d.cts
CHANGED
|
@@ -9,7 +9,7 @@ interface Spinner {
|
|
|
9
9
|
readonly frames: readonly string[];
|
|
10
10
|
readonly interval: number;
|
|
11
11
|
}
|
|
12
|
-
type BrailleSpinnerName = 'braille' | 'braillewave' | 'dna' | 'scan' | 'rain' | 'scanline' | 'pulse' | 'snake' | 'sparkle' | 'cascade' | 'columns' | 'orbit' | 'breathe' | 'waverows' | 'checkerboard' | 'helix' | 'fillsweep' | 'diagswipe'
|
|
12
|
+
type BrailleSpinnerName = 'braille' | 'braillewave' | 'dna' | 'scan' | 'rain' | 'scanline' | 'pulse' | 'snake' | 'sparkle' | 'cascade' | 'columns' | 'orbit' | 'breathe' | 'waverows' | 'checkerboard' | 'helix' | 'fillsweep' | 'diagswipe';
|
|
13
13
|
/**
|
|
14
14
|
* Convert a 2D boolean grid into a braille string.
|
|
15
15
|
* grid[row][col] = true means dot is raised.
|
package/dist/braille.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ interface Spinner {
|
|
|
9
9
|
readonly frames: readonly string[];
|
|
10
10
|
readonly interval: number;
|
|
11
11
|
}
|
|
12
|
-
type BrailleSpinnerName = 'braille' | 'braillewave' | 'dna' | 'scan' | 'rain' | 'scanline' | 'pulse' | 'snake' | 'sparkle' | 'cascade' | 'columns' | 'orbit' | 'breathe' | 'waverows' | 'checkerboard' | 'helix' | 'fillsweep' | 'diagswipe'
|
|
12
|
+
type BrailleSpinnerName = 'braille' | 'braillewave' | 'dna' | 'scan' | 'rain' | 'scanline' | 'pulse' | 'snake' | 'sparkle' | 'cascade' | 'columns' | 'orbit' | 'breathe' | 'waverows' | 'checkerboard' | 'helix' | 'fillsweep' | 'diagswipe';
|
|
13
13
|
/**
|
|
14
14
|
* Convert a 2D boolean grid into a braille string.
|
|
15
15
|
* grid[row][col] = true means dot is raised.
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var UnicodeAnimations = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/braille.ts
|
|
22
|
+
var braille_exports = {};
|
|
23
|
+
__export(braille_exports, {
|
|
24
|
+
default: () => braille_default,
|
|
25
|
+
gridToBraille: () => gridToBraille,
|
|
26
|
+
makeGrid: () => makeGrid,
|
|
27
|
+
spinners: () => spinners
|
|
28
|
+
});
|
|
29
|
+
var BRAILLE_DOT_MAP = [
|
|
30
|
+
[1, 8],
|
|
31
|
+
// row 0
|
|
32
|
+
[2, 16],
|
|
33
|
+
// row 1
|
|
34
|
+
[4, 32],
|
|
35
|
+
// row 2
|
|
36
|
+
[64, 128]
|
|
37
|
+
// row 3
|
|
38
|
+
];
|
|
39
|
+
function gridToBraille(grid) {
|
|
40
|
+
const rows = grid.length;
|
|
41
|
+
const cols = grid[0] ? grid[0].length : 0;
|
|
42
|
+
const charCount = Math.ceil(cols / 2);
|
|
43
|
+
let result = "";
|
|
44
|
+
for (let c = 0; c < charCount; c++) {
|
|
45
|
+
let code = 10240;
|
|
46
|
+
for (let r = 0; r < 4 && r < rows; r++) {
|
|
47
|
+
for (let d = 0; d < 2; d++) {
|
|
48
|
+
const col = c * 2 + d;
|
|
49
|
+
if (col < cols && grid[r] && grid[r][col]) {
|
|
50
|
+
code |= BRAILLE_DOT_MAP[r][d];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
result += String.fromCodePoint(code);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function makeGrid(rows, cols) {
|
|
59
|
+
if (rows <= 0 || cols <= 0) return [];
|
|
60
|
+
return Array.from({ length: rows }, () => Array(cols).fill(false));
|
|
61
|
+
}
|
|
62
|
+
function genScan() {
|
|
63
|
+
const W = 8, H = 4, frames = [];
|
|
64
|
+
for (let pos = -1; pos < W + 1; pos++) {
|
|
65
|
+
const g = makeGrid(H, W);
|
|
66
|
+
for (let r = 0; r < H; r++) {
|
|
67
|
+
for (let c = 0; c < W; c++) {
|
|
68
|
+
if (c === pos || c === pos - 1) g[r][c] = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
frames.push(gridToBraille(g));
|
|
72
|
+
}
|
|
73
|
+
return frames;
|
|
74
|
+
}
|
|
75
|
+
function genRain() {
|
|
76
|
+
const W = 8, H = 4, totalFrames = 12, frames = [];
|
|
77
|
+
const offsets = [0, 3, 1, 5, 2, 7, 4, 6];
|
|
78
|
+
for (let f = 0; f < totalFrames; f++) {
|
|
79
|
+
const g = makeGrid(H, W);
|
|
80
|
+
for (let c = 0; c < W; c++) {
|
|
81
|
+
const row = (f + offsets[c]) % (H + 2);
|
|
82
|
+
if (row < H) g[row][c] = true;
|
|
83
|
+
}
|
|
84
|
+
frames.push(gridToBraille(g));
|
|
85
|
+
}
|
|
86
|
+
return frames;
|
|
87
|
+
}
|
|
88
|
+
function genScanLine() {
|
|
89
|
+
const W = 6, H = 4, frames = [];
|
|
90
|
+
const positions = [0, 1, 2, 3, 2, 1];
|
|
91
|
+
for (const row of positions) {
|
|
92
|
+
const g = makeGrid(H, W);
|
|
93
|
+
for (let c = 0; c < W; c++) {
|
|
94
|
+
g[row][c] = true;
|
|
95
|
+
if (row > 0) g[row - 1][c] = c % 2 === 0;
|
|
96
|
+
}
|
|
97
|
+
frames.push(gridToBraille(g));
|
|
98
|
+
}
|
|
99
|
+
return frames;
|
|
100
|
+
}
|
|
101
|
+
function genPulse() {
|
|
102
|
+
const W = 6, H = 4, frames = [];
|
|
103
|
+
const cx = W / 2 - 0.5, cy = H / 2 - 0.5;
|
|
104
|
+
const radii = [0.5, 1.2, 2, 3, 3.5];
|
|
105
|
+
for (const r of radii) {
|
|
106
|
+
const g = makeGrid(H, W);
|
|
107
|
+
for (let row = 0; row < H; row++) {
|
|
108
|
+
for (let col = 0; col < W; col++) {
|
|
109
|
+
const dist = Math.sqrt((col - cx) ** 2 + (row - cy) ** 2);
|
|
110
|
+
if (Math.abs(dist - r) < 0.9) g[row][col] = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
frames.push(gridToBraille(g));
|
|
114
|
+
}
|
|
115
|
+
return frames;
|
|
116
|
+
}
|
|
117
|
+
function genSnake() {
|
|
118
|
+
const W = 4, H = 4;
|
|
119
|
+
const path = [];
|
|
120
|
+
for (let r = 0; r < H; r++) {
|
|
121
|
+
if (r % 2 === 0) {
|
|
122
|
+
for (let c = 0; c < W; c++) path.push([r, c]);
|
|
123
|
+
} else {
|
|
124
|
+
for (let c = W - 1; c >= 0; c--) path.push([r, c]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const frames = [];
|
|
128
|
+
for (let i = 0; i < path.length; i++) {
|
|
129
|
+
const g = makeGrid(H, W);
|
|
130
|
+
for (let t = 0; t < 4; t++) {
|
|
131
|
+
const idx = (i - t + path.length) % path.length;
|
|
132
|
+
g[path[idx][0]][path[idx][1]] = true;
|
|
133
|
+
}
|
|
134
|
+
frames.push(gridToBraille(g));
|
|
135
|
+
}
|
|
136
|
+
return frames;
|
|
137
|
+
}
|
|
138
|
+
function genSparkle() {
|
|
139
|
+
const patterns = [
|
|
140
|
+
[1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0],
|
|
141
|
+
[0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0],
|
|
142
|
+
[0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1],
|
|
143
|
+
[1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0],
|
|
144
|
+
[0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
|
|
145
|
+
[0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0]
|
|
146
|
+
];
|
|
147
|
+
const W = 8, H = 4, frames = [];
|
|
148
|
+
for (const pat of patterns) {
|
|
149
|
+
const g = makeGrid(H, W);
|
|
150
|
+
for (let r = 0; r < H; r++) {
|
|
151
|
+
for (let c = 0; c < W; c++) {
|
|
152
|
+
g[r][c] = !!pat[r * W + c];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
frames.push(gridToBraille(g));
|
|
156
|
+
}
|
|
157
|
+
return frames;
|
|
158
|
+
}
|
|
159
|
+
function genCascade() {
|
|
160
|
+
const W = 8, H = 4, frames = [];
|
|
161
|
+
for (let offset = -2; offset < W + H; offset++) {
|
|
162
|
+
const g = makeGrid(H, W);
|
|
163
|
+
for (let r = 0; r < H; r++) {
|
|
164
|
+
for (let c = 0; c < W; c++) {
|
|
165
|
+
const diag = c + r;
|
|
166
|
+
if (diag === offset || diag === offset - 1) g[r][c] = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
frames.push(gridToBraille(g));
|
|
170
|
+
}
|
|
171
|
+
return frames;
|
|
172
|
+
}
|
|
173
|
+
function genColumns() {
|
|
174
|
+
const W = 6, H = 4, frames = [];
|
|
175
|
+
for (let col = 0; col < W; col++) {
|
|
176
|
+
for (let fillTo = H - 1; fillTo >= 0; fillTo--) {
|
|
177
|
+
const g = makeGrid(H, W);
|
|
178
|
+
for (let pc = 0; pc < col; pc++) {
|
|
179
|
+
for (let r = 0; r < H; r++) g[r][pc] = true;
|
|
180
|
+
}
|
|
181
|
+
for (let r = fillTo; r < H; r++) g[r][col] = true;
|
|
182
|
+
frames.push(gridToBraille(g));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const full = makeGrid(H, W);
|
|
186
|
+
for (let r = 0; r < H; r++) for (let c = 0; c < W; c++) full[r][c] = true;
|
|
187
|
+
frames.push(gridToBraille(full));
|
|
188
|
+
frames.push(gridToBraille(makeGrid(H, W)));
|
|
189
|
+
return frames;
|
|
190
|
+
}
|
|
191
|
+
function genOrbit() {
|
|
192
|
+
const W = 2, H = 4;
|
|
193
|
+
const path = [
|
|
194
|
+
[0, 0],
|
|
195
|
+
[0, 1],
|
|
196
|
+
[1, 1],
|
|
197
|
+
[2, 1],
|
|
198
|
+
[3, 1],
|
|
199
|
+
[3, 0],
|
|
200
|
+
[2, 0],
|
|
201
|
+
[1, 0]
|
|
202
|
+
];
|
|
203
|
+
const frames = [];
|
|
204
|
+
for (let i = 0; i < path.length; i++) {
|
|
205
|
+
const g = makeGrid(H, W);
|
|
206
|
+
g[path[i][0]][path[i][1]] = true;
|
|
207
|
+
const t1 = (i - 1 + path.length) % path.length;
|
|
208
|
+
g[path[t1][0]][path[t1][1]] = true;
|
|
209
|
+
frames.push(gridToBraille(g));
|
|
210
|
+
}
|
|
211
|
+
return frames;
|
|
212
|
+
}
|
|
213
|
+
function genBreathe() {
|
|
214
|
+
const stages = [
|
|
215
|
+
[],
|
|
216
|
+
[[1, 0]],
|
|
217
|
+
[[0, 1], [2, 0]],
|
|
218
|
+
[[0, 0], [1, 1], [3, 0]],
|
|
219
|
+
[[0, 0], [1, 1], [2, 0], [3, 1]],
|
|
220
|
+
[[0, 0], [0, 1], [1, 1], [2, 0], [3, 1]],
|
|
221
|
+
[[0, 0], [0, 1], [1, 0], [2, 1], [3, 0], [3, 1]],
|
|
222
|
+
[[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [3, 0], [3, 1]],
|
|
223
|
+
[[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1], [3, 0], [3, 1]]
|
|
224
|
+
];
|
|
225
|
+
const frames = [];
|
|
226
|
+
const sequence = [...stages, ...stages.slice().reverse().slice(1)];
|
|
227
|
+
for (const dots of sequence) {
|
|
228
|
+
const g = makeGrid(4, 2);
|
|
229
|
+
for (const [r, c] of dots) g[r][c] = true;
|
|
230
|
+
frames.push(gridToBraille(g));
|
|
231
|
+
}
|
|
232
|
+
return frames;
|
|
233
|
+
}
|
|
234
|
+
function genWaveRows() {
|
|
235
|
+
const W = 8, H = 4, totalFrames = 16, frames = [];
|
|
236
|
+
for (let f = 0; f < totalFrames; f++) {
|
|
237
|
+
const g = makeGrid(H, W);
|
|
238
|
+
for (let c = 0; c < W; c++) {
|
|
239
|
+
const phase = f - c * 0.5;
|
|
240
|
+
const row = Math.round((Math.sin(phase * 0.8) + 1) / 2 * (H - 1));
|
|
241
|
+
g[row][c] = true;
|
|
242
|
+
if (row > 0) g[row - 1][c] = (f + c) % 3 === 0;
|
|
243
|
+
}
|
|
244
|
+
frames.push(gridToBraille(g));
|
|
245
|
+
}
|
|
246
|
+
return frames;
|
|
247
|
+
}
|
|
248
|
+
function genCheckerboard() {
|
|
249
|
+
const W = 6, H = 4, frames = [];
|
|
250
|
+
for (let phase = 0; phase < 4; phase++) {
|
|
251
|
+
const g = makeGrid(H, W);
|
|
252
|
+
for (let r = 0; r < H; r++) {
|
|
253
|
+
for (let c = 0; c < W; c++) {
|
|
254
|
+
if (phase < 2) {
|
|
255
|
+
g[r][c] = (r + c + phase) % 2 === 0;
|
|
256
|
+
} else {
|
|
257
|
+
g[r][c] = (r + c + phase) % 3 === 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
frames.push(gridToBraille(g));
|
|
262
|
+
}
|
|
263
|
+
return frames;
|
|
264
|
+
}
|
|
265
|
+
function genHelix() {
|
|
266
|
+
const W = 8, H = 4, totalFrames = 16, frames = [];
|
|
267
|
+
for (let f = 0; f < totalFrames; f++) {
|
|
268
|
+
const g = makeGrid(H, W);
|
|
269
|
+
for (let c = 0; c < W; c++) {
|
|
270
|
+
const phase = (f + c) * (Math.PI / 4);
|
|
271
|
+
const y1 = Math.round((Math.sin(phase) + 1) / 2 * (H - 1));
|
|
272
|
+
const y2 = Math.round((Math.sin(phase + Math.PI) + 1) / 2 * (H - 1));
|
|
273
|
+
g[y1][c] = true;
|
|
274
|
+
g[y2][c] = true;
|
|
275
|
+
}
|
|
276
|
+
frames.push(gridToBraille(g));
|
|
277
|
+
}
|
|
278
|
+
return frames;
|
|
279
|
+
}
|
|
280
|
+
function genFillSweep() {
|
|
281
|
+
const W = 4, H = 4, frames = [];
|
|
282
|
+
for (let row = H - 1; row >= 0; row--) {
|
|
283
|
+
const g = makeGrid(H, W);
|
|
284
|
+
for (let r = row; r < H; r++) {
|
|
285
|
+
for (let c = 0; c < W; c++) g[r][c] = true;
|
|
286
|
+
}
|
|
287
|
+
frames.push(gridToBraille(g));
|
|
288
|
+
}
|
|
289
|
+
const full = makeGrid(H, W);
|
|
290
|
+
for (let r = 0; r < H; r++) for (let c = 0; c < W; c++) full[r][c] = true;
|
|
291
|
+
frames.push(gridToBraille(full));
|
|
292
|
+
frames.push(gridToBraille(full));
|
|
293
|
+
for (let row = 0; row < H; row++) {
|
|
294
|
+
const g = makeGrid(H, W);
|
|
295
|
+
for (let r = row + 1; r < H; r++) {
|
|
296
|
+
for (let c = 0; c < W; c++) g[r][c] = true;
|
|
297
|
+
}
|
|
298
|
+
frames.push(gridToBraille(g));
|
|
299
|
+
}
|
|
300
|
+
frames.push(gridToBraille(makeGrid(H, W)));
|
|
301
|
+
return frames;
|
|
302
|
+
}
|
|
303
|
+
function genDiagonalSwipe() {
|
|
304
|
+
const W = 4, H = 4, frames = [];
|
|
305
|
+
const maxDiag = W + H - 2;
|
|
306
|
+
for (let d = 0; d <= maxDiag; d++) {
|
|
307
|
+
const g = makeGrid(H, W);
|
|
308
|
+
for (let r = 0; r < H; r++) {
|
|
309
|
+
for (let c = 0; c < W; c++) {
|
|
310
|
+
if (r + c <= d) g[r][c] = true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
frames.push(gridToBraille(g));
|
|
314
|
+
}
|
|
315
|
+
const full = makeGrid(H, W);
|
|
316
|
+
for (let r = 0; r < H; r++) for (let c = 0; c < W; c++) full[r][c] = true;
|
|
317
|
+
frames.push(gridToBraille(full));
|
|
318
|
+
for (let d = 0; d <= maxDiag; d++) {
|
|
319
|
+
const g = makeGrid(H, W);
|
|
320
|
+
for (let r = 0; r < H; r++) {
|
|
321
|
+
for (let c = 0; c < W; c++) {
|
|
322
|
+
if (r + c > d) g[r][c] = true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
frames.push(gridToBraille(g));
|
|
326
|
+
}
|
|
327
|
+
frames.push(gridToBraille(makeGrid(H, W)));
|
|
328
|
+
return frames;
|
|
329
|
+
}
|
|
330
|
+
var spinners = {
|
|
331
|
+
// === Classic braille single-char ===
|
|
332
|
+
braille: {
|
|
333
|
+
frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
334
|
+
interval: 80
|
|
335
|
+
},
|
|
336
|
+
braillewave: {
|
|
337
|
+
frames: [
|
|
338
|
+
"\u2801\u2802\u2804\u2840",
|
|
339
|
+
"\u2802\u2804\u2840\u2880",
|
|
340
|
+
"\u2804\u2840\u2880\u2820",
|
|
341
|
+
"\u2840\u2880\u2820\u2810",
|
|
342
|
+
"\u2880\u2820\u2810\u2808",
|
|
343
|
+
"\u2820\u2810\u2808\u2801",
|
|
344
|
+
"\u2810\u2808\u2801\u2802",
|
|
345
|
+
"\u2808\u2801\u2802\u2804"
|
|
346
|
+
],
|
|
347
|
+
interval: 100
|
|
348
|
+
},
|
|
349
|
+
dna: {
|
|
350
|
+
frames: [
|
|
351
|
+
"\u280B\u2809\u2819\u281A",
|
|
352
|
+
"\u2809\u2819\u281A\u2812",
|
|
353
|
+
"\u2819\u281A\u2812\u2802",
|
|
354
|
+
"\u281A\u2812\u2802\u2802",
|
|
355
|
+
"\u2812\u2802\u2802\u2812",
|
|
356
|
+
"\u2802\u2802\u2812\u2832",
|
|
357
|
+
"\u2802\u2812\u2832\u2834",
|
|
358
|
+
"\u2812\u2832\u2834\u2824",
|
|
359
|
+
"\u2832\u2834\u2824\u2804",
|
|
360
|
+
"\u2834\u2824\u2804\u280B",
|
|
361
|
+
"\u2824\u2804\u280B\u2809",
|
|
362
|
+
"\u2804\u280B\u2809\u2819"
|
|
363
|
+
],
|
|
364
|
+
interval: 80
|
|
365
|
+
},
|
|
366
|
+
// === Generated braille grid animations ===
|
|
367
|
+
scan: { frames: genScan(), interval: 70 },
|
|
368
|
+
rain: { frames: genRain(), interval: 100 },
|
|
369
|
+
scanline: { frames: genScanLine(), interval: 120 },
|
|
370
|
+
pulse: { frames: genPulse(), interval: 180 },
|
|
371
|
+
snake: { frames: genSnake(), interval: 80 },
|
|
372
|
+
sparkle: { frames: genSparkle(), interval: 150 },
|
|
373
|
+
cascade: { frames: genCascade(), interval: 60 },
|
|
374
|
+
columns: { frames: genColumns(), interval: 60 },
|
|
375
|
+
orbit: { frames: genOrbit(), interval: 100 },
|
|
376
|
+
breathe: { frames: genBreathe(), interval: 100 },
|
|
377
|
+
waverows: { frames: genWaveRows(), interval: 90 },
|
|
378
|
+
checkerboard: { frames: genCheckerboard(), interval: 250 },
|
|
379
|
+
helix: { frames: genHelix(), interval: 80 },
|
|
380
|
+
fillsweep: { frames: genFillSweep(), interval: 100 },
|
|
381
|
+
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
382
|
+
};
|
|
383
|
+
var braille_default = spinners;
|
|
384
|
+
return __toCommonJS(braille_exports);
|
|
385
|
+
})();
|
package/dist/braille.js
CHANGED
|
@@ -29,6 +29,7 @@ function gridToBraille(grid) {
|
|
|
29
29
|
return result;
|
|
30
30
|
}
|
|
31
31
|
function makeGrid(rows, cols) {
|
|
32
|
+
if (rows <= 0 || cols <= 0) return [];
|
|
32
33
|
return Array.from({ length: rows }, () => Array(cols).fill(false));
|
|
33
34
|
}
|
|
34
35
|
function genScan() {
|
|
@@ -350,24 +351,7 @@ var spinners = {
|
|
|
350
351
|
checkerboard: { frames: genCheckerboard(), interval: 250 },
|
|
351
352
|
helix: { frames: genHelix(), interval: 80 },
|
|
352
353
|
fillsweep: { frames: genFillSweep(), interval: 100 },
|
|
353
|
-
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
354
|
-
// === Non-braille classics ===
|
|
355
|
-
arc: {
|
|
356
|
-
frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"],
|
|
357
|
-
interval: 100
|
|
358
|
-
},
|
|
359
|
-
halfmoon: {
|
|
360
|
-
frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"],
|
|
361
|
-
interval: 180
|
|
362
|
-
},
|
|
363
|
-
line: {
|
|
364
|
-
frames: ["|", "/", "\u2014", "\\"],
|
|
365
|
-
interval: 100
|
|
366
|
-
},
|
|
367
|
-
blocks: {
|
|
368
|
-
frames: ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588", "\u2587", "\u2586", "\u2585", "\u2584", "\u2583", "\u2582"],
|
|
369
|
-
interval: 100
|
|
370
|
-
}
|
|
354
|
+
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
371
355
|
};
|
|
372
356
|
var braille_default = spinners;
|
|
373
357
|
|
package/dist/index.cjs
CHANGED
|
@@ -58,6 +58,7 @@ function gridToBraille(grid) {
|
|
|
58
58
|
return result;
|
|
59
59
|
}
|
|
60
60
|
function makeGrid(rows, cols) {
|
|
61
|
+
if (rows <= 0 || cols <= 0) return [];
|
|
61
62
|
return Array.from({ length: rows }, () => Array(cols).fill(false));
|
|
62
63
|
}
|
|
63
64
|
function genScan() {
|
|
@@ -379,24 +380,7 @@ var spinners = {
|
|
|
379
380
|
checkerboard: { frames: genCheckerboard(), interval: 250 },
|
|
380
381
|
helix: { frames: genHelix(), interval: 80 },
|
|
381
382
|
fillsweep: { frames: genFillSweep(), interval: 100 },
|
|
382
|
-
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
383
|
-
// === Non-braille classics ===
|
|
384
|
-
arc: {
|
|
385
|
-
frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"],
|
|
386
|
-
interval: 100
|
|
387
|
-
},
|
|
388
|
-
halfmoon: {
|
|
389
|
-
frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"],
|
|
390
|
-
interval: 180
|
|
391
|
-
},
|
|
392
|
-
line: {
|
|
393
|
-
frames: ["|", "/", "\u2014", "\\"],
|
|
394
|
-
interval: 100
|
|
395
|
-
},
|
|
396
|
-
blocks: {
|
|
397
|
-
frames: ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588", "\u2587", "\u2586", "\u2585", "\u2584", "\u2583", "\u2582"],
|
|
398
|
-
interval: 100
|
|
399
|
-
}
|
|
383
|
+
diagswipe: { frames: genDiagonalSwipe(), interval: 60 }
|
|
400
384
|
};
|
|
401
385
|
var braille_default = spinners;
|
|
402
386
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unicode-animations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Unicode spinner animations as raw frame data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
-
"import": {
|
|
9
|
-
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/index.d.cts",
|
|
14
|
+
"default": "./dist/index.cjs"
|
|
15
|
+
}
|
|
10
16
|
},
|
|
11
17
|
"./braille": {
|
|
12
|
-
"import": {
|
|
13
|
-
|
|
18
|
+
"import": {
|
|
19
|
+
"types": "./dist/braille.d.ts",
|
|
20
|
+
"default": "./dist/braille.js"
|
|
21
|
+
},
|
|
22
|
+
"require": {
|
|
23
|
+
"types": "./dist/braille.d.cts",
|
|
24
|
+
"default": "./dist/braille.cjs"
|
|
25
|
+
}
|
|
14
26
|
}
|
|
15
27
|
},
|
|
16
28
|
"bin": {
|
|
@@ -19,10 +31,14 @@
|
|
|
19
31
|
"main": "./dist/index.cjs",
|
|
20
32
|
"module": "./dist/index.js",
|
|
21
33
|
"types": "./dist/index.d.ts",
|
|
22
|
-
"files": [
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"scripts"
|
|
37
|
+
],
|
|
23
38
|
"sideEffects": false,
|
|
24
39
|
"scripts": {
|
|
25
40
|
"build": "tsup",
|
|
41
|
+
"test": "vitest run",
|
|
26
42
|
"postinstall": "node scripts/postinstall.cjs",
|
|
27
43
|
"prepublishOnly": "npm run build"
|
|
28
44
|
},
|
|
@@ -31,10 +47,19 @@
|
|
|
31
47
|
"url": "git+https://github.com/gunnargray-dev/unicode-animations.git"
|
|
32
48
|
},
|
|
33
49
|
"homepage": "https://github.com/gunnargray-dev/unicode-animations",
|
|
34
|
-
"keywords": [
|
|
50
|
+
"keywords": [
|
|
51
|
+
"braille",
|
|
52
|
+
"spinner",
|
|
53
|
+
"unicode",
|
|
54
|
+
"animation",
|
|
55
|
+
"loading",
|
|
56
|
+
"cli",
|
|
57
|
+
"terminal"
|
|
58
|
+
],
|
|
35
59
|
"license": "MIT",
|
|
36
60
|
"devDependencies": {
|
|
37
61
|
"tsup": "^8.4.0",
|
|
38
|
-
"typescript": "^5.7.0"
|
|
62
|
+
"typescript": "^5.7.0",
|
|
63
|
+
"vitest": "^3.0.0"
|
|
39
64
|
}
|
|
40
65
|
}
|
package/scripts/demo.cjs
CHANGED
|
@@ -34,7 +34,7 @@ if (!out.isTTY) {
|
|
|
34
34
|
out = new tty.WriteStream(fd);
|
|
35
35
|
} catch {
|
|
36
36
|
// Fallback: no TTY available, just list and exit
|
|
37
|
-
console.log('
|
|
37
|
+
console.log('18 spinners: ' + names.join(', '));
|
|
38
38
|
process.exit(0);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -51,9 +51,23 @@ const cleanup = () => { try { out.write(show); } catch {} };
|
|
|
51
51
|
process.on('SIGINT', () => { cleanup(); out.write('\n'); process.exit(0); });
|
|
52
52
|
process.on('exit', cleanup);
|
|
53
53
|
|
|
54
|
+
// Enable raw mode so keypresses (q, Ctrl+C, Esc) are caught immediately
|
|
55
|
+
if (process.stdin.isTTY) {
|
|
56
|
+
process.stdin.setRawMode(true);
|
|
57
|
+
process.stdin.resume();
|
|
58
|
+
process.stdin.on('data', (key) => {
|
|
59
|
+
// q, Ctrl+C, or Escape
|
|
60
|
+
if (key[0] === 0x71 || key[0] === 0x03 || key[0] === 0x1B) {
|
|
61
|
+
cleanup();
|
|
62
|
+
out.write('\n');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
54
68
|
if (args[0] === '--list' || args[0] === '-l') {
|
|
55
69
|
cleanup();
|
|
56
|
-
out.write(`\n${bold}
|
|
70
|
+
out.write(`\n${bold}18 spinners available:${reset}\n\n`);
|
|
57
71
|
for (const name of names) {
|
|
58
72
|
const s = S[name];
|
|
59
73
|
out.write(` ${magenta}${s.frames[0]}${reset} ${name} ${dim}(${s.frames.length} frames, ${s.interval}ms)${reset}\n`);
|
package/scripts/demo.html
CHANGED
|
@@ -155,6 +155,59 @@
|
|
|
155
155
|
}
|
|
156
156
|
footer a { color: var(--accent); text-decoration: none; }
|
|
157
157
|
footer a:hover { text-decoration: underline; }
|
|
158
|
+
|
|
159
|
+
.prose {
|
|
160
|
+
font-size: 0.9rem;
|
|
161
|
+
line-height: 1.7;
|
|
162
|
+
color: var(--text-2);
|
|
163
|
+
margin-bottom: 1rem;
|
|
164
|
+
}
|
|
165
|
+
.prose code, .sub-label code, .ref-table code {
|
|
166
|
+
font-family: var(--mono);
|
|
167
|
+
font-size: 0.8rem;
|
|
168
|
+
background: var(--surface);
|
|
169
|
+
border: 1px solid var(--border);
|
|
170
|
+
border-radius: 4px;
|
|
171
|
+
padding: 0.15rem 0.4rem;
|
|
172
|
+
}
|
|
173
|
+
.ref-table code { font-size: 0.75rem; }
|
|
174
|
+
|
|
175
|
+
.doc-section {
|
|
176
|
+
margin-top: 3rem;
|
|
177
|
+
}
|
|
178
|
+
.doc-section .usage {
|
|
179
|
+
margin-top: 1rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.sub-label {
|
|
183
|
+
font-size: 0.8rem;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
color: var(--text);
|
|
186
|
+
margin: 1.5rem 0 0.75rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.ref-table {
|
|
190
|
+
width: 100%;
|
|
191
|
+
border-collapse: collapse;
|
|
192
|
+
font-size: 0.8rem;
|
|
193
|
+
margin: 1rem 0;
|
|
194
|
+
}
|
|
195
|
+
.ref-table th {
|
|
196
|
+
text-align: left;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
font-size: 0.7rem;
|
|
199
|
+
text-transform: uppercase;
|
|
200
|
+
letter-spacing: 0.08em;
|
|
201
|
+
color: var(--text-3);
|
|
202
|
+
padding: 0.5rem 0.75rem;
|
|
203
|
+
border-bottom: 1px solid var(--border);
|
|
204
|
+
}
|
|
205
|
+
.ref-table td {
|
|
206
|
+
padding: 0.5rem 0.75rem;
|
|
207
|
+
border-bottom: 1px solid var(--border);
|
|
208
|
+
color: var(--text-2);
|
|
209
|
+
}
|
|
210
|
+
.ref-table tr:last-child td { border-bottom: none; }
|
|
158
211
|
</style>
|
|
159
212
|
</head>
|
|
160
213
|
<body>
|
|
@@ -194,65 +247,214 @@
|
|
|
194
247
|
clearInterval(timer)
|
|
195
248
|
process.stdout.write(<span class="str">'\r✔ Done.\n'</span>)</pre>
|
|
196
249
|
</section>
|
|
250
|
+
|
|
251
|
+
<section class="doc-section">
|
|
252
|
+
<div class="section-label">Quick start</div>
|
|
253
|
+
<div class="usage">
|
|
254
|
+
<pre><span class="cm">// ESM</span>
|
|
255
|
+
<span class="kw">import</span> spinners <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
256
|
+
|
|
257
|
+
<span class="cm">// CJS</span>
|
|
258
|
+
<span class="kw">const</span> spinners = require(<span class="str">'unicode-animations'</span>)</pre>
|
|
259
|
+
</div>
|
|
260
|
+
<p class="prose">Each spinner is a <code>{ frames: string[], interval: number }</code> object.</p>
|
|
261
|
+
</section>
|
|
262
|
+
|
|
263
|
+
<section class="doc-section">
|
|
264
|
+
<div class="section-label">Examples</div>
|
|
265
|
+
|
|
266
|
+
<div class="sub-label">CLI tool — spinner during async work</div>
|
|
267
|
+
<div class="usage">
|
|
268
|
+
<pre><span class="kw">import</span> spinners <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
269
|
+
|
|
270
|
+
<span class="kw">const</span> { frames, interval } = spinners.braille
|
|
271
|
+
<span class="kw">let</span> i = 0
|
|
272
|
+
|
|
273
|
+
<span class="kw">const</span> spinner = setInterval(() => {
|
|
274
|
+
process.stdout.write(<span class="str">`\r\x1B[2K ${frames[i++ % frames.length]} Deploying...`</span>)
|
|
275
|
+
}, interval)
|
|
276
|
+
|
|
277
|
+
<span class="kw">await</span> deploy()
|
|
278
|
+
|
|
279
|
+
clearInterval(spinner)
|
|
280
|
+
process.stdout.write(<span class="str">'\r\x1B[2K ✔ Deployed.\n'</span>)</pre>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<div class="sub-label">Reusable spinner helper</div>
|
|
284
|
+
<div class="usage">
|
|
285
|
+
<pre><span class="kw">import</span> spinners <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
286
|
+
|
|
287
|
+
<span class="kw">function</span> createSpinner(msg, name = <span class="str">'braille'</span>) {
|
|
288
|
+
<span class="kw">const</span> { frames, interval } = spinners[name]
|
|
289
|
+
<span class="kw">let</span> i = 0, text = msg
|
|
290
|
+
<span class="kw">const</span> timer = setInterval(() => {
|
|
291
|
+
process.stdout.write(<span class="str">`\r\x1B[2K ${frames[i++ % frames.length]} ${text}`</span>)
|
|
292
|
+
}, interval)
|
|
293
|
+
|
|
294
|
+
<span class="kw">return</span> {
|
|
295
|
+
update(msg) { text = msg },
|
|
296
|
+
stop(msg) { clearInterval(timer); process.stdout.write(<span class="str">`\r\x1B[2K ✔ ${msg}\n`</span>) },
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
<span class="kw">const</span> s = createSpinner(<span class="str">'Connecting to database...'</span>)
|
|
301
|
+
<span class="kw">const</span> db = <span class="kw">await</span> connect()
|
|
302
|
+
s.update(<span class="str">`Running ${migrations.length} migrations...`</span>)
|
|
303
|
+
<span class="kw">await</span> db.migrate(migrations)
|
|
304
|
+
s.stop(<span class="str">'Database ready.'</span>)</pre>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div class="sub-label">Multi-step pipeline</div>
|
|
308
|
+
<div class="usage">
|
|
309
|
+
<pre><span class="kw">import</span> spinners <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
310
|
+
|
|
311
|
+
<span class="kw">async function</span> runWithSpinner(label, fn, name = <span class="str">'braille'</span>) {
|
|
312
|
+
<span class="kw">const</span> { frames, interval } = spinners[name]
|
|
313
|
+
<span class="kw">let</span> i = 0
|
|
314
|
+
<span class="kw">const</span> timer = setInterval(() => {
|
|
315
|
+
process.stdout.write(<span class="str">`\r\x1B[2K ${frames[i++ % frames.length]} ${label}`</span>)
|
|
316
|
+
}, interval)
|
|
317
|
+
<span class="kw">const</span> result = <span class="kw">await</span> fn()
|
|
318
|
+
clearInterval(timer)
|
|
319
|
+
process.stdout.write(<span class="str">`\r\x1B[2K ✔ ${label}\n`</span>)
|
|
320
|
+
<span class="kw">return</span> result
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
<span class="kw">await</span> runWithSpinner(<span class="str">'Linting...'</span>, lint, <span class="str">'scan'</span>)
|
|
324
|
+
<span class="kw">await</span> runWithSpinner(<span class="str">'Running tests...'</span>, test, <span class="str">'helix'</span>)
|
|
325
|
+
<span class="kw">await</span> runWithSpinner(<span class="str">'Building...'</span>, build, <span class="str">'cascade'</span>)
|
|
326
|
+
<span class="kw">await</span> runWithSpinner(<span class="str">'Publishing...'</span>, publish, <span class="str">'braille'</span>)</pre>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<div class="sub-label">React component</div>
|
|
330
|
+
<div class="usage">
|
|
331
|
+
<pre><span class="kw">import</span> { useState, useEffect } <span class="kw">from</span> <span class="str">'react'</span>
|
|
332
|
+
<span class="kw">import</span> spinners <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
333
|
+
|
|
334
|
+
<span class="kw">function</span> Spinner({ name = <span class="str">'braille'</span>, children }) {
|
|
335
|
+
<span class="kw">const</span> [frame, setFrame] = useState(0)
|
|
336
|
+
<span class="kw">const</span> s = spinners[name]
|
|
337
|
+
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
<span class="kw">const</span> timer = setInterval(
|
|
340
|
+
() => setFrame(f => (f + 1) % s.frames.length),
|
|
341
|
+
s.interval
|
|
342
|
+
)
|
|
343
|
+
<span class="kw">return</span> () => clearInterval(timer)
|
|
344
|
+
}, [name])
|
|
345
|
+
|
|
346
|
+
<span class="kw">return</span> <span style={{ fontFamily: <span class="str">'monospace'</span> }}>{s.frames[frame]} {children}</span>
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
<span class="cm">// Usage: <Spinner name="helix">Generating response...</Spinner></span></pre>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<div class="sub-label">Browser — status indicator</div>
|
|
353
|
+
<div class="usage">
|
|
354
|
+
<pre><span class="kw">import</span> spinners <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
355
|
+
|
|
356
|
+
<span class="kw">const</span> el = document.getElementById(<span class="str">'status'</span>)
|
|
357
|
+
<span class="kw">const</span> { frames, interval } = spinners.orbit
|
|
358
|
+
<span class="kw">let</span> i = 0
|
|
359
|
+
|
|
360
|
+
<span class="kw">const</span> spinner = setInterval(() => {
|
|
361
|
+
el.textContent = <span class="str">`${frames[i++ % frames.length]} Syncing...`</span>
|
|
362
|
+
}, interval)
|
|
363
|
+
|
|
364
|
+
<span class="kw">await</span> sync()
|
|
365
|
+
clearInterval(spinner)
|
|
366
|
+
el.textContent = <span class="str">'✔ Synced'</span></pre>
|
|
367
|
+
</div>
|
|
368
|
+
</section>
|
|
369
|
+
|
|
370
|
+
<section class="doc-section">
|
|
371
|
+
<div class="section-label">All spinners</div>
|
|
372
|
+
|
|
373
|
+
<div class="sub-label">Classic braille</div>
|
|
374
|
+
<table class="ref-table">
|
|
375
|
+
<thead><tr><th>Name</th><th>Preview</th><th>Interval</th></tr></thead>
|
|
376
|
+
<tbody>
|
|
377
|
+
<tr><td><code>braille</code></td><td><code>⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏</code></td><td>80ms</td></tr>
|
|
378
|
+
<tr><td><code>braillewave</code></td><td><code>⠁⠂⠄⡀</code> → <code>⠂⠄⡀⢀</code></td><td>100ms</td></tr>
|
|
379
|
+
<tr><td><code>dna</code></td><td><code>⠋⠉⠙⠚</code> → <code>⠉⠙⠚⠒</code></td><td>80ms</td></tr>
|
|
380
|
+
</tbody>
|
|
381
|
+
</table>
|
|
382
|
+
|
|
383
|
+
<div class="sub-label">Grid animations (braille)</div>
|
|
384
|
+
<table class="ref-table">
|
|
385
|
+
<thead><tr><th>Name</th><th>Frames</th><th>Interval</th></tr></thead>
|
|
386
|
+
<tbody>
|
|
387
|
+
<tr><td><code>scan</code></td><td>10</td><td>70ms</td></tr>
|
|
388
|
+
<tr><td><code>rain</code></td><td>12</td><td>100ms</td></tr>
|
|
389
|
+
<tr><td><code>scanline</code></td><td>6</td><td>120ms</td></tr>
|
|
390
|
+
<tr><td><code>pulse</code></td><td>5</td><td>180ms</td></tr>
|
|
391
|
+
<tr><td><code>snake</code></td><td>16</td><td>80ms</td></tr>
|
|
392
|
+
<tr><td><code>sparkle</code></td><td>6</td><td>150ms</td></tr>
|
|
393
|
+
<tr><td><code>cascade</code></td><td>12</td><td>60ms</td></tr>
|
|
394
|
+
<tr><td><code>columns</code></td><td>26</td><td>60ms</td></tr>
|
|
395
|
+
<tr><td><code>orbit</code></td><td>8</td><td>100ms</td></tr>
|
|
396
|
+
<tr><td><code>breathe</code></td><td>17</td><td>100ms</td></tr>
|
|
397
|
+
<tr><td><code>waverows</code></td><td>16</td><td>90ms</td></tr>
|
|
398
|
+
<tr><td><code>checkerboard</code></td><td>4</td><td>250ms</td></tr>
|
|
399
|
+
<tr><td><code>helix</code></td><td>16</td><td>80ms</td></tr>
|
|
400
|
+
<tr><td><code>fillsweep</code></td><td>11</td><td>100ms</td></tr>
|
|
401
|
+
<tr><td><code>diagswipe</code></td><td>16</td><td>60ms</td></tr>
|
|
402
|
+
</tbody>
|
|
403
|
+
</table>
|
|
404
|
+
</section>
|
|
405
|
+
|
|
406
|
+
<section class="doc-section">
|
|
407
|
+
<div class="section-label">Custom spinners</div>
|
|
408
|
+
<p class="prose">Create your own braille spinners using the grid utilities:</p>
|
|
409
|
+
<div class="usage">
|
|
410
|
+
<pre><span class="kw">import</span> { gridToBraille, makeGrid } <span class="kw">from</span> <span class="str">'unicode-animations'</span>
|
|
411
|
+
|
|
412
|
+
<span class="cm">// Create a 4-row × 4-col grid</span>
|
|
413
|
+
<span class="kw">const</span> grid = makeGrid(4, 4)
|
|
414
|
+
grid[0][0] = <span class="kw">true</span>
|
|
415
|
+
grid[1][1] = <span class="kw">true</span>
|
|
416
|
+
grid[2][2] = <span class="kw">true</span>
|
|
417
|
+
grid[3][3] = <span class="kw">true</span>
|
|
418
|
+
|
|
419
|
+
console.log(gridToBraille(grid)) <span class="cm">// diagonal braille pattern</span></pre>
|
|
420
|
+
</div>
|
|
421
|
+
<p class="prose"><code>makeGrid(rows, cols)</code> returns a <code>boolean[][]</code>. Set cells to <code>true</code> to raise dots. <code>gridToBraille(grid)</code> converts it to a braille string (2 dot-columns per character).</p>
|
|
422
|
+
</section>
|
|
423
|
+
|
|
424
|
+
<section class="doc-section">
|
|
425
|
+
<div class="section-label">API</div>
|
|
426
|
+
|
|
427
|
+
<div class="sub-label">Spinner</div>
|
|
428
|
+
<div class="usage">
|
|
429
|
+
<pre><span class="kw">interface</span> Spinner {
|
|
430
|
+
<span class="kw">readonly</span> frames: <span class="kw">readonly</span> string[]
|
|
431
|
+
<span class="kw">readonly</span> interval: number
|
|
432
|
+
}</pre>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
<div class="sub-label">Exports from <code>'unicode-animations'</code></div>
|
|
436
|
+
<table class="ref-table">
|
|
437
|
+
<thead><tr><th>Export</th><th>Type</th></tr></thead>
|
|
438
|
+
<tbody>
|
|
439
|
+
<tr><td><code>default</code> / <code>spinners</code></td><td><code>Record<BrailleSpinnerName, Spinner></code></td></tr>
|
|
440
|
+
<tr><td><code>gridToBraille(grid)</code></td><td><code>(boolean[][]) => string</code></td></tr>
|
|
441
|
+
<tr><td><code>makeGrid(rows, cols)</code></td><td><code>(number, number) => boolean[][]</code></td></tr>
|
|
442
|
+
<tr><td><code>Spinner</code></td><td>TypeScript interface</td></tr>
|
|
443
|
+
<tr><td><code>BrailleSpinnerName</code></td><td>Union type of all 18 spinner names</td></tr>
|
|
444
|
+
</tbody>
|
|
445
|
+
</table>
|
|
446
|
+
<p class="prose">The subpath <code>'unicode-animations/braille'</code> re-exports everything from the main entrypoint.</p>
|
|
447
|
+
</section>
|
|
197
448
|
</main>
|
|
198
449
|
|
|
199
450
|
<footer>
|
|
200
|
-
<a href="https://
|
|
451
|
+
made by <a href="https://x.com/gunnargray">Gunnar Gray</a>
|
|
201
452
|
· MIT License
|
|
202
453
|
</footer>
|
|
203
454
|
|
|
455
|
+
<script src="../dist/braille.global.js"></script>
|
|
204
456
|
<script>
|
|
205
|
-
|
|
206
|
-
const BRAILLE_DOT_MAP = [[0x01,0x08],[0x02,0x10],[0x04,0x20],[0x40,0x80]];
|
|
207
|
-
function gridToBraille(grid) {
|
|
208
|
-
const rows = grid.length, cols = grid[0]?.length || 0, cc = Math.ceil(cols / 2);
|
|
209
|
-
let r = '';
|
|
210
|
-
for (let c = 0; c < cc; c++) {
|
|
211
|
-
let code = 0x2800;
|
|
212
|
-
for (let ri = 0; ri < 4 && ri < rows; ri++)
|
|
213
|
-
for (let d = 0; d < 2; d++) { const col = c*2+d; if (col < cols && grid[ri]?.[col]) code |= BRAILLE_DOT_MAP[ri][d]; }
|
|
214
|
-
r += String.fromCodePoint(code);
|
|
215
|
-
}
|
|
216
|
-
return r;
|
|
217
|
-
}
|
|
218
|
-
function makeGrid(r,c) { return Array.from({length:r},()=>Array(c).fill(false)); }
|
|
219
|
-
|
|
220
|
-
function genScan(){const W=8,H=4,f=[];for(let p=-1;p<W+1;p++){const g=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)if(c===p||c===p-1)g[r][c]=true;f.push(gridToBraille(g))}return f}
|
|
221
|
-
function genRain(){const W=8,H=4,n=12,f=[],o=[0,3,1,5,2,7,4,6];for(let i=0;i<n;i++){const g=makeGrid(H,W);for(let c=0;c<W;c++){const r=(i+o[c])%(H+2);if(r<H)g[r][c]=true}f.push(gridToBraille(g))}return f}
|
|
222
|
-
function genScanLine(){const W=6,H=4,f=[],p=[0,1,2,3,2,1];for(const row of p){const g=makeGrid(H,W);for(let c=0;c<W;c++){g[row][c]=true;if(row>0)g[row-1][c]=(c%2===0)}f.push(gridToBraille(g))}return f}
|
|
223
|
-
function genPulse(){const W=6,H=4,f=[],cx=W/2-0.5,cy=H/2-0.5;for(const r of[0.5,1.2,2,3,3.5]){const g=makeGrid(H,W);for(let row=0;row<H;row++)for(let col=0;col<W;col++)if(Math.abs(Math.sqrt((col-cx)**2+(row-cy)**2)-r)<0.9)g[row][col]=true;f.push(gridToBraille(g))}return f}
|
|
224
|
-
function genSnake(){const W=4,H=4,path=[];for(let r=0;r<H;r++)if(r%2===0)for(let c=0;c<W;c++)path.push([r,c]);else for(let c=W-1;c>=0;c--)path.push([r,c]);const f=[];for(let i=0;i<path.length;i++){const g=makeGrid(H,W);for(let t=0;t<4;t++){const idx=(i-t+path.length)%path.length;g[path[idx][0]][path[idx][1]]=true}f.push(gridToBraille(g))}return f}
|
|
225
|
-
function genSparkle(){const ps=[[1,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0,1,1,0,0,0,0,1,0,0],[0,1,0,0,1,0,0,1,1,0,0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,0,1,0,1,0,1,0],[0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,1,0,1,0,0,0,0,1,0,1,0,1,0,0,0,1],[1,0,0,0,0,0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0],[0,0,0,1,1,0,0,0,0,1,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1],[0,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,1,0,0,0]];const W=8,H=4,f=[];for(const p of ps){const g=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)g[r][c]=!!p[r*W+c];f.push(gridToBraille(g))}return f}
|
|
226
|
-
function genCascade(){const W=8,H=4,f=[];for(let o=-2;o<W+H;o++){const g=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)if(c+r===o||c+r===o-1)g[r][c]=true;f.push(gridToBraille(g))}return f}
|
|
227
|
-
function genColumns(){const W=6,H=4,f=[];for(let col=0;col<W;col++)for(let ft=H-1;ft>=0;ft--){const g=makeGrid(H,W);for(let pc=0;pc<col;pc++)for(let r=0;r<H;r++)g[r][pc]=true;for(let r=ft;r<H;r++)g[r][col]=true;f.push(gridToBraille(g))}const full=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)full[r][c]=true;f.push(gridToBraille(full));f.push(gridToBraille(makeGrid(H,W)));return f}
|
|
228
|
-
function genOrbit(){const W=2,H=4,path=[[0,0],[0,1],[1,1],[2,1],[3,1],[3,0],[2,0],[1,0]],f=[];for(let i=0;i<path.length;i++){const g=makeGrid(H,W);g[path[i][0]][path[i][1]]=true;const t=(i-1+path.length)%path.length;g[path[t][0]][path[t][1]]=true;f.push(gridToBraille(g))}return f}
|
|
229
|
-
function genBreathe(){const stages=[[],[[1,0]],[[0,1],[2,0]],[[0,0],[1,1],[3,0]],[[0,0],[1,1],[2,0],[3,1]],[[0,0],[0,1],[1,1],[2,0],[3,1]],[[0,0],[0,1],[1,0],[2,1],[3,0],[3,1]],[[0,0],[0,1],[1,0],[1,1],[2,0],[3,0],[3,1]],[[0,0],[0,1],[1,0],[1,1],[2,0],[2,1],[3,0],[3,1]]];const seq=[...stages,...stages.slice().reverse().slice(1)],f=[];for(const dots of seq){const g=makeGrid(4,2);for(const[r,c]of dots)g[r][c]=true;f.push(gridToBraille(g))}return f}
|
|
230
|
-
function genWaveRows(){const W=8,H=4,n=16,f=[];for(let i=0;i<n;i++){const g=makeGrid(H,W);for(let c=0;c<W;c++){const row=Math.round((Math.sin((i-c*0.5)*0.8)+1)/2*(H-1));g[row][c]=true;if(row>0)g[row-1][c]=(i+c)%3===0}f.push(gridToBraille(g))}return f}
|
|
231
|
-
function genCheckerboard(){const W=6,H=4,f=[];for(let p=0;p<4;p++){const g=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)g[r][c]=p<2?(r+c+p)%2===0:(r+c+p)%3===0;f.push(gridToBraille(g))}return f}
|
|
232
|
-
function genHelix(){const W=8,H=4,n=16,f=[];for(let i=0;i<n;i++){const g=makeGrid(H,W);for(let c=0;c<W;c++){const ph=(i+c)*(Math.PI/4);g[Math.round((Math.sin(ph)+1)/2*(H-1))][c]=true;g[Math.round((Math.sin(ph+Math.PI)+1)/2*(H-1))][c]=true}f.push(gridToBraille(g))}return f}
|
|
233
|
-
function genFillSweep(){const W=4,H=4,f=[];for(let row=H-1;row>=0;row--){const g=makeGrid(H,W);for(let r=row;r<H;r++)for(let c=0;c<W;c++)g[r][c]=true;f.push(gridToBraille(g))}const full=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)full[r][c]=true;f.push(gridToBraille(full));f.push(gridToBraille(full));for(let row=0;row<H;row++){const g=makeGrid(H,W);for(let r=row+1;r<H;r++)for(let c=0;c<W;c++)g[r][c]=true;f.push(gridToBraille(g))}f.push(gridToBraille(makeGrid(H,W)));return f}
|
|
234
|
-
function genDiagSwipe(){const W=4,H=4,f=[],mx=W+H-2;for(let d=0;d<=mx;d++){const g=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)if(r+c<=d)g[r][c]=true;f.push(gridToBraille(g))}const full=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)full[r][c]=true;f.push(gridToBraille(full));for(let d=0;d<=mx;d++){const g=makeGrid(H,W);for(let r=0;r<H;r++)for(let c=0;c<W;c++)if(r+c>d)g[r][c]=true;f.push(gridToBraille(g))}f.push(gridToBraille(makeGrid(H,W)));return f}
|
|
235
|
-
|
|
236
|
-
const spinners = {
|
|
237
|
-
braille:{frames:['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'],interval:80},
|
|
238
|
-
braillewave:{frames:['⠁⠂⠄⡀','⠂⠄⡀⢀','⠄⡀⢀⠠','⡀⢀⠠⠐','⢀⠠⠐⠈','⠠⠐⠈⠁','⠐⠈⠁⠂','⠈⠁⠂⠄'],interval:100},
|
|
239
|
-
dna:{frames:['⠋⠉⠙⠚','⠉⠙⠚⠒','⠙⠚⠒⠂','⠚⠒⠂⠂','⠒⠂⠂⠒','⠂⠂⠒⠲','⠂⠒⠲⠴','⠒⠲⠴⠤','⠲⠴⠤⠄','⠴⠤⠄⠋','⠤⠄⠋⠉','⠄⠋⠉⠙'],interval:80},
|
|
240
|
-
scan:{frames:genScan(),interval:70},
|
|
241
|
-
rain:{frames:genRain(),interval:100},
|
|
242
|
-
scanline:{frames:genScanLine(),interval:120},
|
|
243
|
-
pulse:{frames:genPulse(),interval:180},
|
|
244
|
-
snake:{frames:genSnake(),interval:80},
|
|
245
|
-
sparkle:{frames:genSparkle(),interval:150},
|
|
246
|
-
cascade:{frames:genCascade(),interval:60},
|
|
247
|
-
columns:{frames:genColumns(),interval:60},
|
|
248
|
-
orbit:{frames:genOrbit(),interval:100},
|
|
249
|
-
breathe:{frames:genBreathe(),interval:100},
|
|
250
|
-
waverows:{frames:genWaveRows(),interval:90},
|
|
251
|
-
checkerboard:{frames:genCheckerboard(),interval:250},
|
|
252
|
-
helix:{frames:genHelix(),interval:80},
|
|
253
|
-
fillsweep:{frames:genFillSweep(),interval:100},
|
|
254
|
-
diagswipe:{frames:genDiagSwipe(),interval:60},
|
|
255
|
-
};
|
|
457
|
+
const spinners = UnicodeAnimations.spinners;
|
|
256
458
|
|
|
257
459
|
// Build grid
|
|
258
460
|
const grid = document.getElementById('spinnerGrid');
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -7,6 +7,9 @@ const path = require('path');
|
|
|
7
7
|
const ci = process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS;
|
|
8
8
|
if (ci) process.exit(0);
|
|
9
9
|
|
|
10
|
+
// Skip postinstall when run via npx (temporary install for CLI usage)
|
|
11
|
+
if (__dirname.includes('_npx')) process.exit(0);
|
|
12
|
+
|
|
10
13
|
let out;
|
|
11
14
|
try {
|
|
12
15
|
const fd = fs.openSync('/dev/tty', 'w');
|
|
@@ -28,82 +31,130 @@ try {
|
|
|
28
31
|
const DURATION = 3000;
|
|
29
32
|
const INTERVAL = 80;
|
|
30
33
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const show = '\x1B[?25h';
|
|
40
|
-
|
|
41
|
-
out.write(hide);
|
|
42
|
-
const cleanup = () => { try { out.write(show); } catch {} };
|
|
34
|
+
const B = '\x1B[1m';
|
|
35
|
+
const D = '\x1B[2m';
|
|
36
|
+
const R = '\x1B[0m';
|
|
37
|
+
const HIDE = '\x1B[?25l';
|
|
38
|
+
const SHOW = '\x1B[?25h';
|
|
39
|
+
|
|
40
|
+
out.write(HIDE);
|
|
41
|
+
const cleanup = () => { try { out.write(SHOW); } catch {} };
|
|
43
42
|
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
44
43
|
|
|
45
|
-
//
|
|
46
|
-
out.
|
|
47
|
-
|
|
48
|
-
${
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
${d} ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝${r}
|
|
53
|
-
${b}${c} b r a i l l e a n i m a t i o n s${r}
|
|
54
|
-
|
|
55
|
-
`);
|
|
56
|
-
|
|
57
|
-
// Braille spinners only, 2 columns of 9
|
|
58
|
-
const left = [
|
|
59
|
-
['Braille', S.braille],
|
|
60
|
-
['Orbit', S.orbit],
|
|
61
|
-
['Breathe', S.breathe],
|
|
62
|
-
['Snake', S.snake],
|
|
63
|
-
['Fill Sweep', S.fillsweep],
|
|
64
|
-
['Diag Swipe', S.diagswipe],
|
|
65
|
-
['Pulse', S.pulse],
|
|
66
|
-
['Scanline', S.scanline],
|
|
67
|
-
['Columns', S.columns],
|
|
68
|
-
];
|
|
44
|
+
// Narrow terminal fallback
|
|
45
|
+
const termCols = out.columns || 80;
|
|
46
|
+
if (termCols < 60) {
|
|
47
|
+
out.write(`\n ${B}unicode-animations${R} ${D}— 18 braille spinners${R}\n\n`);
|
|
48
|
+
cleanup();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
69
51
|
|
|
70
|
-
|
|
71
|
-
['Checkerboard', S.checkerboard],
|
|
72
|
-
['Scan', S.scan],
|
|
73
|
-
['Rain', S.rain],
|
|
74
|
-
['Sparkle', S.sparkle],
|
|
75
|
-
['Cascade', S.cascade],
|
|
76
|
-
['Wave Rows', S.waverows],
|
|
77
|
-
['Helix', S.helix],
|
|
78
|
-
['Braille Wave', S.braillewave],
|
|
79
|
-
['DNA', S.dna],
|
|
80
|
-
];
|
|
52
|
+
function pad(str, n) { return str + ' '.repeat(Math.max(0, n - str.length)); }
|
|
81
53
|
|
|
82
|
-
|
|
54
|
+
// ─── Title (box-drawing art) ───
|
|
55
|
+
const titleLines = [
|
|
56
|
+
'██╗ ██╗███╗ ██╗██╗ ██████╗ ██████╗ ██████╗ ███████╗',
|
|
57
|
+
'██║ ██║████╗ ██║██║██╔════╝██╔═══██╗██╔══██╗██╔════╝',
|
|
58
|
+
'██║ ██║██╔██╗ ██║██║██║ ██║ ██║██║ ██║█████╗ ',
|
|
59
|
+
'██║ ██║██║╚██╗██║██║██║ ██║ ██║██║ ██║██╔══╝ ',
|
|
60
|
+
'╚██████╔╝██║ ╚████║██║╚██████╗╚██████╔╝██████╔╝███████╗',
|
|
61
|
+
' ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝',
|
|
62
|
+
];
|
|
63
|
+
const titleW = 57;
|
|
64
|
+
|
|
65
|
+
// ─── Spinner grid: 3 cols × 6 rows ───
|
|
66
|
+
const layout = [
|
|
67
|
+
['braille', 'scan', 'rain'],
|
|
68
|
+
['orbit', 'pulse', 'sparkle'],
|
|
69
|
+
['breathe', 'cascade', 'waverows'],
|
|
70
|
+
['snake', 'columns', 'helix'],
|
|
71
|
+
['fillsweep', 'scanline', 'braillewave'],
|
|
72
|
+
['diagswipe', 'checkerboard', 'dna'],
|
|
73
|
+
];
|
|
74
|
+
const NPAD = 13;
|
|
75
|
+
|
|
76
|
+
// Compute max frame width per column for consistent spacing
|
|
77
|
+
const colFPad = [0, 1, 2].map(c => {
|
|
78
|
+
let max = 0;
|
|
79
|
+
for (const row of layout) {
|
|
80
|
+
const sp = S[row[c]];
|
|
81
|
+
for (const f of sp.frames) max = Math.max(max, [...f].length);
|
|
82
|
+
}
|
|
83
|
+
return max;
|
|
84
|
+
});
|
|
85
|
+
const GRID_W = colFPad.reduce((sum, fp) => sum + fp + 1 + NPAD, 0) + 4;
|
|
86
|
+
const CONTENT_W = Math.max(GRID_W, titleW) + 4;
|
|
87
|
+
|
|
88
|
+
// ─── Crop marks ───
|
|
89
|
+
const ARM = 1;
|
|
90
|
+
const inner = Math.max(0, CONTENT_W - 2 - ARM * 2);
|
|
91
|
+
const cropPad = ' ';
|
|
92
|
+
const topCrop = cropPad + '\u280F' + '\u2809'.repeat(ARM) + ' '.repeat(inner) + '\u2809'.repeat(ARM) + '\u28B9';
|
|
93
|
+
const botCrop = cropPad + '\u28C7' + '\u28C0'.repeat(ARM) + ' '.repeat(inner) + '\u28C0'.repeat(ARM) + '\u28F8';
|
|
94
|
+
|
|
95
|
+
// Center each element within the crop frame
|
|
96
|
+
function centerPad(w) {
|
|
97
|
+
return cropPad + ' '.repeat(Math.max(0, Math.floor((CONTENT_W - w) / 2)));
|
|
98
|
+
}
|
|
99
|
+
// Left-align all content to the same column, centered as a block within crops
|
|
100
|
+
const contentW = Math.max(GRID_W, titleW);
|
|
101
|
+
const contentPad = cropPad + ' '.repeat(Math.max(0, Math.floor((CONTENT_W - contentW) / 2)));
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
// ─── Render spinner grid ───
|
|
104
|
+
const ROWS = layout.length;
|
|
85
105
|
|
|
86
106
|
function renderGrid(tick) {
|
|
87
107
|
let buf = '';
|
|
88
|
-
for (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
for (const row of layout) {
|
|
109
|
+
let line = contentPad;
|
|
110
|
+
for (let c = 0; c < 3; c++) {
|
|
111
|
+
const name = row[c];
|
|
112
|
+
const sp = S[name];
|
|
113
|
+
const frame = sp.frames[tick % sp.frames.length];
|
|
114
|
+
line += B + pad(frame, colFPad[c]) + R + ' ' + D + pad(name, NPAD) + R;
|
|
115
|
+
if (c < 2) line += ' ';
|
|
116
|
+
}
|
|
117
|
+
buf += line + '\n';
|
|
94
118
|
}
|
|
95
119
|
return buf;
|
|
96
120
|
}
|
|
97
121
|
|
|
122
|
+
// ─── Print static top ───
|
|
123
|
+
let top = '\n';
|
|
124
|
+
top += topCrop + '\n';
|
|
125
|
+
top += '\n';
|
|
126
|
+
for (let i = 0; i < titleLines.length; i++) {
|
|
127
|
+
const style = i === titleLines.length - 1 ? D : B;
|
|
128
|
+
top += contentPad + style + titleLines[i] + R + '\n';
|
|
129
|
+
}
|
|
130
|
+
top += contentPad + D + 'BRAILLE ANIMATIONS' + R + '\n';
|
|
131
|
+
top += '\n';
|
|
132
|
+
out.write(top);
|
|
133
|
+
|
|
134
|
+
// ─── Print first frame of spinners ───
|
|
98
135
|
out.write(renderGrid(0));
|
|
99
136
|
|
|
137
|
+
// ─── Animate ───
|
|
100
138
|
let tick = 1;
|
|
101
139
|
const start = Date.now();
|
|
102
140
|
|
|
103
141
|
const timer = setInterval(() => {
|
|
104
142
|
if (Date.now() - start >= DURATION) {
|
|
105
143
|
clearInterval(timer);
|
|
106
|
-
|
|
144
|
+
// Print static bottom
|
|
145
|
+
let bot = '\n';
|
|
146
|
+
const cmds = [
|
|
147
|
+
['npx unicode-animations', 'demo all spinners'],
|
|
148
|
+
['npx unicode-animations --list', 'list all spinners'],
|
|
149
|
+
['npx unicode-animations --web', 'open in browser'],
|
|
150
|
+
];
|
|
151
|
+
for (const [left, right] of cmds) {
|
|
152
|
+
const gap = ' '.repeat(Math.max(2, contentW - left.length - right.length));
|
|
153
|
+
bot += contentPad + D + left + R + gap + D + right + R + '\n';
|
|
154
|
+
}
|
|
155
|
+
bot += '\n';
|
|
156
|
+
bot += botCrop + '\n\n';
|
|
157
|
+
out.write(bot);
|
|
107
158
|
cleanup();
|
|
108
159
|
return;
|
|
109
160
|
}
|
|
@@ -111,6 +162,7 @@ ${b}${c} b r a i l l e a n i m a t i o n s${r}
|
|
|
111
162
|
out.write(renderGrid(tick));
|
|
112
163
|
tick++;
|
|
113
164
|
}, INTERVAL);
|
|
165
|
+
|
|
114
166
|
} catch {
|
|
115
167
|
try { out.write('\x1B[?25h'); } catch {}
|
|
116
168
|
process.exit(0);
|