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