PageSourceSearch

https://www.worldwar2database.com/wp-content/plugins/ml-slider-lig…/js/ml-lightgallery-init.js?ver=1786677902

js worldwar2database.com collected 2026-09-24 23:56:38 UTC 125,877 bytes, 3,280 lines download raw bytes

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