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