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