neiki-gallery 2.1.0 โ†’ 3.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.
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
@@ -1,16 +1,16 @@
1
- <h1 align="center">Neiki Gallery</h1>
2
-
3
1
  <p align="center">
4
- <img src="logo.png" alt="neiki-gallery" width="400">
2
+ <img src="logo.png" alt="Neiki's Gallery" width="400">
5
3
  </p>
6
4
 
5
+ <h1 align="center">Neiki's Gallery</h1>
6
+
7
7
  <p align="center">
8
8
  <img src="https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E" alt="JavaScript">
9
9
  <img src="https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white" alt="HTML5">
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.1.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.1.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">
@@ -25,11 +25,24 @@
25
25
  <img src="https://img.shields.io/badge/Size-Lightweight-f97316?style=flat&labelColor=383C43" />
26
26
  </p>
27
27
 
28
+
29
+ <p align="center">
30
+ <a href="https://sourceforge.net/projects/neiki-gallery/files/latest/download"><img alt="Download Neiki&#39;s Gallery" src="https://a.fsdn.com/con/app/sf-download-button" width=276 height=48 srcset="https://a.fsdn.com/con/app/sf-download-button?button_size=2x 2x"></a>
31
+ </p>
32
+
33
+ ---
34
+
35
+ <img src="preview.png" width="900px">
36
+
37
+ ---
38
+
39
+ **Live demo:** [https://neikiri.dev/gallery](https://neikiri.dev/gallery)
40
+
28
41
  ---
29
42
 
30
43
  ## ๐Ÿ“– About
31
44
 
32
- **Neiki Gallery** is a production-ready image gallery and lightbox library built with vanilla JavaScript and CSS. No frameworks, no dependencies โ€” just drop two files into your project and you're ready to go.
45
+ **Neiki's Gallery** is a production-ready image gallery and lightbox library built with vanilla JavaScript and CSS. No frameworks, no dependencies โ€” just drop two files into your project and you're ready to go.
33
46
 
34
47
  ## โœจ Features
35
48
 
@@ -55,7 +68,7 @@
55
68
  - **Multiple instances** โ€” independent galleries on one page
56
69
  - **Dark & light themes** โ€” CSS custom properties + accent color system (`--neiki-accent`)
57
70
  - **Accessibility** โ€” ARIA attributes, focus management, `prefers-reduced-motion`
58
- - **Event system** โ€” `open`, `close`, `change`, `filter`, `select`, `slideshowStart`, `slideshowStop`
71
+ - **Event system** โ€” `open`, `close`, `change`, `filter`, `select`, `slideshowStart`, `slideshowStop`, `favorite`, `unfavorite`, `infoOpen`, `infoClose`, `print`, `editorOpen`, `editorExport`, `annotateOpen`, `annotateExport`, `append`, `remove`
59
72
  - **Blurhash placeholders** โ€” `data-blurhash` attribute decoded into blurred preview (replaces shimmer)
60
73
  - **Story mode** โ€” Instagram-like vertical fullscreen viewer with progress bars and tap navigation
61
74
  - **Picture-in-Picture** โ€” minimize lightbox to a resizable corner window
@@ -68,7 +81,21 @@
68
81
  - **Drag & drop reorder** โ€” HTML5 drag to reorder grid items
69
82
  - **Aspect-ratio skeleton** โ€” `data-width`/`data-height` for placeholder sizing
70
83
  - **Web Share API** โ€” native mobile sharing with clipboard fallback
71
- - **Cross-browser** โ€” Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
84
+ - **Video & embeds** โ€” auto-detect MP4/WebM/Ogg/MOV/M4V, YouTube and Vimeo URLs in lightbox
85
+ - **Plugin system** โ€” `NeikiGallery.registerPlugin()` with `init`/`open`/`change`/`close`/`destroy` lifecycle hooks
86
+ - **Album groups** โ€” `data-group="album"` links galleries; navigation traverses across all
87
+ - **Favorites** โ€” heart button + localStorage persistence + `B` key shortcut
88
+ - **Info panel** โ€” slide-out sidebar with metadata, EXIF, source URL (`I` key)
89
+ - **Image editor** โ€” rotate / flip / export to PNG via `<canvas>`
90
+ - **Annotation layer** โ€” freehand drawing with color picker, brush size, undo, clear, export
91
+ - **Print** โ€” print current image with caption (`P` key)
92
+ - **Right-click context menu** โ€” open original, copy link, download, share, print, favorite
93
+ - **Infinite scroll** โ€” `loadMore` callback + `append()` / `remove()` API
94
+ - **Keyboard shortcuts overlay** โ€” press `?` to view all shortcuts
95
+ - **System theme detection** โ€” `theme: 'system'` follows OS `prefers-color-scheme`
96
+ - **Kenburns slideshow** โ€” slow zoom-and-pan animation per slide
97
+ - **Configurable counter** โ€” `counterFormat` with `{current}`/`{total}`/`{percent}` tokens
98
+ - **Cross-browser** โ€” Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
72
99
 
73
100
  ## ๐Ÿš€ Quick Start
74
101
 
@@ -77,7 +104,7 @@
77
104
  Add this single line to your HTML โ€” that's all you need (CSS is included automatically):
78
105
 
79
106
  ```html
80
- <script src="https://cdn.neiki.eu/neiki-gallery/neiki-gallery.min.js"></script>
107
+ <script src="https://cdn.neikiri.dev/neiki-gallery/neiki-gallery.min.js"></script>
81
108
  ```
82
109
 
83
110
  Then create your gallery markup:
@@ -97,13 +124,13 @@ That's it โ€” galleries with `data-neiki-gallery` auto-initialize on page load.
97
124
 
98
125
  > **Unminified files** are also available if you need them for debugging (require separate CSS):
99
126
  > ```
100
- > https://cdn.neiki.eu/neiki-gallery/neiki-gallery.js
101
- > https://cdn.neiki.eu/neiki-gallery/neiki-gallery.css
127
+ > https://cdn.neikiri.dev/neiki-gallery/neiki-gallery.js
128
+ > https://cdn.neikiri.dev/neiki-gallery/neiki-gallery.css
102
129
  > ```
103
130
 
104
131
  > **Pin a specific version** to avoid unexpected changes:
105
132
  > ```
106
- > https://cdn.neiki.eu/neiki-gallery/2.1.0/neiki-gallery.min.js
133
+ > https://cdn.neikiri.dev/neiki-gallery/3.1.0/neiki-gallery.min.js
107
134
  > ```
108
135
 
109
136
  ### Self-Hosting (Local)
@@ -147,26 +174,45 @@ const gallery = new NeikiGallery('#my-gallery', {
147
174
  contextualZoom: false, // zoom to click point
148
175
  fullscreen: true,
149
176
  transition: 'fade', // 'fade' | 'slide'
150
- theme: 'dark', // 'dark' | 'light'
177
+ theme: 'system', // 'dark' | 'light' | 'system' (auto-detect) โ† v3 default
151
178
  hashNavigation: true,
152
- stagger: true, // entrance animation
153
- slideshow: false, // auto-start slideshow on open
154
- slideshowInterval: 4000, // ms between slides
155
- share: true, // share/download popup
156
- filter: false, // tag filtering
157
- batchSelect: false, // Shift+click multi-select
179
+ counter: true,
180
+ counterFormat: '{current} / {total}', // v3 โ€” tokens: {current}, {total}, {percent}
181
+ stagger: true,
182
+ slideshow: { // v3 โ€” nested config (boolean still works)
183
+ interval: 4000,
184
+ pauseOnHover: true,
185
+ kenburns: false,
186
+ direction: 'forward' // or 'reverse'
187
+ },
188
+ share: true,
189
+ filter: false,
190
+ batchSelect: false,
158
191
  // v2.1.0
159
- focusPoint: true, // respect data-focus for object-position
160
- blurhash: true, // decode data-blurhash placeholders
161
- exif: false, // show EXIF overlay in lightbox
162
- storyMode: false, // vertical fullscreen story viewer
163
- pip: false, // picture-in-picture lightbox
164
- virtualScroll: false, // virtualize grid for large galleries
165
- dragReorder: false, // drag-and-drop reorder
166
- backdropTint: false, // tint overlay with dominant color
167
- morphTransition: false, // FLIP morph from grid to lightbox
168
- colorPalette: false, // extract dominant colors
169
- aspectSkeleton: true, // data-width/data-height skeleton
192
+ focusPoint: true,
193
+ blurhash: true,
194
+ exif: false,
195
+ storyMode: false,
196
+ pip: false,
197
+ virtualScroll: false,
198
+ dragReorder: false,
199
+ backdropTint: false,
200
+ morphTransition: false,
201
+ colorPalette: false,
202
+ aspectSkeleton: true,
203
+ // v3.0.0
204
+ video: true, // detect MP4 / YouTube / Vimeo
205
+ plugins: ['watermark', { name: 'analytics', trackingId: 'X' }],
206
+ group: '', // album group name (or use data-group)
207
+ favorites: false, // โค๏ธ button + localStorage
208
+ favoritesKey: '', // custom localStorage key suffix
209
+ infoPanel: false, // sidebar with metadata (I key)
210
+ contextMenu: false, // right-click menu on items
211
+ shortcutsHelp: true, // overlay on '?' key
212
+ infiniteScroll: false, // auto-load more
213
+ loadMore: null, // function(currentLength) โ†’ array | Promise<array>
214
+ editor: false, // crop/rotate/flip toolbar
215
+ annotate: false // freehand drawing layer
170
216
  });
171
217
  ```
172
218
 
@@ -177,15 +223,50 @@ const gallery = new NeikiGallery('#my-gallery', {
177
223
  ```js
178
224
  gallery.open(index); // Open lightbox at image index
179
225
  gallery.close(); // Close lightbox
180
- gallery.next(); // Go to next image
181
- gallery.prev(); // Go to previous image
226
+ gallery.next(); // Go to next image (group-aware)
227
+ gallery.prev(); // Go to previous image (group-aware)
182
228
  gallery.startSlideshow(); // Start autoplay
183
229
  gallery.stopSlideshow(); // Stop autoplay
230
+ gallery.pauseSlideshow(); // v3 โ€” pause without emitting stop
184
231
  gallery.toggleSlideshow(); // Toggle autoplay
185
232
  gallery.filter('landscape'); // Filter by tag (null = show all)
186
233
  gallery.getSelected(); // Get batch-selected items
187
234
  gallery.clearSelection(); // Clear batch selection
188
235
  gallery.getOrder(); // Get current item order (after drag reorder)
236
+
237
+ // v3.0.0 โ€” Favorites
238
+ gallery.toggleFavorite(); // Toggle favorite for current image
239
+ gallery.isFavorite(index); // Check if image is favorited
240
+ gallery.getFavorites(); // Get array of favorited image src URLs
241
+ gallery.clearFavorites(); // Remove all favorites
242
+
243
+ // v3.0.0 โ€” Info panel / overlays
244
+ gallery.toggleInfoPanel(force); // Toggle info sidebar (force = bool)
245
+ gallery.toggleShortcutsHelp(force); // Toggle keyboard shortcuts overlay
246
+
247
+ // v3.0.0 โ€” Editor & annotation
248
+ gallery.openEditor(); // Open crop/rotate/flip editor
249
+ gallery.closeEditor();
250
+ gallery.getEditedBlob(); // Last exported edited image as Blob
251
+
252
+ gallery.openAnnotate(); // Open drawing layer
253
+ gallery.closeAnnotate();
254
+ gallery.getAnnotatedBlob();
255
+
256
+ // v3.0.0 โ€” Print & dynamic content
257
+ gallery.print(index); // Print current or specified image
258
+ gallery.append([{src,...}]); // Append new items at runtime
259
+ gallery.remove(index); // Remove item by index
260
+
261
+ // v3.0.0 โ€” Plugin access
262
+ gallery.plugin('watermark'); // Get plugin instance by name
263
+
264
+ // v3.0.0 โ€” Event helpers
265
+ gallery.on('change', fn);
266
+ gallery.off('change', fn); // Remove specific listener
267
+ gallery.off('change'); // Remove all listeners for event
268
+ gallery.once('open', fn); // Auto-removed after first fire
269
+
189
270
  gallery.destroy(); // Remove gallery, clean up all listeners & DOM
190
271
  ```
191
272
 
@@ -221,8 +302,57 @@ NeikiGallery.parseExif('photo.jpg', (tags) => {
221
302
 
222
303
  // Blurhash decoding (v2.1.0)
223
304
  const pixels = NeikiGallery.decodeBlurhash('LEHV6nWB2y...', 32, 32);
305
+
306
+ // Media type detection (v3.0.0)
307
+ NeikiGallery.detectMediaType('https://youtube.com/watch?v=abc'); // 'youtube'
308
+ NeikiGallery.detectMediaType('clip.mp4'); // 'video'
309
+ NeikiGallery.detectMediaType('photo.jpg'); // 'image'
310
+
311
+ // Plugin registration (v3.0.0)
312
+ NeikiGallery.registerPlugin('watermark', (gallery, options) => ({
313
+ init() { /* set up DOM, listeners */ },
314
+ open() { /* on lightbox open */ },
315
+ change(d){ /* on slide change, d = {index, item, prevIndex} */ },
316
+ close() { /* on lightbox close */ },
317
+ destroy(){ /* cleanup */ }
318
+ }));
319
+
320
+ NeikiGallery.unregisterPlugin('watermark');
321
+ NeikiGallery.getRegisteredPlugins(); // ['watermark', ...]
322
+
323
+ // Version
324
+ console.log(NeikiGallery.version); // '3.1.0'
325
+ ```
326
+
327
+ ### Plugin System
328
+
329
+ A plugin is a factory function that receives the gallery instance and an options
330
+ object, and returns an object with optional lifecycle methods:
331
+
332
+ ```js
333
+ NeikiGallery.registerPlugin('analytics', (gallery, opts) => ({
334
+ init() {
335
+ console.log('[analytics] gallery initialized');
336
+ },
337
+ open(data) {
338
+ track('lightbox_open', { index: data.index });
339
+ },
340
+ change(data) {
341
+ track('slide_view', { src: data.item.src, index: data.index });
342
+ },
343
+ close() {
344
+ track('lightbox_close');
345
+ },
346
+ destroy() {
347
+ console.log('[analytics] cleanup');
348
+ }
349
+ }));
350
+
351
+ new NeikiGallery('#g', { plugins: [{ name: 'analytics', sampleRate: 0.5 }] });
224
352
  ```
225
353
 
354
+ You can access plugin instances on a gallery via `gallery.plugin('analytics')`.
355
+
226
356
  ### Events
227
357
 
228
358
  ```js
@@ -256,6 +386,16 @@ gallery.on('reorder', (order) => {
256
386
 
257
387
  gallery.on('pipEnter', () => console.log('PiP on'));
258
388
  gallery.on('storyEnter', () => console.log('Story opened'));
389
+
390
+ // v3.0.0 events
391
+ gallery.on('favorite', ({ index, src }) => console.log('โ˜… favorited', src));
392
+ gallery.on('unfavorite', ({ index, src }) => console.log('โ˜† removed'));
393
+ gallery.on('infoOpen', () => console.log('info panel opened'));
394
+ gallery.on('print', ({ index, src }) => console.log('printed', src));
395
+ gallery.on('editorExport', ({ blob, url }) => console.log('edited blob', url));
396
+ gallery.on('annotateExport', ({ blob, url }) => console.log('annotated', url));
397
+ gallery.on('append', (items) => console.log('appended', items.length));
398
+ gallery.on('remove', ({ index }) => console.log('removed item', index));
259
399
  ```
260
400
 
261
401
  ### Options
@@ -269,15 +409,15 @@ gallery.on('storyEnter', () => console.log('Story opened'));
269
409
  | `contextualZoom` | `boolean` | `false` | Zoom to cursor point instead of center |
270
410
  | `fullscreen` | `boolean` | `true` | Show fullscreen button |
271
411
  | `transition` | `string` | `'fade'` | `'fade'` or `'slide'` |
272
- | `theme` | `string` | `'dark'` | `'dark'` or `'light'` |
412
+ | `theme` | `string` | `'system'` | `'dark'` ยท `'light'` ยท `'system'` (auto-detect) |
273
413
  | `hashNavigation` | `boolean` | `true` | URL hash deep linking |
274
414
  | `counter` | `boolean` | `true` | Show image counter |
415
+ | `counterFormat` | `string` | `'{current} / {total}'` | Counter format with `{current}`, `{total}`, `{percent}` tokens |
275
416
  | `captions` | `boolean` | `true` | Show image captions |
276
417
  | `preload` | `number` | `1` | Adjacent images to preload |
277
418
  | `lazyLoad` | `boolean` | `true` | Lazy load grid thumbnails |
278
419
  | `stagger` | `boolean` | `true` | Staggered entrance animation |
279
- | `slideshow` | `boolean` | `false` | Auto-start slideshow on open |
280
- | `slideshowInterval` | `number` | `4000` | Slideshow interval in ms |
420
+ | `slideshow` | `boolean \| object` | `false` | `true` or `{ interval, pauseOnHover, kenburns, direction }` |
281
421
  | `share` | `boolean` | `true` | Show share button in lightbox |
282
422
  | `filter` | `boolean` | `false` | Enable tag filtering bar |
283
423
  | `batchSelect` | `boolean` | `false` | Enable Shift+click multi-select |
@@ -292,6 +432,18 @@ gallery.on('storyEnter', () => console.log('Story opened'));
292
432
  | `morphTransition` | `boolean` | `false` | FLIP morph from grid to lightbox |
293
433
  | `colorPalette` | `boolean` | `false` | Show extracted color palette |
294
434
  | `aspectSkeleton` | `boolean` | `true` | Use `data-width`/`data-height` for skeleton |
435
+ | `video` | `boolean` | `true` | Auto-detect MP4 / YouTube / Vimeo URLs |
436
+ | `plugins` | `array` | `null` | Plugins to instantiate; e.g. `['name', { name, ...opts }]` |
437
+ | `group` | `string` | `''` | Album group name (also via `data-group`) |
438
+ | `favorites` | `boolean` | `false` | Heart button + localStorage |
439
+ | `favoritesKey` | `string` | `''` | Custom suffix for localStorage key |
440
+ | `infoPanel` | `boolean` | `false` | Slide-out metadata sidebar |
441
+ | `contextMenu` | `boolean` | `false` | Right-click menu on items |
442
+ | `shortcutsHelp` | `boolean` | `true` | `?` key shortcuts overlay |
443
+ | `infiniteScroll` | `boolean` | `false` | Auto-load more on scroll (with `loadMore`) |
444
+ | `loadMore` | `function` | `null` | `(currentLength) => array \| Promise<array>` |
445
+ | `editor` | `boolean` | `false` | Crop/rotate/flip toolbar |
446
+ | `annotate` | `boolean` | `false` | Freehand drawing layer |
295
447
 
296
448
  ### Data Attributes
297
449
 
@@ -306,6 +458,9 @@ All options can also be set via `data-` attributes on the container:
306
458
  data-stagger="true"
307
459
  data-slideshow="false"
308
460
  data-slideshow-interval="5000"
461
+ data-slideshow-pause-on-hover="true"
462
+ data-slideshow-kenburns="true"
463
+ data-counter-format="{current} of {total}"
309
464
  data-share="true"
310
465
  data-filter="true"
311
466
  data-batch-select="false"
@@ -318,8 +473,16 @@ All options can also be set via `data-` attributes on the container:
318
473
  data-color-palette="false"
319
474
  data-drag-reorder="false"
320
475
  data-virtual-scroll="false"
321
- data-aspect-skeleton="true">
322
- <a href="full.jpg" data-tags="landscape,nature" data-size="large" data-width="1200" data-height="800">
476
+ data-aspect-skeleton="true"
477
+ data-group="vacation"
478
+ data-favorites="true"
479
+ data-info-panel="true"
480
+ data-context-menu="true"
481
+ data-shortcuts-help="true"
482
+ data-editor="true"
483
+ data-annotate="true"
484
+ data-video="true">
485
+ <a href="full.jpg" data-tags="landscape,nature" data-size="large" data-width="1200" data-height="800" data-poster="poster.jpg" data-type="image">
323
486
  <img src="thumb.jpg" alt="Photo" data-focus="0.5 0.3" data-blurhash="LEHV6nWB2y...">
324
487
  </a>
325
488
  </div>
@@ -327,7 +490,7 @@ All options can also be set via `data-` attributes on the container:
327
490
 
328
491
  ## ๐ŸŽจ Theming
329
492
 
330
- Neiki Gallery uses CSS custom properties for full theming control:
493
+ Neiki's Gallery uses CSS custom properties for full theming control:
331
494
 
332
495
  ```css
333
496
  :root {
@@ -357,10 +520,59 @@ Switch themes at runtime by setting `data-theme="light"` or `data-theme="dark"`
357
520
 
358
521
  | Browser | Version |
359
522
  |---------|---------|
360
- | Chrome | 60+ |
361
- | Firefox | 55+ |
362
- | Safari | 12+ |
363
- | Edge | 79+ |
523
+ | Chrome | 80+ |
524
+ | Firefox | 75+ |
525
+ | Safari | 14+ |
526
+ | Edge | 80+ |
527
+
528
+ > **v3.0.0** raised the minimum to take advantage of `aspect-ratio`, `backdrop-filter`,
529
+ > stable `IntersectionObserver`, and modern Canvas/Blob APIs. If you need older
530
+ > browser support, pin to v2.1.0.
531
+
532
+ ## ๐Ÿ” Migration from v2.x to v3.0.0
533
+
534
+ **v3.0.0 contains breaking changes.** Most galleries will continue to work, but
535
+ review the following:
536
+
537
+ ### Slideshow config is nested
538
+
539
+ ```js
540
+ // v2.x
541
+ new NeikiGallery('#g', { slideshow: true, slideshowInterval: 5000 });
542
+
543
+ // v3.0.0 โ€” preferred (fine-grained control)
544
+ new NeikiGallery('#g', {
545
+ slideshow: { interval: 5000, pauseOnHover: true, kenburns: true }
546
+ });
547
+
548
+ // v3.0.0 โ€” still works (uses defaults)
549
+ new NeikiGallery('#g', { slideshow: true });
550
+ ```
551
+
552
+ ### Default theme is `'system'`
553
+
554
+ If you relied on the old default `'dark'`, set it explicitly:
555
+
556
+ ```js
557
+ new NeikiGallery('#g', { theme: 'dark' });
558
+ ```
559
+
560
+ ### Hash format changed
561
+
562
+ Old: `#neiki-1=3` โ†’ New: `#gallery-id-or-group-name/3`
563
+
564
+ If you have external links to the old format, update them or set
565
+ `hashNavigation: false` and implement custom routing.
566
+
567
+ ### Counter format
568
+
569
+ Default output (`"3 / 12"`) is unchanged, but it's now formatted via
570
+ `counterFormat: '{current} / {total}'`. Override with custom tokens.
571
+
572
+ ### Browser support
573
+
574
+ Minimum Chrome 80+ / Firefox 75+ / Safari 14+ / Edge 80+. If you need older
575
+ browser support, stay on v2.1.0.
364
576
 
365
577
  ## ๐Ÿ“„ License
366
578
 
@@ -368,10 +580,10 @@ This project is licensed under the MIT License โ€” see the [LICENSE](LICENSE) fi
368
580
 
369
581
  ## ๐Ÿ“ฌ Contact
370
582
 
371
- - **Email:** [dev@neiki.eu](mailto:dev@neiki.eu)
583
+ - **Email:** [neikiri@neikiri.dev](mailto:neikiri@neikiri.dev)
372
584
 
373
585
  ---
374
586
 
375
587
  <p align="center">
376
588
  Made with โค๏ธ for the web community
377
- </p>
589
+ </p>