PageSourceSearch

https://www.elle.cz/js/modules/galleryViewer.js

js elle.cz collected 2026-10-02 07:55:25 UTC 23,984 bytes, 849 lines download raw bytes

1import {parsePositiveInteger} from '../core/measure.js';
2import {createFacebookEmbedController} from './gallery/facebookEmbeds.js';
3import {getGalleryIndexFromHash, replaceGalleryHash} from './gallery/galleryHash.js';
4import {createLazyAdLoader} from './gallery/lazyAds.js';
5import {processSocialEmbedProvider} from './gallery/socialEmbeds.js';
6
7export function initGalleryViewer() {
8  const ANIMATION_DURATION_MS = 280;
9  const DEFAULT_ADVERT_RELOAD_NAVIGATION_INTERVAL = 5;
10  const MOBILE_BREAKPOINT_QUERY = '(max-width: 1023px)';
11  const EMBED_REFRESH_DELAYS_MS = [300, 1000];
12  const EMBED_SELECTOR = '.gallery-viewer__embed';
13  const EMBED_CONTENT_SELECTOR = '.gallery-viewer__embed-content';
14  const UPSCORE_INITIALIZED_EVENT = 'upscore:initialized';
15  const TRACK_NO_TRANSITION_CLASS = 'gallery-viewer__track--no-transition';
16  const TRUE_DATA_VALUE = 'true';
17  const viewer = document.querySelector('[data-gallery-viewer]');
18  if (!viewer) return;
19
20  const supportsOnePageMobileLayout = viewer.classList.contains('gallery-viewer--mobile-one-page');
21  const main = viewer.querySelector('.gallery-viewer__main');
22  const imageContainer = viewer.querySelector('.gallery-viewer__image-container');
23  const stage = viewer.querySelector('[data-gallery-stage]');
24  const track = viewer.querySelector('[data-gallery-track]');
25  const originalSlides = Array.from(track?.querySelectorAll('[data-gallery-slide]') || []);
26  const infoSlides = viewer.querySelectorAll('[data-gallery-info]');
27  const prevBtn = viewer.querySelector('[data-gallery-prev]');
28  const nextBtn = viewer.querySelector('[data-gallery-next]');
29  const fullscreenBtn = viewer.querySelector('[data-gallery-fullscreen]');
30  const lightbox = viewer.querySelector('[data-gallery-lightbox]');
31  const lightboxContent = viewer.querySelector('[data-gallery-lightbox-content]');
32  const lightboxCloseBtn = viewer.querySelector('[data-gallery-lightbox-close]');
33  const onePageMobileLayoutQuery = window.matchMedia(MOBILE_BREAKPOINT_QUERY);
34  const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
35
36  if (!track || originalSlides.length === 0) return;
37
38  const slideCount = originalSlides.length;
39  const createClone = (slide) => {
40    const clone = slide.cloneNode(true);
41    clone.setAttribute('aria-hidden', 'true');
42    clone.dataset.galleryClone = TRUE_DATA_VALUE;
43    clone.querySelectorAll('img').forEach((image) => {
44      image.loading = 'lazy';
45      image.setAttribute('loading', 'lazy');
46      image.fetchPriority = 'auto';
47    });
48    return clone;
49  };
50
51  if (slideCount > 1) {
52    track.prepend(createClone(originalSlides[slideCount - 1]));
53    track.append(createClone(originalSlides[0]));
54  }
55
56  let currentIndex = 0;
57  let currentTrackIndex = slideCount > 1 ? 1 : 0;
58  let isAnimating = false;
59  let pendingTransition = null;
60  let resizeFrameId = 0;
61  let scrollSyncFrameId = 0;
62  let embedMetricFrameId = 0;
63  let facebookEmbedFrameId = 0;
64  let wasOnePageMobileLayoutActive = isOnePageMobileLayoutActive();
65  let lightboxSlideState = null;
66  let lastTrackedUpScoreIndex = null;
67  let navigationCount = 0;
68  let advertReloadNavigationInterval = DEFAULT_ADVERT_RELOAD_NAVIGATION_INTERVAL;
69  const facebookEmbeds = createFacebookEmbedController(originalSlides);
70  const lazyAdLoader = createLazyAdLoader({
71    owner: viewer,
72    loadAdvert(containerElement) {
73      if (typeof window.burdaAds?.addAdvert !== 'function') {
74        return;
75      }
76
77      window.burdaAds.addAdvert({containerElement});
78    }
79  });
80  const embedMutationObserver = typeof window.MutationObserver === 'function'
81    ? new MutationObserver(scheduleCurrentEmbedMetricSync)
82    : null;
83  const embedResizeObserver = typeof window.ResizeObserver === 'function'
84    ? new ResizeObserver(scheduleCurrentEmbedMetricSync)
85    : null;
86
87  function isOnePageMobileLayoutActive() {
88    return supportsOnePageMobileLayout && onePageMobileLayoutQuery.matches;
89  }
90
91  function resolveAdvertReloadNavigationInterval() {
92    const burdaAdsQueue = window.burdaAds?.queue;
93    if (typeof burdaAdsQueue?.push !== 'function') {
94      return;
95    }
96
97    burdaAdsQueue.push(async () => {
98      if (typeof window.burdaAds?.getGalleryConfig !== 'function') {
99        return;
100      }
101
102      try {
103        const galleryConfig = await window.burdaAds.getGalleryConfig();
104
105        const reloadAllFrequency = galleryConfig?.reloadAllFrequency;
106
107        if (Number.isInteger(reloadAllFrequency) && reloadAllFrequency >= 0) {
108          advertReloadNavigationInterval = reloadAllFrequency;
109        }
110      } catch {
111        // Keep the fallback frequency when the BurdaAds configuration cannot be resolved.
112      }
113    });
114  }
115
116  function reloadAdvertsAfterNavigationInterval() {
117    navigationCount++;
118
119    if (
120      advertReloadNavigationInterval === 0
121      || navigationCount % advertReloadNavigationInterval !== 0
122    ) {
123      return;
124    }
125
126    const burdaAdsQueue = window.burdaAds?.queue;
127    if (typeof burdaAdsQueue?.push !== 'function') {
128      return;
129    }
130
131    burdaAdsQueue.push(() => {
132      if (typeof window.burdaAds?.reloadAdverts === 'function') {
133        window.burdaAds.reloadAdverts();
134      }
135    });
136  }
137
138  function isUpScoreReady() {
139    return window.upScoreInitialized === true && typeof window.upScore === 'function';
140  }
141
142  function trackUpScoreGallerySlide(index) {
143    if (!Number.isInteger(index) || index < 0 || index >= slideCount || lastTrackedUpScoreIndex === index) {
144      return;
145    }
146
147    if (!isUpScoreReady()) {
148      return;
149    }
150
151    window.upScore('gallery_view', {
152      image: index + 1
153    });
154    lastTrackedUpScoreIndex = index;
155  }
156
157  function isLightboxOpen() {
158    return Boolean(lightbox) && lightbox.hidden === false;
159  }
160
161  function supportsLightbox(index) {
162    return originalSlides[index]?.dataset.gallerySupportsLightbox === TRUE_DATA_VALUE;
163  }
164
165  function isEmbedSlide(index) {
166    return !supportsLightbox(index) && Boolean(originalSlides[index]?.querySelector(EMBED_SELECTOR));
167  }
168
169  function requiresProviderEmbedProcessing(index) {
170    return originalSlides[index]?.dataset.galleryRequiresSocialEmbed === TRUE_DATA_VALUE;
171  }
172
173  function getEmbedProvider(index) {
174    return originalSlides[index]?.dataset.galleryEmbedProvider || '';
175  }
176
177  function prefersReducedMotion() {
178    return reducedMotionQuery.matches;
179  }
180
181  function clearStageMetrics() {
182    if (!stage) {
183      return;
184    }
185
186    stage.style.removeProperty('--gallery-stage-width');
187    stage.style.removeProperty('--gallery-stage-height');
188    stage.style.removeProperty('--gallery-stage-rendered-height');
189  }
190
191  function getSlideDimensions(index) {
192    return {
193      width: parsePositiveInteger(originalSlides[index]?.dataset.galleryWidth, 1),
194      height: parsePositiveInteger(originalSlides[index]?.dataset.galleryHeight, 1)
195    };
196  }
197
198  function getMeasuredEmbedHeight(index) {
199    if (!isEmbedSlide(index)) {
200      return 0;
201    }
202
203    const embed = originalSlides[index]?.querySelector(EMBED_SELECTOR);
204    const embedContent = embed?.querySelector(EMBED_CONTENT_SELECTOR);
205    if (!embed) {
206      return 0;
207    }
208
209    const measuredHeight = Math.max(
210      embed.getBoundingClientRect().height,
211      embed.scrollHeight,
212      embedContent?.getBoundingClientRect().height || 0,
213      embedContent?.scrollHeight || 0
214    );
215
216    return Number.isFinite(measuredHeight) && measuredHeight > 0 ? measuredHeight : 0;
217  }
218
219  function syncEmbedLayout(index) {
220    if (!isEmbedSlide(index)) {
221      return;
222    }
223
224    syncStageMetrics(index);
225  }
226
227  function getAvailableSlideWidth() {
228    return imageContainer?.getBoundingClientRect().width
229      || main?.getBoundingClientRect().width
230      || stage?.getBoundingClientRect().width
231      || viewer.getBoundingClientRect().width
232      || 1;
233  }
234
235  function getImageStageMetrics(index) {
236    const {width: intrinsicWidth, height: intrinsicHeight} = getSlideDimensions(index);
237    const availableWidth = getAvailableSlideWidth();
238
239    return {
240      intrinsicWidth,
241      intrinsicHeight,
242      renderedHeight: availableWidth * intrinsicHeight / intrinsicWidth
243    };
244  }
245
246  function getEmbedStageMetrics(index) {
247    const availableWidth = getAvailableSlideWidth();
248    const measuredHeight = getMeasuredEmbedHeight(index);
249
250    return {
251      intrinsicWidth: availableWidth,
252      intrinsicHeight: Math.max(Math.round(measuredHeight), 1),
253      renderedHeight: Math.max(measuredHeight, 1)
254    };
255  }
256
257  function syncStageMetrics(index) {
258    if (!stage || !originalSlides[index]) {
259      return;
260    }
261
262    if (isOnePageMobileLayoutActive()) {
263      clearStageMetrics();
264      return;
265    }
266
267    const metrics = isEmbedSlide(index) ? getEmbedStageMetrics(index) : getImageStageMetrics(index);
268    stage.style.setProperty('--gallery-stage-width', String(metrics.intrinsicWidth));
269    stage.style.setProperty('--gallery-stage-height', String(metrics.intrinsicHeight));
270    stage.style.setProperty('--gallery-stage-rendered-height', `${metrics.renderedHeight}px`);
271  }
272
273  function scheduleCurrentEmbedMetricSync() {
274    window.cancelAnimationFrame(embedMetricFrameId);
275    embedMetricFrameId = window.requestAnimationFrame(() => {
276      syncEmbedLayout(currentIndex);
277    });
278  }
279
280  function observeSocialEmbeds() {
281    if (!embedMutationObserver && !embedResizeObserver) {
282      return;
283    }
284
285    originalSlides.forEach((slide, index) => {
286      if (!isEmbedSlide(index)) {
287        return;
288      }
289
290      const embed = slide.querySelector(EMBED_SELECTOR);
291      const embedContent = slide.querySelector(EMBED_CONTENT_SELECTOR);
292
293      if (embedMutationObserver) {
294        embedMutationObserver.observe(slide, {
295          attributes: true,
296          childList: true,
297          subtree: true,
298          attributeFilter: ['class', 'height', 'style']
299        });
300      }
301
302      if (embedResizeObserver) {
303        if (embed) {
304          embedResizeObserver.observe(embed);
305        }
306
307        if (embedContent) {
308          embedResizeObserver.observe(embedContent);
309        }
310      }
311    });
312  }
313
314  function setTrackTransformWithoutTransition(transformValue) {
315    track.classList.add(TRACK_NO_TRANSITION_CLASS);
316    track.style.transform = transformValue;
317    void track.offsetWidth;
318    track.classList.remove(TRACK_NO_TRANSITION_CLASS);
319  }
320
321  function setTrackPosition(trackIndex, animate) {
322    currentTrackIndex = trackIndex;
323
324    if (isOnePageMobileLayoutActive()) {
325      setTrackTransformWithoutTransition('');
326      return;
327    }
328
329    const offset = getAvailableSlideWidth() * trackIndex;
330    const transformValue = `translate3d(${-offset}px, 0, 0)`;
331
332    if (!animate || prefersReducedMotion()) {
333      setTrackTransformWithoutTransition(transformValue);
334      return;
335    }
336
337    track.style.transform = transformValue;
338  }
339
340  function getRealTrackIndex(index) {
341    return slideCount > 1 ? index + 1 : index;
342  }
343
344  function syncFullscreenAvailability(index) {
345    if (!fullscreenBtn) {
346      return;
347    }
348
349    fullscreenBtn.hidden = isOnePageMobileLayoutActive() || !supportsLightbox(index);
350  }
351
352  function processProviderEmbed(index) {
353    if (!requiresProviderEmbedProcessing(index)) {
354      return;
355    }
356
357    processSocialEmbedProvider(getEmbedProvider(index));
358  }
359
360  function refreshEmbed(index) {
361    if (!isEmbedSlide(index)) {
362      return;
363    }
364
365    processProviderEmbed(index);
366    syncEmbedLayout(index);
367
368    EMBED_REFRESH_DELAYS_MS.forEach((delay) => {
369      window.setTimeout(() => {
370        if (currentIndex !== index) {
371          return;
372        }
373
374        processProviderEmbed(index);
375        syncEmbedLayout(index);
376      }, delay);
377    });
378  }
379
380  function initializeFacebookEmbeds() {
381    window.cancelAnimationFrame(facebookEmbedFrameId);
382    facebookEmbedFrameId = window.requestAnimationFrame(() => {
383      facebookEmbedFrameId = window.requestAnimationFrame(() => {
384        facebookEmbeds.initializeAll();
385      });
386    });
387  }
388
389  function setActiveInfo(index) {
390    infoSlides.forEach((info, infoIndex) => {
391      info.classList.toggle('gallery-viewer__info-slide--active', infoIndex === index);
392    });
393  }
394
395  function setDisplayedSlide(index) {
396    const isOnePageMobileLayout = isOnePageMobileLayoutActive();
397
398    originalSlides.forEach((slide, slideIndex) => {
399      slide.setAttribute('aria-hidden', isOnePageMobileLayout || slideIndex === index ? 'false' : 'true');
400    });
401  }
402
403  function restoreLightboxSlide() {
404    if (!lightboxSlideState) {
405      return;
406    }
407
408    const {slide, placeholder} = lightboxSlideState;
409    slide.removeAttribute('data-gallery-lightbox-active');
410
411    if (placeholder.parentNode) {
412      placeholder.parentNode.insertBefore(slide, placeholder);
413      placeholder.remove();
414    }
415
416    lightboxSlideState = null;
417  }
418
419  function moveSlideToLightbox(index) {
420    const slide = originalSlides[index];
421    const slideParent = slide?.parentNode;
422    if (!lightboxContent || !slide || !slideParent) {
423      return;
424    }
425
426    if (lightboxSlideState?.slide === slide) {
427      return;
428    }
429
430    restoreLightboxSlide();
431
432    const {width, height} = getSlideDimensions(index);
433    lightboxContent.style.setProperty('--gallery-lightbox-width', String(width));
434    lightboxContent.style.setProperty('--gallery-lightbox-height', String(height));
435
436    const placeholder = document.createComment('gallery lightbox slide placeholder');
437    slideParent.insertBefore(placeholder, slide);
438    slide.dataset.galleryLightboxActive = TRUE_DATA_VALUE;
439    slide.setAttribute('aria-hidden', 'false');
440    lightboxContent.append(slide);
441    lightboxSlideState = {slide, placeholder};
442  }
443
444  function syncLightboxSlide(index) {
445    if (isLightboxOpen()) {
446      moveSlideToLightbox(index);
447    }
448  }
449
450  function openLightbox() {
451    if (!lightbox || isAnimating || isOnePageMobileLayoutActive() || !supportsLightbox(currentIndex)) {
452      return;
453    }
454
455    moveSlideToLightbox(currentIndex);
456    lightbox.hidden = false;
457    document.body.classList.add('gallery-lightbox-open');
458    lightboxCloseBtn?.focus();
459  }
460
461  function closeLightbox() {
462    if (!lightbox) {
463      return;
464    }
465
466    lightbox.hidden = true;
467    restoreLightboxSlide();
468    setDisplayedSlide(currentIndex);
469    setTrackPosition(getRealTrackIndex(currentIndex), false);
470    document.body.classList.remove('gallery-lightbox-open');
471    if (fullscreenBtn && !fullscreenBtn.hidden) {
472      fullscreenBtn.focus();
473    }
474  }
475
476  function isAdjacentTransition(fromIndex, targetIndex) {
477    if (slideCount <= 1) {
478      return false;
479    }
480
481    return targetIndex === (fromIndex + 1) % slideCount
482      || targetIndex === (fromIndex - 1 + slideCount) % slideCount;
483  }
484
485  function resolveDirection(fromIndex, targetIndex) {
486    const forwardSteps = (targetIndex - fromIndex + slideCount) % slideCount;
487    const backwardSteps = (fromIndex - targetIndex + slideCount) % slideCount;
488
489    return forwardSteps <= backwardSteps ? 'next' : 'prev';
490  }
491
492  function applyCurrentSlideState(index, options = {}) {
493    const {updateHashValue = false} = options;
494
495    setDisplayedSlide(index);
496    setActiveInfo(index);
497    syncLightboxSlide(index);
498    syncFullscreenAvailability(index);
499
500    refreshEmbed(index);
501
502    if (updateHashValue) {
503      replaceGalleryHash(index);
504    }
505
506    trackUpScoreGallerySlide(index);
507  }
508
509  function finalizeSlideChange(index) {
510    applyCurrentSlideState(index);
511    setTrackPosition(getRealTrackIndex(index), false);
512  }
513
514  function flushPendingTransition() {
515    if (!pendingTransition) {
516      return;
517    }
518
519    const nextTransition = pendingTransition;
520    pendingTransition = null;
521    showSlide(nextTransition.index, nextTransition.options);
522  }
523
524  function getAnimatedTrackIndex(fromIndex, targetIndex, direction) {
525    if (slideCount <= 1) {
526      return 0;
527    }
528
529    if (direction === 'next' && fromIndex === slideCount - 1 && targetIndex === 0) {
530      return slideCount + 1;
531    }
532
533    if (direction === 'prev' && fromIndex === 0 && targetIndex === slideCount - 1) {
534      return 0;
535    }
536
537    return getRealTrackIndex(targetIndex);
538  }
539
540  function animateSlideChange(targetIndex, trackIndex) {
541    let isFinished = false;
542
543    const finish = () => {
544      if (isFinished) {
545        return;
546      }
547
548      isFinished = true;
549      track.removeEventListener('transitionend', handleTransitionEnd);
550      finalizeSlideChange(targetIndex);
551      isAnimating = false;
552      flushPendingTransition();
553    };
554
555    const handleTransitionEnd = (event) => {
556      if (event.target === track && event.propertyName === 'transform') {
557        finish();
558      }
559    };
560
561    track.addEventListener('transitionend', handleTransitionEnd);
562    setTrackPosition(trackIndex, true);
563    window.setTimeout(finish, ANIMATION_DURATION_MS + 80);
564  }
565
566  function scrollToOnePageSlide(index) {
567    originalSlides[index]?.scrollIntoView({
568      behavior: 'auto',
569      block: 'start'
570    });
571  }
572
573  function getMostVisibleOnePageSlideIndex() {
574    const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
575    const viewportCenter = viewportHeight / 2;
576    let bestIndex = null;
577    let bestDistance = Number.POSITIVE_INFINITY;
578
579    originalSlides.forEach((slide, index) => {
580      const rect = slide.getBoundingClientRect();
581      if (rect.bottom <= 0 || rect.top >= viewportHeight) {
582        return;
583      }
584
585      const distanceFromViewportCenter = Math.abs((rect.top + (rect.height / 2)) - viewportCenter);
586      if (distanceFromViewportCenter < bestDistance) {
587        bestDistance = distanceFromViewportCenter;
588        bestIndex = index;
589      }
590    });
591
592    return bestIndex;
593  }
594
595  function syncCurrentSlideFromScroll() {
596    if (!isOnePageMobileLayoutActive()) {
597      return;
598    }
599
600    const visibleIndex = getMostVisibleOnePageSlideIndex();
601    if (visibleIndex === null || visibleIndex === currentIndex) {
602      return;
603    }
604
605    currentIndex = visibleIndex;
606    applyCurrentSlideState(visibleIndex, {updateHashValue: true});
607  }
608
609  function scheduleOnePageScrollSync() {
610    if (!isOnePageMobileLayoutActive()) {
611      return;
612    }
613
614    window.cancelAnimationFrame(scrollSyncFrameId);
615    scrollSyncFrameId = window.requestAnimationFrame(syncCurrentSlideFromScroll);
616  }
617
618  function prepareOnePageLazyAds() {
619    if (!supportsOnePageMobileLayout) {
620      return;
621    }
622
623    lazyAdLoader.prepare();
624  }
625
626  function applyOnePageMobileSlideChange(index, options = {}) {
627    const {
628      updateHashValue = true,
629      scrollIntoView = false
630    } = options;
631
632    if (!supportsLightbox(index) && isLightboxOpen()) {
633      closeLightbox();
634    }
635
636    syncStageMetrics(index);
637    applyCurrentSlideState(index, {updateHashValue});
638
639    if (scrollIntoView) {
640      scrollToOnePageSlide(index);
641    }
642  }
643
644  function showSlide(index, options = {}) {
645    if (!originalSlides[index]) {
646      return;
647    }
648
649    const {
650      direction = resolveDirection(currentIndex, index),
651      animate = true,
652      updateHashValue = true,
653      scrollIntoView = false
654    } = options;
655
656    const previousIndex = currentIndex;
657    currentIndex = index;
658
659    if (isOnePageMobileLayoutActive()) {
660      applyOnePageMobileSlideChange(index, {updateHashValue, scrollIntoView});
661      return;
662    }
663
664    if (updateHashValue) {
665      replaceGalleryHash(index);
666    }
667
668    if (isAnimating) {
669      pendingTransition = {
670        index,
671        options: {
672          direction,
673          animate,
674          updateHashValue: false,
675          scrollIntoView: false
676        }
677      };
678      return;
679    }
680
681    syncStageMetrics(index);
682    syncFullscreenAvailability(index);
683    syncEmbedLayout(index);
684
685    if (!supportsLightbox(index) && isLightboxOpen()) {
686      closeLightbox();
687    }
688
689    const shouldAnimate = animate
690      && !prefersReducedMotion()
691      && index !== previousIndex
692      && isAdjacentTransition(previousIndex, index);
693
694    if (!shouldAnimate) {
695      finalizeSlideChange(index);
696      return;
697    }
698
699    isAnimating = true;
700    animateSlideChange(index, getAnimatedTrackIndex(previousIndex, index, direction));
701  }
702
703  if (prevBtn) {
704    prevBtn.addEventListener('click', () => {
705      if (isOnePageMobileLayoutActive()) {
706        return;
707      }
708
709      const newIndex = currentIndex > 0 ? currentIndex - 1 : slideCount - 1;
710      reloadAdvertsAfterNavigationInterval();
711      showSlide(newIndex, {direction: 'prev'});
712    });
713  }
714
715  if (nextBtn) {
716    nextBtn.addEventListener('click', () => {
717      if (isOnePageMobileLayoutActive()) {
718        return;
719      }
720
721      const newIndex = currentIndex < slideCount - 1 ? currentIndex + 1 : 0;
722      reloadAdvertsAfterNavigationInterval();
723      showSlide(newIndex, {direction: 'next'});
724    });
725  }
726
727  fullscreenBtn?.addEventListener('click', openLightbox);
728  lightboxCloseBtn?.addEventListener('click', closeLightbox);
729  lightbox?.addEventListener('click', (e) => {
730    if (e.target === lightbox) {
731      closeLightbox();
732    }
733  });
734
735  // Keyboard navigation
736  document.addEventListener('keydown', (e) => {
737    if (e.key === 'Escape' && isLightboxOpen()) {
738      closeLightbox();
739      return;
740    }
741
742    if (isLightboxOpen()) {
743      return;
744    }
745
746    if (isOnePageMobileLayoutActive()) {
747      return;
748    }
749
750    if (e.key === 'ArrowLeft' && prevBtn) {
751      prevBtn.click();
752    } else if (e.key === 'ArrowRight' && nextBtn) {
753      nextBtn.click();
754    }
755  });
756
757  const initialIndex = getGalleryIndexFromHash(slideCount);
758  resolveAdvertReloadNavigationInterval();
759  showSlide(initialIndex ?? 0, {
760    animate: false,
761    updateHashValue: false,
762    scrollIntoView: initialIndex !== null && isOnePageMobileLayoutActive()
763  });
764  viewer.classList.add('gallery-viewer--initialized');
765  document.documentElement.classList.remove('gallery-fragment-pending');
766
767  if (isOnePageMobileLayoutActive()) {
768    scheduleOnePageScrollSync();
769  }
770
771  trackUpScoreGallerySlide(currentIndex);
772  document.addEventListener(UPSCORE_INITIALIZED_EVENT, () => {
773    trackUpScoreGallerySlide(currentIndex);
774  });
775
776  prepareOnePageLazyAds();
777  observeSocialEmbeds();
778  initializeFacebookEmbeds();
779
780  window.addEventListener('hashchange', () => {
781    const hashIndex = getGalleryIndexFromHash(slideCount);
782    if (hashIndex === null) {
783      return;
784    }
785
786    if (isOnePageMobileLayoutActive()) {
787      showSlide(hashIndex, {
788        animate: false,
789        updateHashValue: false,
790        scrollIntoView: true
791      });
792      return;
793    }
794
795    if (hashIndex !== currentIndex) {
796      showSlide(hashIndex, {
797        animate: true,
798        direction: resolveDirection(currentIndex, hashIndex),
799        updateHashValue: false
800      });
801    }
802  });
803
804  window.addEventListener('scroll', () => {
805    scheduleOnePageScrollSync();
806  }, {passive: true});
807
808  window.addEventListener('resize', () => {
809    window.cancelAnimationFrame(resizeFrameId);
810    resizeFrameId = window.requestAnimationFrame(() => {
811      if (isOnePageMobileLayoutActive()) {
812        setTrackPosition(getRealTrackIndex(currentIndex), false);
813        scheduleOnePageScrollSync();
814        return;
815      }
816
817      syncStageMetrics(currentIndex);
818      setTrackPosition(currentTrackIndex, false);
819    });
820  });
821
822  const handleOnePageMobileLayoutChange = () => {
823    const isOnePageMobileLayoutNowActive = isOnePageMobileLayoutActive();
824    if (isOnePageMobileLayoutNowActive === wasOnePageMobileLayoutActive) {
825      return;
826    }
827
828    wasOnePageMobileLayoutActive = isOnePageMobileLayoutNowActive;
829    facebookEmbeds.resetAll();
830
831    if (isLightboxOpen()) {
832      closeLightbox();
833    }
834
835    const targetIndex = getGalleryIndexFromHash(slideCount) ?? currentIndex;
836    showSlide(targetIndex, {
837      animate: false,
838      updateHashValue: false,
839      scrollIntoView: isOnePageMobileLayoutNowActive
840    });
841    initializeFacebookEmbeds();
842  };
843
844  if (typeof onePageMobileLayoutQuery.addEventListener === 'function') {
845    onePageMobileLayoutQuery.addEventListener('change', handleOnePageMobileLayoutChange);
846  } else {
847    onePageMobileLayoutQuery.addListener(handleOnePageMobileLayoutChange);
848  }
849}

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.