PageSourceSearch

https://www.maeoe.org/wp-content/plugins/ml-slider-lightbox/assets/js/ml-lightgallery-init.js?ver=1790099832

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