neiki-gallery 2.0.0 → 3.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 neikiri
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neikiri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <img src="https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge&logo=css&logoColor=white" alt="CSS">
11
11
  <br>
12
12
  <img src="https://img.shields.io/badge/License-MIT-2563EB?style=for-the-badge&logo=open-source-initiative&logoColor=white&labelColor=000F15&logoWidth=20" alt="License">
13
- <img src="https://img.shields.io/badge/Version-2.0.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
13
+ <img src="https://img.shields.io/badge/Version-3.0.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
14
14
  </p>
15
15
 
16
16
  <p align="center">
@@ -55,18 +55,43 @@
55
55
  - **Multiple instances** — independent galleries on one page
56
56
  - **Dark & light themes** — CSS custom properties + accent color system (`--neiki-accent`)
57
57
  - **Accessibility** — ARIA attributes, focus management, `prefers-reduced-motion`
58
- - **Event system** — `open`, `close`, `change`, `filter`, `select`, `slideshowStart`, `slideshowStop`
59
- - **Cross-browser** — Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
58
+ - **Event system** — `open`, `close`, `change`, `filter`, `select`, `slideshowStart`, `slideshowStop`, `favorite`, `unfavorite`, `infoOpen`, `infoClose`, `print`, `editorOpen`, `editorExport`, `annotateOpen`, `annotateExport`, `append`, `remove`
59
+ - **Blurhash placeholders** — `data-blurhash` attribute decoded into blurred preview (replaces shimmer)
60
+ - **Story mode** — Instagram-like vertical fullscreen viewer with progress bars and tap navigation
61
+ - **Picture-in-Picture** — minimize lightbox to a resizable corner window
62
+ - **Image focus point** — `data-focus="0.3 0.7"` controls `object-position` for smart cropping
63
+ - **EXIF overlay** — camera model, focal length, aperture, shutter speed, ISO from JPEG binary
64
+ - **Color palette extraction** — k-means quantized 5-color palette strip in lightbox
65
+ - **Backdrop tint** — overlay adapts to dominant color of current image
66
+ - **FLIP morph transition** — smooth thumbnail-to-lightbox animation
67
+ - **Virtual scrolling** — `content-visibility` virtualization for large galleries (50+ items)
68
+ - **Drag & drop reorder** — HTML5 drag to reorder grid items
69
+ - **Aspect-ratio skeleton** — `data-width`/`data-height` for placeholder sizing
70
+ - **Web Share API** — native mobile sharing with clipboard fallback
71
+ - **Video & embeds** — auto-detect MP4/WebM/Ogg/MOV/M4V, YouTube and Vimeo URLs in lightbox
72
+ - **Plugin system** — `NeikiGallery.registerPlugin()` with `init`/`open`/`change`/`close`/`destroy` lifecycle hooks
73
+ - **Album groups** — `data-group="album"` links galleries; navigation traverses across all
74
+ - **Favorites** — heart button + localStorage persistence + `B` key shortcut
75
+ - **Info panel** — slide-out sidebar with metadata, EXIF, source URL (`I` key)
76
+ - **Image editor** — rotate / flip / export to PNG via `<canvas>`
77
+ - **Annotation layer** — freehand drawing with color picker, brush size, undo, clear, export
78
+ - **Print** — print current image with caption (`P` key)
79
+ - **Right-click context menu** — open original, copy link, download, share, print, favorite
80
+ - **Infinite scroll** — `loadMore` callback + `append()` / `remove()` API
81
+ - **Keyboard shortcuts overlay** — press `?` to view all shortcuts
82
+ - **System theme detection** — `theme: 'system'` follows OS `prefers-color-scheme`
83
+ - **Kenburns slideshow** — slow zoom-and-pan animation per slide
84
+ - **Configurable counter** — `counterFormat` with `{current}`/`{total}`/`{percent}` tokens
85
+ - **Cross-browser** — Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
60
86
 
61
87
  ## 🚀 Quick Start
62
88
 
63
89
  ### CDN (Recommended)
64
90
 
65
- Add these two lines to your HTML — that's all you need:
91
+ Add this single line to your HTML — that's all you need (CSS is included automatically):
66
92
 
67
93
  ```html
68
- <link rel="stylesheet" href="http://cdn.neiki.eu/neiki-gallery/neiki-gallery.min.css">
69
- <script src="http://cdn.neiki.eu/neiki-gallery/neiki-gallery.min.js"></script>
94
+ <script src="https://cdn.neiki.eu/neiki-gallery/neiki-gallery.min.js"></script>
70
95
  ```
71
96
 
72
97
  Then create your gallery markup:
@@ -84,16 +109,15 @@ Then create your gallery markup:
84
109
 
85
110
  That's it — galleries with `data-neiki-gallery` auto-initialize on page load.
86
111
 
87
- > **Unminified files** are also available if you need them for debugging:
112
+ > **Unminified files** are also available if you need them for debugging (require separate CSS):
88
113
  > ```
89
- > http://cdn.neiki.eu/neiki-gallery/neiki-gallery.js
90
- > http://cdn.neiki.eu/neiki-gallery/neiki-gallery.css
114
+ > https://cdn.neiki.eu/neiki-gallery/neiki-gallery.js
115
+ > https://cdn.neiki.eu/neiki-gallery/neiki-gallery.css
91
116
  > ```
92
117
 
93
118
  > **Pin a specific version** to avoid unexpected changes:
94
119
  > ```
95
- > http://cdn.neiki.eu/neiki-gallery/2.0.0/neiki-gallery.min.js
96
- > http://cdn.neiki.eu/neiki-gallery/2.0.0/neiki-gallery.min.css
120
+ > https://cdn.neiki.eu/neiki-gallery/3.0.0/neiki-gallery.min.js
97
121
  > ```
98
122
 
99
123
  ### Self-Hosting (Local)
@@ -104,20 +128,26 @@ Download or clone the repository from GitHub:
104
128
  git clone https://github.com/neikiri/neiki-gallery.git
105
129
  ```
106
130
 
107
- Copy the files from the `dist/` folder into your project and link them directly:
131
+ For production, you only need one file CSS is already included:
108
132
 
109
133
  ```html
110
- <link rel="stylesheet" href="path/to/neiki-gallery.min.css">
111
134
  <script src="path/to/neiki-gallery.min.js"></script>
112
135
  ```
113
136
 
114
- The `dist/` folder contains both minified and unminified versions:
137
+ For development / debugging, use the unminified versions (requires separate CSS):
138
+
139
+ ```html
140
+ <link rel="stylesheet" href="path/to/neiki-gallery.css">
141
+ <script src="path/to/neiki-gallery.js"></script>
142
+ ```
143
+
144
+ The `dist/` folder contains:
115
145
 
116
146
  | File | Description |
117
147
  |------|-------------|
118
- | `neiki-gallery.min.js` | Minified JS (production) |
119
- | `neiki-gallery.min.css` | Minified CSS (production) |
120
- | `neiki-gallery.js` | Full JS (development / debugging) |
148
+ | `neiki-gallery.min.js` | Minified JS + CSS included (production) |
149
+ | `neiki-gallery.min.css` | Minified CSS standalone (optional) |
150
+ | `neiki-gallery.js` | Full JS without CSS (development / debugging) |
121
151
  | `neiki-gallery.css` | Full CSS (development / debugging) |
122
152
 
123
153
  ### Manual Initialization
@@ -131,14 +161,45 @@ const gallery = new NeikiGallery('#my-gallery', {
131
161
  contextualZoom: false, // zoom to click point
132
162
  fullscreen: true,
133
163
  transition: 'fade', // 'fade' | 'slide'
134
- theme: 'dark', // 'dark' | 'light'
164
+ theme: 'system', // 'dark' | 'light' | 'system' (auto-detect) ← v3 default
135
165
  hashNavigation: true,
136
- stagger: true, // entrance animation
137
- slideshow: false, // auto-start slideshow on open
138
- slideshowInterval: 4000, // ms between slides
139
- share: true, // share/download popup
140
- filter: false, // tag filtering
141
- batchSelect: false, // Shift+click multi-select
166
+ counter: true,
167
+ counterFormat: '{current} / {total}', // v3 tokens: {current}, {total}, {percent}
168
+ stagger: true,
169
+ slideshow: { // v3 — nested config (boolean still works)
170
+ interval: 4000,
171
+ pauseOnHover: true,
172
+ kenburns: false,
173
+ direction: 'forward' // or 'reverse'
174
+ },
175
+ share: true,
176
+ filter: false,
177
+ batchSelect: false,
178
+ // v2.1.0
179
+ focusPoint: true,
180
+ blurhash: true,
181
+ exif: false,
182
+ storyMode: false,
183
+ pip: false,
184
+ virtualScroll: false,
185
+ dragReorder: false,
186
+ backdropTint: false,
187
+ morphTransition: false,
188
+ colorPalette: false,
189
+ aspectSkeleton: true,
190
+ // v3.0.0
191
+ video: true, // detect MP4 / YouTube / Vimeo
192
+ plugins: ['watermark', { name: 'analytics', trackingId: 'X' }],
193
+ group: '', // album group name (or use data-group)
194
+ favorites: false, // ❤️ button + localStorage
195
+ favoritesKey: '', // custom localStorage key suffix
196
+ infoPanel: false, // sidebar with metadata (I key)
197
+ contextMenu: false, // right-click menu on items
198
+ shortcutsHelp: true, // overlay on '?' key
199
+ infiniteScroll: false, // auto-load more
200
+ loadMore: null, // function(currentLength) → array | Promise<array>
201
+ editor: false, // crop/rotate/flip toolbar
202
+ annotate: false // freehand drawing layer
142
203
  });
143
204
  ```
144
205
 
@@ -149,14 +210,50 @@ const gallery = new NeikiGallery('#my-gallery', {
149
210
  ```js
150
211
  gallery.open(index); // Open lightbox at image index
151
212
  gallery.close(); // Close lightbox
152
- gallery.next(); // Go to next image
153
- gallery.prev(); // Go to previous image
213
+ gallery.next(); // Go to next image (group-aware)
214
+ gallery.prev(); // Go to previous image (group-aware)
154
215
  gallery.startSlideshow(); // Start autoplay
155
216
  gallery.stopSlideshow(); // Stop autoplay
217
+ gallery.pauseSlideshow(); // v3 — pause without emitting stop
156
218
  gallery.toggleSlideshow(); // Toggle autoplay
157
219
  gallery.filter('landscape'); // Filter by tag (null = show all)
158
220
  gallery.getSelected(); // Get batch-selected items
159
221
  gallery.clearSelection(); // Clear batch selection
222
+ gallery.getOrder(); // Get current item order (after drag reorder)
223
+
224
+ // v3.0.0 — Favorites
225
+ gallery.toggleFavorite(); // Toggle favorite for current image
226
+ gallery.isFavorite(index); // Check if image is favorited
227
+ gallery.getFavorites(); // Get array of favorited image src URLs
228
+ gallery.clearFavorites(); // Remove all favorites
229
+
230
+ // v3.0.0 — Info panel / overlays
231
+ gallery.toggleInfoPanel(force); // Toggle info sidebar (force = bool)
232
+ gallery.toggleShortcutsHelp(force); // Toggle keyboard shortcuts overlay
233
+
234
+ // v3.0.0 — Editor & annotation
235
+ gallery.openEditor(); // Open crop/rotate/flip editor
236
+ gallery.closeEditor();
237
+ gallery.getEditedBlob(); // Last exported edited image as Blob
238
+
239
+ gallery.openAnnotate(); // Open drawing layer
240
+ gallery.closeAnnotate();
241
+ gallery.getAnnotatedBlob();
242
+
243
+ // v3.0.0 — Print & dynamic content
244
+ gallery.print(index); // Print current or specified image
245
+ gallery.append([{src,...}]); // Append new items at runtime
246
+ gallery.remove(index); // Remove item by index
247
+
248
+ // v3.0.0 — Plugin access
249
+ gallery.plugin('watermark'); // Get plugin instance by name
250
+
251
+ // v3.0.0 — Event helpers
252
+ gallery.on('change', fn);
253
+ gallery.off('change', fn); // Remove specific listener
254
+ gallery.off('change'); // Remove all listeners for event
255
+ gallery.once('open', fn); // Auto-removed after first fire
256
+
160
257
  gallery.destroy(); // Remove gallery, clean up all listeners & DOM
161
258
  ```
162
259
 
@@ -174,8 +271,75 @@ const slider = NeikiGallery.compare('#compare-container', {
174
271
 
175
272
  slider.setPosition(30); // Move handle to 30%
176
273
  slider.destroy(); // Clean up
274
+
275
+ // Color palette extraction (v2.1.0)
276
+ NeikiGallery.extractPalette('photo.jpg', 5, (colors) => {
277
+ console.log(colors); // [{r, g, b, hex}, ...]
278
+ });
279
+
280
+ // Dominant color (v2.1.0)
281
+ NeikiGallery.extractDominantColor('photo.jpg', (color) => {
282
+ console.log(color); // {r, g, b, hex}
283
+ });
284
+
285
+ // EXIF parsing (v2.1.0)
286
+ NeikiGallery.parseExif('photo.jpg', (tags) => {
287
+ console.log(tags); // {make, model, iso, fNumber, exposure, focalLength}
288
+ });
289
+
290
+ // Blurhash decoding (v2.1.0)
291
+ const pixels = NeikiGallery.decodeBlurhash('LEHV6nWB2y...', 32, 32);
292
+
293
+ // Media type detection (v3.0.0)
294
+ NeikiGallery.detectMediaType('https://youtube.com/watch?v=abc'); // 'youtube'
295
+ NeikiGallery.detectMediaType('clip.mp4'); // 'video'
296
+ NeikiGallery.detectMediaType('photo.jpg'); // 'image'
297
+
298
+ // Plugin registration (v3.0.0)
299
+ NeikiGallery.registerPlugin('watermark', (gallery, options) => ({
300
+ init() { /* set up DOM, listeners */ },
301
+ open() { /* on lightbox open */ },
302
+ change(d){ /* on slide change, d = {index, item, prevIndex} */ },
303
+ close() { /* on lightbox close */ },
304
+ destroy(){ /* cleanup */ }
305
+ }));
306
+
307
+ NeikiGallery.unregisterPlugin('watermark');
308
+ NeikiGallery.getRegisteredPlugins(); // ['watermark', ...]
309
+
310
+ // Version
311
+ console.log(NeikiGallery.version); // '3.0.0'
312
+ ```
313
+
314
+ ### Plugin System
315
+
316
+ A plugin is a factory function that receives the gallery instance and an options
317
+ object, and returns an object with optional lifecycle methods:
318
+
319
+ ```js
320
+ NeikiGallery.registerPlugin('analytics', (gallery, opts) => ({
321
+ init() {
322
+ console.log('[analytics] gallery initialized');
323
+ },
324
+ open(data) {
325
+ track('lightbox_open', { index: data.index });
326
+ },
327
+ change(data) {
328
+ track('slide_view', { src: data.item.src, index: data.index });
329
+ },
330
+ close() {
331
+ track('lightbox_close');
332
+ },
333
+ destroy() {
334
+ console.log('[analytics] cleanup');
335
+ }
336
+ }));
337
+
338
+ new NeikiGallery('#g', { plugins: [{ name: 'analytics', sampleRate: 0.5 }] });
177
339
  ```
178
340
 
341
+ You can access plugin instances on a gallery via `gallery.plugin('analytics')`.
342
+
179
343
  ### Events
180
344
 
181
345
  ```js
@@ -202,6 +366,23 @@ gallery.on('select', (indices) => {
202
366
  gallery.on('slideshowStart', () => {
203
367
  console.log('Slideshow started');
204
368
  });
369
+
370
+ gallery.on('reorder', (order) => {
371
+ console.log('New order:', order);
372
+ });
373
+
374
+ gallery.on('pipEnter', () => console.log('PiP on'));
375
+ gallery.on('storyEnter', () => console.log('Story opened'));
376
+
377
+ // v3.0.0 events
378
+ gallery.on('favorite', ({ index, src }) => console.log('★ favorited', src));
379
+ gallery.on('unfavorite', ({ index, src }) => console.log('☆ removed'));
380
+ gallery.on('infoOpen', () => console.log('info panel opened'));
381
+ gallery.on('print', ({ index, src }) => console.log('printed', src));
382
+ gallery.on('editorExport', ({ blob, url }) => console.log('edited blob', url));
383
+ gallery.on('annotateExport', ({ blob, url }) => console.log('annotated', url));
384
+ gallery.on('append', (items) => console.log('appended', items.length));
385
+ gallery.on('remove', ({ index }) => console.log('removed item', index));
205
386
  ```
206
387
 
207
388
  ### Options
@@ -215,18 +396,41 @@ gallery.on('slideshowStart', () => {
215
396
  | `contextualZoom` | `boolean` | `false` | Zoom to cursor point instead of center |
216
397
  | `fullscreen` | `boolean` | `true` | Show fullscreen button |
217
398
  | `transition` | `string` | `'fade'` | `'fade'` or `'slide'` |
218
- | `theme` | `string` | `'dark'` | `'dark'` or `'light'` |
399
+ | `theme` | `string` | `'system'` | `'dark'` · `'light'` · `'system'` (auto-detect) |
219
400
  | `hashNavigation` | `boolean` | `true` | URL hash deep linking |
220
401
  | `counter` | `boolean` | `true` | Show image counter |
402
+ | `counterFormat` | `string` | `'{current} / {total}'` | Counter format with `{current}`, `{total}`, `{percent}` tokens |
221
403
  | `captions` | `boolean` | `true` | Show image captions |
222
404
  | `preload` | `number` | `1` | Adjacent images to preload |
223
405
  | `lazyLoad` | `boolean` | `true` | Lazy load grid thumbnails |
224
406
  | `stagger` | `boolean` | `true` | Staggered entrance animation |
225
- | `slideshow` | `boolean` | `false` | Auto-start slideshow on open |
226
- | `slideshowInterval` | `number` | `4000` | Slideshow interval in ms |
407
+ | `slideshow` | `boolean \| object` | `false` | `true` or `{ interval, pauseOnHover, kenburns, direction }` |
227
408
  | `share` | `boolean` | `true` | Show share button in lightbox |
228
409
  | `filter` | `boolean` | `false` | Enable tag filtering bar |
229
410
  | `batchSelect` | `boolean` | `false` | Enable Shift+click multi-select |
411
+ | `focusPoint` | `boolean` | `true` | Respect `data-focus` for `object-position` |
412
+ | `blurhash` | `boolean` | `true` | Decode `data-blurhash` placeholders |
413
+ | `exif` | `boolean` | `false` | Show EXIF data overlay in lightbox |
414
+ | `storyMode` | `boolean` | `false` | Enable story mode button in toolbar |
415
+ | `pip` | `boolean` | `false` | Enable PiP button in toolbar |
416
+ | `virtualScroll` | `boolean` | `false` | Virtualize grid for 50+ items |
417
+ | `dragReorder` | `boolean` | `false` | Enable drag & drop reorder |
418
+ | `backdropTint` | `boolean` | `false` | Tint overlay with dominant color |
419
+ | `morphTransition` | `boolean` | `false` | FLIP morph from grid to lightbox |
420
+ | `colorPalette` | `boolean` | `false` | Show extracted color palette |
421
+ | `aspectSkeleton` | `boolean` | `true` | Use `data-width`/`data-height` for skeleton |
422
+ | `video` | `boolean` | `true` | Auto-detect MP4 / YouTube / Vimeo URLs |
423
+ | `plugins` | `array` | `null` | Plugins to instantiate; e.g. `['name', { name, ...opts }]` |
424
+ | `group` | `string` | `''` | Album group name (also via `data-group`) |
425
+ | `favorites` | `boolean` | `false` | Heart button + localStorage |
426
+ | `favoritesKey` | `string` | `''` | Custom suffix for localStorage key |
427
+ | `infoPanel` | `boolean` | `false` | Slide-out metadata sidebar |
428
+ | `contextMenu` | `boolean` | `false` | Right-click menu on items |
429
+ | `shortcutsHelp` | `boolean` | `true` | `?` key shortcuts overlay |
430
+ | `infiniteScroll` | `boolean` | `false` | Auto-load more on scroll (with `loadMore`) |
431
+ | `loadMore` | `function` | `null` | `(currentLength) => array \| Promise<array>` |
432
+ | `editor` | `boolean` | `false` | Crop/rotate/flip toolbar |
433
+ | `annotate` | `boolean` | `false` | Freehand drawing layer |
230
434
 
231
435
  ### Data Attributes
232
436
 
@@ -241,12 +445,32 @@ All options can also be set via `data-` attributes on the container:
241
445
  data-stagger="true"
242
446
  data-slideshow="false"
243
447
  data-slideshow-interval="5000"
448
+ data-slideshow-pause-on-hover="true"
449
+ data-slideshow-kenburns="true"
450
+ data-counter-format="{current} of {total}"
244
451
  data-share="true"
245
452
  data-filter="true"
246
453
  data-batch-select="false"
247
- data-contextual-zoom="true">
248
- <a href="full.jpg" data-tags="landscape,nature" data-size="large">
249
- <img src="thumb.jpg" alt="Photo">
454
+ data-contextual-zoom="true"
455
+ data-story-mode="false"
456
+ data-pip="false"
457
+ data-exif="false"
458
+ data-backdrop-tint="false"
459
+ data-morph-transition="false"
460
+ data-color-palette="false"
461
+ data-drag-reorder="false"
462
+ data-virtual-scroll="false"
463
+ data-aspect-skeleton="true"
464
+ data-group="vacation"
465
+ data-favorites="true"
466
+ data-info-panel="true"
467
+ data-context-menu="true"
468
+ data-shortcuts-help="true"
469
+ data-editor="true"
470
+ data-annotate="true"
471
+ data-video="true">
472
+ <a href="full.jpg" data-tags="landscape,nature" data-size="large" data-width="1200" data-height="800" data-poster="poster.jpg" data-type="image">
473
+ <img src="thumb.jpg" alt="Photo" data-focus="0.5 0.3" data-blurhash="LEHV6nWB2y...">
250
474
  </a>
251
475
  </div>
252
476
  ```
@@ -283,10 +507,59 @@ Switch themes at runtime by setting `data-theme="light"` or `data-theme="dark"`
283
507
 
284
508
  | Browser | Version |
285
509
  |---------|---------|
286
- | Chrome | 60+ |
287
- | Firefox | 55+ |
288
- | Safari | 12+ |
289
- | Edge | 79+ |
510
+ | Chrome | 80+ |
511
+ | Firefox | 75+ |
512
+ | Safari | 14+ |
513
+ | Edge | 80+ |
514
+
515
+ > **v3.0.0** raised the minimum to take advantage of `aspect-ratio`, `backdrop-filter`,
516
+ > stable `IntersectionObserver`, and modern Canvas/Blob APIs. If you need older
517
+ > browser support, pin to v2.1.0.
518
+
519
+ ## 🔁 Migration from v2.x to v3.0.0
520
+
521
+ **v3.0.0 contains breaking changes.** Most galleries will continue to work, but
522
+ review the following:
523
+
524
+ ### Slideshow config is nested
525
+
526
+ ```js
527
+ // v2.x
528
+ new NeikiGallery('#g', { slideshow: true, slideshowInterval: 5000 });
529
+
530
+ // v3.0.0 — preferred (fine-grained control)
531
+ new NeikiGallery('#g', {
532
+ slideshow: { interval: 5000, pauseOnHover: true, kenburns: true }
533
+ });
534
+
535
+ // v3.0.0 — still works (uses defaults)
536
+ new NeikiGallery('#g', { slideshow: true });
537
+ ```
538
+
539
+ ### Default theme is `'system'`
540
+
541
+ If you relied on the old default `'dark'`, set it explicitly:
542
+
543
+ ```js
544
+ new NeikiGallery('#g', { theme: 'dark' });
545
+ ```
546
+
547
+ ### Hash format changed
548
+
549
+ Old: `#neiki-1=3` → New: `#gallery-id-or-group-name/3`
550
+
551
+ If you have external links to the old format, update them or set
552
+ `hashNavigation: false` and implement custom routing.
553
+
554
+ ### Counter format
555
+
556
+ Default output (`"3 / 12"`) is unchanged, but it's now formatted via
557
+ `counterFormat: '{current} / {total}'`. Override with custom tokens.
558
+
559
+ ### Browser support
560
+
561
+ Minimum Chrome 80+ / Firefox 75+ / Safari 14+ / Edge 80+. If you need older
562
+ browser support, stay on v2.1.0.
290
563
 
291
564
  ## 📄 License
292
565