PageSourceSearch

https://www.rubyrosemaquiagem.com.br/common/js/shortVideo.js?=v2

js rubyrosemaquiagem.com.br collected 2026-09-26 12:08:44 UTC 32,402 bytes, 1,009 lines download raw bytes

1var shortVideo = [];
2
3shortVideo.init = () => {
4  const container = utilsSpeed.getCachedElement(".short-video");
5  if (!container) return;
6
7  const carousel = utilsSpeed.safeQuerySelector(container, ".short-video__container");
8  if (!carousel) return;
9
10  const items = utilsSpeed.getCachedElements('.short-video__item', carousel);
11
12  if (!items?.length) {
13    return;
14  }
15
16  shortVideo.initializeCarousel(container, carousel, items);
17
18  items.forEach(item => {
19    const videoEl = utilsSpeed.safeQuerySelector(item, '.short-video__overflow-container');
20    if (videoEl) {
21      videoEl.addEventListener('click', (e) => shortVideo.openGallery(e, item));
22    }
23  });
24};
25
26shortVideo.initializeCarousel = (container, carousel, items) => {
27  const totalItems = items.length;
28  const containerWidth = container.clientWidth;
29  
30  const rawItemWidth = items[0].offsetWidth || 210;
31  const rawItemHeight = items[0].offsetHeight || 380;
32  const itemMargin = 0;
33  const itemWidth = rawItemWidth + itemMargin; 
34  const totalWidth = itemWidth * totalItems;
35
36
37  const allVideosData = [];
38  items.forEach(item => {
39    allVideosData.push({
40      productId: item.getAttribute('data-short-video-product-id') || '',
41      videoSrc: item.getAttribute('data-short-video-source') || '',
42      productName: item.getAttribute('data-short-video-product-name') || '',
43      productUrl: item.getAttribute('data-short-video-url') || '#',
44      productPrice: item.getAttribute('data-short-video-product-price') || '',
45      productImage: item.getAttribute('data-short-video-product-image') || '',
46      borderColor: item.getAttribute('data-short-video-border-color') || '',
47    });
48  });
49  shortVideo.cachedVideoList = allVideosData;
50
51  const isCircleStyle = items[0].classList.contains('short-video__circle-item');
52
53  if (isCircleStyle && totalWidth <= containerWidth) {
54    carousel.style.justifyContent = 'center';
55    items.forEach(item => {
56      const videoEl = utilsSpeed.safeQuerySelector(item, 'video');
57      if (videoEl) {
58        const img = item.querySelector('.short-video__image');
59        if (img) {
60          videoEl.addEventListener('playing', () => { img.style.display = 'none'; }, { once: true });
61        }
62        videoEl.preload = 'auto';
63        videoEl.play();
64      }
65    });
66    return;
67  }
68
69  carousel.innerHTML = '';
70  carousel.style.justifyContent = 'flex-start';
71  carousel.style.position = 'relative';
72  carousel.style.overflow = 'hidden';
73  carousel.style.height = rawItemHeight + 'px';
74  
75  const track = document.createElement('div');
76  track.className = 'short-video-carousel-track';
77  track.style.position = 'absolute';
78  track.style.height = '100%';
79  track.style.width = '100%';
80  track.style.display = 'flex';
81  track.style.left = '0';
82  carousel.appendChild(track);
83
84
85  const safeItemWidth = itemWidth > 0 ? itemWidth : 230;
86  
87  let visibleCount = Math.ceil(containerWidth / safeItemWidth);
88  if (visibleCount % 2 === 0) visibleCount += 1;
89  visibleCount += 2;
90  if (visibleCount % 2 === 0) visibleCount += 1;
91
92  const centerIndex = Math.floor(visibleCount / 2);
93  const startOffset = (containerWidth / 2) - (centerIndex * safeItemWidth) - (safeItemWidth / 2);
94
95  const slots = [];
96  
97
98  for (let i = 0; i < visibleCount; i++) {
99    const slot = document.createElement('div');
100    slot.className = 'short-video-carousel-item';
101    slot.style.position = 'absolute';
102    slot.style.width = rawItemWidth + 'px';
103    slot.style.height = '100%';
104    slot.style.left = (startOffset + (i * safeItemWidth)) + 'px';
105    track.appendChild(slot);
106    slots.push(slot);
107  }
108
109
110  const carouselData = [];
111  items.forEach((item, index) => {
112    carouselData.push({
113      html: item.innerHTML,
114      isRect: item.classList.contains('short-video__rect-item'),
115      classes: item.className,
116      attributes: allVideosData[index]
117    });
118  });
119
120
121  let currentIndex = 0;
122  let isAnimating = false;
123  
124  const render = (skipAnimation = false) => {
125    slots.forEach((slot, i) => {
126      const dataIndex = (currentIndex + i) % totalItems;
127      const itemData = carouselData[dataIndex < 0 ? dataIndex + totalItems : dataIndex];
128      
129      const currentVideoSrc = slot.getAttribute('data-short-video-source');
130      const needsUpdate = currentVideoSrc !== itemData.attributes.videoSrc;
131      
132      if (needsUpdate || !slot.hasChildNodes()) {
133        slot.innerHTML = itemData.html;
134        slot.className = 'short-video-carousel-item ' + itemData.classes;
135        slot.style.position = 'absolute';
136        slot.style.width = rawItemWidth + 'px';
137        slot.style.height = '100%';
138        slot.style.margin = '0';
139        slot.setAttribute('data-short-video-source', itemData.attributes.videoSrc);
140        slot.setAttribute('data-short-video-product-name', itemData.attributes.productName);
141        slot.setAttribute('data-short-video-url', itemData.attributes.productUrl);
142        slot.setAttribute('data-short-video-product-price', itemData.attributes.productPrice);
143        slot.setAttribute('data-short-video-product-image', itemData.attributes.productImage);
144
145        const vid = slot.querySelector('video');
146        if (vid) {
147          const dist = Math.abs(i - Math.floor(visibleCount / 2));
148          vid.preload = dist === 0 ? 'auto' : dist <= 3 ? 'metadata' : 'none';
149
150          if (!itemData.isRect) {
151            const img = slot.querySelector('.short-video__image');
152            if (img) {
153              vid.addEventListener('playing', () => { img.style.display = 'none'; }, { once: true });
154            }
155          }
156        }
157      }
158      
159      if (itemData.isRect) {
160        const centerOffset = Math.floor(visibleCount / 2);
161        const isCenter = i === centerOffset;
162        
163        if (isCenter) {
164            slot.classList.add('is-center');
165            const video = slot.querySelector('video');
166            if (video) video.play().catch(() => {});
167        } else {
168            slot.classList.remove('is-center');
169            const video = slot.querySelector('video');
170            if (video) {
171                video.pause();
172                video.currentTime = 0;
173            }
174        }
175      } else {
176        const video = slot.querySelector('video');
177        if (video) video.play().catch(() => {});
178      }
179    });
180  };
181
182  render();
183
184  const controls = document.createElement('div');
185  controls.className = 'short-video-controls';
186  controls.innerHTML = `
187    <button class="short-video-nav prev" aria-label="Previous"></button>
188    <button class="short-video-nav next" aria-label="Next"></button>
189  `;
190  container.appendChild(controls);
191
192  controls.querySelector('.prev').addEventListener('click', () => {
193    if (isAnimating) return;
194    isAnimating = true;
195    
196    currentIndex--;
197    if (currentIndex < 0) currentIndex = totalItems - 1;
198    
199    track.style.transition = 'transform 0.3s ease';
200    track.style.transform = `translateX(${itemWidth}px)`;
201    
202    setTimeout(() => {
203      const lastSlotIndex = slots.length - 1;
204      const newDataIndex = (currentIndex) % totalItems;
205      const itemData = carouselData[newDataIndex < 0 ? newDataIndex + totalItems : newDataIndex];
206      
207      const lastSlot = slots[lastSlotIndex];
208      
209      lastSlot.innerHTML = itemData.html;
210      lastSlot.className = 'short-video-carousel-item ' + itemData.classes;
211      lastSlot.style.position = 'absolute';
212      lastSlot.style.width = rawItemWidth + 'px';
213      lastSlot.style.height = '100%';
214      lastSlot.style.margin = '0';
215
216      lastSlot.setAttribute('data-short-video-source', itemData.attributes.videoSrc);
217      lastSlot.setAttribute('data-short-video-product-name', itemData.attributes.productName);
218      lastSlot.setAttribute('data-short-video-url', itemData.attributes.productUrl);
219      lastSlot.setAttribute('data-short-video-product-price', itemData.attributes.productPrice);
220      lastSlot.setAttribute('data-short-video-product-image', itemData.attributes.productImage);
221
222      if (!itemData.isRect) {
223        const vidNew = lastSlot.querySelector('video');
224        const imgNew = lastSlot.querySelector('.short-video__image');
225        if (vidNew && imgNew) {
226          vidNew.addEventListener('playing', () => { imgNew.style.display = 'none'; }, { once: true });
227        }
228      }
229
230      slots.pop();
231      slots.unshift(lastSlot);
232      
233      slots.forEach((slot, i) => {
234        slot.style.left = (startOffset + (i * safeItemWidth)) + 'px';
235      });
236      
237      track.style.transition = 'none';
238      track.style.transform = 'translateX(0)';
239      
240      requestAnimationFrame(() => {
241        updateHighlightStates();
242        isAnimating = false;
243      });
244    }, 300);
245  });
246
247  controls.querySelector('.next').addEventListener('click', () => {
248    if (isAnimating) return;
249    isAnimating = true;
250    
251    currentIndex++;
252    if (currentIndex >= totalItems) currentIndex = 0;
253    
254    track.style.transition = 'transform 0.3s ease';
255    track.style.transform = `translateX(-${itemWidth}px)`;
256    
257    setTimeout(() => {
258      const newDataIndex = (currentIndex + slots.length - 1) % totalItems;
259      const itemData = carouselData[newDataIndex < 0 ? newDataIndex + totalItems : newDataIndex];
260      
261      const firstSlot = slots[0];
262
263      firstSlot.innerHTML = itemData.html;
264      firstSlot.className = 'short-video-carousel-item ' + itemData.classes;
265      firstSlot.style.position = 'absolute';
266      firstSlot.style.width = rawItemWidth + 'px';
267      firstSlot.style.height = '100%';
268      firstSlot.style.margin = '0';
269
270      firstSlot.setAttribute('data-short-video-source', itemData.attributes.videoSrc);
271      firstSlot.setAttribute('data-short-video-product-name', itemData.attributes.productName);
272      firstSlot.setAttribute('data-short-video-url', itemData.attributes.productUrl);
273      firstSlot.setAttribute('data-short-video-product-price', itemData.attributes.productPrice);
274      firstSlot.setAttribute('data-short-video-product-image', itemData.attributes.productImage);
275
276      if (!itemData.isRect) {
277        const vidNew = firstSlot.querySelector('video');
278        const imgNew = firstSlot.querySelector('.short-video__image');
279        if (vidNew && imgNew) {
280          vidNew.addEventListener('playing', () => { imgNew.style.display = 'none'; }, { once: true });
281        }
282      }
283
284      slots.shift();
285      slots.push(firstSlot);
286      
287      slots.forEach((slot, i) => {
288        slot.style.left = (startOffset + (i * safeItemWidth)) + 'px';
289      });
290      
291      track.style.transition = 'none';
292      track.style.transform = 'translateX(0)';
293      
294      requestAnimationFrame(() => {
295        updateHighlightStates();
296        isAnimating = false;
297      });
298    }, 300);
299  });
300
301  const updateHighlightStates = () => {
302    const centerOffset = Math.floor(visibleCount / 2);
303    
304    const updates = [];
305    
306    slots.forEach((slot, i) => {
307      const isRect = slot.classList.contains('short-video__rect-item');
308      const dist = Math.abs(i - centerOffset);
309      if (isRect) {
310        const isCenter = i === centerOffset;
311        const video = slot.querySelector('video');
312        updates.push({ slot, isCenter, video, isRect: true, dist });
313      } else {
314        const video = slot.querySelector('video');
315        updates.push({ slot, video, isRect: false, dist });
316      }
317    });
318
319    updates.forEach(({ slot, isCenter, video, isRect, dist }) => {
320      if (isRect) {
321        if (isCenter) {
322          slot.classList.add('is-center');
323          if (video) video.preload = 'auto';
324        } else {
325          slot.classList.remove('is-center');
326          if (video) video.preload = dist <= 3 ? 'metadata' : 'none';
327        }
328      }
329    });
330    setTimeout(() => {
331      updates.forEach(({ isCenter, video, isRect }) => {
332        if (!video) return;
333        if (isRect) {
334          if (isCenter) {
335            video.play().catch(() => {});
336          } else {
337            video.pause();
338            video.currentTime = 0;
339          }
340        } else {
341          video.play().catch(() => {});
342        }
343      });
344    }, 50);
345  };
346
347  let lastScrollTime = 0;
348  const scrollCooldown = 500;
349
350  container.addEventListener('wheel', (e) => {
351    if (Math.abs(e.deltaX) > 10) {
352        e.preventDefault();
353        
354        const now = Date.now();
355        if (now - lastScrollTime < scrollCooldown) return;
356
357        if (e.deltaX > 0) {
358            controls.querySelector('.next').click();
359        } else {
360            controls.querySelector('.prev').click();
361        }
362        lastScrollTime = now;
363    }
364  }, { passive: false });
365
366  let isDragging = false;
367  let startX = 0;
368  let dragThreshold = 50;
369
370  container.addEventListener('mousedown', (e) => {
371    if (e.target.closest('.short-video-nav') || e.target.closest('video')) return;
372    isDragging = true;
373    startX = e.clientX;
374    container.style.cursor = 'grabbing';
375  });
376
377  container.addEventListener('mousemove', (e) => {
378    if (!isDragging) return;
379    e.preventDefault();
380  });
381
382  container.addEventListener('mouseup', (e) => {
383    if (!isDragging) return;
384    isDragging = false;
385    container.style.cursor = 'grab';
386    
387    const deltaX = e.clientX - startX;
388    if (Math.abs(deltaX) > dragThreshold) {
389      if (deltaX > 0) {
390        controls.querySelector('.prev').click();
391      } else {
392        controls.querySelector('.next').click();
393      }
394    }
395  });
396
397  container.addEventListener('mouseleave', () => {
398    if (isDragging) {
399      isDragging = false;
400      container.style.cursor = 'grab';
401    }
402  });
403
404  let touchStartX = 0;
405  let touchStartY = 0;
406  let isSwiping = false;
407
408  container.addEventListener('touchstart', (e) => {
409    if (e.target.closest('.short-video-nav') || e.target.closest('video')) return;
410    touchStartX = e.touches[0].clientX;
411    touchStartY = e.touches[0].clientY;
412    isSwiping = true;
413  }, { passive: true });
414
415  container.addEventListener('touchmove', (e) => {
416    if (!isSwiping) return;
417
418    const deltaX = Math.abs(e.touches[0].clientX - touchStartX);
419    const deltaY = Math.abs(e.touches[0].clientY - touchStartY);
420    if (deltaX > deltaY && deltaX > 10) {
421      e.preventDefault();
422    }
423  }, { passive: false });
424
425  container.addEventListener('touchend', (e) => {
426    if (!isSwiping) return;
427    isSwiping = false;
428    
429    const touchEndX = e.changedTouches[0].clientX;
430    const deltaX = touchEndX - touchStartX;
431    
432    if (Math.abs(deltaX) > dragThreshold) {
433      if (deltaX > 0) {
434        controls.querySelector('.prev').click();
435      } else {
436        controls.querySelector('.next').click();
437      }
438    }
439  }, { passive: true });
440
441  container.style.cursor = 'grab';
442  
443  container.addEventListener('click', (e) => {
444      const item = e.target.closest('.short-video-carousel-item') || e.target.closest('.short-video__item');
445      if (item && !isDragging) shortVideo.openGallery(e, item);
446  });
447};
448
449
450shortVideo.items = [];
451shortVideo.currentIndex = 0;
452shortVideo.slots = [];
453shortVideo.isAnimating = false;
454
455shortVideo.openGallery = (event, item) => {
456  event.preventDefault();
457  
458
459  shortVideo.items = shortVideo.getVideosData();
460  
461
462  const source = item.getAttribute('data-short-video-source');
463  shortVideo.currentIndex = shortVideo.items.findIndex(v => v.videoSrc === source);
464  if (shortVideo.currentIndex === -1) shortVideo.currentIndex = 0;
465
466
467  if (!document.getElementById('shortVideoLightbox')) {
468    shortVideo.createGalleryHTML();
469  }
470
471
472  shortVideo.initSlots();
473
474
475  const lightbox = document.getElementById('shortVideoLightbox');
476  lightbox.style.display = 'block';
477  document.body.style.overflow = 'hidden';
478  
479
480  document.addEventListener('keydown', shortVideo.handleKeydown);
481};
482
483shortVideo.createGalleryHTML = () => {
484  const html = `
485    <div class="short-video-lightbox" id="shortVideoLightbox">
486      <div class="short-video-lightbox__overlay"></div>
487      <div class="short-video-lightbox__container">
488        <button class="short-video-lightbox__close" aria-label="Close">&times;</button>
489        <div class="short-video-gallery">
490          <div class="gallery-track">
491            <div class="gallery-item" data-slot="0"></div>
492            <div class="gallery-item" data-slot="1"></div>
493            <div class="gallery-item" data-slot="2"></div>
494            <div class="gallery-item" data-slot="3"></div>
495            <div class="gallery-item" data-slot="4"></div>
496          </div>
497        </div>
498        <button class="gallery-nav gallery-nav--prev"></button>
499        <button class="gallery-nav gallery-nav--next"></button>
500      </div>
501    </div>
502  `;
503  document.body.insertAdjacentHTML('beforeend', html);
504
505
506  document.querySelector('.short-video-lightbox__close').addEventListener('click', shortVideo.closeGallery);
507  
508  const lightbox = document.getElementById('shortVideoLightbox');
509  lightbox.addEventListener('click', (e) => {
510    const isProductLink = e.target.closest('.js-open-product-iframe');
511    if (isProductLink) {
512      return;
513    }
514    shortVideo.closeGallery();
515  });
516  
517  const gallery = document.querySelector('.short-video-gallery');
518  gallery.addEventListener('click', (e) => {
519    const isProductLink = e.target.closest('.js-open-product-iframe');
520    if (isProductLink) {
521      return;
522    }
523    e.stopPropagation();
524  });
525  
526  document.querySelector('.gallery-nav--prev').addEventListener('click', (e) => {
527    e.stopPropagation();
528    shortVideo.prev();
529  });
530  document.querySelector('.gallery-nav--next').addEventListener('click', (e) => {
531    e.stopPropagation();
532    shortVideo.next();
533  });
534  
535
536  shortVideo.slots = Array.from(document.querySelectorAll('.gallery-item'));
537};
538
539shortVideo.initSlots = () => {
540  const indices = [
541    shortVideo.getCircularIndex(shortVideo.currentIndex - 2),
542    shortVideo.getCircularIndex(shortVideo.currentIndex - 1),
543    shortVideo.getCircularIndex(shortVideo.currentIndex),
544    shortVideo.getCircularIndex(shortVideo.currentIndex + 1),
545    shortVideo.getCircularIndex(shortVideo.currentIndex + 2)
546  ];
547
548  shortVideo.slots.forEach((slot, i) => {
549
550    slot.className = 'gallery-item';
551    
552
553    if (i === 0) slot.classList.add('pos-far-left');
554    if (i === 1) slot.classList.add('pos-left');
555    if (i === 2) slot.classList.add('pos-center');
556    if (i === 3) slot.classList.add('pos-right');
557    if (i === 4) slot.classList.add('pos-far-right');
558    
559
560    shortVideo.renderSlotContent(slot, shortVideo.items[indices[i]]);
561    
562
563    const video = slot.querySelector('video');
564    if (i === 2 && video) video.play();
565    else if (video) {
566        video.pause();
567        video.currentTime = 0;
568    }
569  });
570};
571
572shortVideo.renderSlotContent = (slot, data) => {
573  if (!data) return;
574
575  slot.innerHTML = `
576    <div class="gallery-item__content">
577      <div class="gallery-item__video-container">
578        <video
579          class="gallery-video"
580          disablePictureInPicture
581          loop
582          playsinline
583          preload="metadata"
584          muted
585        >
586          <source src="${data.videoSrc}" type="video/mp4">
587        </video>
588        
589
590        <div class="custom-video-controls">
591            <button class="custom-play-button" aria-label="Play/Pause">
592                <div class="play-icon"></div>
593            </button>
594            
595            <div class="volume-feedback">
596                <div class="volume-icon"></div>
597                <div class="volume-bar-container">
598                    <div class="volume-bar-fill"></div>
599                </div>
600                <span class="volume-text">100%</span>
601            </div>
602            
603            <button class="custom-mute-button" aria-label="Mute/Unmute">
604                <div class="mute-icon"></div>
605            </button>
606        </div>
607      </div>
608      <a class="gallery-item__product js-open-product-iframe" data-product-id="${data.productId}">
609        ${data.productImage ? `<img src="${data.productImage}" alt="${data.productName}">` : ''}
610        <div class="gallery-item__product-info">
611          <span class="name">${data.productName}</span>
612          ${data.productPrice ? `<span class="price">${data.productPrice}</span>` : ''}
613        </div>
614        <i id="iconShoppingCartShortVideosModal" class="fa fa-shopping-cart" aria-hidden="true" style="background: ${data.borderColor || '#000'}"></i>
615      </a>
616    </div>
617  `;
618
619
620  const video = slot.querySelector('video');
621  const container = slot.querySelector('.gallery-item__video-container');
622  const playBtn = slot.querySelector('.custom-play-button');
623  const muteBtn = slot.querySelector('.custom-mute-button');
624  
625  if (video && container) {
626
627      const updatePlayState = () => {
628          if (video.paused) {
629              container.classList.remove('is-playing');
630              container.classList.add('is-paused');
631          } else {
632              container.classList.add('is-playing');
633              container.classList.remove('is-paused');
634          }
635      };
636
637      video.addEventListener('play', updatePlayState);
638      video.addEventListener('pause', updatePlayState);
639      video.addEventListener('playing', updatePlayState);
640
641
642      const togglePlay = (e) => {
643          e.stopPropagation();
644          if (video.paused) {
645              video.play().catch(err => console.log('Play failed', err));
646          } else {
647              video.pause();
648          }
649      };
650      
651      playBtn.addEventListener('click', togglePlay);
652      let isDrag = false;
653      let startX = 0, startY = 0;
654      
655      container.addEventListener('mousedown', (e) => {
656          isDrag = false;
657          startX = e.clientX;
658          startY = e.clientY;
659      });
660      
661      container.addEventListener('mousemove', (e) => {
662          if (Math.abs(e.clientX - startX) > 10 || Math.abs(e.clientY - startY) > 10) {
663              isDrag = true;
664          }
665      });
666      
667      container.addEventListener('click', (e) => {
668          if (!isDrag && !e.target.closest('button') && !e.target.closest('a')) {
669              togglePlay(e);
670          }
671      });
672
673      const toggleMute = (e) => {
674          e.stopPropagation();
675          
676          const allVideos = document.querySelectorAll('.gallery-video');
677          const newMutedState = !video.muted;
678          
679          allVideos.forEach(v => {
680              v.muted = newMutedState;
681          });
682          
683
684          updateAllMuteIcons(newMutedState);
685      };
686      
687      muteBtn.addEventListener('click', toggleMute);
688      
689      const updateMuteIcon = (isMuted) => {
690          if (isMuted) {
691              muteBtn.classList.add('is-muted');
692          } else {
693              muteBtn.classList.remove('is-muted');
694          }
695      };
696      
697      const updateAllMuteIcons = (isMuted) => {
698          const allMuteButtons = document.querySelectorAll('.custom-mute-button');
699          allMuteButtons.forEach(btn => {
700              if (isMuted) {
701                  btn.classList.add('is-muted');
702              } else {
703                  btn.classList.remove('is-muted');
704              }
705          });
706      };
707      
708
709      updateMuteIcon(video.muted);
710
711      updatePlayState();
712      
713      shortVideo.initVolumeSwipe(container, video);
714  }
715};
716
717shortVideo.initVolumeSwipe = (container, video) => {
718    let startY = 0;
719    let startVolume = 1;
720    let isSwipingVolume = false;
721    const feedback = container.querySelector('.volume-feedback');
722    const fill = container.querySelector('.volume-bar-fill');
723    const text = container.querySelector('.volume-text');
724    
725    const handleStart = (y) => {
726        startY = y;
727        startVolume = video.volume;
728    };
729    
730    const handleMove = (y) => {
731        const deltaY = startY - y;
732        
733        if (!isSwipingVolume && Math.abs(deltaY) > 20) {
734            isSwipingVolume = true;
735            feedback.classList.add('visible');
736        }
737        
738        if (isSwipingVolume) {
739            const volumeChange = deltaY / 200;
740            let newVolume = startVolume + volumeChange;
741            
742            newVolume = Math.max(0, Math.min(1, newVolume));
743            
744            video.volume = newVolume;
745            if (newVolume > 0) video.muted = false;
746            
747            const pct = Math.round(newVolume * 100);
748            fill.style.height = `${pct}%`;
749            text.textContent = `${pct}%`;
750        }
751    };
752    
753    const handleEnd = () => {
754        if (isSwipingVolume) {
755            isSwipingVolume = false;
756            setTimeout(() => {
757                feedback.classList.remove('visible');
758            }, 1000);
759        }
760    };
761    
762    // Mouse Events
763    const onMouseMove = (e) => {
764        if (e.buttons === 1) handleMove(e.clientY);
765    };
766    
767    const onMouseUp = () => {
768        handleEnd();
769        window.removeEventListener('mousemove', onMouseMove);
770        window.removeEventListener('mouseup', onMouseUp);
771    };
772    
773    container.addEventListener('mousedown', (e) => {
774        handleStart(e.clientY);
775        window.addEventListener('mousemove', onMouseMove);
776        window.addEventListener('mouseup', onMouseUp);
777    });
778    
779    // Touch Events
780    container.addEventListener('touchstart', e => handleStart(e.touches[0].clientY), {passive: true});
781    container.addEventListener('touchmove', e => {
782        // If we are swiping volume, prevent default scroll
783        if (isSwipingVolume) e.preventDefault();
784        handleMove(e.touches[0].clientY);
785    }, {passive: false});
786    container.addEventListener('touchend', handleEnd);
787};
788
789shortVideo.next = () => {
790  if (shortVideo.isAnimating) return;
791  shortVideo.isAnimating = true;
792
793  shortVideo.currentIndex = shortVideo.getCircularIndex(shortVideo.currentIndex + 1);
794  
795  // Rotate slots: 
796  // The one that was far-left (0) moves to far-right (4)
797  // All others shift left (1->0, 2->1, 3->2, 4->3)
798  
799  const firstSlot = shortVideo.slots.shift(); // Remove first
800  shortVideo.slots.push(firstSlot); // Add to end
801  
802  // Update content of the new far-right slot (which was far-left)
803  // It should show current + 2
804  const newIndex = shortVideo.getCircularIndex(shortVideo.currentIndex + 2);
805  shortVideo.renderSlotContent(firstSlot, shortVideo.items[newIndex]);
806  
807  // Update classes for animation
808  shortVideo.updateSlotClasses();
809  
810  setTimeout(() => {
811    shortVideo.isAnimating = false;
812  }, 400); // Match CSS transition duration
813};
814
815shortVideo.prev = () => {
816  if (shortVideo.isAnimating) return;
817  shortVideo.isAnimating = true;
818
819  shortVideo.currentIndex = shortVideo.getCircularIndex(shortVideo.currentIndex - 1);
820  
821  // Rotate slots:
822  // The one that was far-right (4) moves to far-left (0)
823  // All others shift right
824  
825  const lastSlot = shortVideo.slots.pop(); // Remove last
826  shortVideo.slots.unshift(lastSlot); // Add to start
827  
828  // Update content of the new far-left slot
829  // It should show current - 2
830  const newIndex = shortVideo.getCircularIndex(shortVideo.currentIndex - 2);
831  shortVideo.renderSlotContent(lastSlot, shortVideo.items[newIndex]);
832  
833  // Update classes for animation
834  shortVideo.updateSlotClasses();
835  
836  setTimeout(() => {
837    shortVideo.isAnimating = false;
838  }, 400);
839};
840
841shortVideo.updateSlotClasses = () => {
842  shortVideo.slots.forEach((slot, i) => {
843    slot.classList.remove('pos-far-left', 'pos-left', 'pos-center', 'pos-right', 'pos-far-right');
844    
845    if (i === 0) slot.classList.add('pos-far-left');
846    if (i === 1) slot.classList.add('pos-left');
847    if (i === 2) slot.classList.add('pos-center');
848    if (i === 3) slot.classList.add('pos-right');
849    if (i === 4) slot.classList.add('pos-far-right');
850
851    const video = slot.querySelector('video');
852    const container = slot.querySelector('.gallery-item__video-container');
853
854    if (video && container) {
855        if (i === 2) {
856            video.play().then(() => {
857                container.classList.add('is-playing');
858                container.classList.remove('is-paused');
859            }).catch(() => {
860                // Auto-play might fail, show paused state
861                container.classList.remove('is-playing');
862                container.classList.add('is-paused');
863            });
864        } else {
865            video.pause();
866            video.currentTime = 0;
867            container.classList.remove('is-playing');
868            container.classList.add('is-paused');
869        }
870    }
871  });
872};
873
874shortVideo.getCircularIndex = (index) => {
875  const len = shortVideo.items.length;
876  return ((index % len) + len) % len;
877};
878
879shortVideo.closeGallery = () => {
880  const lightbox = document.getElementById('shortVideoLightbox');
881  if (lightbox) {
882    lightbox.style.display = 'none';
883    document.body.style.overflow = '';
884    
885    // Pause all videos
886    document.querySelectorAll('.gallery-video').forEach(v => {
887      v.pause();
888      v.currentTime = 0;
889    });
890  }
891  document.removeEventListener('keydown', shortVideo.handleKeydown);
892};
893
894shortVideo.handleKeydown = (e) => {
895  if (e.key === 'Escape') shortVideo.closeGallery();
896  if (e.key === 'ArrowLeft') shortVideo.prev();
897  if (e.key === 'ArrowRight') shortVideo.next();
898};
899
900shortVideo.openProductIframe = (productId) => {
901  const ajaxBaseUrl = utilsSpeed.getCachedElement('#urlajax');
902
903  if (X && typeof X.get === "function") {
904    X.get(ajaxBaseUrl.value + "/produto.php?act=comprarapida&noRecaptcha=noRecaptcha&id=" + productId);
905  }
906};
907
908shortVideo.closeProductIframe = () => {
909  const iframeContainer = document.getElementById('productIframeContainer');
910  if (iframeContainer) {
911    iframeContainer.classList.remove('is-visible');
912    setTimeout(() => {
913      iframeContainer.style.display = 'none';
914      iframeContainer.querySelector('iframe').src = '';
915    }, 300);
916  }
917
918  if (window.demo && typeof demo.updateCart === "function") {
919    demo.updateCart();
920  }
921};
922
923document.addEventListener('click', (e) => {
924  const link = e.target.closest('.js-open-product-iframe');
925  if (link) {
926    e.preventDefault();
927    e.stopPropagation();
928    const productId = link.getAttribute('data-product-id');
929    if (productId) {
930      shortVideo.openProductIframe(productId);
931    }
932  }
933});
934
935shortVideo.getVideosData = () => {
936  // Use cached list if available (from Virtual Carousel)
937  if (shortVideo.cachedVideoList && shortVideo.cachedVideoList.length > 0) {
938      let videos = [...shortVideo.cachedVideoList];
939      
940      // Ensure we have at least 5 items for the circular gallery to work smoothly
941      if (videos.length > 0 && videos.length < 5) {
942        const originalLength = videos.length;
943        while (videos.length < 5) {
944          for (let i = 0; i < originalLength; i++) {
945            if (videos.length < 5) {
946                 // Clone the object to avoid reference issues
947                videos.push({...videos[i]});
948            }
949          }
950        }
951      }
952      return videos;
953  }
954
955  const videoElements = document.querySelectorAll('.short-video__item');
956  const videos = [];
957
958  videoElements.forEach(videoEl => {
959    videos.push({
960      id: videoEl.getAttribute('data-short-video-id') || '',
961      videoSrc: videoEl.getAttribute('data-short-video-source') || '',
962      productName: videoEl.getAttribute('data-short-video-product-name') || '',
963      productUrl: videoEl.getAttribute('data-short-video-url') || '#',
964      productPrice: videoEl.getAttribute('data-short-video-product-price') || '',
965      productImage: videoEl.getAttribute('data-short-video-product-image') || '',
966      borderColor: videoEl.getAttribute('data-short-video-border-color') || ''
967    });
968  });
969
970  // Ensure we have at least 5 items for the circular gallery to work smoothly
971  if (videos.length > 0 && videos.length < 5) {
972    const originalLength = videos.length;
973    while (videos.length < 5) {
974      for (let i = 0; i < originalLength; i++) {
975        if (videos.length < 5) {
976             // Clone the object to avoid reference issues
977            videos.push({...videos[i]});
978        }
979      }
980    }
981  }
982
983  return videos;
984};
985
986document.addEventListener("DOMContentLoaded", () => {
987    if (!shortVideo.init) {
988        return;
989    }
990
991    const container = document.querySelector('.short-video');
992    if (!container) {
993      return;
994    }
995
996    if ('IntersectionObserver' in window) {
997        const observer = new IntersectionObserver((entries) => {
998            if (entries[0].isIntersecting) {
999                observer.disconnect();
1000                shortVideo.init();
1001            }
1002        }, { rootMargin: '300px' });
1003        observer.observe(container);
1004    } else if (typeof lazyLoadService !== 'undefined') {
1005        lazyLoadService.addFunctionToQueue(shortVideo.init);
1006    } else {
1007        shortVideo.init();
1008    }
1009});

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.