@sarmal/core 0.31.0 → 0.34.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 -2
- package/dist/auto-init.cjs +42 -4
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js +42 -4
- package/dist/auto-init.js.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/curves/artemis2.d.cts +1 -1
- package/dist/curves/artemis2.d.ts +1 -1
- package/dist/curves/astroid.d.cts +1 -1
- package/dist/curves/astroid.d.ts +1 -1
- package/dist/curves/deltoid.d.cts +1 -1
- package/dist/curves/deltoid.d.ts +1 -1
- package/dist/curves/epicycloid3.d.cts +1 -1
- package/dist/curves/epicycloid3.d.ts +1 -1
- package/dist/curves/epitrochoid7.d.cts +1 -1
- package/dist/curves/epitrochoid7.d.ts +1 -1
- package/dist/curves/index.d.cts +1 -1
- package/dist/curves/index.d.ts +1 -1
- package/dist/curves/lame.d.cts +1 -1
- package/dist/curves/lame.d.ts +1 -1
- package/dist/curves/lissajous32.d.cts +1 -1
- package/dist/curves/lissajous32.d.ts +1 -1
- package/dist/curves/lissajous43.d.cts +1 -1
- package/dist/curves/lissajous43.d.ts +1 -1
- package/dist/curves/rose3.d.cts +1 -1
- package/dist/curves/rose3.d.ts +1 -1
- package/dist/curves/rose5.d.cts +1 -1
- package/dist/curves/rose5.d.ts +1 -1
- package/dist/curves/rose52.d.cts +1 -1
- package/dist/curves/rose52.d.ts +1 -1
- package/dist/curves/star.d.cts +1 -1
- package/dist/curves/star.d.ts +1 -1
- package/dist/curves/star4.d.cts +1 -1
- package/dist/curves/star4.d.ts +1 -1
- package/dist/curves/star7.d.cts +1 -1
- package/dist/curves/star7.d.ts +1 -1
- package/dist/index.cjs +375 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -4
- package/dist/index.d.ts +80 -4
- package/dist/index.js +375 -8
- package/dist/index.js.map +1 -1
- package/dist/{renderer-shared-jqw_Q1WO.d.cts → renderer-shared-C3KCEABq.d.cts} +8 -4
- package/dist/{renderer-shared-OR--cv-t.d.ts → renderer-shared-DyOI68gd.d.ts} +8 -4
- package/dist/terminal.cjs.map +1 -1
- package/dist/terminal.d.cts +2 -2
- package/dist/terminal.d.ts +2 -2
- package/dist/terminal.js.map +1 -1
- package/dist/{types-zbxUgcmZ.d.cts → types-_f27GDkU.d.cts} +30 -9
- package/dist/{types-zbxUgcmZ.d.ts → types-_f27GDkU.d.ts} +30 -9
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, R as RendererOptions,
|
|
2
|
-
export {
|
|
1
|
+
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, T as TrailColor, a as TrailStyle, D as DotMatrixRuntimeRenderOptions, R as RendererOptions, b as ControlPoint, P as Point, c as SarmalOptions } from './types-_f27GDkU.cjs';
|
|
2
|
+
export { d as BaseRuntimeRenderOptions, J as JumpOptions, e as RuntimeRenderOptions, f as SeekOptions } from './types-_f27GDkU.cjs';
|
|
3
3
|
export { CurveName, curves } from './curves/index.cjs';
|
|
4
|
-
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-
|
|
4
|
+
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-C3KCEABq.cjs';
|
|
5
5
|
export { artemis2 } from './curves/artemis2.cjs';
|
|
6
6
|
export { astroid } from './curves/astroid.cjs';
|
|
7
7
|
export { deltoid } from './curves/deltoid.cjs';
|
|
@@ -53,6 +53,82 @@ declare function createSVGRenderer(options: SVGRendererOptions): SarmalInstance;
|
|
|
53
53
|
*/
|
|
54
54
|
declare function createSarmalSVG(container: SVGSVGElement, curveDef: CurveDef, options?: SVGSarmalOptions): SarmalInstance;
|
|
55
55
|
|
|
56
|
+
interface DotMatrixSarmalOptions extends Pick<BaseRendererOptions, "autoStart" | "pauseOnHidden" | "initialPhase"> {
|
|
57
|
+
/**
|
|
58
|
+
* Number of dot columns in the grid.
|
|
59
|
+
* @default 32
|
|
60
|
+
*/
|
|
61
|
+
cols?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Number of dot rows in the grid.
|
|
64
|
+
* @default 32
|
|
65
|
+
*/
|
|
66
|
+
rows?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Controls the corner rounding of each dot.
|
|
69
|
+
* `0` renders as a sharp-cornered square,
|
|
70
|
+
* `1` renders as a full circle.
|
|
71
|
+
* Values between `0` and `1` give rounded rectangles.
|
|
72
|
+
* @default 1
|
|
73
|
+
*/
|
|
74
|
+
roundness?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Number of trail points to keep.
|
|
77
|
+
* Larger values mean the trail extends further back from the head.
|
|
78
|
+
* @default cols * 3
|
|
79
|
+
*/
|
|
80
|
+
trailLength?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Color of lit dots. Single color string for solid mode; array of two or more colors for gradient mode.
|
|
83
|
+
* Gradient mode samples a color per dot based on its position in the trail (tail → head).
|
|
84
|
+
* Background dots always use the first color at 5% opacity.
|
|
85
|
+
* @default '#ffffff'
|
|
86
|
+
*/
|
|
87
|
+
trailColor?: TrailColor;
|
|
88
|
+
/**
|
|
89
|
+
* Trail rendering style.
|
|
90
|
+
* - `'default'` — solid color, alpha varies by intensity.
|
|
91
|
+
* - `'gradient-static'` — each dot's color is sampled from the `trailColor` gradient. Requires `trailColor` array.
|
|
92
|
+
* - `'gradient-animated'` — same as `gradient-static` but the gradient phase shifts over time.
|
|
93
|
+
* @default 'default'
|
|
94
|
+
*/
|
|
95
|
+
trailStyle?: TrailStyle;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a dot matrix renderer for a sarmal animation on a canvas element.
|
|
99
|
+
*
|
|
100
|
+
* The renderer maps the animation's trail to a grid of dots.
|
|
101
|
+
* Each frame, the grid is cleared and rebuilt: dots near the head of the trail are bright,
|
|
102
|
+
* dots near the tail are dim, and dots with no trail activity are barely visible (5% opacity).
|
|
103
|
+
*
|
|
104
|
+
* Grid geometry is derived from `cols` and `rows`.
|
|
105
|
+
* For example, a 240x240 canvas with `cols: 32, rows: 32` produces 1024 dots with cells approximately 7.5x7.5 px each.
|
|
106
|
+
*
|
|
107
|
+
* The background layer (all dim dots) is pre-rendered to an OffscreenCanvas at init and
|
|
108
|
+
* restored each frame with a single `drawImage` call.
|
|
109
|
+
* Lit dots are batched by brightness level,
|
|
110
|
+
* so the total draw calls per frame is around 10–12 regardless of grid size.
|
|
111
|
+
*
|
|
112
|
+
* @param canvas - The canvas element to draw into.
|
|
113
|
+
* Its `width` and `height` HTML attributes determine the rendering area.
|
|
114
|
+
* CSS display size is not read.
|
|
115
|
+
* @param curveDef - The curve to animate.
|
|
116
|
+
* @param options - Optional configuration for grid size, color, roundness, and lifecycle.
|
|
117
|
+
* @returns A `SarmalInstance` with the standard `play` / `pause` / `destroy` / `morphTo` interface.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { createSarmalDotMatrix, curves } from '@sarmal/core'
|
|
122
|
+
*
|
|
123
|
+
* const instance = createSarmalDotMatrix(canvas, curves.lissajous43, {
|
|
124
|
+
* cols: 32,
|
|
125
|
+
* rows: 32,
|
|
126
|
+
* trailColor: '#2dd4bf',
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createSarmalDotMatrix(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: DotMatrixSarmalOptions): SarmalInstance<DotMatrixRuntimeRenderOptions>;
|
|
131
|
+
|
|
56
132
|
declare function createEngine(curveDef: CurveDef, trailLength?: number): Engine;
|
|
57
133
|
|
|
58
134
|
/**
|
|
@@ -119,4 +195,4 @@ declare function drawCurve(points: Array<ControlPoint>, opts?: {
|
|
|
119
195
|
*/
|
|
120
196
|
declare function createSarmal(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: SarmalOptions): SarmalInstance;
|
|
121
197
|
|
|
122
|
-
export { BaseRendererOptions, CurveDef, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
|
198
|
+
export { BaseRendererOptions, CurveDef, DotMatrixRuntimeRenderOptions, type DotMatrixSarmalOptions, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, TrailColor, TrailStyle, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalDotMatrix, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, R as RendererOptions,
|
|
2
|
-
export {
|
|
1
|
+
import { B as BaseRendererOptions, E as Engine, S as SarmalInstance, C as CurveDef, T as TrailColor, a as TrailStyle, D as DotMatrixRuntimeRenderOptions, R as RendererOptions, b as ControlPoint, P as Point, c as SarmalOptions } from './types-_f27GDkU.js';
|
|
2
|
+
export { d as BaseRuntimeRenderOptions, J as JumpOptions, e as RuntimeRenderOptions, f as SeekOptions } from './types-_f27GDkU.js';
|
|
3
3
|
export { CurveName, curves } from './curves/index.js';
|
|
4
|
-
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-
|
|
4
|
+
export { B as BoundaryResult, S as SarmalPalette, c as computeBoundaries, p as palettes } from './renderer-shared-DyOI68gd.js';
|
|
5
5
|
export { artemis2 } from './curves/artemis2.js';
|
|
6
6
|
export { astroid } from './curves/astroid.js';
|
|
7
7
|
export { deltoid } from './curves/deltoid.js';
|
|
@@ -53,6 +53,82 @@ declare function createSVGRenderer(options: SVGRendererOptions): SarmalInstance;
|
|
|
53
53
|
*/
|
|
54
54
|
declare function createSarmalSVG(container: SVGSVGElement, curveDef: CurveDef, options?: SVGSarmalOptions): SarmalInstance;
|
|
55
55
|
|
|
56
|
+
interface DotMatrixSarmalOptions extends Pick<BaseRendererOptions, "autoStart" | "pauseOnHidden" | "initialPhase"> {
|
|
57
|
+
/**
|
|
58
|
+
* Number of dot columns in the grid.
|
|
59
|
+
* @default 32
|
|
60
|
+
*/
|
|
61
|
+
cols?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Number of dot rows in the grid.
|
|
64
|
+
* @default 32
|
|
65
|
+
*/
|
|
66
|
+
rows?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Controls the corner rounding of each dot.
|
|
69
|
+
* `0` renders as a sharp-cornered square,
|
|
70
|
+
* `1` renders as a full circle.
|
|
71
|
+
* Values between `0` and `1` give rounded rectangles.
|
|
72
|
+
* @default 1
|
|
73
|
+
*/
|
|
74
|
+
roundness?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Number of trail points to keep.
|
|
77
|
+
* Larger values mean the trail extends further back from the head.
|
|
78
|
+
* @default cols * 3
|
|
79
|
+
*/
|
|
80
|
+
trailLength?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Color of lit dots. Single color string for solid mode; array of two or more colors for gradient mode.
|
|
83
|
+
* Gradient mode samples a color per dot based on its position in the trail (tail → head).
|
|
84
|
+
* Background dots always use the first color at 5% opacity.
|
|
85
|
+
* @default '#ffffff'
|
|
86
|
+
*/
|
|
87
|
+
trailColor?: TrailColor;
|
|
88
|
+
/**
|
|
89
|
+
* Trail rendering style.
|
|
90
|
+
* - `'default'` — solid color, alpha varies by intensity.
|
|
91
|
+
* - `'gradient-static'` — each dot's color is sampled from the `trailColor` gradient. Requires `trailColor` array.
|
|
92
|
+
* - `'gradient-animated'` — same as `gradient-static` but the gradient phase shifts over time.
|
|
93
|
+
* @default 'default'
|
|
94
|
+
*/
|
|
95
|
+
trailStyle?: TrailStyle;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a dot matrix renderer for a sarmal animation on a canvas element.
|
|
99
|
+
*
|
|
100
|
+
* The renderer maps the animation's trail to a grid of dots.
|
|
101
|
+
* Each frame, the grid is cleared and rebuilt: dots near the head of the trail are bright,
|
|
102
|
+
* dots near the tail are dim, and dots with no trail activity are barely visible (5% opacity).
|
|
103
|
+
*
|
|
104
|
+
* Grid geometry is derived from `cols` and `rows`.
|
|
105
|
+
* For example, a 240x240 canvas with `cols: 32, rows: 32` produces 1024 dots with cells approximately 7.5x7.5 px each.
|
|
106
|
+
*
|
|
107
|
+
* The background layer (all dim dots) is pre-rendered to an OffscreenCanvas at init and
|
|
108
|
+
* restored each frame with a single `drawImage` call.
|
|
109
|
+
* Lit dots are batched by brightness level,
|
|
110
|
+
* so the total draw calls per frame is around 10–12 regardless of grid size.
|
|
111
|
+
*
|
|
112
|
+
* @param canvas - The canvas element to draw into.
|
|
113
|
+
* Its `width` and `height` HTML attributes determine the rendering area.
|
|
114
|
+
* CSS display size is not read.
|
|
115
|
+
* @param curveDef - The curve to animate.
|
|
116
|
+
* @param options - Optional configuration for grid size, color, roundness, and lifecycle.
|
|
117
|
+
* @returns A `SarmalInstance` with the standard `play` / `pause` / `destroy` / `morphTo` interface.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { createSarmalDotMatrix, curves } from '@sarmal/core'
|
|
122
|
+
*
|
|
123
|
+
* const instance = createSarmalDotMatrix(canvas, curves.lissajous43, {
|
|
124
|
+
* cols: 32,
|
|
125
|
+
* rows: 32,
|
|
126
|
+
* trailColor: '#2dd4bf',
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createSarmalDotMatrix(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: DotMatrixSarmalOptions): SarmalInstance<DotMatrixRuntimeRenderOptions>;
|
|
131
|
+
|
|
56
132
|
declare function createEngine(curveDef: CurveDef, trailLength?: number): Engine;
|
|
57
133
|
|
|
58
134
|
/**
|
|
@@ -119,4 +195,4 @@ declare function drawCurve(points: Array<ControlPoint>, opts?: {
|
|
|
119
195
|
*/
|
|
120
196
|
declare function createSarmal(canvas: HTMLCanvasElement, curveDef: CurveDef, options?: SarmalOptions): SarmalInstance;
|
|
121
197
|
|
|
122
|
-
export { BaseRendererOptions, CurveDef, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
|
198
|
+
export { BaseRendererOptions, CurveDef, DotMatrixRuntimeRenderOptions, type DotMatrixSarmalOptions, Engine, Point, RendererOptions, type SVGRendererOptions, type SVGSarmalOptions, SarmalInstance, SarmalOptions, TrailColor, TrailStyle, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalDotMatrix, createSarmalSVG, drawCurve, evaluateCatmullRom };
|
package/dist/index.js
CHANGED
|
@@ -373,11 +373,15 @@ function enginePassthroughs(engine) {
|
|
|
373
373
|
}
|
|
374
374
|
var palettes = {
|
|
375
375
|
bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
|
|
376
|
-
|
|
376
|
+
carnival: ["#ff6b6b", "#4ecdc4", "#ffe66d"],
|
|
377
377
|
ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
|
|
378
|
+
sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
|
|
378
379
|
ice: ["#1e3a8a", "#67e8f9"],
|
|
379
|
-
|
|
380
|
-
|
|
380
|
+
rocketpop: ["#08b8cd", "#ffffff", "#ff001f"],
|
|
381
|
+
neon: ["#00e5ff", "#7c3aed", "#e040fb"],
|
|
382
|
+
vaporwave: ["#ff71ce", "#01cdfe", "#b967ff"],
|
|
383
|
+
pastel: ["#c4b5fd", "#fbcfe8", "#bae6fd"],
|
|
384
|
+
sakura: ["#fff1f2", "#fda4af", "#fb7185"]
|
|
381
385
|
};
|
|
382
386
|
function hexToRgb(hex) {
|
|
383
387
|
const n = parseInt(hex.slice(1), 16);
|
|
@@ -514,13 +518,28 @@ function getPaletteColor(palette, position, timeOffset = 0) {
|
|
|
514
518
|
return lerpOklab(c1, c2, t);
|
|
515
519
|
}
|
|
516
520
|
var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
|
|
521
|
+
var BASE_RENDER_OPTION_KEYS = /* @__PURE__ */ new Set(["trailColor", "trailStyle"]);
|
|
517
522
|
var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
518
523
|
"trailColor",
|
|
519
524
|
"headColor",
|
|
520
525
|
"skeletonColor",
|
|
521
526
|
"trailStyle",
|
|
522
|
-
"headRadius"
|
|
527
|
+
"headRadius",
|
|
528
|
+
"trailWidth"
|
|
523
529
|
]);
|
|
530
|
+
function validateBaseRenderOptions(partial) {
|
|
531
|
+
for (const key of Object.keys(partial)) {
|
|
532
|
+
if (!BASE_RENDER_OPTION_KEYS.has(key)) {
|
|
533
|
+
throw new TypeError(`[sarmal] setRenderOptions: unsupported key "${key}" for this renderer`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (partial.trailColor !== void 0) {
|
|
537
|
+
assertTrailColor(partial.trailColor);
|
|
538
|
+
}
|
|
539
|
+
if (partial.trailStyle !== void 0) {
|
|
540
|
+
assertTrailStyle(partial.trailStyle);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
524
543
|
function validateRenderOptions(partial) {
|
|
525
544
|
for (const key of Object.keys(partial)) {
|
|
526
545
|
if (!RENDER_OPTION_KEYS.has(key)) {
|
|
@@ -542,6 +561,9 @@ function validateRenderOptions(partial) {
|
|
|
542
561
|
if (partial.headRadius !== void 0) {
|
|
543
562
|
assertHeadRadius(partial.headRadius);
|
|
544
563
|
}
|
|
564
|
+
if (partial.trailWidth !== void 0) {
|
|
565
|
+
assertTrailWidth(partial.trailWidth);
|
|
566
|
+
}
|
|
545
567
|
}
|
|
546
568
|
function assertTrailColor(value) {
|
|
547
569
|
if (typeof value === "string") {
|
|
@@ -611,6 +633,18 @@ function assertHeadRadius(value) {
|
|
|
611
633
|
);
|
|
612
634
|
}
|
|
613
635
|
}
|
|
636
|
+
function assertTrailWidth(value) {
|
|
637
|
+
if (typeof value !== "number") {
|
|
638
|
+
throw new TypeError(
|
|
639
|
+
`[sarmal] setRenderOptions: trailWidth must be a number, got ${JSON.stringify(value)}`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
643
|
+
throw new TypeError(
|
|
644
|
+
`[sarmal] setRenderOptions: trailWidth must be a finite positive number, got ${value}`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
614
648
|
function resolveTrailMainColor(trailColor) {
|
|
615
649
|
return typeof trailColor === "string" ? trailColor : trailColor[0];
|
|
616
650
|
}
|
|
@@ -679,7 +713,14 @@ function createRenderer(options) {
|
|
|
679
713
|
setupCanvas();
|
|
680
714
|
let logicalWidth = canvas.width / dpr;
|
|
681
715
|
let logicalHeight = canvas.height / dpr;
|
|
716
|
+
if (options.headRadius !== void 0) {
|
|
717
|
+
validateRenderOptions({ headRadius: options.headRadius });
|
|
718
|
+
}
|
|
719
|
+
if (options.trailWidth !== void 0) {
|
|
720
|
+
validateRenderOptions({ trailWidth: options.trailWidth });
|
|
721
|
+
}
|
|
682
722
|
let headRadius = options.headRadius ?? getHeadDotRadius(logicalWidth, logicalHeight);
|
|
723
|
+
let trailWidth = options.trailWidth ?? 1;
|
|
683
724
|
let skeleton = [];
|
|
684
725
|
let skeletonCanvas = null;
|
|
685
726
|
let trail = [];
|
|
@@ -773,7 +814,9 @@ function createRenderer(options) {
|
|
|
773
814
|
i,
|
|
774
815
|
trailCount,
|
|
775
816
|
toX,
|
|
776
|
-
toY
|
|
817
|
+
toY,
|
|
818
|
+
TRAIL_MIN_WIDTH * trailWidth,
|
|
819
|
+
TRAIL_MAX_WIDTH * trailWidth
|
|
777
820
|
);
|
|
778
821
|
if (trailStyle === "default") {
|
|
779
822
|
ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
|
|
@@ -931,6 +974,9 @@ function createRenderer(options) {
|
|
|
931
974
|
if (partial.headRadius !== void 0) {
|
|
932
975
|
headRadius = partial.headRadius;
|
|
933
976
|
}
|
|
977
|
+
if (partial.trailWidth !== void 0) {
|
|
978
|
+
trailWidth = partial.trailWidth;
|
|
979
|
+
}
|
|
934
980
|
if (userHeadColor === null) {
|
|
935
981
|
headColor = resolveHeadColor(trailColor, trailStyle);
|
|
936
982
|
} else {
|
|
@@ -1029,7 +1075,14 @@ function createSVGRenderer(options) {
|
|
|
1029
1075
|
const svgTrailMinWidth = TRAIL_MIN_WIDTH * viewSize / containerPx;
|
|
1030
1076
|
const svgTrailMaxWidth = TRAIL_MAX_WIDTH * viewSize / containerPx;
|
|
1031
1077
|
const svgSkeletonStrokeWidth = String(SKELETON_STROKE_WIDTH_PX * viewSize / containerPx);
|
|
1078
|
+
if (options.headRadius !== void 0) {
|
|
1079
|
+
validateRenderOptions({ headRadius: options.headRadius });
|
|
1080
|
+
}
|
|
1081
|
+
if (options.trailWidth !== void 0) {
|
|
1082
|
+
validateRenderOptions({ trailWidth: options.trailWidth });
|
|
1083
|
+
}
|
|
1032
1084
|
headRadius = options.headRadius ?? SVG_DEFAULT_HEAD_RADIUS;
|
|
1085
|
+
let trailWidth = options.trailWidth ?? 1;
|
|
1033
1086
|
container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
|
|
1034
1087
|
container.setAttribute("role", "img");
|
|
1035
1088
|
container.setAttribute("aria-label", ariaLabel);
|
|
@@ -1124,8 +1177,8 @@ function createSVGRenderer(options) {
|
|
|
1124
1177
|
trailCount,
|
|
1125
1178
|
px,
|
|
1126
1179
|
py,
|
|
1127
|
-
svgTrailMinWidth,
|
|
1128
|
-
svgTrailMaxWidth
|
|
1180
|
+
svgTrailMinWidth * trailWidth,
|
|
1181
|
+
svgTrailMaxWidth * trailWidth
|
|
1129
1182
|
);
|
|
1130
1183
|
const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
|
|
1131
1184
|
trailPaths[i].setAttribute("d", d);
|
|
@@ -1317,6 +1370,9 @@ function createSVGRenderer(options) {
|
|
|
1317
1370
|
headRadius = partial.headRadius;
|
|
1318
1371
|
headCircle.setAttribute("r", String(headRadius));
|
|
1319
1372
|
}
|
|
1373
|
+
if (partial.trailWidth !== void 0) {
|
|
1374
|
+
trailWidth = partial.trailWidth;
|
|
1375
|
+
}
|
|
1320
1376
|
if (userHeadColor === null) {
|
|
1321
1377
|
headColor = resolveHeadColor(trailColor, trailStyle);
|
|
1322
1378
|
} else {
|
|
@@ -1359,6 +1415,317 @@ function createSarmalSVG(container, curveDef, options) {
|
|
|
1359
1415
|
return createSVGRenderer({ container, engine, ...rendererOpts });
|
|
1360
1416
|
}
|
|
1361
1417
|
|
|
1418
|
+
// src/renderer-dot-matrix.ts
|
|
1419
|
+
var NUM_BUCKETS = 8;
|
|
1420
|
+
function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
1421
|
+
const {
|
|
1422
|
+
cols = 32,
|
|
1423
|
+
rows = 32,
|
|
1424
|
+
roundness = 1,
|
|
1425
|
+
trailLength: trailLengthOpt,
|
|
1426
|
+
trailColor: initialColor = "#ffffff",
|
|
1427
|
+
trailStyle: initialTrailStyle = "default",
|
|
1428
|
+
autoStart = true,
|
|
1429
|
+
pauseOnHidden: pauseOnHiddenOpt = true,
|
|
1430
|
+
initialPhase
|
|
1431
|
+
} = options ?? {};
|
|
1432
|
+
const trailLength = trailLengthOpt ?? cols * 3;
|
|
1433
|
+
const engine = createEngine(curveDef, trailLength);
|
|
1434
|
+
if (!canvas.getContext("2d")) {
|
|
1435
|
+
throw new Error("[sarmal] Could not get 2d context from canvas");
|
|
1436
|
+
}
|
|
1437
|
+
const ctx = canvas.getContext("2d");
|
|
1438
|
+
const W = canvas.width;
|
|
1439
|
+
const H = canvas.height;
|
|
1440
|
+
const cellW = W / cols;
|
|
1441
|
+
const cellH = H / rows;
|
|
1442
|
+
const dotR = Math.min(cellW, cellH) * 0.36;
|
|
1443
|
+
let gradientRgb;
|
|
1444
|
+
if (Array.isArray(initialColor)) {
|
|
1445
|
+
validateBaseRenderOptions({ trailColor: initialColor });
|
|
1446
|
+
gradientRgb = initialColor.map(colorToRgb);
|
|
1447
|
+
} else {
|
|
1448
|
+
gradientRgb = null;
|
|
1449
|
+
}
|
|
1450
|
+
let colorRgb = gradientRgb ? gradientRgb[0] : colorToRgb(initialColor);
|
|
1451
|
+
let currentTrailStyle = initialTrailStyle;
|
|
1452
|
+
let animTime = 0;
|
|
1453
|
+
const ANIM_PERIOD = 6;
|
|
1454
|
+
const grid = new Float32Array(cols * rows);
|
|
1455
|
+
let scale = 1;
|
|
1456
|
+
let offsetX = 0;
|
|
1457
|
+
let offsetY = 0;
|
|
1458
|
+
let bgCanvas = null;
|
|
1459
|
+
let animationId = null;
|
|
1460
|
+
let lastTime = 0;
|
|
1461
|
+
let pausedByVisibility = false;
|
|
1462
|
+
let morphResolve = null;
|
|
1463
|
+
let morphReject = null;
|
|
1464
|
+
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
1465
|
+
let morphProgress = 0;
|
|
1466
|
+
function buildBgCanvas() {
|
|
1467
|
+
bgCanvas = new OffscreenCanvas(W, H);
|
|
1468
|
+
const bgCtx = bgCanvas.getContext("2d");
|
|
1469
|
+
const bg = gradientRgb ? gradientRgb[0] : colorRgb;
|
|
1470
|
+
bgCtx.fillStyle = `rgba(${bg.r},${bg.g},${bg.b},0.05)`;
|
|
1471
|
+
bgCtx.beginPath();
|
|
1472
|
+
for (let row = 0; row < rows; row++) {
|
|
1473
|
+
for (let col = 0; col < cols; col++) {
|
|
1474
|
+
const cx = (col + 0.5) * cellW;
|
|
1475
|
+
const cy = (row + 0.5) * cellH;
|
|
1476
|
+
bgCtx.roundRect(cx - dotR, cy - dotR, dotR * 2, dotR * 2, roundness * dotR);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
bgCtx.fill();
|
|
1480
|
+
}
|
|
1481
|
+
function sampleGradientRgb(stops, t) {
|
|
1482
|
+
const n = stops.length;
|
|
1483
|
+
const scaled = Math.max(0, Math.min(1, t)) * (n - 1);
|
|
1484
|
+
const i = Math.min(Math.floor(scaled), n - 2);
|
|
1485
|
+
const a = stops[i];
|
|
1486
|
+
const bStop = stops[i + 1];
|
|
1487
|
+
const mix = scaled - i;
|
|
1488
|
+
return {
|
|
1489
|
+
r: Math.round(a.r + (bStop.r - a.r) * mix),
|
|
1490
|
+
g: Math.round(a.g + (bStop.g - a.g) * mix),
|
|
1491
|
+
b: Math.round(a.b + (bStop.b - a.b) * mix)
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function calculateBoundaries(skel) {
|
|
1495
|
+
const b = computeBoundaries(skel, W, H);
|
|
1496
|
+
if (b) {
|
|
1497
|
+
scale = b.scale;
|
|
1498
|
+
offsetX = b.offsetX;
|
|
1499
|
+
offsetY = b.offsetY;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function mapPt(x, y) {
|
|
1503
|
+
const px = x * scale + offsetX;
|
|
1504
|
+
const py = y * scale + offsetY;
|
|
1505
|
+
return [
|
|
1506
|
+
Math.max(0, Math.min(cols - 1, Math.round(px / W * (cols - 1)))),
|
|
1507
|
+
Math.max(0, Math.min(rows - 1, Math.round(py / H * (rows - 1))))
|
|
1508
|
+
];
|
|
1509
|
+
}
|
|
1510
|
+
function stamp(c, r, intensity) {
|
|
1511
|
+
const idx = r * cols + c;
|
|
1512
|
+
if (intensity > grid[idx]) {
|
|
1513
|
+
grid[idx] = intensity;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function buildGrid(deltaTime) {
|
|
1517
|
+
const trail = engine.tick(deltaTime);
|
|
1518
|
+
const count = engine.trailCount;
|
|
1519
|
+
grid.fill(0);
|
|
1520
|
+
for (let i = 0; i < count; i++) {
|
|
1521
|
+
const pt = trail[i];
|
|
1522
|
+
const intensity = (i + 1) / count;
|
|
1523
|
+
const [c, r] = mapPt(pt.x, pt.y);
|
|
1524
|
+
stamp(c, r, intensity);
|
|
1525
|
+
if (i < count - 1) {
|
|
1526
|
+
const next = trail[i + 1];
|
|
1527
|
+
const [nc, nr] = mapPt(next.x, next.y);
|
|
1528
|
+
const steps = Math.ceil(Math.max(Math.abs(nc - c), Math.abs(nr - r))) * 2;
|
|
1529
|
+
for (let s = 1; s < steps; s++) {
|
|
1530
|
+
const t = s / steps;
|
|
1531
|
+
const ix = pt.x + (next.x - pt.x) * t;
|
|
1532
|
+
const iy = pt.y + (next.y - pt.y) * t;
|
|
1533
|
+
const ii = intensity + 1 / count * t;
|
|
1534
|
+
const [ic, ir] = mapPt(ix, iy);
|
|
1535
|
+
stamp(ic, ir, ii);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function draw() {
|
|
1541
|
+
ctx.clearRect(0, 0, W, H);
|
|
1542
|
+
if (bgCanvas) {
|
|
1543
|
+
ctx.drawImage(bgCanvas, 0, 0);
|
|
1544
|
+
}
|
|
1545
|
+
const animOffset = currentTrailStyle === "gradient-animated" ? Math.abs(animTime / ANIM_PERIOD % 2 - 1) * 0.35 : 0;
|
|
1546
|
+
for (let bucket = 0; bucket < NUM_BUCKETS; bucket++) {
|
|
1547
|
+
const lo = bucket / NUM_BUCKETS;
|
|
1548
|
+
const hi = (bucket + 1) / NUM_BUCKETS;
|
|
1549
|
+
const midpoint = (lo + hi) / 2;
|
|
1550
|
+
const alpha = 0.08 + midpoint * 0.92;
|
|
1551
|
+
let hasLit = false;
|
|
1552
|
+
ctx.beginPath();
|
|
1553
|
+
for (let row = 0; row < rows; row++) {
|
|
1554
|
+
for (let col = 0; col < cols; col++) {
|
|
1555
|
+
const intensity = grid[row * cols + col];
|
|
1556
|
+
if (intensity > lo && intensity <= hi) {
|
|
1557
|
+
const cx = (col + 0.5) * cellW;
|
|
1558
|
+
const cy = (row + 0.5) * cellH;
|
|
1559
|
+
ctx.roundRect(cx - dotR, cy - dotR, dotR * 2, dotR * 2, roundness * dotR);
|
|
1560
|
+
hasLit = true;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
if (hasLit) {
|
|
1565
|
+
if (gradientRgb !== null) {
|
|
1566
|
+
const t = ((midpoint + animOffset) % 1 + 1) % 1;
|
|
1567
|
+
const { r, g, b } = sampleGradientRgb(gradientRgb, t);
|
|
1568
|
+
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
|
1569
|
+
} else {
|
|
1570
|
+
const { r, g, b } = colorRgb;
|
|
1571
|
+
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
|
1572
|
+
}
|
|
1573
|
+
ctx.globalAlpha = alpha;
|
|
1574
|
+
ctx.fill();
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
ctx.globalAlpha = 1;
|
|
1578
|
+
}
|
|
1579
|
+
function renderFrame(deltaTime) {
|
|
1580
|
+
if (engine.morphAlpha !== null) {
|
|
1581
|
+
morphProgress = Math.min(1, morphProgress + deltaTime / (morphDurationMs / 1e3));
|
|
1582
|
+
engine.setMorphAlpha(morphProgress);
|
|
1583
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1584
|
+
if (morphProgress >= 1) {
|
|
1585
|
+
engine.completeMorph();
|
|
1586
|
+
morphResolve?.();
|
|
1587
|
+
morphResolve = null;
|
|
1588
|
+
morphReject = null;
|
|
1589
|
+
morphProgress = 0;
|
|
1590
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1591
|
+
}
|
|
1592
|
+
} else if (engine.isLiveSkeleton) {
|
|
1593
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1594
|
+
}
|
|
1595
|
+
if (currentTrailStyle === "gradient-animated") {
|
|
1596
|
+
animTime += deltaTime;
|
|
1597
|
+
}
|
|
1598
|
+
buildGrid(deltaTime);
|
|
1599
|
+
draw();
|
|
1600
|
+
}
|
|
1601
|
+
function loop(timestamp = performance.now()) {
|
|
1602
|
+
const deltaTime = Math.min((timestamp - lastTime) / 1e3, 1 / 30);
|
|
1603
|
+
lastTime = timestamp;
|
|
1604
|
+
renderFrame(deltaTime);
|
|
1605
|
+
animationId = requestAnimationFrame(loop);
|
|
1606
|
+
}
|
|
1607
|
+
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1608
|
+
buildBgCanvas();
|
|
1609
|
+
if (initialPhase !== void 0) {
|
|
1610
|
+
engine.seek(initialPhase);
|
|
1611
|
+
}
|
|
1612
|
+
renderFrame(0);
|
|
1613
|
+
const instance = {
|
|
1614
|
+
/** Starts the animation loop. Does nothing if already running. */
|
|
1615
|
+
play() {
|
|
1616
|
+
if (animationId !== null) return;
|
|
1617
|
+
lastTime = performance.now();
|
|
1618
|
+
loop();
|
|
1619
|
+
},
|
|
1620
|
+
/** Pauses the animation loop. Preserves current trail state. */
|
|
1621
|
+
pause() {
|
|
1622
|
+
if (animationId === null) return;
|
|
1623
|
+
cancelAnimationFrame(animationId);
|
|
1624
|
+
animationId = null;
|
|
1625
|
+
engine.cancelSpeedTransition();
|
|
1626
|
+
},
|
|
1627
|
+
/** Resets the animation to the start of the curve and clears the grid. */
|
|
1628
|
+
reset() {
|
|
1629
|
+
engine.reset();
|
|
1630
|
+
grid.fill(0);
|
|
1631
|
+
},
|
|
1632
|
+
/** Stops the animation and removes all event listeners. */
|
|
1633
|
+
destroy() {
|
|
1634
|
+
if (animationId !== null) {
|
|
1635
|
+
cancelAnimationFrame(animationId);
|
|
1636
|
+
animationId = null;
|
|
1637
|
+
}
|
|
1638
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1639
|
+
if (morphReject !== null) {
|
|
1640
|
+
morphReject(new Error("[sarmal] Instance destroyed during morph"));
|
|
1641
|
+
morphResolve = null;
|
|
1642
|
+
morphReject = null;
|
|
1643
|
+
}
|
|
1644
|
+
},
|
|
1645
|
+
...enginePassthroughs(engine),
|
|
1646
|
+
/**
|
|
1647
|
+
* Smoothly transitions from the current curve to `target`.
|
|
1648
|
+
* If a morph is already in progress, it is snapped to completion before the new one starts.
|
|
1649
|
+
* @returns A Promise that resolves when the transition finishes.
|
|
1650
|
+
*/
|
|
1651
|
+
morphTo(target, opts) {
|
|
1652
|
+
if (morphResolve !== null) {
|
|
1653
|
+
engine.completeMorph();
|
|
1654
|
+
morphResolve();
|
|
1655
|
+
morphResolve = null;
|
|
1656
|
+
morphReject = null;
|
|
1657
|
+
morphProgress = 0;
|
|
1658
|
+
}
|
|
1659
|
+
morphDurationMs = opts?.duration ?? DEFAULT_MORPH_DURATION_MS;
|
|
1660
|
+
morphProgress = 0;
|
|
1661
|
+
engine.startMorph(target, opts?.morphStrategy);
|
|
1662
|
+
return new Promise((resolve, reject) => {
|
|
1663
|
+
morphResolve = resolve;
|
|
1664
|
+
morphReject = reject;
|
|
1665
|
+
});
|
|
1666
|
+
},
|
|
1667
|
+
/**
|
|
1668
|
+
* Updates render options on a live instance without stopping the animation.
|
|
1669
|
+
*
|
|
1670
|
+
* Supported: `trailColor` and `trailStyle`.
|
|
1671
|
+
* ! Unsupported fields (`headColor`, `skeletonColor`, `headRadius`, `trailWidth`) throw.
|
|
1672
|
+
* ! Validation fails the entire call if any field is invalid, leaving options unchanged.
|
|
1673
|
+
*/
|
|
1674
|
+
setRenderOptions(partial) {
|
|
1675
|
+
validateBaseRenderOptions(partial);
|
|
1676
|
+
let needsRebuildBg = false;
|
|
1677
|
+
if (partial.trailColor !== void 0) {
|
|
1678
|
+
if (Array.isArray(partial.trailColor)) {
|
|
1679
|
+
gradientRgb = partial.trailColor.map(colorToRgb);
|
|
1680
|
+
colorRgb = gradientRgb[0];
|
|
1681
|
+
} else {
|
|
1682
|
+
gradientRgb = null;
|
|
1683
|
+
colorRgb = colorToRgb(partial.trailColor);
|
|
1684
|
+
}
|
|
1685
|
+
needsRebuildBg = true;
|
|
1686
|
+
}
|
|
1687
|
+
if (partial.trailStyle !== void 0) {
|
|
1688
|
+
currentTrailStyle = partial.trailStyle;
|
|
1689
|
+
if (currentTrailStyle === "default") {
|
|
1690
|
+
animTime = 0;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (needsRebuildBg) {
|
|
1694
|
+
buildBgCanvas();
|
|
1695
|
+
}
|
|
1696
|
+
if (currentTrailStyle !== "default" && gradientRgb === null) {
|
|
1697
|
+
console.warn(
|
|
1698
|
+
"[sarmal] dot matrix: gradient trailStyle has no effect without a trailColor array"
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
function handleVisibilityChange() {
|
|
1704
|
+
if (document.hidden) {
|
|
1705
|
+
if (animationId !== null) {
|
|
1706
|
+
instance.pause();
|
|
1707
|
+
pausedByVisibility = true;
|
|
1708
|
+
}
|
|
1709
|
+
} else {
|
|
1710
|
+
if (pausedByVisibility) {
|
|
1711
|
+
pausedByVisibility = false;
|
|
1712
|
+
instance.play();
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (pauseOnHiddenOpt) {
|
|
1717
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1718
|
+
}
|
|
1719
|
+
const shouldAutoStart = autoStart !== false;
|
|
1720
|
+
const actuallyAutoStart = shouldAutoStart && !(pauseOnHiddenOpt && document.hidden);
|
|
1721
|
+
if (actuallyAutoStart) {
|
|
1722
|
+
instance.play();
|
|
1723
|
+
} else if (shouldAutoStart) {
|
|
1724
|
+
pausedByVisibility = true;
|
|
1725
|
+
}
|
|
1726
|
+
return instance;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1362
1729
|
// src/catmull-rom.ts
|
|
1363
1730
|
var PERIOD = 2 * Math.PI;
|
|
1364
1731
|
function catmullRom1D(p0, p1, p2, p3, u) {
|
|
@@ -1688,6 +2055,6 @@ function createSarmal(canvas, curveDef, options) {
|
|
|
1688
2055
|
return createRenderer({ canvas, engine, ...rendererOpts });
|
|
1689
2056
|
}
|
|
1690
2057
|
|
|
1691
|
-
export { artemis2, astroid, computeBoundaries, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves, deltoid, drawCurve, epicycloid3, epitrochoid7, evaluateCatmullRom, lame, lissajous32, lissajous43, palettes, rose3, rose5 };
|
|
2058
|
+
export { artemis2, astroid, computeBoundaries, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalDotMatrix, createSarmalSVG, curves, deltoid, drawCurve, epicycloid3, epitrochoid7, evaluateCatmullRom, lame, lissajous32, lissajous43, palettes, rose3, rose5 };
|
|
1692
2059
|
//# sourceMappingURL=index.js.map
|
|
1693
2060
|
//# sourceMappingURL=index.js.map
|