@frybynite/image-cloud 0.4.2 → 0.5.2

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 (70) hide show
  1. package/dist/ImageCloud.d.ts +99 -0
  2. package/dist/ImageCloud.d.ts.map +1 -0
  3. package/dist/config/adapter.d.ts +50 -0
  4. package/dist/config/adapter.d.ts.map +1 -0
  5. package/dist/config/defaults.d.ts +118 -0
  6. package/dist/config/defaults.d.ts.map +1 -0
  7. package/dist/config/types.d.ts +599 -0
  8. package/dist/config/types.d.ts.map +1 -0
  9. package/dist/engines/AnimationEngine.d.ts +82 -0
  10. package/dist/engines/AnimationEngine.d.ts.map +1 -0
  11. package/dist/engines/EntryAnimationEngine.d.ts +161 -0
  12. package/dist/engines/EntryAnimationEngine.d.ts.map +1 -0
  13. package/dist/engines/LayoutEngine.d.ts +68 -0
  14. package/dist/engines/LayoutEngine.d.ts.map +1 -0
  15. package/dist/engines/PathAnimator.d.ts +50 -0
  16. package/dist/engines/PathAnimator.d.ts.map +1 -0
  17. package/dist/engines/SwipeEngine.d.ts +53 -0
  18. package/dist/engines/SwipeEngine.d.ts.map +1 -0
  19. package/dist/engines/ZoomEngine.d.ts +139 -0
  20. package/dist/engines/ZoomEngine.d.ts.map +1 -0
  21. package/dist/image-cloud-auto-init.d.ts +14 -0
  22. package/dist/image-cloud-auto-init.d.ts.map +1 -0
  23. package/dist/image-cloud-auto-init.js +215 -244
  24. package/dist/image-cloud-auto-init.js.map +1 -1
  25. package/dist/image-cloud.js +235 -264
  26. package/dist/image-cloud.js.map +1 -1
  27. package/dist/image-cloud.umd.js +3 -3
  28. package/dist/image-cloud.umd.js.map +1 -1
  29. package/dist/index.d.ts +23 -1637
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/layouts/ClusterPlacementLayout.d.ts +40 -0
  32. package/dist/layouts/ClusterPlacementLayout.d.ts.map +1 -0
  33. package/dist/layouts/GridPlacementLayout.d.ts +27 -0
  34. package/dist/layouts/GridPlacementLayout.d.ts.map +1 -0
  35. package/dist/layouts/RadialPlacementLayout.d.ts +33 -0
  36. package/dist/layouts/RadialPlacementLayout.d.ts.map +1 -0
  37. package/dist/layouts/RandomPlacementLayout.d.ts +26 -0
  38. package/dist/layouts/RandomPlacementLayout.d.ts.map +1 -0
  39. package/dist/layouts/SpiralPlacementLayout.d.ts +43 -0
  40. package/dist/layouts/SpiralPlacementLayout.d.ts.map +1 -0
  41. package/dist/layouts/WavePlacementLayout.d.ts +48 -0
  42. package/dist/layouts/WavePlacementLayout.d.ts.map +1 -0
  43. package/dist/loaders/CompositeLoader.d.ts +37 -0
  44. package/dist/loaders/CompositeLoader.d.ts.map +1 -0
  45. package/dist/loaders/GoogleDriveLoader.d.ts +90 -0
  46. package/dist/loaders/GoogleDriveLoader.d.ts.map +1 -0
  47. package/dist/loaders/ImageFilter.d.ts +26 -0
  48. package/dist/loaders/ImageFilter.d.ts.map +1 -0
  49. package/dist/loaders/StaticImageLoader.d.ts +85 -0
  50. package/dist/loaders/StaticImageLoader.d.ts.map +1 -0
  51. package/dist/react/index.d.ts +16 -0
  52. package/dist/react/index.d.ts.map +1 -0
  53. package/dist/react.d.ts +1 -0
  54. package/dist/react.js +3227 -0
  55. package/dist/react.js.map +1 -0
  56. package/dist/styles/functionalStyles.d.ts +11 -0
  57. package/dist/styles/functionalStyles.d.ts.map +1 -0
  58. package/dist/utils/styleUtils.d.ts +54 -0
  59. package/dist/utils/styleUtils.d.ts.map +1 -0
  60. package/dist/vue/index.d.ts +18 -0
  61. package/dist/vue/index.d.ts.map +1 -0
  62. package/dist/vue.d.ts +1 -0
  63. package/dist/vue.js +3241 -0
  64. package/dist/vue.js.map +1 -0
  65. package/dist/web-component/index.d.ts +15 -0
  66. package/dist/web-component/index.d.ts.map +1 -0
  67. package/dist/web-component.d.ts +1 -0
  68. package/dist/web-component.js +3265 -0
  69. package/dist/web-component.js.map +1 -0
  70. package/package.json +38 -4
package/dist/react.js ADDED
@@ -0,0 +1,3227 @@
1
+ import { jsx as Ot } from "react/jsx-runtime";
2
+ import { forwardRef as $t, useRef as wt, useImperativeHandle as Ut, useEffect as Pt } from "react";
3
+ const mt = Object.freeze({
4
+ none: "none",
5
+ sm: "0 2px 4px rgba(0,0,0,0.1)",
6
+ md: "0 4px 16px rgba(0,0,0,0.4)",
7
+ lg: "0 8px 32px rgba(0,0,0,0.5)",
8
+ glow: "0 0 30px rgba(255,255,255,0.6)"
9
+ }), xt = Object.freeze({
10
+ energetic: Object.freeze({ overshoot: 0.25, bounces: 2, decayRatio: 0.5 }),
11
+ playful: Object.freeze({ overshoot: 0.15, bounces: 1, decayRatio: 0.5 }),
12
+ subtle: Object.freeze({ overshoot: 0.08, bounces: 1, decayRatio: 0.5 })
13
+ }), St = Object.freeze({
14
+ gentle: Object.freeze({ stiffness: 150, damping: 30, mass: 1, oscillations: 2 }),
15
+ bouncy: Object.freeze({ stiffness: 300, damping: 15, mass: 1, oscillations: 4 }),
16
+ wobbly: Object.freeze({ stiffness: 180, damping: 12, mass: 1.5, oscillations: 5 }),
17
+ snappy: Object.freeze({ stiffness: 400, damping: 25, mass: 0.8, oscillations: 2 })
18
+ }), Et = Object.freeze({
19
+ gentle: Object.freeze({ amplitude: 30, frequency: 1.5, decay: !0, decayRate: 0.9, phase: 0 }),
20
+ playful: Object.freeze({ amplitude: 50, frequency: 2.5, decay: !0, decayRate: 0.7, phase: 0 }),
21
+ serpentine: Object.freeze({ amplitude: 60, frequency: 3, decay: !1, decayRate: 1, phase: 0 }),
22
+ flutter: Object.freeze({ amplitude: 20, frequency: 4, decay: !0, decayRate: 0.5, phase: 0 })
23
+ }), pt = Object.freeze({
24
+ type: "linear"
25
+ }), bt = Object.freeze({
26
+ mode: "none"
27
+ }), yt = Object.freeze({
28
+ mode: "none"
29
+ }), Tt = Object.freeze({
30
+ default: Object.freeze({
31
+ border: Object.freeze({
32
+ width: 0,
33
+ color: "#000000",
34
+ radius: 0,
35
+ style: "solid"
36
+ }),
37
+ shadow: "none",
38
+ filter: Object.freeze({}),
39
+ opacity: 1,
40
+ cursor: "pointer",
41
+ outline: Object.freeze({
42
+ width: 0,
43
+ color: "#000000",
44
+ style: "solid",
45
+ offset: 0
46
+ })
47
+ }),
48
+ hover: Object.freeze({
49
+ shadow: "none"
50
+ }),
51
+ focused: Object.freeze({
52
+ shadow: "none"
53
+ })
54
+ }), _t = Object.freeze({
55
+ rows: 1,
56
+ amplitude: 100,
57
+ frequency: 2,
58
+ phaseShift: 0,
59
+ synchronization: "offset"
60
+ // Note: Image rotation along wave is now controlled via image.rotation.mode = 'tangent'
61
+ }), Ht = Object.freeze({
62
+ mobile: Object.freeze({ maxWidth: 767 }),
63
+ tablet: Object.freeze({ maxWidth: 1199 })
64
+ }), Nt = Object.freeze({
65
+ mode: "adaptive",
66
+ // Default to adaptive sizing
67
+ minSize: 50,
68
+ // Adaptive mode minimum
69
+ maxSize: 400,
70
+ // Adaptive mode maximum
71
+ variance: Object.freeze({
72
+ min: 1,
73
+ // No variance by default
74
+ max: 1
75
+ })
76
+ }), jt = Object.freeze({
77
+ mode: "none",
78
+ range: Object.freeze({
79
+ min: -15,
80
+ max: 15
81
+ })
82
+ }), Ct = Object.freeze({
83
+ sizing: Nt,
84
+ rotation: jt
85
+ }), Rt = Object.freeze({
86
+ validateUrls: !0,
87
+ validationTimeout: 5e3,
88
+ validationMethod: "head",
89
+ allowedExtensions: ["jpg", "jpeg", "png", "gif", "webp", "bmp"]
90
+ }), Mt = Object.freeze({
91
+ enabled: !1,
92
+ centers: !1,
93
+ loaders: !1
94
+ }), y = Object.freeze({
95
+ // Loader configuration (always an array, composite behavior is implicit)
96
+ loaders: [],
97
+ // Shared loader settings and debug config
98
+ config: Object.freeze({
99
+ loaders: Rt,
100
+ debug: Mt
101
+ }),
102
+ // Image sizing and rotation configuration
103
+ image: Ct,
104
+ // Pattern-based layout configuration
105
+ layout: Object.freeze({
106
+ algorithm: "radial",
107
+ scaleDecay: 0,
108
+ // No decay by default (0-1 for radial/spiral)
109
+ responsive: Ht,
110
+ targetCoverage: 0.6,
111
+ // Target 60% of container area
112
+ densityFactor: 1,
113
+ // Default density
114
+ spacing: Object.freeze({
115
+ padding: 50,
116
+ // padding from viewport edges
117
+ minGap: 20
118
+ // minimum spacing between images
119
+ })
120
+ }),
121
+ // Pattern-based animation configuration
122
+ animation: Object.freeze({
123
+ duration: 600,
124
+ // milliseconds
125
+ easing: Object.freeze({
126
+ default: "cubic-bezier(0.4, 0.0, 0.2, 1)",
127
+ // smooth easing
128
+ bounce: "cubic-bezier(0.68, -0.55, 0.265, 1.55)",
129
+ // bounce effect
130
+ focus: "cubic-bezier(0.4, 0.0, 0.2, 1)"
131
+ // focus/zoom easing
132
+ }),
133
+ queue: Object.freeze({
134
+ enabled: !0,
135
+ // When false, all images display simultaneously
136
+ interval: 150,
137
+ // ms between processing queue items (when enabled)
138
+ maxConcurrent: void 0
139
+ // STUB: Not implemented yet
140
+ }),
141
+ performance: Object.freeze({
142
+ useGPU: void 0,
143
+ // STUB: Not implemented yet
144
+ reduceMotion: void 0
145
+ // STUB: Not implemented yet
146
+ }),
147
+ entry: Object.freeze({
148
+ start: Object.freeze({
149
+ position: "nearest-edge",
150
+ // Default to nearest edge (current behavior)
151
+ offset: 100,
152
+ // pixels beyond edge
153
+ circular: Object.freeze({
154
+ radius: "120%",
155
+ // 120% of container diagonal
156
+ distribution: "even"
157
+ })
158
+ }),
159
+ timing: Object.freeze({
160
+ duration: 600
161
+ // ms
162
+ }),
163
+ easing: "cubic-bezier(0.25, 1, 0.5, 1)",
164
+ // smooth deceleration
165
+ path: pt,
166
+ rotation: bt,
167
+ scale: yt
168
+ })
169
+ }),
170
+ // Pattern-based interaction configuration
171
+ interaction: Object.freeze({
172
+ focus: Object.freeze({
173
+ scalePercent: 0.8,
174
+ // 80% of container height
175
+ zIndex: 1e3,
176
+ animationDuration: void 0
177
+ // Use default animation duration
178
+ }),
179
+ navigation: Object.freeze({
180
+ keyboard: void 0,
181
+ // STUB: Not implemented yet
182
+ swipe: void 0,
183
+ // STUB: Not implemented yet
184
+ mouseWheel: void 0
185
+ // STUB: Not implemented yet
186
+ }),
187
+ gestures: Object.freeze({
188
+ pinchToZoom: void 0,
189
+ // STUB: Not implemented yet
190
+ doubleTapToFocus: void 0
191
+ // STUB: Not implemented yet
192
+ })
193
+ }),
194
+ // Pattern-based rendering configuration
195
+ rendering: Object.freeze({
196
+ responsive: Object.freeze({
197
+ breakpoints: Object.freeze({
198
+ mobile: 768,
199
+ tablet: void 0,
200
+ // STUB: Not implemented yet
201
+ desktop: void 0
202
+ // STUB: Not implemented yet
203
+ }),
204
+ mobileDetection: () => typeof window > "u" ? !1 : window.innerWidth <= 768
205
+ }),
206
+ ui: Object.freeze({
207
+ showLoadingSpinner: !1,
208
+ showImageCounter: !1,
209
+ showThumbnails: void 0,
210
+ // STUB: Not implemented yet
211
+ theme: void 0
212
+ // STUB: Not implemented yet
213
+ }),
214
+ performance: Object.freeze({
215
+ lazyLoad: void 0,
216
+ // STUB: Not implemented yet
217
+ preloadCount: void 0,
218
+ // STUB: Not implemented yet
219
+ imageQuality: void 0
220
+ // STUB: Not implemented yet
221
+ })
222
+ }),
223
+ // Image styling
224
+ styling: Tt
225
+ });
226
+ function Z(n, t) {
227
+ if (!n) return t || {};
228
+ if (!t) return { ...n };
229
+ const e = { ...n };
230
+ return t.border !== void 0 && (e.border = { ...n.border, ...t.border }), t.borderTop !== void 0 && (e.borderTop = { ...n.borderTop, ...t.borderTop }), t.borderRight !== void 0 && (e.borderRight = { ...n.borderRight, ...t.borderRight }), t.borderBottom !== void 0 && (e.borderBottom = { ...n.borderBottom, ...t.borderBottom }), t.borderLeft !== void 0 && (e.borderLeft = { ...n.borderLeft, ...t.borderLeft }), t.filter !== void 0 && (e.filter = { ...n.filter, ...t.filter }), t.outline !== void 0 && (e.outline = { ...n.outline, ...t.outline }), t.shadow !== void 0 && (e.shadow = t.shadow), t.opacity !== void 0 && (e.opacity = t.opacity), t.cursor !== void 0 && (e.cursor = t.cursor), t.className !== void 0 && (e.className = t.className), t.objectFit !== void 0 && (e.objectFit = t.objectFit), t.aspectRatio !== void 0 && (e.aspectRatio = t.aspectRatio), t.borderRadiusTopLeft !== void 0 && (e.borderRadiusTopLeft = t.borderRadiusTopLeft), t.borderRadiusTopRight !== void 0 && (e.borderRadiusTopRight = t.borderRadiusTopRight), t.borderRadiusBottomRight !== void 0 && (e.borderRadiusBottomRight = t.borderRadiusBottomRight), t.borderRadiusBottomLeft !== void 0 && (e.borderRadiusBottomLeft = t.borderRadiusBottomLeft), e;
231
+ }
232
+ function kt(n, t) {
233
+ if (!t) return { ...n };
234
+ const e = Z(n.default, t.default), i = Z(
235
+ Z(e, n.hover),
236
+ t.hover
237
+ ), o = Z(
238
+ Z(e, n.focused),
239
+ t.focused
240
+ );
241
+ return {
242
+ default: e,
243
+ hover: i,
244
+ focused: o
245
+ };
246
+ }
247
+ function Wt(n, t) {
248
+ if (!t) return { ...n };
249
+ const e = { ...n };
250
+ if (t.sizing !== void 0 && (e.sizing = {
251
+ ...n.sizing,
252
+ ...t.sizing
253
+ }, t.sizing.variance)) {
254
+ const i = t.sizing.variance, o = i.min !== void 0 && i.min >= 0.25 && i.min <= 1 ? i.min : n.sizing?.variance?.min ?? 1, s = i.max !== void 0 && i.max >= 1 && i.max <= 1.75 ? i.max : n.sizing?.variance?.max ?? 1;
255
+ e.sizing.variance = { min: o, max: s };
256
+ }
257
+ if (t.rotation !== void 0 && (e.rotation = {
258
+ ...n.rotation,
259
+ ...t.rotation
260
+ }, t.rotation.range)) {
261
+ const i = t.rotation.range, o = i.min !== void 0 && i.min >= -180 && i.min <= 0 ? i.min : n.rotation?.range?.min ?? -15, s = i.max !== void 0 && i.max >= 0 && i.max <= 180 ? i.max : n.rotation?.range?.max ?? 15;
262
+ e.rotation.range = { min: o, max: s };
263
+ }
264
+ return e;
265
+ }
266
+ function Gt(n) {
267
+ const t = n.layout?.rotation;
268
+ if (t && "enabled" in t)
269
+ return {
270
+ rotation: {
271
+ mode: t.enabled ? "random" : "none",
272
+ range: t.range
273
+ }
274
+ };
275
+ }
276
+ function qt(n) {
277
+ const t = n.layout?.sizing?.variance;
278
+ if (t)
279
+ return {
280
+ sizing: {
281
+ mode: "adaptive",
282
+ // Legacy variance config implies adaptive mode
283
+ variance: t
284
+ }
285
+ };
286
+ }
287
+ function Yt(n = {}) {
288
+ const t = Gt(n), e = qt(n);
289
+ let i = n.image;
290
+ (t || e) && (i = {
291
+ ...e || {},
292
+ ...t || {},
293
+ ...i
294
+ }, i.rotation && t?.rotation && n.image?.rotation && (i.rotation = {
295
+ ...t.rotation,
296
+ ...n.image.rotation
297
+ }));
298
+ const o = [...n.loaders ?? []];
299
+ n.images && n.images.length > 0 && o.unshift({
300
+ static: {
301
+ sources: [{ urls: n.images }]
302
+ }
303
+ });
304
+ const r = {
305
+ loaders: {
306
+ ...Rt,
307
+ ...n.config?.loaders ?? {}
308
+ }
309
+ }, a = {
310
+ loaders: o,
311
+ config: r,
312
+ image: Wt(Ct, i),
313
+ layout: { ...y.layout },
314
+ animation: { ...y.animation },
315
+ interaction: { ...y.interaction },
316
+ rendering: { ...y.rendering },
317
+ styling: kt(Tt, n.styling)
318
+ };
319
+ return n.layout && (a.layout = {
320
+ ...y.layout,
321
+ ...n.layout
322
+ }, n.layout.responsive && (a.layout.responsive = {
323
+ ...y.layout.responsive,
324
+ mobile: n.layout.responsive.mobile ? { ...y.layout.responsive.mobile, ...n.layout.responsive.mobile } : y.layout.responsive.mobile,
325
+ tablet: n.layout.responsive.tablet ? { ...y.layout.responsive.tablet, ...n.layout.responsive.tablet } : y.layout.responsive.tablet
326
+ }), n.layout.spacing && (a.layout.spacing = {
327
+ ...y.layout.spacing,
328
+ ...n.layout.spacing
329
+ })), n.animation && (a.animation = {
330
+ ...y.animation,
331
+ ...n.animation
332
+ }, n.animation.easing && (a.animation.easing = {
333
+ ...y.animation.easing,
334
+ ...n.animation.easing
335
+ }), n.animation.queue && (a.animation.queue = {
336
+ ...y.animation.queue,
337
+ ...n.animation.queue
338
+ }), n.animation.performance && (a.animation.performance = {
339
+ ...y.animation.performance,
340
+ ...n.animation.performance
341
+ }), n.animation.entry && (a.animation.entry = {
342
+ ...y.animation.entry,
343
+ ...n.animation.entry,
344
+ start: n.animation.entry.start ? {
345
+ ...y.animation.entry.start,
346
+ ...n.animation.entry.start,
347
+ circular: n.animation.entry.start.circular ? { ...y.animation.entry.start.circular, ...n.animation.entry.start.circular } : y.animation.entry.start.circular
348
+ } : y.animation.entry.start,
349
+ timing: n.animation.entry.timing ? { ...y.animation.entry.timing, ...n.animation.entry.timing } : y.animation.entry.timing,
350
+ path: n.animation.entry.path ? { ...pt, ...n.animation.entry.path } : y.animation.entry.path,
351
+ rotation: n.animation.entry.rotation ? { ...bt, ...n.animation.entry.rotation } : y.animation.entry.rotation,
352
+ scale: n.animation.entry.scale ? { ...yt, ...n.animation.entry.scale } : y.animation.entry.scale
353
+ })), n.interaction && (a.interaction = {
354
+ ...y.interaction,
355
+ ...n.interaction
356
+ }, n.interaction.focus && (a.interaction.focus = {
357
+ ...y.interaction.focus,
358
+ ...n.interaction.focus
359
+ }), n.interaction.navigation && (a.interaction.navigation = {
360
+ ...y.interaction.navigation,
361
+ ...n.interaction.navigation
362
+ }), n.interaction.gestures && (a.interaction.gestures = {
363
+ ...y.interaction.gestures,
364
+ ...n.interaction.gestures
365
+ })), n.rendering && (a.rendering = {
366
+ ...y.rendering,
367
+ ...n.rendering
368
+ }, n.rendering.responsive && (a.rendering.responsive = {
369
+ ...y.rendering.responsive,
370
+ ...n.rendering.responsive,
371
+ breakpoints: n.rendering.responsive.breakpoints ? { ...y.rendering.responsive.breakpoints, ...n.rendering.responsive.breakpoints } : y.rendering.responsive.breakpoints,
372
+ mobileDetection: n.rendering.responsive.mobileDetection ? n.rendering.responsive.mobileDetection : y.rendering.responsive.mobileDetection
373
+ }), n.rendering.ui && (a.rendering.ui = {
374
+ ...y.rendering.ui,
375
+ ...n.rendering.ui
376
+ }), n.rendering.performance && (a.rendering.performance = {
377
+ ...y.rendering.performance,
378
+ ...n.rendering.performance
379
+ })), a.config.debug = {
380
+ ...Mt,
381
+ ...n.config?.debug ?? {}
382
+ }, a;
383
+ }
384
+ function Xt(n, t) {
385
+ return { ...n ? xt[n] : xt.playful, ...t };
386
+ }
387
+ function Bt(n, t) {
388
+ return { ...n ? St[n] : St.gentle, ...t };
389
+ }
390
+ function Jt(n, t) {
391
+ return { ...n ? Et[n] : Et.gentle, ...t };
392
+ }
393
+ class Vt {
394
+ constructor(t) {
395
+ this.activeAnimations = /* @__PURE__ */ new Map(), this.animationIdCounter = 0, this.config = t;
396
+ }
397
+ /**
398
+ * Build transform string from transform params
399
+ * Always starts with centering transform to match image positioning system
400
+ */
401
+ buildTransformString(t) {
402
+ const e = ["translate(-50%, -50%)"];
403
+ if (t.x !== void 0 || t.y !== void 0) {
404
+ const i = t.x ?? 0, o = t.y ?? 0;
405
+ e.push(`translate(${i}px, ${o}px)`);
406
+ }
407
+ return t.rotation !== void 0 && e.push(`rotate(${t.rotation}deg)`), t.scale !== void 0 && e.push(`scale(${t.scale})`), e.join(" ");
408
+ }
409
+ /**
410
+ * Start a cancellable transform animation using Web Animations API
411
+ * @param element - The element to animate
412
+ * @param from - Starting transform state
413
+ * @param to - Ending transform state
414
+ * @param duration - Animation duration in ms (optional)
415
+ * @param easing - CSS easing function (optional)
416
+ * @returns AnimationHandle that can be used to cancel or query the animation
417
+ */
418
+ animateTransformCancellable(t, e, i, o = null, s = null) {
419
+ this.cancelAllAnimations(t);
420
+ const r = o ?? this.config.duration, a = s ?? this.config.easing.default, h = this.buildTransformString(e), c = this.buildTransformString(i);
421
+ t.style.transition = "none";
422
+ const u = t.animate(
423
+ [
424
+ { transform: h },
425
+ { transform: c }
426
+ ],
427
+ {
428
+ duration: r,
429
+ easing: a,
430
+ fill: "forwards"
431
+ // Keep final state after animation
432
+ }
433
+ ), l = {
434
+ id: `anim-${++this.animationIdCounter}`,
435
+ element: t,
436
+ animation: u,
437
+ fromState: e,
438
+ toState: i,
439
+ startTime: performance.now(),
440
+ duration: r
441
+ };
442
+ return this.activeAnimations.set(t, l), u.finished.then(() => {
443
+ t.style.transform = c, this.activeAnimations.delete(t);
444
+ }).catch(() => {
445
+ this.activeAnimations.delete(t);
446
+ }), l;
447
+ }
448
+ /**
449
+ * Cancel an active animation
450
+ * @param handle - The animation handle to cancel
451
+ * @param commitStyle - If true, keeps current position; if false, no style change
452
+ * @returns Snapshot of where the animation was when cancelled
453
+ */
454
+ cancelAnimation(t, e = !0) {
455
+ const i = this.getCurrentTransform(t.element);
456
+ if (t.animation.cancel(), e) {
457
+ const o = this.buildTransformString({
458
+ x: i.x,
459
+ y: i.y,
460
+ rotation: i.rotation,
461
+ scale: i.scale
462
+ });
463
+ t.element.style.transform = o;
464
+ }
465
+ return this.activeAnimations.delete(t.element), i;
466
+ }
467
+ /**
468
+ * Cancel all animations on an element
469
+ * Uses Web Animations API to find and cancel ALL animations, not just tracked ones
470
+ * @param element - The element to cancel animations for
471
+ */
472
+ cancelAllAnimations(t) {
473
+ const e = this.activeAnimations.get(t);
474
+ e && this.cancelAnimation(e, !1);
475
+ const i = t.getAnimations();
476
+ for (const o of i)
477
+ o.cancel();
478
+ }
479
+ /**
480
+ * Get current transform state of an element (works mid-animation)
481
+ * Uses DOMMatrix to parse the computed transform
482
+ * @param element - The element to query
483
+ * @returns Current transform snapshot
484
+ */
485
+ getCurrentTransform(t) {
486
+ const i = getComputedStyle(t).transform;
487
+ if (i === "none" || !i)
488
+ return { x: 0, y: 0, rotation: 0, scale: 1 };
489
+ const o = new DOMMatrix(i), s = Math.sqrt(o.a * o.a + o.b * o.b), r = Math.atan2(o.b, o.a) * (180 / Math.PI), a = o.e, h = o.f;
490
+ return { x: a, y: h, rotation: r, scale: s };
491
+ }
492
+ /**
493
+ * Check if an element has an active animation
494
+ * @param element - The element to check
495
+ * @returns True if animation is in progress
496
+ */
497
+ hasActiveAnimation(t) {
498
+ return this.activeAnimations.has(t);
499
+ }
500
+ /**
501
+ * Get animation handle for an element if it exists
502
+ * @param element - The element to query
503
+ * @returns AnimationHandle or undefined
504
+ */
505
+ getAnimationHandle(t) {
506
+ return this.activeAnimations.get(t);
507
+ }
508
+ /**
509
+ * Animate element transform with smooth easing (CSS transitions - legacy method)
510
+ * @param element - The element to animate
511
+ * @param properties - Transform properties {x, y, rotation, scale}
512
+ * @param duration - Animation duration in ms (optional)
513
+ * @param easing - CSS easing function (optional)
514
+ * @returns Promise that resolves when animation completes
515
+ */
516
+ animateTransform(t, e, i = null, o = null) {
517
+ return new Promise((s) => {
518
+ const r = i ?? this.config.duration, a = o ?? this.config.easing.default;
519
+ t.style.transition = `transform ${r}ms ${a}, box-shadow ${r}ms ${a}`, t.style.transform = this.buildTransformString(e), setTimeout(() => {
520
+ s();
521
+ }, r);
522
+ });
523
+ }
524
+ /**
525
+ * Reset element to its original transform
526
+ * @param element - The element to reset
527
+ * @param originalState - Original transform state {x, y, rotation, scale}
528
+ * @returns Promise that resolves when animation completes
529
+ */
530
+ resetTransform(t, e) {
531
+ return this.animateTransform(t, e);
532
+ }
533
+ /**
534
+ * Remove transition styles from element
535
+ * @param element - The element to clear
536
+ */
537
+ clearTransition(t) {
538
+ t.style.transition = "";
539
+ }
540
+ /**
541
+ * Utility: Wait for a specified duration
542
+ * @param ms - Milliseconds to wait
543
+ * @returns Promise that resolves after the specified duration
544
+ */
545
+ wait(t) {
546
+ return new Promise((e) => setTimeout(e, t));
547
+ }
548
+ }
549
+ function V(n, t, e) {
550
+ return n + (t - n) * e;
551
+ }
552
+ function Kt(n, t, e, i) {
553
+ const { overshoot: o, bounces: s, decayRatio: r } = i, a = e.x - t.x, h = e.y - t.y, c = Zt(s, r);
554
+ let u = 0, l = 0, d = 1, m = o, b = !1;
555
+ for (let g = 0; g < c.length; g++)
556
+ if (n <= c[g].time) {
557
+ l = g === 0 ? 0 : c[g - 1].time, d = c[g].time, m = c[g].overshoot, b = c[g].isOvershoot;
558
+ break;
559
+ }
560
+ const p = (n - l) / (d - l);
561
+ if (b)
562
+ u = 1 + m * ot(p);
563
+ else if (l === 0)
564
+ u = ot(p);
565
+ else {
566
+ const f = 1 + (c.find(
567
+ (S, v) => S.time > l && v > 0 && c[v - 1].isOvershoot
568
+ )?.overshoot || m);
569
+ u = V(f, 1, ot(p));
570
+ }
571
+ return {
572
+ x: t.x + a * u,
573
+ y: t.y + h * u
574
+ };
575
+ }
576
+ function Zt(n, t) {
577
+ const e = [];
578
+ let i = 0.6;
579
+ e.push({ time: i, overshoot: 0, isOvershoot: !1 });
580
+ let o = 0.15;
581
+ const r = 0.4 / (n * 2);
582
+ for (let a = 0; a < n; a++)
583
+ i += r, e.push({ time: i, overshoot: o, isOvershoot: !0 }), i += r, e.push({ time: i, overshoot: o * t, isOvershoot: !1 }), o *= t;
584
+ return e.push({ time: 1, overshoot: 0, isOvershoot: !1 }), e;
585
+ }
586
+ function Qt(n, t, e, i) {
587
+ const { stiffness: o, damping: s, mass: r, oscillations: a } = i, h = e.x - t.x, c = e.y - t.y, u = Math.sqrt(o / r), l = s / (2 * Math.sqrt(o * r));
588
+ let d;
589
+ if (l < 1) {
590
+ const m = u * Math.sqrt(1 - l * l), b = Math.exp(-l * u * n * 3), p = Math.cos(m * n * a * Math.PI);
591
+ d = 1 - b * p;
592
+ } else
593
+ d = 1 - Math.exp(-u * n * 3);
594
+ return d = Math.max(0, Math.min(d, 1.3)), {
595
+ x: t.x + h * d,
596
+ y: t.y + c * d
597
+ };
598
+ }
599
+ function te(n, t, e, i) {
600
+ const { amplitude: o, frequency: s, decay: r, decayRate: a, phase: h } = i, c = e.x - t.x, u = e.y - t.y, l = Math.sqrt(c * c + u * u), d = l > 0 ? -u / l : 0, m = l > 0 ? c / l : 1, b = s * Math.PI * 2 * n + h, p = r ? Math.pow(1 - n, a) : 1, g = o * Math.sin(b) * p, f = ee(n);
601
+ return {
602
+ x: V(t.x, e.x, f) + g * d,
603
+ y: V(t.y, e.y, f) + g * m
604
+ };
605
+ }
606
+ function ot(n) {
607
+ return 1 - (1 - n) * (1 - n);
608
+ }
609
+ function ee(n) {
610
+ return 1 - Math.pow(1 - n, 3);
611
+ }
612
+ function ie(n, t, e) {
613
+ const { amplitude: i, frequency: o, decay: s } = e, r = Math.sin(n * o * Math.PI * 2), a = s ? Math.pow(1 - n, 2) : 1, h = i * r * a;
614
+ return t + h;
615
+ }
616
+ function ne(n, t, e) {
617
+ const { overshoot: i, bounces: o } = e, s = [];
618
+ s.push({ time: 0.5, scale: i });
619
+ let r = i;
620
+ const a = 0.5, c = 0.5 / (o * 2);
621
+ let u = 0.5;
622
+ for (let d = 0; d < o; d++) {
623
+ const m = 1 - (r - 1) * a;
624
+ u += c, s.push({ time: u, scale: m }), r = 1 + (r - 1) * a * a, u += c, d < o - 1 && s.push({ time: u, scale: r });
625
+ }
626
+ s.push({ time: 1, scale: 1 });
627
+ let l = 1;
628
+ for (let d = 0; d < s.length; d++)
629
+ if (n <= s[d].time) {
630
+ const m = d === 0 ? 0 : s[d - 1].time, b = d === 0 ? 1 : s[d - 1].scale, p = (n - m) / (s[d].time - m), g = ot(p);
631
+ l = b + (s[d].scale - b) * g;
632
+ break;
633
+ }
634
+ return l * t;
635
+ }
636
+ function oe(n) {
637
+ const {
638
+ element: t,
639
+ startPosition: e,
640
+ endPosition: i,
641
+ pathConfig: o,
642
+ duration: s,
643
+ imageWidth: r,
644
+ imageHeight: a,
645
+ rotation: h,
646
+ scale: c,
647
+ onComplete: u,
648
+ rotationConfig: l,
649
+ startRotation: d,
650
+ scaleConfig: m,
651
+ startScale: b
652
+ } = n, p = o.type, g = d !== void 0 && d !== h, f = l?.mode === "wobble", S = l?.wobble || { amplitude: 15, frequency: 3, decay: !0 }, v = g || f, w = b !== void 0 && b !== c, E = m?.mode === "pop", x = m?.pop || { overshoot: 1.2, bounces: 1 };
653
+ if ((p === "linear" || p === "arc") && !v && !(w || E)) {
654
+ u && u();
655
+ return;
656
+ }
657
+ const L = performance.now(), z = -r / 2, P = -a / 2;
658
+ function _(Y) {
659
+ const N = Y - L, T = Math.min(N / s, 1);
660
+ let D;
661
+ switch (p) {
662
+ case "bounce": {
663
+ const O = Xt(
664
+ o.bouncePreset,
665
+ o.bounce
666
+ );
667
+ D = Kt(T, e, i, O);
668
+ break;
669
+ }
670
+ case "elastic": {
671
+ const O = Bt(
672
+ o.elasticPreset,
673
+ o.elastic
674
+ );
675
+ D = Qt(T, e, i, O);
676
+ break;
677
+ }
678
+ case "wave": {
679
+ const O = Jt(
680
+ o.wavePreset,
681
+ o.wave
682
+ );
683
+ D = te(T, e, i, O);
684
+ break;
685
+ }
686
+ default:
687
+ D = {
688
+ x: V(e.x, i.x, T),
689
+ y: V(e.y, i.y, T)
690
+ };
691
+ }
692
+ const k = D.x - i.x, H = D.y - i.y;
693
+ let R;
694
+ f ? R = ie(T, h, S) : g ? R = V(d, h, T) : R = h;
695
+ let C;
696
+ E ? C = ne(T, c, x) : w ? C = V(b, c, T) : C = c, t.style.transform = `translate(${z}px, ${P}px) translate(${k}px, ${H}px) rotate(${R}deg) scale(${C})`, T < 1 ? requestAnimationFrame(_) : (t.style.transform = `translate(${z}px, ${P}px) rotate(${h}deg) scale(${c})`, u && u());
697
+ }
698
+ requestAnimationFrame(_);
699
+ }
700
+ function se(n) {
701
+ return n === "bounce" || n === "elastic" || n === "wave";
702
+ }
703
+ const ae = {
704
+ radial: "center",
705
+ spiral: "center",
706
+ grid: "top",
707
+ cluster: "nearest-edge",
708
+ random: "nearest-edge",
709
+ wave: "left"
710
+ };
711
+ class re {
712
+ constructor(t, e) {
713
+ this.config = t, this.layoutAlgorithm = e, this.resolvedStartPosition = this.resolveStartPosition(), this.pathConfig = t.path || pt, this.rotationConfig = t.rotation || bt, this.scaleConfig = t.scale || yt;
714
+ }
715
+ /**
716
+ * Get the effective start position, considering layout-aware defaults
717
+ */
718
+ resolveStartPosition() {
719
+ return this.config.start.position ? this.config.start.position : ae[this.layoutAlgorithm] || "nearest-edge";
720
+ }
721
+ /**
722
+ * Calculate the starting position for an image's entry animation
723
+ */
724
+ calculateStartPosition(t, e, i, o, s) {
725
+ const r = this.resolvedStartPosition, a = this.config.start.offset ?? 100;
726
+ switch (r) {
727
+ case "nearest-edge":
728
+ return this.calculateNearestEdge(t, e, i, a);
729
+ case "top":
730
+ return this.calculateEdgePosition("top", t, e, i, a);
731
+ case "bottom":
732
+ return this.calculateEdgePosition("bottom", t, e, i, a);
733
+ case "left":
734
+ return this.calculateEdgePosition("left", t, e, i, a);
735
+ case "right":
736
+ return this.calculateEdgePosition("right", t, e, i, a);
737
+ case "center":
738
+ return this.calculateCenterPosition(i, t, e);
739
+ case "random-edge":
740
+ return this.calculateRandomEdge(t, e, i, a);
741
+ case "circular":
742
+ return this.calculateCircularPosition(
743
+ t,
744
+ e,
745
+ i,
746
+ o,
747
+ s
748
+ );
749
+ default:
750
+ return this.calculateNearestEdge(t, e, i, a);
751
+ }
752
+ }
753
+ /**
754
+ * Calculate start position from the nearest edge (current default behavior)
755
+ */
756
+ calculateNearestEdge(t, e, i, o) {
757
+ const s = t.x, r = t.y, a = s, h = i.width - s, c = r, u = i.height - r, l = Math.min(a, h, c, u);
758
+ let d = t.x, m = t.y;
759
+ return l === a ? d = -(e.width + o) : l === h ? d = i.width + o : l === c ? m = -(e.height + o) : m = i.height + o, { x: d, y: m };
760
+ }
761
+ /**
762
+ * Calculate start position from a specific edge
763
+ */
764
+ calculateEdgePosition(t, e, i, o, s) {
765
+ let r = e.x, a = e.y;
766
+ switch (t) {
767
+ case "top":
768
+ a = -(i.height + s);
769
+ break;
770
+ case "bottom":
771
+ a = o.height + s;
772
+ break;
773
+ case "left":
774
+ r = -(i.width + s);
775
+ break;
776
+ case "right":
777
+ r = o.width + s;
778
+ break;
779
+ }
780
+ return { x: r, y: a };
781
+ }
782
+ /**
783
+ * Calculate start position from center with scale animation
784
+ */
785
+ calculateCenterPosition(t, e, i) {
786
+ const o = t.width / 2, s = t.height / 2;
787
+ return {
788
+ x: o,
789
+ y: s,
790
+ useScale: !0
791
+ // Signal to use scale animation from 0
792
+ };
793
+ }
794
+ /**
795
+ * Calculate start position from a random edge
796
+ */
797
+ calculateRandomEdge(t, e, i, o) {
798
+ const s = ["top", "bottom", "left", "right"], r = s[Math.floor(Math.random() * s.length)];
799
+ return this.calculateEdgePosition(r, t, e, i, o);
800
+ }
801
+ /**
802
+ * Calculate start position on a circle around the container
803
+ */
804
+ calculateCircularPosition(t, e, i, o, s) {
805
+ const r = this.config.start.circular || {}, a = r.distribution || "even";
806
+ let h;
807
+ const c = r.radius || "120%";
808
+ if (typeof c == "string" && c.endsWith("%")) {
809
+ const p = parseFloat(c) / 100;
810
+ h = Math.sqrt(
811
+ i.width ** 2 + i.height ** 2
812
+ ) * p / 2;
813
+ } else
814
+ h = typeof c == "number" ? c : 500;
815
+ let u;
816
+ a === "even" ? u = o / s * 2 * Math.PI : u = Math.random() * 2 * Math.PI;
817
+ const l = i.width / 2, d = i.height / 2, m = l + Math.cos(u) * h, b = d + Math.sin(u) * h;
818
+ return { x: m, y: b };
819
+ }
820
+ /**
821
+ * Get animation parameters for an image
822
+ */
823
+ getAnimationParams(t) {
824
+ const e = this.config.timing.duration, i = this.config.easing;
825
+ return {
826
+ startTransform: "",
827
+ // Will be computed by caller based on start position
828
+ duration: e,
829
+ delay: 0,
830
+ easing: i
831
+ };
832
+ }
833
+ /**
834
+ * Build a CSS transform string for the start position
835
+ * Uses pixel-based centering offset for reliable cross-browser behavior
836
+ */
837
+ buildStartTransform(t, e, i, o, s, r, a, h) {
838
+ const c = t.x - e.x, u = t.y - e.y, l = a !== void 0 ? a : i, d = h !== void 0 ? h : o, m = s !== void 0 ? -s / 2 : 0, b = r !== void 0 ? -r / 2 : 0, p = s !== void 0 ? `translate(${m}px, ${b}px)` : "translate(-50%, -50%)";
839
+ return t.useScale ? `${p} translate(${c}px, ${u}px) rotate(${l}deg) scale(0)` : `${p} translate(${c}px, ${u}px) rotate(${l}deg) scale(${d})`;
840
+ }
841
+ /**
842
+ * Build the final CSS transform string
843
+ * Uses pixel-based centering offset for reliable cross-browser behavior
844
+ */
845
+ buildFinalTransform(t, e, i, o) {
846
+ if (i !== void 0 && o !== void 0) {
847
+ const s = -i / 2, r = -o / 2;
848
+ return `translate(${s}px, ${r}px) rotate(${t}deg) scale(${e})`;
849
+ }
850
+ return `translate(-50%, -50%) rotate(${t}deg) scale(${e})`;
851
+ }
852
+ /**
853
+ * Get the transition CSS for entry animation
854
+ * For JS-animated paths, only animate opacity (transform handled by JS)
855
+ */
856
+ getTransitionCSS() {
857
+ const t = this.config.timing.duration, e = this.config.easing;
858
+ return this.requiresJSAnimation() ? `opacity ${t}ms ease-out` : `opacity ${t}ms ease-out, transform ${t}ms ${e}`;
859
+ }
860
+ /**
861
+ * Check if the current path type requires JavaScript animation
862
+ */
863
+ requiresJSAnimation() {
864
+ return se(this.pathConfig.type);
865
+ }
866
+ /**
867
+ * Get the path configuration
868
+ */
869
+ getPathConfig() {
870
+ return this.pathConfig;
871
+ }
872
+ /**
873
+ * Get the path type
874
+ */
875
+ getPathType() {
876
+ return this.pathConfig.type;
877
+ }
878
+ /**
879
+ * Get animation timing configuration
880
+ */
881
+ getTiming() {
882
+ return {
883
+ duration: this.config.timing.duration
884
+ };
885
+ }
886
+ /**
887
+ * Get the rotation configuration
888
+ */
889
+ getRotationConfig() {
890
+ return this.rotationConfig;
891
+ }
892
+ /**
893
+ * Get the rotation mode
894
+ */
895
+ getRotationMode() {
896
+ return this.rotationConfig.mode;
897
+ }
898
+ /**
899
+ * Calculate the starting rotation for an entry animation
900
+ * @param finalRotation - The final rotation from the layout
901
+ * @returns The starting rotation in degrees
902
+ */
903
+ calculateStartRotation(t) {
904
+ switch (this.rotationConfig.mode) {
905
+ case "none":
906
+ return t;
907
+ case "settle": {
908
+ const i = this.rotationConfig.startRotation;
909
+ if (i === void 0)
910
+ return t + (Math.random() - 0.5) * 60;
911
+ if (typeof i == "number")
912
+ return i;
913
+ const o = i.max - i.min;
914
+ return i.min + Math.random() * o;
915
+ }
916
+ case "spin": {
917
+ const i = this.rotationConfig.spinCount ?? 1, o = this.resolveSpinDirection(t);
918
+ return t + i * 360 * o;
919
+ }
920
+ case "random":
921
+ return t + (Math.random() - 0.5) * 60;
922
+ case "wobble":
923
+ return t;
924
+ default:
925
+ return t;
926
+ }
927
+ }
928
+ /**
929
+ * Resolve spin direction based on config
930
+ * @returns 1 for clockwise, -1 for counterclockwise
931
+ */
932
+ resolveSpinDirection(t) {
933
+ switch (this.rotationConfig.direction ?? "auto") {
934
+ case "clockwise":
935
+ return -1;
936
+ // Negative rotation = clockwise spin to final
937
+ case "counterclockwise":
938
+ return 1;
939
+ // Positive rotation = counterclockwise spin to final
940
+ case "random":
941
+ return Math.random() < 0.5 ? 1 : -1;
942
+ default:
943
+ return t >= 0 ? 1 : -1;
944
+ }
945
+ }
946
+ /**
947
+ * Check if the current rotation mode requires JavaScript animation
948
+ * (as opposed to CSS transitions)
949
+ */
950
+ requiresJSRotation() {
951
+ return this.rotationConfig.mode === "wobble";
952
+ }
953
+ /**
954
+ * Calculate wobble rotation for a given animation progress
955
+ * @param progress - Animation progress from 0 to 1
956
+ * @param finalRotation - The final rotation in degrees
957
+ * @returns The current rotation in degrees
958
+ */
959
+ calculateWobbleRotation(t, e) {
960
+ if (this.rotationConfig.mode !== "wobble")
961
+ return e;
962
+ const i = this.rotationConfig.wobble || {
963
+ amplitude: 15,
964
+ frequency: 3,
965
+ decay: !0
966
+ }, { amplitude: o, frequency: s, decay: r } = i, a = Math.sin(t * s * Math.PI * 2), h = r ? Math.pow(1 - t, 2) : 1, c = o * a * h;
967
+ return e + c;
968
+ }
969
+ /**
970
+ * Get the scale configuration
971
+ */
972
+ getScaleConfig() {
973
+ return this.scaleConfig;
974
+ }
975
+ /**
976
+ * Get the scale mode
977
+ */
978
+ getScaleMode() {
979
+ return this.scaleConfig.mode;
980
+ }
981
+ /**
982
+ * Calculate the starting scale for an entry animation
983
+ * @param finalScale - The final scale from the layout
984
+ * @returns The starting scale
985
+ */
986
+ calculateStartScale(t) {
987
+ switch (this.scaleConfig.mode) {
988
+ case "none":
989
+ return t;
990
+ case "grow":
991
+ return (this.scaleConfig.startScale ?? 0.3) * t;
992
+ case "shrink":
993
+ return (this.scaleConfig.startScale ?? 1.5) * t;
994
+ case "pop":
995
+ return t;
996
+ case "random": {
997
+ const i = this.scaleConfig.range ?? { min: 0.5, max: 1 };
998
+ return (i.min + Math.random() * (i.max - i.min)) * t;
999
+ }
1000
+ default:
1001
+ return t;
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Check if the current scale mode requires JavaScript animation
1006
+ * (as opposed to CSS transitions)
1007
+ */
1008
+ requiresJSScale() {
1009
+ return this.scaleConfig.mode === "pop";
1010
+ }
1011
+ /**
1012
+ * Calculate pop scale for a given animation progress
1013
+ * @param progress - Animation progress from 0 to 1
1014
+ * @param finalScale - The final scale value
1015
+ * @returns The current scale value with bounce effect
1016
+ */
1017
+ calculatePopScale(t, e) {
1018
+ if (this.scaleConfig.mode !== "pop")
1019
+ return e;
1020
+ const i = this.scaleConfig.pop || {
1021
+ overshoot: 1.2,
1022
+ bounces: 1
1023
+ }, { overshoot: o, bounces: s } = i, r = this.generateScaleBounceKeyframes(s, o);
1024
+ let a = e;
1025
+ for (let h = 0; h < r.length; h++)
1026
+ if (t <= r[h].time) {
1027
+ const c = h === 0 ? 0 : r[h - 1].time, u = h === 0 ? e : r[h - 1].scale, l = (t - c) / (r[h].time - c), d = this.easeOutQuad(l);
1028
+ a = u + (r[h].scale - u) * d;
1029
+ break;
1030
+ }
1031
+ return a * e;
1032
+ }
1033
+ /**
1034
+ * Generate keyframes for scale bounce animation
1035
+ */
1036
+ generateScaleBounceKeyframes(t, e) {
1037
+ const i = [];
1038
+ i.push({ time: 0.5, scale: e });
1039
+ let o = e;
1040
+ const s = 0.5, a = 0.5 / (t * 2);
1041
+ let h = 0.5;
1042
+ for (let c = 0; c < t; c++) {
1043
+ const u = 1 - (o - 1) * s;
1044
+ h += a, i.push({ time: h, scale: u }), o = 1 + (o - 1) * s * s, h += a, c < t - 1 && i.push({ time: h, scale: o });
1045
+ }
1046
+ return i.push({ time: 1, scale: 1 }), i;
1047
+ }
1048
+ /**
1049
+ * Easing function for smooth transitions
1050
+ */
1051
+ easeOutQuad(t) {
1052
+ return 1 - (1 - t) * (1 - t);
1053
+ }
1054
+ }
1055
+ class ce {
1056
+ constructor(t, e = {}) {
1057
+ this.config = t, this.imageConfig = e;
1058
+ }
1059
+ /**
1060
+ * Generate random layout positions for images
1061
+ * @param imageCount - Number of images to layout
1062
+ * @param containerBounds - Container dimensions {width, height}
1063
+ * @param options - Optional overrides (includes fixedHeight)
1064
+ * @returns Array of layout objects with position, rotation, scale
1065
+ */
1066
+ generate(t, e, i = {}) {
1067
+ const o = [], { width: s, height: r } = e, a = this.config.spacing.padding, h = i.fixedHeight ?? 200, c = this.imageConfig.rotation?.mode ?? "none", u = this.imageConfig.rotation?.range?.min ?? -15, l = this.imageConfig.rotation?.range?.max ?? 15, d = this.imageConfig.sizing?.variance?.min ?? 1, m = this.imageConfig.sizing?.variance?.max ?? 1, b = d !== 1 || m !== 1, g = h * 1.5 / 2, f = h / 2, S = s - a - g, v = r - a - f, w = a + g, E = a + f;
1068
+ for (let x = 0; x < t; x++) {
1069
+ const A = this.random(w, S), L = this.random(E, v), z = c === "random" ? this.random(u, l) : 0, P = b ? this.random(d, m) : 1, _ = h * P, Y = {
1070
+ id: x,
1071
+ x: A,
1072
+ y: L,
1073
+ rotation: z,
1074
+ scale: P,
1075
+ baseSize: _
1076
+ };
1077
+ o.push(Y);
1078
+ }
1079
+ return o;
1080
+ }
1081
+ /**
1082
+ * Utility: Generate random number between min and max
1083
+ * @param min - Minimum value
1084
+ * @param max - Maximum value
1085
+ * @returns Random number in range
1086
+ */
1087
+ random(t, e) {
1088
+ return Math.random() * (e - t) + t;
1089
+ }
1090
+ }
1091
+ class le {
1092
+ constructor(t, e = {}) {
1093
+ this.config = t, this.imageConfig = e;
1094
+ }
1095
+ /**
1096
+ * Generate radial layout positions for images
1097
+ * @param imageCount - Number of images to layout
1098
+ * @param containerBounds - Container dimensions {width, height}
1099
+ * @param options - Optional overrides
1100
+ * @returns Array of layout objects with position, rotation, scale
1101
+ */
1102
+ generate(t, e, i = {}) {
1103
+ const o = [], { width: s, height: r } = e, a = i.fixedHeight ?? 200, h = this.imageConfig.rotation?.mode ?? "none", c = this.imageConfig.rotation?.range?.min ?? -15, u = this.imageConfig.rotation?.range?.max ?? 15, l = this.imageConfig.sizing?.variance?.min ?? 1, d = this.imageConfig.sizing?.variance?.max ?? 1, m = l !== 1 || d !== 1, b = this.config.scaleDecay ?? 0, p = i.fixedHeight ?? a, g = s / 2, f = r / 2, S = Math.ceil(Math.sqrt(t));
1104
+ if (t > 0) {
1105
+ const E = m ? this.random(l, d) : 1, x = p * E;
1106
+ o.push({
1107
+ id: 0,
1108
+ x: g,
1109
+ y: f,
1110
+ rotation: h === "random" ? this.random(c * 0.33, u * 0.33) : 0,
1111
+ // Less rotation for center
1112
+ scale: E,
1113
+ baseSize: x,
1114
+ zIndex: 100
1115
+ // Center image is highest
1116
+ });
1117
+ }
1118
+ let v = 1, w = 1;
1119
+ for (; v < t; ) {
1120
+ const E = w / S, x = b > 0 ? 1 - E * b * 0.5 : 1, A = w * (p * 0.8), L = A * 1.5, z = Math.PI * (3 * (L + A) - Math.sqrt((3 * L + A) * (L + 3 * A))), P = this.estimateWidth(p), _ = Math.floor(z / (P * 0.7));
1121
+ if (_ === 0) {
1122
+ w++;
1123
+ continue;
1124
+ }
1125
+ const Y = 2 * Math.PI / _, N = w * (20 * Math.PI / 180);
1126
+ for (let T = 0; T < _ && v < t; T++) {
1127
+ const D = T * Y + N, k = m ? this.random(l, d) : 1, H = x * k, R = p * H;
1128
+ let C = g + Math.cos(D) * L, O = f + Math.sin(D) * A;
1129
+ const $ = this.config.spacing.padding ?? 50, U = R * 1.5 / 2, M = R / 2;
1130
+ C - U < $ ? C = $ + U : C + U > s - $ && (C = s - $ - U), O - M < $ ? O = $ + M : O + M > r - $ && (O = r - $ - M);
1131
+ const W = h === "random" ? this.random(c, u) : 0;
1132
+ o.push({
1133
+ id: v,
1134
+ x: C,
1135
+ y: O,
1136
+ rotation: W,
1137
+ scale: H,
1138
+ baseSize: R,
1139
+ zIndex: Math.max(1, 100 - w)
1140
+ // Outer rings have lower z-index
1141
+ }), v++;
1142
+ }
1143
+ w++;
1144
+ }
1145
+ return o;
1146
+ }
1147
+ /**
1148
+ * Estimate image width based on height
1149
+ * Assumes landscape aspect ratio (approximately 1.4:1)
1150
+ * @param height - Image height
1151
+ * @returns Estimated width
1152
+ */
1153
+ estimateWidth(t) {
1154
+ return t * 1.4;
1155
+ }
1156
+ /**
1157
+ * Utility: Generate random number between min and max
1158
+ * @param min - Minimum value
1159
+ * @param max - Maximum value
1160
+ * @returns Random number in range
1161
+ */
1162
+ random(t, e) {
1163
+ return Math.random() * (e - t) + t;
1164
+ }
1165
+ }
1166
+ const he = {
1167
+ columns: "auto",
1168
+ rows: "auto",
1169
+ stagger: "none",
1170
+ jitter: 0,
1171
+ overlap: 0,
1172
+ fillDirection: "row",
1173
+ alignment: "center",
1174
+ gap: 10,
1175
+ overflowOffset: 0.25
1176
+ }, It = [
1177
+ { x: 1, y: 1 },
1178
+ // bottom-right
1179
+ { x: -1, y: -1 },
1180
+ // upper-left
1181
+ { x: 1, y: -1 },
1182
+ // upper-right
1183
+ { x: -1, y: 1 },
1184
+ // bottom-left
1185
+ { x: -1, y: 0 },
1186
+ // left
1187
+ { x: 1, y: 0 },
1188
+ // right
1189
+ { x: 0, y: -1 },
1190
+ // up
1191
+ { x: 0, y: 1 }
1192
+ // down
1193
+ ];
1194
+ class de {
1195
+ constructor(t, e = {}) {
1196
+ this.config = t, this.imageConfig = e;
1197
+ }
1198
+ /**
1199
+ * Generate grid layout positions for images
1200
+ * @param imageCount - Number of images to layout
1201
+ * @param containerBounds - Container dimensions {width, height}
1202
+ * @param options - Optional overrides (includes fixedHeight)
1203
+ * @returns Array of layout objects with position, rotation, scale
1204
+ */
1205
+ generate(t, e, i = {}) {
1206
+ const o = [], { width: s, height: r } = e, a = { ...he, ...this.config.grid }, h = this.config.spacing.padding, c = i.fixedHeight ?? 200, u = this.imageConfig.rotation?.mode ?? "none", l = this.imageConfig.sizing?.variance?.min ?? 1, d = this.imageConfig.sizing?.variance?.max ?? 1, m = l !== 1 || d !== 1, b = s - 2 * h, p = r - 2 * h, { columns: g, rows: f } = this.calculateGridDimensions(
1207
+ t,
1208
+ b,
1209
+ p,
1210
+ c,
1211
+ a
1212
+ ), S = a.stagger === "row", v = a.stagger === "column", w = S ? g + 0.5 : g, E = v ? f + 0.5 : f, x = (b - a.gap * (g - 1)) / w, A = (p - a.gap * (f - 1)) / E, L = S ? x / 2 : 0, z = v ? A / 2 : 0, P = 1 + a.overlap, _ = Math.min(x, A) * P, Y = i.fixedHeight ? Math.min(i.fixedHeight, _) : _, N = g * x + (g - 1) * a.gap + L, T = f * A + (f - 1) * a.gap + z, D = h + (b - N) / 2, k = h + (p - T) / 2, H = g * f, R = a.columns !== "auto" && a.rows !== "auto", C = R && t > H;
1213
+ typeof window < "u" && (window.__gridOverflowDebug = {
1214
+ gridConfigColumns: a.columns,
1215
+ gridConfigRows: a.rows,
1216
+ columns: g,
1217
+ rows: f,
1218
+ cellCount: H,
1219
+ hasFixedGrid: R,
1220
+ imageCount: t,
1221
+ isOverflowMode: C
1222
+ });
1223
+ const O = C ? new Array(H).fill(0) : [], $ = Math.min(x, A) * a.overflowOffset;
1224
+ for (let F = 0; F < t; F++) {
1225
+ let U, M, W = 0;
1226
+ if (C && F >= H) {
1227
+ const q = F - H, j = q % H;
1228
+ W = Math.floor(q / H) + 1, O[j]++, a.fillDirection === "row" ? (U = j % g, M = Math.floor(j / g)) : (M = j % f, U = Math.floor(j / f));
1229
+ } else
1230
+ a.fillDirection === "row" ? (U = F % g, M = Math.floor(F / g)) : (M = F % f, U = Math.floor(F / f));
1231
+ let G = D + U * (x + a.gap) + x / 2, X = k + M * (A + a.gap) + A / 2;
1232
+ if (a.stagger === "row" && M % 2 === 1 ? G += x / 2 : a.stagger === "column" && U % 2 === 1 && (X += A / 2), W > 0) {
1233
+ const q = (W - 1) % It.length, j = It[q];
1234
+ G += j.x * $, X += j.y * $;
1235
+ }
1236
+ if (a.jitter > 0) {
1237
+ const q = x / 2 * a.jitter, j = A / 2 * a.jitter;
1238
+ G += this.random(-q, q), X += this.random(-j, j);
1239
+ }
1240
+ let B = G, J = X;
1241
+ if (!C && a.fillDirection === "row") {
1242
+ const q = t % g || g;
1243
+ if (M === Math.floor((t - 1) / g) && q < g) {
1244
+ const vt = q * x + (q - 1) * a.gap;
1245
+ let gt = 0;
1246
+ a.alignment === "center" ? gt = (N - vt) / 2 : a.alignment === "end" && (gt = N - vt), B += gt;
1247
+ }
1248
+ }
1249
+ const rt = m ? this.random(l, d) : 1, K = Y * rt, it = K * 1.5 / 2, nt = K / 2, lt = h + it, ht = s - h - it, Ft = h + nt, Dt = r - h - nt;
1250
+ B = Math.max(lt, Math.min(B, ht)), J = Math.max(Ft, Math.min(J, Dt));
1251
+ let dt = 0;
1252
+ if (u === "random") {
1253
+ const q = this.imageConfig.rotation?.range?.min ?? -15, j = this.imageConfig.rotation?.range?.max ?? 15;
1254
+ a.jitter > 0 ? dt = this.random(q * a.jitter, j * a.jitter) : dt = this.random(q, j);
1255
+ }
1256
+ let ut;
1257
+ C && W > 0 ? ut = 50 - W : ut = C ? 100 + F : F + 1, o.push({
1258
+ id: F,
1259
+ x: B,
1260
+ y: J,
1261
+ rotation: dt,
1262
+ scale: rt,
1263
+ baseSize: K,
1264
+ zIndex: ut
1265
+ });
1266
+ }
1267
+ return o;
1268
+ }
1269
+ /**
1270
+ * Calculate optimal grid dimensions based on image count and container
1271
+ */
1272
+ calculateGridDimensions(t, e, i, o, s) {
1273
+ let r, a;
1274
+ if (s.columns !== "auto" && s.rows !== "auto")
1275
+ r = s.columns, a = s.rows;
1276
+ else if (s.columns !== "auto")
1277
+ r = s.columns, a = Math.ceil(t / r);
1278
+ else if (s.rows !== "auto")
1279
+ a = s.rows, r = Math.ceil(t / a);
1280
+ else {
1281
+ const h = e / i;
1282
+ for (r = Math.max(1, Math.round(Math.sqrt(t * h / 1.4))), a = Math.ceil(t / r); r > 1 && (r - 1) * a >= t; )
1283
+ r--;
1284
+ }
1285
+ return { columns: Math.max(1, r), rows: Math.max(1, a) };
1286
+ }
1287
+ /**
1288
+ * Utility: Generate random number between min and max
1289
+ */
1290
+ random(t, e) {
1291
+ return Math.random() * (e - t) + t;
1292
+ }
1293
+ }
1294
+ const ue = Math.PI * (3 - Math.sqrt(5)), ge = {
1295
+ spiralType: "golden",
1296
+ direction: "counterclockwise",
1297
+ tightness: 1,
1298
+ scaleDecay: 0,
1299
+ startAngle: 0
1300
+ };
1301
+ class me {
1302
+ constructor(t, e = {}) {
1303
+ this.config = t, this.imageConfig = e;
1304
+ }
1305
+ /**
1306
+ * Generate spiral layout positions for images
1307
+ * @param imageCount - Number of images to layout
1308
+ * @param containerBounds - Container dimensions {width, height}
1309
+ * @param options - Optional overrides (includes fixedHeight)
1310
+ * @returns Array of layout objects with position, rotation, scale
1311
+ */
1312
+ generate(t, e, i = {}) {
1313
+ const o = [], { width: s, height: r } = e, a = { ...ge, ...this.config.spiral }, h = this.config.spacing.padding, c = i.fixedHeight ?? 200, u = this.imageConfig.rotation?.mode ?? "none", l = this.imageConfig.rotation?.range?.min ?? -15, d = this.imageConfig.rotation?.range?.max ?? 15, m = this.imageConfig.sizing?.variance?.min ?? 1, b = this.imageConfig.sizing?.variance?.max ?? 1, p = m !== 1 || b !== 1, g = this.config.scaleDecay ?? a.scaleDecay, f = s / 2, S = r / 2, v = Math.min(
1314
+ f - h - c / 2,
1315
+ S - h - c / 2
1316
+ ), w = a.direction === "clockwise" ? -1 : 1;
1317
+ for (let E = 0; E < t; E++) {
1318
+ let x, A;
1319
+ if (a.spiralType === "golden")
1320
+ x = E * ue * w + a.startAngle, A = this.calculateGoldenRadius(E, t, v, a.tightness);
1321
+ else if (a.spiralType === "archimedean") {
1322
+ const G = E * 0.5 * a.tightness;
1323
+ x = G * w + a.startAngle, A = this.calculateArchimedeanRadius(G, t, v, a.tightness);
1324
+ } else {
1325
+ const G = E * 0.3 * a.tightness;
1326
+ x = G * w + a.startAngle, A = this.calculateLogarithmicRadius(G, t, v, a.tightness);
1327
+ }
1328
+ const L = f + Math.cos(x) * A, z = S + Math.sin(x) * A, P = A / v, _ = g > 0 ? 1 - P * g * 0.5 : 1, Y = p ? this.random(m, b) : 1, N = _ * Y, T = c * N, k = T * 1.5 / 2, H = T / 2, R = h + k, C = s - h - k, O = h + H, $ = r - h - H, F = Math.max(R, Math.min(L, C)), U = Math.max(O, Math.min(z, $));
1329
+ let M = 0;
1330
+ if (u === "random") {
1331
+ const G = x * 180 / Math.PI % 360, X = this.random(l, d);
1332
+ M = a.spiralType === "golden" ? X : G * 0.1 + X * 0.9;
1333
+ } else u === "tangent" && (M = this.calculateSpiralTangent(x, A, a));
1334
+ const W = t - E;
1335
+ o.push({
1336
+ id: E,
1337
+ x: F,
1338
+ y: U,
1339
+ rotation: M,
1340
+ scale: N,
1341
+ baseSize: T,
1342
+ zIndex: W
1343
+ });
1344
+ }
1345
+ return o;
1346
+ }
1347
+ /**
1348
+ * Calculate tangent angle for spiral curve at given position
1349
+ * This aligns the image along the spiral's direction of travel
1350
+ */
1351
+ calculateSpiralTangent(t, e, i) {
1352
+ let o;
1353
+ if (i.spiralType === "golden")
1354
+ o = t + Math.PI / 2;
1355
+ else if (i.spiralType === "archimedean") {
1356
+ const r = 1 / i.tightness, a = Math.atan(e / r);
1357
+ o = t + a;
1358
+ } else {
1359
+ const r = 0.15 / i.tightness, a = Math.atan(1 / r);
1360
+ o = t + a;
1361
+ }
1362
+ return o * 180 / Math.PI % 360 - 90;
1363
+ }
1364
+ /**
1365
+ * Calculate radius for golden spiral (Vogel's model)
1366
+ * Creates even distribution like sunflower seeds
1367
+ */
1368
+ calculateGoldenRadius(t, e, i, o) {
1369
+ const r = i / Math.sqrt(e) * Math.sqrt(t) / o;
1370
+ return Math.min(r, i);
1371
+ }
1372
+ /**
1373
+ * Calculate radius for Archimedean spiral
1374
+ * r = a + b*θ (constant spacing between arms)
1375
+ */
1376
+ calculateArchimedeanRadius(t, e, i, o) {
1377
+ const s = e * 0.5 * o;
1378
+ return t / s * i;
1379
+ }
1380
+ /**
1381
+ * Calculate radius for logarithmic (equiangular) spiral
1382
+ * r = a * e^(b*θ)
1383
+ */
1384
+ calculateLogarithmicRadius(t, e, i, o) {
1385
+ const s = i * 0.05, r = 0.15 / o, a = s * Math.exp(r * t), h = e * 0.3 * o, c = s * Math.exp(r * h);
1386
+ return a / c * i;
1387
+ }
1388
+ /**
1389
+ * Utility: Generate random number between min and max
1390
+ */
1391
+ random(t, e) {
1392
+ return Math.random() * (e - t) + t;
1393
+ }
1394
+ }
1395
+ const fe = {
1396
+ clusterCount: "auto",
1397
+ clusterSpread: 150,
1398
+ clusterSpacing: 200,
1399
+ density: "uniform",
1400
+ overlap: 0.3,
1401
+ distribution: "gaussian"
1402
+ };
1403
+ class pe {
1404
+ constructor(t, e = {}) {
1405
+ this.config = t, this.imageConfig = e;
1406
+ }
1407
+ /**
1408
+ * Generate cluster layout positions for images
1409
+ * @param imageCount - Number of images to layout
1410
+ * @param containerBounds - Container dimensions {width, height}
1411
+ * @param options - Optional overrides (includes fixedHeight)
1412
+ * @returns Array of layout objects with position, rotation, scale
1413
+ */
1414
+ generate(t, e, i = {}) {
1415
+ const o = [], { width: s, height: r } = e, a = { ...fe, ...this.config.cluster }, h = this.config.spacing.padding, c = i.fixedHeight ?? 200, u = this.imageConfig.rotation?.mode ?? "none", l = this.imageConfig.rotation?.range?.min ?? -15, d = this.imageConfig.rotation?.range?.max ?? 15, m = this.imageConfig.sizing?.variance?.min ?? 1, b = this.imageConfig.sizing?.variance?.max ?? 1, p = m !== 1 || b !== 1, g = this.calculateClusterCount(
1416
+ t,
1417
+ a.clusterCount,
1418
+ s,
1419
+ r,
1420
+ a.clusterSpacing
1421
+ ), f = this.generateClusterCenters(
1422
+ g,
1423
+ s,
1424
+ r,
1425
+ h,
1426
+ a
1427
+ ), S = new Array(g).fill(0);
1428
+ for (let w = 0; w < t; w++)
1429
+ S[w % g]++;
1430
+ let v = 0;
1431
+ for (let w = 0; w < g; w++) {
1432
+ const E = f[w], x = S[w];
1433
+ for (let A = 0; A < x; A++) {
1434
+ let L, z;
1435
+ if (a.distribution === "gaussian")
1436
+ L = this.gaussianRandom() * E.spread, z = this.gaussianRandom() * E.spread;
1437
+ else {
1438
+ const M = this.random(0, Math.PI * 2), W = this.random(0, E.spread);
1439
+ L = Math.cos(M) * W, z = Math.sin(M) * W;
1440
+ }
1441
+ const P = 1 + a.overlap * 0.5, _ = 1 + a.overlap * 0.3;
1442
+ L /= P, z /= P;
1443
+ const Y = p ? this.random(m, b) : 1, N = _ * Y, T = c * N;
1444
+ let D = E.x + L, k = E.y + z;
1445
+ const R = T * 1.5 / 2, C = T / 2;
1446
+ D = Math.max(h + R, Math.min(D, s - h - R)), k = Math.max(h + C, Math.min(k, r - h - C));
1447
+ const O = u === "random" ? this.random(l, d) : 0, F = Math.sqrt(L * L + z * z) / E.spread, U = Math.round((1 - F) * 50) + 1;
1448
+ o.push({
1449
+ id: v,
1450
+ x: D,
1451
+ y: k,
1452
+ rotation: O,
1453
+ scale: N,
1454
+ baseSize: T,
1455
+ zIndex: U
1456
+ }), v++;
1457
+ }
1458
+ }
1459
+ return o;
1460
+ }
1461
+ /**
1462
+ * Calculate optimal number of clusters based on image count and container
1463
+ */
1464
+ calculateClusterCount(t, e, i, o, s) {
1465
+ if (e !== "auto")
1466
+ return Math.max(1, Math.min(e, t));
1467
+ const a = Math.max(1, Math.ceil(t / 8)), h = Math.floor(
1468
+ i / s * (o / s) * 0.6
1469
+ );
1470
+ return Math.max(1, Math.min(a, h, 10));
1471
+ }
1472
+ /**
1473
+ * Generate cluster center positions with spacing constraints
1474
+ */
1475
+ generateClusterCenters(t, e, i, o, s) {
1476
+ const r = [], h = o + s.clusterSpread, c = e - o - s.clusterSpread, u = o + s.clusterSpread, l = i - o - s.clusterSpread;
1477
+ for (let d = 0; d < t; d++) {
1478
+ let m = null, b = -1;
1479
+ for (let p = 0; p < 100; p++) {
1480
+ const g = {
1481
+ x: this.random(h, c),
1482
+ y: this.random(u, l),
1483
+ spread: this.calculateClusterSpread(s)
1484
+ };
1485
+ let f = 1 / 0;
1486
+ for (const S of r) {
1487
+ const v = g.x - S.x, w = g.y - S.y, E = Math.sqrt(v * v + w * w);
1488
+ f = Math.min(f, E);
1489
+ }
1490
+ if ((r.length === 0 || f > b) && (m = g, b = f), f >= s.clusterSpacing)
1491
+ break;
1492
+ }
1493
+ m && r.push(m);
1494
+ }
1495
+ return r;
1496
+ }
1497
+ /**
1498
+ * Calculate spread for a cluster (may vary if density='varied')
1499
+ */
1500
+ calculateClusterSpread(t) {
1501
+ return t.density === "uniform" ? t.clusterSpread : t.clusterSpread * this.random(0.5, 1.5);
1502
+ }
1503
+ /**
1504
+ * Generate a random number with approximately Gaussian distribution
1505
+ * Using Box-Muller transform
1506
+ */
1507
+ gaussianRandom() {
1508
+ let t = 0, e = 0;
1509
+ for (; t === 0; ) t = Math.random();
1510
+ for (; e === 0; ) e = Math.random();
1511
+ const i = Math.sqrt(-2 * Math.log(t)) * Math.cos(2 * Math.PI * e);
1512
+ return Math.max(-3, Math.min(3, i)) / 3;
1513
+ }
1514
+ /**
1515
+ * Utility: Generate random number between min and max
1516
+ */
1517
+ random(t, e) {
1518
+ return Math.random() * (e - t) + t;
1519
+ }
1520
+ }
1521
+ class be {
1522
+ constructor(t, e = {}) {
1523
+ this.config = t, this.imageConfig = e;
1524
+ }
1525
+ /**
1526
+ * Generate wave layout positions for images
1527
+ * @param imageCount - Number of images to layout
1528
+ * @param containerBounds - Container dimensions {width, height}
1529
+ * @param options - Optional overrides
1530
+ * @returns Array of layout objects with position, rotation, scale
1531
+ */
1532
+ generate(t, e, i = {}) {
1533
+ const o = [], { width: s, height: r } = e, a = i.fixedHeight ?? 200, h = this.config.spacing.padding ?? 50, c = this.imageConfig.rotation?.mode ?? "none", u = this.imageConfig.rotation?.range?.min ?? -15, l = this.imageConfig.rotation?.range?.max ?? 15, d = this.imageConfig.sizing?.variance?.min ?? 1, m = this.imageConfig.sizing?.variance?.max ?? 1, b = d !== 1 || m !== 1, p = i.fixedHeight ?? a, g = {
1534
+ ..._t,
1535
+ ...this.config.wave
1536
+ }, { rows: f, amplitude: S, frequency: v, phaseShift: w, synchronization: E } = g, x = Math.ceil(t / f), z = p * 1.5 / 2, P = h + z, _ = s - h - z, Y = _ - P, N = x > 1 ? Y / (x - 1) : 0, T = h + S + p / 2, D = r - h - S - p / 2, k = D - T, H = f > 1 ? k / (f - 1) : 0;
1537
+ let R = 0;
1538
+ for (let C = 0; C < f && R < t; C++) {
1539
+ const O = f === 1 ? (T + D) / 2 : T + C * H;
1540
+ let $ = 0;
1541
+ E === "offset" ? $ = C * w : E === "alternating" && ($ = C * Math.PI);
1542
+ for (let F = 0; F < x && R < t; F++) {
1543
+ const U = x === 1 ? (P + _) / 2 : P + F * N, M = this.calculateWaveY(U, s, S, v, $), W = U, G = O + M, X = b ? this.random(d, m) : 1, B = p * X;
1544
+ let J = 0;
1545
+ c === "tangent" ? J = this.calculateRotation(U, s, S, v, $) : c === "random" && (J = this.random(u, l));
1546
+ const K = B * 1.5 / 2, ct = B / 2, it = h + K, nt = s - h - K, lt = h + ct, ht = r - h - ct;
1547
+ o.push({
1548
+ id: R,
1549
+ x: Math.max(it, Math.min(W, nt)),
1550
+ y: Math.max(lt, Math.min(G, ht)),
1551
+ rotation: J,
1552
+ scale: X,
1553
+ baseSize: B,
1554
+ zIndex: R + 1
1555
+ }), R++;
1556
+ }
1557
+ }
1558
+ return o;
1559
+ }
1560
+ /**
1561
+ * Calculate Y position displacement on wave curve
1562
+ * @param x - Horizontal position
1563
+ * @param containerWidth - Container width
1564
+ * @param amplitude - Wave amplitude
1565
+ * @param frequency - Wave frequency
1566
+ * @param phase - Phase offset
1567
+ * @returns Y displacement from baseline
1568
+ */
1569
+ calculateWaveY(t, e, i, o, s) {
1570
+ const r = t / e;
1571
+ return i * Math.sin(o * r * 2 * Math.PI + s);
1572
+ }
1573
+ /**
1574
+ * Calculate rotation based on wave tangent
1575
+ * @param x - Horizontal position
1576
+ * @param containerWidth - Container width
1577
+ * @param amplitude - Wave amplitude
1578
+ * @param frequency - Wave frequency
1579
+ * @param phase - Phase offset
1580
+ * @returns Rotation angle in degrees
1581
+ */
1582
+ calculateRotation(t, e, i, o, s) {
1583
+ const r = t / e, a = i * o * 2 * Math.PI * Math.cos(o * r * 2 * Math.PI + s) / e;
1584
+ return Math.atan(a) * (180 / Math.PI);
1585
+ }
1586
+ /**
1587
+ * Estimate image width based on height
1588
+ /**
1589
+ * Utility: Generate random number between min and max
1590
+ * @param min - Minimum value
1591
+ * @param max - Maximum value
1592
+ * @returns Random number in range
1593
+ */
1594
+ random(t, e) {
1595
+ return Math.random() * (e - t) + t;
1596
+ }
1597
+ }
1598
+ class ye {
1599
+ constructor(t) {
1600
+ this.config = t.layout, this.imageConfig = t.image, this.layouts = /* @__PURE__ */ new Map(), this.placementLayout = this.initLayout();
1601
+ }
1602
+ /**
1603
+ * Initialize the appropriate placement layout based on config type
1604
+ * @returns Initialized placement layout
1605
+ */
1606
+ initLayout() {
1607
+ switch (this.config.algorithm) {
1608
+ case "radial":
1609
+ return new le(this.config, this.imageConfig);
1610
+ case "grid":
1611
+ return new de(this.config, this.imageConfig);
1612
+ case "spiral":
1613
+ return new me(this.config, this.imageConfig);
1614
+ case "cluster":
1615
+ return new pe(this.config, this.imageConfig);
1616
+ case "wave":
1617
+ return new be(this.config, this.imageConfig);
1618
+ default:
1619
+ return new ce(this.config, this.imageConfig);
1620
+ }
1621
+ }
1622
+ /**
1623
+ * Generate layout positions for images
1624
+ * @param imageCount - Number of images to layout
1625
+ * @param containerBounds - Container dimensions {width, height}
1626
+ * @param options - Optional overrides for configuration (e.g. fixedHeight)
1627
+ * @returns Array of layout objects with position, rotation, scale
1628
+ */
1629
+ generateLayout(t, e, i = {}) {
1630
+ const o = this.placementLayout.generate(t, e, i);
1631
+ return o.forEach((s) => {
1632
+ this.layouts.set(s.id, s);
1633
+ }), o;
1634
+ }
1635
+ /**
1636
+ * Get the original layout state for an image
1637
+ * @param imageId - The image ID (number or string)
1638
+ * @returns Original layout state or undefined if not found
1639
+ */
1640
+ getOriginalState(t) {
1641
+ return this.layouts.get(Number(t));
1642
+ }
1643
+ /**
1644
+ * Reset all stored layouts
1645
+ */
1646
+ reset() {
1647
+ this.layouts.clear();
1648
+ }
1649
+ /**
1650
+ * Update config dynamically (useful for responsive changes)
1651
+ * @param newConfig - Updated configuration
1652
+ */
1653
+ updateConfig(t) {
1654
+ t.layout && (Object.assign(this.config, t.layout), t.layout.algorithm && t.layout.algorithm !== this.config.algorithm && (this.placementLayout = this.initLayout())), t.image && Object.assign(this.imageConfig, t.image);
1655
+ }
1656
+ /**
1657
+ * Get responsive breakpoints from layout config
1658
+ */
1659
+ getBreakpoints() {
1660
+ return this.config.responsive ?? {
1661
+ mobile: { maxWidth: 767 },
1662
+ tablet: { maxWidth: 1199 }
1663
+ };
1664
+ }
1665
+ /**
1666
+ * Resolve breakpoint name based on viewport width
1667
+ */
1668
+ resolveBreakpoint(t) {
1669
+ const e = this.getBreakpoints();
1670
+ return t <= e.mobile.maxWidth ? "mobile" : t <= e.tablet.maxWidth ? "tablet" : "screen";
1671
+ }
1672
+ /**
1673
+ * Resolve the effective base height based on image config and current viewport
1674
+ * @param viewportWidth - Current viewport width
1675
+ * @returns Resolved base height or undefined if should auto-calculate (adaptive mode)
1676
+ */
1677
+ resolveBaseHeight(t) {
1678
+ const e = this.imageConfig.sizing;
1679
+ if (!e || e.mode === "adaptive")
1680
+ return;
1681
+ const i = e.height;
1682
+ if (i === void 0)
1683
+ return;
1684
+ if (typeof i == "number")
1685
+ return i;
1686
+ const o = i, s = this.resolveBreakpoint(t);
1687
+ return s === "mobile" ? o.mobile ?? o.tablet ?? o.screen : s === "tablet" ? o.tablet ?? o.screen ?? o.mobile : o.screen ?? o.tablet ?? o.mobile;
1688
+ }
1689
+ /**
1690
+ * Calculate adaptive image size based on container dimensions and image count
1691
+ * @param containerBounds - Container dimensions {width, height}
1692
+ * @param imageCount - Number of images to display
1693
+ * @param maxHeight - Maximum height constraint (upper bound)
1694
+ * @param viewportWidth - Current viewport width for baseHeight resolution
1695
+ * @returns Calculated sizing result with height
1696
+ */
1697
+ calculateAdaptiveSize(t, e, i, o) {
1698
+ const s = this.imageConfig.sizing, r = this.resolveBaseHeight(o);
1699
+ if (r !== void 0)
1700
+ return { height: r };
1701
+ const a = s?.minSize ?? 50, h = s?.maxSize ?? 400, c = this.config.targetCoverage ?? 0.6, u = this.config.densityFactor ?? 1, { width: l, height: d } = t, p = l * d * c / e;
1702
+ let f = Math.sqrt(p / 1.4);
1703
+ f *= u, f = Math.min(f, i);
1704
+ let S = this.clamp(f, a, h);
1705
+ if (S === a && f < a) {
1706
+ const v = Math.max(a * 0.05, 20);
1707
+ S = Math.max(v, f);
1708
+ }
1709
+ return { height: S };
1710
+ }
1711
+ /**
1712
+ * Utility: Clamp a value between min and max
1713
+ */
1714
+ clamp(t, e, i) {
1715
+ return Math.max(e, Math.min(i, t));
1716
+ }
1717
+ }
1718
+ var I = /* @__PURE__ */ ((n) => (n.IDLE = "idle", n.FOCUSING = "focusing", n.FOCUSED = "focused", n.UNFOCUSING = "unfocusing", n.CROSS_ANIMATING = "cross_animating", n))(I || {});
1719
+ function ve(n) {
1720
+ return n in mt;
1721
+ }
1722
+ function we(n) {
1723
+ return n ? ve(n) ? mt[n] : n : mt.md;
1724
+ }
1725
+ function xe(n) {
1726
+ if (!n) return "";
1727
+ const t = [];
1728
+ if (n.grayscale !== void 0 && t.push(`grayscale(${n.grayscale})`), n.blur !== void 0 && t.push(`blur(${n.blur}px)`), n.brightness !== void 0 && t.push(`brightness(${n.brightness})`), n.contrast !== void 0 && t.push(`contrast(${n.contrast})`), n.saturate !== void 0 && t.push(`saturate(${n.saturate})`), n.opacity !== void 0 && t.push(`opacity(${n.opacity})`), n.sepia !== void 0 && t.push(`sepia(${n.sepia})`), n.hueRotate !== void 0 && t.push(`hue-rotate(${n.hueRotate}deg)`), n.invert !== void 0 && t.push(`invert(${n.invert})`), n.dropShadow !== void 0)
1729
+ if (typeof n.dropShadow == "string")
1730
+ t.push(`drop-shadow(${n.dropShadow})`);
1731
+ else {
1732
+ const e = n.dropShadow;
1733
+ t.push(`drop-shadow(${e.x}px ${e.y}px ${e.blur}px ${e.color})`);
1734
+ }
1735
+ return t.join(" ");
1736
+ }
1737
+ function Q(n) {
1738
+ if (!n || n.style === "none" || n.width === 0)
1739
+ return "none";
1740
+ const t = n.width ?? 0, e = n.style ?? "solid", i = n.color ?? "#000000";
1741
+ return `${t}px ${e} ${i}`;
1742
+ }
1743
+ function st(n) {
1744
+ if (!n) return {};
1745
+ const t = {};
1746
+ if (n.borderRadiusTopLeft !== void 0 || n.borderRadiusTopRight !== void 0 || n.borderRadiusBottomRight !== void 0 || n.borderRadiusBottomLeft !== void 0) {
1747
+ const s = n.border?.radius ?? 0;
1748
+ n.borderRadiusTopLeft !== void 0 ? t.borderTopLeftRadius = `${n.borderRadiusTopLeft}px` : s && (t.borderTopLeftRadius = `${s}px`), n.borderRadiusTopRight !== void 0 ? t.borderTopRightRadius = `${n.borderRadiusTopRight}px` : s && (t.borderTopRightRadius = `${s}px`), n.borderRadiusBottomRight !== void 0 ? t.borderBottomRightRadius = `${n.borderRadiusBottomRight}px` : s && (t.borderBottomRightRadius = `${s}px`), n.borderRadiusBottomLeft !== void 0 ? t.borderBottomLeftRadius = `${n.borderRadiusBottomLeft}px` : s && (t.borderBottomLeftRadius = `${s}px`);
1749
+ } else n.border?.radius !== void 0 && (t.borderRadius = `${n.border.radius}px`);
1750
+ if (n.borderTop || n.borderRight || n.borderBottom || n.borderLeft) {
1751
+ const s = n.border || {}, r = { ...s, ...n.borderTop }, a = { ...s, ...n.borderRight }, h = { ...s, ...n.borderBottom }, c = { ...s, ...n.borderLeft };
1752
+ t.borderTop = Q(r), t.borderRight = Q(a), t.borderBottom = Q(h), t.borderLeft = Q(c);
1753
+ } else n.border && (t.border = Q(n.border));
1754
+ n.shadow !== void 0 && (t.boxShadow = we(n.shadow));
1755
+ const o = xe(n.filter);
1756
+ if (t.filter = o || "none", n.opacity !== void 0 && (t.opacity = String(n.opacity)), n.cursor !== void 0 && (t.cursor = n.cursor), n.outline && n.outline.style !== "none" && (n.outline.width ?? 0) > 0) {
1757
+ const s = n.outline.width ?? 0, r = n.outline.style ?? "solid", a = n.outline.color ?? "#000000";
1758
+ t.outline = `${s}px ${r} ${a}`, n.outline.offset !== void 0 && (t.outlineOffset = `${n.outline.offset}px`);
1759
+ }
1760
+ return n.objectFit !== void 0 && (t.objectFit = n.objectFit), n.aspectRatio !== void 0 && (t.aspectRatio = n.aspectRatio), t;
1761
+ }
1762
+ function tt(n, t) {
1763
+ t.borderRadius !== void 0 && (n.style.borderRadius = t.borderRadius), t.borderTopLeftRadius !== void 0 && (n.style.borderTopLeftRadius = t.borderTopLeftRadius), t.borderTopRightRadius !== void 0 && (n.style.borderTopRightRadius = t.borderTopRightRadius), t.borderBottomRightRadius !== void 0 && (n.style.borderBottomRightRadius = t.borderBottomRightRadius), t.borderBottomLeftRadius !== void 0 && (n.style.borderBottomLeftRadius = t.borderBottomLeftRadius), t.border !== void 0 && (n.style.border = t.border), t.borderTop !== void 0 && (n.style.borderTop = t.borderTop), t.borderRight !== void 0 && (n.style.borderRight = t.borderRight), t.borderBottom !== void 0 && (n.style.borderBottom = t.borderBottom), t.borderLeft !== void 0 && (n.style.borderLeft = t.borderLeft), t.boxShadow !== void 0 && (n.style.boxShadow = t.boxShadow), t.filter !== void 0 && (n.style.filter = t.filter), t.opacity !== void 0 && (n.style.opacity = t.opacity), t.cursor !== void 0 && (n.style.cursor = t.cursor), t.outline !== void 0 && (n.style.outline = t.outline), t.outlineOffset !== void 0 && (n.style.outlineOffset = t.outlineOffset), t.objectFit !== void 0 && (n.style.objectFit = t.objectFit), t.aspectRatio !== void 0 && (n.style.aspectRatio = t.aspectRatio);
1764
+ }
1765
+ function Lt(n) {
1766
+ return n ? Array.isArray(n) ? n.join(" ") : n : "";
1767
+ }
1768
+ function et(n, t) {
1769
+ const e = Lt(t);
1770
+ e && e.split(" ").forEach((i) => {
1771
+ i.trim() && n.classList.add(i.trim());
1772
+ });
1773
+ }
1774
+ function zt(n, t) {
1775
+ const e = Lt(t);
1776
+ e && e.split(" ").forEach((i) => {
1777
+ i.trim() && n.classList.remove(i.trim());
1778
+ });
1779
+ }
1780
+ const At = {
1781
+ UNFOCUSING: 999,
1782
+ FOCUSING: 1e3
1783
+ };
1784
+ class Se {
1785
+ constructor(t, e, i) {
1786
+ this.state = I.IDLE, this.currentFocus = null, this.focusData = null, this.outgoing = null, this.incoming = null, this.focusGeneration = 0, this.config = t, this.animationEngine = e, this.defaultStyles = st(i?.default), this.focusedStyles = st(i?.focused), this.defaultClassName = i?.default?.className, this.focusedClassName = i?.focused?.className;
1787
+ }
1788
+ /**
1789
+ * Get current state machine state
1790
+ */
1791
+ getState() {
1792
+ return this.state;
1793
+ }
1794
+ /**
1795
+ * Check if any animation is in progress
1796
+ */
1797
+ isAnimating() {
1798
+ return this.state !== I.IDLE && this.state !== I.FOCUSED;
1799
+ }
1800
+ /**
1801
+ * Normalize scalePercent value
1802
+ */
1803
+ normalizeScalePercent(t) {
1804
+ return t > 1 ? t / 100 : t;
1805
+ }
1806
+ /**
1807
+ * Calculate target dimensions for focused image
1808
+ * Returns actual pixel dimensions instead of scale factor for sharper rendering
1809
+ */
1810
+ calculateFocusDimensions(t, e, i) {
1811
+ const o = this.normalizeScalePercent(this.config.scalePercent), s = i.height * o, r = t / e;
1812
+ let a = s, h = a * r;
1813
+ const c = i.width * o;
1814
+ return h > c && (h = c, a = h / r), { width: h, height: a };
1815
+ }
1816
+ /**
1817
+ * Calculate the transform needed to center an image (position only, no scale)
1818
+ * Scale is handled by animating actual dimensions for sharper rendering
1819
+ */
1820
+ calculateFocusTransform(t, e) {
1821
+ const i = t.width / 2, o = t.height / 2, s = i - e.x, r = o - e.y;
1822
+ return {
1823
+ x: s,
1824
+ y: r,
1825
+ rotation: 0,
1826
+ scale: 1
1827
+ // No scale transform - dimensions are animated instead
1828
+ };
1829
+ }
1830
+ /**
1831
+ * Build transform string for dimension-based zoom (no scale in transform)
1832
+ */
1833
+ buildDimensionZoomTransform(t) {
1834
+ const e = ["translate(-50%, -50%)"];
1835
+ if (t.x !== void 0 || t.y !== void 0) {
1836
+ const i = t.x ?? 0, o = t.y ?? 0;
1837
+ e.push(`translate(${i}px, ${o}px)`);
1838
+ }
1839
+ return t.rotation !== void 0 && e.push(`rotate(${t.rotation}deg)`), e.join(" ");
1840
+ }
1841
+ /**
1842
+ * Create a Web Animation that animates both transform (position) and dimensions
1843
+ * This provides sharper zoom by re-rendering at target size instead of scaling pixels
1844
+ */
1845
+ animateWithDimensions(t, e, i, o, s, r, a, h) {
1846
+ const c = this.buildDimensionZoomTransform(e), u = this.buildDimensionZoomTransform(i);
1847
+ return t.style.transition = "none", t.animate(
1848
+ [
1849
+ {
1850
+ transform: c,
1851
+ width: `${o}px`,
1852
+ height: `${s}px`
1853
+ },
1854
+ {
1855
+ transform: u,
1856
+ width: `${r}px`,
1857
+ height: `${a}px`
1858
+ }
1859
+ ],
1860
+ {
1861
+ duration: h,
1862
+ easing: "cubic-bezier(0.4, 0, 0.2, 1)",
1863
+ fill: "forwards"
1864
+ }
1865
+ );
1866
+ }
1867
+ /**
1868
+ * Apply focused styling to an element
1869
+ */
1870
+ applyFocusedStyling(t, e) {
1871
+ t.style.zIndex = String(e), t.classList.add("fbn-ic-focused"), tt(t, this.focusedStyles), et(t, this.focusedClassName);
1872
+ }
1873
+ /**
1874
+ * Remove focused styling from an element
1875
+ */
1876
+ removeFocusedStyling(t, e) {
1877
+ t.style.zIndex = e, t.classList.remove("fbn-ic-focused"), zt(t, this.focusedClassName), tt(t, this.defaultStyles), et(t, this.defaultClassName);
1878
+ }
1879
+ /**
1880
+ * Start focus animation for an image using dimension-based zoom
1881
+ * Animates actual width/height for sharper rendering instead of transform scale
1882
+ * @param fromTransform - Optional starting transform (for mid-animation reversals)
1883
+ * @param fromDimensions - Optional starting dimensions (for mid-animation reversals)
1884
+ */
1885
+ startFocusAnimation(t, e, i, o, s) {
1886
+ const r = t.style.zIndex || "", a = t.offsetWidth, h = t.offsetHeight, c = this.calculateFocusDimensions(a, h, e), u = this.calculateFocusTransform(e, i);
1887
+ this.applyFocusedStyling(t, At.FOCUSING), this.animationEngine.cancelAllAnimations(t);
1888
+ const l = o ?? {
1889
+ x: 0,
1890
+ y: 0,
1891
+ rotation: i.rotation,
1892
+ scale: 1
1893
+ // No scale - using dimensions
1894
+ }, d = s?.width ?? a, m = s?.height ?? h, b = this.config.animationDuration ?? 600, p = this.animateWithDimensions(
1895
+ t,
1896
+ l,
1897
+ u,
1898
+ d,
1899
+ m,
1900
+ c.width,
1901
+ c.height,
1902
+ b
1903
+ ), g = {
1904
+ id: `focus-${Date.now()}`,
1905
+ element: t,
1906
+ animation: p,
1907
+ fromState: l,
1908
+ toState: u,
1909
+ startTime: performance.now(),
1910
+ duration: b
1911
+ };
1912
+ return this.focusData = {
1913
+ element: t,
1914
+ originalState: i,
1915
+ focusTransform: u,
1916
+ originalZIndex: r,
1917
+ originalWidth: a,
1918
+ originalHeight: h,
1919
+ focusWidth: c.width,
1920
+ focusHeight: c.height
1921
+ }, {
1922
+ element: t,
1923
+ originalState: i,
1924
+ animationHandle: g,
1925
+ direction: "in",
1926
+ originalWidth: a,
1927
+ originalHeight: h
1928
+ };
1929
+ }
1930
+ /**
1931
+ * Start unfocus animation for an image using dimension-based zoom
1932
+ * Animates back to original dimensions for consistent behavior
1933
+ * @param fromDimensions - Optional starting dimensions (for mid-animation reversals)
1934
+ */
1935
+ startUnfocusAnimation(t, e, i, o) {
1936
+ t.style.zIndex = String(At.UNFOCUSING), this.animationEngine.cancelAllAnimations(t);
1937
+ const s = i ?? this.focusData?.focusTransform ?? { x: 0, y: 0, rotation: 0, scale: 1 }, r = o?.width ?? this.focusData?.focusWidth ?? t.offsetWidth, a = o?.height ?? this.focusData?.focusHeight ?? t.offsetHeight, h = {
1938
+ x: 0,
1939
+ y: 0,
1940
+ rotation: e.rotation,
1941
+ scale: 1
1942
+ // No scale - using dimensions
1943
+ }, c = this.focusData?.originalWidth ?? t.offsetWidth, u = this.focusData?.originalHeight ?? t.offsetHeight, l = this.config.animationDuration ?? 600, d = this.animateWithDimensions(
1944
+ t,
1945
+ s,
1946
+ h,
1947
+ r,
1948
+ a,
1949
+ c,
1950
+ u,
1951
+ l
1952
+ ), m = {
1953
+ id: `unfocus-${Date.now()}`,
1954
+ element: t,
1955
+ animation: d,
1956
+ fromState: s,
1957
+ toState: h,
1958
+ startTime: performance.now(),
1959
+ duration: l
1960
+ };
1961
+ return {
1962
+ element: t,
1963
+ originalState: e,
1964
+ animationHandle: m,
1965
+ direction: "out",
1966
+ originalWidth: c,
1967
+ originalHeight: u
1968
+ };
1969
+ }
1970
+ /**
1971
+ * Capture the current visual state of an element mid-animation, BEFORE cancelling.
1972
+ *
1973
+ * The computed matrix.e/f include the -50%/-50% centering offset resolved to pixels.
1974
+ * buildDimensionZoomTransform prepends its own translate(-50%,-50%), so passing raw
1975
+ * matrix.e/f doubles the centering and produces the wrong starting position.
1976
+ *
1977
+ * This method extracts the PURE positional offset (pureX = matrix.e + 0.5*midWidth)
1978
+ * and commits width/height/transform to inline styles before the animation is cancelled,
1979
+ * preventing any visual snap.
1980
+ *
1981
+ * Must be called while the animation is still running (offsetWidth reflects animated size).
1982
+ * Caller is responsible for calling animationEngine.cancelAllAnimations() afterwards.
1983
+ */
1984
+ captureMidAnimationState(t) {
1985
+ const e = getComputedStyle(t), i = new DOMMatrix(e.transform), o = t.offsetWidth, s = t.offsetHeight, r = i.e + o * 0.5, a = i.f + s * 0.5, h = Math.atan2(i.b, i.a) * (180 / Math.PI);
1986
+ return t.style.width = `${o}px`, t.style.height = `${s}px`, t.style.transform = `translate(-50%, -50%) translate(${r}px, ${a}px) rotate(${h}deg)`, t.style.transition = "none", {
1987
+ transform: { x: r, y: a, rotation: h, scale: 1 },
1988
+ dimensions: { width: o, height: s }
1989
+ };
1990
+ }
1991
+ /**
1992
+ * Handle animation completion
1993
+ */
1994
+ async waitForAnimation(t) {
1995
+ try {
1996
+ await t.animation.finished;
1997
+ } catch {
1998
+ }
1999
+ }
2000
+ /**
2001
+ * Reset an element instantly to its original position and dimensions (no animation)
2002
+ */
2003
+ resetElementInstantly(t, e, i, o, s) {
2004
+ this.animationEngine.cancelAllAnimations(t);
2005
+ const r = ["translate(-50%, -50%)"];
2006
+ r.push("translate(0px, 0px)"), r.push(`rotate(${e.rotation}deg)`), t.style.transition = "none", t.style.transform = r.join(" "), o !== void 0 && s !== void 0 && (t.style.width = `${o}px`, t.style.height = `${s}px`), this.removeFocusedStyling(t, i);
2007
+ }
2008
+ /**
2009
+ * Focus (zoom) an image to center of container
2010
+ * Implements cross-animation when swapping focus
2011
+ */
2012
+ async focusImage(t, e, i) {
2013
+ if (this.currentFocus === t && this.state === I.FOCUSED)
2014
+ return this.unfocusImage();
2015
+ if (this.incoming?.element === t && this.state === I.FOCUSING) {
2016
+ const { transform: s, dimensions: r } = this.captureMidAnimationState(t);
2017
+ this.animationEngine.cancelAllAnimations(t), this.outgoing = this.startUnfocusAnimation(
2018
+ t,
2019
+ this.incoming.originalState,
2020
+ s,
2021
+ r
2022
+ ), this.incoming = null, this.state = I.UNFOCUSING, await this.waitForAnimation(this.outgoing.animationHandle), this.removeFocusedStyling(this.outgoing.element, this.focusData?.originalZIndex || ""), this.outgoing = null, this.currentFocus = null, this.focusData = null, this.state = I.IDLE;
2023
+ return;
2024
+ }
2025
+ const o = ++this.focusGeneration;
2026
+ switch (this.state) {
2027
+ case I.IDLE:
2028
+ if (this.state = I.FOCUSING, this.incoming = this.startFocusAnimation(t, e, i), await this.waitForAnimation(this.incoming.animationHandle), this.focusGeneration !== o) return;
2029
+ this.currentFocus = t, this.incoming = null, this.state = I.FOCUSED;
2030
+ break;
2031
+ case I.FOCUSED:
2032
+ if (this.state = I.CROSS_ANIMATING, this.currentFocus && this.focusData && (this.outgoing = this.startUnfocusAnimation(
2033
+ this.currentFocus,
2034
+ this.focusData.originalState
2035
+ )), this.incoming = this.startFocusAnimation(t, e, i), await Promise.all([
2036
+ this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),
2037
+ this.waitForAnimation(this.incoming.animationHandle)
2038
+ ]), this.focusGeneration !== o)
2039
+ return;
2040
+ this.outgoing && (this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || ""), this.outgoing = null), this.currentFocus = t, this.incoming = null, this.state = I.FOCUSED;
2041
+ break;
2042
+ case I.FOCUSING:
2043
+ if (this.incoming && (this.animationEngine.cancelAnimation(this.incoming.animationHandle, !1), this.resetElementInstantly(
2044
+ this.incoming.element,
2045
+ this.incoming.originalState,
2046
+ this.focusData?.originalZIndex || "",
2047
+ this.focusData?.originalWidth,
2048
+ this.focusData?.originalHeight
2049
+ ), this.incoming = null), this.incoming = this.startFocusAnimation(t, e, i), await this.waitForAnimation(this.incoming.animationHandle), this.focusGeneration !== o) return;
2050
+ this.currentFocus = t, this.incoming = null, this.state = I.FOCUSED;
2051
+ break;
2052
+ case I.UNFOCUSING:
2053
+ if (this.state = I.CROSS_ANIMATING, this.incoming = this.startFocusAnimation(t, e, i), await Promise.all([
2054
+ this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),
2055
+ this.waitForAnimation(this.incoming.animationHandle)
2056
+ ]), this.focusGeneration !== o) return;
2057
+ this.outgoing && (this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || ""), this.outgoing = null), this.currentFocus = t, this.incoming = null, this.state = I.FOCUSED;
2058
+ break;
2059
+ case I.CROSS_ANIMATING:
2060
+ if (this.incoming?.element === t)
2061
+ return;
2062
+ if (this.outgoing?.element === t) {
2063
+ const { transform: s, dimensions: r } = this.captureMidAnimationState(t);
2064
+ if (this.animationEngine.cancelAllAnimations(t), this.incoming) {
2065
+ const { transform: a, dimensions: h } = this.captureMidAnimationState(this.incoming.element);
2066
+ this.animationEngine.cancelAllAnimations(this.incoming.element), this.outgoing = this.startUnfocusAnimation(
2067
+ this.incoming.element,
2068
+ this.incoming.originalState,
2069
+ a,
2070
+ h
2071
+ );
2072
+ } else
2073
+ this.outgoing = null;
2074
+ if (this.incoming = this.startFocusAnimation(t, e, i, s, r), await Promise.all([
2075
+ this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),
2076
+ this.waitForAnimation(this.incoming.animationHandle)
2077
+ ]), this.focusGeneration !== o) return;
2078
+ this.outgoing && (this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || ""), this.outgoing = null), this.currentFocus = t, this.incoming = null, this.state = I.FOCUSED;
2079
+ return;
2080
+ }
2081
+ if (this.outgoing && (this.animationEngine.cancelAnimation(this.outgoing.animationHandle, !1), this.resetElementInstantly(
2082
+ this.outgoing.element,
2083
+ this.outgoing.originalState,
2084
+ this.outgoing.originalState.zIndex?.toString() || "",
2085
+ this.outgoing.originalWidth,
2086
+ this.outgoing.originalHeight
2087
+ ), this.outgoing = null), this.incoming) {
2088
+ const { transform: s, dimensions: r } = this.captureMidAnimationState(this.incoming.element);
2089
+ this.animationEngine.cancelAllAnimations(this.incoming.element), this.outgoing = this.startUnfocusAnimation(
2090
+ this.incoming.element,
2091
+ this.incoming.originalState,
2092
+ s,
2093
+ r
2094
+ );
2095
+ }
2096
+ if (this.incoming = this.startFocusAnimation(t, e, i), await Promise.all([
2097
+ this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),
2098
+ this.waitForAnimation(this.incoming.animationHandle)
2099
+ ]), this.focusGeneration !== o) return;
2100
+ this.outgoing && (this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || ""), this.outgoing = null), this.currentFocus = t, this.incoming = null, this.state = I.FOCUSED;
2101
+ break;
2102
+ }
2103
+ }
2104
+ /**
2105
+ * Unfocus current image, returning it to original position
2106
+ */
2107
+ async unfocusImage() {
2108
+ if (this.state === I.UNFOCUSING)
2109
+ return;
2110
+ const t = ++this.focusGeneration;
2111
+ if (!this.currentFocus || !this.focusData) {
2112
+ if (this.incoming && this.state === I.FOCUSING) {
2113
+ const { transform: s, dimensions: r } = this.captureMidAnimationState(this.incoming.element);
2114
+ if (this.animationEngine.cancelAllAnimations(this.incoming.element), this.outgoing = this.startUnfocusAnimation(
2115
+ this.incoming.element,
2116
+ this.incoming.originalState,
2117
+ s,
2118
+ r
2119
+ ), this.incoming = null, this.state = I.UNFOCUSING, await this.waitForAnimation(this.outgoing.animationHandle), this.focusGeneration !== t) return;
2120
+ this.removeFocusedStyling(this.outgoing.element, this.focusData?.originalZIndex || ""), this.outgoing = null, this.focusData = null, this.state = I.IDLE;
2121
+ }
2122
+ return;
2123
+ }
2124
+ if (this.state === I.CROSS_ANIMATING && this.incoming) {
2125
+ const { transform: s, dimensions: r } = this.captureMidAnimationState(this.incoming.element);
2126
+ this.animationEngine.cancelAllAnimations(this.incoming.element);
2127
+ const a = this.startUnfocusAnimation(
2128
+ this.incoming.element,
2129
+ this.incoming.originalState,
2130
+ s,
2131
+ r
2132
+ );
2133
+ if (await Promise.all([
2134
+ this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),
2135
+ this.waitForAnimation(a.animationHandle)
2136
+ ]), this.focusGeneration !== t) return;
2137
+ this.outgoing && this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || ""), this.removeFocusedStyling(a.element, this.incoming.originalState.zIndex?.toString() || ""), this.outgoing = null, this.incoming = null, this.currentFocus = null, this.focusData = null, this.state = I.IDLE;
2138
+ return;
2139
+ }
2140
+ this.state = I.UNFOCUSING;
2141
+ const e = this.currentFocus, i = this.focusData.originalState, o = this.focusData.originalZIndex;
2142
+ this.outgoing = this.startUnfocusAnimation(e, i), await this.waitForAnimation(this.outgoing.animationHandle), this.focusGeneration === t && (this.removeFocusedStyling(e, o), this.outgoing = null, this.currentFocus = null, this.focusData = null, this.state = I.IDLE);
2143
+ }
2144
+ /**
2145
+ * Swap focus from current image to a new one (alias for focusImage with cross-animation)
2146
+ */
2147
+ async swapFocus(t, e, i) {
2148
+ return this.focusImage(t, e, i);
2149
+ }
2150
+ /**
2151
+ * Get currently focused image element
2152
+ */
2153
+ getCurrentFocus() {
2154
+ return this.currentFocus;
2155
+ }
2156
+ /**
2157
+ * Check if an image is currently focused (stable state)
2158
+ */
2159
+ isFocused(t) {
2160
+ return this.currentFocus === t && this.state === I.FOCUSED;
2161
+ }
2162
+ /**
2163
+ * Check if an image is the target of current focus animation
2164
+ */
2165
+ isTargetingFocus(t) {
2166
+ return this.incoming?.element === t;
2167
+ }
2168
+ /**
2169
+ * Check if an image is involved in any focus/animation state
2170
+ * Returns true if the image is focused, animating in, or animating out
2171
+ * Useful for hover state management - don't apply hover to animating images
2172
+ */
2173
+ isInvolved(t) {
2174
+ return this.currentFocus === t || this.incoming?.element === t || this.outgoing?.element === t;
2175
+ }
2176
+ /**
2177
+ * Apply a temporary horizontal drag offset to the focused image
2178
+ * Used during swipe gestures for visual feedback
2179
+ */
2180
+ setDragOffset(t) {
2181
+ if (!this.currentFocus || !this.focusData || this.state !== I.FOCUSED) return;
2182
+ const e = this.currentFocus, i = this.focusData.focusTransform, o = ["translate(-50%, -50%)"], s = (i.x ?? 0) + t, r = i.y ?? 0;
2183
+ o.push(`translate(${s}px, ${r}px)`), i.rotation !== void 0 && o.push(`rotate(${i.rotation}deg)`), e.style.transition = "none", e.style.transform = o.join(" ");
2184
+ }
2185
+ /**
2186
+ * Clear the drag offset, optionally animating back to center
2187
+ * @param animate - If true, animate back to center; if false, snap instantly
2188
+ * @param duration - Animation duration in ms (default 150)
2189
+ */
2190
+ clearDragOffset(t, e = 150) {
2191
+ if (!this.currentFocus || !this.focusData || this.state !== I.FOCUSED) return;
2192
+ const i = this.currentFocus, o = this.focusData.focusTransform, s = ["translate(-50%, -50%)"], r = o.x ?? 0, a = o.y ?? 0;
2193
+ s.push(`translate(${r}px, ${a}px)`), o.rotation !== void 0 && s.push(`rotate(${o.rotation}deg)`);
2194
+ const h = s.join(" ");
2195
+ t ? (i.style.transition = `transform ${e}ms ease-out`, i.style.transform = h, setTimeout(() => {
2196
+ this.currentFocus === i && (i.style.transition = "none");
2197
+ }, e)) : (i.style.transition = "none", i.style.transform = h);
2198
+ }
2199
+ /**
2200
+ * Reset zoom state (cancels all animations)
2201
+ */
2202
+ reset() {
2203
+ this.outgoing && (this.animationEngine.cancelAnimation(this.outgoing.animationHandle, !1), this.resetElementInstantly(
2204
+ this.outgoing.element,
2205
+ this.outgoing.originalState,
2206
+ this.outgoing.originalState.zIndex?.toString() || "",
2207
+ this.outgoing.originalWidth,
2208
+ this.outgoing.originalHeight
2209
+ )), this.incoming && (this.animationEngine.cancelAnimation(this.incoming.animationHandle, !1), this.resetElementInstantly(
2210
+ this.incoming.element,
2211
+ this.incoming.originalState,
2212
+ this.focusData?.originalZIndex || "",
2213
+ this.focusData?.originalWidth,
2214
+ this.focusData?.originalHeight
2215
+ )), this.currentFocus && this.focusData && this.resetElementInstantly(
2216
+ this.currentFocus,
2217
+ this.focusData.originalState,
2218
+ this.focusData.originalZIndex,
2219
+ this.focusData.originalWidth,
2220
+ this.focusData.originalHeight
2221
+ ), this.state = I.IDLE, this.currentFocus = null, this.focusData = null, this.outgoing = null, this.incoming = null;
2222
+ }
2223
+ }
2224
+ const Ee = 50, Ie = 0.5, Ae = 20, Te = 0.3, Ce = 150, Re = 30, at = class at {
2225
+ constructor(t, e) {
2226
+ this.enabled = !1, this.touchState = null, this.recentTouchTimestamp = 0, this.container = t, this.callbacks = e, this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), this.boundTouchCancel = this.handleTouchCancel.bind(this);
2227
+ }
2228
+ /**
2229
+ * Start listening for touch events
2230
+ */
2231
+ enable() {
2232
+ this.enabled || (this.enabled = !0, this.container.style.touchAction = "pan-y", this.container.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), this.container.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), this.container.addEventListener("touchend", this.boundTouchEnd, { passive: !0 }), this.container.addEventListener("touchcancel", this.boundTouchCancel, { passive: !0 }));
2233
+ }
2234
+ /**
2235
+ * Stop listening for touch events
2236
+ */
2237
+ disable() {
2238
+ this.enabled && (this.enabled = !1, this.container.style.touchAction = "", this.container.removeEventListener("touchstart", this.boundTouchStart), this.container.removeEventListener("touchmove", this.boundTouchMove), this.container.removeEventListener("touchend", this.boundTouchEnd), this.container.removeEventListener("touchcancel", this.boundTouchCancel), this.touchState?.isDragging && this.callbacks.onDragEnd(!1), this.touchState = null);
2239
+ }
2240
+ /**
2241
+ * Clean up all event listeners
2242
+ */
2243
+ destroy() {
2244
+ this.disable();
2245
+ }
2246
+ /**
2247
+ * Check if a touch interaction happened recently
2248
+ * Used to prevent click-outside from unfocusing immediately after touch
2249
+ */
2250
+ hadRecentTouch() {
2251
+ return Date.now() - this.recentTouchTimestamp < at.TOUCH_CLICK_DELAY;
2252
+ }
2253
+ handleTouchStart(t) {
2254
+ if (t.touches.length !== 1) return;
2255
+ this.recentTouchTimestamp = Date.now();
2256
+ const e = t.touches[0];
2257
+ this.touchState = {
2258
+ startX: e.clientX,
2259
+ startY: e.clientY,
2260
+ startTime: performance.now(),
2261
+ currentX: e.clientX,
2262
+ isDragging: !1,
2263
+ isHorizontalSwipe: null
2264
+ };
2265
+ }
2266
+ handleTouchMove(t) {
2267
+ if (!this.touchState || t.touches.length !== 1) return;
2268
+ const e = t.touches[0], i = e.clientX - this.touchState.startX, o = e.clientY - this.touchState.startY;
2269
+ if (this.touchState.isHorizontalSwipe === null && Math.sqrt(i * i + o * o) > 10) {
2270
+ const a = Math.atan2(Math.abs(o), Math.abs(i)) * (180 / Math.PI);
2271
+ this.touchState.isHorizontalSwipe = a <= Re;
2272
+ }
2273
+ if (this.touchState.isHorizontalSwipe !== !1 && this.touchState.isHorizontalSwipe === !0) {
2274
+ t.preventDefault(), this.touchState.isDragging = !0, this.touchState.currentX = e.clientX;
2275
+ const s = i * Te;
2276
+ this.callbacks.onDragOffset(s);
2277
+ }
2278
+ }
2279
+ handleTouchEnd(t) {
2280
+ if (!this.touchState) return;
2281
+ this.recentTouchTimestamp = Date.now();
2282
+ const e = this.touchState.currentX - this.touchState.startX, i = performance.now() - this.touchState.startTime, o = Math.abs(e) / i, s = Math.abs(e);
2283
+ let r = !1;
2284
+ this.touchState.isHorizontalSwipe === !0 && this.touchState.isDragging && (s >= Ee || o >= Ie && s >= Ae) && (r = !0, e < 0 ? this.callbacks.onNext() : this.callbacks.onPrev()), this.touchState.isDragging && this.callbacks.onDragEnd(r), this.touchState = null;
2285
+ }
2286
+ handleTouchCancel(t) {
2287
+ this.touchState?.isDragging && this.callbacks.onDragEnd(!1), this.touchState = null;
2288
+ }
2289
+ };
2290
+ at.TOUCH_CLICK_DELAY = 300;
2291
+ let ft = at;
2292
+ class Me {
2293
+ constructor(t) {
2294
+ if (this._prepared = !1, this._discoveredUrls = [], this.apiKey = t.apiKey ?? "", this.apiEndpoint = t.apiEndpoint ?? "https://www.googleapis.com/drive/v3/files", this.debugLogging = t.debugLogging ?? !1, this.sources = t.sources ?? [], !this.sources || this.sources.length === 0)
2295
+ throw new Error("GoogleDriveLoader requires at least one source to be configured");
2296
+ }
2297
+ /**
2298
+ * Prepare the loader by discovering all images from configured sources
2299
+ * @param filter - Filter to apply to discovered images
2300
+ */
2301
+ async prepare(t) {
2302
+ this._discoveredUrls = [];
2303
+ for (const e of this.sources)
2304
+ if ("folders" in e)
2305
+ for (const i of e.folders) {
2306
+ const o = e.recursive !== void 0 ? e.recursive : !0, s = await this.loadFromFolder(i, t, o);
2307
+ this._discoveredUrls.push(...s);
2308
+ }
2309
+ else if ("files" in e) {
2310
+ const i = await this.loadFiles(e.files, t);
2311
+ this._discoveredUrls.push(...i);
2312
+ }
2313
+ this._prepared = !0;
2314
+ }
2315
+ /**
2316
+ * Get the number of discovered images
2317
+ * @throws Error if called before prepare()
2318
+ */
2319
+ imagesLength() {
2320
+ if (!this._prepared)
2321
+ throw new Error("GoogleDriveLoader.imagesLength() called before prepare()");
2322
+ return this._discoveredUrls.length;
2323
+ }
2324
+ /**
2325
+ * Get the ordered list of image URLs
2326
+ * @throws Error if called before prepare()
2327
+ */
2328
+ imageURLs() {
2329
+ if (!this._prepared)
2330
+ throw new Error("GoogleDriveLoader.imageURLs() called before prepare()");
2331
+ return [...this._discoveredUrls];
2332
+ }
2333
+ /**
2334
+ * Check if the loader has been prepared
2335
+ */
2336
+ isPrepared() {
2337
+ return this._prepared;
2338
+ }
2339
+ /**
2340
+ * Extract folder ID from various Google Drive URL formats
2341
+ * @param folderUrl - Google Drive folder URL
2342
+ * @returns Folder ID or null if invalid
2343
+ */
2344
+ extractFolderId(t) {
2345
+ const e = [
2346
+ /\/folders\/([a-zA-Z0-9_-]+)/,
2347
+ // Standard format
2348
+ /id=([a-zA-Z0-9_-]+)/
2349
+ // Alternative format
2350
+ ];
2351
+ for (const i of e) {
2352
+ const o = t.match(i);
2353
+ if (o && o[1])
2354
+ return o[1];
2355
+ }
2356
+ return null;
2357
+ }
2358
+ /**
2359
+ * Load images from a Google Drive folder
2360
+ * @param folderUrl - Google Drive folder URL
2361
+ * @param filter - Filter to apply to discovered images
2362
+ * @param recursive - Whether to include images from subfolders
2363
+ * @returns Promise resolving to array of image URLs
2364
+ */
2365
+ async loadFromFolder(t, e, i = !0) {
2366
+ const o = this.extractFolderId(t);
2367
+ if (!o)
2368
+ throw new Error("Invalid Google Drive folder URL. Please check the URL format.");
2369
+ if (!this.apiKey || this.apiKey === "YOUR_API_KEY_HERE")
2370
+ return this.loadImagesDirectly(o, e);
2371
+ try {
2372
+ return i ? await this.loadImagesRecursively(o, e) : await this.loadImagesFromSingleFolder(o, e);
2373
+ } catch (s) {
2374
+ return console.error("Error loading from Google Drive API:", s), this.loadImagesDirectly(o, e);
2375
+ }
2376
+ }
2377
+ /**
2378
+ * Load images from a single folder (non-recursive)
2379
+ * @param folderId - Google Drive folder ID
2380
+ * @param filter - Filter to apply to discovered images
2381
+ * @returns Promise resolving to array of image URLs
2382
+ */
2383
+ async loadImagesFromSingleFolder(t, e) {
2384
+ const i = [], o = `'${t}' in parents and trashed=false`, r = `${this.apiEndpoint}?q=${encodeURIComponent(o)}&fields=files(id,name,mimeType,thumbnailLink)&key=${this.apiKey}`, a = await fetch(r);
2385
+ if (!a.ok)
2386
+ throw new Error(`API request failed: ${a.status} ${a.statusText}`);
2387
+ const c = (await a.json()).files.filter(
2388
+ (u) => u.mimeType.startsWith("image/") && e.isAllowed(u.name)
2389
+ );
2390
+ return this.log(`Found ${c.length} images in folder ${t} (non-recursive)`), c.forEach((u) => {
2391
+ i.push(`https://lh3.googleusercontent.com/d/${u.id}=s1600`), this.log(`Added file: ${u.name}`);
2392
+ }), i;
2393
+ }
2394
+ /**
2395
+ * Load specific files by their URLs or IDs
2396
+ * @param fileUrls - Array of Google Drive file URLs or IDs
2397
+ * @param filter - Filter to apply to discovered images
2398
+ * @returns Promise resolving to array of image URLs
2399
+ */
2400
+ async loadFiles(t, e) {
2401
+ const i = [];
2402
+ for (const o of t) {
2403
+ const s = this.extractFileId(o);
2404
+ if (!s) {
2405
+ this.log(`Skipping invalid file URL: ${o}`);
2406
+ continue;
2407
+ }
2408
+ if (this.apiKey && this.apiKey !== "YOUR_API_KEY_HERE")
2409
+ try {
2410
+ const r = `${this.apiEndpoint}/${s}?fields=name,mimeType&key=${this.apiKey}`, a = await fetch(r);
2411
+ if (a.ok) {
2412
+ const h = await a.json();
2413
+ h.mimeType.startsWith("image/") && e.isAllowed(h.name) ? (i.push(`https://lh3.googleusercontent.com/d/${s}=s1600`), this.log(`Added file: ${h.name}`)) : this.log(`Skipping non-image file: ${h.name} (${h.mimeType})`);
2414
+ } else
2415
+ this.log(`Failed to fetch metadata for file ${s}: ${a.status}`);
2416
+ } catch (r) {
2417
+ this.log(`Error fetching metadata for file ${s}:`, r);
2418
+ }
2419
+ else
2420
+ i.push(`https://lh3.googleusercontent.com/d/${s}=s1600`);
2421
+ }
2422
+ return i;
2423
+ }
2424
+ /**
2425
+ * Extract file ID from Google Drive file URL
2426
+ * @param fileUrl - Google Drive file URL or file ID
2427
+ * @returns File ID or null if invalid
2428
+ */
2429
+ extractFileId(t) {
2430
+ if (!/[/:.]/.test(t))
2431
+ return t;
2432
+ const e = [
2433
+ /\/file\/d\/([a-zA-Z0-9_-]+)/,
2434
+ // Standard file format
2435
+ /\/open\?id=([a-zA-Z0-9_-]+)/,
2436
+ // Alternative format
2437
+ /id=([a-zA-Z0-9_-]+)/
2438
+ // Generic id parameter
2439
+ ];
2440
+ for (const i of e) {
2441
+ const o = t.match(i);
2442
+ if (o && o[1])
2443
+ return o[1];
2444
+ }
2445
+ return null;
2446
+ }
2447
+ /**
2448
+ * Recursively load images from a folder and all its subfolders
2449
+ * @param folderId - Google Drive folder ID
2450
+ * @param filter - Filter to apply to discovered images
2451
+ * @returns Promise resolving to array of image URLs
2452
+ */
2453
+ async loadImagesRecursively(t, e) {
2454
+ const i = [], o = `'${t}' in parents and trashed=false`, r = `${this.apiEndpoint}?q=${encodeURIComponent(o)}&fields=files(id,name,mimeType,thumbnailLink)&key=${this.apiKey}`, a = await fetch(r);
2455
+ if (!a.ok)
2456
+ throw new Error(`API request failed: ${a.status} ${a.statusText}`);
2457
+ const h = await a.json(), c = h.files.filter(
2458
+ (l) => l.mimeType.startsWith("image/") && e.isAllowed(l.name)
2459
+ ), u = h.files.filter(
2460
+ (l) => l.mimeType === "application/vnd.google-apps.folder"
2461
+ );
2462
+ this.log(`Found ${h.files.length} total items in folder ${t}`), h.files.forEach((l) => this.log(` - File: ${l.name} (${l.mimeType})`)), this.log(`- ${c.length} valid files (images only)`), this.log(`- ${u.length} subfolders`), c.forEach((l) => {
2463
+ i.push(`https://lh3.googleusercontent.com/d/${l.id}=s1600`), this.log(`Added file: ${l.name}`);
2464
+ });
2465
+ for (const l of u) {
2466
+ this.log(`Loading images from subfolder: ${l.name}`);
2467
+ const d = await this.loadImagesRecursively(l.id, e);
2468
+ i.push(...d);
2469
+ }
2470
+ return i;
2471
+ }
2472
+ /**
2473
+ * Direct loading method (no API key required, but less reliable)
2474
+ * Uses embedded folder view to scrape image IDs
2475
+ * @param folderId - Google Drive folder ID
2476
+ * @param filter - Filter to apply (not used in fallback mode)
2477
+ * @returns Promise resolving to array of image URLs
2478
+ */
2479
+ async loadImagesDirectly(t, e) {
2480
+ try {
2481
+ const i = `https://drive.google.com/embeddedfolderview?id=${t}`, o = await fetch(i, { mode: "cors" });
2482
+ if (!o.ok)
2483
+ throw new Error("Cannot access folder directly (CORS or permissions issue)");
2484
+ const s = await o.text(), r = /\/file\/d\/([a-zA-Z0-9_-]+)/g, a = [...s.matchAll(r)];
2485
+ return [...new Set(a.map((u) => u[1]))].map(
2486
+ (u) => `https://drive.google.com/uc?export=view&id=${u}`
2487
+ );
2488
+ } catch (i) {
2489
+ throw console.error("Direct loading failed:", i), new Error(
2490
+ `Unable to load images. Please ensure:
2491
+ 1. The folder is shared publicly (Anyone with the link can view)
2492
+ 2. The folder contains image files
2493
+ 3. Consider adding a Google Drive API key in config.js for better reliability`
2494
+ );
2495
+ }
2496
+ }
2497
+ /**
2498
+ * Manually add image URLs (for testing or when auto-loading fails)
2499
+ * @param imageIds - Array of Google Drive file IDs
2500
+ * @returns Array of direct image URLs
2501
+ */
2502
+ manualImageUrls(t) {
2503
+ return t.map((e) => `https://drive.google.com/uc?export=view&id=${e}`);
2504
+ }
2505
+ /**
2506
+ * Debug logging helper
2507
+ * @param args - Arguments to log
2508
+ */
2509
+ log(...t) {
2510
+ this.debugLogging && typeof console < "u" && console.log(...t);
2511
+ }
2512
+ }
2513
+ class Le {
2514
+ constructor(t) {
2515
+ if (this._prepared = !1, this._discoveredUrls = [], this.validateUrls = t.validateUrls !== !1, this.validationTimeout = t.validationTimeout ?? 5e3, this.validationMethod = t.validationMethod ?? "head", this.debugLogging = t.debugLogging ?? !1, this.sources = t.sources ?? [], !this.sources || this.sources.length === 0)
2516
+ throw new Error("StaticImageLoader requires at least one source to be configured");
2517
+ this.log("StaticImageLoader initialized with config:", t);
2518
+ }
2519
+ /**
2520
+ * Prepare the loader by discovering all images from configured sources
2521
+ * @param filter - Filter to apply to discovered images
2522
+ */
2523
+ async prepare(t) {
2524
+ this._discoveredUrls = [], this.log(`Processing ${this.sources.length} source(s)`);
2525
+ for (const e of this.sources)
2526
+ try {
2527
+ const i = await this.processSource(e, t);
2528
+ this._discoveredUrls.push(...i);
2529
+ } catch (i) {
2530
+ console.warn("Failed to process source:", e, i);
2531
+ }
2532
+ this._prepared = !0, this.log(`Successfully loaded ${this._discoveredUrls.length} image(s)`);
2533
+ }
2534
+ /**
2535
+ * Get the number of discovered images
2536
+ * @throws Error if called before prepare()
2537
+ */
2538
+ imagesLength() {
2539
+ if (!this._prepared)
2540
+ throw new Error("StaticImageLoader.imagesLength() called before prepare()");
2541
+ return this._discoveredUrls.length;
2542
+ }
2543
+ /**
2544
+ * Get the ordered list of image URLs
2545
+ * @throws Error if called before prepare()
2546
+ */
2547
+ imageURLs() {
2548
+ if (!this._prepared)
2549
+ throw new Error("StaticImageLoader.imageURLs() called before prepare()");
2550
+ return [...this._discoveredUrls];
2551
+ }
2552
+ /**
2553
+ * Check if the loader has been prepared
2554
+ */
2555
+ isPrepared() {
2556
+ return this._prepared;
2557
+ }
2558
+ /**
2559
+ * Process a single source object using shape-based detection
2560
+ * @param source - Source configuration detected by key presence
2561
+ * @param filter - Filter to apply to discovered images
2562
+ * @returns Promise resolving to array of valid URLs from this source
2563
+ */
2564
+ async processSource(t, e) {
2565
+ return t ? "urls" in t ? await this.processUrls(t.urls, e) : "path" in t ? await this.processPath(t.path, t.files, e) : "json" in t ? await this.processJson(t.json, e) : (console.warn("Unknown source shape:", t), []) : (console.warn("Invalid source object:", t), []);
2566
+ }
2567
+ /**
2568
+ * Process a list of direct URLs
2569
+ * @param urls - Array of image URLs
2570
+ * @param filter - Filter to apply to discovered images
2571
+ * @returns Promise resolving to array of validated URLs
2572
+ */
2573
+ async processUrls(t, e) {
2574
+ if (!Array.isArray(t))
2575
+ return console.warn("URLs must be an array:", t), [];
2576
+ const i = [];
2577
+ for (const o of t) {
2578
+ const s = o.split("/").pop() || o;
2579
+ if (!e.isAllowed(s)) {
2580
+ this.log(`Skipping filtered URL: ${o}`);
2581
+ continue;
2582
+ }
2583
+ this.validateUrls ? await this.validateUrl(o) ? i.push(o) : console.warn(`Skipping invalid/missing URL: ${o}`) : i.push(o);
2584
+ }
2585
+ return i;
2586
+ }
2587
+ /**
2588
+ * Process a path-based source
2589
+ * @param basePath - Base path (relative or absolute)
2590
+ * @param files - Array of filenames
2591
+ * @param filter - Filter to apply to discovered images
2592
+ * @returns Promise resolving to array of validated URLs
2593
+ */
2594
+ async processPath(t, e, i) {
2595
+ if (!Array.isArray(e))
2596
+ return console.warn("files must be an array:", e), [];
2597
+ const o = [];
2598
+ for (const s of e) {
2599
+ if (!i.isAllowed(s)) {
2600
+ this.log(`Skipping filtered file: ${s}`);
2601
+ continue;
2602
+ }
2603
+ const r = this.constructUrl(t, s);
2604
+ this.validateUrls ? await this.validateUrl(r) ? o.push(r) : console.warn(`Skipping invalid/missing file: ${r}`) : o.push(r);
2605
+ }
2606
+ return o;
2607
+ }
2608
+ /**
2609
+ * Process a JSON endpoint source
2610
+ * Fetches a JSON endpoint that returns { images: string[] }
2611
+ * @param url - JSON endpoint URL
2612
+ * @param filter - Filter to apply to discovered images
2613
+ * @returns Promise resolving to array of validated URLs
2614
+ */
2615
+ async processJson(t, e) {
2616
+ this.log(`Fetching JSON endpoint: ${t}`);
2617
+ const i = new AbortController(), o = setTimeout(() => i.abort(), 1e4);
2618
+ try {
2619
+ const s = await fetch(t, { signal: i.signal });
2620
+ if (clearTimeout(o), !s.ok)
2621
+ throw new Error(`HTTP ${s.status} fetching ${t}`);
2622
+ const r = await s.json();
2623
+ if (!r || !Array.isArray(r.images))
2624
+ throw new Error('JSON source must return JSON with shape { "images": ["url1", "url2", ...] }');
2625
+ return this.log(`JSON endpoint returned ${r.images.length} image(s)`), await this.processUrls(r.images, e);
2626
+ } catch (s) {
2627
+ throw clearTimeout(o), s instanceof Error && s.name === "AbortError" ? new Error(`Timeout fetching JSON endpoint: ${t}`) : s;
2628
+ }
2629
+ }
2630
+ /**
2631
+ * Validate a single URL using HEAD request
2632
+ * @param url - URL to validate
2633
+ * @returns Promise resolving to true if valid and accessible
2634
+ */
2635
+ async validateUrl(t) {
2636
+ if (this.validationMethod === "none")
2637
+ return !0;
2638
+ if (this.validationMethod === "simple")
2639
+ try {
2640
+ return typeof window < "u" ? new URL(t, window.location.origin) : new URL(t), !0;
2641
+ } catch {
2642
+ return !1;
2643
+ }
2644
+ if (typeof window > "u")
2645
+ return !0;
2646
+ if (!(t.startsWith(window.location.origin) || t.startsWith("/")))
2647
+ return this.log(`Skipping validation for cross-origin URL: ${t}`), !0;
2648
+ try {
2649
+ const i = new AbortController(), o = setTimeout(() => i.abort(), this.validationTimeout), s = await fetch(t, {
2650
+ method: "HEAD",
2651
+ signal: i.signal
2652
+ });
2653
+ return clearTimeout(o), s.ok ? !0 : (this.log(`Validation failed for ${t}: HTTP ${s.status}`), !1);
2654
+ } catch (i) {
2655
+ return i instanceof Error && (i.name === "AbortError" ? this.log(`Validation timeout for ${t}`) : this.log(`Validation failed for ${t}:`, i.message)), !1;
2656
+ }
2657
+ }
2658
+ /**
2659
+ * Construct full URL from basePath and filename
2660
+ * @param basePath - Base path (relative or absolute)
2661
+ * @param filename - Filename to append
2662
+ * @returns Complete URL
2663
+ */
2664
+ constructUrl(t, e) {
2665
+ const i = t.replace(/\/$/, "");
2666
+ if (this.isAbsoluteUrl(t))
2667
+ return `${i}/${e}`;
2668
+ if (typeof window > "u")
2669
+ return `${i}/${e}`;
2670
+ const o = window.location.origin, r = (t.startsWith("/") ? t : "/" + t).replace(/\/$/, "");
2671
+ return `${o}${r}/${e}`;
2672
+ }
2673
+ /**
2674
+ * Check if URL is absolute (contains protocol)
2675
+ * @param url - URL to check
2676
+ * @returns True if absolute URL
2677
+ */
2678
+ isAbsoluteUrl(t) {
2679
+ try {
2680
+ return new URL(t), !0;
2681
+ } catch {
2682
+ return !1;
2683
+ }
2684
+ }
2685
+ /**
2686
+ * Debug logging helper
2687
+ * @param args - Arguments to log
2688
+ */
2689
+ log(...t) {
2690
+ this.debugLogging && typeof console < "u" && console.log(...t);
2691
+ }
2692
+ }
2693
+ class ze {
2694
+ constructor(t) {
2695
+ if (this._prepared = !1, this._discoveredUrls = [], this.loaders = t.loaders, this.debugLogging = t.debugLogging ?? !1, !this.loaders || this.loaders.length === 0)
2696
+ throw new Error("CompositeLoader requires at least one loader to be configured");
2697
+ this.log(`CompositeLoader initialized with ${this.loaders.length} loader(s)`);
2698
+ }
2699
+ /**
2700
+ * Prepare all loaders in parallel and combine their results
2701
+ * @param filter - Filter to apply to discovered images
2702
+ */
2703
+ async prepare(t) {
2704
+ this._discoveredUrls = [], this.log(`Preparing ${this.loaders.length} loader(s) in parallel`);
2705
+ const e = this.loaders.map((i, o) => i.prepare(t).then(() => {
2706
+ this.log(`Loader ${o} prepared with ${i.imagesLength()} images`);
2707
+ }).catch((s) => {
2708
+ console.warn(`Loader ${o} failed to prepare:`, s);
2709
+ }));
2710
+ await Promise.all(e);
2711
+ for (const i of this.loaders)
2712
+ if (i.isPrepared()) {
2713
+ const o = i.imageURLs();
2714
+ this._discoveredUrls.push(...o);
2715
+ }
2716
+ this._prepared = !0, this.log(`CompositeLoader prepared with ${this._discoveredUrls.length} total images`);
2717
+ }
2718
+ /**
2719
+ * Get the combined number of discovered images
2720
+ * @throws Error if called before prepare()
2721
+ */
2722
+ imagesLength() {
2723
+ if (!this._prepared)
2724
+ throw new Error("CompositeLoader.imagesLength() called before prepare()");
2725
+ return this._discoveredUrls.length;
2726
+ }
2727
+ /**
2728
+ * Get the combined ordered list of image URLs
2729
+ * @throws Error if called before prepare()
2730
+ */
2731
+ imageURLs() {
2732
+ if (!this._prepared)
2733
+ throw new Error("CompositeLoader.imageURLs() called before prepare()");
2734
+ return [...this._discoveredUrls];
2735
+ }
2736
+ /**
2737
+ * Check if the loader has been prepared
2738
+ */
2739
+ isPrepared() {
2740
+ return this._prepared;
2741
+ }
2742
+ /**
2743
+ * Debug logging helper
2744
+ * @param args - Arguments to log
2745
+ */
2746
+ log(...t) {
2747
+ this.debugLogging && typeof console < "u" && console.log("[CompositeLoader]", ...t);
2748
+ }
2749
+ }
2750
+ class Fe {
2751
+ /**
2752
+ * Create a new ImageFilter
2753
+ * @param extensions - Array of allowed file extensions (without dots)
2754
+ * Defaults to common image formats if not provided
2755
+ */
2756
+ constructor(t) {
2757
+ this.allowedExtensions = t || [
2758
+ "jpg",
2759
+ "jpeg",
2760
+ "png",
2761
+ "gif",
2762
+ "webp",
2763
+ "bmp"
2764
+ ];
2765
+ }
2766
+ /**
2767
+ * Check if a filename has an allowed extension
2768
+ * @param filename - The filename to check (can include path or query string)
2769
+ * @returns True if the file extension is allowed
2770
+ */
2771
+ isAllowed(t) {
2772
+ const i = t.split("?")[0].split(".").pop()?.toLowerCase();
2773
+ return i ? this.allowedExtensions.includes(i) : !1;
2774
+ }
2775
+ /**
2776
+ * Get the list of allowed extensions
2777
+ * @returns Array of allowed extensions
2778
+ */
2779
+ getAllowedExtensions() {
2780
+ return [...this.allowedExtensions];
2781
+ }
2782
+ // Future expansion methods:
2783
+ // isAllowedSize(sizeBytes: number): boolean
2784
+ // isAllowedDate(date: Date): boolean
2785
+ // isAllowedDimensions(width: number, height: number): boolean
2786
+ }
2787
+ const De = `
2788
+ .fbn-ic-gallery {
2789
+ position: relative;
2790
+ width: 100%;
2791
+ height: 100%;
2792
+ overflow: hidden;
2793
+ perspective: 1000px;
2794
+ }
2795
+
2796
+ .fbn-ic-image {
2797
+ position: absolute;
2798
+ cursor: pointer;
2799
+ transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),
2800
+ box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1),
2801
+ filter 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2802
+ opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2803
+ border 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2804
+ outline 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2805
+ z-index 0s 0.6s;
2806
+ will-change: transform;
2807
+ user-select: none;
2808
+ backface-visibility: hidden;
2809
+ -webkit-backface-visibility: hidden;
2810
+ }
2811
+
2812
+ .fbn-ic-image.fbn-ic-focused {
2813
+ z-index: 1000;
2814
+ transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),
2815
+ box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1),
2816
+ filter 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2817
+ opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2818
+ border 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2819
+ outline 0.3s cubic-bezier(0.4, 0, 0.2, 1),
2820
+ z-index 0s 0s;
2821
+ will-change: auto;
2822
+ }
2823
+
2824
+ .fbn-ic-counter {
2825
+ position: fixed;
2826
+ bottom: 24px;
2827
+ left: 50%;
2828
+ transform: translateX(-50%);
2829
+ z-index: 10001;
2830
+ pointer-events: none;
2831
+ }
2832
+
2833
+ .fbn-ic-hidden {
2834
+ display: none !important;
2835
+ }
2836
+ `;
2837
+ function Oe() {
2838
+ if (typeof document > "u") return;
2839
+ const n = "fbn-ic-functional-styles";
2840
+ if (document.getElementById(n)) return;
2841
+ const t = document.createElement("style");
2842
+ t.id = n, t.textContent = De, document.head.appendChild(t);
2843
+ }
2844
+ let $e = class {
2845
+ constructor(t = {}) {
2846
+ this.fullConfig = Yt(t), t.container instanceof HTMLElement ? (this.containerRef = t.container, this.containerId = null) : (this.containerRef = null, this.containerId = t.container || "imageCloud"), this.imagesLoaded = !1, this.imageElements = [], this.imageLayouts = [], this.currentImageHeight = 225, this.currentFocusIndex = null, this.hoveredImage = null, this.resizeTimeout = null, this.displayQueue = [], this.queueInterval = null, this.loadGeneration = 0, this.loadingElAutoCreated = !1, this.errorElAutoCreated = !1, this.counterEl = null, this.counterElAutoCreated = !1, this.animationEngine = new Vt(this.fullConfig.animation), this.layoutEngine = new ye({
2847
+ layout: this.fullConfig.layout,
2848
+ image: this.fullConfig.image
2849
+ }), this.zoomEngine = new Se(this.fullConfig.interaction.focus, this.animationEngine, this.fullConfig.styling), this.defaultStyles = st(this.fullConfig.styling?.default), this.hoverStyles = st(this.fullConfig.styling?.hover), this.defaultClassName = this.fullConfig.styling?.default?.className, this.hoverClassName = this.fullConfig.styling?.hover?.className;
2850
+ const e = this.fullConfig.animation.entry || y.animation.entry;
2851
+ this.entryAnimationEngine = new re(
2852
+ e,
2853
+ this.fullConfig.layout.algorithm
2854
+ ), this.swipeEngine = null, this.imageFilter = this.createImageFilter(), this.imageLoader = this.createLoader(), this.containerEl = null, this.loadingEl = null, this.errorEl = null;
2855
+ }
2856
+ /**
2857
+ * Create image filter based on shared loader config
2858
+ */
2859
+ createImageFilter() {
2860
+ const t = this.fullConfig.config.loaders?.allowedExtensions;
2861
+ return new Fe(t);
2862
+ }
2863
+ /**
2864
+ * Create appropriate image loader based on config
2865
+ * Processes loaders array, merges shared config, wraps in CompositeLoader if needed
2866
+ */
2867
+ createLoader() {
2868
+ const t = this.fullConfig.loaders, e = this.fullConfig.config.loaders ?? {};
2869
+ if (!t || t.length === 0)
2870
+ throw new Error("No loaders configured. Provide `images`, `loaders`, or both.");
2871
+ const i = t.map((o) => this.createLoaderFromEntry(o, e));
2872
+ return i.length === 1 ? i[0] : new ze({
2873
+ loaders: i,
2874
+ debugLogging: this.fullConfig.config.debug?.loaders
2875
+ });
2876
+ }
2877
+ /**
2878
+ * Create a single loader from a LoaderEntry, merging shared config
2879
+ */
2880
+ createLoaderFromEntry(t, e) {
2881
+ if ("static" in t) {
2882
+ const i = t.static, o = {
2883
+ ...i,
2884
+ validateUrls: i.validateUrls ?? e.validateUrls,
2885
+ validationTimeout: i.validationTimeout ?? e.validationTimeout,
2886
+ validationMethod: i.validationMethod ?? e.validationMethod,
2887
+ allowedExtensions: i.allowedExtensions ?? e.allowedExtensions,
2888
+ debugLogging: i.debugLogging ?? this.fullConfig.config.debug?.loaders
2889
+ };
2890
+ return new Le(o);
2891
+ } else if ("googleDrive" in t) {
2892
+ const i = t.googleDrive, o = {
2893
+ ...i,
2894
+ allowedExtensions: i.allowedExtensions ?? e.allowedExtensions,
2895
+ debugLogging: i.debugLogging ?? this.fullConfig.config.debug?.loaders
2896
+ };
2897
+ return new Me(o);
2898
+ } else
2899
+ throw new Error(`Unknown loader entry: ${JSON.stringify(t)}`);
2900
+ }
2901
+ /**
2902
+ * Initialize the gallery
2903
+ */
2904
+ async init() {
2905
+ try {
2906
+ if (Oe(), this.containerRef)
2907
+ this.containerEl = this.containerRef;
2908
+ else if (this.containerEl = document.getElementById(this.containerId), !this.containerEl)
2909
+ throw new Error(`Container #${this.containerId} not found`);
2910
+ this.containerEl.classList.add("fbn-ic-gallery"), this.swipeEngine = new ft(this.containerEl, {
2911
+ onNext: () => this.navigateToNextImage(),
2912
+ onPrev: () => this.navigateToPreviousImage(),
2913
+ onDragOffset: (t) => this.zoomEngine.setDragOffset(t),
2914
+ onDragEnd: (t) => {
2915
+ t ? this.zoomEngine.clearDragOffset(!1) : this.zoomEngine.clearDragOffset(!0, Ce);
2916
+ }
2917
+ }), this.setupUI(), this.setupEventListeners(), this.logDebug("ImageCloud initialized"), await this.loadImages();
2918
+ } catch (t) {
2919
+ console.error("Gallery initialization failed:", t), this.errorEl && t instanceof Error && this.showError("Gallery failed to initialize: " + t.message);
2920
+ }
2921
+ }
2922
+ setupUI() {
2923
+ const t = this.fullConfig.rendering.ui;
2924
+ t.showLoadingSpinner && (t.loadingElement ? (this.loadingEl = this.resolveElement(t.loadingElement), this.loadingElAutoCreated = !1) : (this.loadingEl = this.createDefaultLoadingElement(), this.loadingElAutoCreated = !0)), t.errorElement ? (this.errorEl = this.resolveElement(t.errorElement), this.errorElAutoCreated = !1) : (this.errorEl = this.createDefaultErrorElement(), this.errorElAutoCreated = !0), t.showImageCounter && (t.counterElement ? (this.counterEl = this.resolveElement(t.counterElement), this.counterElAutoCreated = !1) : (this.counterEl = this.createDefaultCounterElement(), this.counterElAutoCreated = !0));
2925
+ }
2926
+ resolveElement(t) {
2927
+ return t instanceof HTMLElement ? t : document.getElementById(t);
2928
+ }
2929
+ createDefaultLoadingElement() {
2930
+ const t = document.createElement("div");
2931
+ t.className = "fbn-ic-loading fbn-ic-hidden";
2932
+ const e = document.createElement("div");
2933
+ e.className = "fbn-ic-spinner", t.appendChild(e);
2934
+ const i = document.createElement("p");
2935
+ return i.textContent = "Loading images...", t.appendChild(i), this.containerEl.appendChild(t), t;
2936
+ }
2937
+ createDefaultErrorElement() {
2938
+ const t = document.createElement("div");
2939
+ return t.className = "fbn-ic-error fbn-ic-hidden", this.containerEl.appendChild(t), t;
2940
+ }
2941
+ createDefaultCounterElement() {
2942
+ const t = document.createElement("div");
2943
+ return t.className = "fbn-ic-counter fbn-ic-hidden", this.containerEl.appendChild(t), t;
2944
+ }
2945
+ setupEventListeners() {
2946
+ document.addEventListener("keydown", (t) => {
2947
+ t.key === "Escape" ? (this.zoomEngine.unfocusImage(), this.currentFocusIndex = null, this.swipeEngine?.disable(), this.hideCounter()) : t.key === "ArrowRight" ? this.navigateToNextImage() : t.key === "ArrowLeft" ? this.navigateToPreviousImage() : (t.key === "Enter" || t.key === " ") && this.hoveredImage && (this.handleImageClick(this.hoveredImage.element, this.hoveredImage.layout), t.preventDefault());
2948
+ }), document.addEventListener("click", (t) => {
2949
+ this.swipeEngine?.hadRecentTouch() || t.target.closest(".fbn-ic-image") || (this.zoomEngine.unfocusImage(), this.currentFocusIndex = null, this.swipeEngine?.disable(), this.hideCounter());
2950
+ }), window.addEventListener("resize", () => this.handleResize());
2951
+ }
2952
+ /**
2953
+ * Navigate to the next image (Right arrow)
2954
+ */
2955
+ navigateToNextImage() {
2956
+ if (this.currentFocusIndex === null || this.imageElements.length === 0) return;
2957
+ const t = (this.currentFocusIndex + 1) % this.imageLayouts.length, e = this.imageElements.find(
2958
+ (o) => o.dataset.imageId === String(t)
2959
+ );
2960
+ if (!e) return;
2961
+ const i = this.imageLayouts[t];
2962
+ i && (this.currentFocusIndex = t, this.handleImageClick(e, i), this.updateCounter(t));
2963
+ }
2964
+ /**
2965
+ * Navigate to the previous image (Left arrow)
2966
+ */
2967
+ navigateToPreviousImage() {
2968
+ if (this.currentFocusIndex === null || this.imageElements.length === 0) return;
2969
+ const t = (this.currentFocusIndex - 1 + this.imageLayouts.length) % this.imageLayouts.length, e = this.imageElements.find(
2970
+ (o) => o.dataset.imageId === String(t)
2971
+ );
2972
+ if (!e) return;
2973
+ const i = this.imageLayouts[t];
2974
+ i && (this.currentFocusIndex = t, this.handleImageClick(e, i), this.updateCounter(t));
2975
+ }
2976
+ /**
2977
+ * Navigate to a specific image by index
2978
+ */
2979
+ handleResize() {
2980
+ this.imagesLoaded && (this.resizeTimeout !== null && clearTimeout(this.resizeTimeout), this.resizeTimeout = window.setTimeout(() => {
2981
+ const t = this.getImageHeight();
2982
+ t !== this.currentImageHeight ? (this.logDebug(`Window resized to new breakpoint (height: ${t}px). Reloading images...`), this.loadImages()) : this.logDebug("Window resized (no breakpoint change)");
2983
+ }, 500));
2984
+ }
2985
+ getImageHeight() {
2986
+ const t = window.innerWidth, e = this.fullConfig.layout.responsive, o = this.fullConfig.image.sizing?.maxSize ?? 400;
2987
+ return e ? t <= e.mobile.maxWidth ? Math.min(100, o) : t <= e.tablet.maxWidth ? Math.min(180, o) : Math.min(225, o) : t <= 767 ? Math.min(100, o) : t <= 1199 ? Math.min(180, o) : Math.min(225, o);
2988
+ }
2989
+ /**
2990
+ * Get container bounds for layout calculations
2991
+ */
2992
+ getContainerBounds() {
2993
+ return this.containerEl ? {
2994
+ width: this.containerEl.offsetWidth,
2995
+ height: this.containerEl.offsetHeight || window.innerHeight * 0.7
2996
+ } : { width: window.innerWidth, height: window.innerHeight * 0.7 };
2997
+ }
2998
+ /**
2999
+ * Load images using the unified loader interface
3000
+ */
3001
+ async loadImages() {
3002
+ try {
3003
+ this.showLoading(!0), this.hideError(), this.clearImageCloud(), await this.imageLoader.prepare(this.imageFilter);
3004
+ const t = this.imageLoader.imagesLength();
3005
+ let e = this.imageLoader.imageURLs();
3006
+ if (t === 0) {
3007
+ this.showError("No images found."), this.showLoading(!1);
3008
+ return;
3009
+ }
3010
+ const i = this.getContainerBounds(), o = this.getImageHeight(), s = window.innerWidth;
3011
+ this.logDebug(`Adaptive sizing input: container=${i.width}x${i.height}px, images=${t}, responsiveMax=${o}px`);
3012
+ const r = this.layoutEngine.calculateAdaptiveSize(
3013
+ i,
3014
+ t,
3015
+ o,
3016
+ s
3017
+ );
3018
+ this.logDebug(`Adaptive sizing result: height=${r.height}px`), await this.createImageCloud(e, r.height), this.showLoading(!1), this.imagesLoaded = !0;
3019
+ } catch (t) {
3020
+ console.error("Error loading images:", t), t instanceof Error && this.showError(t.message || "Failed to load images."), this.showLoading(!1);
3021
+ }
3022
+ }
3023
+ /**
3024
+ * Helper for debug logging
3025
+ */
3026
+ logDebug(...t) {
3027
+ this.fullConfig.config.debug?.enabled && typeof console < "u" && console.log(...t);
3028
+ }
3029
+ async createImageCloud(t, e) {
3030
+ if (!this.containerEl) return;
3031
+ const i = this.getContainerBounds();
3032
+ this.currentImageHeight = e;
3033
+ const o = this.loadGeneration, s = this.layoutEngine.generateLayout(t.length, i, { fixedHeight: e });
3034
+ this.imageLayouts = s, this.displayQueue = [];
3035
+ let r = 0;
3036
+ const a = (c) => {
3037
+ this.containerEl && (this.containerEl.appendChild(c), this.imageElements.push(c), requestAnimationFrame(() => {
3038
+ if (c.offsetWidth, c.style.opacity = this.defaultStyles.opacity ?? "1", c.dataset.startX && (this.entryAnimationEngine.requiresJSAnimation() || this.entryAnimationEngine.requiresJSRotation() || this.entryAnimationEngine.requiresJSScale() || c.dataset.startRotation !== c.dataset.rotation || c.dataset.startScale !== c.dataset.scale)) {
3039
+ const d = {
3040
+ x: parseFloat(c.dataset.startX),
3041
+ y: parseFloat(c.dataset.startY)
3042
+ }, m = {
3043
+ x: parseFloat(c.dataset.endX),
3044
+ y: parseFloat(c.dataset.endY)
3045
+ }, b = parseFloat(c.dataset.imageWidth), p = parseFloat(c.dataset.imageHeight), g = parseFloat(c.dataset.rotation), f = parseFloat(c.dataset.scale), S = c.dataset.startRotation ? parseFloat(c.dataset.startRotation) : g, v = c.dataset.startScale ? parseFloat(c.dataset.startScale) : f, w = this.entryAnimationEngine.getTiming();
3046
+ oe({
3047
+ element: c,
3048
+ startPosition: d,
3049
+ endPosition: m,
3050
+ pathConfig: this.entryAnimationEngine.getPathConfig(),
3051
+ duration: w.duration,
3052
+ imageWidth: b,
3053
+ imageHeight: p,
3054
+ rotation: g,
3055
+ scale: f,
3056
+ rotationConfig: this.entryAnimationEngine.getRotationConfig(),
3057
+ startRotation: S,
3058
+ scaleConfig: this.entryAnimationEngine.getScaleConfig(),
3059
+ startScale: v
3060
+ });
3061
+ } else {
3062
+ const d = c.dataset.finalTransform || "";
3063
+ c.style.transform = d;
3064
+ }
3065
+ const l = parseInt(c.dataset.imageId || "0");
3066
+ if (this.fullConfig.config.debug?.enabled && l < 3) {
3067
+ const d = c.dataset.finalTransform || "";
3068
+ console.log(`Image ${l} final state:`, {
3069
+ left: c.style.left,
3070
+ top: c.style.top,
3071
+ width: c.style.width,
3072
+ height: c.style.height,
3073
+ computedWidth: c.offsetWidth,
3074
+ computedHeight: c.offsetHeight,
3075
+ transform: d,
3076
+ pathType: this.entryAnimationEngine.getPathType()
3077
+ });
3078
+ }
3079
+ }), r++);
3080
+ }, h = () => {
3081
+ if (this.logDebug("Starting queue processing, enabled:", this.fullConfig.animation.queue.enabled), !this.fullConfig.animation.queue.enabled) {
3082
+ for (; this.displayQueue.length > 0; ) {
3083
+ const c = this.displayQueue.shift();
3084
+ c && a(c);
3085
+ }
3086
+ return;
3087
+ }
3088
+ this.queueInterval !== null && clearInterval(this.queueInterval), this.queueInterval = window.setInterval(() => {
3089
+ if (o !== this.loadGeneration) {
3090
+ this.queueInterval !== null && (clearInterval(this.queueInterval), this.queueInterval = null);
3091
+ return;
3092
+ }
3093
+ if (this.displayQueue.length > 0) {
3094
+ const c = this.displayQueue.shift();
3095
+ c && a(c);
3096
+ }
3097
+ r >= t.length && this.displayQueue.length === 0 && this.queueInterval !== null && (clearInterval(this.queueInterval), this.queueInterval = null);
3098
+ }, this.fullConfig.animation.queue.interval);
3099
+ };
3100
+ if ("IntersectionObserver" in window && this.containerEl) {
3101
+ const c = new IntersectionObserver((u) => {
3102
+ u.forEach((l) => {
3103
+ l.isIntersecting && (h(), c.disconnect());
3104
+ });
3105
+ }, { threshold: 0.1, rootMargin: "50px" });
3106
+ c.observe(this.containerEl);
3107
+ } else
3108
+ h();
3109
+ this.fullConfig.config.debug?.centers && this.containerEl && (this.containerEl.querySelectorAll(".fbn-ic-debug-center").forEach((c) => c.remove()), s.forEach((c, u) => {
3110
+ const l = document.createElement("div");
3111
+ l.className = "fbn-ic-debug-center", l.style.position = "absolute", l.style.width = "12px", l.style.height = "12px", l.style.borderRadius = "50%", l.style.backgroundColor = "red", l.style.border = "2px solid yellow", l.style.zIndex = "9999", l.style.pointerEvents = "none";
3112
+ const d = c.x, m = c.y;
3113
+ l.style.left = `${d - 6}px`, l.style.top = `${m - 6}px`, l.title = `Image ${u}: center (${Math.round(d)}, ${Math.round(m)})`, this.containerEl.appendChild(l);
3114
+ })), t.forEach((c, u) => {
3115
+ const l = document.createElement("img");
3116
+ l.referrerPolicy = "no-referrer", l.classList.add("fbn-ic-image"), l.dataset.imageId = String(u);
3117
+ const d = s[u];
3118
+ l.style.position = "absolute", l.style.width = "auto", l.style.height = `${e}px`, l.style.left = `${d.x}px`, l.style.top = `${d.y}px`, d.zIndex && (l.style.zIndex = String(d.zIndex)), tt(l, this.defaultStyles), et(l, this.defaultClassName), l.addEventListener("mouseenter", () => {
3119
+ this.hoveredImage = { element: l, layout: d }, this.zoomEngine.isInvolved(l) || (tt(l, this.hoverStyles), et(l, this.hoverClassName));
3120
+ }), l.addEventListener("mouseleave", () => {
3121
+ this.hoveredImage = null, this.zoomEngine.isInvolved(l) || (tt(l, this.defaultStyles), zt(l, this.hoverClassName), et(l, this.defaultClassName));
3122
+ }), l.addEventListener("click", (m) => {
3123
+ m.stopPropagation(), this.handleImageClick(l, d);
3124
+ }), l.style.opacity = "0", l.style.transition = this.entryAnimationEngine.getTransitionCSS(), l.onload = () => {
3125
+ if (o !== this.loadGeneration)
3126
+ return;
3127
+ const m = l.naturalWidth / l.naturalHeight, b = e * m;
3128
+ l.style.width = `${b}px`;
3129
+ const p = { x: d.x, y: d.y }, g = { width: b, height: e }, f = this.entryAnimationEngine.calculateStartPosition(
3130
+ p,
3131
+ g,
3132
+ i,
3133
+ u,
3134
+ t.length
3135
+ ), S = this.entryAnimationEngine.calculateStartRotation(d.rotation), v = this.entryAnimationEngine.calculateStartScale(d.scale), w = this.entryAnimationEngine.buildFinalTransform(
3136
+ d.rotation,
3137
+ d.scale,
3138
+ b,
3139
+ e
3140
+ ), E = this.entryAnimationEngine.buildStartTransform(
3141
+ f,
3142
+ p,
3143
+ d.rotation,
3144
+ d.scale,
3145
+ b,
3146
+ e,
3147
+ S,
3148
+ v
3149
+ );
3150
+ this.fullConfig.config.debug?.enabled && u < 3 && console.log(`Image ${u}:`, {
3151
+ finalPosition: p,
3152
+ imageSize: g,
3153
+ left: d.x,
3154
+ top: d.y,
3155
+ finalTransform: w,
3156
+ renderedWidth: b,
3157
+ renderedHeight: e
3158
+ }), l.style.transform = E, l.dataset.finalTransform = w, (this.entryAnimationEngine.requiresJSAnimation() || this.entryAnimationEngine.requiresJSRotation() || this.entryAnimationEngine.requiresJSScale() || S !== d.rotation || v !== d.scale) && (l.dataset.startX = String(f.x), l.dataset.startY = String(f.y), l.dataset.endX = String(p.x), l.dataset.endY = String(p.y), l.dataset.imageWidth = String(b), l.dataset.imageHeight = String(e), l.dataset.rotation = String(d.rotation), l.dataset.scale = String(d.scale), l.dataset.startRotation = String(S), l.dataset.startScale = String(v)), this.displayQueue.push(l);
3159
+ }, l.onerror = () => r++, l.src = c;
3160
+ });
3161
+ }
3162
+ async handleImageClick(t, e) {
3163
+ if (!this.containerEl) return;
3164
+ const i = this.zoomEngine.isFocused(t), o = {
3165
+ width: this.containerEl.offsetWidth,
3166
+ height: this.containerEl.offsetHeight
3167
+ };
3168
+ if (i)
3169
+ await this.zoomEngine.unfocusImage(), this.currentFocusIndex = null, this.swipeEngine?.disable(), this.hideCounter();
3170
+ else {
3171
+ const s = t.dataset.imageId;
3172
+ this.currentFocusIndex = s !== void 0 ? parseInt(s, 10) : null, this.swipeEngine?.enable(), await this.zoomEngine.focusImage(t, o, e), this.currentFocusIndex !== null && this.updateCounter(this.currentFocusIndex);
3173
+ }
3174
+ }
3175
+ /**
3176
+ * Clear the image cloud and reset state
3177
+ */
3178
+ clearImageCloud() {
3179
+ this.queueInterval !== null && (clearInterval(this.queueInterval), this.queueInterval = null), this.loadGeneration++, this.displayQueue = [], this.containerEl && this.containerEl.querySelectorAll(".fbn-ic-image, .fbn-ic-debug-center").forEach((t) => t.remove()), this.imageElements = [], this.imageLayouts = [], this.currentFocusIndex = null, this.hoveredImage = null, this.layoutEngine.reset(), this.zoomEngine.reset(), this.imagesLoaded = !1;
3180
+ }
3181
+ showLoading(t) {
3182
+ !this.fullConfig.rendering.ui.showLoadingSpinner || !this.loadingEl || (t ? this.loadingEl.classList.remove("fbn-ic-hidden") : this.loadingEl.classList.add("fbn-ic-hidden"));
3183
+ }
3184
+ showError(t) {
3185
+ this.errorEl && (this.errorEl.textContent = t, this.errorEl.classList.remove("fbn-ic-hidden"));
3186
+ }
3187
+ hideError() {
3188
+ this.errorEl && this.errorEl.classList.add("fbn-ic-hidden");
3189
+ }
3190
+ updateCounter(t) {
3191
+ !this.fullConfig.rendering.ui.showImageCounter || !this.counterEl || (this.counterEl.textContent = `${t + 1} of ${this.imageElements.length}`, this.counterEl.classList.remove("fbn-ic-hidden"));
3192
+ }
3193
+ hideCounter() {
3194
+ this.counterEl && this.counterEl.classList.add("fbn-ic-hidden");
3195
+ }
3196
+ /**
3197
+ * Destroy the gallery and clean up resources
3198
+ */
3199
+ destroy() {
3200
+ this.clearImageCloud(), this.loadingElAutoCreated && this.loadingEl && (this.loadingEl.remove(), this.loadingEl = null), this.errorElAutoCreated && this.errorEl && (this.errorEl.remove(), this.errorEl = null), this.counterElAutoCreated && this.counterEl && (this.counterEl.remove(), this.counterEl = null), this.resizeTimeout !== null && clearTimeout(this.resizeTimeout), this.swipeEngine?.destroy();
3201
+ }
3202
+ };
3203
+ const He = $t(
3204
+ function({ className: t, style: e, ...i }, o) {
3205
+ const s = wt(null), r = wt(null);
3206
+ return Ut(o, () => ({
3207
+ get instance() {
3208
+ return r.current;
3209
+ }
3210
+ })), Pt(() => {
3211
+ if (!s.current) return;
3212
+ const a = new $e({
3213
+ container: s.current,
3214
+ ...i
3215
+ });
3216
+ return r.current = a, a.init().catch((h) => {
3217
+ console.error("ImageCloud init failed:", h);
3218
+ }), () => {
3219
+ a.destroy(), r.current = null;
3220
+ };
3221
+ }, [JSON.stringify(i)]), /* @__PURE__ */ Ot("div", { ref: s, className: t, style: e });
3222
+ }
3223
+ );
3224
+ export {
3225
+ He as ImageCloud
3226
+ };
3227
+ //# sourceMappingURL=react.js.map