@frybynite/image-cloud 0.4.1 → 0.5.0

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