PageSourceSearch

https://online.wattsatelier.com/wp-content/themes/watts-studio-bb-child/assets/js/slider.js?ver=1.0.3

js wattsatelier.com collected 2026-10-02 12:49:56 UTC 29,988 bytes, 875 lines download raw bytes

1/**
2 * Glide.js Slider Initialization and Control Logic
3 * Assumes that Glide CSS and JS files are already enqueued (or loaded).
4 */
5document.addEventListener('DOMContentLoaded', function () {
6
7  // --- Configuration Variables ---
8  const defaultInterval = 3; // seconds
9  // CRITICAL: Must match the Glide animationDuration option (0.5s = 500ms)
10  const ANIMATION_DURATION_MS = 500;
11
12  // --- DOM Element Selectors ---
13  const sliderContainerId = 'my-glide';
14  const slideListSelector = `#${sliderContainerId} .glide__slides`;
15  const intervalInputId = 'interval-input';
16  const numImagesInputId = 'num-images-input';
17
18  // Image Grid Container
19  const imageGridContainerId = 'image-grid-container';
20
21  // Selector for hidden initial mode input
22  const initialModeInputId = 'initial-view-mode';
23
24  // NEW SELECTOR for single-option tag filter
25  const singleTagSelectId = 'tag-select-single';
26
27  // Selector for view selection radio buttons (for runtime switching)
28  const viewSelectionName = 'view-option';
29
30  // Control Buttons
31  const playButtonId = 'play-slider-button';
32  const pauseButtonId = 'pause-slider-button';
33  const loadBtnId = 'load-posts';
34  const timelapseContainerId = 'timelapse';
35  const controlsOnlySelector = '.slider-controls-only'; // Selector for Slider-only controls
36
37  // Navigation Controls
38  const nextButtonId = 'next-slide-button';
39  const prevButtonId = 'prev-slide-button';
40
41  // Fullscreen Selector
42  const fullscreenButtonId = 'fullscreen-toggle-button';
43
44
45  // Check if the container exists (prevent errors if shortcode isn't used)
46  const glideElement = document.getElementById(sliderContainerId);
47  if (!glideElement) {
48    console.warn('Glide container (#my-glide) not found. Slider script terminated.');
49    return;
50  }
51
52  // --- Core Variables ---
53  let glide = null; // Variable to hold the Glide instance
54  let countdownInterval = null; // Variable to hold the timer interval
55  let isPlaying = false; // Custom state variable for play/pause status
56  let currentIntervalMs = defaultInterval * 1000; // Stores the current interval in ms
57
58  // Get the initial view mode from the hidden input, default to 'slider'
59  const initialModeInput = document.getElementById(initialModeInputId);
60  let currentViewMode = initialModeInput ? initialModeInput.value : 'slider';
61
62
63  // --- Utility Functions ---
64
65  /**
66   * Converts milliseconds into a formatted MM:SS string.
67   */
68  function formatTime(ms) {
69    const totalSeconds = Math.max(0, Math.floor(ms / 1000));
70    const minutes = Math.floor(totalSeconds / 60);
71    const seconds = totalSeconds % 60;
72    const formattedMinutes = String(minutes).padStart(2, '0');
73    const formattedSeconds = String(seconds).padStart(2, '0');
74    return `${formattedMinutes}:${formattedSeconds}`;
75  }
76
77  // ----------------------------------------------------------------
78  // --- UTILITY FUNCTION: Random Selection ---
79  // ----------------------------------------------------------------
80
81  /**
82   * Selects 'count' elements from an array randomly without repetition.
83   * Assumes the input array contains post URLs (or similar data).
84   */
85  function selectRandomItems(array, count) {
86    if (count >= array.length) {
87      return array;
88    }
89
90    const shuffled = [...array];
91
92    for (let i = array.length - 1; i > 0; i--) {
93      const j = Math.floor(Math.random() * (i + 1));
94      [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; // Swap
95    }
96
97    return shuffled.slice(0, count);
98  }
99
100
101  // ----------------------------------------------------------------
102  // --- Rendering Slides (Slider) ---
103  // ----------------------------------------------------------------
104
105  /**
106   * Generates the slide HTML from an array of URLs and injects it.
107   */
108  function renderSlidesFromUrls(imageUrls) {
109    const slideList = document.querySelector(slideListSelector);
110    if (!slideList) return;
111
112    let slidesHtml = '';
113
114    imageUrls.forEach(url => {
115      slidesHtml += `
116				<li class="glide__slide">
117					<img src="${url}">
118				</li>
119			`;
120    });
121
122    slideList.innerHTML = slidesHtml;
123  }
124
125  // ----------------------------------------------------------------
126  // --- Rendering Image Grid ---
127  // ----------------------------------------------------------------
128
129  /**
130   * Generates the grid HTML from an array of URLs and injects it.
131   */
132  function renderImageGrid(imageUrls) {
133    const gridContainer = document.getElementById(imageGridContainerId);
134    if (!gridContainer) return;
135
136    let gridHtml = '';
137
138    imageUrls.forEach((url, index) => {
139      gridHtml += `
140				<div class="grid-item">
141					<img src="${url}" alt="Gallery Image ${index + 1}" data-full-url="${url}">
142				</div>
143			`;
144    });
145
146    gridContainer.innerHTML = gridHtml;
147
148    const images = gridContainer.querySelectorAll('.grid-item img');
149    images.forEach(img => {
150      img.addEventListener('click', function () {
151        setupLightbox(this.getAttribute('data-full-url'));
152      });
153    });
154  }
155
156
157  // ----------------------------------------------------------------
158  // --- LIGHTBOX LOGIC ---
159  // ----------------------------------------------------------------
160
161  /**
162   * Shows the lightbox with the selected image.
163   */
164  function setupLightbox(imageUrl) {
165    let lightbox = document.getElementById('image-lightbox');
166
167    if (!lightbox) {
168      // If the lightbox doesn't exist, create it dynamically
169      lightbox = document.createElement('div');
170      lightbox.id = 'image-lightbox';
171      lightbox.className = 'image-lightbox';
172      lightbox.innerHTML = `
173				<div class="lightbox-content">
174					<button class="lightbox-close-btn" id="lightbox-close-btn">
175						<i class="fas fa-times"></i>
176					</button>
177					<img src="" alt="Zoomed Image" class="lightbox-img">
178				</div>
179			`;
180      document.body.appendChild(lightbox);
181
182      // Assign listener to the close button
183      document.getElementById('lightbox-close-btn').addEventListener('click', closeLightbox);
184      // Close on click on the background
185      lightbox.addEventListener('click', function (e) {
186        if (e.target.id === 'image-lightbox') {
187          closeLightbox();
188        }
189      });
190      // Close with the ESC key
191      document.addEventListener('keydown', (e) => {
192        if (e.key === 'Escape' && lightbox.classList.contains('is-visible')) {
193          closeLightbox();
194        }
195      });
196    }
197
198    const lightboxImg = lightbox.querySelector('.lightbox-img');
199    lightboxImg.src = imageUrl;
200    lightbox.classList.add('is-visible');
201    document.body.classList.add('no-scroll'); // Prevent scroll in the background
202  }
203
204  /**
205   * Closes the lightbox.
206   */
207  function closeLightbox() {
208    const lightbox = document.getElementById('image-lightbox');
209    if (lightbox) {
210      lightbox.classList.remove('is-visible');
211      document.body.classList.remove('no-scroll');
212    }
213  }
214
215
216  // ----------------------------------------------------------------
217  // --- SYNCHRONIZED TIMER LOGIC ---
218  // ----------------------------------------------------------------
219
220  /**
221   * Stops the existing countdown timer.
222   */
223  function stopCountdown() {
224    if (countdownInterval) {
225      clearInterval(countdownInterval);
226      countdownInterval = null;
227    }
228    const timelapseElement = document.getElementById(timelapseContainerId);
229    if (timelapseElement) {
230      timelapseElement.textContent = 'Paused.';
231    }
232  }
233
234  /**
235   * Starts a new synchronized countdown timer.
236   */
237  function startCountdown(totalMs) {
238    stopCountdown();
239
240    const timelapseElement = document.getElementById(timelapseContainerId);
241
242    if (!timelapseElement) {
243      return;
244    }
245
246    if (totalMs <= ANIMATION_DURATION_MS) {
247      timelapseElement.textContent = `Interval too short for visible timer.`;
248      return;
249    }
250
251    let timeRemainingMs = totalMs;
252    // Initial display
253    timelapseElement.textContent = `${formatTime(timeRemainingMs)}`;
254
255    countdownInterval = setInterval(() => {
256      timeRemainingMs -= 1000;
257
258      if (timeRemainingMs <= 0) {
259        // The counter ends and waits for Glide to trigger run.before
260        timeRemainingMs = 0;
261        clearInterval(countdownInterval);
262        countdownInterval = null;
263        return;
264      }
265
266      const currentTimelapseElement = document.getElementById(timelapseContainerId);
267
268      if (currentTimelapseElement) {
269        currentTimelapseElement.textContent = `${formatTime(timeRemainingMs)}`;
270      } else {
271        clearInterval(countdownInterval);
272        countdownInterval = null;
273      }
274
275    }, 1000);
276  }
277
278  // ----------------------------------------------------------------
279  // --- Main Glide Initialization ---
280  // ----------------------------------------------------------------
281
282  /**
283   * Initializes or re-initializes the Glide slider.
284   * @param {number} intervalMs - The new interval in milliseconds (used for options and currentIntervalMs).
285   * @param {boolean} startPlaying - Whether to start playing immediately.
286   */
287  function initializeGlide(intervalMs, startPlaying = false) {
288    const container = document.getElementById(sliderContainerId);
289    if (!container) {
290      console.warn(`Cannot initialize Glide: container #${sliderContainerId} not found.`);
291      return;
292    }
293
294    // Don't initialize Glide if the view is Gallery (early check)
295    if (currentViewMode === 'gallery') {
296      if (glide) {
297        glide.destroy();
298        glide = null;
299      }
300      stopCountdown();
301      container.classList.remove('is-visible');
302      return;
303    }
304
305    // If it already exists, we exit (it should not be re-initialized unless we change the interval in handlePlayClick)
306    if (glide) {
307      isPlaying = startPlaying;
308      updateButtonVisibility();
309      return;
310    }
311
312    // --- INSTANCE INITIALIZATION ---
313    currentIntervalMs = intervalMs;
314    const options = {
315      type: 'carousel',
316      perView: 1,
317      // We use the 'startPlaying' state only if it is the first initialization
318      autoplay: startPlaying && intervalMs > 0 ? intervalMs : false,
319      focusAt: 'center',
320      gap: 0,
321      animationDuration: ANIMATION_DURATION_MS,
322      hoverpause: false
323    };
324
325    const totalSlides = document.querySelectorAll(slideListSelector + ' .glide__slide').length;
326    if (totalSlides > 0) {
327      glide = new Glide(`#${sliderContainerId}`, options);
328      glide.mount();
329      updateBullets();
330    } else {
331      // If there are no slides, there is nothing to initialize or play.
332      return;
333    }
334
335    // --- AUTOPLAY LISTENERS CONFIGURATION (SINGLE PLACE) ---
336    if (intervalMs > 0) {
337      // Restarts the counter every time the slider moves
338      glide.on('run.before', function () {
339        // Here we use the current interval, synchronized with the Glide transition
340        startCountdown(currentIntervalMs);
341      });
342
343      // If it starts in autoplay, starts the counter immediately
344      if (options.autoplay) {
345        startCountdown(intervalMs);
346        console.log(`Glide slider started with an initial interval of ${intervalMs}ms.`);
347      }
348    }
349
350    // Synchronize states and buttons
351    isPlaying = startPlaying;
352    updateButtonVisibility();
353  }
354
355  /**
356   * Generates and inserts the navigation bullets based on the number of slides.
357   */
358  function updateBullets() {
359    // Note: Bullets container is not included in the PHP shortcode anymore, 
360    // so this function might be legacy unless bullets are added back.
361    const bulletsContainer = document.querySelector(`#${sliderContainerId} .glide__bullets`);
362    const totalSlides = document.querySelectorAll(slideListSelector + ' .glide__slide').length;
363
364    if (!bulletsContainer) return;
365
366    let bulletsHtml = '';
367    for (let i = 0; i < totalSlides; i++) {
368      bulletsHtml += `<button class="glide__bullet" data-glide-dir="=${i}"></button>`;
369    }
370    bulletsContainer.innerHTML = bulletsHtml;
371  }
372
373  /**
374   * Toggles the visibility of the Play and Pause buttons based on the isPlaying state.
375   */
376  function updateButtonVisibility() {
377    const playButton = document.getElementById(playButtonId);
378    const pauseButton = document.getElementById(pauseButtonId);
379    if (playButton && pauseButton) {
380      playButton.style.display = isPlaying ? 'none' : 'flex';
381      pauseButton.style.display = isPlaying ? 'flex' : 'none';
382    }
383
384    // Controls that are ONLY visible in Slider mode (Timelapse/Play/Pause/Nav)
385    const topControls = document.querySelector(`#${sliderContainerId} .custom-controls-top`);
386    const bottomControls = document.querySelector(`#${sliderContainerId} .custom-controls-bottom`);
387    const timelapse = document.getElementById(timelapseContainerId);
388
389    if (currentViewMode === 'gallery') {
390      if (topControls) topControls.style.display = 'none';
391      if (bottomControls) bottomControls.style.display = 'none';
392      if (timelapse) timelapse.style.display = 'none';
393    } else {
394      if (topControls) topControls.style.display = 'flex';
395      if (bottomControls) bottomControls.style.display = 'flex';
396      if (timelapse) timelapse.style.display = 'block';
397    }
398
399  }
400
401
402  // ----------------------------------------------------------------
403  // --- Control Handlers ---
404  // ----------------------------------------------------------------
405
406  /**
407   * Handles the click on the 'Play' button. (Resume/Restart)
408   */
409  function handlePlayClick() {
410    if (currentViewMode === 'gallery') {
411      console.warn('Play button is only active in Slider mode.');
412      return;
413    }
414
415    const intervalInput = document.getElementById(intervalInputId);
416    const intervalSeconds = parseFloat(intervalInput.value);
417    const newIntervalMs = intervalSeconds * 1000;
418
419    const totalSlides = document.querySelectorAll(slideListSelector + ' .glide__slide').length;
420    if (totalSlides === 0 || document.querySelector('.loading-slide')) {
421      console.error('Cannot start play: Slider is empty or still loading. Please load images first.');
422      return;
423    }
424
425    if (isNaN(intervalSeconds) || intervalSeconds <= 0) {
426      console.error('Please enter a valid time (seconds) greater than 0.');
427      return;
428    }
429
430    // 1. If the slider does not exist, we initialize it and start playing.
431    if (!glide) {
432      console.log('Glide not initialized. Re-initializing now.');
433      initializeGlide(newIntervalMs, true);
434      return;
435    }
436
437    // 2. If the interval has changed, we must destroy and re-initialize to apply the new configuration.
438    if (newIntervalMs !== currentIntervalMs) {
439      console.log('Interval changed. Destroying and re-initializing to apply new autoplay duration.');
440      if (glide) {
441        glide.destroy();
442        glide = null;
443      }
444      // This initializes with the new interval and starts playing.
445      initializeGlide(newIntervalMs, true);
446      return;
447    }
448
449
450    // 3. IF THE SLIDER EXISTS AND THE INTERVAL HAS NOT CHANGED, WE SIMPLY RESUME/RESTART THE CYCLE.
451    if (glide && !isPlaying) {
452      // Resumes playback. Glide will trigger 'run.before' before the next transition.
453      glide.play(currentIntervalMs);
454      isPlaying = true;
455      updateButtonVisibility();
456      // We start the countdown to immediately synchronize with the current slide
457      startCountdown(currentIntervalMs);
458      console.log('Glide slider resumed via control button.');
459    } else if (glide && isPlaying) {
460      // If it is already playing, restart the cycle (moves to the next slide).
461      // This forces Glide to trigger 'run.before' immediately after the pause.
462      glide.pause();
463      glide.play(currentIntervalMs);
464      // We start the countdown to immediately synchronize with the current slide
465      startCountdown(currentIntervalMs);
466      console.log('Glide slider cycle restarted (synced).');
467    }
468  }
469
470
471  /**
472   * Pauses the slider and stops the countdown timer.
473   */
474  function handlePauseClick() {
475    if (glide) {
476      glide.pause();
477      isPlaying = false;
478      updateButtonVisibility();
479      stopCountdown();
480      console.log('Glide slider paused via control button.');
481    }
482  }
483
484
485  /**
486   * Handles the click on the 'Load posts' button, including view selection.
487   */
488  function handleLoadClick() {
489
490    // The view is determined by the current state, which might have been set by the radio buttons change listener
491    const selectedView = currentViewMode;
492
493    const sliderContainer = document.getElementById(sliderContainerId);
494    const gridContainer = document.getElementById(imageGridContainerId);
495
496    // --- 2. Prepare containers before AJAX ---
497    // When loading new images, ALWAYS DESTROY the previous instance
498    if (glide) {
499      console.log('Destroying old Glide instance before AJAX...');
500      glide.destroy();
501      glide = null;
502    }
503    stopCountdown();
504    isPlaying = false;
505    updateButtonVisibility();
506
507    // Hide both and then show the spinner in the ACTIVE container
508    sliderContainer.classList.remove('is-visible');
509    gridContainer.classList.remove('is-visible');
510
511
512    if (selectedView === 'slider') {
513      sliderContainer.classList.add('is-visible');
514      const slideList = document.querySelector(slideListSelector);
515      if (slideList) {
516        slideList.innerHTML = `
517					<li class="glide__slide loading-slide">
518						<i class="fas fa-spinner fa-spin"></i>
519						<p>Loading images for slider...</p>
520					</li>
521				`;
522      }
523    } else { // 'gallery'
524      gridContainer.classList.add('is-visible');
525      gridContainer.innerHTML = '<p class="loading-message"><i class="fas fa-spinner fa-spin"></i> Loading images for grid...</p>';
526    }
527
528
529    // --- 3. Get values and make the AJAX call ---
530
531    // --- MODIFICATION: Read value from the SINGLE OPTION SELECT ---
532    const tagSelect = document.getElementById(singleTagSelectId);
533    let selectedTagValue = '';
534
535    if (tagSelect && tagSelect.value !== "") {
536      selectedTagValue = tagSelect.value;
537    }
538
539    // Create the tags array. If a value was selected, it is added to the array; otherwise, it is an empty array.
540    const selected_tags = selectedTagValue ? [selectedTagValue] : [];
541    const current_tag = JSON.stringify(selected_tags);
542    // --- END MODIFICATION ---
543
544
545    const current_page = 1;
546
547    const perPageInput = document.getElementById(numImagesInputId);
548    let requestedCount = parseInt(perPageInput.value);
549
550    if (isNaN(requestedCount) || requestedCount < 1) {
551      requestedCount = 5;
552    }
553    const perPage = requestedCount;
554
555    // Obtener el intervalo actual para el futuro initializeGlide
556    const intervalInput = document.getElementById(intervalInputId);
557    currentIntervalMs = parseFloat(intervalInput.value) * 1000;
558
559
560    jQuery.ajax({
561      type: "POST",
562      url: ajax_object.ajax_url,
563      dataType: "json",
564      data: {
565        action: "f1_advanced_search",
566        security: ajax_object.nonce,
567        tag: current_tag, // Ahora solo contiene un ID o un array vacío
568        paged: current_page,
569        ppp: perPage,
570      },
571      success: function success(res) {
572
573        console.log('Response: ', res);
574
575        // Ensure the final container visibility is correct
576        if (selectedView === 'slider') {
577          gridContainer.classList.remove('is-visible');
578          sliderContainer.classList.add('is-visible');
579        } else {
580          sliderContainer.classList.remove('is-visible');
581          gridContainer.classList.add('is-visible');
582        }
583
584
585        if (res.posts && res.posts.length > 0) {
586
587          // Note: Selecting random items ensures we only use 'requestedCount' items
588          const imagesToRender = selectRandomItems(res.posts, requestedCount);
589
590          if (imagesToRender.length > 0) {
591
592            // Render according to the selected view
593            if (selectedView === 'slider') {
594              renderSlidesFromUrls(imagesToRender);
595              // Initializes paused (autoplay: false). The user must press Play
596              initializeGlide(currentIntervalMs, false);
597            } else {
598              renderImageGrid(imagesToRender);
599            }
600
601          } else {
602            // Message for No posts (Empty Array)
603            const messageHtml = 'No posts found matching your criteria.';
604            if (selectedView === 'slider') {
605              const slideList = document.querySelector(slideListSelector);
606              if (slideList) {
607                slideList.innerHTML = `<li class="glide__slide message-slide">${messageHtml}</li>`;
608              }
609              // Re-initializes to clear listeners and show the message in the slider
610              initializeGlide(0, false);
611            } else {
612              gridContainer.innerHTML = `<p class="message-slide">${messageHtml}</p>`;
613            }
614          }
615
616        } else {
617          // No posts. (Empty server response)
618          const messageHtml = 'No posts found.';
619          if (selectedView === 'slider') {
620            const slideList = document.querySelector(slideListSelector);
621            if (slideList) {
622              slideList.innerHTML = `<li class="glide__slide message-slide">${messageHtml}</li>`;
623            }
624            // Re-initializes to clear listeners and show the message in the slider
625            initializeGlide(0, false);
626          } else {
627            gridContainer.innerHTML = `<p class="message-slide">${messageHtml}</p>`;
628          }
629        }
630      },
631      error: function (xhr, status, error) {
632        console.error("AJAX Error:", status, error);
633
634        // Ensure error container visibility
635        if (selectedView === 'slider') {
636          gridContainer.classList.remove('is-visible');
637          sliderContainer.classList.add('is-visible');
638        } else {
639          sliderContainer.classList.remove('is-visible');
640          gridContainer.classList.add('is-visible');
641        }
642
643        // Display error in the visible container
644        const errorHtml = `Error loading content: ${error}`;
645        if (selectedView === 'slider') {
646          const slideList = document.querySelector(slideListSelector);
647          if (slideList) {
648            slideList.innerHTML = `<li class="glide__slide error-slide">${errorHtml}</li>`;
649          }
650          initializeGlide(0, false);
651        } else {
652          gridContainer.innerHTML = `<p class="error-slide">${errorHtml}</p>`;
653        }
654      }
655    });
656  }
657
658
659  /**
660   * Moves to the next slide.
661   */
662  function handleNextClick() {
663    if (glide) {
664      glide.go('>');
665      // Manual navigation stops the timer if it's currently paused
666      if (!isPlaying) {
667        stopCountdown();
668      }
669    }
670  }
671
672  /**
673   * Moves to the previous slide.
674   */
675  function handlePrevClick() {
676    if (glide) {
677      glide.go('<');
678      // Manual navigation stops the timer if it's currently paused
679      if (!isPlaying) {
680        stopCountdown();
681      }
682    }
683  }
684
685
686  // --- FULLSCREEN LOGIC ---
687
688
689  /**
690   * Toggles in or out of fullscreen mode.
691   */
692  function toggleFullscreen() {
693    // Only allow fullscreen in Slider mode
694    if (currentViewMode !== 'slider') {
695      console.warn('Fullscreen is only available in Slider mode.');
696      return;
697    }
698
699    const slider = document.getElementById(sliderContainerId);
700    const isFullscreen = document.fullscreenElement;
701
702    if (!isFullscreen) {
703      if (slider.requestFullscreen) {
704        slider.requestFullscreen();
705      } else if (slider.webkitRequestFullscreen) { /* Safari */
706        slider.webkitRequestFullscreen();
707      } else if (slider.msRequestFullscreen) { /* IE11 */
708        slider.msRequestFullscreen();
709      }
710    } else {
711      if (document.exitFullscreen) {
712        document.exitFullscreen();
713      } else if (document.webkitExitFullscreen) { /* Safari */
714        document.webkitExitFullscreen();
715      } else if (document.msExitFullscreen) { /* IE11 */
716        document.msExitFullscreen();
717      }
718    }
719
720    if (glide) {
721      // Update Glide dimensions after fullscreen change
722      setTimeout(() => {
723        glide.update();
724      }, 50);
725    }
726  }
727
728  /**
729   * Global listener to handle exiting fullscreen and updating the icon.
730   */
731  document.addEventListener('fullscreenchange', function () {
732    const isFullscreen = document.fullscreenElement;
733    const fullscreenButton = document.getElementById(fullscreenButtonId);
734
735    if (fullscreenButton) {
736      const icon = fullscreenButton.querySelector('i');
737      if (icon) {
738        icon.className = isFullscreen ? 'fas fa-compress-alt' : 'fas fa-expand-alt';
739      }
740    }
741    if (glide) {
742      setTimeout(() => {
743        glide.update();
744      }, 50);
745    }
746  });
747
748
749  // ----------------------------------------------------------------
750  // --- Setup and Event Listeners ---
751  // ----------------------------------------------------------------
752
753
754  /**
755   * RE-ASSIGNMENT OF LISTENERS
756   */
757  function assignControlListeners() {
758    const playButton = document.getElementById(playButtonId);
759    const pauseButton = document.getElementById(pauseButtonId);
760    const nextButton = document.getElementById(nextButtonId);
761    const prevButton = document.getElementById(prevButtonId);
762    const fullscreenButton = document.getElementById(fullscreenButtonId);
763
764    // Play/Pause/Nav Controls
765    if (playButton) {
766      playButton.addEventListener('click', handlePlayClick);
767    }
768    if (pauseButton) {
769      pauseButton.addEventListener('click', handlePauseClick);
770    }
771    if (prevButton) {
772      prevButton.addEventListener('click', handlePrevClick);
773    }
774    if (nextButton) {
775      nextButton.addEventListener('click', handleNextClick);
776    }
777
778    // Fullscreen
779    if (fullscreenButton) {
780      fullscreenButton.addEventListener('click', toggleFullscreen);
781    }
782
783    // Listen for changes in view radios to ensure control visibility and container display
784    const viewRadios = document.querySelectorAll(`input[name="${viewSelectionName}"]`);
785    viewRadios.forEach(radio => {
786      radio.addEventListener('change', function () {
787        const newViewMode = this.value;
788        currentViewMode = newViewMode; // Update global state
789
790        const isSlider = newViewMode === 'slider';
791
792        // Toggle visibility of slider-specific controls (Interval Time)
793        document.querySelectorAll(controlsOnlySelector).forEach(control => {
794          control.style.display = isSlider ? 'block' : 'none';
795        });
796
797        // Toggle main container visibility
798        const sliderContainer = document.getElementById(sliderContainerId);
799        const gridContainer = document.getElementById(imageGridContainerId);
800
801        if (isSlider) {
802          sliderContainer.classList.add('is-visible');
803          gridContainer.classList.remove('is-visible');
804        } else {
805          sliderContainer.classList.remove('is-visible');
806          gridContainer.classList.add('is-visible');
807        }
808
809
810        // If switching to Gallery, ensure the slider is destroyed and countdown stopped
811        if (!isSlider && glide) {
812          glide.destroy();
813          glide = null;
814          stopCountdown();
815          isPlaying = false;
816          updateButtonVisibility();
817        }
818
819        // If switching back to Slider, ensure the controls are correct and placeholder is set
820        if (isSlider) {
821          // Placeholder message (if not already initialized by Load Images)
822          if (!glide) {
823            const slideList = document.querySelector(slideListSelector);
824            if (slideList && slideList.children.length === 0) {
825              slideList.innerHTML = `<li class="glide__slide message-slide">Use the controls above and click "Load Images" to begin.</li>`;
826            }
827          }
828          // Re-run initialization to set up buttons and potential autoplay if image data exists
829          initializeGlide(currentIntervalMs, false);
830        }
831
832        updateButtonVisibility(); // Update Play/Pause/Nav controls visibility based on new mode
833
834      });
835    });
836  }
837
838
839  // 1. Assign listeners to configuration controls
840  const intervalInput = document.getElementById(intervalInputId);
841  const loadBtn = document.getElementById(loadBtnId);
842
843  if (intervalInput) {
844    intervalInput.value = defaultInterval;
845  }
846
847  if (loadBtn) {
848    loadBtn.addEventListener('click', handleLoadClick);
849  }
850
851  // 2. Assign listeners to navigation and Fullscreen controls (initial load)
852  assignControlListeners();
853
854
855  // 3. Initial Load: Based on initialMode (from PHP shortcode attribute)
856  // Ensure the interval is set based on the default or user input
857  currentIntervalMs = defaultInterval * 1000;
858
859  if (currentViewMode === 'slider') {
860    // Initialize slider (empty and paused)
861    const slideList = document.querySelector(slideListSelector);
862    if (slideList) {
863      // Only inject placeholder if it wasn't already done by PHP (which is fine)
864    }
865    initializeGlide(currentIntervalMs, false);
866  } else {
867    // If 'gallery' (grid), ensure controls are hidden and the grid is visible
868    const gridContainer = document.getElementById(imageGridContainerId);
869    if (gridContainer) {
870      // Grid is already visible due to PHP class insertion, add placeholder message
871      gridContainer.innerHTML = `<p class="message-slide">Use the controls above and click "Load Images" to begin.</p>`;
872    }
873    updateButtonVisibility(); // Hide slider controls
874  }
875});

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.