1(function($) { 2 'use strict'; 3 4 var NewsTicker = { 5 init: function() { 6 $(window).on('elementor/frontend/init', function() { 7 elementorFrontend.hooks.addAction('frontend/element_ready/news_ticker.default', NewsTicker.initWidget); 8 }); 9 10 // Initialize on document ready for non-Elementor or initial load 11 $(document).ready(function() { 12 if (!window.elementorFrontend || !window.elementorFrontend.isEditMode()) { 13 $('.news-ticker-wrapper').each(function() { 14 NewsTicker.initWidget($(this).closest('.elementor-widget-news_ticker')); 15 }); 16 } 17 }); 18 }, 19 20 initWidget: function($scope) { 21 var $ticker = $scope.find('.news-ticker-wrapper'); 22 23 if (!$ticker.length) { 24 return; 25 } 26 27 // Clean up any existing animations first 28 NewsTicker.destroy($ticker); 29 30 var animationType = $ticker.data('animation'); 31 var speed = $ticker.data('speed') || 50; 32 var duration = $ticker.data('duration') || 3000; 33 var pauseOnHover = $ticker.data('pause') === true || $ticker.data('pause') === 'true'; 34 var direction = $ticker.data('direction') || 'ltr'; 35 36 switch(animationType) { 37 case 'scroll': 38 NewsTicker.initScroll($ticker, speed, pauseOnHover); 39 break; 40 case 'slide': 41 NewsTicker.initSlide($ticker, duration, pauseOnHover, direction); 42 break; 43 case 'fade': 44 NewsTicker.initFade($ticker, duration, pauseOnHover); 45 break; 46 } 47 48 // Initialize modal functionality 49 NewsTicker.initModal($ticker); 50 }, 51 52 initModal: function($ticker) { 53 var $modal = $ticker.find('.news-ticker-modal'); 54 var $modalImage = $modal.find('.news-ticker-modal-image'); 55 var $modalClose = $modal.find('.news-ticker-modal-close'); 56 var $modalOverlay = $modal.find('.news-ticker-modal-overlay'); 57 58 // Remove any existing modal handlers first 59 $ticker.off('click.tickermodal'); 60 $modalClose.off('click.tickermodal'); 61 $modalOverlay.off('click.tickermodal'); 62 $(document).off('keyup.tickermodal'); 63 64 // Handle clicks on items with modal images using event delegation 65 $ticker.on('click.tickermodal', '.news-ticker-item.has-modal-image', function(e) { 66 e.preventDefault(); 67 e.stopPropagation(); 68 69 var $item = $(this); 70 var imageUrl = $item.data('modal-image'); 71 72 console.log('Modal item clicked, image URL:', imageUrl); 73 74 if (imageUrl) { 75 $modalImage.attr('src', imageUrl); 76 $modal.css('display', 'flex').addClass('active'); 77 $('body').css('overflow', 'hidden'); 78 console.log('Modal opened'); 79 } 80 }); 81 82 // Close modal on close button click 83 $modalClose.on('click.tickermodal', function(e) { 84 e.preventDefault(); 85 e.stopPropagation(); 86 $modal.removeClass('active'); 87 setTimeout(function() { 88 $modal.css('display', 'none'); 89 }, 300); // Match transition duration 90 $('body').css('overflow', ''); 91 }); 92 93 // Close modal on overlay click 94 $modalOverlay.on('click.tickermodal', function(e) { 95 e.preventDefault(); 96 e.stopPropagation(); 97 $modal.removeClass('active'); 98 setTimeout(function() { 99 $modal.css('display', 'none'); 100 }, 300); 101 $('body').css('overflow', ''); 102 }); 103 104 // Close modal on ESC key 105 $(document).on('keyup.tickermodal', function(e) { 106 if (e.keyCode === 27 && $modal.hasClass('active')) { 107 $modal.removeClass('active'); 108 setTimeout(function() { 109 $modal.css('display', 'none'); 110 }, 300); 111 $('body').css('overflow', ''); 112 } 113 }); 114 115 // Log modal setup 116 var modalItemsCount = $ticker.find('.news-ticker-item.has-modal-image').length; 117 console.log('Modal initialized. Items with modal images:', modalItemsCount); 118 }, 119 120 initScroll: function($ticker, speed, pauseOnHover) { 121 var $wrapper = $ticker.find('.news-ticker-content-wrapper'); 122 var $track = $wrapper.find('.news-ticker-content').first(); 123 124 console.log('Init scroll - found track:', $track.length); 125 126 if (!$track.length) return; 127 128 var track = $track[0]; 129 var wrapper = $wrapper[0]; 130 var direction = $ticker.data('direction') || 'ltr'; 131 var isRTL = direction === 'rtl'; 132 133 // Check if mobile device 134 var isMobile = window.innerWidth <= 768; 135 136 // Calculate pixels per second - increase speed on mobile for better UX 137 // Mobile screens are smaller so content needs to move faster relative to viewport 138 var baseSpeed = speed * 2; 139 var pixelsPerSecond = isMobile ? baseSpeed * 1.5 : baseSpeed; 140 141 var animationId; 142 var currentPosition = 0; 143 var contentWidth = 0; 144 var isPaused = false;
145 var isAnimating = false; 146 var lastTime = 0; 147 148 // Touch/drag variables 149 var isDragging = false; 150 var touchStartX = 0; 151 var touchStartPosition = 0; 152 var lastTouchX = 0; 153 var dragVelocity = 0; 154 var lastDragTime = 0; 155 156 function initInfiniteScroll() { 157 // Remove any existing clones 158 $wrapper.find('.news-ticker-content').not(':first').remove(); 159 160 // Get dimensions 161 var wrapperWidth = wrapper.offsetWidth; 162 contentWidth = track.offsetWidth; 163 164 if (contentWidth === 0) { 165 console.log('Content width is 0, retrying...'); 166 setTimeout(initInfiniteScroll, 200); 167 return; 168 } 169 170 // Calculate how many clones we need 171 var numClones = Math.ceil((wrapperWidth * 2) / contentWidth) + 1; 172 numClones = Math.max(numClones, 2); 173 174 console.log('Creating clones:', {
175 wrapperWidth: wrapperWidth, 176 contentWidth: contentWidth, 177 numClones: numClones, 178 pixelsPerSecond: pixelsPerSecond 179 }); 180 181 // Create clones 182 for (var i = 0; i < numClones; i++) { 183 var $clone = $track.clone(); 184 $clone.removeClass('news-ticker-content-original'); 185 $wrapper.append($clone); 186 } 187 188 // Set initial position 189 currentPosition = 0; 190 track.style.transform = 'translateX(' + currentPosition + 'px)'; 191 192 console.log('Infinite scroll initialized'); 193 startAnimation(); 194 } 195 196 function startAnimation() { 197 if (isAnimating) return; 198 isAnimating = true; 199 lastTime = performance.now(); 200 201 function animate(currentTime) { 202 if (!isAnimating) return; 203 204 if (isPaused) { 205 lastTime = currentTime; 206 animationId = requestAnimationFrame(animate); 207 return; 208 } 209 210 var deltaTime = (currentTime - lastTime) / 1000; 211 lastTime = currentTime; 212 213 // Move based on direction 214 if (isRTL) { 215 currentPosition += pixelsPerSecond * deltaTime; 216 if (currentPosition >= contentWidth) { 217 currentPosition = currentPosition - contentWidth; 218 } 219 } else { 220 currentPosition -= pixelsPerSecond * deltaTime; 221 if (currentPosition <= -contentWidth) { 222 currentPosition = currentPosition + contentWidth; 223 } 224 } 225 226 // Apply transform to all content elements 227 $wrapper.find('.news-ticker-content').each(function() { 228 this.style.transform = 'translateX(' + currentPosition + 'px)'; 229 }); 230 231 animationId = requestAnimationFrame(animate); 232 } 233 234 animationId = requestAnimationFrame(animate); 235 } 236 237 function stopAnimation() { 238 isAnimating = false; 239 if (animationId) { 240 cancelAnimationFrame(animationId); 241 animationId = null; 242 } 243 } 244 245 // Pause on hover 246 if (pauseOnHover) { 247 $ticker.on('mouseenter', function() { 248 isPaused = true; 249 }); 250 251 $ticker.on('mouseleave', function() { 252 isPaused = false; 253 }); 254 } 255 256 // Touch drag functionality for mobile 257 function handleTouchStart(e) { 258 if (e.touches.length !== 1) return; 259 260 isDragging = true; 261 isPaused = true; 262 touchStartX = e.touches[0].clientX; 263 touchStartPosition = currentPosition; 264 lastTouchX = touchStartX; 265 lastDragTime = performance.now(); 266 dragVelocity = 0; 267 268 // Prevent default to avoid page scroll while dragging 269 e.preventDefault(); 270 } 271 272 function handleTouchMove(e) { 273 if (!isDragging || e.touches.length !== 1) return; 274 275 var touchX = e.touches[0].clientX; 276 var deltaX = touchX - touchStartX; 277 var now = performance.now(); 278 var timeDelta = now - lastDragTime; 279 280 // Calculate velocity for momentum 281 if (timeDelta > 0) { 282 dragVelocity = (touchX - lastTouchX) / timeDelta * 1000; 283 } 284 285 lastTouchX = touchX; 286 lastDragTime = now; 287 288 // Update position based on drag 289 var newPosition = touchStartPosition + deltaX; 290 291 // Keep position within bounds (wrap around) 292 if (newPosition >= contentWidth) { 293 newPosition = newPosition - contentWidth; 294 } else if (newPosition <= -contentWidth) { 295 newPosition = newPosition + contentWidth; 296 } 297 298 currentPosition = newPosition; 299 300 // Apply transform to all content elements 301 $wrapper.find('.news-ticker-content').each(function() { 302 this.style.transform = 'translateX(' + currentPosition + 'px)'; 303 }); 304 305 e.preventDefault(); 306 } 307 308 function handleTouchEnd(e) { 309 if (!isDragging) return; 310 311 isDragging = false;
312 313 // Apply momentum based on drag velocity 314 var momentum = dragVelocity * 0.1; 315 var targetPosition = currentPosition + momentum; 316 317 // Animate momentum smoothly 318 var momentumDuration = Math.abs(momentum) * 2; 319 var startTime = performance.now(); 320 var startPosition = currentPosition; 321 322 function animateMomentum(now) { 323 var elapsed = now - startTime; 324 var progress = Math.min(elapsed / Math.max(momentumDuration, 100), 1); 325 326 // Ease out 327 var easeProgress = 1 - Math.pow(1 - progress, 3); 328 329 currentPosition = startPosition + (targetPosition - startPosition) * easeProgress; 330 331 // Wrap position 332 if (currentPosition >= contentWidth) { 333 currentPosition = currentPosition - contentWidth; 334 } else if (currentPosition <= -contentWidth) { 335 currentPosition = currentPosition + contentWidth; 336 } 337 338 $wrapper.find('.news-ticker-content').each(function() { 339 this.style.transform = 'translateX(' + currentPosition + 'px)'; 340 }); 341 342 if (progress < 1) { 343 requestAnimationFrame(animateMomentum); 344 } else { 345 // Resume auto-scroll after momentum ends 346 setTimeout(function() { 347 isPaused = false; 348 }, 500); 349 } 350 } 351 352 if (Math.abs(momentum) > 10) { 353 requestAnimationFrame(animateMomentum); 354 } else { 355 // Resume auto-scroll after short delay 356 setTimeout(function() { 357 isPaused = false; 358 }, 500); 359 } 360 } 361 362 // Add touch event listeners 363 wrapper.addEventListener('touchstart', handleTouchStart, { passive: false }); 364 wrapper.addEventListener('touchmove', handleTouchMove, { passive: false }); 365 wrapper.addEventListener('touchend', handleTouchEnd, { passive: true }); 366 wrapper.addEventListener('touchcancel', handleTouchEnd, { passive: true }); 367 368 // Store cleanup functions 369 $ticker.data('touch-cleanup', function() { 370 wrapper.removeEventListener('touchstart', handleTouchStart); 371 wrapper.removeEventListener('touchmove', handleTouchMove); 372 wrapper.removeEventListener('touchend', handleTouchEnd); 373 wrapper.removeEventListener('touchcancel', handleTouchEnd); 374 }); 375 376 // Handle window resize - recalculate speed for mobile/desktop switch 377 var resizeTimer; 378 $(window).on('resize.tickeranimation', function() { 379 clearTimeout(resizeTimer); 380 resizeTimer = setTimeout(function() { 381 // Recalculate mobile state and speed 382 isMobile = window.innerWidth <= 768; 383 pixelsPerSecond = isMobile ? baseSpeed * 1.5 : baseSpeed; 384 385 stopAnimation(); 386 initInfiniteScroll(); 387 }, 250); 388 }); 389 390 // Store stop function for cleanup 391 $ticker.data('stop-animation', stopAnimation); 392 393 // Initialize 394 initInfiniteScroll(); 395 }, 396 397 initSlide: function($ticker, duration, pauseOnHover, direction) { 398 var $items = $ticker.find('.news-ticker-item'); 399 var currentIndex = 0; 400 var intervalId; 401 var isPaused = false; 402 var isAnimating = false; 403 404 if ($items.length === 0) { 405 return; 406 } 407 408 // Show first item 409 $items.eq(0).addClass('active'); 410 411 function showNextItem() { 412 if (isPaused || isAnimating) { 413 return; 414 } 415 416 isAnimating = true; 417 418 var $current = $items.eq(currentIndex); 419 var nextIndex = (currentIndex + 1) % $items.length; 420 var $next = $items.eq(nextIndex); 421 422 // Start exit animation for current 423 $current.removeClass('active'); 424 425 // After a short delay, show next item 426 setTimeout(function() { 427 $next.addClass('active'); 428 currentIndex = nextIndex; 429 isAnimating = false;
430 }, 100); 431 } 432 433 // Start the slide animation - infinite loop 434 intervalId = setInterval(showNextItem, duration); 435 436 // Pause on hover 437 if (pauseOnHover) { 438 $ticker.on('mouseenter', function() { 439 isPaused = true; 440 }); 441 442 $ticker.on('mouseleave', function() { 443 isPaused = false; 444 }); 445 } 446 447 // Store interval ID for cleanup 448 $ticker.data('ticker-interval', intervalId); 449 }, 450 451 initFade: function($ticker, duration, pauseOnHover) { 452 var $items = $ticker.find('.news-ticker-item'); 453 var currentIndex = 0; 454 var intervalId; 455 var isPaused = false; 456 var isAnimating = false; 457 458 if ($items.length === 0) { 459 return; 460 } 461 462 // Show first item 463 $items.eq(0).addClass('active'); 464 465 function showNextItem() { 466 if (isPaused || isAnimating) { 467 return; 468 } 469 470 isAnimating = true; 471 472 var $current = $items.eq(currentIndex); 473 var nextIndex = (currentIndex + 1) % $items.length; 474 var $next = $items.eq(nextIndex); 475 476 // Fade out current 477 $current.removeClass('active'); 478 479 // After fade transition, show next item 480 setTimeout(function() { 481 $next.addClass('active'); 482 currentIndex = nextIndex; 483 isAnimating = false; 484 }, 500); // Match CSS transition duration 485 } 486 487 // Start the fade animation - infinite loop 488 intervalId = setInterval(showNextItem, duration); 489 490 // Pause on hover 491 if (pauseOnHover) { 492 $ticker.on('mouseenter', function() { 493 isPaused = true; 494 }); 495 496 $ticker.on('mouseleave', function() { 497 isPaused = false; 498 }); 499 } 500 501 // Store interval ID for cleanup 502 $ticker.data('ticker-interval', intervalId); 503 }, 504 505 // Cleanup function 506 destroy: function($ticker) { 507 var intervalId = $ticker.data('ticker-interval'); 508 if (intervalId) { 509 clearInterval(intervalId); 510 } 511 var resetInterval = $ticker.data('reset-interval'); 512 if (resetInterval) { 513 clearInterval(resetInterval); 514 } 515 516 // Stop requestAnimationFrame animation 517 var stopAnimation = $ticker.data('stop-animation'); 518 if (stopAnimation && typeof stopAnimation === 'function') { 519 stopAnimation(); 520 } 521 522 // Clean up touch event listeners 523 var touchCleanup = $ticker.data('touch-cleanup'); 524 if (touchCleanup && typeof touchCleanup === 'function') { 525 touchCleanup(); 526 } 527 528 // Remove cloned tracks 529 var $wrapper = $ticker.find('.news-ticker-content-wrapper'); 530 $wrapper.find('.news-ticker-content').not(':first').remove(); 531 532 // Remove animation styles 533 $wrapper.find('.news-ticker-content').css({ 534 'animation': 'none', 535 'transform': '', 536 'animation-play-state': '', 537 'animation-duration': '' 538 }); 539 540 // Only remove animation-related handlers, not modal handlers 541 $ticker.off('mouseenter mouseleave'); 542 $(window).off('resize.tickeranimation'); 543 } 544 }; 545 546 // Initialize 547 NewsTicker.init(); 548 549 // Cleanup on widget removal 550 $(document).on('remove', '.news-ticker-wrapper', function() { 551 NewsTicker.destroy($(this)); 552 }); 553 554})(jQuery);
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.