1(function () { 2 'use strict'; 3 4 function initMarquee() { 5 document.querySelectorAll('.tf-title, .tf-desc').forEach(function (el) { 6 var inner = el.querySelector('.tf-title-inner, .tf-desc-inner'); 7 if (!inner) return; 8 9 inner.style.animation = 'none'; 10 inner.style.removeProperty('--tf-scroll'); 11 void inner.offsetWidth; 12 13 var innerW = inner.getBoundingClientRect().width; 14 var elW = el.getBoundingClientRect().width; 15 if (innerW <= elW) return; 16 17 el.style.overflow = 'hidden'; 18 19 var dist = innerW - elW; 20 var dur = Math.max(4, dist / 30); 21 22 inner.style.setProperty('--tf-scroll', '-' + dist + 'px'); 23 inner.style.animation = 'tf-scroll ' + dur + 's ease-in-out infinite'; 24 inner.style.animationDelay = '1s'; 25 }); 26 } 27 28 var style = document.createElement('style'); 29 style.textContent = '@keyframes tf-scroll { 0% { transform: translateX(0); } 40% { transform: translateX(var(--tf-scroll)); } 60% { transform: translateX(var(--tf-scroll)); } 100% { transform: translateX(0); } }'; 30 document.head.appendChild(style); 31 32 var AJAX_URL = typeof tfTrack !== 'undefined' && tfTrack.ajaxurl ? tfTrack.ajaxurl : ''; 33 34 function initClickTracking() { 35 if (!AJAX_URL) return; 36 document.addEventListener('click', function (e) { 37 if (!(e.target instanceof Element)) return; 38 var link = e.target.closest('.tf-item, .tf-s3-item, .tf-catfish .logo-item'); 39 if (!link) return; 40 var code = link.getAttribute('data-code') || ''; 41 var domain = ''; 42 try { domain = new URL(link.href).hostname; } catch (e2) {} 43 44 var data = new URLSearchParams(); 45 data.append('action', 'tf_track_click'); 46 data.append('domain', domain); 47 data.append('brand', code); 48 data.append('target_url', link.href); 49 50 fetch(AJAX_URL, { method: 'POST', body: data, keepalive: true }); 51 }); 52 } 53 54 function updateCtaVisibility() { 55 document.querySelectorAll('.tf-section').forEach(function (section) { 56 var w = section.clientWidth; 57 var isSlider = section.classList.contains('tf-theme-style3') || section.classList.contains('tf-theme-style4'); 58 section.style.display = w < 280 ? 'none' : ''; 59 if (isSlider) { 60 section.classList.toggle('tf-s3-small', section.style.display !== 'none' && w < 390); 61 } else { 62 section.classList.toggle('tf-cta-hidden', w < 360); 63 } 64 }); 65 } 66 67 var roTimer; 68 function initSlider(section) { 69 var track = section.querySelector('.tf-s3-track'); 70 var prev = section.querySelector('.tf-s3-prev'); 71 var next = section.querySelector('.tf-s3-next'); 72 var wrap = section.querySelector('.tf-s3-track-wrap'); 73 if (!track || !prev || !next || !wrap) return; 74 75 var items = track.children; 76 if (items.length === 0) return; 77 78 fillTrack(); 79 var currentPos = 0; 80 var autoTimer = null; 81 82 function fillTrack() { 83 var wrapW = wrap.getBoundingClientRect().width; 84 var gap = getGap(); 85 var totalW = 0; 86 var originals = Array.from(items); 87 if (originals.length === 0) return; 88 for (var i = 0; i < originals.length; i++) { 89 totalW += originals[i].offsetWidth; 90 if (i < originals.length - 1) totalW += gap; 91 } 92 var minClones = 1; 93 var clonesAdded = 0; 94 while ((totalW < wrapW || clonesAdded < minClones) && clonesAdded < 20) { 95 for (var i = 0; i < originals.length && (totalW < wrapW || clonesAdded < minClones); i++) { 96 var clone = originals[i].cloneNode(true); 97 track.appendChild(clone); 98 totalW += originals[i].offsetWidth + gap; 99 } 100 clonesAdded++; 101 } 102 } 103 104 function getItemWidth() { 105 if (items.length === 0) return 0; 106 return items[0].getBoundingClientRect().width; 107 } 108 109 function getGap() { 110 return parseFloat(getComputedStyle(track).gap) || 0; 111 } 112
113 function getItemsPerView() { 114 var wrapW = wrap.getBoundingClientRect().width; 115 var itemW = getItemWidth(); 116 var gap = getGap(); 117 var step = itemW + gap; 118 if (step <= 0) return 1; 119 return Math.max(1, Math.floor(wrapW / step)); 120 } 121 122 function getMaxTranslate() { 123 var wrapW = wrap.getBoundingClientRect().width; 124 var gap = getGap(); 125 var totalItemW = 0; 126 for (var i = 0; i < items.length; i++) { 127 totalItemW += items[i].getBoundingClientRect().width; 128 } 129 var totalGaps = (items.length - 1) * gap; 130 return Math.max(0, totalItemW + totalGaps - wrapW); 131 } 132 133 function getStep() { 134 var perView = getItemsPerView(); 135 return perView >= 4 ? 2 : 1; 136 } 137 138 function update(instant) { 139 if (section.offsetParent === null) return; 140 var prevTransition = track.style.transition; 141 if (instant) { 142 track.style.transition = 'none'; 143 } 144 var itemW = getItemWidth(); 145 var gap = getGap(); 146 var step = itemW + gap; 147 if (step <= 0) return; 148 var maxTx = getMaxTranslate(); 149 var maxPos = maxTx > 0 ? Math.ceil(maxTx / step) : 0; 150 if (currentPos > maxPos) currentPos = 0; 151 if (currentPos < 0) currentPos = maxPos; 152 var tx = Math.min(currentPos * step, maxTx); 153 track.style.transform = 'translateX(-' + tx + 'px)'; 154 if (instant) { 155 void track.offsetHeight; 156 track.style.transition = prevTransition; 157 } 158 } 159 160 function goNext() { 161 var s = getStep(); 162 var maxTx = getMaxTranslate(); 163 var step = getItemWidth() + getGap(); 164 var maxPos = maxTx > 0 ? Math.ceil(maxTx / step) : 0; 165 if (currentPos >= maxPos) { 166 currentPos = 0; 167 update(true); 168 } else { 169 currentPos = Math.min(currentPos + s, maxPos); 170 update(); 171 } 172 } 173 174 function goPrev() { 175 var s = getStep(); 176 var maxTx = getMaxTranslate(); 177 var step = getItemWidth() + getGap(); 178 var maxPos = maxTx > 0 ? Math.ceil(maxTx / step) : 0; 179 if (currentPos <= 0) { 180 currentPos = maxPos; 181 update(true); 182 } else { 183 currentPos = Math.max(currentPos - s, 0); 184 update(); 185 } 186 } 187 188 function startAuto() { 189 stopAuto(); 190 autoTimer = setInterval(goNext, 3000); 191 } 192 193 function stopAuto() { 194 if (autoTimer) { 195 clearInterval(autoTimer); 196 autoTimer = null; 197 } 198 } 199 200 next.addEventListener('click', function () { stopAuto(); goNext(); }); 201 prev.addEventListener('click', function () { stopAuto(); goPrev(); }); 202 203 section.addEventListener('mouseenter', stopAuto); 204 section.addEventListener('mouseleave', startAuto); 205 206 var startX = 0, isDragging = false; 207 track.addEventListener('touchstart', function (e) { 208 stopAuto(); 209 startX = e.touches[0].clientX; 210 isDragging = true; 211 }, { passive: true }); 212 213 track.addEventListener('touchend', function (e) { 214 if (!isDragging) return; 215 isDragging = false; 216 var endX = e.changedTouches[0].clientX; 217 var diff = startX - endX; 218 if (Math.abs(diff) >= 50) { 219 var maxTx = getMaxTranslate(); 220 var step = getItemWidth() + getGap(); 221 var maxPos = maxTx > 0 ? Math.ceil(maxTx / step) : 0; 222 if (diff > 0) { 223 if (currentPos >= maxPos) { currentPos = 0; update(true); } 224 else { currentPos = Math.min(currentPos + getStep(), maxPos); update(); } 225 } else { 226 if (currentPos <= 0) { currentPos = maxPos; update(true); } 227 else { currentPos = Math.max(currentPos - getStep(), 0); update(); } 228 } 229 } 230 startAuto(); 231 }, { passive: true }); 232 233 update(); 234 startAuto(); 235 return { update: update, destroy: stopAuto }; 236 } 237 238 var sliders = []; 239 240 // ======================================== 241 // CATFISH BANNER 242 // ======================================== 243 function initCatfish() { 244 document.querySelectorAll('.tf-catfish').forEach(function (root) { 245 var id = root.getAttribute('data-uid') || 'tf-catfish'; 246 var banner = root.querySelector('.catfish-banner'); 247 var bubble = root.querySelector('.promo-bubble'); 248 var bubbleLine1 = root.querySelector('.bubble-line1'); 249 var bubbleLine2 = root.querySelector('.bubble-line2'); 250 var bubbleArrow = root.querySelector('.bubble-arrow'); 251 var wrapper = root.querySelector('.logo-swiper-wrapper'); 252 var sliderContainer = root.querySelector('.logo-slider-container'); 253 var swiperEl = root.querySelector('.logo-swiper'); 254 var hideBtn = root.querySelector('.hide-ad-btn'); 255 var collapsed = root.querySelector('.collapsed-banner'); 256 var countdownEl = root.querySelector('.countdown-value'); 257 var showBtn = root.querySelector('.collapsed-content'); 258 if (!banner || !wrapper || !hideBtn || !showBtn) return; 259 260 var countdownDuration = parseInt(root.getAttribute('data-countdown') || '180', 10); 261 var STORAGE_END = 'catfishBannerEndTime_' + id; 262 var STORAGE_HIDDEN = 'catfishBannerHidden_' + id; 263 var MOBILE_BREAKPOINT = 768; 264 var AUTO_SLIDE_INTERVAL = 5000; 265 var SLIDE_SPEED = 600; 266 267 var state = { 268 isMobile: window.innerWidth < MOBILE_BREAKPOINT, 269 isHidden: false, 270 swiperInstance: null, 271 currentSlideIndex: 0, 272 slideTimer: null, 273 bubbleTimer: null, 274 autoSlidePaused: false 275 }; 276 277 function isMobile() { 278 return window.innerWidth < MOBILE_BREAKPOINT; 279 } 280 281 var logoCount = wrapper.querySelectorAll('.logo-item').length; 282 if (logoCount === 0) return; 283 284 if (state.isMobile) banner.classList.add('is-mobile'); 285 286 // --- Swiper slider (logic from catfish-banner-plugin) --- 287 function getSlidesPerView() { 288 var available = sliderContainer ? sliderContainer.clientWidth : 1000; 289 var slideW = state.isMobile ? 60 : 80; 290 var space = state.isMobile ? 4 : 10; 291 return Math.max(1, Math.floor(available / (slideW + space))); 292 } 293 294 function setSwiperWidth() { 295 if (!swiperEl) return; 296 var slideW = state.isMobile ? 60 : 80; 297 var space = state.isMobile ? 4 : 10; 298 var perView = getSlidesPerView(); 299 var exactWidth = (perView * slideW) + ((perView - 1) * space); 300 swiperEl.style.width = exactWidth + 'px'; 301 } 302 303 function ensureSlideCoverage() { 304 if (logoCount <= 0) return; 305 var perView = getSlidesPerView(); 306 var minSlides = Math.max(logoCount * 3, perView * 2); 307 var slides = wrapper.querySelectorAll('.swiper-slide:not(.swiper-slide-duplicate)'); 308 if (slides.length >= minSlides) return; 309 var originals = Array.prototype.slice.call(slides); 310 var i = 0; 311 while (wrapper.querySelectorAll('.swiper-slide:not(.swiper-slide-duplicate)').length < minSlides) { 312 wrapper.appendChild(originals[i % originals.length].cloneNode(true)); 313 i++; 314 } 315 } 316 317 function initSwiper() { 318 if (typeof Swiper === 'undefined') return; 319 var dup = wrapper.querySelectorAll('.swiper-slide-duplicate'); 320 for (var d = 0; d < dup.length; d++) dup[d].parentNode.removeChild(dup[d]); 321 state.isMobile = isMobile(); 322 if (state.isMobile) banner.classList.add('is-mobile'); 323 else banner.classList.remove('is-mobile'); 324 325 setSwiperWidth(); 326 ensureSlideCoverage(); 327 state.swiperInstance = new Swiper(swiperEl, { 328 direction: 'horizontal', 329 loop: true, 330 loopedSlides: logoCount, 331 loopAdditionalSlides: 0, 332 freeMode: { 333 enabled: true, 334 momentum: true, 335 momentumRatio: 0.5, 336 momentumBounceRatio: 1 337 }, 338 slidesPerView: getSlidesPerView(), 339 slidesPerGroup: 1, 340 spaceBetween: state.isMobile ? 4 : 10, 341 simulateTouch: true, 342 touchEventsTarget: 'container', 343 touchRatio: 1, 344 longSwipesRatio: 0.5, 345 allowTouchMove: true, 346 autoplay: false, 347 speed: SLIDE_SPEED, 348 centeredSlides: false, 349 observer: true, 350 observeParents: true, 351 observeSlideChildren: true, 352 watchOverflow: true, 353 on: { 354 init: function () { 355 state.currentSlideIndex = 0; 356 setSwiperWidth(); 357 if (window.innerWidth >= 2500) { 358 this.wrapperEl.style.transform = 'translate3d(0px, 0px, 0px)'; 359 } 360 }, 361 touchStart: function () { 362 state.autoSlidePaused = true; 363 stopAutoSlide(); 364 }, 365 touchEnd: function () { 366 setTimeout(function () { 367 state.autoSlidePaused = false;
368 startAutoSlide(); 369 }, 1500); 370 }, 371 resize: function (swiper) { 372 setTimeout(function () { 373 state.isMobile = isMobile(); 374 setSwiperWidth(); 375 swiper.params.slidesPerView = getSlidesPerView(); 376 swiper.params.slideWidth = state.isMobile ? 60 : 80; 377 swiper.params.spaceBetween = state.isMobile ? 4 : 10; 378 swiper.params.freeMode.enabled = true; 379 swiper.params.freeMode.momentum = true; 380 swiper.params.simulateTouch = true; 381 swiper.params.allowTouchMove = true; 382 swiper.update(); 383 startAutoSlide(); 384 }, 150); 385 } 386 } 387 }); 388 } 389 390 function resetSlider() { 391 state.currentSlideIndex = 0; 392 if (state.swiperInstance) { 393 state.swiperInstance.slideTo(logoCount, SLIDE_SPEED, false); 394 } 395 } 396 397 function startAutoSlide() { 398 if (state.isHidden) return; 399 if (state.slideTimer) return; 400 if (state.autoSlidePaused) return; 401 scheduleNextSlide(); 402 } 403 404 function stopAutoSlide() { 405 if (state.slideTimer) { 406 clearTimeout(state.slideTimer); 407 state.slideTimer = null; 408 } 409 hideBubble(); 410 } 411 412 function scheduleNextSlide() { 413 if (state.isHidden) return; 414 if (!state.swiperInstance) return; 415 state.slideTimer = setTimeout(function () { 416 slideToNextAndShow(); 417 }, AUTO_SLIDE_INTERVAL); 418 } 419 420 function slideToNextAndShow() { 421 if (state.isHidden) return; 422 var swiper = state.swiperInstance; 423 if (!swiper) { 424 scheduleNextSlide(); 425 return; 426 } 427 var looped = logoCount; 428 var currentIndex = swiper.activeIndex; 429 var targetIndex; 430 if (currentIndex < looped) { 431 targetIndex = currentIndex + 1; 432 if (targetIndex >= looped) targetIndex = looped; 433 } else { 434 targetIndex = currentIndex + 1; 435 if (targetIndex >= looped * 2) targetIndex = looped; 436 } 437 state.currentSlideIndex = (state.currentSlideIndex + 1) % logoCount; 438 swiper.slideTo(targetIndex, SLIDE_SPEED, true); 439 scheduleNextSlide(); 440 } 441 442 // --- Promo bubble --- 443 function setBubbleContent(el) { 444 if (!bubbleLine1 || !bubbleLine2) return; 445 bubbleLine1.textContent = el.getAttribute('data-line1') || ''; 446 bubbleLine2.textContent = el.getAttribute('data-line2') || ''; 447 } 448 449 function positionAndShowDesktopBubble(el) { 450 setBubbleContent(el); 451 var rect = el.getBoundingClientRect(); 452 var viewportWidth = window.innerWidth; 453 var bubbleWidth = bubble.offsetWidth || 270; 454 var finalX = rect.left + (rect.width / 2) - (bubbleWidth / 2); 455 var arrowPercentage = 50; 456 if (finalX < 0) { 457 finalX = 0; 458 arrowPercentage = ((rect.left + rect.width / 2) / bubbleWidth) * 100; 459 } 460 var bubbleRightEdge = finalX + bubbleWidth; 461 if (bubbleRightEdge > viewportWidth) { 462 finalX = viewportWidth - bubbleWidth; 463 arrowPercentage = ((rect.left + rect.width / 2 - finalX) / bubbleWidth) * 100; 464 } 465 arrowPercentage = Math.max(15, Math.min(85, arrowPercentage)); 466 bubble.style.left = finalX + 'px'; 467 if (bubbleArrow) bubbleArrow.style.left = arrowPercentage + '%'; 468 bubble.classList.add('visible'); 469 } 470 471 function hideBubble() { 472 bubble.classList.remove('visible'); 473 } 474 475 // --- Hover (desktop) --- 476 wrapper.addEventListener('mouseenter', function (e) { 477 if (isMobile()) return; 478 var el = e.target.closest('.logo-item'); 479 if (el) { 480 state.autoSlidePaused = true; 481 stopAutoSlide(); 482 positionAndShowDesktopBubble(el); 483 } 484 }, true); 485 486 wrapper.addEventListener('mouseleave', function (e) { 487 if (isMobile()) return; 488 var el = e.target.closest('.logo-item'); 489 if (el && !wrapper.contains(e.relatedTarget)) { 490 hideBubble(); 491 state.autoSlidePaused = false;
492 startAutoSlide(); 493 } 494 }, true); 495 496 // --- Hide / show + countdown --- 497 function setStorage(key, val) { 498 try { localStorage.setItem(key, val); } catch (e) { } 499 } 500 function getStorage(key) { 501 try { return localStorage.getItem(key); } catch (e) { return null; } 502 } 503 function clearStorage() { 504 try { 505 localStorage.removeItem(STORAGE_END); 506 localStorage.removeItem(STORAGE_HIDDEN); 507 } catch (e) { } 508 } 509 510 function hideBanner() { 511 state.isHidden = true; 512 stopAutoSlide(); 513 hideBubble(); 514 resetSlider(); 515 var endTime = Date.now() + (countdownDuration * 1000); 516 setStorage(STORAGE_END, endTime.toString()); 517 setStorage(STORAGE_HIDDEN, 'true'); 518 banner.classList.add('hidden'); 519 collapsed.classList.add('visible'); 520 startCountdown(); 521 } 522 523 function showBanner() { 524 state.isHidden = false; 525 stopCountdown(); 526 clearStorage(); 527 collapsed.classList.remove('visible'); 528 banner.classList.remove('hidden'); 529 resetSlider(); 530 startAutoSlide(); 531 } 532 533 function startCountdown() { 534 stopCountdown(); 535 var endTime; 536 var stored = getStorage(STORAGE_END); 537 if (stored) { 538 endTime = parseInt(stored, 10); 539 } else { 540 endTime = Date.now() + (countdownDuration * 1000); 541 setStorage(STORAGE_END, endTime.toString()); 542 } 543 updateCountdown(endTime); 544 state.bubbleTimer = setInterval(function () { 545 updateCountdown(endTime); 546 }, 1000); 547 } 548 549 function updateCountdown(endTime) { 550 var remaining = Math.max(0, Math.ceil((endTime - Date.now()) / 1000)); 551 if (countdownEl) countdownEl.textContent = remaining; 552 if (remaining <= 0) showBanner(); 553 } 554 555 function stopCountdown() { 556 if (state.bubbleTimer) { 557 clearInterval(state.bubbleTimer); 558 state.bubbleTimer = null; 559 } 560 } 561 562 function restoreState() { 563 if (getStorage(STORAGE_HIDDEN) === 'true') { 564 var stored = getStorage(STORAGE_END); 565 if (stored && parseInt(stored, 10) > Date.now()) { 566 state.isHidden = true; 567 banner.classList.add('hidden'); 568 collapsed.classList.add('visible'); 569 startCountdown(); 570 } else { 571 clearStorage(); 572 } 573 } 574 } 575 576 hideBtn.addEventListener('click', hideBanner); 577 showBtn.addEventListener('click', showBanner); 578 579 // --- Resize / orientation handling --- 580 var resizeTimer; 581 var isOrientationChange = false; 582 window.addEventListener('resize', function () { 583 clearTimeout(resizeTimer); 584 resizeTimer = setTimeout(function () { 585 var wasMobile = state.isMobile; 586 state.isMobile = isMobile(); 587 if (state.isMobile !== wasMobile) { 588 state.autoSlidePaused = false; 589 } 590 if (state.isMobile && !wasMobile) { 591 banner.classList.add('is-mobile'); 592 startAutoSlide(); 593 } else if (!state.isMobile && wasMobile) { 594 banner.classList.remove('is-mobile'); 595 stopAutoSlide(); 596 } 597 if (state.swiperInstance) { 598 if (isOrientationChange) { 599 state.swiperInstance.detachEvents(); 600 state.swiperInstance.destroy(false, true); 601 initSwiper(); 602 if (wasMobile) startAutoSlide(); 603 } else { 604 setSwiperWidth(); 605 if (wrapper.querySelectorAll('.swiper-slide:not(.swiper-slide-duplicate)').length < getSlidesPerView() * 2) { 606 state.swiperInstance.detachEvents(); 607 state.swiperInstance.destroy(false, true); 608 initSwiper(); 609 } else {
610 state.swiperInstance.update(); 611 state.swiperInstance.updateSize(); 612 state.swiperInstance.updateSlides(); 613 state.swiperInstance.updateProgress(); 614 state.swiperInstance.updateSlidesClasses(); 615 } 616 } 617 } 618 isOrientationChange = false; 619 }, isOrientationChange ? 300 : 100); 620 }); 621 622 window.addEventListener('orientationchange', function () { 623 isOrientationChange = true; 624 }); 625 626 initSwiper(); 627 startAutoSlide(); 628 restoreState(); 629 }); 630 } 631 632 function initAll() { 633 initMarquee(); 634 initClickTracking(); 635 updateCtaVisibility(); 636 initCatfish(); 637 var sections = document.querySelectorAll('.tf-section'); 638 sections.forEach(function (section) { 639 if (section.classList.contains('tf-theme-style3') || section.classList.contains('tf-theme-style4')) { 640 var sl = initSlider(section); 641 if (sl) sliders.push(sl); 642 } 643 }); 644 } 645 646 var ro = new ResizeObserver(function () { 647 clearTimeout(roTimer); 648 roTimer = setTimeout(function () { 649 initMarquee(); 650 updateCtaVisibility(); 651 sliders.forEach(function (s) { if (s.update) s.update(); }); 652 }, 150); 653 }); 654 655 if (document.readyState === 'loading') { 656 document.addEventListener('DOMContentLoaded', function () { 657 initAll(); 658 var section = document.querySelector('.tf-section'); 659 if (section) ro.observe(section); 660 }); 661 } else { 662 initAll(); 663 var section = document.querySelector('.tf-section'); 664 if (section) ro.observe(section); 665 } 666})();
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.