1/** 2 * Hero Video Controller - Enhanced YouTube Video Loop Management 3/** 4 * Hero Video Controller - Enhanced YouTube Video Loop Management 5 * 6 * This controller manages seamless looping of YouTube videos in the hero section with: 7 * - Uses mb_YTPlayer's built-in data-property parsing for clean initialization 8 * - Robust restart logic with multiple fallback strategies 9 * - Anti-flash overlay system to prevent YouTube thumbnail flash 10 * - Progressive timing detection for seamless looping 11 * - Manual loop handling (disabled YouTube's built-in loop for better control) 12 * - Multiple initialization fallbacks for reliability 13 * 14 * Key Features: 15 * - Automatic initialization from HTML data-property attribute 16 * - Overlay appears early to prevent any visual flash during restart 17 * - Fallback restart s // Force styles on wrapper - ensure it fills the hero__video container 18 $wrapper.each(function() { 19 this.style.setProperty('position', 'absolute', 'important'); 20 this.style.setProperty('top', '0', 'important'); 21 this.style.setProperty('left', '0', 'important'); 22 this.style.setProperty('width', '100%', 'important'); 23 this.style.setProperty('height', '100%', 'important'); 24 this.style.setProperty('max-width', 'none', 'important'); 25 this.style.setProperty('min-width', '100%', 'important'); 26 this.style.setProperty('margin', '0', 'important'); 27 });uilt-in events â manual restart â iframe detection 28 * - Responsive progress monitoring (500ms intervals) when video is stable 29 * - Comprehensive error handling and failure recovery 30 * - Fallback to background image after maximum restart failures 31 */ 32(function() { 33 'use strict'; 34 35 // Check for jQuery and retry if not available 36 function waitForJQuery() { 37 var $ = window.jQuery || window.$; 38 39 if (typeof $ === 'undefined') { 40 setTimeout(waitForJQuery, 100); 41 return; 42 } 43 44 // jQuery is available, set up the video controller 45 setupHeroVideo($); 46 } 47 48 function setupHeroVideo($) { 49 // Selectors for DOM elements - support both single video and multiple videos in sliders 50 var selectors = { 51 heroVideo: '.hero__video', 52 heroImage: '.hero__image-content picture', 53 videoPlayer: '#background-video, [id^="background-video"]' // Support both single video and indexed videos 54 }, 55 56 classNames = { 57 videoLoaded: 'video-loaded', 58 videoPlaying: 'video-playing', 59 videoTransitioning: 'video-transitioning' 60 }, 61 62 // Store cached references to DOM elements 63 dom = {}, 64 65 // Video state 66 videoState = { 67 isLoaded: false, 68 isPlaying: false, 69 attempts: 0, 70 restartTimer: null, 71 failureCount: 0, 72 maxFailures: 5, 73 isRestarting: false, 74 restartTriggered: false 75 }; 76 77 /** 78 * Handle video failure by showing the background image and cleaning up state 79 */ 80 function _handleVideoFailure() { 81 videoState.isLoaded = false; 82 videoState.isPlaying = false; 83 videoState.isRestarting = false; 84 videoState.restartTriggered = false; 85 videoState.failureCount = videoState.maxFailures; 86 _showBackgroundImage(); 87 } 88 89 /** 90 * Initialize function 91 */ 92 function initialize() { 93 // Check if jQuery and required libraries are available 94 if (typeof $ === 'undefined') { 95 return; 96 } 97 98 if (typeof $.fn.mb_YTPlayer === 'undefined') { 99 // Retry after a longer delay to allow library to load 100 setTimeout(initialize, 1000); 101 return; 102 } 103 104 // Set up DOM and cache references 105 _setupDOM(); 106 107 // On mobile, ensure hero image is always visible and skip video functionality 108 if (window.innerWidth <= 768) { 109 dom.$heroImage.css('opacity', '1'); 110 return; 111 } 112 113 // Only proceed if we have a video player 114 if (dom.$videoPlayer.length > 0) { 115 // Check if data-property is available (contains video configuration) 116 var dataProperty = dom.$videoPlayer.attr('data-property'); 117 118 if (dataProperty) { 119 _initVideoPlayer(); 120 } else {
121 _showBackgroundImage(); 122 } 123 } 124 } 125 126 /** 127 * Set up DOM elements and cache references 128 */ 129 function _setupDOM() { 130 dom.$heroVideo = $(selectors.heroVideo); 131 dom.$heroImage = $(selectors.heroImage); 132 dom.$videoPlayer = $(selectors.videoPlayer).first(); // Use the first video player found 133 } 134 135 /** 136 * Initialize the video player with event handlers 137 */ 138 function _initVideoPlayer() { 139 // Check if we're on mobile (768px or smaller) 140 if (window.innerWidth <= 768) { 141 // On mobile, ensure hero image is always visible and don't manipulate opacity 142 dom.$heroImage.css('opacity', '1'); 143 return; 144 } 145 146 // Do not hide the background image until the video is actually playing 147 // Opacity will be set to 0 in _showVideo() only after video starts 148 149 // Find the existing overlay in the HTML 150 dom.$overlay = dom.$heroVideo.find('.hero-video-overlay'); 151 dom.$initialOverlay = $('.hero-video-overlay--initial'); // Initial overlay on picture 152 dom.$antiThumbnailOverlay = $('.video-anti-thumbnail-overlay'); // Anti-thumbnail overlay 153 154 // If overlay doesn't exist, create it (fallback) 155 if (dom.$overlay.length === 0) { 156 dom.$overlay = $('<div class="hero-video-overlay"></div>').prependTo(dom.$heroVideo); 157 } 158 159 // If anti-thumbnail overlay doesn't exist, create it (fallback) 160 if (dom.$antiThumbnailOverlay.length === 0) { 161 dom.$antiThumbnailOverlay = $('<div class="video-anti-thumbnail-overlay"></div>').prependTo(dom.$heroVideo); 162 } 163 164 // CRITICAL: Ensure overlay is hidden initially to allow video to load properly 165 if (dom.$overlay && dom.$overlay.length > 0) { 166 dom.$overlay.css({ 167 'display': 'none', 168 'position': 'absolute', 169 'top': '0', 170 'left': '0', 171 'width': '100%', 172 'height': '100%', 173 'z-index': '10', 174 'pointer-events': 'none', 175 'background': 'transparent' 176 }); 177 } 178 179 // Remove the overlay element from the DOM initially; add it only after video starts 180 if (dom.$initialOverlay && dom.$initialOverlay.length > 0) { 181 dom.$initialOverlay.remove(); 182 } 183 184 // Initialize the YouTube player 185 _initializePlayer(); 186 } 187 188 /** 189 * Initialize the YouTube player 190 */ 191 function _initializePlayer() { 192 try { 193 // Check if the element has the data-property attribute 194 var dataProperty = dom.$videoPlayer.attr('data-property'); 195 196 if (!dataProperty) { 197 _showBackgroundImage(); 198 return; 199 } 200 201 // Use mb_YTPlayer's built-in initialization which automatically reads data-property 202 try { 203 // Set up a YTPReady event listener BEFORE initializing 204 dom.$videoPlayer.on('YTPReady', function() { 205 videoState.isLoaded = true; 206 _setupVideoEvents(); 207 // Try to play after a short delay 208 setTimeout(function() { 209 if (!videoState.isPlaying) { 210 try { 211 dom.$videoPlayer.YTPPlay(); 212 } catch (e) {} 213 } 214 }, 500); 215 }); 216 217 // Initialize the player 218 dom.$videoPlayer.mb_YTPlayer(); 219 220 // Extended fallback: keep trying to play for up to 10 seconds (server slow) 221 var playAttempts = 0; 222 var maxPlayAttempts = 20; // 20 x 500ms = 10s 223 function tryPlayLoop() { 224 if (videoState.isLoaded && videoState.isPlaying) return; 225 playAttempts++; 226 try { 227 dom.$videoPlayer.YTPPlay(); 228 } catch (e) {} 229 if (playAttempts < maxPlayAttempts && (!videoState.isLoaded || !videoState.isPlaying)) { 230 setTimeout(tryPlayLoop, 500); 231 } 232 } 233 setTimeout(tryPlayLoop, 3500); // Start after initial fallback 234 235 // Additional timeout fallback in case YTPReady doesn't fire (increased to 6s) 236 setTimeout(function() { 237 if (!videoState.isLoaded) { 238 videoState.isLoaded = true; 239 _setupVideoEvents(); 240 // Try to play 241 setTimeout(function() { 242 try { 243 dom.$videoPlayer.YTPPlay(); 244 } catch (e) {} 245 }, 200); 246 } 247 }, 6000); 248 249 } catch (e) { 250 // Ultimate fallback - just detect if video iframe exists and show video 251 setTimeout(function() { 252 var $iframe = dom.$videoPlayer.find('iframe'); 253 if ($iframe.length > 0) { 254 videoState.isLoaded = true; 255 videoState.isPlaying = true; 256 _showVideo(); 257 _setupVideoEvents(); 258 } else { 259 _handleVideoFailure(); 260 } 261 }, 2000); // Increased fallback delay 262 } 263 264 } catch (e) { 265 _handleVideoFailure(); 266 } 267 } 268 269 /** 270 * Setup video event handlers 271 */ 272 function _setupVideoEvents() { 273 // Listen for YTPlayer events 274 dom.$videoPlayer.on('YTPStart YTPPlay', function () { 275 videoState.isPlaying = true; 276 videoState.isRestarting = false;
277 videoState.restartTriggered = false; // Reset restart flag when video starts playing 278 clearTimeout(videoState.restartTimer); 279 _showVideo(); 280 281 // Handle responsive sizing after video starts 282 setTimeout(function() { 283 _handleResponsiveResize(); 284 _forceStyleOverride(); 285 }, 100); 286 287 // Continue forcing styles periodically 288 setTimeout(function() { 289 _forceStyleOverride(); 290 }, 500); 291 setTimeout(function() { 292 _forceStyleOverride(); 293 }, 1000); 294 }); 295 296 // FINAL fallback: after 8 seconds, if iframe is present but video not playing, force video display 297 setTimeout(function() { 298 if (!videoState.isPlaying && !videoState.isLoaded) { 299 var $videoElement = $('#background-video'); 300 if ($videoElement.length > 0) { 301 var $iframe = $videoElement.find('iframe'); 302 if ($iframe.length > 0) { 303 videoState.isLoaded = true; 304 videoState.isPlaying = true; 305 // Set up DOM if not already done 306 if (!dom.$videoPlayer) { 307 dom.$heroVideo = $('.hero__video'); 308 dom.$heroImage = $('.hero__image-content picture'); 309 dom.$videoPlayer = $('#background-video'); 310 dom.$overlay = dom.$heroVideo.find('.hero-video-overlay'); 311 } 312 _showVideo(); 313 } 314 } 315 } 316 }, 8000); // Final fallback after 8 seconds 317 318 dom.$videoPlayer.on('YTPPause', function () { 319 videoState.isPlaying = false; 320 }); 321 322 dom.$videoPlayer.on('YTPEnd', function () { 323 // Always restart on YTPEnd for continuous looping 324 if (!videoState.isRestarting) { 325 // CRITICAL: Show overlay immediately to prevent thumbnail flash 326 _showOverlay(); 327 328 // Mark as not playing and transitioning 329 videoState.isPlaying = false; 330 videoState.restartTriggered = true; 331 dom.$heroVideo.removeClass(classNames.videoPlaying); 332 dom.$heroVideo.addClass(classNames.videoTransitioning); 333 334 // Clear any existing restart timer 335 clearTimeout(videoState.restartTimer); 336 337 // Restart immediately when YTPEnd fires 338 _restartVideo(); 339 } 340 }); 341 342 // Set up window resize handler for responsive video 343 var resizeTimeout; 344 $(window).on('resize.heroVideo', function() { 345 clearTimeout(resizeTimeout); 346 resizeTimeout = setTimeout(function() { 347 _handleResponsiveResize(); 348 _forceStyleOverride(); 349 }, 250); 350 }); 351 352 // Simplified monitoring - check state immediately and then periodically 353 _checkVideoState(); 354 355 // Set up basic monitoring 356 var stateMonitor = setInterval(function () { 357 if (videoState.isLoaded) { 358 _checkVideoState(); 359 360 // Check if video is near the end to prevent thumbnail flash 361 _checkVideoProgress(); 362 } 363 364 // Stop monitoring after reasonable attempts if video is working 365 if (videoState.isPlaying && videoState.attempts > 20) { 366 clearInterval(stateMonitor); 367 368 // Set up less frequent monitoring for loop maintenance with aggressive restart detection 369 var loopMonitor = setInterval(function() { 370 if (videoState.isLoaded) { 371 _checkVideoProgress(); 372 _checkForVideoEnd(); // Additional end detection 373 } 374 }, 100); // Check every 100ms for ultra-responsive restart detection (more aggressive than before) 375 } 376 377 if (videoState.attempts > 40) { 378 clearInterval(stateMonitor); 379 if (!videoState.isPlaying) { 380 _handleVideoFailure(); 381 } 382 } 383 }, 1000); // Check every 1 second 384 385 // Setup user interaction handlers 386 _setupUserInteractionHandlers(); 387 } 388 389 /** 390 * Check the current state of the video 391 */ 392 function _checkVideoState() { 393 if (!videoState.isLoaded) return; 394 395 videoState.attempts++; 396 397 try { 398 // Try to get the player state using the mb_YTPlayer methods 399 if (typeof dom.$videoPlayer.YTPGetPlayer === 'function') { 400 var player = dom.$videoPlayer.YTPGetPlayer(); 401 if (player && typeof player.getPlayerState === 'function') { 402 var state = player.getPlayerState(); 403 404 // YouTube player states: -1 (unstarted), 0 (ended), 1 (playing), 2 (paused), 3 (buffering), 5 (cued) 405 if (state === 1 || state === 3) { // Playing or buffering 406 if (!videoState.isPlaying) { 407 videoState.isPlaying = true; 408 _showVideo(); 409 } 410 } else if (state === 0) { // Ended 411 // Only restart if not already restarting 412 if (!videoState.isRestarting) { 413 // CRITICAL: Show overlay immediately to prevent thumbnail flash 414 _showOverlay(); 415 videoState.isPlaying = false;
416 dom.$heroVideo.removeClass(classNames.videoPlaying); 417 dom.$heroVideo.addClass(classNames.videoTransitioning); 418 _restartVideo(); 419 } 420 } else if (state === 2) { // Paused 421 try { 422 dom.$videoPlayer.YTPPlay(); 423 } catch (e) { 424 // Silent fail 425 } 426 } 427 } 428 } 429 430 // Fallback: Check for video element presence and try to show video 431 var videoElement = dom.$videoPlayer.find('iframe, video').first(); 432 if (videoElement.length > 0 && !videoState.isPlaying && videoState.attempts < 15) { 433 videoState.isPlaying = true; // Assume it's playing 434 _showVideo(); 435 } 436 437 // If we still don't have a playing video after some attempts, try a different approach 438 if (videoState.attempts > 10 && !videoState.isPlaying) { 439 try { 440 dom.$videoPlayer.YTPPlay(); 441 } catch (e) { 442 // Silent fail 443 } 444 } 445 446 } catch (e) { 447 if (videoState.attempts < 5) { 448 // Try to show video anyway on early attempts 449 videoState.isPlaying = true; 450 _showVideo(); 451 } 452 } 453 } 454 455 /** 456 * Check video progress and prepare for restart before end to prevent thumbnail flash 457 * ULTRA-AGGRESSIVE: Restart at 50% completion to completely eliminate thumbnail flash 458 */ 459 function _checkVideoProgress() { 460 if (!videoState.isLoaded || !videoState.isPlaying || videoState.isRestarting) return; 461 462 try { 463 if (typeof dom.$videoPlayer.YTPGetPlayer === 'function') { 464 var player = dom.$videoPlayer.YTPGetPlayer(); 465 if (player && typeof player.getCurrentTime === 'function' && typeof player.getDuration === 'function') { 466 var currentTime = player.getCurrentTime(); 467 var duration = player.getDuration(); 468 469 // Check if we have valid time data 470 if (duration > 0 && currentTime > 0) { 471 var progress = (currentTime / duration) * 100; 472 473 // ULTRA-AGGRESSIVE: Restart at 50% completion to prevent any thumbnail flash 474 if (progress >= 50 && !videoState.restartTriggered && !videoState.isRestarting) { 475 // Set flags to prevent multiple triggers during this restart 476 videoState.restartTriggered = true; 477 videoState.isRestarting = true; 478 479 // Removed console.log for production 480 481 // Show overlay immediately 482 _showOverlay(); 483 484 // Mark as transitioning 485 videoState.isPlaying = false; 486 dom.$heroVideo.removeClass(classNames.videoPlaying); 487 dom.$heroVideo.addClass(classNames.videoTransitioning); 488 489 // Start restart process 490 _restartVideo(); 491 } 492 } 493 } 494 } 495 } catch (e) { 496 // Ignore errors in progress checking to avoid breaking the video 497 } 498 } 499 500 /** 501 * Show overlay immediately - critical for preventing YouTube thumbnail flash 502 */ 503 function _showOverlay() { 504 // Only show overlays during video restarts or transitions, never on initial load 505 if (videoState.isRestarting) { 506 if (dom.$overlay && dom.$overlay.length > 0) { 507 dom.$overlay.css('display', 'block'); 508 }
509 // Show anti-thumbnail overlay for ultra-aggressive coverage 510 if (dom.$antiThumbnailOverlay && dom.$antiThumbnailOverlay.length > 0) { 511 dom.$antiThumbnailOverlay.addClass('show'); 512 } 513 // Also show initial overlay during restart to cover everything 514 if (dom.$initialOverlay && dom.$initialOverlay.length > 0) { 515 dom.$initialOverlay.css('display', 'block'); 516 } 517 } 518 } 519 520 /** 521 * Hide overlay immediately when video is confirmed playing 522 */ 523 function _hideOverlay() { 524 if (dom.$overlay && dom.$overlay.length > 0) { 525 dom.$overlay.css('display', 'none'); 526 } 527 528 // Hide anti-thumbnail overlay when video is playing 529 if (dom.$antiThumbnailOverlay && dom.$antiThumbnailOverlay.length > 0) { 530 dom.$antiThumbnailOverlay.removeClass('show'); 531 } 532 } 533 534 /** 535 * Hide initial overlay when video is confirmed playing 536 */ 537 function _hideInitialOverlay() { 538 if (dom.$initialOverlay && dom.$initialOverlay.length > 0) { 539 dom.$initialOverlay.css('display', 'none'); 540 } 541 } 542 543 /** 544 * Show the video and hide the background image 545 */ 546 function _showVideo() { 547 // Don't manipulate opacity on mobile 548 if (window.innerWidth <= 768) { 549 return; 550 } 551 // Only hide the image if the video is actually loaded, playing, and the iframe is present and visible 552 if (videoState.isLoaded && videoState.isPlaying) { 553 var $iframe = dom.$videoPlayer ? dom.$videoPlayer.find('iframe') : $(); 554 if ($iframe.length > 0 && $iframe.is(':visible')) { 555 dom.$heroVideo.addClass(classNames.videoPlaying); 556 dom.$heroVideo.removeClass(classNames.videoTransitioning); 557 dom.$heroImage.animate({ opacity: 0 }, 500); 558 } 559 } 560 // Add the overlay element back only after video is confirmed playing 561 if ($('.hero__video .hero-video-overlay--initial').length === 0) { 562 var overlay = $('<div class="hero-video-overlay hero-video-overlay--initial" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: transparent; z-index: 5; pointer-events: none; display: none;"></div>'); 563 $('.hero__video').append(overlay); 564 } 565 // Hide both overlays when video is confirmed playing - critical timing 566 _hideOverlay(); 567 _hideInitialOverlay(); 568 } 569 570 /** 571 * Show the background image and hide/fade the video 572 */ 573 function _showBackgroundImage() { 574 // Don't manipulate opacity on mobile 575 if (window.innerWidth <= 768) { 576 return; 577 } 578 579 dom.$heroVideo.removeClass(classNames.videoPlaying); 580 dom.$heroVideo.removeClass(classNames.videoTransitioning); 581 582 // Ensure background image is fully visible 583 dom.$heroImage.stop(true, false).animate({ opacity: 1 }, 500); 584 585 // Hide video overlay when showing background 586 _hideOverlay(); 587 588 // IMPORTANT: Hide initial overlay when falling back to background image 589 // This ensures the background image is visible without any overlay blocking it 590 if (dom.$initialOverlay && dom.$initialOverlay.length > 0) { 591 dom.$initialOverlay.css('display', 'none'); 592 } 593 594 // Also hide anti-thumbnail overlay 595 if (dom.$antiThumbnailOverlay && dom.$antiThumbnailOverlay.length > 0) { 596 dom.$antiThumbnailOverlay.css('display', 'none'); 597 } 598 599 // Remove any red-overlay class that might be hiding the image 600 dom.$heroImage.closest('.hero__image-content').removeClass('red-overlay'); 601 602 // Ensure the video container doesn't interfere 603 if (dom.$heroVideo && dom.$heroVideo.length > 0) { 604 dom.$heroVideo.css('display', 'none'); 605 } 606 } 607 608 /** 609 * Simplified and robust video restart function 610 */ 611 function _restartVideo() { 612 // Check if we've had too many failures 613 if (videoState.failureCount >= videoState.maxFailures) { 614 _handleVideoFailure(); 615 return; 616 } 617 618 // CRITICAL: Show overlay immediately to prevent any flash during restart 619 _showOverlay(); 620 621 // Clear any existing restart timer 622 clearTimeout(videoState.restartTimer); 623 624 // Ensure video is marked as not playing during restart 625 videoState.isPlaying = false;
626 videoState.isRestarting = true; 627 dom.$heroVideo.removeClass(classNames.videoPlaying); 628 dom.$heroVideo.addClass(classNames.videoTransitioning); 629 630 try { 631 // Method 1: Try YTPRestart first (most reliable for looping) 632 if (typeof dom.$videoPlayer.YTPRestart === 'function') { 633 try { 634 dom.$videoPlayer.YTPRestart(); 635 } catch (e) { 636 // Silent fail 637 } 638 } 639 640 // Method 2: Fallback to seek + play approach 641 setTimeout(function() { 642 if (typeof dom.$videoPlayer.YTPSeekTo === 'function') { 643 dom.$videoPlayer.YTPSeekTo(0); 644 } 645 646 if (typeof dom.$videoPlayer.YTPPlay === 'function') { 647 dom.$videoPlayer.YTPPlay(); 648 } 649 650 // Reset restart flags after restart attempts 651 setTimeout(function() { 652 videoState.restartTriggered = false; 653 videoState.isRestarting = false; 654 }, 1000); 655 656 }, 200); // Quick fallback 657 658 // Check if restart worked after reasonable delay 659 videoState.restartTimer = setTimeout(function() { 660 if (!videoState.isPlaying) { 661 // Try one more restart method 662 if (typeof dom.$videoPlayer.YTPPlay === 'function') { 663 try { 664 dom.$videoPlayer.YTPPlay(); 665 666 // Reset flags after additional attempt 667 setTimeout(function() { 668 videoState.restartTriggered = false; 669 videoState.isRestarting = false; 670 }, 500); 671 672 } catch (e) { 673 // Silent fail 674 } 675 } 676 677 // Final status check 678 setTimeout(function() { 679 if (!videoState.isPlaying) { 680 videoState.failureCount++; 681 682 // Always reset flags to allow future attempts 683 videoState.restartTriggered = false; 684 videoState.isRestarting = false; 685 686 if (videoState.failureCount >= videoState.maxFailures) { 687 _showBackgroundImage(); 688 } 689 } else { 690 // Success! 691 videoState.failureCount = 0; 692 } 693 }, 1500); 694 695 } else { 696 // Success on first check 697 videoState.failureCount = 0; 698 } 699 }, 1000); // Check after 1 second 700 701 } catch (e) { 702 videoState.failureCount++; 703 704 // Reset flags even on error 705 videoState.restartTriggered = false; 706 videoState.isRestarting = false; 707 708 if (videoState.failureCount >= videoState.maxFailures) { 709 _showBackgroundImage(); 710 } 711 } 712 } 713 714 /** 715 * Additional check for video end - more aggressive detection 716 */ 717 function _checkForVideoEnd() { 718 if (!videoState.isLoaded || videoState.isRestarting) return; 719 720 try { 721 if (typeof dom.$videoPlayer.YTPGetPlayer === 'function') { 722 var player = dom.$videoPlayer.YTPGetPlayer(); 723 if (player && typeof player.getCurrentTime === 'function' && typeof player.getDuration === 'function') { 724 var currentTime = player.getCurrentTime(); 725 var duration = player.getDuration(); 726 727 // If video has ended (currentTime >= duration or very close) 728 if (duration > 0 && currentTime > 0) { 729 var timeRemaining = duration - currentTime; 730 731 // Trigger restart if video has basically ended (within 0.1 seconds) 732 if (timeRemaining <= 0.1 && !videoState.isRestarting) { 733 // CRITICAL: Show overlay immediately to prevent thumbnail flash 734 _showOverlay(); 735 736 // Mark as not playing and transitioning 737 videoState.isPlaying = false; 738 videoState.isRestarting = true; 739 dom.$heroVideo.removeClass(classNames.videoPlaying); 740 dom.$heroVideo.addClass(classNames.videoTransitioning); 741 742 // Force restart 743 _restartVideo(); 744 } 745 } 746 747 // Also check player state for ended state 748 if (typeof player.getPlayerState === 'function') { 749 var state = player.getPlayerState(); 750 if (state === 0 && !videoState.isRestarting) { // Video ended 751 _showOverlay(); 752 videoState.isPlaying = false;
753 videoState.isRestarting = true; 754 dom.$heroVideo.removeClass(classNames.videoPlaying); 755 dom.$heroVideo.addClass(classNames.videoTransitioning); 756 757 _restartVideo(); 758 } 759 } 760 } 761 } 762 } catch (e) { 763 // Ignore errors in this aggressive checking to avoid breaking the video 764 } 765 } 766 767 /** 768 * Handle responsive video sizing 769 */ 770 function _handleResponsiveResize() { 771 if (!videoState.isLoaded || !dom.$videoPlayer || dom.$videoPlayer.length === 0) return; 772 773 try { 774 var $iframe = dom.$videoPlayer.find('iframe'); 775 var $wrapper = dom.$videoPlayer.find('.mb_YTPlayer'); 776 777 // Override iframe styles - use centering approach for proper coverage 778 if ($iframe.length > 0) { 779 $iframe.attr('style', 'position: absolute !important; top: 50% !important; left: 50% !important; width: 150% !important; margin: 0 !important; padding: 0 !important; object-fit: cover !important; border: none !important; transform: translate(-50%, -50%) !important; max-width: none !important; min-width: 150% !important;'); 780 } 781 782 // Override wrapper styles - fill container completely 783 if ($wrapper.length > 0) { 784 $wrapper.attr('style', 'position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; margin: 0 !important; max-width: none !important; min-width: 100% !important;'); 785 } 786 787 // Also target the video player container itself - fill container completely 788 if (dom.$videoPlayer.length > 0) { 789 dom.$videoPlayer.css({ 790 'position': 'absolute', 791 'top': '0', 792 'left': '0', 793 'width': '100%', 794 'height': '100%', 795 'margin': '0', 796 'padding': '0', 797 'max-width': 'none', 798 'min-width': '100%' 799 }); 800 } 801 } catch (e) { 802 // Silent fail - don't break video functionality 803 } 804 } 805 806 /** 807 * Force override of inline styles - runs aggressively to combat mb_YTPlayer 808 */ 809 function _forceStyleOverride() { 810 if (!dom.$videoPlayer || dom.$videoPlayer.length === 0) return; 811 812 try { 813 // Find all video-related elements 814 var $iframe = dom.$videoPlayer.find('iframe'); 815 var $wrapper = dom.$videoPlayer.find('.mb_YTPlayer'); 816 var $backgroundVideo = $('#background-video'); 817 818 // Force styles on the background-video element - fill container completely 819 if ($backgroundVideo.length > 0) { 820 $backgroundVideo[0].style.setProperty('position', 'absolute', 'important'); 821 $backgroundVideo[0].style.setProperty('top', '0', 'important'); 822 $backgroundVideo[0].style.setProperty('left', '0', 'important'); 823 $backgroundVideo[0].style.setProperty('width', '100%', 'important'); 824 $backgroundVideo[0].style.setProperty('height', '100%', 'important'); 825 $backgroundVideo[0].style.setProperty('margin', '0', 'important'); 826 $backgroundVideo[0].style.setProperty('padding', '0', 'important'); 827 $backgroundVideo[0].style.setProperty('max-width', 'none', 'important'); 828 $backgroundVideo[0].style.setProperty('min-width', '100%', 'important'); 829 } 830 831 // Force styles on iframe - use centering approach with wider size for full coverage 832 $iframe.each(function() { 833 this.style.setProperty('position', 'absolute', 'important'); 834 this.style.setProperty('top', '50%', 'important'); 835 this.style.setProperty('left', '50%', 'important'); 836 this.style.setProperty('width', '150%', 'important'); 837 this.style.setProperty('max-width', 'none', 'important'); 838 this.style.setProperty('min-width', '150%', 'important'); 839 this.style.setProperty('margin', '0', 'important'); 840 this.style.setProperty('padding', '0', 'important'); 841 this.style.setProperty('transform', 'translate(-50%, -50%)', 'important'); 842 this.style.setProperty('object-fit', 'cover', 'important'); 843 }); 844 845 // Force styles on wrapper - remove height constraints 846 $wrapper.each(function() { 847 this.style.setProperty('position', 'absolute', 'important'); 848 this.style.setProperty('top', '0', 'important'); 849 this.style.setProperty('left', '0', 'important'); 850 this.style.setProperty('width', '100%', 'important'); 851 this.style.setProperty('height', '100%', 'important'); 852 this.style.setProperty('max-width', 'none', 'important'); 853 this.style.setProperty('min-width', '100%', 'important'); 854 this.style.setProperty('margin', '0', 'important'); 855 }); 856 857 // Also ensure the video container itself fills completely 858 if (dom.$videoPlayer.length > 0) { 859 dom.$videoPlayer.each(function() { 860 this.style.setProperty('max-width', 'none', 'important'); 861 this.style.setProperty('width', '100%', 'important'); 862 this.style.setProperty('height', '100%', 'important'); 863 this.style.setProperty('min-width', '100%', 'important'); 864 }); 865 } 866 867 } catch (e) { 868 // Silent fail 869 } 870 } 871 872 /** 873 * Handle user interactions for autoplay policies 874 */ 875 function _setupUserInteractionHandlers() { 876 var userInteractionHandler = function(e) { 877 if (videoState.isLoaded && !videoState.isPlaying) { 878 try { 879 dom.$videoPlayer.YTPPlay(); 880 } catch (e) { 881 // Silent fail 882 } 883 } 884 // Remove the handler after first interaction 885 $(document).off('click scroll touchstart keydown', userInteractionHandler); 886 }; 887 888 // Listen for user interactions to help with autoplay policies 889 $(document).on('click scroll touchstart keydown', userInteractionHandler); 890 } 891 892 // Auto-initialize when document is ready 893 $(document).ready(function() { 894 // Check if we have a video element on the page before initializing 895 var $videoElement = $('#background-video'); 896 897 if ($videoElement.length === 0) { 898 return; 899 } 900 901 // Add a small delay to ensure all scripts are loaded 902 setTimeout(function() { 903 try { 904 initialize(); 905 } catch (e) { 906 // Silent fail 907 } 908 }, 500); 909 910 // Add a fallback initialization after a longer delay 911 setTimeout(function() { 912 if (!videoState.isLoaded && !videoState.isPlaying) { 913 var $videoElement = $('#background-video'); 914 if ($videoElement.length > 0) { 915 var $iframe = $videoElement.find('iframe'); 916 if ($iframe.length > 0) { 917 videoState.isLoaded = true; 918 videoState.isPlaying = true; 919 920 // Set up DOM if not already done 921 if (!dom.$videoPlayer) { 922 dom.$heroVideo = $('.hero__video'); 923 dom.$heroImage = $('.hero__image-content picture'); 924 dom.$videoPlayer = $('#background-video');
925 dom.$overlay = dom.$heroVideo.find('.hero-video-overlay'); 926 } 927 928 _showVideo(); 929 } 930 } 931 } 932 }, 3000); // Fallback after 3 seconds 933 }); 934 935 // Add resize event handler to handle screen size changes 936 $(window).on('resize.heroVideo', function() { 937 // Set up DOM if not already done 938 if (!dom.$heroImage) { 939 dom.$heroImage = $('.hero__image-content picture'); 940 } 941 942 // Ensure hero image is visible on mobile 943 if (window.innerWidth <= 768) { 944 dom.$heroImage.css('opacity', '1'); 945 } 946 }); 947 } 948 949 // Start the process 950 waitForJQuery(); 951 952 /** 953 * Cleanup function to remove event listeners 954 */ 955 function _cleanup() { 956 // Remove resize event listener 957 $(window).off('resize.heroVideo'); 958 959 // Clear any timers 960 clearTimeout(videoState.restartTimer); 961 } 962 963 // Add cleanup on page unload, only after jQuery is available 964 if (typeof $ !== 'undefined') { 965 $(window).on('beforeunload', _cleanup); 966 } 967 968 // Expose some functions globally for debugging, only if $ is available 969 if (typeof window !== 'undefined') { 970 window.heroVideoDebug = { 971 getState: function() { 972 return videoState; 973 }, 974 getDom: function() { 975 return { 976 ...dom, 977 initialOverlay: dom.$initialOverlay ? dom.$initialOverlay.length : 0, 978 antiThumbnailOverlay: dom.$antiThumbnailOverlay ? dom.$antiThumbnailOverlay.length : 0 979 }; 980 }, 981 forceRestart: function() { 982 videoState.restartTriggered = false; 983 videoState.isRestarting = false; 984 videoState.failureCount = 0; 985 _restartVideo(); 986 }, 987 forceLoop: function() { 988 if (dom.$videoPlayer && typeof dom.$videoPlayer.YTPGetPlayer === 'function') { 989 var player = dom.$videoPlayer.YTPGetPlayer(); 990 if (player && typeof player.getDuration === 'function' && typeof player.seekTo === 'function') { 991 var duration = player.getDuration(); 992 if (duration > 2) { 993 player.seekTo(duration - 1); 994 } 995 } 996 } 997 }, 998 resetFlags: function() { 999 videoState.restartTriggered = false; 1000 videoState.isRestarting = false; 1001 videoState.failureCount = 0; 1002 }, 1003 forcePlay: function() { 1004 if (dom.$videoPlayer && typeof dom.$videoPlayer.YTPPlay === 'function') { 1005 dom.$videoPlayer.YTPPlay(); 1006 } 1007 }, 1008 testAllMethods: function() { 1009 if (dom.$videoPlayer && dom.$videoPlayer.length > 0) { 1010 if (typeof dom.$videoPlayer.YTPRestart === 'function') { 1011 dom.$videoPlayer.YTPRestart(); 1012 } 1013 setTimeout(function() { 1014 if (typeof dom.$videoPlayer.YTPSeekTo === 'function') { 1015 dom.$videoPlayer.YTPSeekTo(0); 1016 } 1017 if (typeof dom.$videoPlayer.YTPPlay === 'function') { 1018 dom.$videoPlayer.YTPPlay(); 1019 } 1020 }, 1000); 1021 } 1022 }, 1023 checkMethods: function() { 1024 if (dom.$videoPlayer && dom.$videoPlayer.length > 0) { 1025 var info = { 1026 YTPPlay: typeof dom.$videoPlayer.YTPPlay, 1027 YTPSeekTo: typeof dom.$videoPlayer.YTPSeekTo, 1028 YTPRestart: typeof dom.$videoPlayer.YTPRestart, 1029 YTPGetPlayer: typeof dom.$videoPlayer.YTPGetPlayer 1030 }; 1031 if (typeof dom.$videoPlayer.YTPGetPlayer === 'function') { 1032 var player = dom.$videoPlayer.YTPGetPlayer(); 1033 if (player) { 1034 info.playerState = player.getPlayerState ? player.getPlayerState() : 'unknown'; 1035 info.currentTime = player.getCurrentTime ? player.getCurrentTime() : 'unknown'; 1036 info.duration = player.getDuration ? player.getDuration() : 'unknown'; 1037 } 1038 } 1039 return info; 1040 } else { 1041 return { error: 'No video player found' }; 1042 } 1043 } 1044 }; 1045 } 1046 1047})();
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.