svg-scroll-draw 2.7.0 → 2.10.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 (76) hide show
  1. package/dist/angular/index.cjs +3 -3
  2. package/dist/angular/index.d.mts +48 -0
  3. package/dist/angular/index.d.ts +48 -0
  4. package/dist/angular/index.mjs +3 -3
  5. package/dist/astro/index.cjs +3 -3
  6. package/dist/astro/index.d.mts +20 -5
  7. package/dist/astro/index.d.ts +20 -5
  8. package/dist/astro/index.mjs +3 -3
  9. package/dist/cdn/svg-scroll-draw.dev.global.js +1878 -0
  10. package/dist/cdn/svg-scroll-draw.global.js +3 -3
  11. package/dist/cinematic/index.cjs +2 -2
  12. package/dist/cinematic/index.mjs +2 -2
  13. package/dist/cli/init.js +104 -101
  14. package/dist/devtools/index.cjs +1 -1
  15. package/dist/devtools/index.mjs +1 -1
  16. package/dist/group/index.cjs +3 -3
  17. package/dist/group/index.d.mts +48 -0
  18. package/dist/group/index.d.ts +48 -0
  19. package/dist/group/index.mjs +3 -3
  20. package/dist/horizontal/index.cjs +1 -0
  21. package/dist/horizontal/index.d.mts +108 -0
  22. package/dist/horizontal/index.d.ts +108 -0
  23. package/dist/horizontal/index.mjs +1 -0
  24. package/dist/index.cjs +4 -4
  25. package/dist/index.d.mts +48 -0
  26. package/dist/index.d.ts +48 -0
  27. package/dist/index.mjs +4 -4
  28. package/dist/nuxt/index.cjs +3 -3
  29. package/dist/nuxt/index.d.mts +33 -0
  30. package/dist/nuxt/index.d.ts +33 -0
  31. package/dist/nuxt/index.mjs +3 -3
  32. package/dist/pin/index.cjs +1 -1
  33. package/dist/pin/index.mjs +1 -1
  34. package/dist/progress/index.cjs +1 -0
  35. package/dist/progress/index.d.mts +74 -0
  36. package/dist/progress/index.d.ts +74 -0
  37. package/dist/progress/index.mjs +1 -0
  38. package/dist/react/index.cjs +3 -3
  39. package/dist/react/index.d.mts +33 -0
  40. package/dist/react/index.d.ts +33 -0
  41. package/dist/react/index.mjs +3 -3
  42. package/dist/reveal/index.cjs +1 -0
  43. package/dist/reveal/index.d.mts +80 -0
  44. package/dist/reveal/index.d.ts +80 -0
  45. package/dist/reveal/index.mjs +1 -0
  46. package/dist/snap/index.cjs +1 -1
  47. package/dist/snap/index.d.mts +8 -0
  48. package/dist/snap/index.d.ts +8 -0
  49. package/dist/snap/index.mjs +1 -1
  50. package/dist/solid/index.cjs +3 -3
  51. package/dist/solid/index.d.mts +48 -0
  52. package/dist/solid/index.d.ts +48 -0
  53. package/dist/solid/index.mjs +3 -3
  54. package/dist/svelte/index.cjs +3 -3
  55. package/dist/svelte/index.d.mts +48 -0
  56. package/dist/svelte/index.d.ts +48 -0
  57. package/dist/svelte/index.mjs +3 -3
  58. package/dist/text/index.cjs +1 -1
  59. package/dist/text/index.d.mts +15 -0
  60. package/dist/text/index.d.ts +15 -0
  61. package/dist/text/index.mjs +1 -1
  62. package/dist/timeline/index.cjs +8 -8
  63. package/dist/timeline/index.d.mts +31 -0
  64. package/dist/timeline/index.d.ts +31 -0
  65. package/dist/timeline/index.mjs +8 -8
  66. package/dist/video/index.cjs +1 -1
  67. package/dist/video/index.d.mts +15 -0
  68. package/dist/video/index.d.ts +15 -0
  69. package/dist/video/index.mjs +1 -1
  70. package/dist/vue/index.cjs +3 -3
  71. package/dist/vue/index.d.mts +33 -0
  72. package/dist/vue/index.d.ts +33 -0
  73. package/dist/vue/index.mjs +3 -3
  74. package/dist/web-component/index.cjs +3 -3
  75. package/dist/web-component/index.mjs +3 -3
  76. package/package.json +37 -5
@@ -0,0 +1,1878 @@
1
+ "use strict";
2
+ var SvgScrollDraw = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/cdn.ts
22
+ var cdn_exports = {};
23
+ __export(cdn_exports, {
24
+ Cinematic: () => Cinematic,
25
+ PRESETS: () => PRESETS,
26
+ createBounce: () => createBounce,
27
+ createElastic: () => createElastic,
28
+ createSpring: () => createSpring,
29
+ scrollAnimate: () => scrollAnimate,
30
+ scrollCounter: () => scrollCounter,
31
+ scrollDraw: () => scrollDraw,
32
+ scrollParallax: () => scrollParallax
33
+ });
34
+
35
+ // src/core/utils.ts
36
+ function createSpring({
37
+ tension = 2.5,
38
+ friction = 2.2
39
+ } = {}) {
40
+ return (t) => 1 - Math.cos(t * Math.PI * tension) * Math.pow(1 - t, friction);
41
+ }
42
+ function createBounce({
43
+ bounces = 3,
44
+ decay = 0.5
45
+ } = {}) {
46
+ const n = Math.max(1, Math.round(bounces));
47
+ const d = Math.max(0.01, Math.min(0.99, decay));
48
+ const sqrtD = Math.sqrt(d);
49
+ let totalW = 0;
50
+ const rawWidths = [];
51
+ for (let i = 0; i < n; i++) {
52
+ const w = Math.pow(sqrtD, i);
53
+ rawWidths.push(w);
54
+ totalW += w;
55
+ }
56
+ const boundaries = [0];
57
+ let cum = 0;
58
+ for (let i = 0; i < n; i++) {
59
+ cum += rawWidths[i] / totalW;
60
+ boundaries.push(cum);
61
+ }
62
+ return (t) => {
63
+ if (t <= 0) return 0;
64
+ if (t >= 1) return 1;
65
+ for (let i = 0; i < n; i++) {
66
+ if (t <= boundaries[i + 1]) {
67
+ const u = (t - boundaries[i]) / (boundaries[i + 1] - boundaries[i]);
68
+ if (i === 0) {
69
+ return u * (2 - u);
70
+ }
71
+ const trough = 1 - Math.pow(d, i);
72
+ return trough + (1 - trough) * (2 * u - 1) * (2 * u - 1);
73
+ }
74
+ }
75
+ return 1;
76
+ };
77
+ }
78
+ function createElastic({
79
+ amplitude = 1,
80
+ period = 0.4
81
+ } = {}) {
82
+ const a = Math.max(1, amplitude);
83
+ const p = Math.max(0.1, period);
84
+ const s = a <= 1 ? p / 4 : p / (2 * Math.PI) * Math.asin(1 / a);
85
+ return (t) => {
86
+ if (t <= 0) return 0;
87
+ if (t >= 1) return 1;
88
+ return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
89
+ };
90
+ }
91
+ var EASINGS = {
92
+ linear: (t) => t,
93
+ "ease-in": (t) => t * t,
94
+ "ease-out": (t) => t * (2 - t),
95
+ "ease-in-out": (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
96
+ spring: (t) => 1 - Math.cos(t * Math.PI * 2.5) * Math.pow(1 - t, 2.2),
97
+ bounce: createBounce(),
98
+ elastic: createElastic()
99
+ };
100
+ function parseTrigger(str = "top bottom") {
101
+ const trimmed = str.trim();
102
+ if (/^\d+(\.\d+)?%$/.test(trimmed)) return { element: "top", viewport: trimmed };
103
+ const [element = "top", viewport = "bottom"] = trimmed.split(/\s+/).filter(Boolean);
104
+ return { element, viewport };
105
+ }
106
+ function elementAnchorY(top, height, scrollY, anchor) {
107
+ switch (anchor) {
108
+ case "top":
109
+ return top + scrollY;
110
+ case "center":
111
+ return top + scrollY + height / 2;
112
+ case "bottom":
113
+ return top + scrollY + height;
114
+ default:
115
+ return top + scrollY;
116
+ }
117
+ }
118
+ function viewportAnchorY(anchor, vpHeight) {
119
+ if (/^\d+(\.\d+)?%$/.test(anchor)) return vpHeight * (parseFloat(anchor) / 100);
120
+ switch (anchor) {
121
+ case "top":
122
+ return 0;
123
+ case "center":
124
+ return vpHeight / 2;
125
+ case "bottom":
126
+ return vpHeight;
127
+ default:
128
+ return vpHeight;
129
+ }
130
+ }
131
+ function getElementLength(el) {
132
+ const tag = el.tagName.toLowerCase();
133
+ if (tag === "rect") {
134
+ const w = parseFloat(el.getAttribute("width") ?? "0");
135
+ const h = parseFloat(el.getAttribute("height") ?? "0");
136
+ return 2 * (w + h);
137
+ }
138
+ if (tag === "circle") {
139
+ const r = parseFloat(el.getAttribute("r") ?? "0");
140
+ return 2 * Math.PI * r;
141
+ }
142
+ return el.getTotalLength();
143
+ }
144
+ function clamp(value, min, max) {
145
+ return Math.min(max, Math.max(min, value));
146
+ }
147
+ function computeProgress(scrollY, tStart, tEnd, speed) {
148
+ if (tEnd === tStart) return 0;
149
+ return clamp((scrollY - tStart) / (tEnd - tStart) * speed, 0, 1);
150
+ }
151
+ function measureTriggerFrame(el, scrollEl, axis) {
152
+ const rect = el.getBoundingClientRect();
153
+ const size = axis === "x" ? rect.width : rect.height;
154
+ if (scrollEl) {
155
+ const top = axis === "x" ? rect.left - scrollEl.getBoundingClientRect().left + scrollEl.scrollLeft : rect.top - scrollEl.getBoundingClientRect().top + scrollEl.scrollTop;
156
+ return { top, height: size, scroll: 0 };
157
+ }
158
+ return {
159
+ top: axis === "x" ? rect.left : rect.top,
160
+ height: size,
161
+ scroll: axis === "x" ? window.scrollX : window.scrollY
162
+ };
163
+ }
164
+ function computeTriggers(rect, scrollY, vpHeight, startConfig, endConfig) {
165
+ const tStart = elementAnchorY(rect.top, rect.height, scrollY, startConfig.element) - viewportAnchorY(startConfig.viewport, vpHeight);
166
+ const tEnd = elementAnchorY(rect.top, rect.height, scrollY, endConfig.element) - viewportAnchorY(endConfig.viewport, vpHeight);
167
+ return { tStart, tEnd };
168
+ }
169
+ function parseColor(color) {
170
+ const short = /^#([a-f\d])([a-f\d])([a-f\d])$/i.exec(color);
171
+ if (short) return [
172
+ parseInt(short[1] + short[1], 16),
173
+ parseInt(short[2] + short[2], 16),
174
+ parseInt(short[3] + short[3], 16)
175
+ ];
176
+ const full = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
177
+ if (full) return [parseInt(full[1], 16), parseInt(full[2], 16), parseInt(full[3], 16)];
178
+ const rgb = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i.exec(color);
179
+ if (rgb) return [parseInt(rgb[1]), parseInt(rgb[2]), parseInt(rgb[3])];
180
+ return null;
181
+ }
182
+ function lerpColor(from, to, t) {
183
+ const a = parseColor(from);
184
+ const b = parseColor(to);
185
+ if (!a || !b) return from;
186
+ return `rgb(${Math.round(a[0] + (b[0] - a[0]) * t)},${Math.round(a[1] + (b[1] - a[1]) * t)},${Math.round(a[2] + (b[2] - a[2]) * t)})`;
187
+ }
188
+
189
+ // src/core/presets.ts
190
+ var PRESETS = {
191
+ /**
192
+ * Pencil-drawing feel — paths trace in one by one with a slight stagger.
193
+ * Great for diagrams, illustrations, and handwriting effects.
194
+ */
195
+ sketch: {
196
+ easing: "ease-in",
197
+ stagger: 0.1,
198
+ speed: 0.9,
199
+ fade: false
200
+ },
201
+ /**
202
+ * Clean viewport reveal — fades and draws in once as the element enters view.
203
+ * Best for logos, hero graphics, and section illustrations.
204
+ */
205
+ reveal: {
206
+ easing: "ease-out",
207
+ fade: true,
208
+ speed: 1.2,
209
+ once: true
210
+ },
211
+ /**
212
+ * Fast mechanical draw — paths appear quickly left-to-right, one after another.
213
+ * Great for code, timelines, and data-driven infographics.
214
+ */
215
+ typewriter: {
216
+ easing: "linear",
217
+ stagger: 0.05,
218
+ speed: 1.5
219
+ },
220
+ /**
221
+ * Slow dramatic entrance — long ease-in-out with a gentle fade.
222
+ * Suits hero sections, landing pages, and storytelling layouts.
223
+ */
224
+ cinematic: {
225
+ easing: "ease-in-out",
226
+ fade: true,
227
+ speed: 0.75
228
+ },
229
+ /**
230
+ * Bouncy physics feel — spring easing gives a natural overshoot-and-settle.
231
+ * Pairs well with icons and small decorative SVGs.
232
+ */
233
+ spring: {
234
+ easing: "spring",
235
+ speed: 1.1
236
+ }
237
+ };
238
+
239
+ // src/core/env.ts
240
+ var IS_DEV = (
241
+ /*
242
+ * Build-time override, substituted as a literal by the two CDN builds.
243
+ *
244
+ * The `process` test below is unanswerable without a bundler — `process` does
245
+ * not exist in a browser — so `IS_DEV` was false for every CDN user, the people
246
+ * least likely to have any other diagnostics available. Every warning this
247
+ * library has was invisible to exactly them, including the zero-length trigger
248
+ * window that would have caught the `scrollHorizontal` defect.
249
+ *
250
+ * `svg-scroll-draw.dev.global.js` defines this to `true`; the production CDN
251
+ * build defines it to `false`, which also lets the minifier drop the warning
252
+ * bodies outright. Left undefined for the npm builds, where the `process` test
253
+ * is the right one and bundlers fold it away.
254
+ */
255
+ true ? true : typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.NODE_ENV !== "production"
256
+ );
257
+ function warn(msg, ...rest) {
258
+ if (IS_DEV) console.warn(`[svg-scroll-draw] ${msg}`, ...rest);
259
+ }
260
+
261
+ // src/core/css-easing.ts
262
+ var QUAD_IN = "cubic-bezier(0.333333, 0, 0.666667, 0.333333)";
263
+ var QUAD_OUT = "cubic-bezier(0.333333, 0.666667, 0.666667, 1)";
264
+ var LINEAR_STOPS = 33;
265
+ function supportsLinearTiming() {
266
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("animation-timing-function: linear(0, 1)");
267
+ }
268
+ function sampleAsLinear(fn) {
269
+ const stops = [];
270
+ for (let i = 0; i < LINEAR_STOPS; i++) {
271
+ const t = i / (LINEAR_STOPS - 1);
272
+ stops.push(String(Math.round(fn(t) * 1e5) / 1e5));
273
+ }
274
+ return `linear(${stops.join(",")})`;
275
+ }
276
+ function cssTimingFor(easing) {
277
+ switch (easing) {
278
+ case "linear":
279
+ return "linear";
280
+ case "ease-in":
281
+ return QUAD_IN;
282
+ case "ease-out":
283
+ return QUAD_OUT;
284
+ case "ease-in-out":
285
+ return supportsLinearTiming() ? sampleAsLinear(EASINGS["ease-in-out"]) : null;
286
+ default:
287
+ return null;
288
+ }
289
+ }
290
+
291
+ // src/core/engine.ts
292
+ var nativeUid = 0;
293
+ function supportsNativeTimeline() {
294
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("animation-timeline: view()") && // The fast path anchors the timeline to the container via a named
295
+ // view-timeline, so named-timeline support is also required — without it the
296
+ // animation-timeline reference would not resolve and nothing would animate.
297
+ CSS.supports("view-timeline-name: --x");
298
+ }
299
+ function checkElement(el) {
300
+ const stroke = el.getAttribute("stroke");
301
+ const fill = el.getAttribute("fill");
302
+ if (!stroke || stroke === "none") warn("Element has no stroke \u2014 path will not be visible.", el);
303
+ else if (fill && fill !== "none" && fill !== "transparent") warn("Element has a fill \u2014 it may obscure the stroke animation.", el);
304
+ }
305
+ function createDebugOverlay(tStart, tEnd, axis) {
306
+ const overlay = document.createElement("div");
307
+ overlay.setAttribute("data-svg-scroll-draw-debug", "");
308
+ overlay.style.cssText = "position:fixed;pointer-events:none;z-index:9999;font-family:monospace;font-size:11px;top:0;left:0;right:0;bottom:0;";
309
+ function update() {
310
+ const scroll = axis === "x" ? window.scrollX : window.scrollY;
311
+ const a = tStart - scroll, b = tEnd - scroll, x = axis === "x";
312
+ overlay.innerHTML = `
313
+ <div style="position:absolute;${x ? `left:${a}px;top:0;bottom:0;border-left:2px dashed #22c55e;` : `top:${a}px;left:0;right:0;border-top:2px dashed #22c55e;`}padding:2px 6px;color:#22c55e;background:rgba(0,0,0,.6)">\u25B6 start</div>
314
+ <div style="position:absolute;${x ? `left:${b}px;top:0;bottom:0;border-left:2px dashed #ef4444;` : `top:${b}px;left:0;right:0;border-top:2px dashed #ef4444;`}padding:2px 6px;color:#ef4444;background:rgba(0,0,0,.6)">\u25A0 end</div>`;
315
+ }
316
+ document.body.appendChild(overlay);
317
+ window.addEventListener("scroll", update, { passive: true });
318
+ update();
319
+ return {
320
+ el: overlay,
321
+ destroy() {
322
+ window.removeEventListener("scroll", update);
323
+ overlay.remove();
324
+ }
325
+ };
326
+ }
327
+ var PATH_NUMBER = /[-+]?(?:\d*\.)?\d+(?:[eE][-+]?\d+)?/g;
328
+ var PATH_COMMAND = /[MmLlHhVvCcSsQqTtAaZz]/g;
329
+ function checkMorphCompatible(from, to, el) {
330
+ if (!IS_DEV) return;
331
+ const fromCmds = (from.match(PATH_COMMAND) ?? []).join("").toUpperCase();
332
+ const toCmds = (to.match(PATH_COMMAND) ?? []).join("").toUpperCase();
333
+ const fromNums = (from.match(PATH_NUMBER) ?? []).length;
334
+ const toNums = (to.match(PATH_NUMBER) ?? []).length;
335
+ if (fromCmds !== toCmds) {
336
+ warn(
337
+ `morphTo: command sequence differs (${fromCmds || "\u2205"} \u2192 ${toCmds || "\u2205"}). Interpolation is coordinate-for-coordinate, so the result will not reach the target shape. Redraw both paths with the same commands.`,
338
+ el
339
+ );
340
+ return;
341
+ }
342
+ if (fromNums !== toNums) {
343
+ warn(
344
+ `morphTo: coordinate count differs (${fromNums} \u2192 ${toNums}). Extra coordinates are ignored and missing ones hold their start value, so the result will not reach the target shape.`,
345
+ el
346
+ );
347
+ }
348
+ }
349
+ function morphPath(from, to, t) {
350
+ const toNums = (to.match(/[-+]?(?:\d*\.)?\d+(?:[eE][-+]?\d+)?/g) ?? []).map(Number);
351
+ let idx = 0;
352
+ return from.replace(/[-+]?(?:\d*\.)?\d+(?:[eE][-+]?\d+)?/g, (match) => {
353
+ const f = parseFloat(match);
354
+ const target = toNums[idx++] ?? f;
355
+ return String(+(f + (target - f) * t).toFixed(4));
356
+ });
357
+ }
358
+ function createEngine(container, rawOptions = {}) {
359
+ if (typeof window === "undefined") {
360
+ return { destroy: () => {
361
+ }, replay: () => {
362
+ }, pause: () => {
363
+ }, resume: () => {
364
+ }, seek: () => {
365
+ }, getProgress: () => 0 };
366
+ }
367
+ const { preset, ...userOptions } = rawOptions;
368
+ const options = preset ? { ...PRESETS[preset], ...userOptions } : userOptions;
369
+ const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
370
+ const {
371
+ selector = "path, polyline, line, polygon, rect, circle",
372
+ speed = 1,
373
+ fade = false,
374
+ easing = "linear",
375
+ trigger = {},
376
+ stagger = 0,
377
+ direction = "forward",
378
+ once = false,
379
+ debug = false,
380
+ axis = "y",
381
+ scrollContainer,
382
+ autoReverse = false,
383
+ delay = 0,
384
+ strokeColor,
385
+ strokeWidth,
386
+ fillOpacity,
387
+ waypoints,
388
+ velocityScale = false,
389
+ threshold = 0,
390
+ rootMargin = "0px",
391
+ repeat = 0,
392
+ repeatDelay = 0,
393
+ morphTo,
394
+ clip,
395
+ autoplay = false,
396
+ duration = 1e3,
397
+ native = true,
398
+ onProgress,
399
+ onStart,
400
+ onComplete,
401
+ onEnter,
402
+ onLeave,
403
+ onEnterBack,
404
+ onLeaveBack
405
+ } = options;
406
+ const clipDirection = clip === true ? "left" : typeof clip === "string" ? clip : false;
407
+ const easeFn = typeof easing === "function" ? easing : EASINGS[easing] ?? EASINGS.linear;
408
+ const startConfig = parseTrigger(trigger.start ?? "top bottom");
409
+ const endConfig = parseTrigger(trigger.end ?? "bottom top");
410
+ const scrollEl = typeof scrollContainer === "string" ? document.querySelector(scrollContainer) : scrollContainer ?? null;
411
+ const colorFrom = Array.isArray(strokeColor) ? strokeColor[0] : null;
412
+ const colorTo = Array.isArray(strokeColor) ? strokeColor[1] : typeof strokeColor === "string" ? strokeColor : null;
413
+ const widthFrom = Array.isArray(strokeWidth) ? strokeWidth[0] : null;
414
+ const widthTo = Array.isArray(strokeWidth) ? strokeWidth[1] : typeof strokeWidth === "number" ? strokeWidth : null;
415
+ const fillOpacityFrom = Array.isArray(fillOpacity) ? fillOpacity[0] : null;
416
+ const fillOpacityTo = Array.isArray(fillOpacity) ? fillOpacity[1] : typeof fillOpacity === "number" ? fillOpacity : null;
417
+ function computeClipPath(alpha) {
418
+ const p = alpha * 100;
419
+ switch (clipDirection) {
420
+ case "right":
421
+ return `inset(0 0 0 ${100 - p}%)`;
422
+ case "top":
423
+ return `inset(0 0 ${100 - p}% 0)`;
424
+ case "bottom":
425
+ return `inset(${100 - p}% 0 0 0)`;
426
+ case "center":
427
+ return `circle(${alpha * 150}% at 50% 50%)`;
428
+ default:
429
+ return `inset(0 ${100 - p}% 0 0)`;
430
+ }
431
+ }
432
+ const paths = clipDirection ? [] : Array.from(container.querySelectorAll(selector));
433
+ const lengths = [];
434
+ const originalDs = [];
435
+ let tStart = 0;
436
+ let tEnd = 0;
437
+ let completed = false;
438
+ let started = false;
439
+ let rafId = 0;
440
+ let isVisible = false;
441
+ let frozenAlpha = -1;
442
+ let prevScroll = -1;
443
+ let paused = false;
444
+ let currentAlpha = 0;
445
+ let repeatCount = 0;
446
+ let repeatTimer;
447
+ let debugOverlay = null;
448
+ function destroyDebugOverlay() {
449
+ debugOverlay?.destroy();
450
+ debugOverlay = null;
451
+ }
452
+ const firedWaypoints = /* @__PURE__ */ new Set();
453
+ let lastAppliedScroll = NaN;
454
+ let dirty = true;
455
+ let prevVelScroll = -1;
456
+ let prevVelTime = performance.now();
457
+ let prevRawProgress = NaN;
458
+ function scrollPos() {
459
+ if (scrollEl) return axis === "x" ? scrollEl.scrollLeft : scrollEl.scrollTop;
460
+ return axis === "x" ? window.scrollX : window.scrollY;
461
+ }
462
+ function vpSize() {
463
+ if (scrollEl) return axis === "x" ? scrollEl.clientWidth : scrollEl.clientHeight;
464
+ return axis === "x" ? window.innerWidth : window.innerHeight;
465
+ }
466
+ function cacheTriggers() {
467
+ const frame = measureTriggerFrame(container, scrollEl, axis);
468
+ const result = computeTriggers(frame, frame.scroll, vpSize(), startConfig, endConfig);
469
+ tStart = result.tStart;
470
+ tEnd = result.tEnd;
471
+ if (debug && IS_DEV) {
472
+ destroyDebugOverlay();
473
+ debugOverlay = createDebugOverlay(tStart, tEnd, axis);
474
+ }
475
+ }
476
+ function applyAlpha(alpha, dir) {
477
+ container.style.setProperty("--scroll-draw-progress", String(alpha));
478
+ if (clipDirection) {
479
+ const a = dir === "reverse" ? 1 - alpha : alpha;
480
+ container.style.clipPath = computeClipPath(a);
481
+ return;
482
+ }
483
+ paths.forEach((el, i) => {
484
+ el.style.strokeDashoffset = dir === "reverse" ? `${lengths[i] * alpha}` : `${lengths[i] * (1 - alpha)}`;
485
+ if (fade) el.style.opacity = dir === "reverse" ? `${1 - alpha}` : `${alpha}`;
486
+ if (colorFrom && colorTo) el.style.stroke = lerpColor(colorFrom, colorTo, alpha);
487
+ else if (colorTo) el.style.stroke = colorTo;
488
+ if (widthFrom !== null && widthTo !== null)
489
+ el.style.strokeWidth = `${widthFrom + (widthTo - widthFrom) * alpha}`;
490
+ else if (widthTo !== null)
491
+ el.style.strokeWidth = `${widthTo}`;
492
+ if (fillOpacityFrom !== null && fillOpacityTo !== null)
493
+ el.style.fillOpacity = `${fillOpacityFrom + (fillOpacityTo - fillOpacityFrom) * alpha}`;
494
+ else if (fillOpacityTo !== null)
495
+ el.style.fillOpacity = `${fillOpacityTo}`;
496
+ if (morphTo && el.tagName.toLowerCase() === "path" && originalDs[i]) {
497
+ el.setAttribute("d", morphPath(originalDs[i], morphTo, alpha));
498
+ }
499
+ });
500
+ }
501
+ function resetPaths() {
502
+ container.style.setProperty("--scroll-draw-progress", "0");
503
+ if (clipDirection) {
504
+ container.style.clipPath = computeClipPath(0);
505
+ return;
506
+ }
507
+ paths.forEach((el, i) => {
508
+ el.style.strokeDasharray = `${lengths[i]}`;
509
+ el.style.strokeDashoffset = direction === "reverse" ? "0" : `${lengths[i]}`;
510
+ if (fade) el.style.opacity = direction === "reverse" ? "1" : "0";
511
+ else el.style.opacity = "";
512
+ if (colorFrom) el.style.stroke = colorFrom;
513
+ if (widthFrom !== null) el.style.strokeWidth = `${widthFrom}`;
514
+ if (fillOpacityFrom !== null) el.style.fillOpacity = `${fillOpacityFrom}`;
515
+ if (morphTo && el.tagName.toLowerCase() === "path" && originalDs[i])
516
+ el.setAttribute("d", originalDs[i]);
517
+ });
518
+ }
519
+ paths.forEach((el) => {
520
+ checkElement(el);
521
+ const len = getElementLength(el);
522
+ lengths.push(len);
523
+ const isPath = el.tagName.toLowerCase() === "path";
524
+ if (isPath) originalDs.push(el.getAttribute("d") ?? "");
525
+ else originalDs.push("");
526
+ if (morphTo) {
527
+ if (!isPath) {
528
+ warn(`morphTo only applies to <path> elements \u2014 ignored on <${el.tagName}>.`, el);
529
+ } else {
530
+ checkMorphCompatible(originalDs[originalDs.length - 1], morphTo, el);
531
+ }
532
+ }
533
+ if (prefersReduced) {
534
+ el.style.strokeDasharray = `${len}`;
535
+ el.style.strokeDashoffset = direction === "reverse" ? `${len}` : "0";
536
+ if (fade) el.style.opacity = "1";
537
+ if (colorTo) el.style.stroke = colorTo;
538
+ if (widthTo !== null) el.style.strokeWidth = `${widthTo}`;
539
+ if (fillOpacityTo !== null) el.style.fillOpacity = `${fillOpacityTo}`;
540
+ if (morphTo && el.tagName.toLowerCase() === "path") el.setAttribute("d", morphTo);
541
+ } else {
542
+ el.style.strokeDasharray = `${len}`;
543
+ el.style.strokeDashoffset = direction === "reverse" ? "0" : `${len}`;
544
+ if (fade) el.style.opacity = direction === "reverse" ? "1" : "0";
545
+ else el.style.opacity = "";
546
+ if (colorFrom) el.style.stroke = colorFrom;
547
+ if (widthFrom !== null) el.style.strokeWidth = `${widthFrom}`;
548
+ if (fillOpacityFrom !== null) el.style.fillOpacity = `${fillOpacityFrom}`;
549
+ }
550
+ });
551
+ if (clipDirection) {
552
+ if (prefersReduced) {
553
+ container.style.clipPath = computeClipPath(1);
554
+ onComplete?.();
555
+ return { destroy: () => {
556
+ }, replay: () => {
557
+ }, pause: () => {
558
+ }, resume: () => {
559
+ }, seek: () => {
560
+ }, getProgress: () => 1 };
561
+ }
562
+ container.style.clipPath = computeClipPath(0);
563
+ } else if (prefersReduced) {
564
+ onComplete?.();
565
+ return { destroy: () => {
566
+ }, replay: () => {
567
+ }, pause: () => {
568
+ }, resume: () => {
569
+ }, seek: () => {
570
+ }, getProgress: () => 1 };
571
+ }
572
+ const cssTiming = typeof easing === "string" ? cssTimingFor(easing) : null;
573
+ function nativeEligible() {
574
+ if (native === false) return false;
575
+ if (!supportsNativeTimeline()) return false;
576
+ if (!paths.length) return false;
577
+ if (!cssTiming) return false;
578
+ if (clipDirection) return false;
579
+ if (axis !== "y") return false;
580
+ if (scrollEl) return false;
581
+ if (speed !== 1) return false;
582
+ if (stagger !== 0) return false;
583
+ if (once) return false;
584
+ if (autoReverse) return false;
585
+ if (velocityScale !== false) return false;
586
+ if (morphTo) return false;
587
+ if (waypoints) return false;
588
+ if (repeat) return false;
589
+ if (delay > 0) return false;
590
+ if (onProgress || onStart || onComplete || onEnter || onLeave || onEnterBack || onLeaveBack) return false;
591
+ if (strokeColor != null || strokeWidth != null || fillOpacity != null) return false;
592
+ if ((trigger.start ?? "top bottom").trim() !== "top bottom") return false;
593
+ if ((trigger.end ?? "bottom top").trim() !== "bottom top") return false;
594
+ return true;
595
+ }
596
+ function buildNative() {
597
+ const uid = ++nativeUid;
598
+ const cls = `svg-scroll-draw-${uid}`;
599
+ const containerCls = `svg-scroll-draw-host-${uid}`;
600
+ const timeline = `--svg-scroll-draw-${uid}`;
601
+ const fromO = direction === "reverse" ? "0" : "var(--ssd-len)";
602
+ const toO = direction === "reverse" ? "var(--ssd-len)" : "0";
603
+ let fromBody = `stroke-dashoffset:${fromO};`;
604
+ let toBody = `stroke-dashoffset:${toO};`;
605
+ if (fade) {
606
+ fromBody += `opacity:${direction === "reverse" ? 1 : 0};`;
607
+ toBody += `opacity:${direction === "reverse" ? 0 : 1};`;
608
+ }
609
+ const style = document.createElement("style");
610
+ style.setAttribute("data-svg-scroll-draw", "");
611
+ style.textContent = `@keyframes ${cls}{from{${fromBody}}to{${toBody}}}.${containerCls}{view-timeline-name:${timeline};view-timeline-axis:block;}.${cls}{animation-name:${cls};animation-duration:auto;animation-timing-function:${cssTiming};animation-fill-mode:both;animation-timeline:${timeline};animation-range:cover 0% cover 100%;}`;
612
+ document.head.appendChild(style);
613
+ container.classList.add(containerCls);
614
+ function attach(el, i) {
615
+ el.style.setProperty("--ssd-len", String(lengths[i]));
616
+ el.style.strokeDasharray = `${lengths[i]}`;
617
+ el.style.strokeDashoffset = "";
618
+ el.style.opacity = "";
619
+ el.style.animationPlayState = "";
620
+ el.classList.add(cls);
621
+ }
622
+ paths.forEach(attach);
623
+ let paused2 = false;
624
+ let lastSeek = -1;
625
+ function liveProgress() {
626
+ if (lastSeek >= 0) return lastSeek;
627
+ const rect = container.getBoundingClientRect();
628
+ const { tStart: tStart2, tEnd: tEnd2 } = computeTriggers(
629
+ { top: rect.top, height: rect.height },
630
+ scrollPos(),
631
+ vpSize(),
632
+ startConfig,
633
+ endConfig
634
+ );
635
+ return easeFn(computeProgress(scrollPos(), tStart2, tEnd2, speed));
636
+ }
637
+ return {
638
+ destroy() {
639
+ container.classList.remove(containerCls);
640
+ paths.forEach((el) => {
641
+ el.classList.remove(cls);
642
+ el.style.removeProperty("--ssd-len");
643
+ el.style.animationPlayState = "";
644
+ });
645
+ style.remove();
646
+ },
647
+ replay() {
648
+ paused2 = false;
649
+ lastSeek = -1;
650
+ paths.forEach(attach);
651
+ },
652
+ pause() {
653
+ paused2 = true;
654
+ paths.forEach((el) => {
655
+ el.style.animationPlayState = "paused";
656
+ });
657
+ },
658
+ resume() {
659
+ if (!paused2) return;
660
+ paused2 = false;
661
+ paths.forEach((el) => {
662
+ el.style.animationPlayState = "running";
663
+ });
664
+ },
665
+ seek(progress) {
666
+ const p = Math.min(1, Math.max(0, progress));
667
+ lastSeek = p;
668
+ paused2 = true;
669
+ paths.forEach((el, i) => {
670
+ el.classList.remove(cls);
671
+ el.style.strokeDashoffset = direction === "reverse" ? `${lengths[i] * p}` : `${lengths[i] * (1 - p)}`;
672
+ if (fade) el.style.opacity = direction === "reverse" ? `${1 - p}` : `${p}`;
673
+ });
674
+ },
675
+ getProgress() {
676
+ return liveProgress();
677
+ },
678
+ /**
679
+ * Re-measure the path lengths.
680
+ *
681
+ * There is no trigger window to refresh on this path — the browser's own
682
+ * view-timeline is live and never goes stale — but `--ssd-len` is a number
683
+ * baked in at attach time, and the keyframes interpolate from it. A path
684
+ * that changed length keeps animating to the old one until this is called.
685
+ */
686
+ refresh() {
687
+ paths.forEach((el, i) => {
688
+ lengths[i] = getElementLength(el);
689
+ el.style.setProperty("--ssd-len", String(lengths[i]));
690
+ el.style.strokeDasharray = `${lengths[i]}`;
691
+ });
692
+ }
693
+ };
694
+ }
695
+ if (nativeEligible()) return buildNative();
696
+ function buildAutoplay() {
697
+ const effectiveDuration = Math.max(1, duration);
698
+ let startTime = 0;
699
+ let pausedElapsed = 0;
700
+ let running = false;
701
+ function applyAutoAlpha(elapsed) {
702
+ let allDone = true;
703
+ if (clipDirection) {
704
+ const raw = Math.min(1, Math.max(0, elapsed / effectiveDuration));
705
+ const alpha = easeFn(raw);
706
+ currentAlpha = alpha;
707
+ container.style.setProperty("--scroll-draw-progress", String(alpha));
708
+ container.style.clipPath = computeClipPath(direction === "reverse" ? 1 - alpha : alpha);
709
+ onProgress?.(alpha);
710
+ if (raw < 1) allDone = false;
711
+ } else {
712
+ paths.forEach((el, i) => {
713
+ const staggerMs = i * stagger * effectiveDuration;
714
+ const raw = Math.min(1, Math.max(0, (elapsed - staggerMs) / effectiveDuration));
715
+ const alpha = easeFn(raw);
716
+ el.style.strokeDashoffset = direction === "reverse" ? `${lengths[i] * alpha}` : `${lengths[i] * (1 - alpha)}`;
717
+ if (fade) el.style.opacity = direction === "reverse" ? `${1 - alpha}` : `${alpha}`;
718
+ if (colorFrom && colorTo) el.style.stroke = lerpColor(colorFrom, colorTo, alpha);
719
+ else if (colorTo) el.style.stroke = colorTo;
720
+ if (widthFrom !== null && widthTo !== null)
721
+ el.style.strokeWidth = `${widthFrom + (widthTo - widthFrom) * alpha}`;
722
+ else if (widthTo !== null)
723
+ el.style.strokeWidth = `${widthTo}`;
724
+ if (fillOpacityFrom !== null && fillOpacityTo !== null)
725
+ el.style.fillOpacity = `${fillOpacityFrom + (fillOpacityTo - fillOpacityFrom) * alpha}`;
726
+ else if (fillOpacityTo !== null)
727
+ el.style.fillOpacity = `${fillOpacityTo}`;
728
+ if (morphTo && el.tagName.toLowerCase() === "path" && originalDs[i])
729
+ el.setAttribute("d", morphPath(originalDs[i], morphTo, alpha));
730
+ if (i === 0) {
731
+ currentAlpha = alpha;
732
+ onProgress?.(alpha);
733
+ container.style.setProperty("--scroll-draw-progress", String(alpha));
734
+ }
735
+ if (raw < 1) allDone = false;
736
+ });
737
+ }
738
+ if (waypoints) {
739
+ const globalRaw = Math.min(1, Math.max(0, elapsed / effectiveDuration));
740
+ const globalAlpha = easeFn(globalRaw);
741
+ for (const key in waypoints) {
742
+ const t = parseFloat(key);
743
+ if (globalAlpha >= t && !firedWaypoints.has(t)) {
744
+ firedWaypoints.add(t);
745
+ waypoints[key]?.();
746
+ }
747
+ }
748
+ }
749
+ return allDone;
750
+ }
751
+ function tick(now) {
752
+ if (paused) return;
753
+ const elapsed = now - startTime;
754
+ if (!started) {
755
+ started = true;
756
+ onStart?.();
757
+ }
758
+ const done = applyAutoAlpha(elapsed);
759
+ if (done && !completed) {
760
+ completed = true;
761
+ applyAutoAlpha(effectiveDuration * (1 + Math.max(0, paths.length - 1) * stagger));
762
+ onComplete?.();
763
+ const maxRepeats = repeat === "infinite" ? Infinity : repeat ?? 0;
764
+ if (repeatCount < maxRepeats) {
765
+ repeatCount++;
766
+ repeatTimer = setTimeout(() => {
767
+ startTime = performance.now();
768
+ started = false;
769
+ completed = false;
770
+ firedWaypoints.clear();
771
+ resetPaths();
772
+ rafId = requestAnimationFrame(tick);
773
+ }, repeatDelay);
774
+ }
775
+ return;
776
+ }
777
+ if (!done) rafId = requestAnimationFrame(tick);
778
+ }
779
+ function startAnimation() {
780
+ cancelAnimationFrame(rafId);
781
+ clearTimeout(repeatTimer);
782
+ startTime = performance.now();
783
+ pausedElapsed = 0;
784
+ paused = false;
785
+ started = false;
786
+ completed = false;
787
+ repeatCount = 0;
788
+ running = true;
789
+ currentAlpha = 0;
790
+ firedWaypoints.clear();
791
+ resetPaths();
792
+ rafId = requestAnimationFrame(tick);
793
+ }
794
+ const observer2 = new IntersectionObserver(
795
+ (entries) => {
796
+ entries.forEach((e) => {
797
+ if (e.isIntersecting && !(once && completed)) {
798
+ startAnimation();
799
+ } else if (!e.isIntersecting && !once && !completed) {
800
+ cancelAnimationFrame(rafId);
801
+ clearTimeout(repeatTimer);
802
+ running = false;
803
+ }
804
+ });
805
+ },
806
+ { root: scrollEl ?? null, threshold, rootMargin }
807
+ );
808
+ let resizeTimer2;
809
+ function onResize2() {
810
+ clearTimeout(resizeTimer2);
811
+ resizeTimer2 = setTimeout(() => {
812
+ paths.forEach((el, i) => {
813
+ lengths[i] = getElementLength(el);
814
+ el.style.strokeDasharray = `${lengths[i]}`;
815
+ });
816
+ }, 150);
817
+ }
818
+ window.addEventListener("resize", onResize2);
819
+ window.addEventListener("orientationchange", onResize2);
820
+ if (delay > 0) setTimeout(() => observer2.observe(container), delay);
821
+ else observer2.observe(container);
822
+ return {
823
+ destroy() {
824
+ cancelAnimationFrame(rafId);
825
+ clearTimeout(repeatTimer);
826
+ observer2.disconnect();
827
+ window.removeEventListener("resize", onResize2);
828
+ window.removeEventListener("orientationchange", onResize2);
829
+ clearTimeout(resizeTimer2);
830
+ },
831
+ replay() {
832
+ repeatCount = 0;
833
+ startAnimation();
834
+ },
835
+ pause() {
836
+ if (paused || !running) return;
837
+ paused = true;
838
+ pausedElapsed = performance.now() - startTime;
839
+ cancelAnimationFrame(rafId);
840
+ },
841
+ resume() {
842
+ if (!paused) return;
843
+ paused = false;
844
+ running = true;
845
+ startTime = performance.now() - pausedElapsed;
846
+ rafId = requestAnimationFrame(tick);
847
+ },
848
+ seek(p) {
849
+ const clamped = Math.min(1, Math.max(0, p));
850
+ currentAlpha = clamped;
851
+ paused = true;
852
+ pausedElapsed = clamped * effectiveDuration;
853
+ startTime = performance.now() - pausedElapsed;
854
+ cancelAnimationFrame(rafId);
855
+ applyAutoAlpha(pausedElapsed);
856
+ },
857
+ getProgress() {
858
+ return currentAlpha;
859
+ },
860
+ /** No trigger window on this path — it runs on time — so lengths only. */
861
+ refresh() {
862
+ clearTimeout(resizeTimer2);
863
+ paths.forEach((el, i) => {
864
+ lengths[i] = getElementLength(el);
865
+ el.style.strokeDasharray = `${lengths[i]}`;
866
+ });
867
+ }
868
+ };
869
+ }
870
+ if (autoplay) return buildAutoplay();
871
+ cacheTriggers();
872
+ function update() {
873
+ if (!isVisible || paused) return;
874
+ const now = performance.now();
875
+ const currentScroll = scrollPos();
876
+ if (!dirty && velocityScale === false && currentScroll === lastAppliedScroll) {
877
+ rafId = requestAnimationFrame(update);
878
+ return;
879
+ }
880
+ dirty = false;
881
+ lastAppliedScroll = currentScroll;
882
+ let effectiveSpeed = speed;
883
+ if (velocityScale !== false) {
884
+ const dt = now - prevVelTime;
885
+ const vel = dt > 0 ? Math.abs(currentScroll - (prevVelScroll < 0 ? currentScroll : prevVelScroll)) / dt : 0;
886
+ const sensitivity = typeof velocityScale === "number" ? velocityScale : 1;
887
+ effectiveSpeed = speed * Math.max(0.2, 1 + vel * sensitivity * 0.04);
888
+ }
889
+ prevVelScroll = currentScroll;
890
+ prevVelTime = now;
891
+ const effectiveDir = autoReverse ? prevScroll === -1 || currentScroll >= prevScroll ? "forward" : "reverse" : direction;
892
+ prevScroll = currentScroll;
893
+ const range = tEnd - tStart;
894
+ let allComplete = true;
895
+ const rawProgress = range === 0 ? 0 : (currentScroll - tStart) / range;
896
+ if (!isNaN(prevRawProgress)) {
897
+ if (prevRawProgress <= 0 && rawProgress > 0) onEnter?.();
898
+ else if (prevRawProgress > 0 && rawProgress <= 0) onLeaveBack?.();
899
+ if (prevRawProgress < 1 && rawProgress >= 1) onLeave?.();
900
+ else if (prevRawProgress >= 1 && rawProgress < 1) onEnterBack?.();
901
+ }
902
+ prevRawProgress = rawProgress;
903
+ if (clipDirection) {
904
+ let alpha = easeFn(computeProgress(currentScroll, tStart, tEnd, effectiveSpeed));
905
+ if (once && !autoReverse) {
906
+ frozenAlpha = Math.max(frozenAlpha, alpha);
907
+ alpha = frozenAlpha;
908
+ }
909
+ currentAlpha = alpha;
910
+ container.style.setProperty("--scroll-draw-progress", String(alpha));
911
+ const visual = effectiveDir === "reverse" ? 1 - alpha : alpha;
912
+ container.style.clipPath = computeClipPath(visual);
913
+ onProgress?.(alpha);
914
+ if (!started && computeProgress(currentScroll, tStart, tEnd, effectiveSpeed) > 0) {
915
+ started = true;
916
+ onStart?.();
917
+ }
918
+ if (alpha >= 1 && !completed) {
919
+ completed = true;
920
+ onComplete?.();
921
+ const maxRepeats = repeat === "infinite" ? Infinity : repeat ?? 0;
922
+ if (repeatCount < maxRepeats) {
923
+ repeatCount++;
924
+ repeatTimer = setTimeout(() => {
925
+ frozenAlpha = -1;
926
+ started = false;
927
+ completed = false;
928
+ container.style.clipPath = computeClipPath(0);
929
+ }, repeatDelay);
930
+ }
931
+ } else if (alpha < 1 && !once) {
932
+ completed = false;
933
+ }
934
+ rafId = requestAnimationFrame(update);
935
+ return;
936
+ }
937
+ paths.forEach((el, i) => {
938
+ const offset = i * stagger * range;
939
+ let alpha = easeFn(computeProgress(currentScroll, tStart + offset, tEnd + offset, effectiveSpeed));
940
+ if (once && !autoReverse) {
941
+ frozenAlpha = Math.max(frozenAlpha, alpha);
942
+ alpha = frozenAlpha;
943
+ }
944
+ currentAlpha = alpha;
945
+ el.style.strokeDashoffset = effectiveDir === "reverse" ? `${lengths[i] * alpha}` : `${lengths[i] * (1 - alpha)}`;
946
+ if (fade) el.style.opacity = effectiveDir === "reverse" ? `${1 - alpha}` : `${alpha}`;
947
+ if (colorFrom && colorTo) el.style.stroke = lerpColor(colorFrom, colorTo, alpha);
948
+ else if (colorTo) el.style.stroke = colorTo;
949
+ if (widthFrom !== null && widthTo !== null)
950
+ el.style.strokeWidth = `${widthFrom + (widthTo - widthFrom) * alpha}`;
951
+ else if (widthTo !== null)
952
+ el.style.strokeWidth = `${widthTo}`;
953
+ if (fillOpacityFrom !== null && fillOpacityTo !== null)
954
+ el.style.fillOpacity = `${fillOpacityFrom + (fillOpacityTo - fillOpacityFrom) * alpha}`;
955
+ else if (fillOpacityTo !== null)
956
+ el.style.fillOpacity = `${fillOpacityTo}`;
957
+ if (morphTo && el.tagName.toLowerCase() === "path" && originalDs[i])
958
+ el.setAttribute("d", morphPath(originalDs[i], morphTo, alpha));
959
+ if (i === 0) {
960
+ onProgress?.(alpha);
961
+ container.style.setProperty("--scroll-draw-progress", String(alpha));
962
+ }
963
+ if (alpha < 1) allComplete = false;
964
+ });
965
+ if (waypoints) {
966
+ const rawAlpha = easeFn(computeProgress(currentScroll, tStart, tEnd, effectiveSpeed));
967
+ for (const key in waypoints) {
968
+ const t = parseFloat(key);
969
+ if (rawAlpha >= t && !firedWaypoints.has(t)) {
970
+ firedWaypoints.add(t);
971
+ waypoints[key]?.();
972
+ }
973
+ }
974
+ }
975
+ if (!started && computeProgress(currentScroll, tStart, tEnd, effectiveSpeed) > 0) {
976
+ started = true;
977
+ onStart?.();
978
+ }
979
+ if (allComplete && !completed) {
980
+ completed = true;
981
+ onComplete?.();
982
+ const maxRepeats = repeat === "infinite" ? Infinity : repeat ?? 0;
983
+ if (repeatCount < maxRepeats) {
984
+ repeatCount++;
985
+ repeatTimer = setTimeout(() => {
986
+ frozenAlpha = -1;
987
+ started = false;
988
+ completed = false;
989
+ firedWaypoints.clear();
990
+ resetPaths();
991
+ dirty = true;
992
+ }, repeatDelay);
993
+ }
994
+ } else if (!allComplete && !once) {
995
+ completed = false;
996
+ }
997
+ rafId = requestAnimationFrame(update);
998
+ }
999
+ let looping = false;
1000
+ function startLoop() {
1001
+ if (looping) return;
1002
+ looping = true;
1003
+ rafId = requestAnimationFrame(function frame() {
1004
+ update();
1005
+ });
1006
+ }
1007
+ function stopLoop() {
1008
+ looping = false;
1009
+ cancelAnimationFrame(rafId);
1010
+ }
1011
+ const observer = new IntersectionObserver(
1012
+ (entries) => {
1013
+ entries.forEach((e) => {
1014
+ isVisible = e.isIntersecting;
1015
+ if (isVisible && !paused) {
1016
+ dirty = true;
1017
+ startLoop();
1018
+ } else {
1019
+ stopLoop();
1020
+ }
1021
+ });
1022
+ },
1023
+ { root: scrollEl ?? null, threshold, rootMargin }
1024
+ );
1025
+ function remeasure() {
1026
+ paths.forEach((el, i) => {
1027
+ lengths[i] = getElementLength(el);
1028
+ el.style.strokeDasharray = `${lengths[i]}`;
1029
+ });
1030
+ cacheTriggers();
1031
+ dirty = true;
1032
+ }
1033
+ let resizeTimer;
1034
+ function onResize() {
1035
+ clearTimeout(resizeTimer);
1036
+ resizeTimer = setTimeout(remeasure, 150);
1037
+ }
1038
+ window.addEventListener("resize", onResize);
1039
+ window.addEventListener("orientationchange", onResize);
1040
+ let layoutObserver;
1041
+ if (typeof ResizeObserver !== "undefined") {
1042
+ layoutObserver = new ResizeObserver(() => onResize());
1043
+ layoutObserver.observe(document.body);
1044
+ layoutObserver.observe(document.documentElement);
1045
+ layoutObserver.observe(container);
1046
+ }
1047
+ if (delay > 0) setTimeout(() => observer.observe(container), delay);
1048
+ else observer.observe(container);
1049
+ return {
1050
+ destroy() {
1051
+ cancelAnimationFrame(rafId);
1052
+ clearTimeout(repeatTimer);
1053
+ stopLoop();
1054
+ observer.disconnect();
1055
+ layoutObserver?.disconnect();
1056
+ window.removeEventListener("resize", onResize);
1057
+ window.removeEventListener("orientationchange", onResize);
1058
+ clearTimeout(resizeTimer);
1059
+ destroyDebugOverlay();
1060
+ },
1061
+ replay() {
1062
+ frozenAlpha = -1;
1063
+ prevScroll = -1;
1064
+ prevVelScroll = -1;
1065
+ started = false;
1066
+ completed = false;
1067
+ repeatCount = 0;
1068
+ paused = false;
1069
+ currentAlpha = 0;
1070
+ firedWaypoints.clear();
1071
+ clearTimeout(repeatTimer);
1072
+ resetPaths();
1073
+ dirty = true;
1074
+ if (isVisible) startLoop();
1075
+ },
1076
+ pause() {
1077
+ paused = true;
1078
+ stopLoop();
1079
+ },
1080
+ resume() {
1081
+ if (!paused) return;
1082
+ paused = false;
1083
+ dirty = true;
1084
+ if (isVisible) startLoop();
1085
+ },
1086
+ seek(progress) {
1087
+ const p = Math.min(1, Math.max(0, progress));
1088
+ currentAlpha = p;
1089
+ frozenAlpha = p;
1090
+ paused = true;
1091
+ stopLoop();
1092
+ applyAlpha(p, direction);
1093
+ dirty = true;
1094
+ },
1095
+ getProgress() {
1096
+ return currentAlpha;
1097
+ },
1098
+ /**
1099
+ * Re-measure now, rather than waiting for a resize that may never come.
1100
+ *
1101
+ * Same work the debounced resize handler does — path lengths and the trigger
1102
+ * window — run immediately and marked dirty so the next frame repaints from
1103
+ * the new numbers.
1104
+ */
1105
+ refresh() {
1106
+ clearTimeout(resizeTimer);
1107
+ remeasure();
1108
+ if (isVisible && !paused) startLoop();
1109
+ }
1110
+ };
1111
+ }
1112
+
1113
+ // src/core/registry.ts
1114
+ var _registry = /* @__PURE__ */ new Map();
1115
+ function _register(el, entry) {
1116
+ _registry.set(el, entry);
1117
+ }
1118
+ function _unregister(el) {
1119
+ _registry.delete(el);
1120
+ }
1121
+
1122
+ // src/animate/index.ts
1123
+ function isColorValue(val) {
1124
+ return val.startsWith("#") || val.startsWith("rgb") || val.startsWith("hsl");
1125
+ }
1126
+ function parseTransformFns(str) {
1127
+ const result = [];
1128
+ const re = /([\w]+)\(([^)]*)\)/g;
1129
+ let m;
1130
+ while ((m = re.exec(str)) !== null) {
1131
+ const nums = [];
1132
+ const units = [];
1133
+ const raw = m[2].trim();
1134
+ if (raw) {
1135
+ for (const arg of raw.split(/[\s,]+/)) {
1136
+ const pm = arg.match(/^([-+]?[\d.eE]+)(.*)$/);
1137
+ nums.push(pm ? parseFloat(pm[1]) : 0);
1138
+ units.push(pm ? pm[2] : "");
1139
+ }
1140
+ }
1141
+ result.push({ fn: m[1], nums, units });
1142
+ }
1143
+ return result;
1144
+ }
1145
+ function interpolateTransform(from, to, t) {
1146
+ const fp = parseTransformFns(from);
1147
+ const tp = parseTransformFns(to);
1148
+ if (fp.length === 0 || fp.length !== tp.length) return t < 1 ? from : to;
1149
+ return fp.map((f, i) => {
1150
+ const tf = tp[i];
1151
+ if (f.fn !== tf.fn || f.nums.length !== tf.nums.length) {
1152
+ return t < 1 ? `${f.fn}(${f.nums.map((n, j) => `${n}${f.units[j]}`).join(", ")})` : `${tf.fn}(${tf.nums.map((n, j) => `${n}${tf.units[j]}`).join(", ")})`;
1153
+ }
1154
+ return `${f.fn}(${f.nums.map((n, j) => `${n + (tf.nums[j] - n) * t}${f.units[j]}`).join(", ")})`;
1155
+ }).join(" ");
1156
+ }
1157
+ function interpolateValue(from, to, t) {
1158
+ if (typeof from === "number" && typeof to === "number") {
1159
+ return String(from + (to - from) * t);
1160
+ }
1161
+ const f = String(from);
1162
+ const tStr = String(to);
1163
+ if (isColorValue(f)) return lerpColor(f, tStr, t);
1164
+ if (f.includes("(")) return interpolateTransform(f, tStr, t);
1165
+ const fm = f.match(/^([-+]?[\d.]+)(.*)$/);
1166
+ const tm = tStr.match(/^([-+]?[\d.]+)(.*)$/);
1167
+ if (fm && tm) {
1168
+ const fNum = parseFloat(fm[1]);
1169
+ const tNum = parseFloat(tm[1]);
1170
+ return `${fNum + (tNum - fNum) * t}${fm[2] || tm[2]}`;
1171
+ }
1172
+ return t < 1 ? f : tStr;
1173
+ }
1174
+ function toCSSProp(prop) {
1175
+ return prop.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`);
1176
+ }
1177
+ var NATIVE_SAFE_PROPS = /* @__PURE__ */ new Set([
1178
+ "opacity",
1179
+ "transform",
1180
+ "background-color",
1181
+ "color",
1182
+ "filter",
1183
+ "scale",
1184
+ "translate",
1185
+ "rotate"
1186
+ ]);
1187
+ var animateNativeUid = 0;
1188
+ var PROGRESS_VAR = "--scroll-draw-progress";
1189
+ function supportsNativeTimeline2() {
1190
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("animation-timeline: view()");
1191
+ }
1192
+ function createAnimateEngine(el, options) {
1193
+ const NOOP3 = {
1194
+ destroy: () => {
1195
+ },
1196
+ replay: () => {
1197
+ },
1198
+ pause: () => {
1199
+ },
1200
+ resume: () => {
1201
+ },
1202
+ seek: () => {
1203
+ },
1204
+ getProgress: () => 0
1205
+ };
1206
+ if (typeof window === "undefined") return NOOP3;
1207
+ const {
1208
+ props,
1209
+ trigger = {},
1210
+ easing = "ease-out",
1211
+ speed = 1,
1212
+ once = false,
1213
+ axis = "y",
1214
+ scrollContainer,
1215
+ native = true,
1216
+ velocityScale = false,
1217
+ respectReducedMotion = true,
1218
+ triggerElement,
1219
+ onProgress,
1220
+ onComplete,
1221
+ onEnter,
1222
+ onLeave,
1223
+ onEnterBack,
1224
+ onLeaveBack
1225
+ } = options;
1226
+ const prefersReduced = respectReducedMotion && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1227
+ const easeFn = typeof easing === "function" ? easing : EASINGS[easing] ?? EASINGS["ease-out"];
1228
+ const startConfig = parseTrigger(trigger.start ?? "top bottom");
1229
+ const endConfig = parseTrigger(trigger.end ?? "bottom top");
1230
+ const triggerEl = triggerElement ?? el;
1231
+ const scrollEl = typeof scrollContainer === "string" ? document.querySelector(scrollContainer) : scrollContainer ?? null;
1232
+ const entries = Object.entries(props).map(([key, val]) => ({
1233
+ prop: toCSSProp(key),
1234
+ from: Array.isArray(val) ? val[0] : "",
1235
+ to: Array.isArray(val) ? val[1] : val
1236
+ }));
1237
+ function resolveFromValues() {
1238
+ const computed = window.getComputedStyle(el);
1239
+ for (const entry of entries) {
1240
+ if (entry.from === "") {
1241
+ entry.from = computed.getPropertyValue(entry.prop).trim() || "0";
1242
+ }
1243
+ }
1244
+ }
1245
+ function applyFinal() {
1246
+ for (const entry of entries) {
1247
+ el.style.setProperty(entry.prop, String(entry.to));
1248
+ }
1249
+ }
1250
+ const savedInline = /* @__PURE__ */ new Map();
1251
+ for (const prop of [...entries.map((e) => e.prop), PROGRESS_VAR]) {
1252
+ savedInline.set(prop, el.style.getPropertyValue(prop));
1253
+ }
1254
+ function restoreInline() {
1255
+ for (const [prop, value] of savedInline) {
1256
+ if (value) el.style.setProperty(prop, value);
1257
+ else el.style.removeProperty(prop);
1258
+ }
1259
+ }
1260
+ if (prefersReduced) {
1261
+ applyFinal();
1262
+ onComplete?.();
1263
+ return { ...NOOP3, destroy: restoreInline, getProgress: () => 1 };
1264
+ }
1265
+ resolveFromValues();
1266
+ const cssTiming = typeof easing === "string" ? cssTimingFor(easing) : null;
1267
+ function nativeEligible() {
1268
+ if (!native) return false;
1269
+ if (!supportsNativeTimeline2()) return false;
1270
+ if (!cssTiming) return false;
1271
+ if (axis !== "y") return false;
1272
+ if (scrollEl) return false;
1273
+ if (triggerEl !== el) return false;
1274
+ if (once) return false;
1275
+ if (speed !== 1) return false;
1276
+ if (onProgress || onComplete || onEnter || onLeave || onEnterBack || onLeaveBack) return false;
1277
+ if (velocityScale !== false) return false;
1278
+ if ((trigger.start ?? "top bottom").trim() !== "top bottom") return false;
1279
+ if ((trigger.end ?? "bottom top").trim() !== "bottom top") return false;
1280
+ for (const entry of entries) {
1281
+ if (!NATIVE_SAFE_PROPS.has(entry.prop)) return false;
1282
+ }
1283
+ return true;
1284
+ }
1285
+ function buildNative() {
1286
+ const cls = `ssd-a-${++animateNativeUid}`;
1287
+ const fromBody = entries.map((e) => `${e.prop}:${e.from}`).join(";");
1288
+ const toBody = entries.map((e) => `${e.prop}:${e.to}`).join(";");
1289
+ const style = document.createElement("style");
1290
+ style.setAttribute("data-ssd-animate", "");
1291
+ style.textContent = `@keyframes ${cls}{from{${fromBody}}to{${toBody}}}.${cls}{animation-name:${cls};animation-duration:auto;animation-timing-function:${cssTiming};animation-fill-mode:both;animation-timeline:view();animation-range:cover 0% cover 100%;}`;
1292
+ document.head.appendChild(style);
1293
+ el.classList.add(cls);
1294
+ const scrollPos2 = () => axis === "x" ? window.scrollX : window.scrollY;
1295
+ const vpSize2 = () => axis === "x" ? window.innerWidth : window.innerHeight;
1296
+ return {
1297
+ destroy() {
1298
+ el.classList.remove(cls);
1299
+ style.remove();
1300
+ el.style.animationPlayState = "";
1301
+ restoreInline();
1302
+ },
1303
+ replay() {
1304
+ el.classList.remove(cls);
1305
+ void el.offsetWidth;
1306
+ el.classList.add(cls);
1307
+ },
1308
+ pause() {
1309
+ el.style.animationPlayState = "paused";
1310
+ },
1311
+ resume() {
1312
+ el.style.animationPlayState = "";
1313
+ },
1314
+ seek(p) {
1315
+ const clamped = Math.min(1, Math.max(0, p));
1316
+ el.classList.remove(cls);
1317
+ for (const entry of entries) {
1318
+ el.style.setProperty(entry.prop, interpolateValue(entry.from, entry.to, clamped));
1319
+ }
1320
+ },
1321
+ getProgress() {
1322
+ const rect = el.getBoundingClientRect();
1323
+ const scroll = scrollPos2();
1324
+ const vp = vpSize2();
1325
+ const { tStart: tStart2, tEnd: tEnd2 } = computeTriggers(
1326
+ { top: rect.top, height: rect.height },
1327
+ scroll,
1328
+ vp,
1329
+ startConfig,
1330
+ endConfig
1331
+ );
1332
+ return easeFn(computeProgress(scroll, tStart2, tEnd2, speed));
1333
+ }
1334
+ };
1335
+ }
1336
+ if (nativeEligible()) return buildNative();
1337
+ let tStart = 0;
1338
+ let tEnd = 0;
1339
+ let rafId = 0;
1340
+ let isVisible = false;
1341
+ let paused = false;
1342
+ let frozenAlpha = -1;
1343
+ let currentAlpha = 0;
1344
+ let completed = false;
1345
+ let prevRawProgress = NaN;
1346
+ let prevVelScroll = -1;
1347
+ let prevVelTime = 0;
1348
+ const scrollPos = () => {
1349
+ if (scrollEl) return axis === "x" ? scrollEl.scrollLeft : scrollEl.scrollTop;
1350
+ return axis === "x" ? window.scrollX : window.scrollY;
1351
+ };
1352
+ const vpSize = () => {
1353
+ if (scrollEl) return axis === "x" ? scrollEl.clientWidth : scrollEl.clientHeight;
1354
+ return axis === "x" ? window.innerWidth : window.innerHeight;
1355
+ };
1356
+ function cacheTriggers() {
1357
+ const frame = measureTriggerFrame(triggerEl, scrollEl, axis);
1358
+ const result = computeTriggers(frame, frame.scroll, vpSize(), startConfig, endConfig);
1359
+ tStart = result.tStart;
1360
+ tEnd = result.tEnd;
1361
+ }
1362
+ function applyAlpha(alpha) {
1363
+ el.style.setProperty(PROGRESS_VAR, String(alpha));
1364
+ for (const entry of entries) {
1365
+ el.style.setProperty(entry.prop, interpolateValue(entry.from, entry.to, alpha));
1366
+ }
1367
+ onProgress?.(alpha);
1368
+ }
1369
+ function fireScrollCallbacks(raw) {
1370
+ if (isNaN(prevRawProgress)) {
1371
+ prevRawProgress = raw;
1372
+ return;
1373
+ }
1374
+ if (prevRawProgress <= 0 && raw > 0) onEnter?.();
1375
+ else if (prevRawProgress > 0 && raw <= 0) onLeaveBack?.();
1376
+ if (prevRawProgress < 1 && raw >= 1) onLeave?.();
1377
+ else if (prevRawProgress >= 1 && raw < 1) onEnterBack?.();
1378
+ prevRawProgress = raw;
1379
+ }
1380
+ function update() {
1381
+ if (!isVisible || paused) return;
1382
+ const now = performance.now();
1383
+ const currentScroll = scrollPos();
1384
+ let effectiveSpeed = speed;
1385
+ if (velocityScale !== false) {
1386
+ const dt = now - prevVelTime;
1387
+ const vel = dt > 0 ? Math.abs(currentScroll - (prevVelScroll < 0 ? currentScroll : prevVelScroll)) / dt : 0;
1388
+ const sensitivity = typeof velocityScale === "number" ? velocityScale : 1;
1389
+ effectiveSpeed = speed * Math.max(0.2, 1 + vel * sensitivity * 0.04);
1390
+ }
1391
+ prevVelScroll = currentScroll;
1392
+ prevVelTime = now;
1393
+ const raw = tEnd === tStart ? 0 : (currentScroll - tStart) / (tEnd - tStart);
1394
+ fireScrollCallbacks(raw);
1395
+ let alpha = easeFn(computeProgress(currentScroll, tStart, tEnd, effectiveSpeed));
1396
+ if (once) {
1397
+ frozenAlpha = Math.max(frozenAlpha, alpha);
1398
+ alpha = frozenAlpha;
1399
+ }
1400
+ currentAlpha = alpha;
1401
+ applyAlpha(alpha);
1402
+ if (alpha >= 1 && !completed) {
1403
+ completed = true;
1404
+ onComplete?.();
1405
+ } else if (alpha < 1 && !once) {
1406
+ completed = false;
1407
+ }
1408
+ rafId = requestAnimationFrame(update);
1409
+ }
1410
+ cacheTriggers();
1411
+ if (tEnd === tStart) {
1412
+ warn(
1413
+ "scrollAnimate: the trigger window has zero length (start and end resolve to the same scroll position), so this element will never animate. If the target is inside a position:sticky stage, measure the trigger from the container that holds the scroll room via `triggerElement`.",
1414
+ el
1415
+ );
1416
+ }
1417
+ {
1418
+ const initAlpha = easeFn(computeProgress(scrollPos(), tStart, tEnd, speed));
1419
+ if (once && initAlpha > 0) frozenAlpha = initAlpha;
1420
+ currentAlpha = initAlpha;
1421
+ applyAlpha(initAlpha);
1422
+ }
1423
+ const observer = new IntersectionObserver(
1424
+ (ioEntries) => {
1425
+ ioEntries.forEach((e) => {
1426
+ isVisible = e.isIntersecting;
1427
+ if (isVisible && !paused) rafId = requestAnimationFrame(update);
1428
+ else cancelAnimationFrame(rafId);
1429
+ });
1430
+ },
1431
+ { root: scrollEl ?? null }
1432
+ );
1433
+ let resizeTimer;
1434
+ function onResize() {
1435
+ clearTimeout(resizeTimer);
1436
+ resizeTimer = setTimeout(cacheTriggers, 150);
1437
+ }
1438
+ window.addEventListener("resize", onResize);
1439
+ window.addEventListener("orientationchange", onResize);
1440
+ observer.observe(el);
1441
+ _register(el, {
1442
+ type: "animate",
1443
+ getProgress: () => currentAlpha,
1444
+ getTrigger: () => ({ tStart, tEnd })
1445
+ });
1446
+ return {
1447
+ destroy() {
1448
+ cancelAnimationFrame(rafId);
1449
+ observer.disconnect();
1450
+ window.removeEventListener("resize", onResize);
1451
+ window.removeEventListener("orientationchange", onResize);
1452
+ clearTimeout(resizeTimer);
1453
+ restoreInline();
1454
+ _unregister(el);
1455
+ },
1456
+ replay() {
1457
+ frozenAlpha = -1;
1458
+ completed = false;
1459
+ currentAlpha = 0;
1460
+ paused = false;
1461
+ applyAlpha(0);
1462
+ },
1463
+ pause() {
1464
+ paused = true;
1465
+ cancelAnimationFrame(rafId);
1466
+ },
1467
+ resume() {
1468
+ if (!paused) return;
1469
+ paused = false;
1470
+ if (isVisible) rafId = requestAnimationFrame(update);
1471
+ },
1472
+ seek(p) {
1473
+ const clamped = Math.min(1, Math.max(0, p));
1474
+ currentAlpha = clamped;
1475
+ frozenAlpha = clamped;
1476
+ paused = true;
1477
+ cancelAnimationFrame(rafId);
1478
+ applyAlpha(clamped);
1479
+ },
1480
+ getProgress() {
1481
+ return currentAlpha;
1482
+ }
1483
+ };
1484
+ }
1485
+ var NOOP = {
1486
+ destroy: () => {
1487
+ },
1488
+ replay: () => {
1489
+ },
1490
+ pause: () => {
1491
+ },
1492
+ resume: () => {
1493
+ },
1494
+ seek: () => {
1495
+ },
1496
+ getProgress: () => 0
1497
+ };
1498
+ function scrollAnimate(target, options) {
1499
+ if (typeof window === "undefined") return NOOP;
1500
+ const el = typeof target === "string" ? document.querySelector(target) : target;
1501
+ if (!el) {
1502
+ warn("scrollAnimate: element not found:", target);
1503
+ return NOOP;
1504
+ }
1505
+ return createAnimateEngine(el, options);
1506
+ }
1507
+ function scrollParallax(target, options = {}) {
1508
+ if (typeof window === "undefined") return NOOP;
1509
+ const el = typeof target === "string" ? document.querySelector(target) : target;
1510
+ if (!el) {
1511
+ warn("scrollParallax: element not found:", target);
1512
+ return NOOP;
1513
+ }
1514
+ const { speed = 0.3, axis = "y", easing = "linear", trigger, onProgress } = options;
1515
+ const rect = el.getBoundingClientRect();
1516
+ const size = axis === "x" ? rect.width : rect.height;
1517
+ const travel = speed * size;
1518
+ return createAnimateEngine(el, {
1519
+ props: axis === "x" ? { transform: [`translateX(0px)`, `translateX(${-travel}px)`] } : { transform: [`translateY(0px)`, `translateY(${-travel}px)`] },
1520
+ trigger: trigger ?? { start: "top bottom", end: "bottom top" },
1521
+ easing,
1522
+ native: false,
1523
+ onProgress
1524
+ });
1525
+ }
1526
+
1527
+ // src/counter/index.ts
1528
+ var NOOP2 = {
1529
+ destroy: () => {
1530
+ },
1531
+ replay: () => {
1532
+ },
1533
+ pause: () => {
1534
+ },
1535
+ resume: () => {
1536
+ },
1537
+ seek: () => {
1538
+ },
1539
+ getProgress: () => 0
1540
+ };
1541
+ function scrollCounter(target, options) {
1542
+ if (typeof window === "undefined") return NOOP2;
1543
+ const raw = typeof target === "string" ? document.querySelector(target) : target;
1544
+ if (!raw) {
1545
+ warn("scrollCounter: element not found:", target);
1546
+ return NOOP2;
1547
+ }
1548
+ const el = raw;
1549
+ const {
1550
+ from = 0,
1551
+ to,
1552
+ format,
1553
+ easing = "ease-out",
1554
+ trigger = {},
1555
+ once = true,
1556
+ decimals,
1557
+ onComplete
1558
+ } = options;
1559
+ const fmt = decimals !== void 0 ? (n) => n.toFixed(decimals) : format ?? ((n) => String(Math.round(n)));
1560
+ const easeFn = typeof easing === "function" ? easing : EASINGS[easing] ?? EASINGS["ease-out"];
1561
+ const startConfig = parseTrigger(trigger.start ?? "top 80%");
1562
+ const endConfig = parseTrigger(trigger.end ?? "top 20%");
1563
+ const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1564
+ if (prefersReduced) {
1565
+ el.textContent = fmt(to);
1566
+ onComplete?.();
1567
+ return NOOP2;
1568
+ }
1569
+ let tStart = 0;
1570
+ let tEnd = 0;
1571
+ let rafId = 0;
1572
+ let isVisible = false;
1573
+ let paused = false;
1574
+ let frozenAlpha = -1;
1575
+ let currentAlpha = 0;
1576
+ let completed = false;
1577
+ const scrollPos = () => window.scrollY;
1578
+ const vpSize = () => window.innerHeight;
1579
+ function cacheTriggers() {
1580
+ const rect = el.getBoundingClientRect();
1581
+ const result = computeTriggers(
1582
+ { top: rect.top, height: rect.height },
1583
+ scrollPos(),
1584
+ vpSize(),
1585
+ startConfig,
1586
+ endConfig
1587
+ );
1588
+ tStart = result.tStart;
1589
+ tEnd = result.tEnd;
1590
+ }
1591
+ function applyAlpha(alpha) {
1592
+ el.textContent = fmt(from + (to - from) * alpha);
1593
+ el.style.setProperty("--scroll-draw-progress", String(alpha));
1594
+ }
1595
+ function update() {
1596
+ if (!isVisible || paused) return;
1597
+ let alpha = easeFn(computeProgress(scrollPos(), tStart, tEnd, 1));
1598
+ if (once) {
1599
+ frozenAlpha = Math.max(frozenAlpha, alpha);
1600
+ alpha = frozenAlpha;
1601
+ }
1602
+ currentAlpha = alpha;
1603
+ applyAlpha(alpha);
1604
+ if (alpha >= 1 && !completed) {
1605
+ completed = true;
1606
+ onComplete?.();
1607
+ } else if (alpha < 1 && !once) {
1608
+ completed = false;
1609
+ }
1610
+ rafId = requestAnimationFrame(update);
1611
+ }
1612
+ cacheTriggers();
1613
+ {
1614
+ const initAlpha = easeFn(computeProgress(scrollPos(), tStart, tEnd, 1));
1615
+ if (once && initAlpha > 0) frozenAlpha = initAlpha;
1616
+ currentAlpha = initAlpha;
1617
+ applyAlpha(initAlpha);
1618
+ }
1619
+ const observer = new IntersectionObserver(
1620
+ (entries) => {
1621
+ entries.forEach((e) => {
1622
+ isVisible = e.isIntersecting;
1623
+ if (isVisible && !paused) rafId = requestAnimationFrame(update);
1624
+ else cancelAnimationFrame(rafId);
1625
+ });
1626
+ }
1627
+ );
1628
+ let resizeTimer;
1629
+ function onResize() {
1630
+ clearTimeout(resizeTimer);
1631
+ resizeTimer = setTimeout(cacheTriggers, 150);
1632
+ }
1633
+ window.addEventListener("resize", onResize);
1634
+ window.addEventListener("orientationchange", onResize);
1635
+ observer.observe(el);
1636
+ _register(el, {
1637
+ type: "counter",
1638
+ getProgress: () => currentAlpha,
1639
+ getTrigger: () => ({ tStart, tEnd })
1640
+ });
1641
+ return {
1642
+ destroy() {
1643
+ cancelAnimationFrame(rafId);
1644
+ observer.disconnect();
1645
+ window.removeEventListener("resize", onResize);
1646
+ window.removeEventListener("orientationchange", onResize);
1647
+ clearTimeout(resizeTimer);
1648
+ _unregister(el);
1649
+ },
1650
+ replay() {
1651
+ frozenAlpha = -1;
1652
+ completed = false;
1653
+ currentAlpha = 0;
1654
+ paused = false;
1655
+ applyAlpha(0);
1656
+ },
1657
+ pause() {
1658
+ paused = true;
1659
+ cancelAnimationFrame(rafId);
1660
+ },
1661
+ resume() {
1662
+ if (!paused) return;
1663
+ paused = false;
1664
+ if (isVisible) rafId = requestAnimationFrame(update);
1665
+ },
1666
+ seek(p) {
1667
+ const clamped = Math.min(1, Math.max(0, p));
1668
+ currentAlpha = clamped;
1669
+ frozenAlpha = clamped;
1670
+ paused = true;
1671
+ cancelAnimationFrame(rafId);
1672
+ applyAlpha(clamped);
1673
+ },
1674
+ getProgress() {
1675
+ return currentAlpha;
1676
+ }
1677
+ };
1678
+ }
1679
+
1680
+ // src/cinematic/index.ts
1681
+ var SVGNS = "http://www.w3.org/2000/svg";
1682
+ var PHOTO_ID = "cinematic-photo";
1683
+ var clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
1684
+ function parsePercent(v) {
1685
+ const n = parseFloat(v);
1686
+ return Number.isFinite(n) ? clamp01(n / 100) : 0;
1687
+ }
1688
+ function easeOf(name) {
1689
+ return EASINGS[name] ?? EASINGS.linear;
1690
+ }
1691
+ var noop = { destroy: () => {
1692
+ }, getProgress: () => 0 };
1693
+ var Cinematic = class {
1694
+ constructor(options) {
1695
+ if (typeof document === "undefined") {
1696
+ this.mount = null;
1697
+ return;
1698
+ }
1699
+ this.mount = typeof options.wrapper === "string" ? document.querySelector(options.wrapper) : options.wrapper;
1700
+ if (!this.mount) warn("Cinematic: wrapper not found:", options.wrapper);
1701
+ }
1702
+ loadStory(story) {
1703
+ const mount = this.mount;
1704
+ if (typeof window === "undefined" || !mount) return noop;
1705
+ mount.style.position = "relative";
1706
+ mount.style.height = story.totalHeight;
1707
+ mount.style.display = "block";
1708
+ const stage = document.createElement("div");
1709
+ stage.setAttribute("data-cinematic-stage", "");
1710
+ stage.style.cssText = "position:sticky;top:0;height:100vh;width:100%;overflow:hidden;display:block;";
1711
+ mount.appendChild(stage);
1712
+ const records = [];
1713
+ for (const scene of story.scenes) {
1714
+ if (scene.background) {
1715
+ const img = document.createElement("img");
1716
+ img.id = PHOTO_ID;
1717
+ img.src = scene.background;
1718
+ img.alt = "";
1719
+ img.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:contain;opacity:0;pointer-events:none;";
1720
+ stage.appendChild(img);
1721
+ }
1722
+ const drawAnims = scene.animations.filter((a) => a.type === "draw");
1723
+ if (drawAnims.length) {
1724
+ const svg = document.createElementNS(SVGNS, "svg");
1725
+ svg.setAttribute("viewBox", `0 0 ${story.canvas.width} ${story.canvas.height}`);
1726
+ svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
1727
+ svg.style.cssText = "position:absolute;inset:0;width:100%;height:100%;overflow:visible;";
1728
+ stage.appendChild(svg);
1729
+ for (const a of drawAnims) {
1730
+ const path = document.createElementNS(SVGNS, "path");
1731
+ path.id = a.target.replace(/^#/, "");
1732
+ path.setAttribute("d", a.d);
1733
+ path.setAttribute("fill", "none");
1734
+ path.setAttribute("stroke", a.stroke);
1735
+ path.setAttribute("stroke-width", String(a.strokeWidth));
1736
+ path.setAttribute("stroke-linecap", "round");
1737
+ path.setAttribute("stroke-linejoin", "round");
1738
+ const len = a.length || path.getTotalLength?.() || 0;
1739
+ path.style.strokeDasharray = String(len);
1740
+ path.style.strokeDashoffset = String(len);
1741
+ svg.appendChild(path);
1742
+ records.push({
1743
+ kind: "draw",
1744
+ el: path,
1745
+ start: parsePercent(a.start),
1746
+ end: parsePercent(a.end),
1747
+ ease: easeOf(a.easing),
1748
+ length: len
1749
+ });
1750
+ }
1751
+ }
1752
+ for (const a of scene.animations) {
1753
+ if (a.type !== "fade") continue;
1754
+ const el = stage.querySelector(a.target) ?? document.querySelector(a.target);
1755
+ if (!el) continue;
1756
+ el.style.opacity = String(a.from);
1757
+ records.push({
1758
+ kind: "fade",
1759
+ el,
1760
+ start: parsePercent(a.start),
1761
+ end: parsePercent(a.end),
1762
+ ease: EASINGS["ease-in-out"] ?? EASINGS.linear,
1763
+ from: a.from,
1764
+ to: a.to
1765
+ });
1766
+ }
1767
+ }
1768
+ let progress = 0;
1769
+ const computeProgress2 = () => {
1770
+ const scrollable = mount.offsetHeight - window.innerHeight;
1771
+ if (scrollable <= 0) return mount.getBoundingClientRect().top <= 0 ? 1 : 0;
1772
+ return clamp01(-mount.getBoundingClientRect().top / scrollable);
1773
+ };
1774
+ const apply = (global) => {
1775
+ for (const r of records) {
1776
+ const span = r.end - r.start;
1777
+ const local = span <= 0 ? global >= r.end ? 1 : 0 : clamp01((global - r.start) / span);
1778
+ const alpha = r.ease(local);
1779
+ if (r.kind === "draw") {
1780
+ r.el.style.strokeDashoffset = String(r.length * (1 - alpha));
1781
+ } else {
1782
+ r.el.style.opacity = String(r.from + (r.to - r.from) * alpha);
1783
+ }
1784
+ }
1785
+ };
1786
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
1787
+ apply(1);
1788
+ return { destroy: () => {
1789
+ }, getProgress: () => 1 };
1790
+ }
1791
+ let rafId = 0;
1792
+ let running = false;
1793
+ let visible = false;
1794
+ const tick = () => {
1795
+ progress = computeProgress2();
1796
+ apply(progress);
1797
+ if (running) rafId = requestAnimationFrame(tick);
1798
+ };
1799
+ const start = () => {
1800
+ if (running) return;
1801
+ running = true;
1802
+ rafId = requestAnimationFrame(tick);
1803
+ };
1804
+ const stop = () => {
1805
+ running = false;
1806
+ cancelAnimationFrame(rafId);
1807
+ };
1808
+ const observer = new IntersectionObserver(
1809
+ (entries) => {
1810
+ for (const e of entries) {
1811
+ visible = e.isIntersecting;
1812
+ if (visible) start();
1813
+ else stop();
1814
+ }
1815
+ },
1816
+ { threshold: 0 }
1817
+ );
1818
+ observer.observe(stage);
1819
+ progress = computeProgress2();
1820
+ apply(progress);
1821
+ return {
1822
+ destroy() {
1823
+ stop();
1824
+ observer.disconnect();
1825
+ },
1826
+ getProgress: () => progress
1827
+ };
1828
+ }
1829
+ };
1830
+
1831
+ // src/index.ts
1832
+ function scrollDraw(target, options) {
1833
+ const noop2 = { destroy: () => {
1834
+ }, replay: () => {
1835
+ }, pause: () => {
1836
+ }, resume: () => {
1837
+ }, seek: () => {
1838
+ }, getProgress: () => 0 };
1839
+ if (typeof window === "undefined") return noop2;
1840
+ const container = typeof target === "string" ? document.querySelector(target) : target;
1841
+ if (!container) {
1842
+ warn("Container not found:", target);
1843
+ return noop2;
1844
+ }
1845
+ return createEngine(container, options);
1846
+ }
1847
+
1848
+ // src/web-component/index.ts
1849
+ if (typeof HTMLElement !== "undefined" && typeof customElements !== "undefined" && !customElements.get("scroll-draw")) {
1850
+ class ScrollDrawElement extends HTMLElement {
1851
+ constructor() {
1852
+ super(...arguments);
1853
+ this.instance = null;
1854
+ }
1855
+ connectedCallback() {
1856
+ const opts = {};
1857
+ const speed = this.getAttribute("speed");
1858
+ const easing = this.getAttribute("easing");
1859
+ const stagger = this.getAttribute("stagger");
1860
+ const direction = this.getAttribute("direction");
1861
+ const selector = this.getAttribute("selector");
1862
+ if (speed) opts.speed = parseFloat(speed);
1863
+ if (easing) opts.easing = easing;
1864
+ if (stagger) opts.stagger = parseFloat(stagger);
1865
+ if (direction) opts.direction = direction;
1866
+ if (selector) opts.selector = selector;
1867
+ if (this.hasAttribute("fade")) opts.fade = this.getAttribute("fade") !== "false";
1868
+ this.instance = createEngine(this, opts);
1869
+ }
1870
+ disconnectedCallback() {
1871
+ this.instance?.destroy();
1872
+ this.instance = null;
1873
+ }
1874
+ }
1875
+ customElements.define("scroll-draw", ScrollDrawElement);
1876
+ }
1877
+ return __toCommonJS(cdn_exports);
1878
+ })();