PageSourceSearch

https://bakken.dk/assets/javascripts/hero-video.js

js bakken.dk collected 2026-10-02 08:08:26 UTC 36,822 bytes, 1,047 lines download raw bytes

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.