PageSourceSearch

https://dbeaver.io/wp-content/plugins/ml-slider-lightbox/assets/js/ml-lightgallery-init.js?ver=1783433290

js dbeaver.io collected 2026-10-01 08:46:19 UTC 124,859 bytes, 3,249 lines download raw bytes

1/**
2 * MetaSlider Lightbox - Clean Implementation
3 * Simple class-based lightbox functionality
4 */
5
6(function($) {
7    'use strict';
8
9    const CONSTANTS = {
10        SELECTORS: {
11            WP_GALLERY: '.wp-block-gallery',
12            ML_LIGHTBOX_ENABLED: '.ml-lightbox-enabled',
13            ML_LIGHTBOX_BUTTON: '.ml-lightbox-button',
14            LG_INITIALIZED: '.lg-initialized',
15            CONTENT_CONTAINERS: '.entry-content, .post-content, .page-content, article, .content, main, [role="main"], .site-main, .elementor-widget-theme-post-content, .et_pb_section, .fusion-body'
16        },
17        CLASSES: {
18            ML_LIGHTBOX_ENABLED: 'ml-lightbox-enabled',
19            ML_LIGHTBOX_BUTTON: 'ml-lightbox-button',
20            LG_INITIALIZED: 'lg-initialized'
21        },
22        VIDEO_HANDLERS: {
23            youtube: {
24                selector: 'div.youtube[data-id]',
25                idAttr: 'data-id',
26                urlTemplate: (id) => `https://www.youtube.com/watch?v=${id}`,
27                thumbTemplate: (id) => `https://img.youtube.com/vi/${id}/maxresdefault.jpg`
28            },
29            vimeo: {
30                selector: 'div.vimeo[data-id]',
31                idAttr: 'data-id',
32                urlTemplate: (id) => `https://vimeo.com/${id}`,
33                thumbTemplate: (id) => `https://vumbnail.com/${id}.jpg`,
34                fallbackSelectors: ['[data-slide-id]', 'a[href*="vimeo.com"]']
35            },
36            externalVideo: {
37                selector: 'div.external-video',
38                sourcesAttr: 'data-sources',
39                posterAttr: 'data-poster'
40            },
41            localVideo: {
42                selector: 'div.local-video',
43                sourcesAttr: 'data-sources',
44                posterAttr: 'data-poster'
45            }
46        }
47    };
48
49    // Set by initMetaSliderButtonMode so getButtonText() picks up the per-slider icon setting
50    var _metasliderButtonSliderId = null;
51
52    function escapeHtml(str) {
53        return $('<div>').text(str).html();
54    }
55
56    function getButtonText(sliderId) {
57        var id = sliderId !== undefined ? sliderId : _metasliderButtonSliderId;
58        if (resolveSliderIcon(id)) {
59            return '<svg class="ml-lightbox-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M21 9V3H15M21 3L13 11M10 5H7.8C6.11984 5 5.27976 5 4.63803 5.32698C4.07354 5.6146 3.6146 6.07354 3.32698 6.63803C3 7.27976 3 8.11984 3 9.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H14.2C15.8802 21 16.7202 21 17.362 20.673C17.9265 20.3854 18.3854 19.9265 18.673 19.362C19 18.7202 19 17.8802 19 16.2V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
60        }
61
62        return (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.button_text)
63               ? mlLightboxSettings.button_text
64               : 'Open in Gallery';
65    }
66
67    /**
68     * Add accessibility attributes to overlay elements
69     * @param {jQuery} $overlay - The overlay element
70     * @param {string} type - Type of content ('image' or 'video')
71     * @param {string} altText - Alt text or description
72     */
73    function addOverlayAccessibility($overlay, type, altText) {
74        altText = altText || type;
75        $overlay.attr({
76            'role': 'button',
77            'aria-label': 'View ' + type + (altText ? ' - ' + altText : ''),
78            'tabindex': '0'
79        });
80
81        // Add keyboard support (Enter and Space keys)
82        $overlay.on('keydown', function(e) {
83            if (e.key === 'Enter' || e.key === ' ') {
84                e.preventDefault();
85                $(this).click();
86            }
87        });
88    }
89
90    /**
91     * Add accessible name (aria-label) to a link based on image alt text
92     * @param {jQuery} $link - The link element
93     * @param {jQuery} $img - The image element inside the link
94     */
95    function addAccessibleNameToLink($link, $img) {
96        if (!$link || !$img || $img.length === 0) return;
97
98        var altText = $img.attr('alt') || '';
99        var ariaLabel = 'View image' + (altText ? ': ' + altText : '');
100
101        // Only add aria-label if link doesn't already have accessible text
102        if (!$link.attr('aria-label') && !$link.text().trim()) {
103            $link.attr('aria-label', ariaLabel);
104        }
105    }
106
107    function isInsideMetaSliderContainer($element) {
108        var $container = $element.closest('.metaslider, [id*="metaslider"], .filmstrip, [id*="filmstrip"]');
109        if ($container.length > 0) {
110            if ($container.is('body') || $container.is('html')) {
111                return false;
112            }
113        }
114        return $container.length > 0;
115    }
116
117    $(document).ready(function() {
118        // Create screen reader live region for announcements
119        if ($('#ml-lightbox-sr-live').length === 0) {
120            $('<div id="ml-lightbox-sr-live" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>')
121                .appendTo('body');
122        }
123
124        initLightboxes();
125        removeConflictingAttributes();
126        ensureAccessibleNames();
127
128        // Add lightGallery event listeners for screen reader announcements
129        $(document).on('lgAfterOpen.lg', function(event) {
130            var $lgContainer = $(event.target).find('.lg-container');
131
132            // Add accessible name to dialog
133            if ($lgContainer.length > 0) {
134                var totalItems = $(event.target).find('[data-lg-item-id]').length;
135                var ariaLabel = totalItems > 1
136                    ? 'Image gallery with ' + totalItems + ' images'
137                    : 'Image gallery';
138                $lgContainer.attr('aria-label', ariaLabel);
139            }
140
141            announceToScreenReader('Gallery opened');
142        });
143
144        $(document).on('lgAfterSlide.lg', function(event, prevIndex, index) {
145            var $container = $(event.target);
146            var $items = $container.find('[data-lg-item-id="' + index + '"]');
147            if ($items.length > 0) {
148                var $item = $items.first();
149                var alt = $item.find('img').attr('alt') || '';
150                var caption = $item.attr('data-sub-html') || '';
151                var totalItems = $container.find('[data-lg-item-id]').length;
152
153                var announcement = 'Image ' + (index + 1) + ' of ' + totalItems;
154                if (alt) announcement += ', ' + alt;
155                if (caption) announcement += ', ' + $('<div>').html(caption).text();
156
157                announceToScreenReader(announcement);
158            }
159        });
160
161        // Caption transitions are CSS-driven (keyed to lightGallery's .lg-current
162        // state, like the official caption-animation demo). The mode is applied as
163        // an ml-cap-* class on .lg-container and the caption text is wrapped in
164        // .ml-caption-text at init — see applyGallerySettings / wrapMlCaptionText.
165
166        $(document).on('lgBeforeClose.lg', function() {
167            announceToScreenReader('Gallery closed');
168        });
169    });
170
171    /**
172     * Announce message to screen readers via live region
173     * @param {string} message - The message to announce
174     */
175    function announceToScreenReader(message) {
176        var $liveRegion = $('#ml-lightbox-sr-live');
177        if ($liveRegion.length > 0) {
178            $liveRegion.text(message);
179            // Clear after a delay to allow for new announcements
180            setTimeout(function() {
181                $liveRegion.text('');
182            }, 1000);
183        }
184    }
185
186    /**
187     * Remove conflicting attributes when lightbox button is present
188     * Prevents container clicks when buttons are available
189     */
190    function removeConflictingAttributes() {
191        $('.ml-lightbox-enabled').each(function() {
192            var $container = $(this);
193
194            if ($container.find('.ml-lightbox-button').length > 0) {
195                $container.removeAttr('href');
196                $container.removeAttr('data-src');
197                $container.removeAttr('data-thumb');
198            }
199        });
200    }
201
202    /**
203     * Ensure all lightbox-enabled links have accessible names
204     * Adds aria-label to links without accessible text
205     */
206    function ensureAccessibleNames() {
207        // Add aria-labels to all ml-lightbox-enabled links that wrap images
208        $('.ml-lightbox-enabled').each(function() {
209            var $link = $(this);
210
211            // Skip if link already has accessible text or aria-label
212            if ($link.attr('aria-label') || $link.text().trim()) {
213                return;
214            }
215
216            // Find image inside the link
217            var $img = $link.find('img').first();
218            if ($img.length > 0) {
219                // Ensure image has alt attribute
220                ensureImageAltAttribute($img);
221                addAccessibleNameToLink($link, $img);
222            }
223        });
224
225        // Also handle any links with data-src that might be lightbox links
226        $('a[data-src]').each(function() {
227            var $link = $(this);
228
229            // Skip if already processed or has accessible text
230            if ($link.attr('aria-label') || $link.text().trim()) {
231                return;
232            }
233
234            var $img = $link.find('img').first();
235            if ($img.length > 0) {
236                // Ensure image has alt attribute
237                ensureImageAltAttribute($img);
238                addAccessibleNameToLink($link, $img);
239            }
240        });
241    }
242
243    /**
244     * Ensure an image has an alt attribute
245     * @param {jQuery} $img - The image element
246     */
247    function ensureImageAltAttribute($img) {
248        if (typeof $img.attr('alt') === 'undefined') {
249            // Try to infer alt text from image filename
250            var src = $img.attr('src') || '';
251            var filename = src.split('/').pop().split('?')[0];
252            var altText = '';
253
254            if (filename) {
255                // Remove extension and convert dashes/underscores to spaces
256                altText = filename
257                    .replace(/\.(jpg|jpeg|png|gif|webp|svg)$/i, '')
258                    .replace(/[-_]/g, ' ')
259                    .trim();
260            }
261
262            // Set alt attribute (empty if we couldn't infer)
263            $img.attr('alt', altText);
264        }
265    }
266
267    /**
268     * Main initialization function
269     */
270    function initLightboxes() {
271
272        if (typeof lightGallery === 'undefined') {
273            console.warn('MetaSlider Lightbox: LightGallery not loaded');
274            return;
275        }
276
277        if (typeof mlLightboxSettings === 'undefined') {
278            console.warn('MetaSlider Lightbox: Settings not available');
279            return;
280        }
281
282        initLightboxSystems();
283
284    }
285
286    function initLightboxSystems() {
287        initMetaSlider();
288
289        var manualExcluded = mlLightboxSettings.manual_excluded === true || mlLightboxSettings.manual_excluded === '1' || mlLightboxSettings.manual_excluded === 1;
290
291        if (!manualExcluded) {
292            var $elements = $('.ml-lightbox-enabled');
293            var useButtonsForManual = false;
294            if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.metaslider_options) {
295                useButtonsForManual = mlLightboxSettings.metaslider_options.show_lightbox_button !== false;
296            }
297
298            $elements.each(function() {
299                var $element = $(this);
300                if (isInsideMetaSliderContainer($element)) {
301                    return;
302                }
303                initLightboxEnabled($element, useButtonsForManual);
304            });
305        }
306
307        var overrideEnabled = typeof mlLightboxSettings !== 'undefined' && (mlLightboxSettings.override_enlarge_on_click === true || mlLightboxSettings.override_enlarge_on_click === '1' || mlLightboxSettings.override_enlarge_on_click === 1);
308        if (overrideEnabled && !manualExcluded) {
309            var $wordpressOverrideGalleries = $('.wp-block-gallery').filter(function() {
310                var $gallery = $(this);
311                var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]');
312                var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]');
313                return ($wpImagesOld.length > 1 || $wpContainersNew.length > 1) &&
314                       !$gallery.hasClass('ml-lightbox-enabled') &&
315                       !$gallery.hasClass('lg-initialized');
316            });
317
318            $wordpressOverrideGalleries.each(function() {
319                initWordPressGallery($(this), useButtonsForManual);
320            });
321        }
322
323        var pageExcluded = mlLightboxSettings.page_excluded === true || mlLightboxSettings.page_excluded === '1' || mlLightboxSettings.page_excluded === 1;
324        if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.enable_galleries && !pageExcluded) {
325            var $wordpressGalleries = $('.wp-block-gallery').filter(function() {
326                var $gallery = $(this);
327
328                if ($gallery.attr('data-ml-exclude-gallery') === 'true') {
329                    return false;
330                }
331                var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]');
332                var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]');
333                var overrideEnabled = mlLightboxSettings.override_enlarge_on_click === true || mlLightboxSettings.override_enlarge_on_click === '1' || mlLightboxSettings.override_enlarge_on_click === 1;
334                if (!overrideEnabled && ($wpImagesOld.length > 0 || $wpContainersNew.length > 0)) {
335                    return false;
336                }
337                return ($wpImagesOld.length > 1 || $wpContainersNew.length > 1) &&
338                       !$gallery.hasClass('ml-lightbox-enabled') &&
339                       !$gallery.hasClass('lg-initialized');
340            });
341
342            $wordpressGalleries.each(function() {
343                initWordPressGallery($(this), useButtonsForManual);
344            });
345
346            var $classicGalleries = $('.gallery').filter(function() {
347                var $gallery = $(this);
348
349                if ($gallery.attr('data-ml-exclude-gallery') === 'true') {
350                    return false;
351                }
352
353                var $galleryLinks = $gallery.find('a.ml-gallery-lightbox');
354
355                return $galleryLinks.length > 1 &&
356                       !$gallery.hasClass('ml-lightbox-enabled') &&
357                       !$gallery.hasClass('lg-initialized');
358            });
359
360            $classicGalleries.each(function() {
361                initClassicGallery($(this));
362            });
363        }
364
365        if (!manualExcluded) {
366            var $wordpressContainersOld = $('.wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger');
367            var $wordpressContainersNew = $('.wp-lightbox-container[data-wp-interactive="core/image"]');
368            var $allWordpressContainers = $wordpressContainersOld.add($wordpressContainersNew);
369
370            var $unprocessedContainers = $allWordpressContainers.filter(function() {
371                var $figure = $(this);
372                var $parentGallery = $figure.closest('.wp-block-gallery');
373
374                var overrideEnabled = typeof mlLightboxSettings !== 'undefined' && (mlLightboxSettings.override_enlarge_on_click === true || mlLightboxSettings.override_enlarge_on_click === '1' || mlLightboxSettings.override_enlarge_on_click === 1);
375                if (!overrideEnabled) {
376                    return false;
377                }
378
379            if ($parentGallery.length > 0) {
380                var $galleryContainers = $parentGallery.find('.wp-lightbox-container[data-wp-interactive="core/image"], .wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger');
381                if ($galleryContainers.length > 1) {
382                    return false;
383                }
384            }
385
386            return !$figure.hasClass('ml-lightbox-enabled') &&
387                   !$figure.find('.ml-lightbox-enabled').length &&
388                   !$figure.hasClass('lg-initialized') &&
389                   ($parentGallery.length === 0 || !$parentGallery.hasClass('lg-initialized'));
390        });
391
392            $unprocessedContainers.each(function() {
393                var $container = $(this);
394                var $img = $container.find('img').first();
395
396                if ($img.length) {
397                    if (useButtonsForManual) {
398                        initWordPressEnlargeOnClickWithButton($img);
399                    } else {
400                        initWordPressEnlargeOnClick($img);
401                    }
402                }
403            });
404        }
405
406        var overrideEnabled = typeof mlLightboxSettings !== 'undefined' &&
407            (mlLightboxSettings.override_enlarge_on_click === true ||
408             mlLightboxSettings.override_enlarge_on_click === '1' ||
409             mlLightboxSettings.override_enlarge_on_click === 1);
410
411        if (overrideEnabled && !manualExcluded) {
412            var $enlargeOnClickGalleries = $('.wp-block-gallery').filter(function() {
413                var $gallery = $(this);
414                var $wpButtons = $gallery.find('button[data-wp-on], button.lightbox-trigger');
415
416                return $wpButtons.length > 1 &&
417                       !$gallery.hasClass('ml-lightbox-enabled') &&
418                       !$gallery.hasClass('lg-initialized');
419            });
420
421            $enlargeOnClickGalleries.each(function() {
422                initEnlargeOnClickGallery($(this), useButtonsForManual);
423            });
424        }
425
426        var linkOverrideEnabled = typeof mlLightboxSettings !== 'undefined' &&
427            (mlLightboxSettings.override_link_to_image_file === true ||
428             mlLightboxSettings.override_link_to_image_file === '1' ||
429             mlLightboxSettings.override_link_to_image_file === 1);
430
431        if (linkOverrideEnabled && !manualExcluded) {
432            var $linkToMediaManualGalleries = $('.wp-block-gallery').filter(function() {
433                var $gallery = $(this);
434                var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() {
435                    return $(this).find('img').length > 0;
436                });
437
438                return $mediaLinks.length > 1 &&
439                       !$gallery.hasClass('ml-lightbox-enabled') &&
440                       !$gallery.hasClass('lg-initialized') &&
441                       $gallery.find('.wp-lightbox-container').length === 0 &&
442                       $gallery.find('button[data-wp-on], button.lightbox-trigger').length === 0;
443            });
444
445            $linkToMediaManualGalleries.each(function() {
446                initLinkToMediaManualGallery($(this), useButtonsForManual);
447            });
448        }
449
450        if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.enable_galleries && !pageExcluded) {
451            var $linkToMediaGalleries = $('.wp-block-gallery').filter(function() {
452                var $gallery = $(this);
453
454                if ($gallery.attr('data-ml-exclude-gallery') === 'true') {
455                    return false;
456                }
457
458                var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".
458webp"]').filter(function() {
459                    return $(this).find('img').length > 0;
460                });
461
462                return $mediaLinks.length > 1 &&
463                       !$gallery.hasClass('ml-lightbox-enabled') &&
464                       !$gallery.hasClass('lg-initialized') &&
465                       $gallery.find('.wp-lightbox-container').length === 0;
466            });
467
468            $linkToMediaGalleries.each(function() {
469                initLinkToMediaGallery($(this), useButtonsForManual);
470            });
471        }
472
473        if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.enable_galleries && !pageExcluded) {
474            var $plainImageGalleries = $('.wp-block-gallery').filter(function() {
475                var $gallery = $(this);
476
477                if ($gallery.attr('data-ml-exclude-gallery') === 'true') {
478                    return false;
479                }
480
481                var $images = $gallery.find('img');
482                var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]');
483                var $wpLightboxImages = $gallery.find('img[data-wp-on], img[data-wp-interactive]');
484
485                return $images.length > 1 &&
486                       $mediaLinks.length === 0 &&
487                       $wpLightboxImages.length === 0 &&
488                       !$gallery.hasClass('ml-lightbox-enabled') &&
489                       !$gallery.hasClass('lg-initialized');
490            });
491
492            $plainImageGalleries.each(function() {
493                initPlainImageGallery($(this), useButtonsForManual);
494            });
495        }
496
497        if (mlLightboxSettings.override_link_to_image_file && !manualExcluded) {
498            var $mediaLinks = $('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() {
499                var $link = $(this);
500                var $parentGallery = $link.closest('.wp-block-gallery');
501                var $parentContainer = $link.closest('.wp-lightbox-container');
502                var $wooGallery = $link.closest('.woocommerce-product-gallery');
503                var $mlGallery = $link.closest('.ml-gallery-container');
504                return $link.find('img').length > 0 &&
505                       $parentGallery.length === 0 &&
506                       $parentContainer.length === 0 &&
507                       $wooGallery.length === 0 &&
508                       $mlGallery.length === 0 &&
509                       !$link.hasClass('lg-initialized') &&
510                       !$link.closest('.ml-lightbox-enabled').length;
511            });
512
513            $mediaLinks.each(function() {
514                if (useButtonsForManual) {
515                    initLinkToMediaFileWithButton($(this));
516                } else {
517                    initLinkToMediaFile($(this));
518                }
519            });
520        }
521
522        if (mlLightboxSettings.enable_on_content && !pageExcluded) {
523            var contentImageSelectors = [];
524            var containers = CONSTANTS.SELECTORS.CONTENT_CONTAINERS.split(', ');
525            containers.forEach(function(container) {
526                contentImageSelectors.push(container + ' img[src$=".jpg"]');
527                contentImageSelectors.push(container + ' img[src$=".jpeg"]');
528                contentImageSelectors.push(container + ' img[src$=".png"]');
529                contentImageSelectors.push(container + ' img[src$=".gif"]');
530                contentImageSelectors.push(container + ' img[src$=".webp"]');
531                contentImageSelectors.push(container + ' img[src$=".svg"]');
532            });
533
534            var $standaloneImages = $(contentImageSelectors.join(', ')).filter(function() {
535                var $img = $(this);
536                if ($img.closest('.wp-block-gallery').length > 0) {
537                    return false;
538                }
539                if ($img.closest('.woocommerce-product-gallery').length > 0) {
540                    return false;
541                }
542                if (isInsideMetaSliderContainer($img)) {
543                    return false;
544                }
545                if ($img.closest('.flex-control-nav, .flex-control-thumbs, .filmstrip').length > 0) {
546                    return false;
547                }
548                if ($img.hasClass('ml-lightbox-enabled') ||
549                    $img.closest('.ml-lightbox-wrapper').length > 0 ||
550                    $img.closest('.ml-image-overlay').length > 0 ||
551                    $img.siblings('.ml-lightbox-button').length > 0 ||
552                    $img.parent().hasClass('lg-initialized')) {
553                    return false;
554                }
555                if ($img.parent('a').length > 0) {
556                    return false;
557                }
558                if ($img.hasClass('wp-post-image')) {
559                    return false;
560                }
561                if ($img.attr('data-wp-on') || $img.attr('data-wp-interactive')) {
562                    return false;
563                }
564                if ($img.attr('data-ml-exclude') === 'true') {
565                    return false;
566                }
567
568                var uiClasses = ['custom-logo', 'site-logo', 'logo', 'avatar', 'icon', 'emoji', 'thumbnail', 'thumb'];
569                for (var i = 0; i < uiClasses.length; i++) {
570                    if ($img.hasClass(uiClasses[i])) {
571                        return false;
572                    }
573                }
574
575                var uiContainers = ['.wp-block-site-logo', '.site-logo', '.site-branding', '.site-header', '.site-footer', '.navigation', '.nav', '.menu', '.widget', '.sidebar'];
576                for (var j = 0; j < uiContainers.length; j++) {
577                    if ($img.closest(uiContainers[j]).length > 0) {
578                        return false;
579                    }
580                }
581
582                var minWidth = mlLightboxSettings.minimum_image_width || 200;
583                var minHeight = mlLightboxSettings.minimum_image_height || 200;
584
585                // Check displayed dimensions (not actual file size) to match server-side behavior
586                var imgWidth = $img.width() || $img.attr('width') || 0;
587                var imgHeight = $img.height() || $img.attr('height') || 0;
588
589                if (minWidth > 0 && imgWidth > 0 && imgWidth < minWidth) {
590                    return false;
591                }
592                if (minHeight > 0 && imgHeight > 0 && imgHeight < minHeight) {
593                    return false;
594                }
595
596                return true;
597            });
598
599            $standaloneImages.each(function() {
600                if (useButtonsForManual) {
601                    handleSingleElementWithButton($(this));
602                } else {
603                    handleSingleElement($(this));
604                }
605            });
606        }
607
608        if (mlLightboxSettings.enable_featured_images && !pageExcluded) {
609            $('a.ml-featured-lightbox[data-src]').filter(function() {
610                return !$(this).hasClass('lg-initialized');
611            }).each(function() {
612                if (useButtonsForManual) {
613                    initFeaturedImageWithButton($(this));
614                } else {
615                    initFeaturedImage($(this));
616                }
617            });
618        }
619
620        // Initialize [ml_gallery] shortcode containers.
621        // Uses getLightboxSettings(true) so gallery-specific options (counter,
622        // thumbnails strip, etc.) apply. Respects useButtonsForManual for
623        // consistent button/no-button behaviour across the plugin.
624        // Note: pageExcluded does NOT gate gallery init — galleries are explicitly
625        // placed by the user and are unaffected by Automatic Mode content filtering.
626        {
627            /**
628             * Apply all per-gallery data-lg-* settings onto a settings object.
629             * Used by both regular and carousel init paths.
630             */
631            // Wrap caption HTML in a .ml-caption-text span so the caption
632            // transition animates the text, not the .lg-sub-html bar. Set in JS
633            // (never in the PHP data-sub-html attribute — wptexturize mangles
634            // markup inside attributes). Idempotent.
635            function wrapMlCaptionText(sub) {
636                if (sub && sub.indexOf('ml-caption-text') === -1) {
637                    return '<span class="ml-caption-text">' + sub + '</span>';
638                }
639                return sub;
640            }
641
642            function applyGallerySettings(settings, $container) {
643                var lgClass = $container.data('lg-class') || '';
644                if (lgClass) { settings.addClass = lgClass; }
645
646                // Caption transition (CSS state-driven, per the official demo):
647                // append the caption inside each slide item so .lg-current can
648                // scope it, pass the mode as an ml-cap-* class, and delay slides
649                // so the animation can play.
650                var captionTransition = $container.data('lg-caption-transition');
651                if (captionTransition && captionTransition !== 'none') {
652                    settings.appendSubHtmlTo = '.lg-item';
653                    settings.slideDelay = settings.slideDelay || 400;
654                    settings.addClass = (settings.addClass ? settings.addClass + ' ' : '') + 'ml-cap-' + captionTransition;
655                }
656
657                settings.mode     = $container.data('lg-mode') || settings.mode;
658                settings.controls = !!parseInt($container.data('lg-control
658s'), 10);
659                settings.counter  = !!parseInt($container.data('lg-counter'), 10);
660                settings.download = !!parseInt($container.data('lg-download'), 10);
661                settings.loop     = !!parseInt($container.data('lg-loop'), 10);
662
663                if (!parseInt($container.data('lg-captions'), 10)) {
664                    settings.subHtml = '';
665                    settings.getCaptionFromTitleOrAlt = false;
666                }
667
668                // Thumbnails
669                var wantThumbs = !!parseInt($container.data('lg-thumbnails'), 10);
670                if (typeof lgThumbnail !== 'undefined') {
671                    if (wantThumbs) {
672                        if (settings.plugins.indexOf(lgThumbnail) === -1) {
673                            settings.plugins.push(lgThumbnail);
674                        }
675                    } else {
676                        settings.plugins = settings.plugins.filter(function(p) { return p !== lgThumbnail; });
677                    }
678                }
679                if (wantThumbs) {
680                    settings.thumbWidth   = 100;
681                    settings.thumbHeight  = '80px';
682                    settings.thumbMargin  = 4;
683                    settings.exThumbImage = 'data-thumb';
684                }
685
686                // Plugin toggles
687                if (!!parseInt($container.data('lg-zoom'), 10) && typeof lgZoom !== 'undefined') {
688                    if (settings.plugins.indexOf(lgZoom) === -1) { settings.plugins.push(lgZoom); }
689                } else if (typeof lgZoom !== 'undefined') {
690                    settings.plugins = settings.plugins.filter(function(p) { return p !== lgZoom; });
691                }
692
693                if (!!parseInt($container.data('lg-fullscreen'), 10) && typeof lgFullscreen !== 'undefined') {
694                    if (settings.plugins.indexOf(lgFullscreen) === -1) { settings.plugins.push(lgFullscreen); }
695                }
696
697                if (!!parseInt($container.data('lg-rotate'), 10) && typeof lgRotate !== 'undefined') {
698                    if (settings.plugins.indexOf(lgRotate) === -1) { settings.plugins.push(lgRotate); }
699                }
700
701                if (!!parseInt($container.data('lg-share'), 10) && typeof lgShare !== 'undefined') {
702                    if (settings.plugins.indexOf(lgShare) === -1) { settings.plugins.push(lgShare); }
703                }
704
705                if (!!parseInt($container.data('lg-autoplay'), 10) && typeof lgAutoplay !== 'undefined') {
706                    if (settings.plugins.indexOf(lgAutoplay) === -1) { settings.plugins.push(lgAutoplay); }
707                    settings.autoplay           = true;
708                    settings.progressBar        = true;
709                    settings.autoplayFirstVideo = false;
710                    settings.autoplayInterval   = parseInt($container.data('lg-autoplay-interval'), 10) || 3000;
711                }
712
713                if (!!parseInt($container.data('lg-pager'), 10) && typeof lgPager !== 'undefined') {
714                    if (settings.plugins.indexOf(lgPager) === -1) { settings.plugins.push(lgPager); }
715                }
716
717                if (!!parseInt($container.data('lg-hash'), 10) && typeof lgHash !== 'undefined') {
718                    if (settings.plugins.indexOf(lgHash) === -1) { settings.plugins.push(lgHash); }
719                }
720
721                // Built-in behaviour
722                settings.swipeToClose = !!parseInt($container.data('lg-swipe-close'), 10);
723                settings.mousewheel   = !!parseInt($container.data('lg-mousewheel'), 10);
724                settings.keyPress     = !!parseInt($container.data('lg-keyboard'), 10);
725
726                return settings;
727            }
728
729            // Map of layout name → lightGallery plugins for inline (container) mode.
730            // Add future inline layouts here — one line per layout.
731            var inlineLayoutPlugins = {
732                carousel: []
733            };
734
735            $('.ml-gallery-container[data-ml-gallery]').filter(function() {
736                return !$(this).hasClass('lg-initialized') && $(this).data('ml-lightbox') !== 0;
737            }).each(function() {
738                var $container = $(this);
739
740                // ── Inline layouts (lightGallery container mode) ──────────── //
741                var mlLayout = $container.data('ml-layout');
742
743                if ( inlineLayoutPlugins.hasOwnProperty( mlLayout ) ) {
744                    var dynamicEl = [];
745                    $container.find('a[data-src]').each(function () {
746                        var $a = $(this);
747                        var el = { src: $a.attr('data-src') };
748                        if ($a.attr('data-thumb'))    { el.thumb   = $a.attr('data-thumb'); }
749                        if ($a.attr('data-sub-html')) { el.subHtml = wrapMlCaptionText($a.attr('data-sub-html')); }
750                        dynamicEl.push(el);
751                    });
752
753                    if (dynamicEl.length) {
754                        var inlineSettings = applyGallerySettings({
755                            container       : $container[0],
756                            dynamic         : true,
757                            dynamicEl       : dynamicEl,
758                            plugins         : inlineLayoutPlugins[ mlLayout ],
759                            hash            : false,
760                            closable        : false,
761                            showMaximizeIcon: true,
762                            appendSubHtmlTo : '.lg-item',
763                            slideDelay      : 400,
764                        }, $container);
765
766                        try {
767                            var inlineGallery = lightGallery($container[0], inlineSettings);
768                            inlineGallery.openGallery();
769                            $container.addClass('lg-initialized');
770                        } catch (error) {
771                            console.error('MetaSlider Lightbox: inline gallery init error:', error);
772                        }
773                    }
774                    return;
774 // skip regular lightbox init for inline layouts
775                }
776
777                var settings = getLightboxSettings(true);
778                applyGallerySettings(settings, $container);
779
780                // A gallery can force its own "Open in Gallery" button via
781                // data-ml-show-button, overriding the global manual-button setting.
782                var perGalleryButton    = $container.attr('data-ml-show-button') === '1';
783                var effectiveUseButtons = useButtonsForManual || perGalleryButton;
784
785                if (effectiveUseButtons) {
786                    // Per-gallery galleries carry their own icon/text; global button
787                    // mode falls back to getButtonText() (per-slider icon + global text).
788                    var perIcon = perGalleryButton && $container.attr('data-ml-button-icon') === '1';
789                    var perText = perGalleryButton ? ($container.attr('data-ml-button-text') || '') : '';
790
791                    $container.find('a[data-src]').each(function() {
792                        var $a      = $(this);
793                        var dataSrc = $a.attr('data-src');
794                        var $img    = $a.find('img').first();
795                        var imgSrc  = $img.attr('src') || dataSrc;
796                        var alt     = $img.attr('alt') || '';
797
798                        var btnLabel;
799                        if (perGalleryButton) {
800                            if (perIcon) {
801                                btnLabel = '<svg class="ml-lightbox-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M21 9V3H15M21 3L13 11M10 5H7.8C6.11984 5 5.27976 5 4.63803 5.32698C4.07354 5.6146 3.6146 6.07354 3.32698 6.63803C3 7.27976 3 8.11984 3 9.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H14.2C15.8802 21 16.7202 21 17.362 20.673C17.9265 20.3854 18.3854 19.9265 18.673 19.362C19 18.7202 19 17.8802 19 16.2V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
802                            } else {
803                                // Per-gallery button: a blank custom text defaults to
804                                // "Open in Gallery" (matches the admin placeholder), not
805                                // the global lightbox button text.
806                                btnLabel = perText || 'Open in Gallery';
807                            }
808                        } else {
809                            btnLabel = getButtonText();
810                        }
811
812                        var $btn = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-gallery" href="#">' + btnLabel + '</a>');
813                        $btn.attr({
814                            'data-src'  : dataSrc,
815                            'data-thumb': $a.attr('data-thumb') || imgSrc,
816                            'aria-label': mlLightboxSettings.view_image_label + (alt ? ': ' + alt : ''),
817                        });
818                        // The button becomes lightGallery's slide source (selector below),
819                        // so carry the caption over or it would be dropped.
820                        var subHtml = $a.attr('data-sub-html');
821                        if (subHtml) {
822                            $btn.attr('data-sub-html', subHtml);
823                        }
824                        $a.css('position', 'relative').append($btn);
825
826                        // Button mode: only the button should open the lightbox.
827                        // Strip the wrapper link's own attributes so clicking the
828                        // image itself doesn't navigate to the image URL.
829                        $a.removeAttr('href').removeAttr('data-src').removeAttr('data-thumb');
830                    });
831                    settings.selector = '.ml-lightbox-button';
832                } else {
833                    settings.selector = 'a[data-src]';
834                }
835
836                // Wrap caption text so the caption transition animates the text,
837                // not the bar. Only when a transition is active.
838                var galleryCaptionTransition = $container.data('lg-caption-transition');
839                if (galleryCaptionTransition && galleryCaptionTransition !== 'none') {
840                    $container.find('a[data-sub-html]').each(function() {
841                        var $a = $(this);
842                        $a.attr('data-sub-html', wrapMlCaptionText($a.attr('data-sub-html')));
843                    });
844                }
845
846                try {
847                    $container[0]._mlLgInstance = lightGallery($container[0], settings);
848                    $container.addClass('lg-initialized');
849                } catch (error) {
850                    console.error('MetaSlider Lightbox: Error initializing gallery shortcode:', error);
851                }
852            });
853        }
854    }
855
856    function initLightboxEnabled($container, useButtonMode) {
857        useButtonMode = useButtonMode || false;
858
859        if ($container.hasClass('lg-initialized') || $container.data('lightgallery')) {
860            return;
861        }
862        var $imageLinks = $container.find('a[data-src], a[data-video]');
863
864        if ($imageLinks.length === 0) {
865            var $images = $container.find('img');
866
867            if ($images.length > 1) {
868                $images.each(function() {
869                    var $img = $(this);
870                    var $figure = $img.closest('figure, div');
871                    if ($figure.length === 0) $figure = $img.parent();
872
873                    if ($figure.find('.ml-image-overlay, .ml-lightbox-button').length > 0 || $figure.hasClass('lg-initialized')) {
874                        return;
875                    }
876
877                    var src = $img.attr('src');
878                    if (!src) return;
879
880                    var fullUrl = removeWordPressSizeSuffix(src);
881
882                    if (useButtonMode) {
883                        var $button = $('<a href="#" class="ml-lightbox-button ml-button-manual ml-button-manual-gallery">' + getButtonText() + '</a>');
884                        $button.attr({
885                            'data-src': fullUrl,
886                            'data-thumb': src
887                        });
888                        $figure.css('position', 'relative').append($button);
889                    } else {
890                        var $overlay = $('<a href="#" class="ml-image-overlay"></a>');
891                        $overlay.attr({
892                            'data-src': fullUrl,
893                            'data-thumb': src
894                        });
895
896                        // Add accessibility attributes
897                        var altText = $img.attr('alt') || '';
898                        addOverlayAccessibility($overlay, 'image', altText);
899
900                        $figure.css('position', 'relative').prepend($overlay);
901                    }
902                });
903
904                var gallerySettings = getLightboxSettings(true);
905                gallerySettings.selector = useButtonMode ? '.ml-lightbox-button' : '.ml-image-overlay';
906
907                try {
908                    var instance = lightGallery($container[0], gallerySettings);
909                    $container.addClass('lg-initialized');
910                } catch (error) {
911                    console.error('MetaSlider Lightbox: Error initializing manual gallery:', error);
912                }
913                return;
914            }
915
916            if (useButtonMode) {
917                handleSingleElementWithButton($container);
918            } else {
919                handleSingleElement($container);
920            }
921            return;
922        }
923
924        var isGallery = $imageLinks.length > 1;
925
926
927        var settings = getLightboxSettings(isGallery);
928
929        try {
930            var instance = lightGallery($container[0], settings);
931            $container.addClass('lg-initialized');
932
933        } catch (error) {
934            console.error('MetaSlider Lightbox: Error initializing LightGallery:', error);
935        }
936    }
937
938    /**
939     * Handle single elements that don't have PHP-processed data-src links
940     */
941    function handleSingleElement($element) {
942        var $target = null;
943        var dataSrc = '';
944        var dataThumb = '';
945        var isVideo = false;
946
947        if ($element.is('img')) {
948            var src = $element.attr('src');
949            if (!src) return;
950
951            var $wrapper = $('<div class="ml-lightbox-wrapper"></div>');
952            $element.wrap($wrapper);
953            $target = $element.parent();
954
955            var fullUrl = removeWordPressSizeSuffix(src);
956
957            $target.attr({
958                'data-src': fullUrl,
959                'data-thumb': src
960            });
961
962            var caption = extractCaption($element);
963            if (caption) {
964                $target.attr('data-sub-html', caption);
965            }
966
967
968        } else if ($element.is('a')) {
969            var href = $element.attr('href');
970            if (!href) return;
971
972            $target = $element;
973            var $img = $element.find('img').first();
974            var imgSrc = $img.length ? $img.attr('src') : href;
975
976            isVideo = isVideoUrl(href);
977
978            if (isVideo) {
979                var videoUrl = getVideoUrl(href);
980                $target.attr({
981                    'data-src': videoUrl,
982                    'data-thumb': imgSrc || getVideoThumbnail(href)
983                });
984            } else {
985                $target.attr({
986                    'data-src': href,
987                    'data-thumb': imgSrc
988                });
989            }
990
991            var caption = extractCaption($element);
992            if (caption) {
993                $target.attr('data-sub-html', caption);
994            }
995
996        } else {
997
998            var $iframe = $element.find('iframe[src*="youtube"], iframe[src*="vimeo"]').first();
999            var $video = $element.find('video').first();
1000
1001            if ($iframe.length > 0) {
1002                var iframeSrc = $iframe.attr('src');
1003
1004                var $overlay = $('<a href="#" class="ml-video-overlay"></a>');
1005
1006                var videoUrl = getVideoUrl(iframeSrc);
1007                $overlay.attr({
1008                    'data-src': videoUrl,
1009                    'data-thumb': getVideoThumbnail(iframeSrc)
1010                });
1011
1012                var caption = extractCaption($element);
1013                if (caption) {
1014                    $overlay.attr('data-sub-html', caption);
1015                }
1016
1017                $element.css('position', 'relative').prepend($overlay);
1018                $target = $element;
1019
1020                isVideo = true;
1021
1022            } else if ($video.length > 0) {
1023                var videoSrc = $video.attr('src') || $video.find('source').first().attr('src');
1024                if (!videoSrc) return;
1025
1026                var videoData = {
1027                    source: [{ src: videoSrc, type: getVideoType(videoSrc) }],
1028                    attributes: { preload: false, controls: true }
1029                };
1030
1031                var $overlay = $('<a href="#" class="ml-video-overlay"></a>');
1032
1033                $overlay.attr({
1034                    'data-src': '',
1035                    'data-video': JSON.stringify(videoData),
1036                    'data-thumb': $video.attr('poster') || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+'
1037                });
1038
1039                var caption = extractCaption($element);
1040                if (caption) {
1041                    $overlay.attr('data-sub-html', caption);
1042                }
1043
1044                $element.css('position', 'relative').prepend($overlay);
1045                $target = $element;
1046
1047                isVideo = true;
1048
1049            } else {
1050                var $img = $element.find('img').first();
1051                if (!$img.length) return;
1052
1053                var src = $img.attr('src');
1054                if (!src) return;
1055
1056                var fullUrl = removeWordPressSizeSuffix(src);
1057
1058                if ($element.hasClass('wp-lightbox-container')) {
1059                    var $overlay = $('<a href="#" class="ml-image-overlay"></a>');
1060
1061                    $overlay.attr({
1062                        'data-src': fullUrl,
1063                        'data-thumb': src
1064                    });
1065
1066                    var caption = extractCaption($element);
1067                    if (caption) {
1068                        $overlay.attr('data-sub-html', caption);
1069                    }
1070
1071                    // Add accessibility attributes
1072                    var altText = $img.attr('alt') || '';
1073                    addOverlayAccessibility($overlay, 'image', altText);
1074
1075                    $element.css('position', 'relative').prepend($overlay);
1076                    $target = $element;
1077
1078                } else {
1079                    $target = $element;
1080                    $target.attr({
1081                        'data-src': fullUrl,
1082                        'data-thumb': src
1083                    });
1084
1085                    var caption = extractCaption($element);
1086                    if (caption) {
1087                        $target.attr('data-sub-html', caption);
1088                    }
1089
1090                }
1091            }
1092        }
1093
1094        if (!$target) return;
1095
1096        var settings = getLightboxSettings(false);
1097
1098        if (isVideo && $target.find('.ml-video-overlay').length > 0) {
1099            settings.selector = '.ml-video-overlay';
1100        } else if (!isVideo && $target.find('.ml-image-overlay').length > 0) {
1101            settings.selector = '.ml-image-overlay';
1102        } else {
1103            settings.selector = 'this';
1104        }
1105
1106        try {
1107            var instance = lightGallery($target[0], settings);
1108            $target.addClass('lg-initialized');
1109
1110        } catch (error) {
1111            console.error('MetaSlider Lightbox: Error initializing single lightbox:', error);
1112        }
1113    }
1114
1115    /**
1116     * Handle single elements that need button mode instead of overlay mode
1117     */
1118    function handleSingleElementWithButton($element) {
1119        if ($element.hasClass('lg-initialized') || $element.data('lightgallery')) {
1120            return;
1121        }
1122
1123        var $button = null;
1124
1125        if ($element.is('img')) {
1126            var src = $element.attr('src');
1127            if (!src) return;
1128
1129            var fullUrl = removeWordPressSizeSuffix(src);
1130
1131            $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-img" href="#">' + getButtonText() + '</a>');
1132
1133            $button.attr({
1134                'data-src': fullUrl,
1135                'data-thumb': src
1136            });
1137
1138            var caption = extractCaption($element);
1139            if (caption) {
1140                $button.attr('data-sub-html', caption);
1141            }
1142
1143            $element.wrap('<div style="position: relative; display: inline-block;"></div>');
1144            $element.parent().append($button);
1145
1146            var settings = getLightboxSettings(false);
1147            settings.selector = '.ml-lightbox-button';
1148
1149            try {
1150                var instance = lightGallery($element.parent()[0], settings);
1151                $element.parent().addClass('lg-initialized');
1152            } catch (error) {
1153                console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error);
1154            }
1155
1156        } else if ($element.is('a')) {
1157            var href = $element.attr('href');
1158            if (!href) return;
1159
1160            var $img = $element.find('img').first();
1161            var imgSrc = $img.length > 0 ? $img.attr('src') : '';
1162
1163            var isVideo = isVideoUrl(href);
1164
1165            if (isVideo) {
1166                var videoUrl = getVideoUrl(href);
1167                $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-video" href="#">' + getButtonText() + '</a>');
1168
1169                $button.attr({
1170                    'data-src': videoUrl,
1171                    'data-thumb': imgSrc || getVideoThumbnail(href)
1172                });
1173            } else {
1174                $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-single-link" href="#">' + getButtonText() + '</a>');
1175
1176                $button.attr({
1177                    'data-src': href,
1178                    'data-thumb': imgSrc
1179                });
1180            }
1181
1182            var caption = extractCaption($element);
1183            if (caption) {
1184                $button.attr('data-sub-html', caption);
1185            }
1186
1187            $element.css('position', 'relative').append($button);
1188
1189            var settings = getLightboxSettings(false);
1190            settings.selector = '.ml-lightbox-button';
1191
1192            try {
1193                var instance = lightGallery($element[0], settings);
1194                $element.addClass('lg-initialized');
1195            } catch (error) {
1196                console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error);
1197            }
1198
1199        } else {
1200
1201            var $existingButton = $element.find('.ml-lightbox-button').first();
1202            if ($existingButton.length > 0) {
1203                var settings = getLightboxSettings(false);
1204                settings.selector = '.ml-lightbox-button';
1205
1206                try {
1207                    var instance = lightGallery($element[0], settings);
1208                    $element.addClass('lg-initialized');
1209                } catch (error) {
1210                    console.error('MetaSlider Lightbox: Error initializing existing button:', error);
1211                }
1212                return;
1213            }
1214
1215            var $img = $element.find('img').first();
1216            var $iframe = $element.find('iframe[src*="youtube"], iframe[src*="vimeo"]').first();
1217            var $video = $element.find('video').first();
1218
1219            if ($img.length > 0) {
1220                var src = $img.attr('src');
1221                if (src) {
1222                    var fullUrl = removeWordPressSizeSuffix(src);
1223
1224                    $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-img" href="#">' + getButtonText() + '</a>');
1225
1226                    $button.attr({
1227                        'data-src': fullUrl,
1228                        'data-thumb': src
1229                    });
1230                }
1231            } else if ($iframe.length > 0) {
1232                var iframeSrc = $iframe.attr('src');
1233                if (iframeSrc) {
1234                    var videoUrl = getVideoUrl(iframeSrc);
1235
1236                    $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-iframe" href="#">' + getButtonText() + '</a>');
1237
1238                    $button.attr({
1239                        'data-src': videoUrl,
1240                        'data-thumb': getVideoThumbnail(iframeSrc)
1241                    });
1242                }
1243            } else if ($video.length > 0) {
1244                var videoSrc = $video.attr('src') || $video.find('source').first().attr('src');
1245                if (videoSrc) {
1246                    $button = $('<a class="ml-lightbox-button ml-button-manual ml-button-manual-container-video" href="#">' + getButtonText() + '</a>');
1247
1248                    var videoData = {
1249                        source: [{
1250                            src: videoSrc,
1251                            type: getVideoType(videoSrc)
1252                        }],
1253                        attributes: {
1254                            preload: false,
1255                            controls: true
1256                        }
1257                    };
1258
1259                    $button.attr({
1260                        'data-src': '',
1261                        'data-video': JSON.stringify(videoData),
1262                        'data-thumb': $video.attr('poster') || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+'
1263                    });
1264                }
1265            }
1266
1267            if ($button) {
1268                var caption = extractCaption($element);
1269                if (caption) {
1270                    $button.attr('data-sub-html', caption);
1271                }
1272
1273                $element.css('position', 'relative').append($button);
1274
1275                var settings = getLightboxSettings(false);
1276                settings.selector = '.ml-lightbox-button';
1277
1278                try {
1279                    var instance = lightGallery($element[0], settings);
1280                    $element.addClass('lg-initialized');
1281                } catch (error) {
1282                    console.error('MetaSlider Lightbox: Error initializing button mode for manual lightbox:', error);
1283                }
1284            }
1285        }
1286    }
1287
1288    /**
1289     * Get LightGallery settings based on admin configuration
1290     */
1291    function getLightboxSettings(isGallery) {
1292        var metasliderOptions = mlLightboxSettings.metaslider_options || {};
1293
1294        var settings = {
1295            selector: 'a[data-src]',
1296            mode: 'lg-fade',
1297            speed: 350,
1298            download: false,
1299            counter: isGallery,
1300            closable: true,
1301            closeOnTap: true,
1302            controls: !!metasliderOptions.show_arrows,
1303            plugins: [],
1304            // Accessibility settings
1305            escapeKey: true,  // Allow Escape key to close lightbox
1306            keyPress: true,    // Enable keyboard navigation (arrows, Esc)
1307            mousewheel: true,  // Allow mousewheel navigation
1308            allowMediaOverlap: false,  // Prevent overlap for better screen reader support
1309            ariaLabelledby: '',  // Will be set dynamically per image
1310            ariaDescribedby: ''  // Will be set dynamically per image
1311        };
1312
1313        if (typeof _mlLk !== 'undefined') {
1314            settings.licenseKey = _mlLk;
1315        }
1316
1317        if (!shouldShowCaptions()) {
1318            settings.subHtml = '';
1319            settings.getCaptionFromTitleOrAlt = false;
1320        }
1321
1322        if (isGallery && metasliderOptions.show_thumbnails && typeof lgThumbnail !== 'undefined') {
1323            settings.plugins.push(lgThumbnail);
1324            settings.thumbWidth = 100;
1325            settings.thumbHeight = '80px';
1326            settings.thumbMargin = 4;
1327            settings.exThumbImage = 'data-thumb';
1328        }
1329
1330        if (typeof lgVideo !== 'undefined') {
1331            settings.plugins.push(lgVideo);
1332            settings.videoMaxSize = '1280-720';
1333        }
1334
1335        if (typeof window.MetaSliderLightboxPro !== 'undefined' &&
1336            typeof window.MetaSliderLightboxPro.enhanceSettings === 'function') {
1337            settings = window.MetaSliderLightboxPro.enhanceSettings(settings, null);
1338        }
1339
1340        return settings;
1341    }
1342
1343    /**
1344     * Remove WordPress image size suffix to get full-size image URL
1345     */
1346    function removeWordPressSizeSuffix(url) {
1347        if (!url) return '';
1348        return url.replace(/-\d+x\d+(\.[^.]+)$/, '$1');
1349    }
1350
1351    /**
1352     * Check if URL is a video URL
1353     */
1354    function isVideoUrl(url) {
1355        if (!url) return false;
1356
1357        return /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/|vimeo\.com\/(?:video\/)?|\.(?:mp4|webm|ogg|mov|avi)(?:\?|$))/i.test(url);
1358    }
1359
1360    /**
1361     * Get video URL in format that LightGallery expects
1362     * LightGallery automatically detects and handles YouTube/Vimeo URLs
1363     */
1364    function getVideoUrl(url) {
1365        if (!url) return '';
1366
1367        var youtubeMatch = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
1368        if (youtubeMatch) {
1369            return 'https://www.youtube.com/watch?v=' + youtubeMatch[1];
1370        }
1371
1372        var vimeoMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
1373        if (vimeoMatch) {
1374            return 'https://vimeo.com/' + vimeoMatch[1];
1375        }
1376
1377        return url;
1378    }
1379
1380    /**
1381     * Get video thumbnail URL
1382     */
1383    function getVideoThumbnail(url) {
1384        if (!url) return '';
1385
1386        var youtubeMatch = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
1387        if (youtubeMatch) {
1388            return 'https://img.youtube.com/vi/' + youtubeMatch[1] + '/maxresdefault.jpg';
1389        }
1390
1391        var vimeoMatch = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
1392        if (vimeoMatch) {
1393            return 'https://vumbnail.com/' + vimeoMatch[1] + '.jpg';
1394        }
1395
1396        return '';
1397    }
1398
1399    /**
1400     * Initialize a WordPress "Enlarge on Click" gallery
1401     */
1402    function initWordPressGallery($gallery, useButtonMode) {
1403        useButtonMode = useButtonMode || false;
1404
1405        var $wpImagesOld = $gallery.find('.wp-lightbox-container img[data-wp-on], .wp-lightbox-container img[data-wp-interactive]');
1406        var $wpContainersNew = $gallery.find('.wp-lightbox-container[data-wp-interactive="core/image"]');
1407
1408        var $allContainers = $();
1409
1410        $wpImagesOld.each(function() {
1411            $allContainers = $allContainers.add($(this).closest('.wp-lightbox-container'));
1412        });
1413
1414        $allContainers = $allContainers.add($wpContainersNew);
1415
1416        if ($allContainers.length <= 1) return;
1417
1418        $allContainers.each(function() {
1419            var $figure = $(this);
1420            var $img = $figure.find('img').first();
1421
1422            if ($figure.find('.ml-image-overlay, .ml-lightbox-button').length > 0 || $figure.hasClass('lg-initialized')) {
1423                return;
1424            }
1425
1426            var src = $img.attr('src');
1427            if (!src) return;
1428
1429            var fullUrl = removeWordPressSizeSuffix(src);
1430
1431            if (useButtonMode) {
1432                var $button = $('<a href="#" class="ml-lightbox-button ml-button-manual ml-button-manual-gallery">' + getButtonText() + '</a>');
1433                $button.attr({
1434                    'data-src': fullUrl,
1435                    'data-thumb': src
1436                });
1437                $figure.css('position', 'relative').append($button);
1438            } else {
1439                var $overlay = $('<a href="#" class="ml-image-overlay"></a>');
1440                $overlay.attr({
1441                    'data-src': fullUrl,
1442                    'data-thumb': src
1443                });
1444
1445                // Add accessibility attributes
1446                var altText = $img.attr('alt') || '';
1447                addOverlayAccessibility($overlay, 'image', altText);
1448
1449                $figure.css('position', 'relative').prepend($overlay);
1450            }
1451        });
1452
1453        var gallerySettings = getLightboxSettings(true);
1454        gallerySettings.selector = useButtonMode ? '.ml-lightbox-button' : '.ml-image-overlay';
1455
1456        gallerySettings.galleryId = 'ml-gallery-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
1457
1458        try {
1459            var instance = lightGallery($gallery[0], gallerySettings);
1460            $gallery.addClass('lg-initialized');
1461
1462        } catch (error) {
1463            console.error('MetaSlider Lightbox: Error initializing WordPress gallery:', error);
1464        }
1465    }
1466
1467
1468    /**
1469     * Initialize a classic [gallery] shortcode gallery
1470     */
1471    function initClassicGallery($gallery) {
1472        var $galleryLinks = $gallery.find('a.ml-gallery-lightbox');
1473
1474        if ($galleryLinks.length <= 1) return;
1475
1476        $galleryLinks.each(function() {
1477            var $link = $(this);
1478            var $img = $link.find('img').first();
1479
1480            if ($link.attr('data-src')) {
1481                return;
1482            }
1483
1484            var href = $link.attr('href');
1485            var src = $img.attr('src');
1486
1487            if (!href) return;
1488
1489            $link.attr({
1490                'data-src': href,
1491                'data-thumb': src || href
1492            });
1493
1494            var caption = escapeHtml($img.attr('alt') || $img.attr('title') || '');
1495            var $figcaption = $link.closest('.gallery-item').find('figcaption, .wp-caption-text');
1496            if ($figcaption.length > 0) {
1497                caption = escapeHtml($figcaption.text().trim());
1498            }
1499
1500            if (caption) {
1501                $link.attr('data-sub-html', '<div class="lg-sub-html"><p>' + caption + '</p></div>');
1502            }
1503        });
1504
1505        var gallerySettings = getLightboxSettings(true);
1506        gallerySettings.selector = 'a.ml-gallery-lightbox';
1507
1508        gallerySettings.galleryId = 'ml-gallery-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
1509
1510        try {
1511            var instance = lightGallery($gallery[0], gallerySettings);
1512            $gallery.addClass('lg-initialized');
1513
1514        } catch (error) {
1515            console.error('MetaSlider Lightbox: Error initializing classic gallery:', error);
1516        }
1517    }
1518
1519    /**
1520     * Initialize a single WordPress "Enlarge on Click" image with overlay approach
1521     */
1522    function initWordPressEnlargeOnClick($img) {
1523        var $figure = $img.closest('.wp-lightbox-container');
1524
1525        var src = $img.attr('src');
1526        if (!src) return;
1527
1528        var fullUrl = removeWordPressSizeSuffix(src);
1529
1530        var $overlay = $('<a href="#" class="ml-image-overlay"></a>');
1531
1532        $overlay.attr({
1533            'data-src': fullUrl,
1534            'data-thumb': src
1535        });
1536
1537        var caption = extractCaption($img);
1538        if (caption) {
1539            $overlay.attr('data-sub-html', caption);
1540        }
1541
1542        // Add accessibility attributes
1543        var altText = $img.attr('alt') || '';
1544        addOverlayAccessibility($overlay, 'image', altText);
1545
1546        var $wpButton = $figure.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]');
1547        $wpButton.remove();
1548
1549        $figure.css('position', 'relative').prepend($overlay);
1550
1551        var settings = getLightboxSettings(false);
1552        settings.selector = '.ml-image-overlay';
1553
1554        try {
1555            var instance = lightGallery($figure[0], settings);
1556            $figure.addClass('lg-initialized');
1557
1558        } catch (error) {
1559            console.error('MetaSlider Lightbox: Error initializing Enlarge on Click overlay:', error);
1560        }
1561    }
1562
1563    /**
1564     * Initialize a single WordPress "Enlarge on Click" image with button approach
1565     */
1566    function initWordPressEnlargeOnClickWithButton($img) {
1567        var $figure = $img.closest('.wp-lightbox-container');
1568
1569        var src = $img.attr('src');
1570        if (!src) return;
1571
1572        var fullUrl = removeWordPressSizeSuffix(src);
1573
1574        var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-enlarge-single" href="#">' + getButtonText() + '</a>');
1575
1576        $button.attr({
1577            'data-src': fullUrl,
1578            'data-thumb': src
1579        });
1580
1581        var caption = extractCaption($img);
1582        if (caption) {
1583            $button.attr('data-sub-html', caption);
1584        }
1585
1586        var $wpButton = $figure.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]');
1587        $wpButton.remove();
1588
1589        $figure.css('position', 'relative').append($button);
1590
1591        var settings = getLightboxSettings(false);
1592        settings.selector = '.ml-lightbox-button';
1593
1594        try {
1595            var instance = lightGallery($figure[0], settings);
1596            $figure.addClass('lg-initialized');
1597
1598        } catch (error) {
1599            console.error('MetaSlider Lightbox: Error initializing Enlarge on Click button:', error);
1600        }
1601    }
1602
1603    /**
1604     * Initialize a "Link to Media File" gallery
1605     */
1606    function initLinkToMediaGallery($gallery, useButtonMode) {
1607        var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() {
1608            return $(this).find('img').length > 0;
1609        });
1610
1611        if ($mediaLinks.length <= 1) return;
1612
1613        if (useButtonMode) {
1614            $mediaLinks.each(function() {
1615                var $link = $(this);
1616                var href = $link.attr('href');
1617                var $img = $link.find('img').first();
1618                var imgSrc = $img.attr('src');
1619
1620                if (!href || !imgSrc) return;
1621
1622                var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single" href="#">' + getButtonText() + '</a>');
1623                $button.attr({
1624                    'data-src': href,
1625                    'data-thumb': imgSrc
1626                });
1627
1628                var caption = extractCaption($img);
1629                if (caption) {
1630                    $button.attr('data-sub-html', caption);
1631                }
1632
1633                $link.parent().append($button);
1634            });
1635
1636            var gallerySettings = getLightboxSettings(true);
1637            gallerySettings.selector = '.ml-lightbox-button';
1638
1639        } else {
1640            $mediaLinks.each(function() {
1641                var $link = $(this);
1642                var href = $link.attr('href');
1643                var $img = $link.find('img').first();
1644                var imgSrc = $img.attr('src');
1645
1646                if (!href || !imgSrc) return;
1647
1648                $link.attr({
1649                    'data-src': href,
1650                    'data-thumb': imgSrc
1651                });
1652
1653                var caption = extractCaption($img);
1654                if (caption) {
1655                    $link.attr('data-sub-html', caption);
1656                }
1657            });
1658
1659            var gallerySettings = getLightboxSettings(true);
1660            gallerySettings.selector = 'a[data-src]';
1661        }
1662
1663        try {
1664            var instance = lightGallery($gallery[0], gallerySettings);
1665            $gallery.addClass('lg-initialized');
1666
1667        } catch (error) {
1668            console.error('MetaSlider Lightbox: Error initializing "Link to Media File" gallery:', error);
1669        }
1670    }
1671
1672    /**
1673     * Initialize a plain image gallery (no links, no WordPress lightbox)
1674     */
1675    function initPlainImageGallery($gallery, useButtonMode) {
1676        var $images = $gallery.find('img');
1677
1678        if ($images.length <= 1) return;
1679
1680        if (useButtonMode) {
1681            $images.each(function() {
1682                var $img = $(this);
1683                var imgSrc = $img.attr('src');
1684
1685                if (!imgSrc) return;
1686
1687                var fullUrl = removeWordPressSizeSuffix(imgSrc);
1688
1689                var $figure = $img.closest('.wp-block-image');
1690                if ($figure.length === 0) {
1691                    $figure = $img.parent();
1692                }
1693
1694                $figure.attr({
1695                    'data-src': fullUrl,
1696                    'data-thumb': imgSrc
1697                });
1698
1699                var caption = extractCaption($img);
1700                if (caption) {
1701                    $figure.attr('data-sub-html', caption);
1702                }
1703
1704                var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-plain-gallery" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>');
1705                if (caption) {
1706                    $button.attr('data-sub-html', caption);
1707                }
1708
1709                $figure.css('position', 'relative').append($button);
1710            });
1711
1712            var gallerySettings = getLightboxSettings(true);
1713            gallerySettings.selector = '.ml-lightbox-button';
1714
1715        } else {
1716            $images.each(function() {
1717                var $img = $(this);
1718                var imgSrc = $img.attr('src');
1719
1720                if (!imgSrc) return;
1721
1722                var fullUrl = removeWordPressSizeSuffix(imgSrc);
1723
1724                var $wrapper = $('<a href="#" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '"></a>');
1725
1726                var caption = extractCaption($img);
1727                if (caption) {
1728                    $wrapper.attr('data-sub-html', caption);
1729                }
1730
1731                $img.wrap($wrapper);
1732            });
1733
1734            var gallerySettings = getLightboxSettings(true);
1735            gallerySettings.selector = 'a[data-src]';
1736        }
1737
1738        try {
1739            var instance = lightGallery($gallery[0], gallerySettings);
1740            $gallery.addClass('lg-initialized');
1741
1742        } catch (error) {
1743            console.error('MetaSlider Lightbox: Error initializing plain image gallery:', error);
1744        }
1745    }
1746
1747    /**
1748     * Initialize a single "Link to Media File" image
1749     */
1750    function initLinkToMediaFile($link) {
1751        var href = $link.attr('href');
1752        var $img = $link.find('img').first();
1753        var imgSrc = $img.attr('src');
1754
1755        if (!href || !imgSrc) return;
1756
1757        // Get alt text for accessible name
1758        var altText = $img.attr('alt') || '';
1759        var ariaLabel = 'View image' + (altText ? ': ' + altText : '');
1760
1761        $link.attr({
1762            'data-src': href,
1763            'data-thumb': imgSrc,
1764            'aria-label': ariaLabel
1765        });
1766
1767        var caption = extractCaption($img);
1768        if (caption) {
1769            $link.attr('data-sub-html', caption);
1770        }
1771
1772        var settings = getLightboxSettings(false);
1773        settings.selector = 'this';
1774
1775        try {
1776            var instance = lightGallery($link[0], settings);
1777            $link.addClass('lg-initialized');
1778
1779        } catch (error) {
1780            console.error('MetaSlider Lightbox: Error initializing "Link to Media File" image:', error);
1781        }
1782    }
1783
1784    /**
1785     * Initialize a single "Link to Media File" image with button approach
1786     */
1787    function initLinkToMediaFileWithButton($link) {
1788        var href = $link.attr('href');
1789        var $img = $link.find('img').first();
1790        var imgSrc = $img.attr('src');
1791
1792        if (!href || !imgSrc) return;
1793
1794        // Get alt text for accessible name
1795        var altText = $img.attr('alt') || '';
1796        var ariaLabel = 'View image' + (altText ? ': ' + altText : '');
1797
1798        var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single-img" href="#">
1798' + getButtonText() + '</a>');
1799
1800        $button.attr({
1801            'data-src': href,
1802            'data-thumb': imgSrc,
1803            'aria-label': ariaLabel
1804        });
1805
1806        var caption = extractCaption($img);
1807        if (caption) {
1808            $button.attr('data-sub-html', caption);
1809        }
1810
1811        // Add aria-label to link as well
1812        $link.attr('aria-label', ariaLabel);
1813
1814        $link.css('position', 'relative').append($button);
1815
1816        var settings = getLightboxSettings(false);
1817        settings.selector = '.ml-lightbox-button';
1818
1819        try {
1820            var instance = lightGallery($link[0], settings);
1821            $link.addClass('lg-initialized');
1822
1823        } catch (error) {
1824            console.error('MetaSlider Lightbox: Error initializing "Link to Media File" button:', error);
1825        }
1826    }
1827
1828    /**
1829     * Initialize a featured image lightbox where the outer <a> links to the post
1830     * and data-src holds the image URL for the lightbox
1831     */
1832    function initFeaturedImageWithButton($link) {
1833        var dataSrc = $link.attr('data-src');
1834        var $img = $link.find('img').first();
1835        var imgSrc = $img.attr('src');
1836
1837        if (!dataSrc || !imgSrc) return;
1838
1839        var altText = $img.attr('alt') || '';
1840        var ariaLabel = 'View image' + (altText ? ': ' + altText : '');
1841
1842        var $button = $('<a class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media-single-img" href="#">' + getButtonText() + '</a>');
1843        $button.attr({
1844            'data-src': dataSrc,
1845            'data-thumb': imgSrc,
1846            'aria-label': ariaLabel
1847        });
1848
1849        $link.attr('aria-label', ariaLabel).css('position', 'relative').append($button);
1850
1851        var settings = getLightboxSettings(false);
1852        settings.selector = '.ml-lightbox-button';
1853
1854        try {
1855            lightGallery($link[0], settings);
1856            $link.addClass('lg-initialized');
1857        } catch (error) {
1858            console.error('MetaSlider Lightbox: Error initializing featured image lightbox:', error);
1859        }
1860    }
1861
1862    /**
1863     * Initialize a featured image lightbox without a button — clicking the link opens the lightbox directly
1864     */
1865    function initFeaturedImage($link) {
1866        var dataSrc = $link.attr('data-src');
1867        var $img = $link.find('img').first();
1868        var imgSrc = $img.attr('src');
1869
1870        if (!dataSrc || !imgSrc) return;
1871
1872        var altText = $img.attr('alt') || '';
1873        $link.attr('aria-label', 'View image' + (altText ? ': ' + altText : ''));
1874
1875        var settings = getLightboxSettings(false);
1876        settings.selector = 'this';
1877
1878        try {
1879            lightGallery($link[0], settings);
1880            $link.addClass('lg-initialized');
1881        } catch (error) {
1882            console.error('MetaSlider Lightbox: Error initializing featured image lightbox:', error);
1883        }
1884    }
1885
1886    /**
1887     * Initialize MetaSlider lightbox functionality
1888     * Based on settings from ml-lightgallery-init.js but simplified for integration
1889     */
1890    function initMetaSlider() {
1891        var $sliders = $('.metaslider, [class*="ml-slider-lightbox-"]');
1892
1893        if ($sliders.length === 0) {
1894            return;
1895        }
1896
1897
1898        $sliders.each(function() {
1899            var $slider = $(this);
1900
1901            if ($slider.hasClass('lg-initialized')) {
1902                return;
1903            }
1904
1905            var sliderId = $slider.attr('id');
1906
1907            if (!sliderId) {
1908                var $firstSlide = $slider.find('li').first();
1909                var slideClass = $firstSlide.attr('class');
1910                if (slideClass) {
1911                    var classMatch = slideClass.match(/slider-(\d+)/);
1912                    if (classMatch) {
1913                        sliderId = classMatch[1];
1914                    }
1915                }
1916
1917                if (!sliderId) {
1918                    var $firstImg = $firstSlide.find('img').first();
1919                    var imgClass = $firstImg.attr('class');
1920                    if (imgClass) {
1921                        var imgClassMatch = imgClass.match(/slider-(\d+)/);
1922                        if (imgClassMatch) {
1923                            sliderId = imgClassMatch[1];
1924                        }
1925                    }
1926                }
1927            }
1928
1929            if (sliderId) {
1930                var matches = sliderId.match(/(\d+)/);
1931                if (matches) {
1932                    sliderId = matches[1];
1933                }
1934            }
1935
1936
1937            if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.slider_settings && sliderId) {
1938                var sliderSettings = mlLightboxSettings.slider_settings[sliderId];
1939                if (sliderSettings && sliderSettings.lightbox_enabled === false) {
1940                    return;
1941                }
1942            }
1943
1944            var showButton = resolveSliderButton(sliderId);
1945
1946            if (showButton) {
1947                initMetaSliderButtonMode($slider, sliderId);
1948            } else {
1949                initMetaSliderDirectMode($slider, sliderId);
1950            }
1951        });
1952    }
1953
1954    /**
1955     * Read a per-slider boolean override (true/false) from slider_settings.
1956     * PHP sends true/false/null where null means "use global default".
1957     * Returns the per-slider value when set, otherwise calls globalFn().
1958     */
1959    function resolveSliderSetting(sliderId, key, globalFn) {
1960        var perSlider = mlLightboxSettings.slider_settings &&
1961                        sliderId &&
1962                        mlLightboxSettings.slider_settings[sliderId];
1963        var val = perSlider ? perSlider[key] : null;
1964        if (val === true)  { return true; }
1965        if (val === false) { return false; }
1966        return globalFn();
1967    }
1968
1969    function resolveSliderCaptions(sliderId) {
1970        return resolveSliderSetting(sliderId, 'lightbox_captions', shouldShowCaptions);
1971    }
1972
1973    function resolveSliderNavigation(sliderId) {
1974        return resolveSliderSetting(sliderId, 'lightbox_navigation', function() {
1975            return (mlLightboxSettings.metaslider_options || {}).show_arrows !== false;
1976        });
1977    }
1978
1979    function resolveSliderButton(sliderId) {
1980        return resolveSliderSetting(sliderId, 'lightbox_button', function() {
1981            return (mlLightboxSettings.metaslider_options || {}).show_lightbox_button !== false;
1982        });
1983    }
1984
1985    function resolveSliderIcon(sliderId) {
1986        return resolveSliderSetting(sliderId, 'lightbox_icon', function() {
1987            return !!(mlLightboxSettings.metaslider_options || {}).use_icon_instead_of_button;
1988        });
1989    }
1990
1991    /**
1992     * Initialize MetaSlider in button mode
1993     */
1994    function initMetaSliderButtonMode($slider, sliderId) {
1995        _metasliderButtonSliderId = sliderId;
1996        try {
1997            var $slides = $slider.find('li').not('.clone').not('.flex-control-nav li, .flex-control-thumbs li, .filmstrip li, li.ms-thumb');
1998            var buttonsAdded = 0;
1999
2000            $slides.each(function() {
2001                var $slide = $(this);
2002                var $button = null;
2003
2004                if ($slide.find('.ml-lightbox-button').length > 0) {
2005                    return;
2006                }
2007
2008                if ($slide.hasClass('ms-youtube')) {
2009                    $button = createYouTubeButton($slide);
2010                } else if ($slide.hasClass('ms-vimeo')) {
2011                    $button = createVimeoButton($slide);
2012                } else if ($slide.hasClass('ms-external-video')) {
2013                    $button = createExternalVideoButton($slide);
2014                } else if ($slide.hasClass('ms-local-video')) {
2015                    $button = createLocalVideoButton($slide);
2016                } else if ($slide.hasClass('ms-postfeed')) {
2017                    $button = createPostFeedButton($slide);
2018                } else if ($slide.hasClass('ms-layer')) {
2019                    $button = createLayerButton($slide);
2020                } else if ($slide.hasClass('ms-custom-html')) {
2021                    $button = createCustomHtmlButton($slide);
2022                } else {
2023                    $button = createImageButton($slide);
2024                }
2025
2026                if ($button) {
2027                    $slide.css('position', 'relative').append($button);
2028                    buttonsAdded++;
2029                }
2030            });
2031
2032            if (buttonsAdded > 0) {
2033                var settings = getLightboxSettings(buttonsAdded > 1);
2034                settings.selector = '.ml-lightbox-button';
2035
2036                if (!resolveSliderCaptions(sliderId)) {
2037                    settings.subHtml = '';
2038                    settings.getCaptionFromTitleOrAlt = false;
2039                    $slider.find('.ml-lightbox-button').removeAttr('data-sub-html');
2040                }
2041
2042                settings.controls = resolveSliderNavigation(sliderId);
2043
2044                try {
2045                    lightGallery($slider[0], settings);
2046                    $slider.addClass('lg-initialized');
2047                } catch (error) {
2048                    console.error('MetaSlider Lightbox: Error initializing button mode:', error);
2049                }
2050            }
2051        } finally {
2052            _metasliderButtonSliderId = null;
2053        }
2054    }
2055
2056    /**
2057     * Initialize MetaSlider in direct-click mode
2058     */
2059    function initMetaSliderDirectMode($slider, sliderId) {
2060
2061        var $slides = $slider.find('li').not('.clone').not('.flex-control-nav li, .flex-control-thumbs li, .filmstrip li, li.ms-thumb');
2062        var linksAdded = 0;
2063
2064        $slides.each(function() {
2065            var $slide = $(this);
2066
2067            if ($slide.find('.ml-metaslider-link, .ml-video-overlay').length > 0) return;
2068
2069            var dataAttributes = null;
2070
2071            if ($slide.hasClass('ms-youtube')) {
2072                dataAttributes = getYouTubeOverlayData($slide);
2073            }
2074            else if ($slide.hasClass('ms-vimeo')) {
2075                dataAttributes = getVimeoOverlayData($slide);
2076            }
2077            else if ($slide.hasClass('ms-external-video')) {
2078                dataAttributes = getExternalVideoOverlayData($slide);
2079            }
2080            else if ($slide.hasClass('ms-local-video')) {
2081                dataAttributes = getLocalVideoOverlayData($slide);
2082            }
2083            else if ($slide.hasClass('ms-postfeed')) {
2084                dataAttributes = getPostFeedOverlayData($slide);
2085            }
2086            else if ($slide.hasClass('ms-layer')) {
2087                dataAttributes = getLayerOverlayData($slide);
2088            }
2089            else if ($slide.hasClass('ms-custom-html')) {
2090                dataAttributes = getCustomHtmlOverlayData($slide);
2091            }
2092            else {
2093                dataAttributes = getImageOverlayData($slide);
2094            }
2095
2096            if (dataAttributes) {
2097                if (dataAttributes.overlay) {
2098                    var overlayClass = 'ml-video-overlay';
2099                    var contentType = 'video';
2100                    if ($slide.hasClass('ms-image')) {
2101                        overlayClass = 'ml-image-overlay';
2102                        contentType = 'image';
2103                    }
2104                    var $overlay = $('<a href="#" class="' + overlayClass + '"></a>');
2105                    $overlay.attr(dataAttributes.attrs);
2106
2107                    // Add accessibility attributes
2108                    var $img = $slide.find('img').first();
2109                    var altText = $img.length > 0 ? ($img.attr('alt') || '') : '';
2110                    addOverlayAccessibility($overlay, contentType, altText);
2111
2112                    $slide.css('position', 'relative').prepend($overlay);
2113                } else {
2114                    var $link = $slide.find('a').first();
2115                    if ($link.length > 0) {
2116                        $link.attr(dataAttributes.attrs).addClass('ml-metaslider-link');
2117                    }
2118                }
2119                linksAdded++;
2120            }
2121        });
2122
2123        if (linksAdded > 0) {
2124            var settings = getLightboxSettings(linksAdded > 1);
2125            settings.selector = '.ml-metaslider-link, .ml-video-overlay, .ml-image-overlay';
2126
2127            if (!resolveSliderCaptions(sliderId)) {
2128                settings.subHtml = '';
2129                settings.getCaptionFromTitleOrAlt = false;
2130                $slider.find('.ml-metaslider-link, .ml-video-overlay, .ml-image-overlay').removeAttr('data-sub-html');
2131            }
2132
2133            settings.controls = resolveSliderNavigation(sliderId);
2134
2135            try {
2136                lightGallery($slider[0], settings);
2137                $slider.addClass('lg-initialized');
2138            } catch (error) {
2139                console.error('MetaSlider Lightbox: Error initializing direct-click mode:', error);
2140            }
2141        }
2142    }
2143
2144
2145    /**
2146     * Check if gallery is already processed
2147     */
2148    function isGalleryAlreadyProcessed($gallery) {
2149        return $gallery.hasClass(CONSTANTS.CLASSES.ML_LIGHTBOX_ENABLED) ||
2150               $gallery.hasClass(CONSTANTS.CLASSES.LG_INITIALIZED);
2151    }
2152
2153    /**
2154     * Extract video ID using various fallback methods
2155     */
2156    function extractVideoId($slide, handler) {
2157        var $element = $slide.find(handler.selector);
2158        if ($element.length === 0) return null;
2159
2160        var id = $element.attr(handler.idAttr);
2161
2162        if (!id && handler.fallbackSelectors) {
2163            for (var i = 0; i < handler.fallbackSelectors.length; i++) {
2164                var fallbackSelector = handler.fallbackSelectors[i];
2165                var $fallback = $slide.find(fallbackSelector);
2166
2167                if (fallbackSelector.startsWith('a[href*=')) {
2168                    var url = $fallback.attr('href');
2169                    if (url) {
2170                        var match = url.match(/vimeo\.com\/(\d+)/);
2171                        if (match) {
2172                            id = match[1];
2173                            break;
2174                        }
2175                    }
2176                } else {
2177                    id = $fallback.attr(handler.idAttr);
2178                    if (id) break;
2179                }
2180            }
2181        }
2182
2183        return id;
2184    }
2185
2186    /**
2187     * Unified video button creation function
2188     */
2189    function createVideoButton($slide, videoType) {
2190        var handler = CONSTANTS.VIDEO_HANDLERS[videoType];
2191        if (!handler) return null;
2192
2193        var videoData = null;
2194
2195        if (videoType === 'youtube' || videoType === 'vimeo') {
2196            var videoId = extractVideoId($slide, handler);
2197            if (!videoId) return null;
2198
2199            videoData = {
2200                url: handler.urlTemplate(videoId),
2201                thumb: handler.thumbTemplate(videoId)
2202            };
2203        } else if (videoType === 'externalVideo' || videoType === 'localVideo') {
2204            var $element = $slide.find(handler.selector);
2205            if ($element.length === 0) return null;
2206
2207            var dataSources = $element.attr(handler.sourcesAttr);
2208            var videoSources = [];
2209            var videoUrl = null;
2210
2211            if (dataSources) {
2212                try {
2213                    var sources = JSON.parse(dataSources);
2214                    if (sources && sources.length > 0) {
2215                        videoUrl = sources[0].src;
2216                        videoSources = sources.map(function (source) {
2217                            return {
2218                                'src': source.src,
2219                                'type': source.type || getVideoType(source.src)
2220                            };
2221                        });
2222                    }
2223                } catch (e) {
2224                    console.error('MetaSlider Lightbox: Error parsing video sources', e);
2225                    return null;
2226                }
2227            }
2228
2229            if (!videoUrl || videoSources.length === 0) return null;
2230
2231            videoData = {
2232                url: '',
2233                thumb: $element.attr(handler.posterAttr) || getPlaceholderThumb(),
2234                videoData: {
2235                    'source': videoSources,
2236                    'attributes': {
2237                        'preload': false,
2238                        'controls': true
2239                    }
2240                }
2241            };
2242        }
2243
2244        if (!videoData) return null;
2245
2246        var diagnosticClass = 'ml-button-metaslider ml-button-metaslider-' + videoType.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
2247        var buttonText = getButtonText();
2248        var $button = $('<a class="' + CONSTANTS.CLASSES.ML_LIGHTBOX_BUTTON + ' ' + diagnosticClass + '" href="#">
2248' + buttonText + '</a>');
2249
2250        var baseAttrs = {
2251            'role': 'button',
2252            'aria-label': buttonText + ' - ' + videoType + ' video',
2253            'tabindex': '0'
2254        };
2255
2256        if (videoData.videoData) {
2257            $button.attr($.extend({}, baseAttrs, {
2258                'data-src': '',
2259                'data-video': JSON.stringify(videoData.videoData),
2260                'data-thumb': videoData.thumb
2261            }));
2262        } else {
2263            if (!videoData.url) return null;
2264            $button.attr($.extend({}, baseAttrs, {
2265                'data-src': videoData.url,
2266                'data-thumb': videoData.thumb
2267            }));
2268        }
2269
2270        return $button;
2271    }
2272
2273    /**
2274     * Create lightbox button for regular image slides
2275     */
2276    function createImageButton($slide) {
2277        var $link = $slide.find('a').first();
2278        var $img = $slide.find('img').first();
2279
2280        if ($img.length === 0) return null;
2281
2282        var imgSrc = $img.attr('src');
2283        var linkHref = ($link.length > 0) ? $link.attr('href') : null;
2284        if (!linkHref) {
2285            linkHref = imgSrc;
2286        }
2287
2288        var fullSizeUrl = removeWordPressSizeSuffix(linkHref);
2289
2290        var altText = $img.attr('alt') || 'image';
2291        var buttonText = getButtonText();
2292
2293        var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-image" href="#">' + buttonText + '</a>');
2294
2295        $button.attr({
2296            'data-src': fullSizeUrl,
2297            'data-thumb': imgSrc,
2298            'role': 'button',
2299            'aria-label': buttonText + ' - ' + altText,
2300            'tabindex': '0'
2301        });
2302
2303        var caption = extractCaption($slide);
2304        if (caption) {
2305            $button.attr('data-sub-html', caption);
2306        }
2307
2308        return $button;
2309    }
2310
2311    /**
2312     * Create lightbox button for YouTube slides
2313     */
2314    function createYouTubeButton($slide) {
2315        return createVideoButton($slide, 'youtube');
2316    }
2317
2318    /**
2319     * Create lightbox button for Vimeo slides
2320     */
2321    function createVimeoButton($slide) {
2322        return createVideoButton($slide, 'vimeo');
2323    }
2324
2325    /**
2326     * Create lightbox button for external video slides
2327     */
2328    function createExternalVideoButton($slide) {
2329        return createVideoButton($slide, 'externalVideo');
2330    }
2331
2332    /**
2333     * Create lightbox button for local video slides
2334     */
2335    function createLocalVideoButton($slide) {
2336        return createVideoButton($slide, 'localVideo');
2337    }
2338
2339    /**
2340     * Helper function to get video type from URL
2341     */
2342    function getVideoType(url) {
2343        if (url.includes('.mp4')) return 'video/mp4';
2344        if (url.includes('.webm')) return 'video/webm';
2345        if (url.includes('.ogg')) return 'video/ogg';
2346        return 'video/mp4';
2347    }
2348
2349    /**
2350     * Helper function to get placeholder thumbnail
2351     */
2352    function getPlaceholderThumb() {
2353        return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPjQwMHgzMDA8L3RleHQ+PC9zdmc+';
2354    }
2355
2356    /**
2357     * Create lightbox button for PostFeed slides
2358     */
2359    function createPostFeedButton($slide) {
2360        var $img = $slide.find('img').first();
2361        if ($img.length === 0) return null;
2362
2363        var imgSrc = removeWordPressSizeSuffix($img.attr('src'));
2364        var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-postfeed" href="#">' + getButtonText() + '</a>');
2365
2366        $button.attr({
2367            'data-src': imgSrc,
2368            'data-thumb': imgSrc
2369        });
2370
2371        var caption = extractCaption($slide);
2372        if (caption) {
2373            $button.attr('data-sub-html', caption);
2374        }
2375
2376        return $button;
2377    }
2378
2379    /**
2380     * Get overlay data for PostFeed slides in direct-click mode
2381     */
2382    function getPostFeedOverlayData($slide) {
2383        var $img = $slide.find('img').first();
2384        if ($img.length === 0) return null;
2385
2386        var imgSrc = removeWordPressSizeSuffix($img.attr('src'));
2387
2388        var attrs = {
2389            'data-src': imgSrc,
2390            'data-thumb': imgSrc
2391        };
2392
2393        var caption = extractCaption($slide);
2394        if (caption) {
2395            attrs['data-sub-html'] = caption;
2396        }
2397
2398        return {
2399            overlay: false,
2400            attrs: attrs
2401        };
2402    }
2403
2404    /**
2405     * Create lightbox button for Layer slides using iframe data URI approach
2406     */
2407    function createLayerButton($slide) {
2408        var slideContent = $slide.html();
2409        if (!slideContent || !slideContent.trim()) return null;
2410
2411        var scaledSlideContent = slideContent.replace(/<div class="layer"[^>]*style="([^"]*)"[^>]*>/g, function(match, styleAttr) {
2412            var scaledStyle = styleAttr.replace(/(top|left|right|bottom):\s*(\d+(?:\.\d+)?)(px|%)/g, function(cssMatch, property, value, unit) {
2413                var numValue = parseFloat(value);
2414                var scaledValue = Math.round(numValue * 2);
2415                return property + ': ' + scaledValue + unit;
2416            });
2417            return match.replace(styleAttr, scaledStyle);
2418        });
2419
2420        var $tempDiv = $('<div>').html(slideContent);
2421        var animationClasses = [];
2422
2423        $tempDiv.find('[class*="animated"], [class*="animation_"], [data-animation]').each(function() {
2424            var $elem = $(this);
2425            var classes = $elem.attr('class') || '';
2426            var animationType = $elem.attr('data-animation') || '';
2427
2428            if (classes) {
2429                animationClasses = animationClasses.concat(classes.split(' ').filter(function(cls) {
2430                    return cls.match(/animated|fadeIn|pulse|bounce|slide|animation_/i);
2431                }));
2432            }
2433
2434            if (animationType) {
2435                animationClasses.push(animationType);
2436            }
2437        });
2438
2439        var uniqueAnimationClasses = animationClasses.filter(function(value, index, self) {
2440            return self.indexOf(value) === index;
2441        }).join(' ');
2442
2443        var scaleCSS = `
2444            .layer-slide-content {
2445                position: relative !important;
2446                display: inline-block !important;
2447            }
2448            .layer-slide-content .msDefaultImage {
2449                display: block !important;
2450                width: 1400px !important;
2451                height: 600px !important;
2452                max-width: 80% !important;
2453                margin: 0 auto !important;
2454                object-fit: cover !important;
2455            }
2456            .layer-slide-content .msHtmlOverlay {
2457                position: absolute !important;
2458                top: 0 !important;
2459                left: 50% !important;
2460                width: 80% !important;
2461                height: 100% !important;
2462                transform: translateX(-50%) !important;
2463            }
2464            .layer-slide-content .msHtmlOverlay .content {
2465                font-size: 1.8em !important;
2466                line-height: 1.2 !important;
2467            }
2468        `;
2469
2470        var htmlContent = '<div style="display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; box-sizing: border-box; background: #000;"><div class="layer-slide-content ' + uniqueAnimationClasses + '" style="position: relative; display: inline-block; max-width: 100%; max-height: 100vh;">' + scaledSlideContent + '</div></div>';
2471        var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layer Content</title><style>body{margin:0;padding:0;background:#000;} ' + scaleCSS + '</style><link rel="stylesheet" href="' + window.location.origin + '/wp-content/plugins/metaslider/assets/animate/animate.css" type="text/css" /></head><body>' + htmlContent + '</body></html>');
2472
2473        var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-layer" href="#">' + getButtonText() + '</a>');
2474
2475        $button.attr({
2476            'data-src': dataUri,
2477            'data-thumb': getPlaceholderThumb(),
2478            'data-iframe': 'true',
2479            'data-width': '800',
2480            'data-height': '600'
2481        });
2482
2483        return $button;
2484    }
2485
2486    /**
2487     * Get overlay data for Layer slides in direct-click mode using iframe data URI approach
2488     */
2489    function getLayerOverlayData($slide) {
2490        var slideContent = $slide.html();
2491        if (!slideContent || !slideContent.trim()) return null;
2492
2493        var scaledSlideContent = slideContent.replace(/<div class="layer"[^>]*style="([^"]*)"[^>]*>/g, function(match, styleAttr) {
2494            var scaledStyle = styleAttr.replace(/(top|left|right|bottom):\s*(\d+(?:\.\d+)?)(px|%)/g, function(cssMatch, property, value, unit) {
2495                var numValue = parseFloat(value);
2496                var scaledValue = Math.round(numValue * 2);
2497                return property + ': ' + scaledValue + unit;
2498            });
2499            return match.replace(styleAttr, scaledStyle);
2500        });
2501
2502        var $tempDiv = $('<div>').html(slideContent);
2503        var animationClasses = [];
2504
2505        $tempDiv.find('[class*="animated"], [class*="animation_"], [data-animation]').each(function() {
2506            var $elem = $(this);
2507            var classes = $elem.attr('class') || '';
2508            var animationType = $elem.attr('data-animation') || '';
2509
2510            if (classes) {
2511                animationClasses = animationClasses.concat(classes.split(' ').filter(function(cls) {
2512                    return cls.match(/animated|fadeIn|pulse|bounce|slide|animation_/i);
2513                }));
2514            }
2515
2516            if (animationType) {
2517                animationClasses.push(animationType);
2518            }
2519        });
2520
2521        var uniqueAnimationClasses = animationClasses.filter(function(value, index, self) {
2522            return self.indexOf(value) === index;
2523        }).join(' ');
2524
2525        var scaleCSS = `
2526            .layer-slide-content {
2527                position: relative !important;
2528                display: inline-block !important;
2529            }
2530            .layer-slide-content .msDefaultImage {
2531                display: block !important;
2532                width: 1400px !important;
2533                height: 600px !important;
2534                max-width: 80% !important;
2535                margin: 0 auto !important;
2536                object-fit: cover !important;
2537            }
2538            .layer-slide-content .msHtmlOverlay {
2539                position: absolute !important;
2540                top: 0 !important;
2541                left: 50% !important;
2542                width: 80% !important;
2543                height: 100% !important;
2544                transform: translateX(-50%) !important;
2545            }
2546            .layer-slide-content .msHtmlOverlay .content {
2547                font-size: 1.8em !important;
2548                line-height: 1.2 !important;
2549            }
2550        `;
2551
2552        var htmlContent = '<div style="display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; box-sizing: border-box; background: #000;"><div class="layer-slide-content ' + uniqueAnimationClasses + '" style="position: relative; display: inline-block; max-width: 100%; max-height: 100vh;">' + scaledSlideContent + '</div></div>';
2553        var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layer Content</title><style>body{margin:0;padding:0;background:#000;} ' + scaleCSS + '</style><link rel="stylesheet" href="' + window.location.origin + '/wp-content/plugins/metaslider/assets/animate/animate.css" type="text/css" /></head><body>' + htmlContent + '</body></html>');
2554
2555        var attrs = {
2556            'data-src': dataUri,
2557            'data-thumb': getPlaceholderThumb(),
2558            'data-iframe': 'true',
2559            'data-width': '800',
2560            'data-height': '600'
2561        };
2562
2563        var caption = extractCaption($slide);
2564        if (caption) {
2565            attrs['data-sub-html'] = caption;
2566        }
2567
2568        return {
2569            overlay: true,
2570            attrs: attrs
2571        };
2572    }
2573
2574    /**
2575     * HTML escape function for safe caption display
2576     */
2577
2578    /**
2579     * Check if captions should be shown based on settings
2580     */
2581    function shouldShowCaptions() {
2582        if (typeof mlLightboxSettings !== 'undefined' &&
2583            mlLightboxSettings.metaslider_options &&
2584            typeof mlLightboxSettings.metaslider_options.show_captions !== 'undefined') {
2585            return mlLightboxSettings.metaslider_options.show_captions === true || mlLightboxSettings.metaslider_options.show_captions === 1;
2586        }
2587        return true;
2588    }
2589
2590    /**
2591     * Extract caption from various sources for an image or container
2592     */
2593    function extractCaption($element) {
2594        // PHP sets data-sub-html on the slide's <a> for the built-in lightbox.
2595        // Read this before the shouldShowCaptions() guard so it is available when
2596        // global captions are off but a per-slider override enables them.
2597        // resolveSliderCaptions() is the final authority on removal.
2598        var $childLink = $element.find('a').first();
2599        var childCaption = $childLink.length ? ($childLink.attr('data-sub-html') || '').trim() : '';
2600
2601        if (!shouldShowCaptions()) {
2602            return childCaption;
2603        }
2604
2605        var caption = '';
2606        var $img = $element.is('img') ? $element : $element.find('img').first();
2607
2608        var $figcaption = $element.closest('figure').find('figcaption');
2609        if ($figcaption.length > 0) {
2610            caption = $figcaption.html();
2611        }
2612
2613        if (!caption) {
2614            var $captionWrap = $element.find('.caption-wrap .caption');
2615            if ($captionWrap.length === 0) {
2616                $captionWrap = $element.closest('li').find('.caption-wrap .caption');
2617            }
2618            if ($captionWrap.length === 0) {
2619                $captionWrap = $element.closest('.slide').find('.caption-wrap .caption');
2620            }
2621            if ($captionWrap.length > 0) {
2622                caption = $captionWrap.html();
2623            }
2624        }
2625
2626        if (!caption && $img.length > 0) {
2627            caption = escapeHtml($img.attr('alt') || $img.attr('title') || '');
2628        }
2629
2630        if (!caption) {
2631            caption = $element.attr('data-sub-html') || '';
2632        }
2633
2634        if (!caption) {
2635            caption = childCaption;
2636        }
2637
2638        return caption.trim();
2639    }
2640
2641
2642    /**
2643     * Get overlay data for regular images in direct-click mode
2644     */
2645    function getImageOverlayData($slide) {
2646        var $link = $slide.find('a').first();
2647        var $img = $slide.find('img').first();
2648
2649        if ($img.length === 0) return null;
2650
2651        var imgSrc = $img.attr('src');
2652        var linkHref = ($link.length > 0) ? $link.attr('href') : null;
2653        if (!linkHref) {
2654            linkHref = imgSrc;
2655        }
2656
2657        var fullSizeUrl = removeWordPressSizeSuffix(linkHref);
2658        var needsOverlay = ($link.length === 0);
2659
2660        var attrs = {
2661            'data-src': fullSizeUrl,
2662            'data-thumb': imgSrc
2663        };
2664
2665        var caption = extractCaption($slide);
2666        if (caption) {
2667            attrs['data-sub-html'] = caption;
2668        }
2669
2670        return {
2671            overlay: needsOverlay,
2672            attrs: attrs
2673        };
2674    }
2675
2676    /**
2677     * Get overlay data for YouTube videos in direct-click mode
2678     */
2679    function getYouTubeOverlayData($slide) {
2680        var $youtubeDiv = $slide.find('div.youtube[data-id]');
2681        if ($youtubeDiv.length === 0) return null;
2682
2683        var youtubeId = $youtubeDiv.attr('data-id');
2684        if (!youtubeId) return null;
2685
2686        var youtubeUrl = 'https://www.youtube.com/watch?v=' + youtubeId;
2687        var thumbUrl = 'https://img.youtube.com/vi/' + youtubeId + '/maxresdefault.jpg';
2688
2689        var attrs = {
2690            'data-src': youtubeUrl,
2691            'data-thumb': thumbUrl
2692        };
2693
2694        var caption = extractCaption($slide);
2695        if (caption) {
2696            attrs['data-sub-html'] = caption;
2697        }
2698
2699        return {
2700            overlay: true,
2701            attrs: attrs
2702        };
2703    }
2704
2705    /**
2706     * Get overlay data for Vimeo videos in direct-click mode
2707     */
2708    function getVimeoOverlayData($slide) {
2709        var $vimeoDiv = $slide.find('div.vimeo[data-id]');
2710        if ($vimeoDiv.length === 0) return null;
2711
2712        var vimeoId = $vimeoDiv.attr('data-id');
2713
2714        if (!vimeoId) {
2715            vimeoId = $vimeoDiv.attr('data-slide-id');
2716        }
2717        if (!vimeoId) {
2718            var $existingLink = $slide.find('a[href*="vimeo.com"]').first();
2719            if ($existingLink.length > 0) {
2720                var vimeoUrl = $existingLink.attr('href');
2721                var vimeoMatch = vimeoUrl.match(/vimeo\.com\/(\d+)/);
2722                if (vimeoMatch) {
2723                    vimeoId = vimeoMatch[1];
2724                }
2725            }
2726        }
2727
2728        if (!vimeoId) return null;
2729
2730        var vimeoUrl = 'https://vimeo.com/' + vimeoId;
2731        var vimeoPoster = 'https://vumbnail.com/' + vimeoId + '.jpg';
2732
2733        var attrs = {
2734            'data-src': vimeoUrl,
2735            'data-thumb': vimeoPoster
2736        };
2737
2738        var caption = extractCaption($slide);
2739        if (caption) {
2740            attrs['data-sub-html'] = caption;
2741        }
2742
2743        return {
2744            overlay: true,
2745            attrs: attrs
2746        };
2747    }
2748
2749    /**
2750     * Get overlay data for external videos in direct-click mode
2751     */
2752    function getExternalVideoOverlayData($slide) {
2753        var $videoDiv = $slide.find('div.external-video');
2754        if ($videoDiv.length === 0) return null;
2755
2756        var dataSources = $videoDiv.attr('data-sources');
2757        var videoSources = [];
2758        var videoUrl = null;
2759
2760        if (dataSources) {
2761            try {
2762                var sources = JSON.parse(dataSources);
2763                if (sources && sources.length > 0) {
2764                    videoUrl = sources[0].src;
2765                    videoSources = sources.map(function (source) {
2766                        return {
2767                            'src': source.src,
2768                            'type': source.type || getVideoType(source.src)
2769                        };
2770                    });
2771                }
2772            } catch (e) {
2773                console.error('MetaSlider Lightbox: Error parsing video sources', e);
2774                return null;
2775            }
2776        }
2777
2778        if (!videoUrl || videoSources.length === 0) return null;
2779
2780        var posterUrl = $videoDiv.attr('data-poster') || getPlaceholderThumb();
2781        var videoData = {
2782            'source': videoSources,
2783            'attributes': {
2784                'preload': false,
2785                'controls': true
2786            }
2787        };
2788
2789        var attrs = {
2790            'data-src': '',
2791            'data-video': JSON.stringify(videoData),
2792            'data-thumb': posterUrl
2793        };
2794
2795        var caption = extractCaption($slide);
2796        if (caption) {
2797            attrs['data-sub-html'] = caption;
2798        }
2799
2800        return {
2801            overlay: true,
2802            attrs: attrs
2803        };
2804    }
2805
2806    /**
2807     * Get overlay data for local videos in direct-click mode
2808     */
2809    function getLocalVideoOverlayData($slide) {
2810        var $videoDiv = $slide.find('div.local-video');
2811        if ($videoDiv.length === 0) return null;
2812
2813        var dataSources = $videoDiv.attr('data-sources');
2814        var videoSources = [];
2815        var videoUrl = null;
2816
2817        if (dataSources) {
2818            try {
2819                var sources = JSON.parse(dataSources);
2820                if (sources && sources.length > 0) {
2821                    videoUrl = sources[0].src;
2822                    videoSources = sources.map(function (source) {
2823                        return {
2824                            'src': source.src,
2825                            'type': source.type || getVideoType(source.src)
2826                        };
2827                    });
2828                }
2829            } catch (e) {
2830                console.error('MetaSlider Lightbox: Error parsing video sources', e);
2831                return null;
2832            }
2833        }
2834
2835        if (!videoUrl || videoSources.length === 0) return null;
2836
2837        var posterUrl = $videoDiv.attr('data-poster') || getPlaceholderThumb();
2838        var videoData = {
2839            'source': videoSources,
2840            'attributes': {
2841                'preload': false,
2842                'controls': true
2843            }
2844        };
2845
2846        var attrs = {
2847            'data-src': '',
2848            'data-video': JSON.stringify(videoData),
2849            'data-thumb': posterUrl
2850        };
2851
2852        var caption = extractCaption($slide);
2853        if (caption) {
2854            attrs['data-sub-html'] = caption;
2855        }
2856
2857        return {
2858            overlay: true,
2859            attrs: attrs
2860        };
2861    }
2862
2863    /**
2864     * Create lightbox button for Custom HTML slides using iframe data URI approach (similar to Layer slides)
2865     */
2866    function createCustomHtmlButton($slide) {
2867        var slideContent = $slide.html();
2868        if (!slideContent || !slideContent.trim()) return null;
2869
2870        var responsiveCSS = `
2871            .custom-html-content {
2872                width: 100% !important;
2873                max-width: 100% !important;
2874                height: 100vh !important;
2875                display: flex !important;
2876                align-items: center !important;
2877                justify-content: center !important;
2878                padding: 20px !important;
2879                box-sizing: border-box !important;
2880                background: #000 !important;
2881                color: white !important;
2882                font-family: Arial, sans-serif !important;
2883                overflow: auto !important;
2884            }
2885            .custom-html-content > div {
2886                max-width: 100% !important;
2887                max-height: 100% !important;
2888            }
2889            @media (max-width: 768px) {
2890                .custom-html-content {
2891                    padding: 10px !important;
2892                }
2893            }
2894        `;
2895
2896        var htmlContent = '<div class="custom-html-content">' + slideContent + '</div>';
2897        var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Custom HTML</title><style>body{margin:0;padding:0;} ' + responsiveCSS + '</style></head><body>' + htmlContent + '</body></html>');
2898
2899        var $button = $('<a class="ml-lightbox-button ml-button-metaslider ml-button-metaslider-custom-html" href="#">' + getButtonText() + '</a>');
2900
2901        $button.attr({
2902            'data-src': dataUri,
2903            'data-thumb': getPlaceholderThumb(),
2904            'data-iframe': 'true',
2905            'data-width': '90%',
2906            'data-height': '90%'
2907        });
2908
2909        return $button;
2910    }
2911
2912    /**
2913     * Get overlay data for Custom HTML slides in direct-click mode using iframe data URI approach
2914     */
2915    function getCustomHtmlOverlayData($slide) {
2916        var slideContent = $slide.html();
2917        if (!slideContent || !slideContent.trim()) return null;
2918
2919        var responsiveCSS = `
2920            .custom-html-content {
2921                width: 100% !important;
2922                max-width: 100% !important;
2923                height: 100vh !important;
2924                display: flex !important;
2925                align-items: center !important;
2926                justify-content: center !important;
2927                padding: 20px !important;
2928                box-sizing: border-box !important;
2929                background: #000 !important;
2930                color: white !important;
2931                font-family: Arial, sans-serif !important;
2932                overflow: auto !important;
2933            }
2934            .custom-html-content > div {
2935                max-width: 100% !important;
2936                max-height: 100% !important;
2937            }
2938            @media (max-width: 768px) {
2939                .custom-html-content {
2940                    padding: 10px !important;
2941                }
2942            }
2943        `;
2944
2945        var htmlContent = '<div class="custom-html-content">' + slideContent + '</div>';
2946        var dataUri = 'data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Custom HTML</title><style>body{margin:0;padding:0;} ' + responsiveCSS + '</style></head><body>' + htmlContent + '</body></html>');
2947
2948        var attrs = {
2949            'data-src': dataUri,
2950            'data-thumb': getPlaceholderThumb(),
2951            'data-iframe': 'true',
2952            'data-width': '90%',
2953            'data-height': '90%'
2954        };
2955
2956        var caption = extractCaption($slide);
2957        if (caption) {
2958            attrs['data-sub-html'] = caption;
2959        }
2960
2961        return {
2962            overlay: true,
2963            attrs: attrs
2964        };
2965    }
2966
2967    /**
2968     * Initialize "Enlarge on Click" gallery - similar to initPlainImageGallery but for button-based galleries
2969     */
2970    function initEnlargeOnClickGallery($gallery, useButtonMode) {
2971        var $wpContainers = $gallery.find('.wp-lightbox-container').has('button[data-wp-on], button.lightbox-trigger');
2972
2973        if ($wpContainers.length <= 1) return;
2974
2975        if (useButtonMode) {
2976            $wpContainers.each(function() {
2977                var $container = $(this);
2978                var $img = $container.find('img').first();
2979                var $wpButton = $container.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]');
2980
2981                if ($img.length && $wpButton.length) {
2982                    var imgSrc = $img.attr('src');
2983                    if (!imgSrc) return;
2984
2985                    var fullUrl = removeWordPressSizeSuffix(imgSrc);
2986
2987                    $container.attr({
2988                        'data-src': fullUrl,
2989                        'data-thumb': imgSrc
2990                    });
2991
2992                    var caption = extractCaption($img);
2993                    if (caption) {
2994                        $container.attr('data-sub-html', caption);
2995                    }
2996
2997                    var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-enlarge-override" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>');
2998                    if (caption) {
2999                        $button.attr('data-sub-html', caption);
3000                    }
3001
3002                    $wpButton.remove();
3003                    $container.css('position', 'relative').append($button);
3004                }
3005            });
3006
3007            var gallerySettings = getLightboxSettings(true);
3008            gallerySettings.selector = '.ml-lightbox-button';
3009
3010        } else {
3011            $wpContainers.each(function() {
3012                var $container = $(this);
3013                var $img = $container.find('img').first();
3014                var $wpButton = $container.find('button[data-wp-on], button.lightbox-trigger, button[data-wp-interactive]');
3015
3016                if ($img.length && $wpButton.length) {
3017                    var imgSrc = $img.attr('src');
3018                    if (!imgSrc) return;
3019
3020                    var fullUrl = removeWordPressSizeSuffix(imgSrc);
3021
3022                    var $wrapper = $('<a href="#" data-src="' + fullUrl + '" data-thumb="' + imgSrc + '"></a>');
3023
3024                    var caption = extractCaption($img);
3025                    if (caption) {
3026                        $wrapper.attr('data-sub-html', caption);
3027                    }
3028
3029                    $img.wrap($wrapper);
3030                    $wpButton.remove();
3031                }
3032            });
3033
3034            var gallerySettings = getLightboxSettings(true);
3035            gallerySettings.selector = 'a[data-src]';
3036        }
3037
3038        try {
3039            var instance = lightGallery($gallery[0], gallerySettings);
3040            $gallery.addClass('lg-initialized');
3041        } catch (error) {
3042            console.error('MetaSlider Lightbox: Error initializing Enlarge on Click gallery:', error);
3043        }
3044    }
3045
3046    /**
3047     * Initialize "Link to Media File" gallery when manual override is enabled - similar to automatic processing but runs independently
3048     */
3049    function initLinkToMediaManualGallery($gallery, useButtonMode) {
3050        var $mediaLinks = $gallery.find('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]').filter(function() {
3051            return $(this).find('img').length > 0;
3052        });
3053
3054        if ($mediaLinks.length <= 1) return;
3055
3056        if (useButtonMode) {
3057            $mediaLinks.each(function() {
3058                var $link = $(this);
3059                var href = $link.attr('href');
3060                var $img = $link.find('img').first();
3061                var imgSrc = $img.attr('src');
3062
3063                if (!href || !imgSrc) return;
3064
3065                var $container = $link.closest('.wp-block-image');
3066                if ($container.length === 0) {
3067                    $container = $link.parent();
3068                }
3069
3070                $container.attr({
3071                    'data-src': href,
3072                    'data-thumb': imgSrc
3073                });
3074
3075                var caption = extractCaption($img);
3076                if (caption) {
3077                    $container.attr('data-sub-html', caption);
3078                }
3079
3080                var $button = $('<a href="#" class="ml-lightbox-button ml-button-wordpress ml-button-wordpress-link-to-media" data-src="' + href + '" data-thumb="' + imgSrc + '">' + getButtonText() + '</a>');
3081                if (caption) {
3082                    $button.attr('data-sub-html', caption);
3083                }
3084
3085                $container.css('position', 'relative').append($button);
3086            });
3087
3088            var gallerySettings = getLightboxSettings(true);
3089            gallerySettings.selector = '.ml-lightbox-button';
3090
3091        } else {
3092            $mediaLinks.each(function() {
3093                var $link = $(this);
3094                var href = $link.attr('href');
3095                var $img = $link.find('img').first();
3096                var imgSrc = $img.attr('src');
3097
3098                if (!href || !imgSrc) return;
3099
3100                $link.attr({
3101                    'data-src': href,
3102                    'data-thumb': imgSrc
3103                });
3104
3105                var caption = extractCaption($img);
3106                if (caption) {
3107                    $link.attr('data-sub-html', caption);
3108                }
3109            });
3110
3111            var gallerySettings = getLightboxSettings(true);
3112            gallerySettings.selector = 'a[data-src]';
3113        }
3114
3115        try {
3116            var instance = lightGallery($gallery[0], gallerySettings);
3117            $gallery.addClass('lg-initialized');
3118        } catch (error) {
3119            console.error('MetaSlider Lightbox: Error initializing Link to Media File manual gallery:', error);
3120        }
3121    }
3122
3123    function initWooCommerceGallery() {
3124        var $gallery = $('.woocommerce-product-gallery');
3125
3126        if ($gallery.length === 0) {
3127            return;
3128        }
3129
3130        var useButtonMode = false;
3131        if (typeof mlLightboxSettings !== 'undefined' && mlLightboxSettings.metaslider_options) {
3132            useButtonMode = mlLightboxSettings.metaslider_options.show_lightbox_button !== false;
3133        }
3134
3135        var $images = $gallery.find('.woocommerce-product-gallery__image');
3136
3137        if ($images.length === 0) {
3138            return;
3139        }
3140
3141        if (!useButtonMode) {
3142            $gallery.off('.wc-product-gallery-zoom');
3143            $gallery.find('img').off('.wc-product-gallery-zoom');
3144            $gallery.find('a').off('.wc-product-gallery-zoom');
3145
3146            $images.each(function() {
3147                var $imageWrapper = $(this);
3148                var $img = $imageWrapper.find('img').first();
3149                var $link = $imageWrapper.find('a').first();
3150
3151                $img.css('pointer-events', 'none');
3152                $link.css('cursor', 'pointer');
3153            });
3154
3155            $gallery.find('.zoomImg').remove();
3156
3157            $images.find('a.ml-woo-product-image').each(function() {
3158                var $link = $(this);
3159                if ($link.hasClass('lg-initialized')) {
3160                    var lgInstance = $link.data('lightGallery');
3161                    if (lgInstance) {
3162                        lgInstance.destroy(true);
3163                    }
3164                    $link.removeClass('lg-initialized').removeAttr('data-lg-id');
3165                }
3166            });
3167        }
3168
3169        if ($gallery.hasClass('lg-initialized')) {
3170            var galleryInstance = $gallery.data('lightGallery');
3171            if (galleryInstance) {
3172                galleryInstance.destroy(true);
3173            }
3174            $gallery.removeClass('lg-initialized');
3175        }
3176
3177        $images.each(function() {
3178            var $imageWrapper = $(this);
3179            var $img = $imageWrapper.find('img').first();
3180            var $link = $imageWrapper.find('a').first();
3181
3182            if ($imageWrapper.find('.ml-lightbox-button').length > 0 && useButtonMode) {
3183                return;
3184            }
3185
3186            if (useButtonMode) {
3187                var fullUrl = $img.attr('data-full-url');
3188                var thumbUrl = $img.attr('data-thumb-url');
3189
3190                if (!fullUrl || !thumbUrl) {
3191                    return;
3192                }
3193
3194                var $button = $('<a href="#" class="ml-lightbox-button ml-button-woo">' + getButtonText() + '</a>');
3195                $button.attr({
3196                    'data-src': fullUrl,
3197                    'data-thumb': thumbUrl
3198                });
3199
3200                $imageWrapper.css('position', 'relative').append($button);
3201            }
3202        });
3203
3204        var gallerySettings = getLightboxSettings(true);
3205
3206        if (useButtonMode) {
3207            gallerySettings.selector = '.ml-lightbox-button';
3208        } else {
3209            gallerySettings.selector = '.ml-woo-product-image';
3210        }
3211
3212        gallerySettings.galleryId = 'ml-woo-gallery-' + Date.now();
3213
3214        try {
3215            var instance = lightGallery($gallery[0], gallerySettings);
3216            $gallery.addClass('lg-initialized');
3217
3218            $gallery.on('found_variation', function(event, variation) {
3219                if (instance && typeof instance.refresh === 'function') {
3220                    setTimeout(function() {
3221                        instance.refresh();
3222                    }, 100);
3223                }
3224            });
3225
3226            $gallery.on('reset_data', function() {
3227                if (instance && typeof instance.refresh === 'function') {
3228                    setTimeout(function() {
3229                        instance.refresh();
3230                    }, 100);
3231                }
3232            });
3233
3234        } catch (error) {
3235            console.error('MetaSlider Lightbox: Error initializing WooCommerce gallery:', error);
3236        }
3237    }
3238
3239    window.mlInitWooCommerceGallery = initWooCommerceGallery;
3240
3241    // Exposed for unit tests only — not a public API.
3242    window.__mlLightboxTestable = {
3243        CONSTANTS: CONSTANTS,
3244        escapeHtml: escapeHtml,
3245        ensureImageAltAttribute: ensureImageAltAttribute,
3246        removeConflictingAttributes: removeConflictingAttributes
3247    };
3248
3249})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.