@frybynite/image-cloud 0.11.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,18 +4,16 @@
4
4
 
5
5
  A TypeScript library for creating interactive image clouds with animated scattered layouts and zoom effects. Supports multiple image sources (static URLs, JSON endpoints, Google Drive) and layout algorithms.
6
6
 
7
- > [!WARNING]
8
- > ⚠️ All minor versions of this library before 1.0 (e.g., 0.1, 0.2, ...) will include breaking changes during development. Please re-test every time before upgrading until we have published v1.0.
9
-
10
7
  ## Features
11
8
 
12
9
  - ✨ Animated image layouts with smooth transitions
13
10
  - 🎯 Multiple layout algorithms (radial, grid, spiral, cluster, wave, honeycomb, random)
14
11
  - 🎬 Rich entry animations (bounce, elastic, wave paths; spin, wobble rotations)
15
- - 🔍 Zoom/focus interactions with keyboard, swipe, and mouse wheel navigation
12
+ - 🔍 Zoom/focus interactions with keyboard, swipe, and click navigation
16
13
  - 🎨 State-based image styling (borders, shadows, filters for default/hover/focused)
17
14
  - 📱 Responsive design with adaptive sizing
18
15
  - 🖼️ Multiple image sources (static URLs, JSON endpoints, Google Drive, composite loaders)
16
+ - ⚛️ Framework wrappers for React, Vue 3, and Web Components
19
17
  - 🛠️ Interactive configurator for visual configuration
20
18
  - 📦 Zero runtime dependencies
21
19
  - 🔷 Full TypeScript support
@@ -46,7 +44,7 @@ https://unpkg.com/@frybynite/image-cloud@latest/dist/image-cloud-auto-init.js
46
44
  https://unpkg.com/@frybynite/image-cloud@latest/dist/style.css
47
45
  ```
48
46
 
49
- Replace `@latest` with a specific version (e.g., `@0.5.1`) to pin to that release.
47
+ Replace `@latest` with a specific version (e.g., `@1.0.0`) to pin to that release.
50
48
 
51
49
  ## Quick Start
52
50
 
@@ -76,6 +74,72 @@ const cloud = await imageCloud({
76
74
  > await cloud.init();
77
75
  > ```
78
76
 
77
+ ### React
78
+
79
+ ```bash
80
+ npm install @frybynite/image-cloud react react-dom
81
+ ```
82
+
83
+ ```tsx
84
+ import { ImageCloud } from '@frybynite/image-cloud/react';
85
+ import '@frybynite/image-cloud/style.css';
86
+
87
+ function App() {
88
+ return (
89
+ <ImageCloud
90
+ style={{ width: '100%', height: '80vh' }}
91
+ images={[
92
+ 'https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg?auto=compress&w=600',
93
+ 'https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&w=600',
94
+ 'https://images.pexels.com/photos/1402787/pexels-photo-1402787.jpeg?auto=compress&w=600'
95
+ ]}
96
+ layout={{ algorithm: 'radial' }}
97
+ />
98
+ );
99
+ }
100
+ ```
101
+
102
+ ### Vue 3
103
+
104
+ ```bash
105
+ npm install @frybynite/image-cloud vue
106
+ ```
107
+
108
+ ```vue
109
+ <script setup>
110
+ import { ImageCloud } from '@frybynite/image-cloud/vue';
111
+ import '@frybynite/image-cloud/style.css';
112
+
113
+ const options = {
114
+ images: [
115
+ 'https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg?auto=compress&w=600',
116
+ 'https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&w=600',
117
+ 'https://images.pexels.com/photos/1402787/pexels-photo-1402787.jpeg?auto=compress&w=600'
118
+ ],
119
+ layout: { algorithm: 'radial' }
120
+ };
121
+ </script>
122
+
123
+ <template>
124
+ <ImageCloud :options="options" style="width: 100%; height: 80vh" />
125
+ </template>
126
+ ```
127
+
128
+ ### Web Component
129
+
130
+ ```html
131
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@frybynite/image-cloud@latest/dist/style.css">
132
+ <script type="module">
133
+ import '@frybynite/image-cloud/web-component';
134
+ </script>
135
+
136
+ <image-cloud
137
+ style="display: block; width: 100%; height: 80vh"
138
+ images='["https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg?auto=compress&w=600","https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&w=600","https://images.pexels.com/photos/1402787/pexels-photo-1402787.jpeg?auto=compress&w=600"]'
139
+ layout="radial"
140
+ ></image-cloud>
141
+ ```
142
+
79
143
  ### HTML (Auto-initialization)
80
144
 
81
145
  ```html
@@ -111,6 +175,7 @@ Full documentation is available at **[frybynite.github.io/image-cloud](https://f
111
175
  - **[Loaders](https://frybynite.github.io/image-cloud/loaders/)** — Configure image sources (static URLs, JSON endpoints, Google Drive)
112
176
  - **[Layouts](https://frybynite.github.io/image-cloud/layouts/)** — Layout algorithms (radial, grid, spiral, cluster, wave, honeycomb, random)
113
177
  - **[Image Sizing](https://frybynite.github.io/image-cloud/image_sizing/)** — Base sizes, variance, and responsive/adaptive behavior
178
+ - **[Framework Wrappers](https://frybynite.github.io/image-cloud/parameters/#framework-wrappers)** — React, Vue 3, and Web Component usage
114
179
  - **[Parameters](https://frybynite.github.io/image-cloud/parameters/)** — Complete configuration reference
115
180
  - **[API Reference](https://frybynite.github.io/image-cloud/api/)** — TypeScript API documentation
116
181
 
@@ -1,4 +1,4 @@
1
- const Ut = ".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-counter{background:#0009;color:#fff;padding:6px 16px;border-radius:16px;font-family:system-ui,sans-serif;font-size:14px}.fbn-ic-gallery.fbn-ic-suppress-outline.fbn-ic-has-focus{outline:2px solid var(--fbn-ic-accent-primary);outline-offset:-4px}.fbn-ic-nav-btn{background:#0009;color:#fff;width:44px;height:44px;border-radius:50%;font-family:system-ui,sans-serif;font-size:28px;display:flex;align-items:center;justify-content:center;transition:background .2s ease,opacity .2s ease;opacity:.8}.fbn-ic-nav-btn:hover{background:#000000d9;opacity:1}.fbn-ic-hidden{display:none!important}", mt = Object.freeze({
1
+ const Ut = ".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-counter{background:#0009;color:#fff;padding:6px 16px;border-radius:16px;font-family:system-ui,sans-serif;font-size:14px}.fbn-ic-gallery.fbn-ic-suppress-outline.fbn-ic-has-focus{outline:2px solid var(--fbn-ic-accent-primary);outline-offset:-4px}.fbn-ic-nav-btn{background:#0009;color:#fff;width:44px;height:44px;border-radius:50%;font-family:system-ui,sans-serif;font-size:28px;display:flex;align-items:center;justify-content:center;transition:background .2s ease,opacity .2s ease;opacity:.8}.fbn-ic-nav-btn:hover{background:#000000d9;opacity:1}.fbn-ic-hidden{display:none!important}.fbn-ic-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}", mt = Object.freeze({
2
2
  none: "none",
3
3
  sm: "0 2px 4px rgba(0,0,0,0.1)",
4
4
  md: "0 4px 16px rgba(0,0,0,0.4)",
@@ -583,8 +583,8 @@ function le(o) {
583
583
  return;
584
584
  }
585
585
  const R = performance.now(), M = -r / 2, T = -a / 2;
586
- function O(k) {
587
- const D = k - R, z = Math.min(D / s, 1);
586
+ function O(_) {
587
+ const D = _ - R, z = Math.min(D / s, 1);
588
588
  let S;
589
589
  switch (l) {
590
590
  case "bounce": {
@@ -617,11 +617,11 @@ function le(o) {
617
617
  y: J(i.y, e.y, z)
618
618
  };
619
619
  }
620
- const _ = S.x - e.x, $ = S.y - e.y;
620
+ const U = S.x - e.x, k = S.y - e.y;
621
621
  let A;
622
622
  h ? A = re(z, c, b) : g ? A = J(p, c, z) : A = c;
623
623
  let j;
624
- x ? j = ce(z, d, w) : y ? j = J(v, d, z) : j = d, t.style.transform = `translate(${M}px, ${T}px) translate(${_}px, ${$}px) rotate(${A}deg) scale(${j})`, f && z < 1 && f(z, D, {
624
+ x ? j = ce(z, d, w) : y ? j = J(v, d, z) : j = d, t.style.transform = `translate(${M}px, ${T}px) translate(${U}px, ${k}px) rotate(${A}deg) scale(${j})`, f && z < 1 && f(z, D, {
625
625
  x: S.x,
626
626
  y: S.y,
627
627
  rotation: A,
@@ -1243,25 +1243,25 @@ class me {
1243
1243
  }
1244
1244
  let x = 1, w = 1;
1245
1245
  for (; x < t; ) {
1246
- const F = w / b, R = E > 0 ? 1 - F * E * 0.5 : 1, M = Math.max(l * 0.8, y / b * 1.5 / v.tightness), T = w * M, O = T * 1.5, k = Math.PI * (3 * (O + T) - Math.sqrt((3 * O + T) * (O + 3 * T))), D = this.estimateWidth(l), z = Math.floor(k / (D * 0.7));
1246
+ const F = w / b, R = E > 0 ? 1 - F * E * 0.5 : 1, M = Math.max(l * 0.8, y / b * 1.5 / v.tightness), T = w * M, O = T * 1.5, _ = Math.PI * (3 * (O + T) - Math.sqrt((3 * O + T) * (O + 3 * T))), D = this.estimateWidth(l), z = Math.floor(_ / (D * 0.7));
1247
1247
  if (z === 0) {
1248
1248
  w++;
1249
1249
  continue;
1250
1250
  }
1251
- const S = 2 * Math.PI / z, _ = w * (20 * Math.PI / 180);
1252
- for (let $ = 0; $ < z && x < t; $++) {
1253
- const A = $ * S + _, j = p ? this.random(f, m) : 1, N = R * j, P = l * N;
1254
- let H = g + Math.cos(A) * O, U = h + Math.sin(A) * T;
1255
- const B = P * 1.5 / 2, W = P / 2;
1256
- H - B < I ? H = I + B : H + B > s - I && (H = s - I - B), U - W < I ? U = I + W : U + W > r - I && (U = r - I - W);
1251
+ const S = 2 * Math.PI / z, U = w * (20 * Math.PI / 180);
1252
+ for (let k = 0; k < z && x < t; k++) {
1253
+ const A = k * S + U, j = p ? this.random(f, m) : 1, N = R * j, $ = l * N;
1254
+ let H = g + Math.cos(A) * O, P = h + Math.sin(A) * T;
1255
+ const B = $ * 1.5 / 2, W = $ / 2;
1256
+ H - B < I ? H = I + B : H + B > s - I && (H = s - I - B), P - W < I ? P = I + W : P + W > r - I && (P = r - I - W);
1257
1257
  const X = c === "random" ? this.random(d, u) : 0;
1258
1258
  n.push({
1259
1259
  id: x,
1260
1260
  x: H,
1261
- y: U,
1261
+ y: P,
1262
1262
  rotation: X,
1263
1263
  scale: N,
1264
- baseSize: P,
1264
+ baseSize: $,
1265
1265
  zIndex: Math.max(1, 100 - w)
1266
1266
  // Outer rings have lower z-index
1267
1267
  }), x++;
@@ -1335,27 +1335,16 @@ class be {
1335
1335
  v,
1336
1336
  d,
1337
1337
  a
1338
- ), h = a.stagger === "row", b = a.stagger === "column", I = h ? l + 0.5 : l, y = b ? g + 0.5 : g, x = (E - a.gap * (l - 1)) / I, w = (v - a.gap * (g - 1)) / y, F = h ? x / 2 : 0, R = b ? w / 2 : 0, M = 1 + a.overlap, T = Math.min(x, w) * M, O = e.fixedHeight ? Math.min(e.fixedHeight, T) : T, k = l * x + (l - 1) * a.gap + F, D = g * w + (g - 1) * a.gap + R, z = c + (E - k) / 2, S = c + (v - D) / 2, _ = l * g, $ = a.columns !== "auto" && a.rows !== "auto", A = $ && t > _;
1339
- typeof window < "u" && (window.__gridOverflowDebug = {
1340
- gridConfigColumns: a.columns,
1341
- gridConfigRows: a.rows,
1342
- columns: l,
1343
- rows: g,
1344
- cellCount: _,
1345
- hasFixedGrid: $,
1346
- imageCount: t,
1347
- isOverflowMode: A
1348
- });
1349
- const j = A ? new Array(_).fill(0) : [], N = Math.min(x, w) * a.overflowOffset;
1350
- for (let P = 0; P < t; P++) {
1351
- let H, U, Y = 0;
1352
- if (A && P >= _) {
1353
- const q = P - _, G = q % _;
1354
- Y = Math.floor(q / _) + 1, j[G]++, a.fillDirection === "row" ? (H = G % l, U = Math.floor(G / l)) : (U = G % g, H = Math.floor(G / g));
1338
+ ), h = a.stagger === "row", b = a.stagger === "column", I = h ? l + 0.5 : l, y = b ? g + 0.5 : g, x = (E - a.gap * (l - 1)) / I, w = (v - a.gap * (g - 1)) / y, F = h ? x / 2 : 0, R = b ? w / 2 : 0, M = 1 + a.overlap, T = Math.min(x, w) * M, O = e.fixedHeight ? Math.min(e.fixedHeight, T) : T, _ = l * x + (l - 1) * a.gap + F, D = g * w + (g - 1) * a.gap + R, z = c + (E - _) / 2, S = c + (v - D) / 2, U = l * g, A = a.columns !== "auto" && a.rows !== "auto" && t > U, j = A ? new Array(U).fill(0) : [], N = Math.min(x, w) * a.overflowOffset;
1339
+ for (let $ = 0; $ < t; $++) {
1340
+ let H, P, Y = 0;
1341
+ if (A && $ >= U) {
1342
+ const q = $ - U, G = q % U;
1343
+ Y = Math.floor(q / U) + 1, j[G]++, a.fillDirection === "row" ? (H = G % l, P = Math.floor(G / l)) : (P = G % g, H = Math.floor(G / g));
1355
1344
  } else
1356
- a.fillDirection === "row" ? (H = P % l, U = Math.floor(P / l)) : (U = P % g, H = Math.floor(P / g));
1357
- let B = z + H * (x + a.gap) + x / 2, W = S + U * (w + a.gap) + w / 2;
1358
- if (a.stagger === "row" && U % 2 === 1 ? B += x / 2 : a.stagger === "column" && H % 2 === 1 && (W += w / 2), Y > 0) {
1345
+ a.fillDirection === "row" ? (H = $ % l, P = Math.floor($ / l)) : (P = $ % g, H = Math.floor($ / g));
1346
+ let B = z + H * (x + a.gap) + x / 2, W = S + P * (w + a.gap) + w / 2;
1347
+ if (a.stagger === "row" && P % 2 === 1 ? B += x / 2 : a.stagger === "column" && H % 2 === 1 && (W += w / 2), Y > 0) {
1359
1348
  const q = (Y - 1) % Tt.length, G = Tt[q];
1360
1349
  B += G.x * N, W += G.y * N;
1361
1350
  }
@@ -1366,10 +1355,10 @@ class be {
1366
1355
  let X = B, V = W;
1367
1356
  if (!A && a.fillDirection === "row") {
1368
1357
  const q = t % l || l;
1369
- if (U === Math.floor((t - 1) / l) && q < l) {
1358
+ if (P === Math.floor((t - 1) / l) && q < l) {
1370
1359
  const It = q * x + (q - 1) * a.gap;
1371
1360
  let gt = 0;
1372
- a.alignment === "center" ? gt = (k - It) / 2 : a.alignment === "end" && (gt = k - It), X += gt;
1361
+ a.alignment === "center" ? gt = (_ - It) / 2 : a.alignment === "end" && (gt = _ - It), X += gt;
1373
1362
  }
1374
1363
  }
1375
1364
  const ct = p ? this.random(f, m) : 1, K = O * ct, nt = K * 1.5 / 2, ot = K / 2, ht = c + nt, dt = s - c - nt, Pt = c + ot, _t = r - c - ot;
@@ -1380,8 +1369,8 @@ class be {
1380
1369
  a.jitter > 0 ? ut = this.random(q * a.jitter, G * a.jitter) : ut = this.random(q, G);
1381
1370
  }
1382
1371
  let ft;
1383
- A && Y > 0 ? ft = 50 - Y : ft = A ? 100 + P : P + 1, n.push({
1384
- id: P,
1372
+ A && Y > 0 ? ft = 50 - Y : ft = A ? 100 + $ : $ + 1, n.push({
1373
+ id: $,
1385
1374
  x: X,
1386
1375
  y: V,
1387
1376
  rotation: ut,
@@ -1451,19 +1440,19 @@ class xe {
1451
1440
  const B = y * 0.3 * a.tightness;
1452
1441
  x = B * I + a.startAngle, w = this.calculateLogarithmicRadius(B, t, b, a.tightness);
1453
1442
  }
1454
- const F = g + Math.cos(x) * w, R = h + Math.sin(x) * w, M = w / b, T = l > 0 ? 1 - M * l * 0.5 : 1, O = v ? this.random(p, E) : 1, k = T * O, D = d * k, S = D * 1.5 / 2, _ = D / 2, $ = c + S, A = s - c - S, j = c + _, N = r - c - _, P = Math.max($, Math.min(F, A)), H = Math.max(j, Math.min(R, N));
1455
- let U = 0;
1443
+ const F = g + Math.cos(x) * w, R = h + Math.sin(x) * w, M = w / b, T = l > 0 ? 1 - M * l * 0.5 : 1, O = v ? this.random(p, E) : 1, _ = T * O, D = d * _, S = D * 1.5 / 2, U = D / 2, k = c + S, A = s - c - S, j = c + U, N = r - c - U, $ = Math.max(k, Math.min(F, A)), H = Math.max(j, Math.min(R, N));
1444
+ let P = 0;
1456
1445
  if (u === "random") {
1457
1446
  const B = x * 180 / Math.PI % 360, W = this.random(f, m);
1458
- U = a.spiralType === "golden" ? W : B * 0.1 + W * 0.9;
1459
- } else u === "tangent" && (U = this.calculateSpiralTangent(x, w, a));
1447
+ P = a.spiralType === "golden" ? W : B * 0.1 + W * 0.9;
1448
+ } else u === "tangent" && (P = this.calculateSpiralTangent(x, w, a));
1460
1449
  const Y = t - y;
1461
1450
  n.push({
1462
1451
  id: y,
1463
- x: P,
1452
+ x: $,
1464
1453
  y: H,
1465
- rotation: U,
1466
- scale: k,
1454
+ rotation: P,
1455
+ scale: _,
1467
1456
  baseSize: D,
1468
1457
  zIndex: Y
1469
1458
  });
@@ -1561,22 +1550,22 @@ class Ee {
1561
1550
  if (a.distribution === "gaussian")
1562
1551
  F = this.gaussianRandom() * y.spread, R = this.gaussianRandom() * y.spread;
1563
1552
  else {
1564
- const U = this.random(0, Math.PI * 2), Y = this.random(0, y.spread);
1565
- F = Math.cos(U) * Y, R = Math.sin(U) * Y;
1553
+ const P = this.random(0, Math.PI * 2), Y = this.random(0, y.spread);
1554
+ F = Math.cos(P) * Y, R = Math.sin(P) * Y;
1566
1555
  }
1567
1556
  const M = 1 + a.overlap * 0.5, T = 1 + a.overlap * 0.3;
1568
1557
  F /= M, R /= M;
1569
- const O = v ? this.random(p, E) : 1, k = T * O, D = d * k;
1558
+ const O = v ? this.random(p, E) : 1, _ = T * O, D = d * _;
1570
1559
  let z = y.x + F, S = y.y + R;
1571
- const $ = D * 1.5 / 2, A = D / 2;
1572
- z = Math.max(c + $, Math.min(z, s - c - $)), S = Math.max(c + A, Math.min(S, r - c - A));
1573
- const j = u === "random" ? this.random(f, m) : 0, P = Math.sqrt(F * F + R * R) / y.spread, H = Math.round((1 - P) * 50) + 1;
1560
+ const k = D * 1.5 / 2, A = D / 2;
1561
+ z = Math.max(c + k, Math.min(z, s - c - k)), S = Math.max(c + A, Math.min(S, r - c - A));
1562
+ const j = u === "random" ? this.random(f, m) : 0, $ = Math.sqrt(F * F + R * R) / y.spread, H = Math.round((1 - $) * 50) + 1;
1574
1563
  n.push({
1575
1564
  id: b,
1576
1565
  x: z,
1577
1566
  y: S,
1578
1567
  rotation: j,
1579
- scale: k,
1568
+ scale: _,
1580
1569
  baseSize: D,
1581
1570
  zIndex: H
1582
1571
  }), b++;
@@ -1659,26 +1648,26 @@ class Ie {
1659
1648
  const n = [], { width: s, height: r } = i, a = e.fixedHeight ?? 200, c = this.config.spacing.padding ?? 50, d = this.imageConfig.rotation?.mode ?? "none", u = this.imageConfig.rotation?.range?.min ?? -15, f = this.imageConfig.rotation?.range?.max ?? 15, m = this.imageConfig.sizing?.variance?.min ?? 1, p = this.imageConfig.sizing?.variance?.max ?? 1, E = m !== 1 || p !== 1, v = e.fixedHeight ?? a, l = {
1660
1649
  ...Ht,
1661
1650
  ...this.config.wave
1662
- }, { rows: g, amplitude: h, frequency: b, phaseShift: I, synchronization: y } = l, x = Math.ceil(t / g), R = v * 1.5 / 2, M = c + R, T = s - c - R, O = T - M, k = x > 1 ? O / (x - 1) : 0, D = c + h + v / 2, z = r - c - h - v / 2, S = z - D, _ = g > 1 ? S / (g - 1) : 0;
1663
- let $ = 0;
1664
- for (let A = 0; A < g && $ < t; A++) {
1665
- const j = g === 1 ? (D + z) / 2 : D + A * _;
1651
+ }, { rows: g, amplitude: h, frequency: b, phaseShift: I, synchronization: y } = l, x = Math.ceil(t / g), R = v * 1.5 / 2, M = c + R, T = s - c - R, O = T - M, _ = x > 1 ? O / (x - 1) : 0, D = c + h + v / 2, z = r - c - h - v / 2, S = z - D, U = g > 1 ? S / (g - 1) : 0;
1652
+ let k = 0;
1653
+ for (let A = 0; A < g && k < t; A++) {
1654
+ const j = g === 1 ? (D + z) / 2 : D + A * U;
1666
1655
  let N = 0;
1667
1656
  y === "offset" ? N = A * I : y === "alternating" && (N = A * Math.PI);
1668
- for (let P = 0; P < x && $ < t; P++) {
1669
- const H = x === 1 ? (M + T) / 2 : M + P * k, U = this.calculateWaveY(H, s, h, b, N), Y = H, B = j + U, W = E ? this.random(m, p) : 1, X = v * W;
1657
+ for (let $ = 0; $ < x && k < t; $++) {
1658
+ const H = x === 1 ? (M + T) / 2 : M + $ * _, P = this.calculateWaveY(H, s, h, b, N), Y = H, B = j + P, W = E ? this.random(m, p) : 1, X = v * W;
1670
1659
  let V = 0;
1671
1660
  d === "tangent" ? V = this.calculateRotation(H, s, h, b, N) : d === "random" && (V = this.random(u, f));
1672
1661
  const K = X * 1.5 / 2, lt = X / 2, nt = c + K, ot = s - c - K, ht = c + lt, dt = r - c - lt;
1673
1662
  n.push({
1674
- id: $,
1663
+ id: k,
1675
1664
  x: Math.max(nt, Math.min(Y, ot)),
1676
1665
  y: Math.max(ht, Math.min(B, dt)),
1677
1666
  rotation: V,
1678
1667
  scale: W,
1679
1668
  baseSize: X,
1680
- zIndex: $ + 1
1681
- }), $++;
1669
+ zIndex: k + 1
1670
+ }), k++;
1682
1671
  }
1683
1672
  }
1684
1673
  return n;
@@ -3221,7 +3210,7 @@ function Ke() {
3221
3210
  }
3222
3211
  class Ze {
3223
3212
  constructor(t = {}) {
3224
- this.fullConfig = Kt(t), t.container instanceof HTMLElement ? (this.containerRef = t.container, this.containerId = null) : (this.containerRef = null, this.containerId = t.container || "imageCloud"), this.callbacks = t.on ?? {}, this.imagesLoaded = !1, this.imageElements = [], this.imageLayouts = [], this.currentImageHeight = 225, this.currentFocusIndex = null, this.hoveredImage = null, this.resizeTimeout = null, this.displayQueue = [], this.queueInterval = null, this.loadGeneration = 0, this.loadingElAutoCreated = !1, this.errorElAutoCreated = !1, this.counterEl = null, this.counterElAutoCreated = !1, this.prevButtonEl = null, this.nextButtonEl = null, this.prevButtonElAutoCreated = !1, this.nextButtonElAutoCreated = !1, this.animationEngine = new ee(this.fullConfig.animation), this.layoutEngine = new Me({
3213
+ this.ariaLiveEl = null, this.fullConfig = Kt(t), t.container instanceof HTMLElement ? (this.containerRef = t.container, this.containerId = null) : (this.containerRef = null, this.containerId = t.container || "imageCloud"), this.callbacks = t.on ?? {}, this.imagesLoaded = !1, this.imageElements = [], this.imageLayouts = [], this.currentImageHeight = 225, this.currentFocusIndex = null, this.hoveredImage = null, this.resizeTimeout = null, this.displayQueue = [], this.queueInterval = null, this.loadGeneration = 0, this.loadingElAutoCreated = !1, this.errorElAutoCreated = !1, this.counterEl = null, this.counterElAutoCreated = !1, this.prevButtonEl = null, this.nextButtonEl = null, this.prevButtonElAutoCreated = !1, this.nextButtonElAutoCreated = !1, this.animationEngine = new ee(this.fullConfig.animation), this.layoutEngine = new Me({
3225
3214
  layout: this.fullConfig.layout,
3226
3215
  image: this.fullConfig.image
3227
3216
  }), this.zoomEngine = new ke(this.fullConfig.interaction.focus, this.animationEngine, this.fullConfig.styling), this.defaultStyles = it(this.fullConfig.styling?.default), this.defaultClassName = this.fullConfig.styling?.default?.className, this.hoverClassName = this.fullConfig.styling?.hover?.className;
@@ -3311,7 +3300,7 @@ class Ze {
3311
3300
  this.containerEl = this.containerRef;
3312
3301
  else if (this.containerEl = document.getElementById(this.containerId), !this.containerEl)
3313
3302
  throw new Error(`Container "#${this.containerId}" not found. Ensure an element with id="${this.containerId}" exists in the DOM before calling imageCloud().`);
3314
- this.containerEl.classList.add("fbn-ic-gallery"), this.containerEl.setAttribute("tabindex", "0"), this.fullConfig.interaction.navigation?.swipe !== !1 && (this.swipeEngine = new bt(this.containerEl, {
3303
+ this.containerEl.classList.add("fbn-ic-gallery"), this.containerEl.setAttribute("tabindex", "0"), this.containerEl.setAttribute("role", "region"), this.containerEl.setAttribute("aria-label", "Image gallery"), this.fullConfig.interaction.navigation?.swipe !== !1 && (this.swipeEngine = new bt(this.containerEl, {
3315
3304
  onNext: () => this.navigateToNextImage(),
3316
3305
  onPrev: () => this.navigateToPreviousImage(),
3317
3306
  onDragOffset: (t) => this.zoomEngine.setDragOffset(t),
@@ -3329,7 +3318,7 @@ class Ze {
3329
3318
  i.stopPropagation(), this.navigateToPreviousImage();
3330
3319
  }), this.nextButtonEl?.addEventListener("click", (i) => {
3331
3320
  i.stopPropagation(), this.navigateToNextImage();
3332
- }));
3321
+ })), this.ariaLiveEl = document.createElement("div"), this.ariaLiveEl.setAttribute("aria-live", "polite"), this.ariaLiveEl.setAttribute("aria-atomic", "true"), this.ariaLiveEl.className = "fbn-ic-sr-only", this.containerEl.appendChild(this.ariaLiveEl);
3333
3322
  }
3334
3323
  resolveElement(t) {
3335
3324
  return t instanceof HTMLElement ? t : document.getElementById(t);
@@ -3352,11 +3341,19 @@ class Ze {
3352
3341
  }
3353
3342
  createDefaultPrevButtonElement() {
3354
3343
  const t = document.createElement("button");
3355
- return t.className = "fbn-ic-nav-btn fbn-ic-nav-btn-prev fbn-ic-hidden", t.textContent = "‹", t.setAttribute("aria-label", "Previous image"), t.setAttribute("tabindex", "-1"), this.containerEl.appendChild(t), t;
3344
+ return t.className = "fbn-ic-nav-btn fbn-ic-nav-btn-prev fbn-ic-hidden", t.textContent = "‹", t.setAttribute("aria-label", "Previous image"), this.containerEl.appendChild(t), t;
3356
3345
  }
3357
3346
  createDefaultNextButtonElement() {
3358
3347
  const t = document.createElement("button");
3359
- return t.className = "fbn-ic-nav-btn fbn-ic-nav-btn-next fbn-ic-hidden", t.textContent = "›", t.setAttribute("aria-label", "Next image"), t.setAttribute("tabindex", "-1"), this.containerEl.appendChild(t), t;
3348
+ return t.className = "fbn-ic-nav-btn fbn-ic-nav-btn-next fbn-ic-hidden", t.textContent = "›", t.setAttribute("aria-label", "Next image"), this.containerEl.appendChild(t), t;
3349
+ }
3350
+ getImageAlt(t, i) {
3351
+ try {
3352
+ const s = (new URL(t).pathname.split("/").pop() ?? "").replace(/\.[^.]+$/, "").replace(/[-_]/g, " ").trim();
3353
+ return s.length > 0 ? s : `Image ${i + 1}`;
3354
+ } catch {
3355
+ return `Image ${i + 1}`;
3356
+ }
3360
3357
  }
3361
3358
  setupEventListeners() {
3362
3359
  this.fullConfig.interaction.navigation?.keyboard !== !1 && this.containerEl.addEventListener("keydown", (t) => {
@@ -3473,7 +3470,7 @@ class Ze {
3473
3470
  }, E = (l) => {
3474
3471
  this.containerEl && (this.containerEl.appendChild(l), this.imageElements.push(l), requestAnimationFrame(async () => {
3475
3472
  l.offsetWidth, l.style.opacity = this.defaultStyles.opacity ?? "1";
3476
- const g = parseInt(l.dataset.imageId || "0"), h = this.imageLayouts[g], b = this.entryAnimationEngine.getTiming(), I = performance.now(), y = parseFloat(l.dataset.startX || "0"), x = parseFloat(l.dataset.startY || "0"), w = parseFloat(l.dataset.endX || "0"), F = parseFloat(l.dataset.endY || "0"), R = parseFloat(l.dataset.rotation || "0"), M = parseFloat(l.dataset.scale || "1"), T = parseFloat(l.dataset.startRotation || l.dataset.rotation || "0"), O = parseFloat(l.dataset.startScale || l.dataset.scale || "1"), k = parseFloat(l.dataset.imageWidth || "0"), D = parseFloat(l.dataset.imageHeight || "0");
3473
+ const g = parseInt(l.dataset.imageId || "0"), h = this.imageLayouts[g], b = this.entryAnimationEngine.getTiming(), I = performance.now(), y = parseFloat(l.dataset.startX || "0"), x = parseFloat(l.dataset.startY || "0"), w = parseFloat(l.dataset.endX || "0"), F = parseFloat(l.dataset.endY || "0"), R = parseFloat(l.dataset.rotation || "0"), M = parseFloat(l.dataset.scale || "1"), T = parseFloat(l.dataset.startRotation || l.dataset.rotation || "0"), O = parseFloat(l.dataset.startScale || l.dataset.scale || "1"), _ = parseFloat(l.dataset.imageWidth || "0"), D = parseFloat(l.dataset.imageHeight || "0");
3477
3474
  if (this.callbacks.onEntryStart && h) {
3478
3475
  const S = {
3479
3476
  element: l,
@@ -3494,7 +3491,7 @@ class Ze {
3494
3491
  endPosition: { x: w, y: F },
3495
3492
  pathConfig: this.entryAnimationEngine.getPathConfig(),
3496
3493
  duration: b.duration,
3497
- imageWidth: k,
3494
+ imageWidth: _,
3498
3495
  imageHeight: D,
3499
3496
  rotation: R,
3500
3497
  scale: M,
@@ -3502,7 +3499,7 @@ class Ze {
3502
3499
  startRotation: T,
3503
3500
  scaleConfig: this.entryAnimationEngine.getScaleConfig(),
3504
3501
  startScale: O,
3505
- onProgress: this.callbacks.onEntryProgress && h ? (S, _, $) => {
3502
+ onProgress: this.callbacks.onEntryProgress && h ? (S, U, k) => {
3506
3503
  const A = {
3507
3504
  element: l,
3508
3505
  index: g,
@@ -3514,8 +3511,8 @@ class Ze {
3514
3511
  duration: b.duration,
3515
3512
  progress: S,
3516
3513
  rawProgress: S,
3517
- elapsed: _,
3518
- current: $
3514
+ elapsed: U,
3515
+ current: k
3519
3516
  };
3520
3517
  this.callbacks.onEntryProgress(A);
3521
3518
  } : void 0,
@@ -3536,9 +3533,9 @@ class Ze {
3536
3533
  else {
3537
3534
  const S = l.dataset.finalTransform || "";
3538
3535
  if (l.style.transform = S, this.callbacks.onEntryComplete && h) {
3539
- const _ = ($) => {
3540
- if ($.propertyName !== "transform") return;
3541
- l.removeEventListener("transitionend", _);
3536
+ const U = (k) => {
3537
+ if (k.propertyName !== "transform") return;
3538
+ l.removeEventListener("transitionend", U);
3542
3539
  const A = {
3543
3540
  element: l,
3544
3541
  index: g,
@@ -3549,7 +3546,7 @@ class Ze {
3549
3546
  };
3550
3547
  this.callbacks.onEntryComplete(A);
3551
3548
  };
3552
- l.addEventListener("transitionend", _);
3549
+ l.addEventListener("transitionend", U);
3553
3550
  }
3554
3551
  }
3555
3552
  if (this.fullConfig.config.debug?.enabled && g < 3) {
@@ -3606,7 +3603,7 @@ class Ze {
3606
3603
  h.style.left = `${b - 6}px`, h.style.top = `${I - 6}px`, h.title = `Image ${g}: center (${Math.round(b)}, ${Math.round(I)})`, this.containerEl.appendChild(h);
3607
3604
  })), t.forEach((l, g) => {
3608
3605
  const h = document.createElement("img");
3609
- h.referrerPolicy = "no-referrer", h.classList.add("fbn-ic-image"), this.fullConfig.interaction.dragging === !1 && (h.draggable = !1), h.dataset.imageId = String(g), h.dataset.createdFlag = "true";
3606
+ h.referrerPolicy = "no-referrer", h.classList.add("fbn-ic-image"), h.alt = this.getImageAlt(l, g), this.fullConfig.interaction.dragging === !1 && (h.draggable = !1), h.dataset.imageId = String(g);
3610
3607
  const b = s[g];
3611
3608
  h.style.position = "absolute", h.style.width = "auto", h.style.height = `${i}px`, h.style.left = `${b.x}px`, h.style.top = `${b.y}px`, b.zIndex && (h.style.zIndex = String(b.zIndex)), et(h, this.defaultClassName), h.addEventListener("mouseenter", () => {
3612
3609
  if (this.hoveredImage = { element: h, layout: b }, !this.zoomEngine.isInvolved(h)) {
@@ -3632,7 +3629,7 @@ class Ze {
3632
3629
  if (n !== this.loadGeneration)
3633
3630
  return;
3634
3631
  const y = h.naturalWidth / h.naturalHeight, x = i * y;
3635
- h.dataset.onloadCalled = "true", window.DEBUG_CLIPPATH && console.log(`[onload #${g}] Called with imageHeight=${i}, renderedWidth=${x}`), h.style.width = `${x}px`, h.cachedRenderedWidth = x, h.aspectRatio = y, st(h, this.fullConfig.styling?.default, i, x);
3632
+ h.style.width = `${x}px`, h.cachedRenderedWidth = x, h.aspectRatio = y, st(h, this.fullConfig.styling?.default, i, x);
3636
3633
  const w = { x: b.x, y: b.y }, F = { width: x, height: i }, R = this.entryAnimationEngine.calculateStartPosition(
3637
3634
  w,
3638
3635
  F,
@@ -3644,7 +3641,7 @@ class Ze {
3644
3641
  b.scale,
3645
3642
  x,
3646
3643
  i
3647
- ), k = this.entryAnimationEngine.buildStartTransform(
3644
+ ), _ = this.entryAnimationEngine.buildStartTransform(
3648
3645
  R,
3649
3646
  w,
3650
3647
  b.rotation,
@@ -3662,7 +3659,7 @@ class Ze {
3662
3659
  finalTransform: O,
3663
3660
  renderedWidth: x,
3664
3661
  renderedHeight: i
3665
- }), h.style.transform = k, h.dataset.finalTransform = O, h.dataset.startX = String(R.x), h.dataset.startY = String(R.y), h.dataset.endX = String(w.x), h.dataset.endY = String(w.y), h.dataset.imageWidth = String(x), h.dataset.imageHeight = String(i), h.dataset.rotation = String(b.rotation), h.dataset.scale = String(b.scale), h.dataset.startRotation = String(M), h.dataset.startScale = String(T), a++, this.callbacks.onImageLoaded) {
3662
+ }), h.style.transform = _, h.dataset.finalTransform = O, h.dataset.startX = String(R.x), h.dataset.startY = String(R.y), h.dataset.endX = String(w.x), h.dataset.endY = String(w.y), h.dataset.imageWidth = String(x), h.dataset.imageHeight = String(i), h.dataset.rotation = String(b.rotation), h.dataset.scale = String(b.scale), h.dataset.startRotation = String(M), h.dataset.startScale = String(T), a++, this.callbacks.onImageLoaded) {
3666
3663
  const D = {
3667
3664
  element: h,
3668
3665
  url: l,
@@ -3712,8 +3709,8 @@ class Ze {
3712
3709
  const M = await (await fetch(F, w.fetch)).blob(), T = URL.createObjectURL(M);
3713
3710
  y = T;
3714
3711
  const O = h.onload;
3715
- h.onload = (k) => {
3716
- URL.revokeObjectURL(T), O?.call(h, k);
3712
+ h.onload = (_) => {
3713
+ URL.revokeObjectURL(T), O?.call(h, _);
3717
3714
  };
3718
3715
  } catch {
3719
3716
  I();
@@ -3732,11 +3729,15 @@ class Ze {
3732
3729
  height: this.containerEl.offsetHeight
3733
3730
  };
3734
3731
  if (e)
3735
- await this.zoomEngine.unfocusImage(), this.currentFocusIndex = null, this.swipeEngine?.disable(), this.hideCounter(), this.hideNavButtons(), this.hideFocusIndicator();
3732
+ await this.zoomEngine.unfocusImage(), this.currentFocusIndex = null, this.swipeEngine?.disable(), this.hideCounter(), this.hideNavButtons(), this.ariaLiveEl && (this.ariaLiveEl.textContent = ""), this.hideFocusIndicator();
3736
3733
  else {
3737
3734
  this.idleAnimationEngine?.pauseForImage(t);
3738
3735
  const s = t.dataset.imageId;
3739
- if (this.currentFocusIndex = s !== void 0 ? parseInt(s, 10) : null, this.swipeEngine?.enable(), this.containerEl?.focus({ preventScroll: !0 }), await this.zoomEngine.focusImage(t, n, i), this.currentFocusIndex !== null && this.updateCounter(this.currentFocusIndex), this.showNavButtons(), this.showFocusIndicator(), this.callbacks.onImageFocus && this.currentFocusIndex !== null) {
3736
+ if (this.currentFocusIndex = s !== void 0 ? parseInt(s, 10) : null, this.swipeEngine?.enable(), this.containerEl?.focus({ preventScroll: !0 }), await this.zoomEngine.focusImage(t, n, i), this.currentFocusIndex !== null && this.updateCounter(this.currentFocusIndex), this.showNavButtons(), this.ariaLiveEl && this.currentFocusIndex !== null) {
3737
+ const r = t.alt, a = this.imageLayouts.length;
3738
+ this.ariaLiveEl.textContent = `Image ${this.currentFocusIndex + 1} of ${a}: ${r}`;
3739
+ }
3740
+ if (this.showFocusIndicator(), this.callbacks.onImageFocus && this.currentFocusIndex !== null) {
3740
3741
  const r = this.imageLoader.imageURLs(), a = {
3741
3742
  element: t,
3742
3743
  index: this.currentFocusIndex,