fancy-ui-svelte 0.6.0 → 0.7.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.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Theme registry (docs-site theming data).
3
+ *
4
+ * A named theme is a set of CSS-variable overrides applied on top of the base
5
+ * light/dark token sets defined in `src/routes/layout.css`. The theme store
6
+ * (`src/lib/stores/theme.svelte.ts`) applies a theme by setting these variables
7
+ * inline on `<html>`, toggling the `.dark` class per `colorScheme`, and setting
8
+ * a `data-theme` attribute.
9
+ *
10
+ * NOTE: this module is docs/theming data only. It is NOT imported by any shipped
11
+ * component in `src/lib/fancy-ui/*`, so it does not affect the published package.
12
+ */
13
+ export type ColorScheme = "light" | "dark";
14
+ export interface ThemeDef {
15
+ /** Stable id, used as the `data-theme` value and persistence key. */
16
+ name: string;
17
+ /** Human label shown in the switcher. */
18
+ label: string;
19
+ /** Whether the theme reads as light or dark (drives the `.dark` class). */
20
+ colorScheme: ColorScheme;
21
+ /** CSS custom-property overrides applied on top of the base light/dark set. */
22
+ tokens: Record<string, string>;
23
+ /** Four representative colors for the switcher swatch. */
24
+ swatch: [string, string, string, string];
25
+ }
26
+ /** Every CSS variable a theme may set. The store clears these before applying a theme. */
27
+ export declare const MANAGED_VARS: readonly ["--background", "--foreground", "--card", "--card-foreground", "--popover", "--popover-foreground", "--primary", "--primary-foreground", "--secondary", "--secondary-foreground", "--muted", "--muted-foreground", "--accent", "--accent-foreground", "--border", "--input", "--ring", "--radius", "--rainbow-1", "--rainbow-2", "--rainbow-3", "--rainbow-4", "--rainbow-5", "--sidebar", "--sidebar-foreground", "--sidebar-primary", "--sidebar-primary-foreground", "--sidebar-accent", "--sidebar-accent-foreground", "--sidebar-border"];
28
+ /**
29
+ * The theme list.
30
+ *
31
+ * `light` and `dark` carry no token overrides — they are the base look defined
32
+ * by `:root` / `.dark` in `layout.css`. The remaining themes are self-contained.
33
+ * `sunset` / `ocean` / `mono` mirror the theme-generator presets.
34
+ */
35
+ export declare const themes: ThemeDef[];
36
+ export declare function getTheme(name: string): ThemeDef | undefined;
37
+ export declare const themeNames: string[];
38
+ export declare const DEFAULT_THEME = "light";
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Theme registry (docs-site theming data).
3
+ *
4
+ * A named theme is a set of CSS-variable overrides applied on top of the base
5
+ * light/dark token sets defined in `src/routes/layout.css`. The theme store
6
+ * (`src/lib/stores/theme.svelte.ts`) applies a theme by setting these variables
7
+ * inline on `<html>`, toggling the `.dark` class per `colorScheme`, and setting
8
+ * a `data-theme` attribute.
9
+ *
10
+ * NOTE: this module is docs/theming data only. It is NOT imported by any shipped
11
+ * component in `src/lib/fancy-ui/*`, so it does not affect the published package.
12
+ */
13
+ /** Every CSS variable a theme may set. The store clears these before applying a theme. */
14
+ export const MANAGED_VARS = [
15
+ "--background",
16
+ "--foreground",
17
+ "--card",
18
+ "--card-foreground",
19
+ "--popover",
20
+ "--popover-foreground",
21
+ "--primary",
22
+ "--primary-foreground",
23
+ "--secondary",
24
+ "--secondary-foreground",
25
+ "--muted",
26
+ "--muted-foreground",
27
+ "--accent",
28
+ "--accent-foreground",
29
+ "--border",
30
+ "--input",
31
+ "--ring",
32
+ "--radius",
33
+ "--rainbow-1",
34
+ "--rainbow-2",
35
+ "--rainbow-3",
36
+ "--rainbow-4",
37
+ "--rainbow-5",
38
+ "--sidebar",
39
+ "--sidebar-foreground",
40
+ "--sidebar-primary",
41
+ "--sidebar-primary-foreground",
42
+ "--sidebar-accent",
43
+ "--sidebar-accent-foreground",
44
+ "--sidebar-border",
45
+ ];
46
+ const DEFAULT_RAINBOW = [
47
+ "hsl(0 100% 63%)",
48
+ "hsl(270 100% 63%)",
49
+ "hsl(210 100% 63%)",
50
+ "hsl(195 100% 63%)",
51
+ "hsl(90 100% 63%)",
52
+ ];
53
+ /** Expand a compact spec into a full ThemeDef with a complete token map. */
54
+ function defineTheme(s) {
55
+ const card = s.card ?? s.bg;
56
+ const secondary = s.secondary ?? s.accent;
57
+ const secondaryFg = s.secondaryFg ?? s.accentFg;
58
+ const muted = s.muted ?? s.border ?? card;
59
+ const mutedFg = s.mutedFg ?? s.fg;
60
+ const border = s.border ?? s.muted ?? s.fg;
61
+ const ring = s.ring ?? s.primary;
62
+ const radius = s.radius ?? "0.625rem";
63
+ const rainbow = s.rainbow ?? DEFAULT_RAINBOW;
64
+ return {
65
+ name: s.name,
66
+ label: s.label,
67
+ colorScheme: s.colorScheme,
68
+ swatch: [s.primary, s.accent, secondary, s.bg],
69
+ tokens: {
70
+ "--background": s.bg,
71
+ "--foreground": s.fg,
72
+ "--card": card,
73
+ "--card-foreground": s.fg,
74
+ "--popover": card,
75
+ "--popover-foreground": s.fg,
76
+ "--primary": s.primary,
77
+ "--primary-foreground": s.primaryFg,
78
+ "--secondary": secondary,
79
+ "--secondary-foreground": secondaryFg,
80
+ "--muted": muted,
81
+ "--muted-foreground": mutedFg,
82
+ "--accent": s.accent,
83
+ "--accent-foreground": s.accentFg,
84
+ "--border": border,
85
+ "--input": border,
86
+ "--ring": ring,
87
+ "--radius": radius,
88
+ "--rainbow-1": rainbow[0],
89
+ "--rainbow-2": rainbow[1],
90
+ "--rainbow-3": rainbow[2],
91
+ "--rainbow-4": rainbow[3],
92
+ "--rainbow-5": rainbow[4],
93
+ "--sidebar": card,
94
+ "--sidebar-foreground": s.fg,
95
+ "--sidebar-primary": s.primary,
96
+ "--sidebar-primary-foreground": s.primaryFg,
97
+ "--sidebar-accent": muted,
98
+ "--sidebar-accent-foreground": s.fg,
99
+ "--sidebar-border": border,
100
+ },
101
+ };
102
+ }
103
+ /**
104
+ * The theme list.
105
+ *
106
+ * `light` and `dark` carry no token overrides — they are the base look defined
107
+ * by `:root` / `.dark` in `layout.css`. The remaining themes are self-contained.
108
+ * `sunset` / `ocean` / `mono` mirror the theme-generator presets.
109
+ */
110
+ export const themes = [
111
+ {
112
+ name: "light",
113
+ label: "Light",
114
+ colorScheme: "light",
115
+ tokens: {},
116
+ swatch: [
117
+ "oklch(0.208 0.042 265.755)",
118
+ "oklch(0.65 0.15 265)",
119
+ "oklch(0.929 0.013 255.508)",
120
+ "#ffffff",
121
+ ],
122
+ },
123
+ {
124
+ name: "dark",
125
+ label: "Dark",
126
+ colorScheme: "dark",
127
+ tokens: {},
128
+ swatch: [
129
+ "oklch(0.929 0.013 255.508)",
130
+ "oklch(0.7 0.15 265)",
131
+ "oklch(0.15 0.005 260)",
132
+ "#0a0a0a",
133
+ ],
134
+ },
135
+ defineTheme({
136
+ name: "cupcake",
137
+ label: "Cupcake",
138
+ colorScheme: "light",
139
+ bg: "#faf7f5",
140
+ fg: "#291334",
141
+ card: "#ffffff",
142
+ primary: "#65c3c8",
143
+ primaryFg: "#004b50",
144
+ secondary: "#ef9fbc",
145
+ secondaryFg: "#5c2437",
146
+ accent: "#eeaf3a",
147
+ accentFg: "#4a3000",
148
+ border: "#e7e2df",
149
+ radius: "1rem",
150
+ }),
151
+ defineTheme({
152
+ name: "emerald",
153
+ label: "Emerald",
154
+ colorScheme: "light",
155
+ bg: "#ffffff",
156
+ fg: "#333c4d",
157
+ primary: "#66cc8a",
158
+ primaryFg: "#0c3d24",
159
+ secondary: "#377cfb",
160
+ secondaryFg: "#ffffff",
161
+ accent: "#ea5234",
162
+ accentFg: "#ffffff",
163
+ border: "#e5e7eb",
164
+ radius: "0.5rem",
165
+ }),
166
+ defineTheme({
167
+ name: "corporate",
168
+ label: "Corporate",
169
+ colorScheme: "light",
170
+ bg: "#ffffff",
171
+ fg: "#181a2a",
172
+ primary: "#4b6bfb",
173
+ primaryFg: "#ffffff",
174
+ secondary: "#7b92b2",
175
+ secondaryFg: "#ffffff",
176
+ accent: "#67cba0",
177
+ accentFg: "#00382a",
178
+ border: "#e5e7eb",
179
+ radius: "0.25rem",
180
+ }),
181
+ defineTheme({
182
+ name: "retro",
183
+ label: "Retro",
184
+ colorScheme: "light",
185
+ bg: "#ece3ca",
186
+ fg: "#282425",
187
+ card: "#e4d8b4",
188
+ primary: "#ef9995",
189
+ primaryFg: "#282425",
190
+ secondary: "#a4cbb4",
191
+ secondaryFg: "#282425",
192
+ accent: "#dc8850",
193
+ accentFg: "#282425",
194
+ border: "#d1c7a3",
195
+ radius: "0.5rem",
196
+ }),
197
+ defineTheme({
198
+ name: "cyberpunk",
199
+ label: "Cyberpunk",
200
+ colorScheme: "light",
201
+ bg: "#fff248",
202
+ fg: "#0f0f00",
203
+ card: "#fdf700",
204
+ primary: "#ff7598",
205
+ primaryFg: "#330014",
206
+ secondary: "#75d1f0",
207
+ secondaryFg: "#00222e",
208
+ accent: "#c07eec",
209
+ accentFg: "#1e0033",
210
+ border: "#e6dc00",
211
+ radius: "0rem",
212
+ rainbow: [
213
+ "hsl(330 100% 63%)",
214
+ "hsl(280 100% 63%)",
215
+ "hsl(190 100% 60%)",
216
+ "hsl(55 100% 55%)",
217
+ "hsl(150 100% 55%)",
218
+ ],
219
+ }),
220
+ defineTheme({
221
+ name: "synthwave",
222
+ label: "Synthwave",
223
+ colorScheme: "dark",
224
+ bg: "#1a103d",
225
+ fg: "#f9f7fd",
226
+ card: "#241650",
227
+ primary: "#e779c1",
228
+ primaryFg: "#2a0e2b",
229
+ secondary: "#58c7f3",
230
+ secondaryFg: "#003044",
231
+ accent: "#f3cc30",
232
+ accentFg: "#3a2e00",
233
+ muted: "#2b1c5e",
234
+ mutedFg: "#b9a9e6",
235
+ border: "#3a2a6b",
236
+ radius: "0.75rem",
237
+ rainbow: [
238
+ "hsl(320 90% 65%)",
239
+ "hsl(280 90% 68%)",
240
+ "hsl(195 95% 60%)",
241
+ "hsl(48 95% 58%)",
242
+ "hsl(160 80% 55%)",
243
+ ],
244
+ }),
245
+ defineTheme({
246
+ name: "dracula",
247
+ label: "Dracula",
248
+ colorScheme: "dark",
249
+ bg: "#282a36",
250
+ fg: "#f8f8f2",
251
+ card: "#343746",
252
+ primary: "#ff79c6",
253
+ primaryFg: "#28202b",
254
+ secondary: "#bd93f9",
255
+ secondaryFg: "#241a33",
256
+ accent: "#ffb86c",
257
+ accentFg: "#3a2600",
258
+ muted: "#3a3d4d",
259
+ mutedFg: "#c7c9d6",
260
+ border: "#44475a",
261
+ radius: "0.5rem",
262
+ }),
263
+ defineTheme({
264
+ name: "forest",
265
+ label: "Forest",
266
+ colorScheme: "dark",
267
+ bg: "#171212",
268
+ fg: "#e0ded9",
269
+ card: "#1f1a1a",
270
+ primary: "#1eb854",
271
+ primaryFg: "#04220f",
272
+ secondary: "#1fd65f",
273
+ secondaryFg: "#04220f",
274
+ accent: "#d99330",
275
+ accentFg: "#2b1c00",
276
+ muted: "#26201f",
277
+ mutedFg: "#b3b0aa",
278
+ border: "#2e2726",
279
+ radius: "0.75rem",
280
+ }),
281
+ defineTheme({
282
+ name: "sunset",
283
+ label: "Sunset",
284
+ colorScheme: "dark",
285
+ bg: "#1c1410",
286
+ fg: "#f5e9e0",
287
+ card: "#261a13",
288
+ primary: "oklch(0.62 0.19 30)",
289
+ primaryFg: "#22100a",
290
+ secondary: "oklch(0.7 0.17 8)",
291
+ secondaryFg: "#2a0d0a",
292
+ accent: "oklch(0.7 0.17 8)",
293
+ accentFg: "#2a0d0a",
294
+ muted: "#33241b",
295
+ mutedFg: "#d8c3b4",
296
+ border: "#3a2a20",
297
+ radius: "1rem",
298
+ rainbow: [
299
+ "hsl(12 100% 63%)",
300
+ "hsl(30 100% 63%)",
301
+ "hsl(350 100% 63%)",
302
+ "hsl(320 100% 63%)",
303
+ "hsl(50 100% 63%)",
304
+ ],
305
+ }),
306
+ defineTheme({
307
+ name: "ocean",
308
+ label: "Ocean",
309
+ colorScheme: "light",
310
+ bg: "#f4fbff",
311
+ fg: "#0b2a3a",
312
+ card: "#ffffff",
313
+ primary: "oklch(0.55 0.13 230)",
314
+ primaryFg: "#ffffff",
315
+ secondary: "oklch(0.72 0.13 190)",
316
+ secondaryFg: "#00303a",
317
+ accent: "oklch(0.72 0.13 190)",
318
+ accentFg: "#00303a",
319
+ muted: "#e3f1f9",
320
+ mutedFg: "#3a5c6b",
321
+ border: "#d3e7f0",
322
+ radius: "0.5rem",
323
+ rainbow: [
324
+ "hsl(200 100% 63%)",
325
+ "hsl(220 100% 63%)",
326
+ "hsl(180 100% 63%)",
327
+ "hsl(240 100% 63%)",
328
+ "hsl(160 100% 63%)",
329
+ ],
330
+ }),
331
+ defineTheme({
332
+ name: "mono",
333
+ label: "Mono",
334
+ colorScheme: "dark",
335
+ bg: "#0a0a0a",
336
+ fg: "#fafafa",
337
+ card: "#161616",
338
+ primary: "oklch(0.32 0 0)",
339
+ primaryFg: "#fafafa",
340
+ secondary: "oklch(0.6 0 0)",
341
+ secondaryFg: "#0a0a0a",
342
+ accent: "oklch(0.6 0 0)",
343
+ accentFg: "#0a0a0a",
344
+ muted: "#1e1e1e",
345
+ mutedFg: "#a3a3a3",
346
+ border: "#2a2a2a",
347
+ radius: "0.25rem",
348
+ rainbow: ["hsl(0 0% 80%)", "hsl(0 0% 65%)", "hsl(0 0% 90%)", "hsl(0 0% 55%)", "hsl(0 0% 75%)"],
349
+ }),
350
+ ];
351
+ const themeByName = new Map(themes.map((t) => [t.name, t]));
352
+ export function getTheme(name) {
353
+ return themeByName.get(name);
354
+ }
355
+ export const themeNames = themes.map((t) => t.name);
356
+ export const DEFAULT_THEME = "light";
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Animation Utilities
3
+ *
4
+ * Helpers for creating smooth animations, spring physics, and easing functions.
5
+ * Used across many FancyUI components for consistent animation behavior.
6
+ */
7
+ export interface SpringConfig {
8
+ /** Stiffness of the spring (default: 100) */
9
+ stiffness: number;
10
+ /** Damping ratio (default: 10) */
11
+ damping: number;
12
+ /** Mass of the object (default: 1) */
13
+ mass: number;
14
+ }
15
+ export interface AnimationOptions {
16
+ /** Duration in milliseconds */
17
+ duration: number;
18
+ /** Easing function */
19
+ easing?: (t: number) => number;
20
+ /** Delay before starting in milliseconds */
21
+ delay?: number;
22
+ }
23
+ /**
24
+ * Pre-configured spring settings for common use cases
25
+ */
26
+ export declare const springPresets: Record<string, SpringConfig>;
27
+ /**
28
+ * Linear easing (no easing)
29
+ */
30
+ export declare function linear(t: number): number;
31
+ /**
32
+ * Ease in (accelerating)
33
+ */
34
+ export declare function easeIn(t: number): number;
35
+ /**
36
+ * Ease out (decelerating)
37
+ */
38
+ export declare function easeOut(t: number): number;
39
+ /**
40
+ * Ease in-out (accelerate then decelerate)
41
+ */
42
+ export declare function easeInOut(t: number): number;
43
+ /**
44
+ * Cubic ease in
45
+ */
46
+ export declare function easeInCubic(t: number): number;
47
+ /**
48
+ * Cubic ease out
49
+ */
50
+ export declare function easeOutCubic(t: number): number;
51
+ /**
52
+ * Cubic ease in-out
53
+ */
54
+ export declare function easeInOutCubic(t: number): number;
55
+ /**
56
+ * Bounce ease out
57
+ */
58
+ export declare function easeOutBounce(t: number): number;
59
+ /**
60
+ * Elastic ease out
61
+ */
62
+ export declare function easeOutElastic(t: number): number;
63
+ /**
64
+ * Back ease out (overshoots then settles)
65
+ */
66
+ export declare function easeOutBack(t: number): number;
67
+ /**
68
+ * Interpolate between two values
69
+ */
70
+ export declare function lerp(start: number, end: number, t: number): number;
71
+ /**
72
+ * Clamp a value between min and max
73
+ */
74
+ export declare function clamp(value: number, min: number, max: number): number;
75
+ /**
76
+ * Map a value from one range to another
77
+ */
78
+ export declare function mapRange(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number;
79
+ /**
80
+ * Calculate spring physics for a single step
81
+ */
82
+ export declare function springStep(current: number, target: number, velocity: number, config: SpringConfig, deltaTime: number): {
83
+ position: number;
84
+ velocity: number;
85
+ };
86
+ /**
87
+ * Check if a spring animation is at rest
88
+ */
89
+ export declare function isSpringAtRest(current: number, target: number, velocity: number, threshold?: number): boolean;
90
+ /**
91
+ * Create a staggered delay for list animations
92
+ */
93
+ export declare function staggerDelay(index: number, baseDelay?: number): number;
94
+ /**
95
+ * Calculate animation progress based on elapsed time
96
+ */
97
+ export declare function getProgress(elapsed: number, duration: number): number;
98
+ /**
99
+ * Get duration value from CSS custom property or fallback
100
+ */
101
+ export declare function getDurationFromCSS(property: "--animation-fast" | "--animation-normal" | "--animation-slow" | "--animation-slower", fallback?: number): number;
102
+ /**
103
+ * Debounce an animation frame callback
104
+ */
105
+ export declare function rafDebounce<T extends (...args: unknown[]) => void>(fn: T): T;
106
+ /**
107
+ * Throttle function calls to animation frames
108
+ */
109
+ export declare function rafThrottle<T extends (...args: unknown[]) => void>(fn: T): T;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Animation Utilities
3
+ *
4
+ * Helpers for creating smooth animations, spring physics, and easing functions.
5
+ * Used across many FancyUI components for consistent animation behavior.
6
+ */
7
+ // =============================================================================
8
+ // Spring Presets
9
+ // =============================================================================
10
+ /**
11
+ * Pre-configured spring settings for common use cases
12
+ */
13
+ export const springPresets = {
14
+ /** Default balanced spring */
15
+ default: { stiffness: 100, damping: 10, mass: 1 },
16
+ /** Gentle, slow spring */
17
+ gentle: { stiffness: 50, damping: 14, mass: 1 },
18
+ /** Wobbly, bouncy spring */
19
+ wobbly: { stiffness: 180, damping: 12, mass: 1 },
20
+ /** Stiff, snappy spring */
21
+ stiff: { stiffness: 210, damping: 20, mass: 1 },
22
+ /** Very slow, heavy spring */
23
+ slow: { stiffness: 50, damping: 20, mass: 1 },
24
+ /** Quick, responsive spring */
25
+ quick: { stiffness: 300, damping: 25, mass: 1 },
26
+ };
27
+ // =============================================================================
28
+ // Easing Functions
29
+ // =============================================================================
30
+ /**
31
+ * Linear easing (no easing)
32
+ */
33
+ export function linear(t) {
34
+ return t;
35
+ }
36
+ /**
37
+ * Ease in (accelerating)
38
+ */
39
+ export function easeIn(t) {
40
+ return t * t;
41
+ }
42
+ /**
43
+ * Ease out (decelerating)
44
+ */
45
+ export function easeOut(t) {
46
+ return t * (2 - t);
47
+ }
48
+ /**
49
+ * Ease in-out (accelerate then decelerate)
50
+ */
51
+ export function easeInOut(t) {
52
+ return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
53
+ }
54
+ /**
55
+ * Cubic ease in
56
+ */
57
+ export function easeInCubic(t) {
58
+ return t * t * t;
59
+ }
60
+ /**
61
+ * Cubic ease out
62
+ */
63
+ export function easeOutCubic(t) {
64
+ return 1 - Math.pow(1 - t, 3);
65
+ }
66
+ /**
67
+ * Cubic ease in-out
68
+ */
69
+ export function easeInOutCubic(t) {
70
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
71
+ }
72
+ /**
73
+ * Bounce ease out
74
+ */
75
+ export function easeOutBounce(t) {
76
+ const n1 = 7.5625;
77
+ const d1 = 2.75;
78
+ if (t < 1 / d1) {
79
+ return n1 * t * t;
80
+ }
81
+ else if (t < 2 / d1) {
82
+ return n1 * (t -= 1.5 / d1) * t + 0.75;
83
+ }
84
+ else if (t < 2.5 / d1) {
85
+ return n1 * (t -= 2.25 / d1) * t + 0.9375;
86
+ }
87
+ else {
88
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
89
+ }
90
+ }
91
+ /**
92
+ * Elastic ease out
93
+ */
94
+ export function easeOutElastic(t) {
95
+ const c4 = (2 * Math.PI) / 3;
96
+ return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
97
+ }
98
+ /**
99
+ * Back ease out (overshoots then settles)
100
+ */
101
+ export function easeOutBack(t) {
102
+ const c1 = 1.70158;
103
+ const c3 = c1 + 1;
104
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
105
+ }
106
+ // =============================================================================
107
+ // Animation Helpers
108
+ // =============================================================================
109
+ /**
110
+ * Interpolate between two values
111
+ */
112
+ export function lerp(start, end, t) {
113
+ return start + (end - start) * t;
114
+ }
115
+ /**
116
+ * Clamp a value between min and max
117
+ */
118
+ export function clamp(value, min, max) {
119
+ return Math.min(Math.max(value, min), max);
120
+ }
121
+ /**
122
+ * Map a value from one range to another
123
+ */
124
+ export function mapRange(value, inMin, inMax, outMin, outMax) {
125
+ return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
126
+ }
127
+ /**
128
+ * Calculate spring physics for a single step
129
+ */
130
+ export function springStep(current, target, velocity, config, deltaTime) {
131
+ const { stiffness, damping, mass } = config;
132
+ // Spring force: F = -kx - cv
133
+ const springForce = -stiffness * (current - target);
134
+ const dampingForce = -damping * velocity;
135
+ const acceleration = (springForce + dampingForce) / mass;
136
+ const newVelocity = velocity + acceleration * deltaTime;
137
+ const newPosition = current + newVelocity * deltaTime;
138
+ return { position: newPosition, velocity: newVelocity };
139
+ }
140
+ /**
141
+ * Check if a spring animation is at rest
142
+ */
143
+ export function isSpringAtRest(current, target, velocity, threshold = 0.001) {
144
+ return Math.abs(current - target) < threshold && Math.abs(velocity) < threshold;
145
+ }
146
+ /**
147
+ * Create a staggered delay for list animations
148
+ */
149
+ export function staggerDelay(index, baseDelay = 50) {
150
+ return index * baseDelay;
151
+ }
152
+ /**
153
+ * Calculate animation progress based on elapsed time
154
+ */
155
+ export function getProgress(elapsed, duration) {
156
+ return clamp(elapsed / duration, 0, 1);
157
+ }
158
+ /**
159
+ * Get duration value from CSS custom property or fallback
160
+ */
161
+ export function getDurationFromCSS(property, fallback = 300) {
162
+ if (typeof window === "undefined")
163
+ return fallback;
164
+ const value = getComputedStyle(document.documentElement).getPropertyValue(property).trim();
165
+ const parsed = parseInt(value, 10);
166
+ return isNaN(parsed) ? fallback : parsed;
167
+ }
168
+ /**
169
+ * Debounce an animation frame callback
170
+ */
171
+ export function rafDebounce(fn) {
172
+ let frameId = null;
173
+ return ((...args) => {
174
+ if (frameId !== null) {
175
+ cancelAnimationFrame(frameId);
176
+ }
177
+ frameId = requestAnimationFrame(() => {
178
+ fn(...args);
179
+ frameId = null;
180
+ });
181
+ });
182
+ }
183
+ /**
184
+ * Throttle function calls to animation frames
185
+ */
186
+ export function rafThrottle(fn) {
187
+ let frameId = null;
188
+ let lastArgs = null;
189
+ return ((...args) => {
190
+ lastArgs = args;
191
+ if (frameId === null) {
192
+ frameId = requestAnimationFrame(() => {
193
+ fn(...lastArgs);
194
+ frameId = null;
195
+ });
196
+ }
197
+ });
198
+ }