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">×</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.