@autumnsgrove/gossamer 0.1.1 → 0.2.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/LICENSE +21 -0
- package/dist/animation.js +165 -0
- package/dist/animation.test.js +204 -0
- package/dist/characters.js +176 -0
- package/dist/characters.test.js +115 -0
- package/dist/colors.js +199 -0
- package/dist/index.js +79 -1850
- package/dist/index.test.js +92 -0
- package/dist/patterns.js +539 -0
- package/dist/patterns.test.js +223 -0
- package/dist/renderer.js +362 -0
- package/dist/svelte/GossamerBorder.svelte.d.ts +56 -1
- package/dist/svelte/GossamerBorder.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerClouds.svelte.d.ts +31 -1
- package/dist/svelte/GossamerClouds.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerImage.svelte.d.ts +28 -1
- package/dist/svelte/GossamerImage.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerOverlay.svelte.d.ts +32 -1
- package/dist/svelte/GossamerOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerText.svelte.d.ts +29 -1
- package/dist/svelte/GossamerText.svelte.d.ts.map +1 -0
- package/dist/svelte/index.js +31 -3646
- package/dist/svelte/presets.d.ts +4 -2
- package/dist/svelte/presets.js +161 -0
- package/dist/utils/canvas.js +139 -0
- package/dist/utils/image.js +195 -0
- package/dist/utils/performance.js +205 -0
- package/package.json +18 -22
- package/dist/index.js.map +0 -1
- package/dist/style.css +0 -124
- package/dist/svelte/index.js.map +0 -1
- package/src/animation.test.ts +0 -254
- package/src/animation.ts +0 -243
- package/src/characters.test.ts +0 -148
- package/src/characters.ts +0 -219
- package/src/colors.ts +0 -234
- package/src/index.test.ts +0 -115
- package/src/index.ts +0 -234
- package/src/patterns.test.ts +0 -273
- package/src/patterns.ts +0 -760
- package/src/renderer.ts +0 -470
- package/src/svelte/index.ts +0 -75
- package/src/svelte/presets.ts +0 -174
- package/src/utils/canvas.ts +0 -210
- package/src/utils/image.ts +0 -275
- package/src/utils/performance.ts +0 -282
- /package/{src → dist}/svelte/GossamerBorder.svelte +0 -0
- /package/{src → dist}/svelte/GossamerClouds.svelte +0 -0
- /package/{src → dist}/svelte/GossamerImage.svelte +0 -0
- /package/{src → dist}/svelte/GossamerOverlay.svelte +0 -0
- /package/{src → dist}/svelte/GossamerText.svelte +0 -0
package/src/animation.test.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for animation utilities
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import { throttle, debounce, calculateFPS, easings, createAnimationLoop } from './animation';
|
|
6
|
-
|
|
7
|
-
describe('throttle', () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
vi.useFakeTimers();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
vi.useRealTimers();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should call function immediately on first call', () => {
|
|
17
|
-
const fn = vi.fn();
|
|
18
|
-
const throttled = throttle(fn, 100);
|
|
19
|
-
|
|
20
|
-
throttled();
|
|
21
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should throttle subsequent calls within limit', () => {
|
|
25
|
-
const fn = vi.fn();
|
|
26
|
-
const throttled = throttle(fn, 100);
|
|
27
|
-
|
|
28
|
-
throttled();
|
|
29
|
-
throttled();
|
|
30
|
-
throttled();
|
|
31
|
-
|
|
32
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should call function again after limit expires', () => {
|
|
36
|
-
const fn = vi.fn();
|
|
37
|
-
const throttled = throttle(fn, 100);
|
|
38
|
-
|
|
39
|
-
throttled();
|
|
40
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
41
|
-
|
|
42
|
-
vi.advanceTimersByTime(150);
|
|
43
|
-
throttled();
|
|
44
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should pass arguments to throttled function', () => {
|
|
48
|
-
const fn = vi.fn();
|
|
49
|
-
const throttled = throttle(fn, 100);
|
|
50
|
-
|
|
51
|
-
throttled('arg1', 'arg2');
|
|
52
|
-
expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('debounce', () => {
|
|
57
|
-
beforeEach(() => {
|
|
58
|
-
vi.useFakeTimers();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
afterEach(() => {
|
|
62
|
-
vi.useRealTimers();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should not call function immediately', () => {
|
|
66
|
-
const fn = vi.fn();
|
|
67
|
-
const debounced = debounce(fn, 100);
|
|
68
|
-
|
|
69
|
-
debounced();
|
|
70
|
-
expect(fn).not.toHaveBeenCalled();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should call function after delay', () => {
|
|
74
|
-
const fn = vi.fn();
|
|
75
|
-
const debounced = debounce(fn, 100);
|
|
76
|
-
|
|
77
|
-
debounced();
|
|
78
|
-
vi.advanceTimersByTime(100);
|
|
79
|
-
|
|
80
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should reset timer on subsequent calls', () => {
|
|
84
|
-
const fn = vi.fn();
|
|
85
|
-
const debounced = debounce(fn, 100);
|
|
86
|
-
|
|
87
|
-
debounced();
|
|
88
|
-
vi.advanceTimersByTime(50);
|
|
89
|
-
debounced();
|
|
90
|
-
vi.advanceTimersByTime(50);
|
|
91
|
-
|
|
92
|
-
expect(fn).not.toHaveBeenCalled();
|
|
93
|
-
|
|
94
|
-
vi.advanceTimersByTime(50);
|
|
95
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should pass arguments to debounced function', () => {
|
|
99
|
-
const fn = vi.fn();
|
|
100
|
-
const debounced = debounce(fn, 100);
|
|
101
|
-
|
|
102
|
-
debounced('test', 123);
|
|
103
|
-
vi.advanceTimersByTime(100);
|
|
104
|
-
|
|
105
|
-
expect(fn).toHaveBeenCalledWith('test', 123);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('calculateFPS', () => {
|
|
110
|
-
it('should return 0 for empty array', () => {
|
|
111
|
-
expect(calculateFPS([])).toBe(0);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should return 0 for single frame time', () => {
|
|
115
|
-
expect(calculateFPS([1000])).toBe(0);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should calculate FPS correctly', () => {
|
|
119
|
-
// 60 frames over 1 second = 60 FPS
|
|
120
|
-
const frameTimes = Array.from({ length: 61 }, (_, i) => i * (1000 / 60));
|
|
121
|
-
const fps = calculateFPS(frameTimes);
|
|
122
|
-
|
|
123
|
-
expect(fps).toBeCloseTo(60, 0);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should handle 30 FPS', () => {
|
|
127
|
-
// 30 frames over 1 second = 30 FPS
|
|
128
|
-
const frameTimes = Array.from({ length: 31 }, (_, i) => i * (1000 / 30));
|
|
129
|
-
const fps = calculateFPS(frameTimes);
|
|
130
|
-
|
|
131
|
-
expect(fps).toBeCloseTo(30, 0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should use sample size parameter', () => {
|
|
135
|
-
// Create 100 frame times
|
|
136
|
-
const frameTimes = Array.from({ length: 100 }, (_, i) => i * 16.67);
|
|
137
|
-
|
|
138
|
-
// Should only use last 10 samples
|
|
139
|
-
const fps = calculateFPS(frameTimes, 10);
|
|
140
|
-
expect(fps).toBeCloseTo(60, 0);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('easings', () => {
|
|
145
|
-
describe('linear', () => {
|
|
146
|
-
it('should return input unchanged', () => {
|
|
147
|
-
expect(easings.linear(0)).toBe(0);
|
|
148
|
-
expect(easings.linear(0.5)).toBe(0.5);
|
|
149
|
-
expect(easings.linear(1)).toBe(1);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe('easeIn', () => {
|
|
154
|
-
it('should start slow', () => {
|
|
155
|
-
expect(easings.easeIn(0)).toBe(0);
|
|
156
|
-
expect(easings.easeIn(0.5)).toBe(0.25);
|
|
157
|
-
expect(easings.easeIn(1)).toBe(1);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('easeOut', () => {
|
|
162
|
-
it('should end slow', () => {
|
|
163
|
-
expect(easings.easeOut(0)).toBe(0);
|
|
164
|
-
expect(easings.easeOut(0.5)).toBe(0.75);
|
|
165
|
-
expect(easings.easeOut(1)).toBe(1);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('easeInOut', () => {
|
|
170
|
-
it('should start and end slow', () => {
|
|
171
|
-
expect(easings.easeInOut(0)).toBe(0);
|
|
172
|
-
expect(easings.easeInOut(0.5)).toBe(0.5);
|
|
173
|
-
expect(easings.easeInOut(1)).toBe(1);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should be symmetric around 0.5', () => {
|
|
177
|
-
const val1 = easings.easeInOut(0.25);
|
|
178
|
-
const val2 = 1 - easings.easeInOut(0.75);
|
|
179
|
-
expect(val1).toBeCloseTo(val2, 5);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe('sineIn', () => {
|
|
184
|
-
it('should return 0 at start and 1 at end', () => {
|
|
185
|
-
expect(easings.sineIn(0)).toBe(0);
|
|
186
|
-
expect(easings.sineIn(1)).toBeCloseTo(1, 5);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('sineOut', () => {
|
|
191
|
-
it('should return 0 at start and 1 at end', () => {
|
|
192
|
-
expect(easings.sineOut(0)).toBe(0);
|
|
193
|
-
expect(easings.sineOut(1)).toBeCloseTo(1, 5);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('sineInOut', () => {
|
|
198
|
-
it('should return 0 at start, 0.5 at middle, 1 at end', () => {
|
|
199
|
-
expect(easings.sineInOut(0)).toBeCloseTo(0, 5);
|
|
200
|
-
expect(easings.sineInOut(0.5)).toBeCloseTo(0.5, 5);
|
|
201
|
-
expect(easings.sineInOut(1)).toBeCloseTo(1, 5);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('bounceOut', () => {
|
|
206
|
-
it('should return 0 at start and 1 at end', () => {
|
|
207
|
-
expect(easings.bounceOut(0)).toBe(0);
|
|
208
|
-
expect(easings.bounceOut(1)).toBeCloseTo(1, 5);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('should overshoot intermediate values', () => {
|
|
212
|
-
// bounceOut creates bouncing effect with values close to 1
|
|
213
|
-
const val = easings.bounceOut(0.9);
|
|
214
|
-
expect(val).toBeGreaterThan(0.9);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('createAnimationLoop', () => {
|
|
220
|
-
it('should return control functions', () => {
|
|
221
|
-
const loop = createAnimationLoop({
|
|
222
|
-
onFrame: () => {},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
expect(typeof loop.start).toBe('function');
|
|
226
|
-
expect(typeof loop.stop).toBe('function');
|
|
227
|
-
expect(typeof loop.pause).toBe('function');
|
|
228
|
-
expect(typeof loop.resume).toBe('function');
|
|
229
|
-
expect(typeof loop.getState).toBe('function');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should initialize with correct default state', () => {
|
|
233
|
-
const loop = createAnimationLoop({
|
|
234
|
-
fps: 60,
|
|
235
|
-
onFrame: () => {},
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const state = loop.getState();
|
|
239
|
-
expect(state.isRunning).toBe(false);
|
|
240
|
-
expect(state.frameId).toBeNull();
|
|
241
|
-
expect(state.frameInterval).toBeCloseTo(1000 / 60, 1);
|
|
242
|
-
expect(state.elapsedTime).toBe(0);
|
|
243
|
-
expect(state.frameCount).toBe(0);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('should use default FPS of 30', () => {
|
|
247
|
-
const loop = createAnimationLoop({
|
|
248
|
-
onFrame: () => {},
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const state = loop.getState();
|
|
252
|
-
expect(state.frameInterval).toBeCloseTo(1000 / 30, 1);
|
|
253
|
-
});
|
|
254
|
-
});
|
package/src/animation.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gossamer Animation Utilities
|
|
3
|
-
*
|
|
4
|
-
* FPS limiting, animation loop management, and timing utilities.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Animation state for tracking loop execution
|
|
9
|
-
*/
|
|
10
|
-
export interface AnimationState {
|
|
11
|
-
/** Whether animation is currently running */
|
|
12
|
-
isRunning: boolean;
|
|
13
|
-
/** Current animation frame ID */
|
|
14
|
-
frameId: number | null;
|
|
15
|
-
/** Timestamp of last frame */
|
|
16
|
-
lastFrameTime: number;
|
|
17
|
-
/** Frame interval in ms (derived from FPS) */
|
|
18
|
-
frameInterval: number;
|
|
19
|
-
/** Total elapsed time in ms */
|
|
20
|
-
elapsedTime: number;
|
|
21
|
-
/** Current frame count */
|
|
22
|
-
frameCount: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Options for creating an animation loop
|
|
27
|
-
*/
|
|
28
|
-
export interface AnimationOptions {
|
|
29
|
-
/** Target frames per second (default: 30) */
|
|
30
|
-
fps?: number;
|
|
31
|
-
/** Callback when animation starts */
|
|
32
|
-
onStart?: () => void;
|
|
33
|
-
/** Callback when animation stops */
|
|
34
|
-
onStop?: () => void;
|
|
35
|
-
/** Callback for each frame - return false to stop */
|
|
36
|
-
onFrame: (time: number, deltaTime: number, frameCount: number) => boolean | void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create a managed animation loop with FPS limiting
|
|
41
|
-
*/
|
|
42
|
-
export function createAnimationLoop(options: AnimationOptions): {
|
|
43
|
-
start: () => void;
|
|
44
|
-
stop: () => void;
|
|
45
|
-
pause: () => void;
|
|
46
|
-
resume: () => void;
|
|
47
|
-
getState: () => AnimationState;
|
|
48
|
-
} {
|
|
49
|
-
const { fps = 30, onStart, onStop, onFrame } = options;
|
|
50
|
-
|
|
51
|
-
const state: AnimationState = {
|
|
52
|
-
isRunning: false,
|
|
53
|
-
frameId: null,
|
|
54
|
-
lastFrameTime: 0,
|
|
55
|
-
frameInterval: 1000 / fps,
|
|
56
|
-
elapsedTime: 0,
|
|
57
|
-
frameCount: 0,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
let pausedTime = 0;
|
|
61
|
-
let isPaused = false;
|
|
62
|
-
|
|
63
|
-
function animate(currentTime: number): void {
|
|
64
|
-
if (!state.isRunning || isPaused) return;
|
|
65
|
-
|
|
66
|
-
const deltaTime = currentTime - state.lastFrameTime;
|
|
67
|
-
|
|
68
|
-
if (deltaTime >= state.frameInterval) {
|
|
69
|
-
state.elapsedTime += deltaTime;
|
|
70
|
-
state.frameCount++;
|
|
71
|
-
|
|
72
|
-
const continueAnimation = onFrame(currentTime, deltaTime, state.frameCount);
|
|
73
|
-
|
|
74
|
-
if (continueAnimation === false) {
|
|
75
|
-
stop();
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Adjust for frame timing drift
|
|
80
|
-
state.lastFrameTime = currentTime - (deltaTime % state.frameInterval);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
state.frameId = requestAnimationFrame(animate);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function start(): void {
|
|
87
|
-
if (state.isRunning) return;
|
|
88
|
-
|
|
89
|
-
state.isRunning = true;
|
|
90
|
-
state.lastFrameTime = performance.now();
|
|
91
|
-
state.elapsedTime = 0;
|
|
92
|
-
state.frameCount = 0;
|
|
93
|
-
isPaused = false;
|
|
94
|
-
|
|
95
|
-
onStart?.();
|
|
96
|
-
state.frameId = requestAnimationFrame(animate);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function stop(): void {
|
|
100
|
-
state.isRunning = false;
|
|
101
|
-
isPaused = false;
|
|
102
|
-
|
|
103
|
-
if (state.frameId !== null) {
|
|
104
|
-
cancelAnimationFrame(state.frameId);
|
|
105
|
-
state.frameId = null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
onStop?.();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function pause(): void {
|
|
112
|
-
if (!state.isRunning || isPaused) return;
|
|
113
|
-
|
|
114
|
-
isPaused = true;
|
|
115
|
-
pausedTime = performance.now();
|
|
116
|
-
|
|
117
|
-
if (state.frameId !== null) {
|
|
118
|
-
cancelAnimationFrame(state.frameId);
|
|
119
|
-
state.frameId = null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function resume(): void {
|
|
124
|
-
if (!state.isRunning || !isPaused) return;
|
|
125
|
-
|
|
126
|
-
isPaused = false;
|
|
127
|
-
// Adjust lastFrameTime to account for paused duration
|
|
128
|
-
state.lastFrameTime += performance.now() - pausedTime;
|
|
129
|
-
state.frameId = requestAnimationFrame(animate);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getState(): AnimationState {
|
|
133
|
-
return { ...state };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return { start, stop, pause, resume, getState };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Simple throttle function for limiting function execution
|
|
141
|
-
*/
|
|
142
|
-
export function throttle<T extends (...args: unknown[]) => unknown>(
|
|
143
|
-
fn: T,
|
|
144
|
-
limit: number
|
|
145
|
-
): (...args: Parameters<T>) => void {
|
|
146
|
-
let lastCall = 0;
|
|
147
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
148
|
-
|
|
149
|
-
return (...args: Parameters<T>): void => {
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
|
|
152
|
-
if (now - lastCall >= limit) {
|
|
153
|
-
lastCall = now;
|
|
154
|
-
fn(...args);
|
|
155
|
-
} else if (!timeout) {
|
|
156
|
-
timeout = setTimeout(() => {
|
|
157
|
-
lastCall = Date.now();
|
|
158
|
-
timeout = null;
|
|
159
|
-
fn(...args);
|
|
160
|
-
}, limit - (now - lastCall));
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Debounce function for delaying execution until activity stops
|
|
167
|
-
*/
|
|
168
|
-
export function debounce<T extends (...args: unknown[]) => unknown>(
|
|
169
|
-
fn: T,
|
|
170
|
-
delay: number
|
|
171
|
-
): (...args: Parameters<T>) => void {
|
|
172
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
173
|
-
|
|
174
|
-
return (...args: Parameters<T>): void => {
|
|
175
|
-
if (timeout) {
|
|
176
|
-
clearTimeout(timeout);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
timeout = setTimeout(() => {
|
|
180
|
-
timeout = null;
|
|
181
|
-
fn(...args);
|
|
182
|
-
}, delay);
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Calculate actual FPS from frame times
|
|
188
|
-
*/
|
|
189
|
-
export function calculateFPS(frameTimes: number[], sampleSize: number = 60): number {
|
|
190
|
-
if (frameTimes.length < 2) return 0;
|
|
191
|
-
|
|
192
|
-
const samples = frameTimes.slice(-sampleSize);
|
|
193
|
-
const totalTime = samples[samples.length - 1] - samples[0];
|
|
194
|
-
const frameCount = samples.length - 1;
|
|
195
|
-
|
|
196
|
-
if (totalTime <= 0) return 0;
|
|
197
|
-
|
|
198
|
-
return (frameCount / totalTime) * 1000;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Easing functions for smooth animations
|
|
203
|
-
*/
|
|
204
|
-
export const easings = {
|
|
205
|
-
/** Linear - no easing */
|
|
206
|
-
linear: (t: number): number => t,
|
|
207
|
-
|
|
208
|
-
/** Ease in - slow start */
|
|
209
|
-
easeIn: (t: number): number => t * t,
|
|
210
|
-
|
|
211
|
-
/** Ease out - slow end */
|
|
212
|
-
easeOut: (t: number): number => t * (2 - t),
|
|
213
|
-
|
|
214
|
-
/** Ease in/out - slow start and end */
|
|
215
|
-
easeInOut: (t: number): number => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
|
|
216
|
-
|
|
217
|
-
/** Sine ease in */
|
|
218
|
-
sineIn: (t: number): number => 1 - Math.cos((t * Math.PI) / 2),
|
|
219
|
-
|
|
220
|
-
/** Sine ease out */
|
|
221
|
-
sineOut: (t: number): number => Math.sin((t * Math.PI) / 2),
|
|
222
|
-
|
|
223
|
-
/** Sine ease in/out */
|
|
224
|
-
sineInOut: (t: number): number => -(Math.cos(Math.PI * t) - 1) / 2,
|
|
225
|
-
|
|
226
|
-
/** Bounce at end */
|
|
227
|
-
bounceOut: (t: number): number => {
|
|
228
|
-
const n1 = 7.5625;
|
|
229
|
-
const d1 = 2.75;
|
|
230
|
-
|
|
231
|
-
if (t < 1 / d1) {
|
|
232
|
-
return n1 * t * t;
|
|
233
|
-
} else if (t < 2 / d1) {
|
|
234
|
-
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
235
|
-
} else if (t < 2.5 / d1) {
|
|
236
|
-
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
237
|
-
} else {
|
|
238
|
-
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
export type EasingFunction = (t: number) => number;
|
package/src/characters.test.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for character set utilities
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
CHARACTER_SETS,
|
|
7
|
-
getCharacterSet,
|
|
8
|
-
getCharacters,
|
|
9
|
-
getCharacterSetNames,
|
|
10
|
-
createCharacterSet,
|
|
11
|
-
validateCharacterSet,
|
|
12
|
-
invertCharacters,
|
|
13
|
-
} from './characters';
|
|
14
|
-
|
|
15
|
-
describe('CHARACTER_SETS', () => {
|
|
16
|
-
it('should contain standard character set', () => {
|
|
17
|
-
expect(CHARACTER_SETS.standard).toBeDefined();
|
|
18
|
-
expect(CHARACTER_SETS.standard.characters).toBe(' .:-=+*#%@');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should have all expected character sets', () => {
|
|
22
|
-
const expectedSets = [
|
|
23
|
-
'standard',
|
|
24
|
-
'dense',
|
|
25
|
-
'minimal',
|
|
26
|
-
'grove',
|
|
27
|
-
'dots',
|
|
28
|
-
'blocks',
|
|
29
|
-
'lines',
|
|
30
|
-
'stars',
|
|
31
|
-
'nature',
|
|
32
|
-
'weather',
|
|
33
|
-
'binary',
|
|
34
|
-
'math',
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
for (const setName of expectedSets) {
|
|
38
|
-
expect(CHARACTER_SETS[setName]).toBeDefined();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should have all character sets start with space', () => {
|
|
43
|
-
for (const [name, set] of Object.entries(CHARACTER_SETS)) {
|
|
44
|
-
expect(set.characters[0]).toBe(' ');
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('getCharacterSet', () => {
|
|
50
|
-
it('should return character set by name', () => {
|
|
51
|
-
const result = getCharacterSet('standard');
|
|
52
|
-
expect(result).toBeDefined();
|
|
53
|
-
expect(result?.name).toBe('Standard');
|
|
54
|
-
expect(result?.characters).toBe(' .:-=+*#%@');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should return undefined for unknown set', () => {
|
|
58
|
-
const result = getCharacterSet('nonexistent');
|
|
59
|
-
expect(result).toBeUndefined();
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('getCharacters', () => {
|
|
64
|
-
it('should return characters string for valid set', () => {
|
|
65
|
-
expect(getCharacters('minimal')).toBe(' .:*#');
|
|
66
|
-
expect(getCharacters('blocks')).toBe(' ░▒▓█');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should return standard characters for unknown set', () => {
|
|
70
|
-
expect(getCharacters('nonexistent')).toBe(' .:-=+*#%@');
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('getCharacterSetNames', () => {
|
|
75
|
-
it('should return array of all set names', () => {
|
|
76
|
-
const names = getCharacterSetNames();
|
|
77
|
-
expect(Array.isArray(names)).toBe(true);
|
|
78
|
-
expect(names).toContain('standard');
|
|
79
|
-
expect(names).toContain('minimal');
|
|
80
|
-
expect(names).toContain('blocks');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should return same count as CHARACTER_SETS keys', () => {
|
|
84
|
-
const names = getCharacterSetNames();
|
|
85
|
-
expect(names.length).toBe(Object.keys(CHARACTER_SETS).length);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('createCharacterSet', () => {
|
|
90
|
-
it('should create character set with all properties', () => {
|
|
91
|
-
const result = createCharacterSet(
|
|
92
|
-
'custom',
|
|
93
|
-
' abc',
|
|
94
|
-
'A custom set',
|
|
95
|
-
['testing']
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
expect(result.name).toBe('custom');
|
|
99
|
-
expect(result.characters).toBe(' abc');
|
|
100
|
-
expect(result.description).toBe('A custom set');
|
|
101
|
-
expect(result.bestFor).toEqual(['testing']);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should use defaults for optional parameters', () => {
|
|
105
|
-
const result = createCharacterSet('minimal', ' xy');
|
|
106
|
-
|
|
107
|
-
expect(result.name).toBe('minimal');
|
|
108
|
-
expect(result.characters).toBe(' xy');
|
|
109
|
-
expect(result.description).toBe('');
|
|
110
|
-
expect(result.bestFor).toEqual([]);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('validateCharacterSet', () => {
|
|
115
|
-
it('should return true for valid character sets', () => {
|
|
116
|
-
expect(validateCharacterSet(' .:-=+*#%@')).toBe(true);
|
|
117
|
-
expect(validateCharacterSet(' ab')).toBe(true);
|
|
118
|
-
expect(validateCharacterSet(' ░▒▓█')).toBe(true);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should return false for too short sets', () => {
|
|
122
|
-
expect(validateCharacterSet('')).toBe(false);
|
|
123
|
-
expect(validateCharacterSet(' ')).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should return false for sets not starting with space', () => {
|
|
127
|
-
expect(validateCharacterSet('abc')).toBe(false);
|
|
128
|
-
expect(validateCharacterSet('.:-=')).toBe(false);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('invertCharacters', () => {
|
|
133
|
-
it('should reverse character string', () => {
|
|
134
|
-
expect(invertCharacters(' abc')).toBe('cba ');
|
|
135
|
-
expect(invertCharacters(' .:-=')).toBe('=-:. ');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should handle single character', () => {
|
|
139
|
-
expect(invertCharacters(' ')).toBe(' ');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should be reversible', () => {
|
|
143
|
-
const original = ' .:-=+*#%@';
|
|
144
|
-
const inverted = invertCharacters(original);
|
|
145
|
-
const restored = invertCharacters(inverted);
|
|
146
|
-
expect(restored).toBe(original);
|
|
147
|
-
});
|
|
148
|
-
});
|