@sarmal/core 0.26.0 → 0.27.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.
Files changed (79) hide show
  1. package/dist/auto-init.cjs +145 -106
  2. package/dist/auto-init.js +144 -105
  3. package/dist/cli.js +1048 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/curves/artemis2.cjs +16 -10
  6. package/dist/curves/artemis2.d.cts +1 -1
  7. package/dist/curves/artemis2.d.ts +1 -1
  8. package/dist/curves/artemis2.js +15 -9
  9. package/dist/curves/astroid.cjs +4 -4
  10. package/dist/curves/astroid.d.cts +1 -1
  11. package/dist/curves/astroid.d.ts +1 -1
  12. package/dist/curves/astroid.js +3 -3
  13. package/dist/curves/deltoid.cjs +4 -4
  14. package/dist/curves/deltoid.d.cts +1 -1
  15. package/dist/curves/deltoid.d.ts +1 -1
  16. package/dist/curves/deltoid.js +3 -3
  17. package/dist/curves/epicycloid3.cjs +4 -4
  18. package/dist/curves/epicycloid3.d.cts +1 -1
  19. package/dist/curves/epicycloid3.d.ts +1 -1
  20. package/dist/curves/epicycloid3.js +3 -3
  21. package/dist/curves/epitrochoid7.cjs +5 -5
  22. package/dist/curves/epitrochoid7.d.cts +1 -1
  23. package/dist/curves/epitrochoid7.d.ts +1 -1
  24. package/dist/curves/epitrochoid7.js +4 -4
  25. package/dist/curves/index.cjs +59 -43
  26. package/dist/curves/index.d.cts +29 -29
  27. package/dist/curves/index.d.ts +29 -29
  28. package/dist/curves/index.js +75 -43
  29. package/dist/curves/lame.cjs +6 -5
  30. package/dist/curves/lame.d.cts +1 -1
  31. package/dist/curves/lame.d.ts +1 -1
  32. package/dist/curves/lame.js +5 -4
  33. package/dist/curves/lissajous32.cjs +4 -4
  34. package/dist/curves/lissajous32.d.cts +1 -1
  35. package/dist/curves/lissajous32.d.ts +1 -1
  36. package/dist/curves/lissajous32.js +3 -3
  37. package/dist/curves/lissajous43.cjs +4 -4
  38. package/dist/curves/lissajous43.d.cts +1 -1
  39. package/dist/curves/lissajous43.d.ts +1 -1
  40. package/dist/curves/lissajous43.js +3 -3
  41. package/dist/curves/rose3.cjs +4 -4
  42. package/dist/curves/rose3.d.cts +1 -1
  43. package/dist/curves/rose3.d.ts +1 -1
  44. package/dist/curves/rose3.js +3 -3
  45. package/dist/curves/rose5.cjs +4 -4
  46. package/dist/curves/rose5.d.cts +1 -1
  47. package/dist/curves/rose5.d.ts +1 -1
  48. package/dist/curves/rose5.js +3 -3
  49. package/dist/curves/rose52.cjs +5 -5
  50. package/dist/curves/rose52.d.cts +1 -1
  51. package/dist/curves/rose52.d.ts +1 -1
  52. package/dist/curves/rose52.js +4 -4
  53. package/dist/curves/star.cjs +8 -5
  54. package/dist/curves/star.d.cts +1 -1
  55. package/dist/curves/star.d.ts +1 -1
  56. package/dist/curves/star.js +7 -4
  57. package/dist/curves/star4.cjs +8 -5
  58. package/dist/curves/star4.d.cts +1 -1
  59. package/dist/curves/star4.d.ts +1 -1
  60. package/dist/curves/star4.js +7 -4
  61. package/dist/curves/star7.cjs +8 -5
  62. package/dist/curves/star7.d.cts +1 -1
  63. package/dist/curves/star7.d.ts +1 -1
  64. package/dist/curves/star7.js +7 -4
  65. package/dist/index.cjs +131 -94
  66. package/dist/index.d.cts +78 -58
  67. package/dist/index.d.ts +78 -58
  68. package/dist/index.js +152 -94
  69. package/dist/renderer-shared-OR--cv-t.d.ts +49 -0
  70. package/dist/renderer-shared-jqw_Q1WO.d.cts +49 -0
  71. package/dist/terminal.cjs +593 -0
  72. package/dist/terminal.cjs.map +1 -0
  73. package/dist/terminal.d.cts +44 -0
  74. package/dist/terminal.d.ts +44 -0
  75. package/dist/terminal.js +585 -0
  76. package/dist/terminal.js.map +1 -0
  77. package/dist/types-zbxUgcmZ.d.cts +280 -266
  78. package/dist/types-zbxUgcmZ.d.ts +280 -266
  79. package/package.json +11 -1
@@ -0,0 +1,585 @@
1
+ // src/engine.ts
2
+ var TWO_PI = Math.PI * 2;
3
+ var POINTS_PER_PERIOD_UNIT = 50;
4
+ function lerp(start, end, t) {
5
+ return start + (end - start) * t;
6
+ }
7
+ var EMPTY_PARAMS = {};
8
+ var CircularBuffer = class {
9
+ constructor(capacity) {
10
+ this.head = 0;
11
+ this.count = 0;
12
+ this.capacity = capacity;
13
+ this.data = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
14
+ this.result = Array.from({ length: capacity }, () => ({ x: 0, y: 0 }));
15
+ }
16
+ /** Mutates in-place */
17
+ push(x, y) {
18
+ const slot = this.data[this.head];
19
+ slot.x = x;
20
+ slot.y = y;
21
+ this.head = (this.head + 1) % this.capacity;
22
+ if (this.count < this.capacity) {
23
+ this.count++;
24
+ }
25
+ }
26
+ /**
27
+ * Copies ordered points into the pre-allocated result buffer and returns it
28
+ * Note: The *same* array reference is returned every call,
29
+ * so `result.length` is also always `capacity`
30
+ */
31
+ toArray() {
32
+ const start = this.count < this.capacity ? 0 : this.head;
33
+ for (let i = 0; i < this.count; i++) {
34
+ const src = this.data[(start + i) % this.capacity];
35
+ const dst = this.result[i];
36
+ dst.x = src.x;
37
+ dst.y = src.y;
38
+ }
39
+ return this.result;
40
+ }
41
+ clear() {
42
+ this.head = 0;
43
+ this.count = 0;
44
+ }
45
+ get length() {
46
+ return this.count;
47
+ }
48
+ };
49
+ function resolveCurve(curveDef) {
50
+ const period = curveDef.period ?? TWO_PI;
51
+ if (!Number.isFinite(period) || period <= 0) {
52
+ throw new RangeError(`[sarmal] period must be a positive finite number, got ${period}`);
53
+ }
54
+ const speed = curveDef.speed ?? 1;
55
+ if (!Number.isFinite(speed)) {
56
+ throw new RangeError(`[sarmal] speed must be a finite number, got ${speed}`);
57
+ }
58
+ return {
59
+ name: curveDef.name,
60
+ fn: curveDef.fn,
61
+ period,
62
+ speed,
63
+ skeleton: curveDef.skeleton,
64
+ skeletonFn: curveDef.skeletonFn,
65
+ };
66
+ }
67
+ function createEngine(curveDef, trailLength = 120) {
68
+ if (!Number.isFinite(trailLength) || trailLength <= 0) {
69
+ throw new RangeError(
70
+ `[sarmal] trailLength must be a positive finite number, got ${trailLength}`,
71
+ );
72
+ }
73
+ let curve = resolveCurve(curveDef);
74
+ const trail = new CircularBuffer(trailLength);
75
+ let phase = 0;
76
+ let actualTime = 0;
77
+ let userSpeedOverride = null;
78
+ let morphCurveB = null;
79
+ let _morphAlpha = null;
80
+ let _morphStrategy = "normalized";
81
+ let _speedTransition = null;
82
+ function sampleSkeleton(c, samplePhase) {
83
+ if (c.skeletonFn) {
84
+ return c.skeletonFn(samplePhase);
85
+ }
86
+ if (c.skeleton === "live") {
87
+ return c.fn(samplePhase, actualTime, EMPTY_PARAMS);
88
+ }
89
+ return c.fn(samplePhase, 0, EMPTY_PARAMS);
90
+ }
91
+ return {
92
+ tick(deltaTime) {
93
+ if (_speedTransition !== null) {
94
+ _speedTransition.elapsed += deltaTime * 1e3;
95
+ const alpha = Math.min(_speedTransition.elapsed / _speedTransition.duration, 1);
96
+ userSpeedOverride = lerp(_speedTransition.from, _speedTransition.to, alpha);
97
+ if (alpha >= 1) {
98
+ userSpeedOverride = _speedTransition.to;
99
+ _speedTransition.resolve();
100
+ _speedTransition = null;
101
+ }
102
+ }
103
+ let effectiveSpeed = userSpeedOverride ?? curve.speed;
104
+ if (morphCurveB !== null && _morphAlpha !== null) {
105
+ effectiveSpeed = lerp(effectiveSpeed, morphCurveB.speed, _morphAlpha);
106
+ }
107
+ phase = (phase + effectiveSpeed * deltaTime) % curve.period;
108
+ actualTime += deltaTime;
109
+ if (morphCurveB !== null && _morphAlpha !== null) {
110
+ const a = curve.fn(phase, actualTime, EMPTY_PARAMS);
111
+ const phaseB =
112
+ _morphStrategy === "normalized" ? (phase / curve.period) * morphCurveB.period : phase;
113
+ const b = morphCurveB.fn(phaseB, actualTime, EMPTY_PARAMS);
114
+ trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
115
+ } else {
116
+ const point = curve.fn(phase, actualTime, EMPTY_PARAMS);
117
+ trail.push(point.x, point.y);
118
+ }
119
+ return trail.toArray();
120
+ },
121
+ get trailCount() {
122
+ return trail.length;
123
+ },
124
+ get trailLength() {
125
+ return trailLength;
126
+ },
127
+ get isLiveSkeleton() {
128
+ return curve.skeleton === "live";
129
+ },
130
+ get morphAlpha() {
131
+ return _morphAlpha;
132
+ },
133
+ reset() {
134
+ phase = 0;
135
+ actualTime = 0;
136
+ trail.clear();
137
+ },
138
+ jump(newPhase, { clearTrail = false } = {}) {
139
+ phase = ((newPhase % curve.period) + curve.period) % curve.period;
140
+ if (clearTrail) {
141
+ trail.clear();
142
+ }
143
+ },
144
+ seek(targetPhase, { wrap = false, step = curve.period / trailLength } = {}) {
145
+ const advance = curve.speed * step;
146
+ const target = ((targetPhase % curve.period) + curve.period) % curve.period;
147
+ const targetTime = target / curve.speed;
148
+ phase = target;
149
+ actualTime = targetTime;
150
+ trail.clear();
151
+ const pointsFromStart = Math.floor(target / advance) + 1;
152
+ const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
153
+ for (let i = count - 1; i >= 0; i--) {
154
+ const samplePhase = target - i * advance;
155
+ const wrappedPhase = ((samplePhase % curve.period) + curve.period) % curve.period;
156
+ const elapsed = targetTime - i * step;
157
+ const point = curve.fn(wrappedPhase, elapsed, EMPTY_PARAMS);
158
+ trail.push(point.x, point.y);
159
+ }
160
+ },
161
+ startMorph(target, strategy = "normalized") {
162
+ const resolvedTarget = resolveCurve(target);
163
+ if (morphCurveB !== null && _morphAlpha !== null) {
164
+ const frozenAlpha = _morphAlpha;
165
+ const frozenA = curve;
166
+ const frozenB = morphCurveB;
167
+ const frozenStrategy = _morphStrategy;
168
+ curve = {
169
+ ...frozenB,
170
+ fn: (samplePhase, elapsed, params) => {
171
+ const a = frozenA.fn(samplePhase, elapsed, params);
172
+ const phaseB =
173
+ frozenStrategy === "normalized"
174
+ ? (samplePhase / frozenA.period) * frozenB.period
175
+ : samplePhase;
176
+ const b = frozenB.fn(phaseB, elapsed, params);
177
+ return {
178
+ x: a.x + (b.x - a.x) * frozenAlpha,
179
+ y: a.y + (b.y - a.y) * frozenAlpha,
180
+ };
181
+ },
182
+ };
183
+ }
184
+ _morphStrategy = strategy;
185
+ morphCurveB = resolvedTarget;
186
+ _morphAlpha = 0;
187
+ },
188
+ setMorphAlpha(alpha) {
189
+ _morphAlpha = alpha;
190
+ },
191
+ completeMorph() {
192
+ if (morphCurveB !== null) {
193
+ if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
194
+ phase = (phase / curve.period) * morphCurveB.period;
195
+ }
196
+ curve = morphCurveB;
197
+ }
198
+ morphCurveB = null;
199
+ _morphAlpha = null;
200
+ },
201
+ getSarmalSkeleton() {
202
+ const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
203
+ const points = new Array(steps);
204
+ if (morphCurveB !== null && _morphAlpha !== null) {
205
+ for (let i = 0; i < steps; i++) {
206
+ const samplePhase = (i / (steps - 1)) * curve.period;
207
+ const a = sampleSkeleton(curve, samplePhase);
208
+ const phaseB =
209
+ _morphStrategy === "normalized"
210
+ ? (samplePhase / curve.period) * morphCurveB.period
211
+ : samplePhase;
212
+ const b = sampleSkeleton(morphCurveB, phaseB);
213
+ points[i] = {
214
+ x: a.x + (b.x - a.x) * _morphAlpha,
215
+ y: a.y + (b.y - a.y) * _morphAlpha,
216
+ };
217
+ }
218
+ return points;
219
+ }
220
+ for (let i = 0; i < steps; i++) {
221
+ const samplePhase = (i / (steps - 1)) * curve.period;
222
+ points[i] = sampleSkeleton(curve, samplePhase);
223
+ }
224
+ return points;
225
+ },
226
+ setSpeed(speed) {
227
+ if (!Number.isFinite(speed)) {
228
+ throw new Error("speed must be a finite number");
229
+ }
230
+ if (_speedTransition !== null) {
231
+ _speedTransition.reject(new Error("Speed transition cancelled"));
232
+ _speedTransition = null;
233
+ }
234
+ userSpeedOverride = speed;
235
+ },
236
+ getSpeed() {
237
+ return userSpeedOverride ?? curve.speed;
238
+ },
239
+ resetSpeed() {
240
+ userSpeedOverride = null;
241
+ },
242
+ setSpeedOver(speed, duration) {
243
+ if (!Number.isFinite(speed)) {
244
+ throw new Error("speed must be a finite number");
245
+ }
246
+ if (!Number.isFinite(duration) || duration <= 0) {
247
+ throw new Error("duration must be a finite number greater than 0");
248
+ }
249
+ if (_speedTransition !== null) {
250
+ _speedTransition.reject(new Error("Speed transition cancelled"));
251
+ _speedTransition = null;
252
+ }
253
+ const from = userSpeedOverride ?? curve.speed;
254
+ return new Promise((resolve, reject) => {
255
+ _speedTransition = { from, to: speed, elapsed: 0, duration, resolve, reject };
256
+ });
257
+ },
258
+ cancelSpeedTransition() {
259
+ if (_speedTransition !== null) {
260
+ _speedTransition.reject(new Error("Speed transition cancelled"));
261
+ _speedTransition = null;
262
+ }
263
+ },
264
+ };
265
+ }
266
+
267
+ // src/renderer-shared.ts
268
+ var FIT_PADDING = 0.1;
269
+ var FIT_PADDING_MIN = 4;
270
+ function computeBoundaries(pts, logicalWidth, logicalHeight, minPaddingPx = FIT_PADDING_MIN) {
271
+ if (pts.length === 0) return null;
272
+ const first = pts[0];
273
+ let minX = first.x,
274
+ maxX = first.x,
275
+ minY = first.y,
276
+ maxY = first.y;
277
+ for (const p of pts) {
278
+ if (p.x < minX) {
279
+ minX = p.x;
280
+ }
281
+ if (p.x > maxX) {
282
+ maxX = p.x;
283
+ }
284
+ if (p.y < minY) {
285
+ minY = p.y;
286
+ }
287
+ if (p.y > maxY) {
288
+ maxY = p.y;
289
+ }
290
+ }
291
+ const w = maxX - minX;
292
+ const h = maxY - minY;
293
+ if (w === 0 && h === 0) {
294
+ throw new Error(
295
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
296
+ );
297
+ }
298
+ const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
299
+ const scaleYProportional = logicalHeight / (h * (1 + FIT_PADDING * 2));
300
+ const scaleXMinPadding = (logicalWidth - minPaddingPx * 2) / w;
301
+ const scaleYMinPadding = (logicalHeight - minPaddingPx * 2) / h;
302
+ const scale = Math.min(
303
+ scaleXProportional,
304
+ scaleYProportional,
305
+ scaleXMinPadding,
306
+ scaleYMinPadding,
307
+ );
308
+ return {
309
+ scale,
310
+ offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
311
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
312
+ };
313
+ }
314
+ function hexToRgb(hex) {
315
+ const n = parseInt(hex.slice(1), 16);
316
+ return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
317
+ }
318
+
319
+ // src/terminal.ts
320
+ var DEFAULT_TRAIL_HEX = "#ec5571";
321
+ var DEFAULT_FPS = 30;
322
+ var DEFAULT_SIZE = 16;
323
+ var BRIGHTNESS_HEAD = 1;
324
+ var BRIGHTNESS_TAIL = 0.15;
325
+ var BRAILLE_BASE = 10240;
326
+ var BRAILLE_BIT = [
327
+ [1, 8],
328
+ [2, 16],
329
+ [4, 32],
330
+ [64, 128],
331
+ ];
332
+ function brailleChar(bits) {
333
+ return String.fromCodePoint(BRAILLE_BASE + (bits & 255));
334
+ }
335
+ function brailleBit(row, col) {
336
+ return BRAILLE_BIT[row][col];
337
+ }
338
+ function dotToCell(dotCol, dotRow) {
339
+ return {
340
+ charCol: Math.floor(dotCol / 2),
341
+ charRow: Math.floor(dotRow / 4),
342
+ dotColInCell: dotCol % 2,
343
+ dotRowInCell: dotRow % 4,
344
+ };
345
+ }
346
+ function detectColor() {
347
+ const colorterm = (process.env.COLORTERM ?? "").toLowerCase();
348
+ if (colorterm === "truecolor" || colorterm === "24bit") {
349
+ return "truecolor";
350
+ }
351
+ const term = (process.env.TERM ?? "").toLowerCase();
352
+ if (term.includes("256color")) {
353
+ return "256-color";
354
+ }
355
+ if (term.includes("truecolor") || term.includes("24bit")) {
356
+ return "truecolor";
357
+ }
358
+ if (colorterm !== "") {
359
+ return "256-color";
360
+ }
361
+ if (term !== "" && term !== "linux" && term !== "dumb") {
362
+ return "256-color";
363
+ }
364
+ return "monochrome";
365
+ }
366
+ function rgbTo256(r, g, b) {
367
+ const avg = Math.round((r + g + b) / 3);
368
+ if (Math.abs(r - avg) <= 4 && Math.abs(g - avg) <= 4 && Math.abs(b - avg) <= 4) {
369
+ if (avg <= 8) {
370
+ return 16;
371
+ }
372
+ return 232 + Math.min(23, Math.round((avg - 8) / 10));
373
+ }
374
+ const ri = Math.round((r / 255) * 5);
375
+ const gi = Math.round((g / 255) * 5);
376
+ const bi = Math.round((b / 255) * 5);
377
+ return 16 + 36 * ri + 6 * gi + bi;
378
+ }
379
+ function dimColor(hex, brightness) {
380
+ const c = hexToRgb(hex);
381
+ return {
382
+ r: Math.round(c.r * brightness),
383
+ g: Math.round(c.g * brightness),
384
+ b: Math.round(c.b * brightness),
385
+ };
386
+ }
387
+ var AR = "\x1B[0m";
388
+ function ansiTruecolorFg(r, g, b) {
389
+ return `\x1B[38;2;${r};${g};${b}m`;
390
+ }
391
+ function ansi256Fg(code) {
392
+ return `\x1B[38;5;${code}m`;
393
+ }
394
+ function ansiColor(r, g, b, colorCap) {
395
+ if (colorCap === "truecolor") {
396
+ return ansiTruecolorFg(r, g, b);
397
+ }
398
+ if (colorCap === "256-color") {
399
+ return ansi256Fg(rgbTo256(r, g, b));
400
+ }
401
+ return "";
402
+ }
403
+ function snapCol(col, max) {
404
+ return Math.max(0, Math.min(max - 1, Math.round(col)));
405
+ }
406
+ function applyBoundary(x, y, scale, offsetX, offsetY) {
407
+ return {
408
+ screenX: x * scale + offsetX,
409
+ screenY: y * scale + offsetY,
410
+ };
411
+ }
412
+ function renderFrame(
413
+ trail,
414
+ trailCount,
415
+ charWidth,
416
+ charHeight,
417
+ scale,
418
+ offsetX,
419
+ offsetY,
420
+ trailRgb,
421
+ headRgb,
422
+ colorCap,
423
+ ) {
424
+ const dotWidth = charWidth * 2;
425
+ const dotHeight = charHeight * 4;
426
+ const grid = Array.from({ length: charHeight }, () =>
427
+ Array.from({ length: charWidth }, () => ({ bits: 0, brightness: 0, isHead: false })),
428
+ );
429
+ for (let i = 0; i < trailCount; i++) {
430
+ const pt = trail[i];
431
+ const t = trailCount > 1 ? i / (trailCount - 1) : 1;
432
+ const brightness = BRIGHTNESS_TAIL + (BRIGHTNESS_HEAD - BRIGHTNESS_TAIL) * t;
433
+ const { screenX, screenY } = applyBoundary(pt.x, pt.y, scale, offsetX, offsetY);
434
+ const dotCol = snapCol(screenX, dotWidth);
435
+ const dotRow = snapCol(screenY, dotHeight);
436
+ const cell = dotToCell(dotCol, dotRow);
437
+ if (
438
+ cell.charRow < 0 ||
439
+ cell.charRow >= charHeight ||
440
+ cell.charCol < 0 ||
441
+ cell.charCol >= charWidth
442
+ ) {
443
+ continue;
444
+ }
445
+ const c = grid[cell.charRow][cell.charCol];
446
+ c.bits |= BRAILLE_BIT[cell.dotRowInCell][cell.dotColInCell];
447
+ if (brightness > c.brightness) {
448
+ c.brightness = brightness;
449
+ }
450
+ if (i === trailCount - 1) {
451
+ c.isHead = true;
452
+ }
453
+ }
454
+ if (colorCap !== "monochrome") {
455
+ return renderColorFrame(grid, charWidth, charHeight, trailRgb, headRgb, colorCap);
456
+ }
457
+ return renderMonochromeFrame(grid, charWidth, charHeight);
458
+ }
459
+ function renderColorFrame(grid, charWidth, charHeight, trailRgb, headRgb, colorCap) {
460
+ const lines = [];
461
+ for (let row = 0; row < charHeight; row++) {
462
+ let line = "";
463
+ for (let col = 0; col < charWidth; col++) {
464
+ const cell = grid[row][col];
465
+ if (cell.bits === 0) {
466
+ line += " ";
467
+ continue;
468
+ }
469
+ const ch = brailleChar(cell.bits);
470
+ if (cell.isHead) {
471
+ line += ansiColor(headRgb.r, headRgb.g, headRgb.b, colorCap) + ch + AR;
472
+ } else {
473
+ const r = Math.round(trailRgb.r * cell.brightness);
474
+ const g = Math.round(trailRgb.g * cell.brightness);
475
+ const b = Math.round(trailRgb.b * cell.brightness);
476
+ line += ansiColor(r, g, b, colorCap) + ch + AR;
477
+ }
478
+ }
479
+ lines.push(line);
480
+ }
481
+ return lines.join("\n");
482
+ }
483
+ function renderMonochromeFrame(grid, charWidth, charHeight) {
484
+ const lines = [];
485
+ for (let row = 0; row < charHeight; row++) {
486
+ let line = "";
487
+ for (let col = 0; col < charWidth; col++) {
488
+ const cell = grid[row][col];
489
+ if (cell.bits === 0) {
490
+ line += " ";
491
+ continue;
492
+ }
493
+ if (cell.isHead) {
494
+ line += "\u28FF";
495
+ } else {
496
+ line += brailleChar(cell.bits);
497
+ }
498
+ }
499
+ lines.push(line);
500
+ }
501
+ return lines.join("\n");
502
+ }
503
+ function terminalSarmal(stream, curveDef, options) {
504
+ if (!stream.isTTY) {
505
+ return () => {};
506
+ }
507
+ const size = options?.size ?? DEFAULT_SIZE;
508
+ const fps = options?.fps ?? DEFAULT_FPS;
509
+ const trailHex = options?.trailColor ?? DEFAULT_TRAIL_HEX;
510
+ const headHex = options?.headColor ?? trailHex;
511
+ const userSpeed = options?.speed;
512
+ const colorCap = detectColor();
513
+ const trailRgb = hexToRgb(trailHex);
514
+ const headRgb = hexToRgb(headHex);
515
+ const engine = createEngine(curveDef);
516
+ if (userSpeed !== void 0) {
517
+ engine.setSpeed(userSpeed);
518
+ }
519
+ const charWidth = size;
520
+ const charHeight = Math.ceil(size / 2);
521
+ const skeleton = engine.getSarmalSkeleton();
522
+ const b = computeBoundaries(skeleton, charWidth * 2, charHeight * 4, 1);
523
+ if (!b) {
524
+ return () => {};
525
+ }
526
+ const { scale, offsetX, offsetY } = b;
527
+ let running = true;
528
+ let firstFrame = true;
529
+ stream.write("\x1B[?25l");
530
+ function cleanup() {
531
+ running = false;
532
+ stream.write("\x1B[?25h");
533
+ stream.write("\n");
534
+ }
535
+ const onSigint = () => {
536
+ cleanup();
537
+ process.exit(0);
538
+ };
539
+ process.on("SIGINT", onSigint);
540
+ function render() {
541
+ const delta = 1 / fps;
542
+ const trail = engine.tick(delta);
543
+ const trailCount = engine.trailCount;
544
+ const frame = renderFrame(
545
+ trail,
546
+ trailCount,
547
+ charWidth,
548
+ charHeight,
549
+ scale,
550
+ offsetX,
551
+ offsetY,
552
+ trailRgb,
553
+ headRgb,
554
+ colorCap,
555
+ );
556
+ if (firstFrame) {
557
+ firstFrame = false;
558
+ stream.write(frame + "\n");
559
+ return;
560
+ }
561
+ const rows = charHeight;
562
+ stream.write(`\x1B[${rows}A`);
563
+ const lines = frame.split("\n");
564
+ for (const line of lines) {
565
+ stream.write(line + "\n");
566
+ }
567
+ }
568
+ render();
569
+ const interval = setInterval(() => {
570
+ if (!running) {
571
+ return;
572
+ }
573
+ render();
574
+ }, 1e3 / fps);
575
+ function stop() {
576
+ clearInterval(interval);
577
+ process.off("SIGINT", onSigint);
578
+ cleanup();
579
+ }
580
+ return stop;
581
+ }
582
+
583
+ export { brailleBit, brailleChar, detectColor, dimColor, dotToCell, rgbTo256, terminalSarmal };
584
+ //# sourceMappingURL=terminal.js.map
585
+ //# sourceMappingURL=terminal.js.map