PageSourceSearch

https://www.efe.cl/wp-content/plugins/ml-slider-lightbox/assets/js/ml-lightgallery-init.js?ver=1787457611

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