@ansh-ui/core 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.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/dist/behaviours.cjs +64 -0
  3. package/dist/behaviours.cjs.map +1 -0
  4. package/dist/behaviours.d.cts +3 -0
  5. package/dist/behaviours.d.ts +3 -0
  6. package/dist/behaviours.js +7 -0
  7. package/dist/behaviours.js.map +1 -0
  8. package/dist/chunk-23GSSIMK.cjs +439 -0
  9. package/dist/chunk-23GSSIMK.cjs.map +1 -0
  10. package/dist/chunk-56NNBIAO.js +2446 -0
  11. package/dist/chunk-56NNBIAO.js.map +1 -0
  12. package/dist/chunk-6PFYGJWD.cjs +136 -0
  13. package/dist/chunk-6PFYGJWD.cjs.map +1 -0
  14. package/dist/chunk-7MEIXWPH.js +313 -0
  15. package/dist/chunk-7MEIXWPH.js.map +1 -0
  16. package/dist/chunk-7MYTSAPR.js +83 -0
  17. package/dist/chunk-7MYTSAPR.js.map +1 -0
  18. package/dist/chunk-BOQQHICI.js +1036 -0
  19. package/dist/chunk-BOQQHICI.js.map +1 -0
  20. package/dist/chunk-C6XPQO2O.js +1170 -0
  21. package/dist/chunk-C6XPQO2O.js.map +1 -0
  22. package/dist/chunk-DXJ63QAR.cjs +723 -0
  23. package/dist/chunk-DXJ63QAR.cjs.map +1 -0
  24. package/dist/chunk-FAVIEION.js +705 -0
  25. package/dist/chunk-FAVIEION.js.map +1 -0
  26. package/dist/chunk-FNR35HTL.cjs +107 -0
  27. package/dist/chunk-FNR35HTL.cjs.map +1 -0
  28. package/dist/chunk-GHBRGMSO.cjs +91 -0
  29. package/dist/chunk-GHBRGMSO.cjs.map +1 -0
  30. package/dist/chunk-GJRIHN76.cjs +359 -0
  31. package/dist/chunk-GJRIHN76.cjs.map +1 -0
  32. package/dist/chunk-ILKUL7GD.cjs +115 -0
  33. package/dist/chunk-ILKUL7GD.cjs.map +1 -0
  34. package/dist/chunk-KTUBCQC3.js +409 -0
  35. package/dist/chunk-KTUBCQC3.js.map +1 -0
  36. package/dist/chunk-KU2C6GEA.js +90 -0
  37. package/dist/chunk-KU2C6GEA.js.map +1 -0
  38. package/dist/chunk-LGA3N3YJ.cjs +413 -0
  39. package/dist/chunk-LGA3N3YJ.cjs.map +1 -0
  40. package/dist/chunk-MMO7KO53.cjs +1060 -0
  41. package/dist/chunk-MMO7KO53.cjs.map +1 -0
  42. package/dist/chunk-NSXKCJJ7.js +109 -0
  43. package/dist/chunk-NSXKCJJ7.js.map +1 -0
  44. package/dist/chunk-QALZKHWH.js +387 -0
  45. package/dist/chunk-QALZKHWH.js.map +1 -0
  46. package/dist/chunk-R7UJ7MWP.cjs +2479 -0
  47. package/dist/chunk-R7UJ7MWP.cjs.map +1 -0
  48. package/dist/chunk-UJBXY6QC.cjs +1186 -0
  49. package/dist/chunk-UJBXY6QC.cjs.map +1 -0
  50. package/dist/chunk-YFQXRC5R.js +99 -0
  51. package/dist/chunk-YFQXRC5R.js.map +1 -0
  52. package/dist/color.cjs +257 -0
  53. package/dist/color.cjs.map +1 -0
  54. package/dist/color.d.cts +303 -0
  55. package/dist/color.d.ts +303 -0
  56. package/dist/color.js +8 -0
  57. package/dist/color.js.map +1 -0
  58. package/dist/easing.cjs +189 -0
  59. package/dist/easing.cjs.map +1 -0
  60. package/dist/easing.d.cts +134 -0
  61. package/dist/easing.d.ts +134 -0
  62. package/dist/easing.js +4 -0
  63. package/dist/easing.js.map +1 -0
  64. package/dist/effects.cjs +99 -0
  65. package/dist/effects.cjs.map +1 -0
  66. package/dist/effects.d.cts +5 -0
  67. package/dist/effects.d.ts +5 -0
  68. package/dist/effects.js +10 -0
  69. package/dist/effects.js.map +1 -0
  70. package/dist/index-CCdpefMp.d.ts +967 -0
  71. package/dist/index-CQEEPu-D.d.ts +435 -0
  72. package/dist/index-DHOfsEIn.d.cts +967 -0
  73. package/dist/index-Dl8GCsya.d.cts +435 -0
  74. package/dist/index.cjs +2206 -0
  75. package/dist/index.cjs.map +1 -0
  76. package/dist/index.d.cts +1122 -0
  77. package/dist/index.d.ts +1122 -0
  78. package/dist/index.js +1182 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/math.cjs +238 -0
  81. package/dist/math.cjs.map +1 -0
  82. package/dist/math.d.cts +120 -0
  83. package/dist/math.d.ts +120 -0
  84. package/dist/math.js +5 -0
  85. package/dist/math.js.map +1 -0
  86. package/dist/noise.cjs +78 -0
  87. package/dist/noise.cjs.map +1 -0
  88. package/dist/noise.d.cts +190 -0
  89. package/dist/noise.d.ts +190 -0
  90. package/dist/noise.js +5 -0
  91. package/dist/noise.js.map +1 -0
  92. package/dist/palette-6HkVltYU.d.cts +219 -0
  93. package/dist/palette-6HkVltYU.d.ts +219 -0
  94. package/dist/random--4mSc5Lp.d.cts +75 -0
  95. package/dist/random--4mSc5Lp.d.ts +75 -0
  96. package/dist/reduced-motion-D0LUgZle.d.cts +120 -0
  97. package/dist/reduced-motion-D0LUgZle.d.ts +120 -0
  98. package/dist/types-B1FLLf3-.d.cts +21 -0
  99. package/dist/types-B1FLLf3-.d.ts +21 -0
  100. package/package.json +78 -0
@@ -0,0 +1,1122 @@
1
+ export { EPSILON, HALF_PI, PI, Rect, TAU, Vec2, addScaledVec2, addVec2, addVec2Into, angleDelta, angleOfVec2, approximately, clamp, cloneVec2, crossVec2, damp, degrees, distanceSqVec2, distanceVec2, dotVec2, finite, fromAngle, inverseLerp, lengthSqVec2, lengthVec2, lerp, lerpAngle, lerpVec2, lerpVec2Into, limitVec2, mean, moveTowards, normalizeInRect, normalizeVec2, percentileSorted, pingPong, radians, rectContains, reflectVec2, remap, rotateVec2, roundTo, scaleVec2, scaleVec2Into, setVec2, smootherstep, smoothstep, snap, subVec2, subVec2Into, vec2, wrap } from './math.cjs';
2
+ export { R as Rng, c as createRng, d as deriveSeed, g as goldenRatioSequence, h as hashString, j as jitter, r as r2Sequence, w as weightedPick } from './random--4mSc5Lp.cjs';
3
+ import { E as EasingInput } from './types-B1FLLf3-.cjs';
4
+ export { a as EasingFn, b as EasingName } from './types-B1FLLf3-.cjs';
5
+ export { anticipate, cubicBezier, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, easings, linear, listEasings, mirrorEasing, power, readEasingTable, registerEasing, resolveEasing, reverseEasing, sampleEasing, signature, steps, unregisterEasing } from './easing.cjs';
6
+ export { CurlResult, FRACTAL_DEFAULTS, FractalOptions, GRAD3, GRAD4, Noise, Noise2DFn, Noise3DFn, NoiseTables, PerlinNoise, SimplexNoise, clearNoiseTableCache, createNoise, createNoiseTables, createPerlinNoise, createSimplexNoise, createValueNoise, curl2D, curl2DAnimated, fbm2D, fbm3D, ridged2D, tileable2D, turbulence2D, warp2D } from './noise.cjs';
7
+ import { C as ColorSpace } from './palette-6HkVltYU.cjs';
8
+ export { a as ColorInput, b as ColorScale, H as HarmonyKind, c as Hsl, d as Hsv, e as HueDirection, L as LinearRgb, O as Oklab, f as Oklch, P as PaletteName, g as PaletteOptions, R as Rgb, S as ScaleOptions, h as createScale, i as expandPalette, j as generatePalette, k as harmony, l as invertForSurface, p as palettes, r as resolvePalette, s as sortByLightness } from './palette-6HkVltYU.cjs';
9
+ export { ColorParseError, ContrastResult, Gradient, GradientOptions, GradientStop, MixOptions, TextSize, WcagLevel, applyGradientStops, blend, checkContrast, clearColorCache, composite, contrastRatio, createGradient, ensureContrast, fadeBy, fromUint32, gamutError, gamutMapOklch, hslToRgb, hsvToRgb, inSrgbGamut, isGamutStable, lerpHue, linearToOklab, linearToRgb, linearToSrgbChannel, listNamedColors, maxChroma, mixColors, mixParsed, namedColorToHex, oklabToLinear, oklabToOklch, oklabToRgb, oklchToOklab, oklchToRgb, oklchToRgbMapped, parseColor, perceivedLightness, pickReadable, relativeLuminance, requiredRatio, resolveComputed, resolveCssVariable, rgbToHsl, rgbToHsv, rgbToLinear, rgbToOklab, rgbToOklch, srgbToLinearChannel, toHex, toHslString, toOklchString, toRgbString, toUint32, tryParseColor, withAlpha, worstContrast } from './color.cjs';
10
+ import { F as FrameCallback, S as SubscribeOptions, U as Unsubscribe, a as FrameState, b as FramePhase } from './reduced-motion-D0LUgZle.cjs';
11
+ export { c as FRAME_PHASES, M as MotionResolution, d as ReducedMotionMode, R as ReducedMotionStrategy, g as getReducedMotionMode, o as onReducedMotionChange, p as prefersReducedMotion, r as resetReducedMotion, e as resolveMotion, s as setReducedMotionMode } from './reduced-motion-D0LUgZle.cjs';
12
+ import { S as StaggerOptions } from './index-Dl8GCsya.cjs';
13
+ export { B as BaseBehaviourOptions, a as BehaviourDefinition, b as BehaviourHandle, c as BehaviourInstance, d as BehaviourName, M as MagneticOptions, e as MarqueeOptions, P as ParallaxOptions, R as RippleOptions, f as ScrollRevealOptions, g as SplitTextOptions, h as SplitTextResult, i as SplitUnit, j as StaggerFrom, T as TiltOptions, k as behaviours, l as captureStyles, m as defineBehaviour, n as getSplitResult, o as magnetic, p as marquee, q as mountBehaviour, r as parallax, s as ripple, t as scrollReveal, u as split, v as splitText, w as stagger, x as staggerDuration, y as tilt } from './index-Dl8GCsya.cjs';
14
+ export { A as AnimatedGradientOptions, a as AuroraOptions, B as BaseEffectOptions, b as BlendMode, c as BlobOptions, C as ConstellationOptions, E as EffectContext, d as EffectDefinition, e as EffectFrame, f as EffectHandle, g as EffectInstance, h as EffectName, F as FirefliesOptions, G as GradientCache, M as MeshGradientOptions, i as MeteorsOptions, N as NoiseLayerOptions, P as ParticleBuffer, j as ParticleFieldOptions, k as PointerOptions, l as PointerState, m as PointerTracker, R as RainOptions, S as Size, n as SnowOptions, o as SpatialGrid, p as SpotlightOptions, q as Surface, r as SurfaceOptions, U as Unsub, W as WavesOptions, s as ageAndCull, t as animatedGradient, u as aurora, v as blob, w as constellation, x as createSurface, y as defineEffect, z as drawDot, D as drawGlow, H as effects, I as fireflies, J as integrate, K as meshGradient, L as meteors, O as mountEffect, Q as noiseLayer, T as particleField, V as rain, X as resetFilterSupport, Y as snow, Z as spotlight, _ as supportsCanvasFilter, $ as supportsOffscreenCanvas, a0 as supportsWorkerRendering, a1 as traceClosedSpline, a2 as traceOpenSpline, a3 as traceRoundRect, a4 as trackPointer, a5 as waves, a6 as wrapBounds } from './index-DHOfsEIn.cjs';
15
+
16
+ /**
17
+ * Spring physics, solved analytically.
18
+ *
19
+ * Most libraries integrate springs numerically, stepping the ODE once per frame. That is
20
+ * simple but has two real drawbacks: the result depends on frame rate (a dropped frame
21
+ * changes the motion), and you cannot *seek* — asking "where is this spring at t = 0.4s?"
22
+ * requires replaying every frame up to that point.
23
+ *
24
+ * Ansh solves the damped harmonic oscillator in closed form instead:
25
+ *
26
+ * y'' + 2ζω₀y' + ω₀²y = 0
27
+ *
28
+ * where `y` is displacement from the target, `ω₀` is the undamped angular frequency and `ζ`
29
+ * is the damping ratio. That gives an exact position and velocity for any `t`, which makes
30
+ * springs frame-rate independent, seekable, reversible, and placeable on a timeline.
31
+ */
32
+ /** Physical parameterisation — think in terms of the mass-spring-damper system. */
33
+ interface PhysicalSpringOptions {
34
+ /** Spring constant. Higher is snappier. Default `170`. */
35
+ stiffness?: number;
36
+ /** Damping coefficient. Higher settles sooner with less oscillation. Default `26`. */
37
+ damping?: number;
38
+ /** Mass of the attached body. Higher is more sluggish. Default `1`. */
39
+ mass?: number;
40
+ }
41
+ /**
42
+ * Perceptual parameterisation — think in terms of how the motion *reads*.
43
+ *
44
+ * This is almost always the better API for designers, and it is what Apple's
45
+ * `spring(duration:bounce:)` exposes. We use the same mapping so values transfer between
46
+ * SwiftUI and Ansh unchanged.
47
+ */
48
+ interface PerceptualSpringOptions {
49
+ /** Approximate settling time in seconds. Default `0.6`. */
50
+ duration?: number;
51
+ /**
52
+ * Bounciness in `[-1, 1]`.
53
+ *
54
+ * - `0` — critically damped: reaches the target as fast as possible with no overshoot.
55
+ * - `> 0` — underdamped: overshoots and oscillates. `0.3` is lively, `0.7` is springy.
56
+ * - `< 0` — overdamped: creeps in slowly. Useful for heavy, weighty motion.
57
+ *
58
+ * Default `0`.
59
+ */
60
+ bounce?: number;
61
+ }
62
+ interface SpringRestOptions {
63
+ /** Distance from target below which the spring is considered settled. Default `0.01`. */
64
+ restDelta?: number;
65
+ /** Speed below which the spring is considered settled. Default `0.01`. */
66
+ restSpeed?: number;
67
+ }
68
+ interface SpringOptions extends PhysicalSpringOptions, PerceptualSpringOptions, SpringRestOptions {
69
+ /** Starting position. Default `0`. */
70
+ from?: number;
71
+ /** Target position. Default `1`. */
72
+ to?: number;
73
+ /** Initial velocity in units per second. Default `0`. */
74
+ velocity?: number;
75
+ }
76
+ interface SpringState {
77
+ position: number;
78
+ velocity: number;
79
+ /** `true` once both displacement and speed are inside the rest thresholds. */
80
+ done: boolean;
81
+ }
82
+ interface SpringSolver {
83
+ /** Exact state at time `t` (seconds since the spring started). */
84
+ at(t: number): SpringState;
85
+ /** Estimated time in seconds until the spring settles. Capped at 10s. */
86
+ readonly duration: number;
87
+ /** Resolved damping ratio ζ. `< 1` underdamped, `= 1` critical, `> 1` overdamped. */
88
+ readonly dampingRatio: number;
89
+ /** Resolved undamped angular frequency ω₀ in radians per second. */
90
+ readonly angularFrequency: number;
91
+ }
92
+ declare const SPRING_DEFAULTS: {
93
+ readonly stiffness: 170;
94
+ readonly damping: 26;
95
+ readonly mass: 1;
96
+ readonly duration: 0.6;
97
+ readonly bounce: 0;
98
+ readonly restDelta: 0.01;
99
+ readonly restSpeed: 0.01;
100
+ readonly from: 0;
101
+ readonly to: 1;
102
+ readonly velocity: 0;
103
+ };
104
+ declare function perceptualToPhysical(duration: number, bounce: number, mass?: number): Required<PhysicalSpringOptions>;
105
+ /** The inverse of {@link perceptualToPhysical}. */
106
+ declare function physicalToPerceptual(stiffness: number, damping: number, mass?: number): Required<PerceptualSpringOptions>;
107
+ /**
108
+ * Build a closed-form solver for the given spring.
109
+ *
110
+ * If `duration` or `bounce` is supplied, the perceptual parameterisation wins and
111
+ * `stiffness`/`damping` are ignored — mixing the two is always a mistake, and silently
112
+ * preferring one is friendlier than throwing at runtime inside a render.
113
+ */
114
+ declare function createSpring(options?: SpringOptions): SpringSolver;
115
+ /**
116
+ * Momentum / inertial decay — the "flick and let go" motion behind momentum scrolling.
117
+ *
118
+ * Velocity decays exponentially with a time constant of `power * 1000 / decay`, and the
119
+ * resting point is derived from the initial velocity, so a harder flick travels further.
120
+ */
121
+ interface DecayOptions {
122
+ from?: number;
123
+ velocity?: number;
124
+ /** How far a given velocity carries. Default `0.8`. */
125
+ power?: number;
126
+ /** Exponential decay rate. Higher stops sooner. Default `0.325`. */
127
+ timeConstant?: number;
128
+ /** Stop when within this distance of the resting point. Default `0.5`. */
129
+ restDelta?: number;
130
+ /** Optional clamp on the resting point, e.g. scroll bounds. */
131
+ min?: number;
132
+ max?: number;
133
+ }
134
+ declare function createDecay(options?: DecayOptions): {
135
+ at: (t: number) => SpringState;
136
+ duration: number;
137
+ target: number;
138
+ };
139
+
140
+ /**
141
+ * Named spring presets.
142
+ *
143
+ * These exist so that a team can agree on "use `snappy` for menus" rather than negotiating
144
+ * stiffness values in code review. They are expressed perceptually (`duration`/`bounce`)
145
+ * because that is the vocabulary a designer can reason about.
146
+ */
147
+ declare const springs: {
148
+ /** No overshoot, quick. The safe default for UI state changes. */
149
+ default: {
150
+ duration: number;
151
+ bounce: number;
152
+ };
153
+ /** Fast and crisp. Menus, dropdowns, toggles. */
154
+ snappy: {
155
+ duration: number;
156
+ bounce: number;
157
+ };
158
+ /** Lively overshoot. Playful confirmations, likes, badges. */
159
+ bouncy: {
160
+ duration: number;
161
+ bounce: number;
162
+ };
163
+ /** Slow, deliberate, weighty. Large surfaces and sheets. */
164
+ heavy: {
165
+ duration: number;
166
+ bounce: number;
167
+ };
168
+ /** Barely-there motion for micro-interactions. */
169
+ subtle: {
170
+ duration: number;
171
+ bounce: number;
172
+ };
173
+ /** Long, luxurious settle. Hero content and page transitions. */
174
+ cinematic: {
175
+ duration: number;
176
+ bounce: number;
177
+ };
178
+ /** Rubber-band feel for drag release. */
179
+ elastic: {
180
+ duration: number;
181
+ bounce: number;
182
+ };
183
+ /** Molasses. Deliberately sluggish — use for depth cues, not for controls. */
184
+ molasses: {
185
+ duration: number;
186
+ bounce: number;
187
+ };
188
+ };
189
+ type SpringPresetName = keyof typeof springs;
190
+ /** Accept a preset name or an explicit options object anywhere a spring is configured. */
191
+ type SpringInput = SpringPresetName | SpringOptions;
192
+ declare function resolveSpringOptions(input: SpringInput | undefined): SpringOptions;
193
+
194
+ /**
195
+ * Time keeping for the ticker.
196
+ *
197
+ * Two decisions here matter more than they look:
198
+ *
199
+ * **Delta clamping.** When a tab is backgrounded, `requestAnimationFrame` stops. On return,
200
+ * the first frame's delta is however long the user was away — seconds, or minutes. Feeding
201
+ * that into a physics integrator sends every particle to infinity, and into a tween makes it
202
+ * jump to its end. We clamp to `maxDelta` so the worst case is one slightly-long frame.
203
+ *
204
+ * **Seconds, not milliseconds.** Every public duration in Ansh is in seconds, because
205
+ * that is what designers and CSS authors think in, and because it keeps spring constants at
206
+ * human-readable magnitudes. Conversion happens once, here.
207
+ */
208
+ interface ClockOptions {
209
+ /**
210
+ * Largest delta the clock will report, in seconds. Default `1 / 15` (≈67 ms), which is
211
+ * two dropped frames at 30 FPS — slow enough to notice, small enough to be harmless.
212
+ */
213
+ maxDelta?: number;
214
+ /** Multiplier on the passage of time. `0.5` is slow motion, `2` is double speed. Default `1`. */
215
+ timeScale?: number;
216
+ }
217
+ interface FrameTiming {
218
+ /** Scaled seconds elapsed since the clock started, excluding paused time. */
219
+ readonly time: number;
220
+ /** Scaled, clamped seconds since the previous frame. */
221
+ readonly delta: number;
222
+ /** Unscaled, unclamped seconds since the previous frame. Use for profiling, not motion. */
223
+ readonly rawDelta: number;
224
+ /** Monotonically increasing frame counter. */
225
+ readonly frame: number;
226
+ /** The raw `performance.now()` timestamp for this frame. */
227
+ readonly timestamp: number;
228
+ }
229
+ declare class Clock {
230
+ private maxDelta;
231
+ private scale;
232
+ private startTimestamp;
233
+ private lastTimestamp;
234
+ private accumulatedTime;
235
+ private frameIndex;
236
+ private running;
237
+ /** Leftover time for the fixed-step accumulator. */
238
+ private fixedRemainder;
239
+ constructor(options?: ClockOptions);
240
+ get timeScale(): number;
241
+ set timeScale(value: number);
242
+ get isRunning(): boolean;
243
+ get elapsed(): number;
244
+ start(timestamp: number): void;
245
+ /**
246
+ * Resume after a pause.
247
+ *
248
+ * Only `lastTimestamp` is reset, so the delta for the resumed frame is small rather than
249
+ * "however long the pause lasted". `accumulatedTime` continues from where it stopped, which
250
+ * is what makes pause/resume invisible to animations mid-flight.
251
+ */
252
+ resume(timestamp: number): void;
253
+ pause(): void;
254
+ reset(): void;
255
+ /** Advance the clock and produce this frame's timing. */
256
+ tick(timestamp: number): FrameTiming;
257
+ /**
258
+ * Number of fixed-size physics steps to run for a variable-length frame.
259
+ *
260
+ * Verlet and spring-constraint integrators are only stable at a fixed step; running them
261
+ * with a variable delta makes stiffness frame-rate dependent, so a 144 Hz monitor gets
262
+ * different physics from a 60 Hz one. This accumulates the leftover and reports whole steps.
263
+ *
264
+ * `maxSteps` bounds the work when a long frame has queued up many steps — better to fall
265
+ * slightly behind real time than to spiral into an ever-longer catch-up.
266
+ */
267
+ fixedSteps(delta: number, step: number, maxSteps?: number): number;
268
+ /** Fraction of the way into the next fixed step, for interpolating rendered positions. */
269
+ get fixedAlpha(): number;
270
+ get totalWallTime(): number;
271
+ }
272
+
273
+ /**
274
+ * Adaptive frame budgeting.
275
+ *
276
+ * The premise: an effect authored on a MacBook Pro will drop frames on a mid-range Android
277
+ * phone, and the author will never see it. Rather than asking developers to hand-tune particle
278
+ * counts per device — which nobody does — the kernel measures how long frames actually take and
279
+ * publishes a single `quality` scalar in `[0, 1]`. Effects scale their own cost against it.
280
+ *
281
+ * Why p95 rather than the mean: a mean is dominated by the many cheap frames and hides the
282
+ * occasional 40 ms stall that the user actually perceives as a stutter. p95 tracks the bad
283
+ * frames, which are the ones worth reacting to.
284
+ *
285
+ * Quality moves slowly and asymmetrically — quick to shed work when frames are expensive, slow
286
+ * to add it back. Oscillating between quality levels is more visually distracting than simply
287
+ * running at the lower one, so hysteresis is deliberate.
288
+ */
289
+ interface FrameBudgetOptions {
290
+ /**
291
+ * Target frame duration in milliseconds. Defaults to the display's refresh interval as
292
+ * measured at runtime, falling back to 16.67 ms.
293
+ */
294
+ targetFrameMs?: number;
295
+ /**
296
+ * Fraction of the frame Ansh is allowed to spend. The rest is left for the browser's
297
+ * own work — style, layout, paint, compositing — plus the host application. Default `0.5`.
298
+ */
299
+ share?: number;
300
+ /** Rolling window size in frames. Default `90` (≈1.5 s at 60 FPS). */
301
+ window?: number;
302
+ /** How fast quality drops when over budget, per second. Default `0.6`. */
303
+ degradeRate?: number;
304
+ /** How fast quality recovers when under budget, per second. Default `0.12`. */
305
+ recoverRate?: number;
306
+ /** Floor on quality, so an effect never disappears entirely. Default `0.25`. */
307
+ minQuality?: number;
308
+ }
309
+ interface BudgetReport {
310
+ /** Adaptive quality in `[minQuality, 1]`. Multiply your work by this. */
311
+ quality: number;
312
+ /** Smoothed frames per second. */
313
+ fps: number;
314
+ /** Median frame cost attributable to Ansh, in milliseconds. */
315
+ p50: number;
316
+ /** 95th-percentile Ansh frame cost, in milliseconds. */
317
+ p95: number;
318
+ /** The budget being measured against, in milliseconds. */
319
+ budgetMs: number;
320
+ /** `true` while quality is below 1 — useful for a dev-mode indicator. */
321
+ degraded: boolean;
322
+ }
323
+ declare class FrameBudget {
324
+ private readonly share;
325
+ private readonly windowSize;
326
+ private readonly degradeRate;
327
+ private readonly recoverRate;
328
+ private readonly minQuality;
329
+ private targetFrameMs;
330
+ private explicitTarget;
331
+ private costs;
332
+ private cursor;
333
+ private filled;
334
+ private scratch;
335
+ private qualityValue;
336
+ private fpsValue;
337
+ private p50Value;
338
+ private p95Value;
339
+ /** Frame intervals used to detect the display refresh rate. */
340
+ private intervalSamples;
341
+ private refreshDetected;
342
+ constructor(options?: FrameBudgetOptions);
343
+ get quality(): number;
344
+ get budgetMs(): number;
345
+ /**
346
+ * Record one frame.
347
+ *
348
+ * @param costMs Milliseconds spent inside Ansh callbacks this frame.
349
+ * @param rawDelta Unscaled seconds since the previous frame, for FPS and refresh detection.
350
+ */
351
+ record(costMs: number, rawDelta: number): void;
352
+ /**
353
+ * Infer the display refresh rate from observed frame intervals.
354
+ *
355
+ * Assuming 60 Hz wastes half the available budget on a 120 Hz display and overspends on a
356
+ * 30 Hz one. We take the median of the first 40 intervals (median, not mean, so a couple of
357
+ * long startup frames don't skew it) and snap to the nearest common rate.
358
+ */
359
+ private detectRefreshRate;
360
+ private recomputePercentiles;
361
+ private adaptQuality;
362
+ report(): BudgetReport;
363
+ /** Force quality to a fixed value, disabling adaptation. Used by `performance="high"`. */
364
+ lockQuality(value: number): void;
365
+ reset(): void;
366
+ }
367
+
368
+ interface TickerOptions extends ClockOptions {
369
+ budget?: FrameBudgetOptions;
370
+ /**
371
+ * Schedule frames yourself instead of using `requestAnimationFrame`.
372
+ * Tests inject a manual driver; a worker-based renderer could supply its own.
373
+ */
374
+ scheduler?: (callback: (timestamp: number) => void) => number;
375
+ canceller?: (handle: number) => void;
376
+ /**
377
+ * Read the current time. Defaults to `performance.now()`.
378
+ *
379
+ * Must share a time origin with whatever `scheduler` passes to its callback — in the browser
380
+ * `requestAnimationFrame` and `performance.now()` already do. An injected scheduler on its own
381
+ * clock must supply this too, otherwise the first frame's delta is computed between two unrelated
382
+ * timelines and clamps to zero.
383
+ */
384
+ now?: () => number;
385
+ /** Start automatically when the first subscriber is added. Default `true`. */
386
+ autoStart?: boolean;
387
+ }
388
+ declare class Ticker {
389
+ private readonly clock;
390
+ private readonly budget;
391
+ private readonly phases;
392
+ private readonly autoStart;
393
+ private readonly schedule;
394
+ private readonly cancel;
395
+ private readonly readTime;
396
+ private handle;
397
+ private nextId;
398
+ private paused;
399
+ private pauseReasons;
400
+ private disposed;
401
+ /**
402
+ * Phase arrays are rebuilt lazily rather than re-sorted on every add/remove: components
403
+ * mount in bursts, and sorting once before the next frame is cheaper than sorting per call.
404
+ */
405
+ private dirtyPhases;
406
+ /** Callbacks queued to run exactly once on the next frame. */
407
+ private onceQueue;
408
+ private listeners;
409
+ private readonly state;
410
+ constructor(options?: TickerOptions);
411
+ /**
412
+ * Register a per-frame callback.
413
+ *
414
+ * @returns An idempotent unsubscribe function. Always call it on unmount — the leak harness
415
+ * in the test suite fails the build if a component forgets.
416
+ */
417
+ add(callback: FrameCallback, options?: SubscribeOptions): Unsubscribe;
418
+ /** Run a callback on the next frame only. Useful for deferring a DOM read past a write. */
419
+ once(callback: FrameCallback): void;
420
+ /** Resolve on the next frame. `await ticker.nextFrame()` reads well in setup code. */
421
+ nextFrame(): Promise<FrameState>;
422
+ get subscriberCount(): number;
423
+ /** Per-phase subscriber counts. Consumed by the dev inspector and the leak harness. */
424
+ inspect(): Record<FramePhase, {
425
+ count: number;
426
+ names: string[];
427
+ }>;
428
+ start(): void;
429
+ stop(): void;
430
+ /**
431
+ * Pause the ticker, tagged with a reason.
432
+ *
433
+ * Reasons are reference-counted by name so that independent systems can pause without
434
+ * fighting: if the tab is hidden *and* the user prefers reduced motion, both hold a pause,
435
+ * and resuming one doesn't restart animation while the other still applies.
436
+ */
437
+ pause(reason?: string): void;
438
+ resume(reason?: string): void;
439
+ get isPaused(): boolean;
440
+ get pausedBecause(): string[];
441
+ get isRunning(): boolean;
442
+ get timeScale(): number;
443
+ set timeScale(value: number);
444
+ get time(): number;
445
+ /**
446
+ * Advance one frame manually.
447
+ *
448
+ * The escape hatch that makes the whole engine testable: a test can drive 100 deterministic
449
+ * frames without waiting on a real display, and a screenshot harness can seek to an exact
450
+ * frame index.
451
+ */
452
+ step(deltaMs?: number): FrameState;
453
+ /** Release everything. The ticker cannot be restarted afterwards. */
454
+ dispose(): void;
455
+ get quality(): number;
456
+ budgetReport(): BudgetReport;
457
+ /** Disable adaptation and pin quality. Backs the `performance="high" | "low"` prop. */
458
+ lockQuality(value: number): void;
459
+ /** Subscribe to per-frame budget reports — powers the dev-mode FPS overlay. */
460
+ onBudget(listener: (report: BudgetReport) => void): Unsubscribe;
461
+ private readonly loop;
462
+ private runFrame;
463
+ /**
464
+ * Run one subscriber, containing its failures.
465
+ *
466
+ * A throwing callback must not stop every other animation on the page — that turns one
467
+ * component's bug into a whole-page visual failure. We log, and after several consecutive
468
+ * throws we evict the subscriber, because 60 identical stack traces per second is not
469
+ * diagnostics, it's a denial of service on the console.
470
+ */
471
+ private invoke;
472
+ private sortDirtyPhases;
473
+ }
474
+ /** Monotonic clock, falling back to `Date.now()` where `performance` is unavailable. */
475
+ declare function now(): number;
476
+ /**
477
+ * The process-wide ticker.
478
+ *
479
+ * Created lazily on first access so that importing Ansh has no side effects — important
480
+ * for SSR, and for tree-shaking in apps that only use the pure colour or easing modules.
481
+ */
482
+ declare function getTicker(): Ticker;
483
+ /**
484
+ * Replace the shared ticker.
485
+ *
486
+ * Exists for tests (inject a manually-driven ticker) and for advanced hosts that need a
487
+ * custom scheduler. Disposes the previous instance so subscribers can't leak across the swap.
488
+ */
489
+ declare function setTicker(ticker: Ticker): void;
490
+ /** Tear down the shared ticker. Call between tests to guarantee isolation. */
491
+ declare function resetTicker(): void;
492
+
493
+ /**
494
+ * Environment-driven pausing.
495
+ *
496
+ * Animation that runs while nobody is looking is pure waste: it drains battery, heats the
497
+ * device, and on a laptop spins the fan for a page in a background tab. This module wires the
498
+ * ticker to the signals that tell us animation is pointless or unwelcome.
499
+ *
500
+ * Each signal holds a named pause on the ticker, so they compose — being both hidden and
501
+ * battery-saving means two holds, and restoring one doesn't resume prematurely.
502
+ */
503
+ interface LifecycleOptions {
504
+ /** Pause while the document is hidden. Default `true`. */
505
+ pauseWhenHidden?: boolean;
506
+ /**
507
+ * Pause while the window has no focus.
508
+ *
509
+ * Off by default: a user with the page visible in a split-screen alongside their editor
510
+ * still expects to see the animation. Only worth enabling for genuinely heavy scenes.
511
+ */
512
+ pauseWhenBlurred?: boolean;
513
+ /**
514
+ * Reduce quality when the device reports low battery while discharging. Default `true`.
515
+ *
516
+ * The Battery Status API is only exposed in Chromium; where unavailable this is a no-op
517
+ * rather than an approximation, because guessing wrong either wastes power or degrades
518
+ * quality for no reason.
519
+ */
520
+ respectBattery?: boolean;
521
+ /** Battery level below which quality is reduced, in `[0, 1]`. Default `0.2`. */
522
+ batteryThreshold?: number;
523
+ /** Quality ceiling applied while on low battery. Default `0.5`. */
524
+ batterySaveQuality?: number;
525
+ }
526
+ /**
527
+ * Attach environment listeners to a ticker.
528
+ *
529
+ * @returns A cleanup function that removes every listener. Safe to call during SSR, where it
530
+ * is a no-op — nothing is attached in the first place.
531
+ */
532
+ declare function attachLifecycle(ticker: Ticker, options?: LifecycleOptions): Unsubscribe;
533
+
534
+ /**
535
+ * An observable animated value.
536
+ *
537
+ * `MotionValue` is the interop currency of Ansh. It deliberately sits *outside* any
538
+ * framework's reactivity system, which is what lets one engine serve React, Vue and vanilla
539
+ * JS without adapters in the middle:
540
+ *
541
+ * - React subscribes with `useSyncExternalStore` (or writes straight to the DOM, bypassing
542
+ * re-renders entirely — the reason 60 FPS animation in React is possible at all).
543
+ * - Vue wraps it in a `shallowRef`.
544
+ * - Plain JS just calls `.on()`.
545
+ *
546
+ * Velocity tracking is built in because it is needed constantly (a spring that takes over from
547
+ * a drag must inherit the drag's velocity) and reconstructing it after the fact is lossy.
548
+ */
549
+ type MotionValueListener<T> = (value: T, previous: T) => void;
550
+ declare class MotionValue<T = number> {
551
+ private current;
552
+ private previous;
553
+ private listeners;
554
+ private renderListeners;
555
+ /** Timestamp and value of the last update, for velocity. */
556
+ private lastUpdated;
557
+ private lastValue;
558
+ private velocityValue;
559
+ /** Set while an `animate()` drives this value, so a new animation can cancel the old one. */
560
+ private stopAnimation;
561
+ constructor(initial: T);
562
+ get(): T;
563
+ getPrevious(): T;
564
+ /**
565
+ * Write a new value and notify subscribers.
566
+ *
567
+ * Notification is synchronous. That is a deliberate choice: this is called from inside the
568
+ * ticker's `write` phase, and deferring would push DOM writes into the next frame, adding a
569
+ * frame of latency to every animation.
570
+ */
571
+ set(value: T): void;
572
+ /**
573
+ * Write without notifying, then notify once.
574
+ * Used when several related values change together and should produce one visual update.
575
+ */
576
+ setSilent(value: T): void;
577
+ private updateVelocity;
578
+ /**
579
+ * Current velocity in units per second.
580
+ *
581
+ * Returns 0 if the value hasn't changed recently, so a spring picking up from rest doesn't
582
+ * inherit a phantom velocity from an interaction that ended a second ago.
583
+ */
584
+ getVelocity(): number;
585
+ /** Directly set velocity — used when handing off from a gesture to a spring. */
586
+ setVelocity(velocity: number): void;
587
+ /** Subscribe to changes. The listener is *not* called immediately. */
588
+ on(listener: MotionValueListener<T>): Unsubscribe;
589
+ /**
590
+ * Subscribe as a renderer — called before regular listeners.
591
+ *
592
+ * Framework bindings use this for the DOM write, so that visual output is never delayed by
593
+ * application-level subscribers.
594
+ */
595
+ onRender(listener: (value: T) => void): Unsubscribe;
596
+ get listenerCount(): number;
597
+ /**
598
+ * Register the currently-running animation so a subsequent one can interrupt it.
599
+ * Called by `animate()`; not part of the public surface.
600
+ */
601
+ attachAnimation(stop: () => void): void;
602
+ /** Stop whatever is animating this value, leaving it wherever it is. */
603
+ stop(): void;
604
+ get isAnimating(): boolean;
605
+ /** Release listeners and stop any animation. */
606
+ destroy(): void;
607
+ }
608
+ declare function motionValue<T>(initial: T): MotionValue<T>;
609
+ /** Maps a tuple of value types to a tuple of MotionValues holding them. */
610
+ type MotionValueTuple<Values extends readonly unknown[]> = {
611
+ [K in keyof Values]: MotionValue<Values[K]>;
612
+ };
613
+ /**
614
+ * A read-only value computed from one or more others.
615
+ *
616
+ * The derived value updates synchronously when any source changes, so a chain of transforms
617
+ * settles within a single frame rather than propagating one link per frame.
618
+ */
619
+ declare function derived<Values extends readonly unknown[], Result>(sources: MotionValueTuple<Values>, compute: (...values: Values) => Result): MotionValue<Result> & {
620
+ destroy(): void;
621
+ };
622
+ /**
623
+ * Map one motion value through a function.
624
+ * Shorthand for the overwhelmingly common single-source case.
625
+ */
626
+ declare function mapValue<In, Out>(source: MotionValue<In>, transform: (value: In) => Out): MotionValue<Out> & {
627
+ destroy(): void;
628
+ };
629
+
630
+ /**
631
+ * Generic value interpolation.
632
+ *
633
+ * `animate()` accepts numbers, colours, arrays, objects and unit strings, so it needs to pick
634
+ * an interpolator at runtime. Resolution happens **once**, at animation setup, and returns a
635
+ * closure — never per frame. A `typeof` check inside a frame loop that runs for every animated
636
+ * property on the page is exactly the sort of cost that quietly adds up to a dropped frame.
637
+ */
638
+ type Interpolator<T> = (t: number) => T;
639
+ interface InterpolateOptions {
640
+ /** Colour space for colour interpolation. Default `'oklab'`. */
641
+ colorSpace?: ColorSpace;
642
+ }
643
+ /** Numeric interpolation. The fast path, and by far the most common. */
644
+ declare function interpolateNumber(from: number, to: number): Interpolator<number>;
645
+ /**
646
+ * Colour interpolation, producing `rgb()`/`rgba()` strings ready for style assignment.
647
+ *
648
+ * Endpoints are parsed at setup, so per-frame cost is one mix plus one string build.
649
+ */
650
+ declare function interpolateColor(from: string, to: string, space?: ColorSpace): Interpolator<string>;
651
+ declare function interpolateUnitValue(from: string, to: string): Interpolator<string> | null;
652
+ declare function interpolateNumericString(from: string, to: string): Interpolator<string> | null;
653
+ /** Element-wise array interpolation. Length is taken from `from`. */
654
+ declare function interpolateArray<T>(from: readonly T[], to: readonly T[], options?: InterpolateOptions): Interpolator<T[]>;
655
+ /** Key-wise object interpolation. Keys absent from `to` hold their `from` value. */
656
+ declare function interpolateObject<T extends Record<string, unknown>>(from: T, to: Partial<T>, options?: InterpolateOptions): Interpolator<T>;
657
+ /**
658
+ * Pick the right interpolator for a pair of values.
659
+ *
660
+ * Order matters. Colours are checked before generic unit/numeric strings because
661
+ * `rgb(0, 0, 0)` would otherwise match the numeric-string path and get interpolated in sRGB,
662
+ * silently losing the perceptual blending that is the whole point of the colour engine.
663
+ */
664
+ declare function resolveInterpolator<T>(from: T, to: T, options?: InterpolateOptions): Interpolator<T>;
665
+ /**
666
+ * Interpolate through a series of keyframes.
667
+ *
668
+ * `offsets` default to even spacing. Segment interpolators are built once at setup, so a
669
+ * 20-keyframe animation costs the same per frame as a 2-keyframe one plus a short scan.
670
+ */
671
+ declare function interpolateKeyframes<T>(values: readonly T[], offsets?: readonly number[], options?: InterpolateOptions): Interpolator<T>;
672
+
673
+ /**
674
+ * A pure, seekable function of time.
675
+ *
676
+ * This is the shared core beneath both `animate()` and `Timeline`. Separating it out matters
677
+ * for more than DRY: because a sampler is a *pure function of elapsed time* with no internal
678
+ * cursor, a timeline can evaluate it at any point, in any order, forwards or backwards. An
679
+ * animation that advanced itself statefully could not be seeked or reversed at all.
680
+ *
681
+ * `animate()` adds a ticker subscription, delay and repeat handling on top. `Timeline` adds
682
+ * positioning and orchestration. Neither re-implements spring maths or interpolator resolution.
683
+ */
684
+ type SamplerType = 'tween' | 'spring' | 'decay';
685
+ interface SamplerOptions extends InterpolateOptions {
686
+ type?: SamplerType;
687
+ duration?: number;
688
+ easing?: EasingInput;
689
+ spring?: SpringInput;
690
+ stiffness?: number;
691
+ damping?: number;
692
+ mass?: number;
693
+ bounce?: number;
694
+ velocity?: number;
695
+ power?: number;
696
+ timeConstant?: number;
697
+ /** Explicit keyframe offsets in `[0, 1]`. Defaults to even spacing. */
698
+ offsets?: readonly number[];
699
+ }
700
+ interface Sampler<T> {
701
+ /** Value at `elapsed` seconds. Clamped at both ends — sampling past the end holds the last value. */
702
+ at(elapsed: number): T;
703
+ /** Natural duration in seconds. Tweens use the given duration; springs derive their own. */
704
+ readonly duration: number;
705
+ /** Normalised progress at `elapsed`, before interpolation. Springs may exceed `[0, 1]`. */
706
+ progressAt(elapsed: number): number;
707
+ }
708
+ declare const DEFAULT_TWEEN_DURATION = 0.4;
709
+ /**
710
+ * Build a sampler for a value transition.
711
+ *
712
+ * Springs on numeric values drive the number directly, so overshoot is physically correct.
713
+ * Springs on anything else (colours, transform strings) drive *progress* through the
714
+ * interpolator instead — the overshoot is preserved, but there is no meaningful way to
715
+ * integrate a colour through a differential equation, and interpolating past 1 in OKLab is
716
+ * exactly the right visual result.
717
+ */
718
+ declare function createSampler<T>(from: T, to: T | readonly T[], options?: SamplerOptions): Sampler<T>;
719
+ declare function hasSpringOptions(options: SamplerOptions): boolean;
720
+
721
+ /**
722
+ * The imperative animation driver.
723
+ *
724
+ * One function covers tweens, springs and inertial decay, because from a caller's point of view
725
+ * they differ only in how progress is generated — which is the {@link createSampler} concern.
726
+ * `animate()` owns only the parts that are about *driving*: the ticker subscription, delay,
727
+ * repeat handling and lifecycle callbacks.
728
+ *
729
+ * ```ts
730
+ * animate(0, 100, { duration: 0.6, easing: 'easeOutCubic', onUpdate: (v) => … })
731
+ * animate(x, 240, { type: 'spring', bounce: 0.3 })
732
+ * animate('#0275f4', '#9f0550', { duration: 1, onUpdate: (c) => (el.style.color = c) })
733
+ * ```
734
+ */
735
+ type RepeatMode = 'loop' | 'reverse' | 'mirror';
736
+ interface AnimationOptions<T> extends SamplerOptions {
737
+ /** Seconds to wait before starting. Default `0`. */
738
+ delay?: number;
739
+ /** Times to repeat after the first pass. `Infinity` is allowed. Default `0`. */
740
+ repeat?: number;
741
+ /** How to repeat. `'reverse'` plays backwards on alternate passes. Default `'loop'`. */
742
+ repeatMode?: RepeatMode;
743
+ /** Seconds to wait between repeats. Default `0`. */
744
+ repeatDelay?: number;
745
+ onUpdate?: (value: T) => void;
746
+ onComplete?: () => void;
747
+ onStart?: () => void;
748
+ /** Called on `stop()`, or when a new animation interrupts this one on the same MotionValue. */
749
+ onStop?: () => void;
750
+ /**
751
+ * How to behave under `prefers-reduced-motion`.
752
+ *
753
+ * `'jump'` (default) applies the final value immediately — the state change still happens, it
754
+ * just isn't animated, which is nearly always right for UI. `'ignore'` animates anyway; only
755
+ * correct when the motion itself carries information the user needs.
756
+ */
757
+ reducedMotion?: 'jump' | 'ignore';
758
+ }
759
+ interface AnimationControls {
760
+ /** Stop and leave the value where it is. */
761
+ stop(): void;
762
+ pause(): void;
763
+ play(): void;
764
+ /** Jump to a time in seconds, measured from the animation's start including its delay. */
765
+ seek(time: number): void;
766
+ /** Apply the final value and fire `onComplete`. */
767
+ finish(): void;
768
+ readonly isActive: boolean;
769
+ /** Total seconds including delay and repeats. `Infinity` when repeating forever. */
770
+ readonly duration: number;
771
+ /** Resolves on completion *or* on stop — it never rejects. */
772
+ readonly finished: Promise<void>;
773
+ }
774
+ declare function animate<T>(from: T | MotionValue<T>, to: T | readonly T[], options?: AnimationOptions<T>): AnimationControls;
775
+ /**
776
+ * Continuously track a moving target with a spring.
777
+ *
778
+ * Distinct from `animate(…, { type: 'spring' })`, which resolves its target once at setup.
779
+ * Cursor followers, magnetic buttons and drag handles all need a target that moves *while* the
780
+ * spring is still travelling toward it.
781
+ *
782
+ * Uses semi-implicit Euler rather than the analytic solver: with a target that changes every
783
+ * frame, rebuilding the closed form each frame would cost more than integrating.
784
+ */
785
+ declare function springTo(value: MotionValue<number>, getTarget: () => number, options?: SpringOptions & {
786
+ name?: string;
787
+ }): Unsubscribe;
788
+
789
+ /**
790
+ * A seekable, composable animation timeline.
791
+ *
792
+ * The central design decision: a timeline is a **pure function of time**, not a queue that
793
+ * advances itself. Every entry is a {@link import('./sampler').Sampler} placed at an absolute
794
+ * offset, so evaluating the timeline at `t` means evaluating each overlapping entry at `t - start`.
795
+ *
796
+ * That single choice is what makes `seek`, `reverse`, `timeScale` and scroll-driving all fall out
797
+ * for free. A queue-based timeline can only go forwards, one frame at a time, which is why
798
+ * scrubbing is a hard retrofit in libraries built that way.
799
+ *
800
+ * ```ts
801
+ * const tl = createTimeline()
802
+ * .to(opacity, 1, { duration: 0.4 })
803
+ * .to(y, 0, { duration: 0.6, easing: 'easeOutCubic' }, '<') // with the previous
804
+ * .label('revealed')
805
+ * .to(scale, 1.05, { duration: 0.2 }, '+=0.1') // 0.1s after the previous ends
806
+ * .call(() => announce('ready'))
807
+ * ```
808
+ */
809
+ /**
810
+ * Where to place an entry.
811
+ *
812
+ * - `number` — absolute seconds from the timeline's start.
813
+ * - `'>'` — after the previous entry ends. The default.
814
+ * - `'<'` — at the same time the previous entry *started* (parallel).
815
+ * - `'+=0.2'` — 0.2s after the previous entry ends.
816
+ * - `'-=0.2'` — 0.2s before the previous entry ends (overlap).
817
+ * - `'<+=0.1'` — 0.1s after the previous entry started.
818
+ * - `'myLabel'` — at a label, optionally with `'myLabel+=0.3'`.
819
+ */
820
+ type TimelinePosition = number | string;
821
+ interface TimelineEntry {
822
+ start: number;
823
+ duration: number;
824
+ /** Apply this entry's state for a local time within `[0, duration]`. */
825
+ apply(localTime: number): void;
826
+ /** One-shot callbacks fire on crossing rather than continuously. */
827
+ readonly isCallback: boolean;
828
+ /** Tracks whether a callback has already fired in the current direction of travel. */
829
+ fired: boolean;
830
+ readonly name: string;
831
+ }
832
+ interface TimelineOptions {
833
+ /** Start playing as soon as the first entry is added. Default `true`. */
834
+ autoplay?: boolean;
835
+ /** Times to repeat after the first pass. `Infinity` allowed. Default `0`. */
836
+ repeat?: number;
837
+ /** Alternate direction each repeat. Default `false`. */
838
+ yoyo?: boolean;
839
+ /** Playback rate. Negative values play in reverse. Default `1`. */
840
+ timeScale?: number;
841
+ onComplete?: () => void;
842
+ onUpdate?: (time: number, progress: number) => void;
843
+ onRepeat?: (iteration: number) => void;
844
+ /**
845
+ * Under `prefers-reduced-motion`, jump straight to the end state. Default `true`.
846
+ * Set `false` only for timelines whose motion conveys required information.
847
+ */
848
+ respectReducedMotion?: boolean;
849
+ }
850
+ interface Timeline {
851
+ /** Animate a MotionValue to a target. */
852
+ to<T>(value: MotionValue<T>, to: T | readonly T[], options?: SamplerOptions & {
853
+ duration?: number;
854
+ }, position?: TimelinePosition): Timeline;
855
+ /** Animate a MotionValue *from* a value to its current one — the reveal-in pattern. */
856
+ from<T>(value: MotionValue<T>, from: T, options?: SamplerOptions & {
857
+ duration?: number;
858
+ }, position?: TimelinePosition): Timeline;
859
+ /** Animate an arbitrary value, receiving updates through a callback. */
860
+ tween<T>(from: T, to: T | readonly T[], options: SamplerOptions & {
861
+ duration?: number;
862
+ onUpdate: (value: T) => void;
863
+ }, position?: TimelinePosition): Timeline;
864
+ /** Animate many MotionValues with a stagger between them. */
865
+ stagger<T>(values: readonly MotionValue<T>[], to: T | readonly T[], options?: SamplerOptions & {
866
+ duration?: number;
867
+ stagger?: StaggerOptions;
868
+ }, position?: TimelinePosition): Timeline;
869
+ /** Fire a callback at a point in time. Fires once per crossing. */
870
+ call(fn: () => void, position?: TimelinePosition): Timeline;
871
+ /** Name a point so later entries can be positioned relative to it. */
872
+ label(name: string, position?: TimelinePosition): Timeline;
873
+ /** Nest another timeline. Its entries are flattened in at the given offset. */
874
+ add(child: Timeline, position?: TimelinePosition): Timeline;
875
+ play(): Timeline;
876
+ pause(): Timeline;
877
+ /** Play backwards from the current position. */
878
+ reverse(): Timeline;
879
+ /** Jump to a time in seconds without playing. */
880
+ seek(time: number): Timeline;
881
+ /** Jump to a normalised position in `[0, 1]`. */
882
+ progress(value: number): Timeline;
883
+ restart(): Timeline;
884
+ /** Apply the final state and stop. */
885
+ complete(): Timeline;
886
+ /** Stop and release the ticker subscription. */
887
+ kill(): void;
888
+ readonly duration: number;
889
+ readonly time: number;
890
+ readonly isPlaying: boolean;
891
+ timeScale: number;
892
+ /** Resolves when playback completes. Also resolves on `kill()`. */
893
+ readonly finished: Promise<void>;
894
+ /** Internal: used by `add()` to flatten a nested timeline. */
895
+ readonly entries: readonly TimelineEntry[];
896
+ readonly labels: ReadonlyMap<string, number>;
897
+ }
898
+ declare function createTimeline(options?: TimelineOptions): Timeline;
899
+
900
+ /**
901
+ * Pooled `IntersectionObserver` and `ResizeObserver`.
902
+ *
903
+ * Creating one observer per element is the default pattern in most React/Vue code, and it is
904
+ * measurably wasteful: each observer is a separate object with its own callback and its own
905
+ * entry in the browser's internal bookkeeping. A page with 200 scroll-revealed elements creates
906
+ * 200 observers where 1 would do.
907
+ *
908
+ * These pools key observers by their *configuration*, so every element sharing a threshold
909
+ * and root margin shares one observer. Callbacks are dispatched from a `WeakMap` keyed by
910
+ * element, so unobserving an element cannot leak its callback.
911
+ */
912
+ interface IntersectionConfig {
913
+ root?: Element | Document | null;
914
+ rootMargin?: string;
915
+ /** A single threshold or a list. Lists are sorted so `[0, 1]` and `[1, 0]` share a pool. */
916
+ threshold?: number | readonly number[];
917
+ }
918
+ type IntersectionCallback = (entry: IntersectionObserverEntry) => void;
919
+ /**
920
+ * Observe an element's intersection with the viewport (or a custom root).
921
+ *
922
+ * @returns An unsubscribe function. When the last subscriber for an element leaves, the
923
+ * element is unobserved; when the last element leaves a pool, the observer is
924
+ * disconnected and dropped.
925
+ */
926
+ declare function observeIntersection(element: Element, callback: IntersectionCallback, config?: IntersectionConfig): Unsubscribe;
927
+ interface ResizeInfo {
928
+ width: number;
929
+ height: number;
930
+ /** Device-pixel dimensions when the browser reports them, else `width`/`height` × DPR. */
931
+ devicePixelWidth: number;
932
+ devicePixelHeight: number;
933
+ entry: ResizeObserverEntry;
934
+ }
935
+ type ResizeCallback = (info: ResizeInfo) => void;
936
+ /**
937
+ * Observe an element's size.
938
+ *
939
+ * Defaults to the `content-box`, matching CSS layout intuition. Canvas-backed effects pass
940
+ * `'device-pixel-content-box'` where supported, because that gives the exact backing-store
941
+ * size and avoids the half-pixel blurring that comes from rounding CSS pixels ourselves.
942
+ */
943
+ declare function observeResize(element: Element, callback: ResizeCallback, box?: ResizeObserverBoxOptions): Unsubscribe;
944
+ /** Live pool counts. The leak harness asserts these return to zero after unmount. */
945
+ declare function observerPoolStats(): {
946
+ intersection: number;
947
+ resize: number;
948
+ };
949
+ /** Disconnect every pooled observer. Test-only. */
950
+ declare function resetObserverPools(): void;
951
+
952
+ /**
953
+ * Scroll progress tracking.
954
+ *
955
+ * Scroll-linked animation is where layout thrashing is most likely, because the natural
956
+ * implementation — read `getBoundingClientRect()` in a `scroll` handler, then write a
957
+ * transform — forces a synchronous layout on every event, and scroll events fire faster than
958
+ * frames on many devices.
959
+ *
960
+ * Instead: the scroll handler only records that something changed, all measurement happens in
961
+ * the ticker's `read` phase, and consumers receive progress values in `compute`/`write`. One
962
+ * layout read per frame at most, no matter how many elements are tracked.
963
+ */
964
+ type ScrollAxis = 'y' | 'x';
965
+ /**
966
+ * Where an element's progress starts and ends, expressed as two alignment points.
967
+ *
968
+ * Read as "`element` edge relative to `viewport` edge", matching the mental model of GSAP
969
+ * ScrollTrigger and CSS scroll-driven animations:
970
+ *
971
+ * - `['start', 'end']` — starts when the element's top reaches the viewport bottom.
972
+ * - `['start', 'start']` — starts when the element's top reaches the viewport top.
973
+ * - `['end', 'start']` — ends when the element's bottom reaches the viewport top.
974
+ */
975
+ type ScrollOffset = readonly [ScrollEdge, ScrollEdge];
976
+ type ScrollEdge = 'start' | 'center' | 'end' | number;
977
+ interface ScrollProgress {
978
+ /** Progress through the tracked range, `0` to `1`. */
979
+ progress: number;
980
+ /** Signed scroll velocity in pixels per second. */
981
+ velocity: number;
982
+ /** Scroll offset of the container, in pixels. */
983
+ scroll: number;
984
+ /** `1` when scrolling toward the end, `-1` toward the start, `0` when still. */
985
+ direction: -1 | 0 | 1;
986
+ /** Is any part of the element within the viewport range? */
987
+ inView: boolean;
988
+ }
989
+ interface ScrollTrackOptions {
990
+ axis?: ScrollAxis;
991
+ /** Start and end alignment. Default `['start', 'end']` and `['end', 'start']` respectively. */
992
+ offset?: {
993
+ start: ScrollOffset;
994
+ end: ScrollOffset;
995
+ };
996
+ /** Scroll container. Defaults to the window. */
997
+ container?: HTMLElement | null;
998
+ }
999
+ /**
1000
+ * Track an element's progress through the viewport.
1001
+ *
1002
+ * @returns An unsubscribe function. The callback runs in the `compute` phase, so it is safe to
1003
+ * write styles from it — though writing in `write` is tidier for anything expensive.
1004
+ */
1005
+ declare function trackScroll(element: HTMLElement, callback: (progress: ScrollProgress) => void, options?: ScrollTrackOptions): Unsubscribe;
1006
+ /**
1007
+ * Track raw container scroll without reference to an element.
1008
+ * Backs progress bars, "scrolled" header states, and scroll-velocity effects.
1009
+ */
1010
+ declare function trackContainerScroll(callback: (info: {
1011
+ progress: number;
1012
+ scroll: number;
1013
+ velocity: number;
1014
+ max: number;
1015
+ }) => void, options?: {
1016
+ axis?: ScrollAxis;
1017
+ container?: HTMLElement | null;
1018
+ }): Unsubscribe;
1019
+
1020
+ /**
1021
+ * The plugin registry — Ansh's dependency-injection seam.
1022
+ *
1023
+ * Three jobs:
1024
+ *
1025
+ * 1. **Effect discovery.** The CLI and the docs site need to enumerate every available effect
1026
+ * without importing all of them. Registration provides that index.
1027
+ * 2. **Renderer backends.** `canvas2d` ships today; `webgl` and `webgpu` will register against
1028
+ * the same interface. Effects request a *capability*, not a concrete API, so a new backend
1029
+ * requires no changes to any effect.
1030
+ * 3. **Edition gating.** Pro components declare the capabilities they need. The registry is the
1031
+ * one place that decides whether they're available, instead of `if (isPro)` checks scattered
1032
+ * through eighty components.
1033
+ *
1034
+ * Deliberately not a general-purpose IoC container. It is a typed map with lifecycle hooks —
1035
+ * enough for the extension points we actually have, and small enough to reason about.
1036
+ */
1037
+ type CapabilityName = 'renderer:canvas2d' | 'renderer:webgl' | 'renderer:webgpu' | 'renderer:offscreen' | 'worker' | 'effect:core' | 'effect:pro' | 'behaviour:core' | 'behaviour:pro' | (string & {});
1038
+ interface Plugin {
1039
+ readonly name: string;
1040
+ readonly version?: string;
1041
+ /** Capabilities this plugin provides once installed. */
1042
+ readonly provides?: readonly CapabilityName[];
1043
+ /** Capabilities that must already be present. Installation throws if any are missing. */
1044
+ readonly requires?: readonly CapabilityName[];
1045
+ install?(registry: Registry): void;
1046
+ uninstall?(registry: Registry): void;
1047
+ }
1048
+ declare class CapabilityError extends Error {
1049
+ readonly capability: CapabilityName;
1050
+ constructor(capability: CapabilityName, hint?: string);
1051
+ }
1052
+ declare class Registry {
1053
+ private readonly entries;
1054
+ private readonly plugins;
1055
+ private readonly capabilities;
1056
+ /** Human-readable reason a gated capability is unavailable, keyed by capability. */
1057
+ private readonly gateHints;
1058
+ /**
1059
+ * Store a value under `namespace:key`.
1060
+ *
1061
+ * Namespacing keeps effects, behaviours, renderers and easings from colliding, and lets
1062
+ * `list('effect')` enumerate one kind without filtering.
1063
+ */
1064
+ register<T>(namespace: string, key: string, value: T): void;
1065
+ get<T>(namespace: string, key: string): T | undefined;
1066
+ /** Like {@link get} but throws a helpful error rather than returning `undefined`. */
1067
+ require<T>(namespace: string, key: string): T;
1068
+ has(namespace: string, key: string): boolean;
1069
+ unregister(namespace: string, key: string): boolean;
1070
+ list(namespace: string): string[];
1071
+ entriesOf<T>(namespace: string): Array<[string, T]>;
1072
+ provide(capability: CapabilityName): void;
1073
+ revoke(capability: CapabilityName, hint?: string): void;
1074
+ supports(capability: CapabilityName): boolean;
1075
+ /**
1076
+ * Assert a capability is available.
1077
+ *
1078
+ * This is the edition gate. A Pro effect calls
1079
+ * `registry.requireCapability('effect:pro')` in its setup, and in the Free edition that
1080
+ * throws with a message explaining what to do — at mount time, with a stack trace pointing
1081
+ * at the component, rather than as a mysteriously blank canvas.
1082
+ */
1083
+ requireCapability(capability: CapabilityName): void;
1084
+ listCapabilities(): CapabilityName[];
1085
+ use(plugin: Plugin): this;
1086
+ remove(name: string): boolean;
1087
+ listPlugins(): Array<{
1088
+ name: string;
1089
+ version: string;
1090
+ }>;
1091
+ /** Wipe everything. Used between tests. */
1092
+ clear(): void;
1093
+ }
1094
+ /** The process-wide registry, created on first use. */
1095
+ declare function getRegistry(): Registry;
1096
+ declare function resetRegistry(): void;
1097
+
1098
+ /**
1099
+ * @ansh-ui/core — the Ansh animation engine.
1100
+ *
1101
+ * A zero-dependency, framework-agnostic kernel. Everything the React, Vue and Nuxt packages do
1102
+ * is a thin binding over what is exported here, and nothing in this package knows those
1103
+ * frameworks exist.
1104
+ *
1105
+ * ## Deep imports
1106
+ *
1107
+ * For the smallest possible bundles, import from a subpath rather than the root:
1108
+ *
1109
+ * ```ts
1110
+ * import { mixColors } from '@ansh-ui/core/color'
1111
+ * import { createNoise } from '@ansh-ui/core/noise'
1112
+ * import { easings } from '@ansh-ui/core/easing'
1113
+ * ```
1114
+ *
1115
+ * Root imports tree-shake correctly in any modern bundler, so this is an optimisation for
1116
+ * unusual setups rather than a requirement.
1117
+ */
1118
+
1119
+ /** The package version, injected at build time and useful in bug reports. */
1120
+ declare const VERSION = "0.1.0";
1121
+
1122
+ export { type AnimationControls, type AnimationOptions, type BudgetReport, CapabilityError, type CapabilityName, Clock, type ClockOptions, ColorSpace, DEFAULT_TWEEN_DURATION, type DecayOptions, EasingInput, FrameBudget, type FrameBudgetOptions, FrameCallback, FramePhase, FrameState, type FrameTiming, type InterpolateOptions, type Interpolator, type IntersectionConfig, type LifecycleOptions, MotionValue, type MotionValueListener, type MotionValueTuple, type PerceptualSpringOptions, type PhysicalSpringOptions, type Plugin, Registry, type RepeatMode, type ResizeInfo, SPRING_DEFAULTS, type Sampler, type SamplerOptions, type SamplerType, type ScrollAxis, type ScrollEdge, type ScrollOffset, type ScrollProgress, type ScrollTrackOptions, type SpringInput, type SpringOptions, type SpringPresetName, type SpringRestOptions, type SpringSolver, type SpringState, StaggerOptions, SubscribeOptions, Ticker, type TickerOptions, type Timeline, type TimelineOptions, type TimelinePosition, Unsubscribe, VERSION, animate, attachLifecycle, createDecay, createSampler, createSpring, createTimeline, derived, getRegistry, getTicker, hasSpringOptions, interpolateArray, interpolateColor, interpolateKeyframes, interpolateNumber, interpolateNumericString, interpolateObject, interpolateUnitValue, mapValue, motionValue, now, observeIntersection, observeResize, observerPoolStats, perceptualToPhysical, physicalToPerceptual, resetObserverPools, resetRegistry, resetTicker, resolveInterpolator, resolveSpringOptions, setTicker, springTo, springs, trackContainerScroll, trackScroll };