@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,1170 @@
1
+ import { getRegistry, trackPointer, resolveMotion, getTicker, observeResize, prefersReducedMotion, observeIntersection } from './chunk-BOQQHICI.js';
2
+ import { parseColor } from './chunk-QALZKHWH.js';
3
+ import { resolveEasing } from './chunk-7MEIXWPH.js';
4
+ import { clamp, damp, lerp } from './chunk-KU2C6GEA.js';
5
+
6
+ // src/behaviours/types.ts
7
+ function mountBehaviour(definition, element, userOptions = {}) {
8
+ getRegistry().requireCapability(definition.capability ?? "behaviour:core");
9
+ let options = { ...definition.defaults, ...stripUndefined(userOptions) };
10
+ let instance = definition.setup(element, options);
11
+ let destroyed = false;
12
+ return {
13
+ update(next) {
14
+ if (destroyed) return;
15
+ const incoming = stripUndefined(next);
16
+ if (Object.keys(incoming).length === 0) return;
17
+ options = { ...options, ...incoming };
18
+ if (instance?.update) {
19
+ instance.update(incoming);
20
+ } else {
21
+ instance?.destroy();
22
+ instance = definition.setup(element, options);
23
+ }
24
+ },
25
+ destroy() {
26
+ if (destroyed) return;
27
+ destroyed = true;
28
+ instance?.destroy();
29
+ instance = null;
30
+ }
31
+ };
32
+ }
33
+ function defineBehaviour(definition) {
34
+ const registry = getRegistry();
35
+ if (!registry.has("behaviour", definition.name)) {
36
+ registry.register("behaviour", definition.name, definition);
37
+ }
38
+ return definition;
39
+ }
40
+ function stripUndefined(input) {
41
+ const out = {};
42
+ for (const [key, value] of Object.entries(input)) {
43
+ if (value !== void 0) out[key] = value;
44
+ }
45
+ return out;
46
+ }
47
+ function captureStyles(element, properties) {
48
+ const saved = /* @__PURE__ */ new Map();
49
+ for (const property of properties) {
50
+ saved.set(property, element.style.getPropertyValue(kebab(property)));
51
+ }
52
+ return () => {
53
+ for (const [property, value] of saved) {
54
+ if (value) element.style.setProperty(kebab(property), value);
55
+ else element.style.removeProperty(kebab(property));
56
+ }
57
+ };
58
+ }
59
+ function kebab(property) {
60
+ return property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
61
+ }
62
+
63
+ // src/behaviours/magnetic.ts
64
+ var DEFAULTS = {
65
+ intensity: 1,
66
+ // Magnetic response is a direct reaction to the user's own input rather than autonomous
67
+ // motion, but it is still large-scale movement, so the safe default is to disable it.
68
+ reducedMotion: "pause",
69
+ disabled: false,
70
+ strength: 12,
71
+ radius: 120,
72
+ stiffness: 260,
73
+ damping: 24,
74
+ scale: 1,
75
+ rotate: 0,
76
+ axis: "both",
77
+ target: ""
78
+ };
79
+ var magnetic = defineBehaviour({
80
+ name: "magnetic",
81
+ defaults: DEFAULTS,
82
+ setup(element, options) {
83
+ let opts = options;
84
+ const target = opts.target ? element.querySelector(opts.target) ?? element : element;
85
+ const restoreStyles = captureStyles(target, ["transform", "willChange"]);
86
+ const pointer = trackPointer(element, { trackOutside: true, smoothing: 1e-3 });
87
+ let x = 0;
88
+ let y = 0;
89
+ let vx = 0;
90
+ let vy = 0;
91
+ let scaleValue = 1;
92
+ let scaleVelocity = 0;
93
+ let rotation = 0;
94
+ let rotationVelocity = 0;
95
+ let motion = resolveMotion(opts.reducedMotion);
96
+ target.style.willChange = "transform";
97
+ const ticker = getTicker();
98
+ const stop = ticker.add(
99
+ ({ delta }) => {
100
+ if (opts.disabled || !motion.animate) return;
101
+ let targetX = 0;
102
+ let targetY = 0;
103
+ let targetScale = 1;
104
+ let targetRotation = 0;
105
+ const rect = element.getBoundingClientRect();
106
+ const centerX = rect.width / 2;
107
+ const centerY = rect.height / 2;
108
+ const offsetX = pointer.state.x - centerX;
109
+ const offsetY = pointer.state.y - centerY;
110
+ const edgeDistanceX = Math.max(0, Math.abs(offsetX) - centerX);
111
+ const edgeDistanceY = Math.max(0, Math.abs(offsetY) - centerY);
112
+ const edgeDistance = Math.hypot(edgeDistanceX, edgeDistanceY);
113
+ const radius = Math.max(1, opts.radius);
114
+ if (pointer.state.everInside || edgeDistance < radius) {
115
+ const proximity = clamp(1 - edgeDistance / radius, 0, 1);
116
+ const influence = proximity * proximity * opts.intensity;
117
+ const maxDisplacement = opts.strength;
118
+ const magnitude = Math.hypot(offsetX, offsetY);
119
+ if (magnitude > 1e-3) {
120
+ const nx = offsetX / magnitude;
121
+ const ny = offsetY / magnitude;
122
+ targetX = nx * maxDisplacement * influence;
123
+ targetY = ny * maxDisplacement * influence;
124
+ }
125
+ if (opts.axis === "x") targetY = 0;
126
+ else if (opts.axis === "y") targetX = 0;
127
+ targetScale = 1 + (opts.scale - 1) * influence;
128
+ targetRotation = opts.rotate * influence * (offsetX / Math.max(1, centerX));
129
+ }
130
+ const stiffness = opts.stiffness;
131
+ const damping = opts.damping;
132
+ const steps = Math.min(4, Math.max(1, Math.ceil(delta / (1 / 60))));
133
+ const dt = delta / steps;
134
+ for (let i = 0; i < steps; i++) {
135
+ vx += (-stiffness * (x - targetX) - damping * vx) * dt;
136
+ vy += (-stiffness * (y - targetY) - damping * vy) * dt;
137
+ x += vx * dt;
138
+ y += vy * dt;
139
+ scaleVelocity += (-stiffness * (scaleValue - targetScale) - damping * scaleVelocity) * dt;
140
+ scaleValue += scaleVelocity * dt;
141
+ rotationVelocity += (-stiffness * (rotation - targetRotation) - damping * rotationVelocity) * dt;
142
+ rotation += rotationVelocity * dt;
143
+ }
144
+ const parts = [`translate3d(${x.toFixed(2)}px, ${y.toFixed(2)}px, 0)`];
145
+ if (opts.scale !== 1) parts.push(`scale(${scaleValue.toFixed(4)})`);
146
+ if (opts.rotate !== 0) parts.push(`rotate(${rotation.toFixed(3)}deg)`);
147
+ target.style.transform = parts.join(" ");
148
+ },
149
+ { phase: "write", name: "behaviour:magnetic" }
150
+ );
151
+ return {
152
+ update(next) {
153
+ opts = { ...opts, ...next };
154
+ motion = resolveMotion(opts.reducedMotion);
155
+ if (opts.disabled || !motion.animate) {
156
+ x = y = vx = vy = 0;
157
+ scaleValue = 1;
158
+ rotation = 0;
159
+ target.style.transform = "";
160
+ }
161
+ },
162
+ destroy() {
163
+ stop();
164
+ pointer.destroy();
165
+ restoreStyles();
166
+ }
167
+ };
168
+ }
169
+ });
170
+
171
+ // src/behaviours/tilt.ts
172
+ var DEFAULTS2 = {
173
+ intensity: 1,
174
+ reducedMotion: "pause",
175
+ disabled: false,
176
+ max: 10,
177
+ perspective: 900,
178
+ scale: 1.02,
179
+ stiffness: 180,
180
+ damping: 22,
181
+ reverse: false,
182
+ lift: 0,
183
+ glare: true,
184
+ glareOpacity: 0.18,
185
+ axis: "both"
186
+ };
187
+ var tilt = defineBehaviour({
188
+ name: "tilt",
189
+ defaults: DEFAULTS2,
190
+ setup(element, options) {
191
+ let opts = options;
192
+ const restoreStyles = captureStyles(element, ["transform", "transformStyle", "willChange"]);
193
+ const pointer = trackPointer(element, { smoothing: 2e-3 });
194
+ let rotateX = 0;
195
+ let rotateY = 0;
196
+ let velocityX = 0;
197
+ let velocityY = 0;
198
+ let presence = 0;
199
+ let presenceVelocity = 0;
200
+ let motion = resolveMotion(opts.reducedMotion);
201
+ element.style.willChange = "transform";
202
+ element.style.transformStyle = "preserve-3d";
203
+ let glareElement = null;
204
+ function ensureGlare() {
205
+ if (!opts.glare) {
206
+ removeGlare();
207
+ return;
208
+ }
209
+ if (glareElement) return;
210
+ const glare = document.createElement("div");
211
+ glare.setAttribute("aria-hidden", "true");
212
+ Object.assign(glare.style, {
213
+ position: "absolute",
214
+ inset: "0",
215
+ pointerEvents: "none",
216
+ borderRadius: "inherit",
217
+ // `overflow: hidden` on the host would clip content, so instead the gradient itself is
218
+ // sized to the element and simply moves within it.
219
+ opacity: "0",
220
+ transition: "none",
221
+ // Kept behind any positioned content but above the element's own background.
222
+ zIndex: "1"
223
+ });
224
+ element.appendChild(glare);
225
+ glareElement = glare;
226
+ }
227
+ function removeGlare() {
228
+ if (glareElement && glareElement.parentNode === element) {
229
+ element.removeChild(glareElement);
230
+ }
231
+ glareElement = null;
232
+ }
233
+ ensureGlare();
234
+ const ticker = getTicker();
235
+ const stop = ticker.add(
236
+ ({ delta }) => {
237
+ if (opts.disabled || !motion.animate) return;
238
+ const state = pointer.state;
239
+ const hovering = state.inside;
240
+ const nx = (state.smoothNx - 0.5) * 2;
241
+ const ny = (state.smoothNy - 0.5) * 2;
242
+ const direction = opts.reverse ? -1 : 1;
243
+ const max = opts.max * opts.intensity;
244
+ let targetRotateX = hovering ? -ny * max * direction : 0;
245
+ let targetRotateY = hovering ? nx * max * direction : 0;
246
+ if (opts.axis === "x") targetRotateY = 0;
247
+ else if (opts.axis === "y") targetRotateX = 0;
248
+ const targetPresence = hovering ? 1 : 0;
249
+ const steps = Math.min(4, Math.max(1, Math.ceil(delta / (1 / 60))));
250
+ const dt = delta / steps;
251
+ const stiffness = opts.stiffness;
252
+ const damping = opts.damping;
253
+ for (let i = 0; i < steps; i++) {
254
+ velocityX += (-stiffness * (rotateX - targetRotateX) - damping * velocityX) * dt;
255
+ velocityY += (-stiffness * (rotateY - targetRotateY) - damping * velocityY) * dt;
256
+ rotateX += velocityX * dt;
257
+ rotateY += velocityY * dt;
258
+ presenceVelocity += (-stiffness * (presence - targetPresence) - damping * presenceVelocity) * dt;
259
+ presence += presenceVelocity * dt;
260
+ }
261
+ presence = clamp(presence, 0, 1.2);
262
+ const scale = 1 + (opts.scale - 1) * presence;
263
+ const lift = opts.lift * presence;
264
+ element.style.transform = `perspective(${opts.perspective}px) rotateX(${rotateX.toFixed(3)}deg) rotateY(${rotateY.toFixed(3)}deg) translateZ(${lift.toFixed(2)}px) scale(${scale.toFixed(4)})`;
265
+ if (glareElement) {
266
+ const opacity = opts.glareOpacity * presence;
267
+ glareElement.style.opacity = opacity.toFixed(3);
268
+ if (opacity > 1e-3) {
269
+ const angle = Math.atan2(ny, nx) * (180 / Math.PI) + 90;
270
+ glareElement.style.background = `linear-gradient(${angle.toFixed(1)}deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0.12) 45%, rgba(255,255,255,0) 70%)`;
271
+ }
272
+ }
273
+ },
274
+ { phase: "write", name: "behaviour:tilt" }
275
+ );
276
+ return {
277
+ update(next) {
278
+ const previousGlare = opts.glare;
279
+ opts = { ...opts, ...next };
280
+ motion = resolveMotion(opts.reducedMotion);
281
+ if (opts.glare !== previousGlare) ensureGlare();
282
+ if (opts.disabled || !motion.animate) {
283
+ rotateX = rotateY = velocityX = velocityY = 0;
284
+ presence = presenceVelocity = 0;
285
+ element.style.transform = "";
286
+ if (glareElement) glareElement.style.opacity = "0";
287
+ }
288
+ },
289
+ destroy() {
290
+ stop();
291
+ pointer.destroy();
292
+ removeGlare();
293
+ restoreStyles();
294
+ }
295
+ };
296
+ }
297
+ });
298
+
299
+ // src/behaviours/marquee.ts
300
+ var DEFAULTS3 = {
301
+ intensity: 1,
302
+ // Continuous horizontal motion across a wide area is a classic vestibular trigger, so the
303
+ // safe default is to hold it still. The content stays fully readable.
304
+ reducedMotion: "static",
305
+ disabled: false,
306
+ speed: 60,
307
+ direction: "left",
308
+ pauseOnHover: true,
309
+ pauseEasing: 0.4,
310
+ gap: 0,
311
+ fadeEdges: 0
312
+ };
313
+ var marquee = defineBehaviour({
314
+ name: "marquee",
315
+ defaults: DEFAULTS3,
316
+ setup(element, options) {
317
+ let opts = options;
318
+ const restoreHost = captureStyles(element, [
319
+ "overflow",
320
+ "display",
321
+ "maskImage",
322
+ "webkitMaskImage"
323
+ ]);
324
+ const track = document.createElement("div");
325
+ const originalChildren = Array.from(element.childNodes);
326
+ const vertical = opts.direction === "up" || opts.direction === "down";
327
+ Object.assign(element.style, {
328
+ overflow: "hidden",
329
+ display: vertical ? "block" : "flex"
330
+ });
331
+ Object.assign(track.style, {
332
+ display: "flex",
333
+ flexDirection: vertical ? "column" : "row",
334
+ flexShrink: "0",
335
+ willChange: "transform",
336
+ gap: opts.gap ? `${opts.gap}px` : ""
337
+ });
338
+ for (const child of originalChildren) track.appendChild(child);
339
+ element.appendChild(track);
340
+ let clones = [];
341
+ let cycleSize = 0;
342
+ let offset = 0;
343
+ let currentSpeed = 0;
344
+ let hovering = false;
345
+ let motion = resolveMotion(opts.reducedMotion);
346
+ function rebuild() {
347
+ for (const clone of clones) clone.remove();
348
+ clones = [];
349
+ const containerSize = vertical ? element.clientHeight : element.clientWidth;
350
+ const trackSize = vertical ? track.scrollHeight : track.scrollWidth;
351
+ cycleSize = trackSize + (opts.gap ?? 0);
352
+ if (cycleSize <= 0 || containerSize <= 0) return;
353
+ const needed = Math.ceil(containerSize / cycleSize) + 1;
354
+ for (let i = 0; i < needed; i++) {
355
+ const clone = track.cloneNode(true);
356
+ clone.setAttribute("aria-hidden", "true");
357
+ clone.style.willChange = "transform";
358
+ element.appendChild(clone);
359
+ clones.push(clone);
360
+ }
361
+ }
362
+ function applyEdgeFade() {
363
+ const fade = opts.fadeEdges ?? 0;
364
+ if (fade <= 0) {
365
+ element.style.removeProperty("mask-image");
366
+ element.style.removeProperty("-webkit-mask-image");
367
+ return;
368
+ }
369
+ const axis = vertical ? "to bottom" : "to right";
370
+ const mask = `linear-gradient(${axis}, transparent 0, black ${fade}px, black calc(100% - ${fade}px), transparent 100%)`;
371
+ element.style.setProperty("mask-image", mask);
372
+ element.style.setProperty("-webkit-mask-image", mask);
373
+ }
374
+ const stopResize = observeResize(element, rebuild);
375
+ const stopTrackResize = observeResize(track, rebuild);
376
+ const onEnter = () => {
377
+ hovering = true;
378
+ };
379
+ const onLeave = () => {
380
+ hovering = false;
381
+ };
382
+ element.addEventListener("pointerenter", onEnter, { passive: true });
383
+ element.addEventListener("pointerleave", onLeave, { passive: true });
384
+ applyEdgeFade();
385
+ rebuild();
386
+ const ticker = getTicker();
387
+ const stop = ticker.add(
388
+ ({ delta }) => {
389
+ if (opts.disabled || !motion.animate || cycleSize <= 0) return;
390
+ const targetSpeed = opts.pauseOnHover && hovering ? 0 : opts.speed * opts.intensity * motion.timeScale;
391
+ const easing = Math.max(0, opts.pauseEasing);
392
+ currentSpeed = easing <= 0 ? targetSpeed : damp(currentSpeed, targetSpeed, 1e-4 ** (1 / easing), delta);
393
+ offset += currentSpeed * delta;
394
+ offset = (offset % cycleSize + cycleSize) % cycleSize;
395
+ const sign = opts.direction === "left" || opts.direction === "up" ? -1 : 1;
396
+ const translate = sign * offset;
397
+ const transform = vertical ? `translate3d(0, ${translate.toFixed(2)}px, 0)` : `translate3d(${translate.toFixed(2)}px, 0, 0)`;
398
+ track.style.transform = transform;
399
+ for (const clone of clones) clone.style.transform = transform;
400
+ },
401
+ { phase: "write", name: "behaviour:marquee" }
402
+ );
403
+ return {
404
+ update(next) {
405
+ const previousGap = opts.gap;
406
+ const previousFade = opts.fadeEdges;
407
+ opts = { ...opts, ...next };
408
+ motion = resolveMotion(opts.reducedMotion);
409
+ if (opts.gap !== previousGap) {
410
+ track.style.gap = opts.gap ? `${opts.gap}px` : "";
411
+ rebuild();
412
+ }
413
+ if (opts.fadeEdges !== previousFade) applyEdgeFade();
414
+ },
415
+ destroy() {
416
+ stop();
417
+ stopResize();
418
+ stopTrackResize();
419
+ element.removeEventListener("pointerenter", onEnter);
420
+ element.removeEventListener("pointerleave", onLeave);
421
+ for (const clone of clones) clone.remove();
422
+ clones = [];
423
+ for (const child of originalChildren) element.appendChild(child);
424
+ track.remove();
425
+ restoreHost();
426
+ }
427
+ };
428
+ }
429
+ });
430
+
431
+ // src/behaviours/split-text.ts
432
+ var DEFAULTS4 = {
433
+ intensity: 1,
434
+ reducedMotion: "none",
435
+ disabled: false,
436
+ by: "chars",
437
+ charClass: "ansh-char",
438
+ wordClass: "ansh-word",
439
+ lineClass: "ansh-line",
440
+ cssVariables: true,
441
+ mask: false
442
+ };
443
+ var results = /* @__PURE__ */ new WeakMap();
444
+ function getSplitResult(element) {
445
+ return results.get(element);
446
+ }
447
+ var splitText = defineBehaviour({
448
+ name: "split-text",
449
+ defaults: DEFAULTS4,
450
+ setup(element, options) {
451
+ const opts = options;
452
+ const units = Array.isArray(opts.by) ? opts.by : [opts.by];
453
+ const originalHTML = element.innerHTML;
454
+ const originalText = element.textContent ?? "";
455
+ const result = { chars: [], words: [], lines: [] };
456
+ function createAccessibleCopy() {
457
+ const sr = document.createElement("span");
458
+ sr.textContent = originalText;
459
+ Object.assign(sr.style, {
460
+ position: "absolute",
461
+ width: "1px",
462
+ height: "1px",
463
+ padding: "0",
464
+ margin: "-1px",
465
+ overflow: "hidden",
466
+ clipPath: "inset(50%)",
467
+ whiteSpace: "nowrap",
468
+ borderWidth: "0"
469
+ });
470
+ return sr;
471
+ }
472
+ function makeUnit(text, className) {
473
+ const span = document.createElement("span");
474
+ span.className = className;
475
+ span.textContent = text;
476
+ span.style.display = "inline-block";
477
+ return span;
478
+ }
479
+ function wrapInMask(unit) {
480
+ const wrapper = document.createElement("span");
481
+ wrapper.className = "ansh-mask";
482
+ Object.assign(wrapper.style, {
483
+ display: "inline-block",
484
+ overflow: "hidden",
485
+ // Without this, descenders (g, y, p) are clipped by the mask box.
486
+ verticalAlign: "top"
487
+ });
488
+ wrapper.appendChild(unit);
489
+ return wrapper;
490
+ }
491
+ function stampVariables(elements) {
492
+ if (!opts.cssVariables) return;
493
+ const total = elements.length;
494
+ elements.forEach((el, index) => {
495
+ el.style.setProperty("--ansh-index", String(index));
496
+ el.style.setProperty("--ansh-total", String(total));
497
+ el.style.setProperty("--ansh-progress", total <= 1 ? "0" : (index / (total - 1)).toFixed(4));
498
+ });
499
+ }
500
+ function segmentCharacters(text) {
501
+ if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
502
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
503
+ return Array.from(segmenter.segment(text), (s) => s.segment);
504
+ }
505
+ return Array.from(text);
506
+ }
507
+ function buildWordsAndChars(container, text) {
508
+ const wantsWords = units.includes("words") || units.includes("lines");
509
+ const wantsChars = units.includes("chars");
510
+ const tokens = text.split(/(\s+)/);
511
+ for (const token of tokens) {
512
+ if (token.length === 0) continue;
513
+ if (/^\s+$/.test(token)) {
514
+ container.appendChild(document.createTextNode(token));
515
+ continue;
516
+ }
517
+ if (wantsWords) {
518
+ const word = makeUnit(wantsChars ? "" : token, opts.wordClass);
519
+ if (wantsChars) {
520
+ for (const character of segmentCharacters(token)) {
521
+ const char = makeUnit(character, opts.charClass);
522
+ result.chars.push(char);
523
+ word.appendChild(opts.mask ? wrapInMask(char) : char);
524
+ }
525
+ }
526
+ result.words.push(word);
527
+ container.appendChild(opts.mask && !wantsChars ? wrapInMask(word) : word);
528
+ } else if (wantsChars) {
529
+ for (const character of segmentCharacters(token)) {
530
+ const char = makeUnit(character, opts.charClass);
531
+ result.chars.push(char);
532
+ container.appendChild(opts.mask ? wrapInMask(char) : char);
533
+ }
534
+ } else {
535
+ container.appendChild(document.createTextNode(token));
536
+ }
537
+ }
538
+ }
539
+ function groupIntoLines(container) {
540
+ if (result.words.length === 0) return;
541
+ const lines = [];
542
+ let currentTop = null;
543
+ let current = [];
544
+ for (const word of result.words) {
545
+ const top = word.offsetTop;
546
+ if (currentTop === null || Math.abs(top - currentTop) <= 2) {
547
+ currentTop = currentTop ?? top;
548
+ current.push(word);
549
+ } else {
550
+ lines.push(current);
551
+ current = [word];
552
+ currentTop = top;
553
+ }
554
+ }
555
+ if (current.length > 0) lines.push(current);
556
+ for (const lineWords of lines) {
557
+ const line = document.createElement("span");
558
+ line.className = opts.lineClass;
559
+ line.style.display = "inline-block";
560
+ const first = lineWords[0];
561
+ const parent = first.parentElement ?? container;
562
+ parent.insertBefore(opts.mask ? wrapInMask(line) : line, first);
563
+ for (const word of lineWords) {
564
+ line.appendChild(word);
565
+ }
566
+ result.lines.push(line);
567
+ }
568
+ }
569
+ function build() {
570
+ result.chars = [];
571
+ result.words = [];
572
+ result.lines = [];
573
+ const fragment = document.createDocumentFragment();
574
+ const visual = document.createElement("span");
575
+ visual.setAttribute("aria-hidden", "true");
576
+ buildWordsAndChars(visual, originalText);
577
+ fragment.appendChild(createAccessibleCopy());
578
+ fragment.appendChild(visual);
579
+ element.replaceChildren(fragment);
580
+ if (units.includes("lines")) groupIntoLines(visual);
581
+ stampVariables(result.chars);
582
+ stampVariables(result.words);
583
+ stampVariables(result.lines);
584
+ results.set(element, result);
585
+ }
586
+ if (!opts.disabled) build();
587
+ return {
588
+ destroy() {
589
+ results.delete(element);
590
+ element.innerHTML = originalHTML;
591
+ }
592
+ };
593
+ }
594
+ });
595
+ function split(element, options = {}) {
596
+ const originalHTML = element.innerHTML;
597
+ const definition = splitText;
598
+ const merged = { ...definition.defaults, ...options };
599
+ const instance = definition.setup(element, merged);
600
+ const result = results.get(element) ?? { chars: [], words: [], lines: [] };
601
+ return {
602
+ ...result,
603
+ restore() {
604
+ instance.destroy();
605
+ element.innerHTML = originalHTML;
606
+ }
607
+ };
608
+ }
609
+
610
+ // src/motion/stagger.ts
611
+ function stagger(count, options = {}) {
612
+ const total = Math.max(1, Math.floor(count));
613
+ const from = options.from ?? "first";
614
+ const baseDelay = options.delay ?? 0;
615
+ const ease = resolveEasing(options.easing);
616
+ if (total === 1) return () => baseDelay;
617
+ const distances = computeDistances(total, from, options.grid, options.axis);
618
+ let maxDistance = 0;
619
+ for (const d of distances) if (d > maxDistance) maxDistance = d;
620
+ const spanSeconds = options.total !== void 0 ? Math.max(0, options.total) : Math.max(0, options.each ?? 0.05) * maxDistance;
621
+ return (index) => {
622
+ const i = clamp(Math.floor(index), 0, total - 1);
623
+ const normalized = maxDistance === 0 ? 0 : distances[i] / maxDistance;
624
+ return baseDelay + ease(normalized) * spanSeconds;
625
+ };
626
+ }
627
+ function computeDistances(count, from, grid, axis) {
628
+ const distances = new Array(count);
629
+ if (grid) {
630
+ const [columns] = grid;
631
+ const cols = Math.max(1, Math.floor(columns));
632
+ const rows = Math.max(1, Math.ceil(count / cols));
633
+ const origin = resolveGridOrigin(from, cols, rows, count);
634
+ for (let i = 0; i < count; i++) {
635
+ const x = i % cols;
636
+ const y = Math.floor(i / cols);
637
+ const dx = x - origin.x;
638
+ const dy = y - origin.y;
639
+ if (axis === "x") distances[i] = Math.abs(dx);
640
+ else if (axis === "y") distances[i] = Math.abs(dy);
641
+ else distances[i] = Math.hypot(dx, dy);
642
+ }
643
+ if (from === "edges") {
644
+ let max = 0;
645
+ for (const d of distances) if (d > max) max = d;
646
+ for (let i = 0; i < count; i++) distances[i] = max - distances[i];
647
+ }
648
+ return distances;
649
+ }
650
+ switch (from) {
651
+ case "last":
652
+ for (let i = 0; i < count; i++) distances[i] = count - 1 - i;
653
+ return distances;
654
+ case "center": {
655
+ const centre = (count - 1) / 2;
656
+ for (let i = 0; i < count; i++) distances[i] = Math.abs(i - centre);
657
+ return distances;
658
+ }
659
+ case "edges": {
660
+ const centre = (count - 1) / 2;
661
+ for (let i = 0; i < count; i++) distances[i] = centre - Math.abs(i - centre);
662
+ return distances;
663
+ }
664
+ case "first":
665
+ for (let i = 0; i < count; i++) distances[i] = i;
666
+ return distances;
667
+ default: {
668
+ const origin = clamp(Math.floor(from), 0, count - 1);
669
+ for (let i = 0; i < count; i++) distances[i] = Math.abs(i - origin);
670
+ return distances;
671
+ }
672
+ }
673
+ }
674
+ function resolveGridOrigin(from, cols, rows, count) {
675
+ if (typeof from === "number") {
676
+ const index = clamp(Math.floor(from), 0, count - 1);
677
+ return { x: index % cols, y: Math.floor(index / cols) };
678
+ }
679
+ switch (from) {
680
+ case "last":
681
+ return { x: cols - 1, y: rows - 1 };
682
+ case "center":
683
+ case "edges":
684
+ return { x: (cols - 1) / 2, y: (rows - 1) / 2 };
685
+ case "first":
686
+ default:
687
+ return { x: 0, y: 0 };
688
+ }
689
+ }
690
+ function staggerDuration(count, itemDuration, options = {}) {
691
+ const delayFor = stagger(count, options);
692
+ let latest = 0;
693
+ for (let i = 0; i < count; i++) {
694
+ const end = delayFor(i) + itemDuration;
695
+ if (end > latest) latest = end;
696
+ }
697
+ return latest;
698
+ }
699
+
700
+ // src/behaviours/scroll-reveal.ts
701
+ var DEFAULTS5 = {
702
+ intensity: 1,
703
+ reducedMotion: "static",
704
+ disabled: false,
705
+ distance: 24,
706
+ direction: "up",
707
+ opacity: 0,
708
+ scale: 1,
709
+ rotate: 0,
710
+ blur: 0,
711
+ duration: 0.7,
712
+ easing: "cinematic",
713
+ delay: 0,
714
+ threshold: 0.15,
715
+ rootMargin: "0px 0px -10% 0px",
716
+ repeat: false,
717
+ selector: "",
718
+ stagger: { each: 0.06, from: "first" }
719
+ };
720
+ var scrollReveal = defineBehaviour({
721
+ name: "scroll-reveal",
722
+ defaults: DEFAULTS5,
723
+ setup(element, options) {
724
+ let opts = options;
725
+ const easing = resolveEasing(opts.easing);
726
+ const targets = [];
727
+ let revealing = false;
728
+ let reversing = false;
729
+ let stopTicker = null;
730
+ function collectTargets() {
731
+ const elements = opts.selector ? Array.from(element.querySelectorAll(opts.selector)) : [element];
732
+ const delayFor = stagger(elements.length, opts.stagger);
733
+ elements.forEach((el, index) => {
734
+ targets.push({
735
+ element: el,
736
+ restore: captureStyles(el, ["opacity", "transform", "filter", "willChange"]),
737
+ time: 0,
738
+ delay: opts.delay + (opts.selector ? delayFor(index) : 0),
739
+ done: false
740
+ });
741
+ });
742
+ }
743
+ function applyProgress(target, progress) {
744
+ const eased = easing(clamp(progress, 0, 1));
745
+ const inverse = 1 - eased;
746
+ const intensity = opts.intensity;
747
+ const distance = opts.distance * intensity * inverse;
748
+ let translateX = 0;
749
+ let translateY = 0;
750
+ switch (opts.direction) {
751
+ case "up":
752
+ translateY = distance;
753
+ break;
754
+ case "down":
755
+ translateY = -distance;
756
+ break;
757
+ case "left":
758
+ translateX = distance;
759
+ break;
760
+ case "right":
761
+ translateX = -distance;
762
+ break;
763
+ }
764
+ const style = target.element.style;
765
+ style.opacity = String(opts.opacity + (1 - opts.opacity) * eased);
766
+ const parts = [];
767
+ if (translateX !== 0 || translateY !== 0) {
768
+ parts.push(`translate3d(${translateX.toFixed(2)}px, ${translateY.toFixed(2)}px, 0)`);
769
+ }
770
+ if (opts.scale !== 1) {
771
+ parts.push(`scale(${(opts.scale + (1 - opts.scale) * eased).toFixed(4)})`);
772
+ }
773
+ if (opts.rotate !== 0) {
774
+ parts.push(`rotate(${(opts.rotate * inverse).toFixed(3)}deg)`);
775
+ }
776
+ style.transform = parts.length > 0 ? parts.join(" ") : "";
777
+ if (opts.blur > 0) {
778
+ const blur = opts.blur * inverse;
779
+ style.filter = blur > 0.05 ? `blur(${blur.toFixed(2)}px)` : "";
780
+ }
781
+ }
782
+ function settle(target) {
783
+ const style = target.element.style;
784
+ style.opacity = "";
785
+ style.transform = "";
786
+ style.filter = "";
787
+ style.willChange = "";
788
+ target.done = true;
789
+ }
790
+ function hide(target) {
791
+ target.element.style.willChange = "opacity, transform";
792
+ target.done = false;
793
+ target.time = 0;
794
+ applyProgress(target, 0);
795
+ }
796
+ const ticker = getTicker();
797
+ function startTicker() {
798
+ if (stopTicker) return;
799
+ stopTicker = ticker.add(
800
+ ({ delta }) => {
801
+ let allDone = true;
802
+ for (const target of targets) {
803
+ if (target.done && !reversing) continue;
804
+ target.time += reversing ? -delta : delta;
805
+ const elapsed = target.time - target.delay;
806
+ if (elapsed < 0) {
807
+ allDone = false;
808
+ continue;
809
+ }
810
+ const progress = opts.duration <= 0 ? 1 : elapsed / opts.duration;
811
+ if (!reversing && progress >= 1) {
812
+ settle(target);
813
+ continue;
814
+ }
815
+ if (reversing && target.time <= 0) {
816
+ applyProgress(target, 0);
817
+ continue;
818
+ }
819
+ applyProgress(target, progress);
820
+ allDone = false;
821
+ }
822
+ if (allDone) {
823
+ stopTicker?.();
824
+ stopTicker = null;
825
+ }
826
+ },
827
+ { phase: "write", name: "behaviour:scroll-reveal" }
828
+ );
829
+ }
830
+ collectTargets();
831
+ const skipAnimation = opts.disabled || prefersReducedMotion() && opts.reducedMotion !== "none";
832
+ if (!skipAnimation) {
833
+ for (const target of targets) hide(target);
834
+ }
835
+ const stopObserver = skipAnimation ? () => {
836
+ } : observeIntersection(
837
+ element,
838
+ (entry) => {
839
+ if (entry.isIntersecting) {
840
+ if (revealing && !opts.repeat) return;
841
+ revealing = true;
842
+ reversing = false;
843
+ for (const target of targets) {
844
+ if (target.done) continue;
845
+ target.time = 0;
846
+ }
847
+ startTicker();
848
+ } else if (opts.repeat) {
849
+ reversing = true;
850
+ for (const target of targets) {
851
+ target.done = false;
852
+ target.time = opts.duration + target.delay;
853
+ target.element.style.willChange = "opacity, transform";
854
+ }
855
+ startTicker();
856
+ }
857
+ },
858
+ { threshold: opts.threshold, rootMargin: opts.rootMargin }
859
+ );
860
+ return {
861
+ update(next) {
862
+ opts = { ...opts, ...next };
863
+ if (opts.disabled) {
864
+ for (const target of targets) settle(target);
865
+ }
866
+ },
867
+ destroy() {
868
+ stopObserver();
869
+ stopTicker?.();
870
+ stopTicker = null;
871
+ for (const target of targets) target.restore();
872
+ targets.length = 0;
873
+ }
874
+ };
875
+ }
876
+ });
877
+
878
+ // src/behaviours/parallax.ts
879
+ var DEFAULTS6 = {
880
+ intensity: 1,
881
+ // Parallax is the canonical vestibular trigger. Under reduced motion it is disabled outright,
882
+ // not slowed — a slow parallax is still parallax.
883
+ reducedMotion: "pause",
884
+ disabled: false,
885
+ speed: 0.3,
886
+ axis: "y",
887
+ scale: 1,
888
+ rotate: 0,
889
+ fade: [1, 1, 1],
890
+ contain: true,
891
+ smoothing: 0
892
+ };
893
+ var parallax = defineBehaviour({
894
+ name: "parallax",
895
+ defaults: DEFAULTS6,
896
+ setup(element, options) {
897
+ let opts = options;
898
+ const restore = captureStyles(element, ["transform", "opacity", "willChange"]);
899
+ let elementTop = 0;
900
+ let elementHeight = 1;
901
+ let viewportHeight = 1;
902
+ let containerSize = 0;
903
+ let needsMeasure = true;
904
+ let currentTranslate = 0;
905
+ let motion = resolveMotion(opts.reducedMotion);
906
+ element.style.willChange = "transform";
907
+ function measure() {
908
+ const rect = element.getBoundingClientRect();
909
+ const scroll2 = opts.axis === "y" ? window.scrollY : window.scrollX;
910
+ elementTop = (opts.axis === "y" ? rect.top : rect.left) + scroll2;
911
+ elementHeight = Math.max(1, opts.axis === "y" ? rect.height : rect.width);
912
+ viewportHeight = Math.max(1, opts.axis === "y" ? window.innerHeight : window.innerWidth);
913
+ const parent = element.parentElement;
914
+ if (parent) {
915
+ const parentRect = parent.getBoundingClientRect();
916
+ containerSize = opts.axis === "y" ? parentRect.height : parentRect.width;
917
+ } else {
918
+ containerSize = elementHeight;
919
+ }
920
+ needsMeasure = false;
921
+ }
922
+ const invalidate = () => {
923
+ needsMeasure = true;
924
+ };
925
+ window.addEventListener("resize", invalidate, { passive: true });
926
+ const stopResize = observeResize(element, invalidate);
927
+ let scroll = 0;
928
+ let progress = 0;
929
+ const ticker = getTicker();
930
+ const stopRead = ticker.add(
931
+ () => {
932
+ if (opts.disabled || !motion.animate) return;
933
+ if (needsMeasure) measure();
934
+ scroll = opts.axis === "y" ? window.scrollY : window.scrollX;
935
+ },
936
+ { phase: "read", name: "behaviour:parallax:read" }
937
+ );
938
+ const stopWrite = ticker.add(
939
+ ({ delta }) => {
940
+ if (opts.disabled || !motion.animate) return;
941
+ const start = elementTop - viewportHeight;
942
+ const end = elementTop + elementHeight;
943
+ const span = Math.max(1, end - start);
944
+ progress = clamp((scroll - start) / span, 0, 1);
945
+ const centred = progress - 0.5;
946
+ let translate = centred * viewportHeight * opts.speed * opts.intensity;
947
+ if (opts.contain) {
948
+ const slack = Math.max(0, containerSize - elementHeight) / 2 || elementHeight * 0.25;
949
+ translate = clamp(translate, -slack, slack);
950
+ }
951
+ if (opts.smoothing > 0) {
952
+ const retain = Math.pow(clamp(opts.smoothing, 1e-3, 0.999), delta);
953
+ currentTranslate = lerp(translate, currentTranslate, retain);
954
+ } else {
955
+ currentTranslate = translate;
956
+ }
957
+ const parts = [];
958
+ if (opts.axis === "y") {
959
+ parts.push(`translate3d(0, ${currentTranslate.toFixed(2)}px, 0)`);
960
+ } else {
961
+ parts.push(`translate3d(${currentTranslate.toFixed(2)}px, 0, 0)`);
962
+ }
963
+ if (opts.scale !== 1) {
964
+ const proximity = 1 - Math.abs(centred) * 2;
965
+ parts.push(`scale(${(1 + (opts.scale - 1) * clamp(proximity, 0, 1)).toFixed(4)})`);
966
+ }
967
+ if (opts.rotate !== 0) {
968
+ parts.push(`rotate(${(centred * opts.rotate * 2).toFixed(3)}deg)`);
969
+ }
970
+ element.style.transform = parts.join(" ");
971
+ const [enter, centre, exit] = opts.fade;
972
+ if (enter !== 1 || centre !== 1 || exit !== 1) {
973
+ const opacity = progress < 0.5 ? lerp(enter, centre, progress * 2) : lerp(centre, exit, (progress - 0.5) * 2);
974
+ element.style.opacity = opacity.toFixed(4);
975
+ }
976
+ },
977
+ { phase: "write", name: "behaviour:parallax:write" }
978
+ );
979
+ return {
980
+ update(next) {
981
+ opts = { ...opts, ...next };
982
+ motion = resolveMotion(opts.reducedMotion);
983
+ needsMeasure = true;
984
+ if (opts.disabled || !motion.animate) {
985
+ element.style.transform = "";
986
+ element.style.opacity = "";
987
+ }
988
+ },
989
+ destroy() {
990
+ stopRead();
991
+ stopWrite();
992
+ stopResize();
993
+ window.removeEventListener("resize", invalidate);
994
+ restore();
995
+ }
996
+ };
997
+ }
998
+ });
999
+
1000
+ // src/behaviours/ripple.ts
1001
+ var DEFAULTS7 = {
1002
+ intensity: 1,
1003
+ // A ripple is a small, local, user-initiated response — not the kind of motion that causes
1004
+ // vestibular discomfort. But it is still motion, so `slow` rather than `none` is the honest
1005
+ // default: reduced-motion users get a quick, subdued version instead of nothing.
1006
+ reducedMotion: "slow",
1007
+ disabled: false,
1008
+ color: "currentColor",
1009
+ opacity: 0.28,
1010
+ duration: 0.55,
1011
+ fadeDuration: 0.25,
1012
+ easing: "easeOutQuart",
1013
+ centered: false,
1014
+ keyboard: true,
1015
+ maxRipples: 6
1016
+ };
1017
+ var ripple = defineBehaviour({
1018
+ name: "ripple",
1019
+ defaults: DEFAULTS7,
1020
+ setup(element, options) {
1021
+ let opts = options;
1022
+ let easing = resolveEasing(opts.easing);
1023
+ const restore = captureStyles(element, ["position", "overflow"]);
1024
+ if (getComputedStyle(element).position === "static") {
1025
+ element.style.position = "relative";
1026
+ }
1027
+ const layer = document.createElement("span");
1028
+ layer.setAttribute("aria-hidden", "true");
1029
+ Object.assign(layer.style, {
1030
+ position: "absolute",
1031
+ inset: "0",
1032
+ overflow: "hidden",
1033
+ // Inherits the host's rounding, so a pill button clips its ripple to a pill.
1034
+ borderRadius: "inherit",
1035
+ pointerEvents: "none"
1036
+ });
1037
+ element.appendChild(layer);
1038
+ const active = [];
1039
+ let stopTicker = null;
1040
+ const ticker = getTicker();
1041
+ function resolveColor() {
1042
+ const raw = opts.color;
1043
+ if (raw === "currentColor") {
1044
+ const computed = getComputedStyle(element).color;
1045
+ try {
1046
+ const parsed = parseColor(computed);
1047
+ return `${Math.round(parsed.r * 255)},${Math.round(parsed.g * 255)},${Math.round(parsed.b * 255)}`;
1048
+ } catch {
1049
+ return "255,255,255";
1050
+ }
1051
+ }
1052
+ try {
1053
+ const parsed = parseColor(raw);
1054
+ return `${Math.round(parsed.r * 255)},${Math.round(parsed.g * 255)},${Math.round(parsed.b * 255)}`;
1055
+ } catch {
1056
+ return "255,255,255";
1057
+ }
1058
+ }
1059
+ function spawn(clientX, clientY) {
1060
+ if (opts.disabled) return;
1061
+ const rect = element.getBoundingClientRect();
1062
+ const x = opts.centered ? rect.width / 2 : clientX - rect.left;
1063
+ const y = opts.centered ? rect.height / 2 : clientY - rect.top;
1064
+ const radius = Math.max(
1065
+ Math.hypot(x, y),
1066
+ Math.hypot(rect.width - x, y),
1067
+ Math.hypot(x, rect.height - y),
1068
+ Math.hypot(rect.width - x, rect.height - y)
1069
+ );
1070
+ const dot = document.createElement("div");
1071
+ Object.assign(dot.style, {
1072
+ position: "absolute",
1073
+ left: `${x - radius}px`,
1074
+ top: `${y - radius}px`,
1075
+ width: `${radius * 2}px`,
1076
+ height: `${radius * 2}px`,
1077
+ borderRadius: "50%",
1078
+ // A soft edge rather than a hard circle. A hard-edged expanding disc reads as a shape;
1079
+ // a graded one reads as a wave.
1080
+ background: `radial-gradient(circle, rgba(${resolveColor()},1) 0%, rgba(${resolveColor()},0.75) 55%, rgba(${resolveColor()},0) 100%)`,
1081
+ opacity: "0",
1082
+ transform: "scale(0)",
1083
+ willChange: "transform, opacity",
1084
+ pointerEvents: "none"
1085
+ });
1086
+ layer.appendChild(dot);
1087
+ active.push({ element: dot, radius, time: 0 });
1088
+ while (active.length > Math.max(1, opts.maxRipples)) {
1089
+ const oldest = active.shift();
1090
+ oldest?.element.remove();
1091
+ }
1092
+ startTicker();
1093
+ }
1094
+ function startTicker() {
1095
+ if (stopTicker) return;
1096
+ stopTicker = ticker.add(
1097
+ ({ delta }) => {
1098
+ const scale = prefersReducedMotion() && opts.reducedMotion === "slow" ? 0.4 : 1;
1099
+ const duration = Math.max(0.01, opts.duration * scale);
1100
+ const fade = Math.max(0, opts.fadeDuration * scale);
1101
+ const total = duration + fade;
1102
+ const peak = clamp(opts.opacity * opts.intensity, 0, 1);
1103
+ for (let i = active.length - 1; i >= 0; i--) {
1104
+ const r = active[i];
1105
+ r.time += delta;
1106
+ if (r.time >= total) {
1107
+ r.element.remove();
1108
+ active.splice(i, 1);
1109
+ continue;
1110
+ }
1111
+ const expansion = clamp(r.time / duration, 0, 1);
1112
+ r.element.style.transform = `scale(${easing(expansion).toFixed(4)})`;
1113
+ const rampIn = clamp(r.time / 0.08, 0, 1);
1114
+ const decay = 1 - clamp(r.time / total, 0, 1);
1115
+ r.element.style.opacity = (peak * rampIn * decay).toFixed(4);
1116
+ }
1117
+ if (active.length === 0) {
1118
+ stopTicker?.();
1119
+ stopTicker = null;
1120
+ }
1121
+ },
1122
+ { phase: "write", name: "behaviour:ripple" }
1123
+ );
1124
+ }
1125
+ const onPointerDown = (event) => {
1126
+ if (event.button !== 0 && event.pointerType === "mouse") return;
1127
+ spawn(event.clientX, event.clientY);
1128
+ };
1129
+ const onKeyDown = (event) => {
1130
+ if (!opts.keyboard) return;
1131
+ if (event.key !== "Enter" && event.key !== " ") return;
1132
+ if (event.repeat) return;
1133
+ const rect = element.getBoundingClientRect();
1134
+ spawn(rect.left + rect.width / 2, rect.top + rect.height / 2);
1135
+ };
1136
+ element.addEventListener("pointerdown", onPointerDown, { passive: true });
1137
+ element.addEventListener("keydown", onKeyDown);
1138
+ return {
1139
+ update(next) {
1140
+ opts = { ...opts, ...next };
1141
+ easing = resolveEasing(opts.easing);
1142
+ },
1143
+ destroy() {
1144
+ element.removeEventListener("pointerdown", onPointerDown);
1145
+ element.removeEventListener("keydown", onKeyDown);
1146
+ stopTicker?.();
1147
+ stopTicker = null;
1148
+ for (const r of active) r.element.remove();
1149
+ active.length = 0;
1150
+ layer.remove();
1151
+ restore();
1152
+ }
1153
+ };
1154
+ }
1155
+ });
1156
+
1157
+ // src/behaviours/index.ts
1158
+ var behaviours = {
1159
+ magnetic,
1160
+ tilt,
1161
+ marquee,
1162
+ "split-text": splitText,
1163
+ "scroll-reveal": scrollReveal,
1164
+ parallax,
1165
+ ripple
1166
+ };
1167
+
1168
+ export { behaviours, captureStyles, defineBehaviour, getSplitResult, magnetic, marquee, mountBehaviour, parallax, ripple, scrollReveal, split, splitText, stagger, staggerDuration, tilt };
1169
+ //# sourceMappingURL=chunk-C6XPQO2O.js.map
1170
+ //# sourceMappingURL=chunk-C6XPQO2O.js.map