PageSourceSearch

https://www.juliencresp.com/wp-content/themes/fluxus/js/main.js?ver=1.8.2

js juliencresp.com collected 2026-10-04 02:16:24 UTC 29,699 bytes, 955 lines download raw bytes

1(function($, helpers) {
2  $(function() {
3    // --------------------------------------------------------------------
4    // 1. Setup Variables
5    // --------------------------------------------------------------------
6
7    var $window = $(window),
8      $html = $('html'),
9      $main = $('#main'),
10      $header = $('.js-site-header'),
11      $footer = $('.js-site-footer'),
12      isLayoutHorizontal = $html.is('.horizontal-page'),
13      isLayoutWithoutScroll = isLayoutHorizontal && $html.is('.no-scroll');
14
15    // --------------------------------------------------------------------
16    // 2. Setup plugins
17    // --------------------------------------------------------------------
18
19    // Improves click response on mobile devices.
20    // We Need to check if it's loaded because sometimes it gets blocked by uBlock
21    window.FastClick && window.FastClick.attach(document.body);
22
23    //
24    // Setup navigation helpers.
25    //
26    // globalNavigation provides ability to scroll content with keyboard
27    // arrows, mouse wheel and simple API calls used by .js-nav-tip.
28    //
29    window.globalNavigation = (function() {
30      var flashClassOnHtml = function(className) {
31        $('html').addClass(className);
32        setTimeout(function() {
33          $('html').removeClass(className);
34        }, 100);
35      };
36
37      var nav = new Navigation({
38        onNextItem: function() {
39          flashClassOnHtml('global-navigation-on-next');
40        },
41        onPreviousItem: function() {
42          flashClassOnHtml('global-navigation-on-prev');
43        },
44        onActive: function() {
45          $('.js-nav-tip').addClass('nav-tip--visible');
46        },
47        onInactive: function() {
48          $('.js-nav-tip').removeClass('nav-tip--visible');
49        }
50      });
51
52      $('.js-nav-tip__next').click(nav.nextItem);
53      $('.js-nav-tip__prev').click(nav.previousItem);
54      nav.setItems($('.js-global-nav-item'));
55
56      return nav;
57    })();
58
59    // Appreciate project plugin
60    $('.js-btn-appreciate').appreciate();
61
62    // Fluxus Lightbox
63    $('.js-link-to-image, .gallery--link-to-file a').fluxusLightbox();
64
65    //
66    // Sharrre plugin
67    //
68    var getSharrreOptions = function($el) {
69      var options = {
70        services: $el.data('services'),
71        buttonsTitle: $el.data('buttons-title') || ''
72      };
73
74      // Turn array into Object with structure { [socialNetwork: string]: true }
75      options.services = _.object(
76        _.map(options.services.split(','), function(service) {
77          return [service, true];
78        })
79      );
80      return options;
81    };
82
83    helpers.runIfFound($('.js-share-footer'), function($el) {
84      var options = getSharrreOptions($el),
85        buttonsTemplate;
86
87      if (options.buttonsTitle) {
88        buttonsTemplate =
89          '<div class="share-widget__title">' +
90          options.buttonsTitle +
91          '<a href="#" class="share-widget__close close"></a>' +
92          '</div>';
93      }
94
95      $el.sharrre({
96        share: options.services,
97        buttonsTemplate: buttonsTemplate,
98        template: '<b class="share">{title}</b>',
99        render: function(self, options) {
100          var html = this.template.replace('{title}', options.title);
101          $(self.element).html(html);
102          $el.show();
103        }
104      });
105    });
106
107    helpers.runIfFound($('#sharrre-project'), function($el) {
108      var options = getSharrreOptions($el),
109        buttonsTemplate;
110
111      if (options.buttonsTitle) {
112        buttonsTemplate =
113          '<div class="arrow"></div><div class="share-widget__title">' +
114          options.buttonsTitle +
115          '<a href="#" class="share-widget__close close"></a></div>';
116      }
117
118      $el.sharrre({
119        share: options.services,
120        buttonsTemplate: buttonsTemplate,
121        enableCounter: options.enableCounter,
122        template:
123          '<span class="icon"></span><div class="box">' +
124          '<a class="share" href="#">{title}</a>' +
125          '</div>',
126        render: function(self, options) {
127          var html = this.template.replace('{title}', options.title);
128          $(self.element).html(html);
129          $el.css('display', 'inline-block');
130        },
131        afterLoadButtons: function() {
132          var index = 0,
133            $buttons = this.$el.find('.button'),
134            count = $buttons.each(function() {
135              index++;
136              $(this).addClass('button-' + index);
137            }).length;
138          this.$el.addClass('social-services-' + count);
139        }
140      });
141    });
142
143    // --------------------------------------------------------------------
144    // 3. Define misc functions and apply fixes
145    // --------------------------------------------------------------------
146
147    var $wpadminbar = $('#wpadminbar');
148    var wpAdminBarHeight = function() {
149      return $wpadminbar.length ? $wpadminbar.outerHeight() : 0;
150    };
151
152    var getHorizontalPageHeight = function() {
153      return (
154        $window.height() -
155        $header.outerHeight() -
156        $footer.outerHeight() -
157        wpAdminBarHeight()
158      );
159    };
160
161    var menuWalker = function($root) {
162      var items = [];
163
164      $root.find('> li > a').each(function() {
165        var $el = $(this),
166          $parent = $el.parent(),
167          $subEl = $el.next(),
168          item = {
169            href: $el.attr('href'),
170            content: $el.html()
171          };
172
173        if ($subEl.length) {
174          item.children = menuWalker($subEl);
175        }
176        if ($parent.is('.current_page_item')) {
177          item.active = true;
178        }
179        items.push(item);
180      });
181
182      return items;
183    };
184
185    if (window.device.ipad) {
186      // Simulate menu hover events on iPad
187      // Navigate only on double-tap
188      var $cascadedLinks = $('.site-navigation .has-children > a');
189
190      $cascadedLinks.click(function(e) {
191        var $el = $(this);
192        if (!$el.is('.already-tapped')) {
193          $cascadedLinks.removeClass('already-tapped');
194          $el.addClass('already-tapped').trigger('mouseover');
195          e.preventDefault();
196        }
197      });
198
199      if (isLayoutHorizontal) {
200        var scrollToTop = function() {
201          // Scroll to top when we have horizontal page.
202          // >768 is needed since horizontal pages turn
203          // into vertical on small devices (eg. iPad Mini)
204          if ($(window).scrollTop() > 0 && $(window).width() > 768) {
205            $('body').animate({ scrollTop: 0 }, 200);
206          }
207        };
208
209        $window.on('orientationchange.ios7-safari-bug.fluxus', function() {
210          setTimeout(function() {
211            if ($window.height() === 692) {
212              scrollToTop();
213            }
214          }, 200);
215        });
216      }
217    }
218
219    // Fixes menu issue, when popup is outside the screen.
220    // Supports WPML menu items.
221    $('.site-navigation .has-children, .menu-item-language').hover(
222      function() {
223        var $submenu = $(this).children('.sub-menu');
224
225        if ($submenu.length) {
226          // if popup is outside the screen, then align it by the right side of the screen.
227          if (
228            $submenu.offset().left +
229              $submenu.outerWidth() -
230              $(document).scrollLeft() >
231            $window.width()
232          ) {
233            $submenu.addClass('sub-menu-right');
234          }
235        }
236      },
237      function() {
238        $(this)
239          .children('.sub-menu')
240          .removeClass('sub-menu-right');
241      }
242    );
243
244    // --------------------------------------------------------------------
245    // 4. Setup global listeners: window.resize
246    // --------------------------------------------------------------------
247
248    // General resize
249    var scrollListenerAdded = false;
250    var isVerticallyScrolled = false;
251    helpers.onWindowResize(
252      function() {
253        var $menuItems;
254
255        // Activate mobile menu if page is smaller than 768
256        if ($window.width() <= 768) {
257          if (!window.mobileMenu) {
258            $menuItems = $('.site-navigation > nav > ul');
259            if ($menuItems.length > 0) {
260              window.mobileMenu = new BurgerMenu({
261                items: menuWalker($menuItems),
262                backgroundImage: $('.site-navigation').data('image'),
263                container: $('.js-site-header')
264              });
265            }
266          }
267          window.mobileMenu && window.mobileMenu.enable();
268
269          if (!isLayoutWithoutScroll && !scrollListenerAdded) {
270            scrollListenerAdded = true;
271            var touching = false;
272            window.addEventListener('touchstart', function() {
273              touching = true;
274            });
275            window.addEventListener('touchend', function() {
276              touching = false;
277              $html.toggleClass('scroll-y', isVerticallyScrolled);
278            });
279
280            window.addEventListener(
281              'scroll',
282              _.throttle(function() {
283                var rootEl = document.documentElement;
284                var top =
285                  (window.pageYOffset || rootEl.scrollTop) -
286                  (rootEl.clientTop || 0);
287                if (top > 0 && !isVerticallyScrolled) {
288                  isVerticallyScrolled = true;
289                  /**
290                   * On Android Firefox adding class during scroll
291                   * will cause a re-render that results in not smooth scrolling.
292                   * Therefore we wait for touch to end before adding .scroll-y.
293                   */
294                  if (!touching) {
295                    $html.addClass('scroll-y');
296                  }
297                } else if (top === 0 && isVerticallyScrolled) {
298                  isVerticallyScrolled = false;
299                  if (!touching) {
300                    $html.removeClass('scroll-y');
301                  }
302                }
303              }, 100),
304              { passive: true }
305            );
306          }
307        } else {
308          window.mobileMenu && window.mobileMenu.disable();
309        }
310      },
311      true,
312      'page-resizer'
313    );
314
315    // Horizontal page resize
316    if (isLayoutHorizontal) {
317      helpers.onWindowResize(
318        function() {
319          // The header is position: fixed we have to calculate
320          // the offset for main page dynamically.
321          var headerHeight = $header.outerHeight();
322          var isLayoutHorizontalVertical = $window.width() <= 768;
323
324          if (isLayoutHorizontalVertical) {
325            var windowHeight = $window.height();
326
327            if ($html.is('.no-scroll')) {
328              $main.css({
329                height: windowHeight - headerHeight,
330                top: 0
331              });
332            } else {
333              // Grid layout deals with height separately.
334              if (!$html.is('.layout-portfolio-grid')) {
335                $main.css({
336                  height: 'auto',
337                  top: 0
338                });
339              }
340            }
341          } else {
342            $main.css({
343              height: getHorizontalPageHeight(),
344              top: headerHeight
345            });
346          }
347        },
348        true,
349        'horizontal-page'
350      );
351
352      $html.addClass('horizontal-page--loaded');
353
354      // Setup tinyscrollbar plugin.
355      helpers.runIfFound($('.scroll-container'), function($el) {
356        $el.tinyscrollbar();
357
358        helpers.onWindowResize(function() {
359          $el.each(function() {
360            var $t = $(this),
361              tsb = $t.data('tsb');
362
363            $t.find('.scrollbar, .track').css('height', $t.height());
364            tsb && tsb.update();
365          });
366        }, true);
367      });
368    }
369
370    // --------------------------------------------------------------------
371    // 5. Scripts for specific pages
372    // --------------------------------------------------------------------
373
374    // Page: Grid Portfolio
375    if ($html.is('.layout-portfolio-grid')) {
376      (function() {
377        var $grid = $('.js-portfolio-grid'),
378          defaults = $.extend($.Grid.defaults, $grid.data()),
379          options,
380          grid;
381
382        if (!window.getGridOptions) {
383          window.getGridOptions = function() {
384            var windowWidth = $window.width(),
385              windowHeight = $window.height(),
386              options = {
387                orientation: defaults.orientation,
388                rows: defaults.rows,
389                columns: defaults.columns
390              };
391
392            if (windowWidth <= 568) {
393              options.columns = 1;
394              options.rows = Math.ceil(windowHeight / 378);
395              options.orientation = 'vertical';
396            } else if (windowWidth <= 768) {
397              if (defaults.columns > 2) {
398                options.columns = 2;
399              }
400              options.rows = Math.ceil(windowHeight / 250);
401              options.orientation = 'vertical';
402            } else if (windowWidth <= 1024) {
403              if (defaults.columns > 3) {
404                options.columns = 3;
405              }
406              if (defaults.rows > 3) {
407                options.rows = 3;
408              }
409            }
410            return options;
411          };
412        }
413        options = $.extend(defaults, window.getGridOptions());
414
415        options.onRenderStart = function() {
416          var options = this.options;
417          var isVertical = options.orientation === 'vertical';
418          var isAspectRatioAuto = options.aspectRatio === 'auto';
419          var itemsPerScreen = options.rows * options.columns;
420          var pageHeight;
421
422          // When there are more items than could be fitted into
423          // a viewport, the footer will get pushed out of screen.
424          // So we need to remove it from our container height calculation.
425          if (
426            isVertical &&
427            isAspectRatioAuto &&
428            itemsPerScreen < $grid.children().length
429          ) {
430            pageHeight =
431              $window.height() - $header.outerHeight() - options.gutterHeight;
432          } else {
433            pageHeight = getHorizontalPageHeight();
434          }
435
436          $grid.css('height', pageHeight);
437
438          options = window.getGridOptions();
439          this.options.orientation = options.orientation;
440          this.options.rows = options.rows;
441          this.options.columns = options.columns;
442        };
443
444        var loadRetinaImage = function($el) {
445          if (!$el.data('retina-image-loaded')) {
446            $el.data('retina-image-loaded', true);
447            var $preview = $el.children('.preview');
448            if ($preview.data('max-size')) {
449              $.preloadImage($preview.data('max-size')).then(function() {
450                $preview.css('background-image', 'url(' + this.src + ')');
451              });
452            }
453          }
454        };
455
456        options.onRenderComplete = function(grid, renderData, contextChanged) {
457          if (contextChanged) {
458            _.each(renderData.coordinates, function(coordinate, index) {
459              if (coordinate.width > 1166) {
460                loadRetinaImage(grid.$items.eq(index));
461              }
462            });
463
464            if (this.isHorizontal()) {
465              if ($grid.width() < renderData.rightmostX) {
466                renderData.$rightmost.css(
467                  'padding-right',
468                  this.options.gutterWidth
469                );
470              } else {
471                renderData.$rightmost.css('padding-right', 0);
472              }
473            } else {
474              $main.css({
475                height: renderData.bottommostY,
476                top: 0
477              });
478            }
479          }
480        };
481
482        // Enable Grid plugin
483        $grid.grid(options);
484        grid = $grid.data('grid');
485
486        if (grid.isHorizontal()) {
487          globalNavigation.setItems(grid.$items);
488          var gutter = grid.options.gutterWidth;
489
490          globalNavigation.options.nextItem = function() {
491            var windowRight = $window.scrollLeft() + $window.width() - 30,
492              nextItem;
493
494            // Find the first item that is not being fully displayed
495            // and scroll to that item.
496            $.each(grid.lastRender.coords.coordinates, function(index) {
497              if (this.x > windowRight || this.rightX - gutter > windowRight) {
498                nextItem = this;
499                return false;
500              }
501            });
502
503            if (nextItem) {
504              this.scrollTo(nextItem.rightX - $window.width() + gutter * 2);
505            }
506          };
507
508          globalNavigation.options.previousItem = function() {
509            var windowLeft = $window.scrollLeft(),
510              previousItem;
511
512            // Find the first item that is fully visible and scroll to the previous one.
513            $.each(grid.lastRender.coords.coordinates, function(index) {
514              if (index && this.x >= windowLeft) {
515                previousItem = grid.lastRender.coords.coordinates[index - 1];
516                return false;
517              }
518            });
519
520            if (previousItem) {
521              this.scrollTo(previousItem.x);
522            }
523          };
524        }
525      })();
526    }
527
528    //
529    // Page: Portfolio Single
530    //
531    helpers.runIfFound($('.js-portfolio-single'), function($mediaList) {
532      var $resizables = $mediaList.find(
533        '.js-horizontal-item__resizable, iframe'
534      );
535
536      if ($mediaList.is('.portfolio-single--onclick-scroll')) {
537        // Activate click to scroll navigation
538        $('.js-horizontal-media').on('click.click-to-scroll.fluxus', function(
539          event
540        ) {
541          event.preventDefault();
542
543          var $item = $(this).closest('.js-horizontal-content__item'),
544            activeItem = globalNavigation.getActive();
545          var $activeItem = activeItem.item;
546
547          if ($activeItem.length && $item.length) {
548            if ($item[0] == $activeItem[0]) {
549              globalNavigation.nextItem();
550            } else {
551              globalNavigation.scrollToItem($item);
552            }
553          }
554        });
555      } else if ($mediaList.is('.portfolio-single--onclick-lightbox')) {
556        // Bind Lightbox to image links and iframes
557        $('.js-lightbox-media, .js-horizontal-item iframe').fluxusLightbox({
558          onShow: globalNavigation.disable,
559          onHide: globalNavigation.enable
560        });
561      }
562
563      $resizables.each(function() {
564        var $el = $(this),
565          width = $el.data('width') || $el.attr('width') || 1,
566          height = $el.data('height') || $el.attr('height') || 1;
567
568        $el.data({
569          height: height,
570          ratio: width / height
571        });
572      });
573
574      /**
575       * Captions under project images
576       *
577       * When captions are multiline we show an arrow that can
578       * expand full caption of photo.
579       */
580      var calculateMultilineCaption = function($elements) {
581        $elements.each(function() {
582          var $el = $(this);
583          if (!$el.data('expanded')) {
584            var containerHeight = $el.outerHeight();
585            var contentHeight = $el[0].scrollHeight;
586            var hasScroll = containerHeight < contentHeight;
587            $el.toggleClass('captioned-media__caption--multiline', hasScroll);
588          }
589        });
590      };
591      var $captions = $([]);
592
593      $('.js-captioned-media__caption').each(function() {
594        var $el = $(this);
595
596        $el.click(function() {
597          var expanded = $el.data('expanded');
598          $el.data('expanded', !expanded);
599
600          if (expanded) {
601            $el.removeClass('captioned-media__caption--expanded');
602          } else {
603            $el.css('height', 'auto');
604            $el.width();
605            $el.addClass('captioned-media__caption--expanded');
606          }
607        });
608
609        $captions = $captions.add($el);
610      });
611
612      if ($captions.length) {
613        helpers.onWindowResize(
614          function() {
615            calculateMultilineCaption($captions);
616          },
617          false,
618          'multiline-caption'
619        );
620        $(document).on('lazyload-loaded', function() {
621          calculateMultilineCaption($captions);
622        });
623      }
624    });
625
626    //
627    // Page: Contacts
628    //
629    helpers.runIfFound($('.page-contacts'), function($el) {
630      var $contactsForm = $('.wpcf7'),
631        $infobox = $el.find('.page'),
632        $viewport = $infobox.children('.viewport'),
633        iscroll;
634
635      if ($contactsForm.length) {
636        $contactsForm.detach();
637        $('#contacts-modal .modal-contents').append($contactsForm);
638      }
639
640      $('#send-message').click(function(event) {
641        event.preventDefault();
642        $('#contacts-modal').reveal({
643          closeonbackgroundclick: true,
644          middle: true
645        });
646      });
647
648      iscroll = new IScroll($infobox.get(0), {
649        mouseWheel: true,
650        scrollbars: 'custom'
651      });
652
653      helpers.onWindowResize(
654        function() {
655          var mainHeight = getHorizontalPageHeight();
656
657          if ($viewport.height() > mainHeight) {
658            !iscroll.enabled && iscroll.enable();
659            iscroll.refresh();
660          } else {
661            iscroll.enabled && iscroll.disable();
662          }
663        },
664        true,
665        'contacts'
666      );
667    });
668
669    $window.on('orientationchange.fluxus', function() {
670      setTimeout(function() {
671        $window.trigger('resize');
672      }, 10);
673    });
674
675    // --------------------------------------------------------------------
676    // 5. Shortcodes
677    // --------------------------------------------------------------------
678
679    //
680    // Shortcode: Tabs
681    //
682    $('.tabs').each(function() {
683      var $t = $(this);
684
685      $t.find('.tabs-menu a')
686        .click(function() {
687          var $t = $(this),
688            $p = $t.parent(),
689            index = $p.prevAll().length;
690
691          if ($p.is('.active')) {
692            return false;
693          }
694
695          $p.parent()
696            .find('.active')
697            .removeClass('active');
698          $p.addClass('active');
699
700          $p.closest('.tabs')
701            .find('.tab')
702            .hide()
703            .end()
704            .find('.tab:eq(' + index + ')')
705            .show();
706
707          return false;
708        })
709        .each(function(index) {
710          $(this)
711            .wrapInner($('<span />'))
712            .append($('<b>' + (index + 1) + '</b class="index">'));
713        });
714    });
715
716    //
717    // Shortcode: Accordion
718    //
719    $('.accordion').each(function() {
720      var $accordion = $(this);
721
722      $accordion.find('.panel-title a').click(function() {
723        var $t = $(this);
724
725        /**
726         * This is the active panel. Let's collapse it.
727         */
728        if ($t.closest('.panel-active').length) {
729          $t.closest('.panel-active')
730            .find('.panel-content')
731            .slideUp(500, function() {
732              $(this)
733                .closest('.panel-active')
734                .removeClass('panel-active');
735            });
736          return false;
737        }
738
739        var $newPanel = $t.closest('.panel'),
740          index = $newPanel.prevAll().length,
741          $panelActive = $accordion.find('.panel-active');
742
743        if ($panelActive.length) {
744          $panelActive.find('.panel-content').slideUp(500, function() {
745            $(this)
746              .closest('.panel')
747              .removeClass('panel-active');
748            $accordion
749              .find('.panel:eq(' + index + ') .panel-content')
750              .slideDown(300)
751              .closest('.panel')
752              .addClass('panel-active');
753          });
754        } else {
755          $accordion
756            .find('.panel:eq(' + index + ') .panel-content')
757            .slideDown(300)
758            .closest('.panel')
759            .addClass('panel-active');
760        }
761
762        return false;
763      });
764    });
765
766    /**
767     * Configure Plyr player
768     */
769    $('.js-plyr').each(function() {
770      var $el = $(this);
771      var playerDefaultOptions = {
772        // debug: true,
773        controls: [
774          'play-large',
775          'play',
776          'current-time',
777          'mute',
778          'volume',
779          'fullscreen'
780        ],
781        autoplay: false,
782        autopause: false,
783        loop: { active: true },
784        clickToPlay: true,
785        disableContextMenu: false,
786        keyboard: { focused: false, global: false },
787        settings: []
788      };
789      var playerOptions = Object.assign({}, playerDefaultOptions);
790      var isRepeatMode = $el.data('plyr-repeat-mode') !== undefined;
791      var shouldAutoplay = false;
792
793      // Repeat mode = no controls / autoplay
794      if (isRepeatMode) {
795        shouldAutoplay = true;
796        playerOptions.controls = [];
797        playerOptions.clickToPlay = false; // Click mutes / umutes
798      }
799
800      if ($el.attr('playsinline') !== undefined) {
801        shouldAutoplay = true;
802      }
803
804      if (shouldAutoplay) {
805        playerOptions.muted = true;
806        playerOptions.volume = 0;
807      }
808
809      var plyr = new Plyr(this, playerOptions);
810
811      plyr.on('ready', function(event) {
812        // For some reason Plyr 3.4.6 when playing Vimeo does not care for volume / mute settings.
813        if (shouldAutoplay) {
814          plyr.volume = 0;
815          plyr.muted = true;
816        }
817
818        if (isRepeatMode) {
819          // Click to mute / umute
820          var doubleClickTimer;
821          var preventSingleClick = false;
822          $(event.target)
823            .on('click', function() {
824              doubleClickTimer = setTimeout(function() {
825                if (!preventSingleClick) {
826                  plyr.muted = !plyr.muted;
827                  if (plyr.muted) {
828                    plyr.volume = 0;
829                  } else {
830                    plyr.volume = 100;
831                  }
832                }
833                preventSingleClick = false;
834              }, 200);
835            })
836            .on('dblclick', function() {
837              clearTimeout(doubleClickTimer);
838              preventSingleClick = true;
839            });
840        }
841
842        var playResult = plyr.play();
843        // For HTML5 players, play() will return a Promise in some browsers -
844        // WebKit and Mozilla according to MDN at time of writing.
845        if (
846          shouldAutoplay &&
847          playResult &&
848          typeof playResult.catch === 'function'
849        ) {
850          playResult.catch(function() {
851            /**
852             * WHen trying to autoplay and failing, let's re-initialize player
853             * without autoplay option.
854             */
855            var $container = $el.closest('.plyr');
856            var $newEl = $el.removeAttr('data-plyr-repeat-mode').clone();
857            $container.replaceWith($newEl);
858
859            plyr = new Plyr(
860              $newEl[0],
861              Object.assign({}, playerOptions, {
862                controls: playerDefaultOptions.controls
863              })
864            );
865          });
866        }
867      });
868    });
869
870    /**
871     * Creates a blank image that helps layout the page
872     * when real images are not yet loaded.
873     */
874    $('.js-aspect__placeholder').each(function() {
875      var $el = $(this);
876      // It won't work correctly if upscaleSize is smaller
877      // than actual window size. 3840 seems like a big enough number.
878      var upscaleSize = 3840;
879
880      var dimensions = {
881        width: parseInt(this.getAttribute('width'), 10) || 192,
882        height: parseInt(this.getAttribute('height'), 10) || 128
883      };
884
885      var ratio = dimensions.width / dimensions.height;
886      var ratioCacheKey = 'aspect-placeholder-' + Math.round(ratio * 1000);
887
888      var attrs = {};
889      var cachedAttrs = localStorage.getItem(ratioCacheKey);
890
891      if (cachedAttrs) {
892        attrs = JSON.parse(cachedAttrs);
893      } else {
894        var smallerSide =
895          dimensions.width < dimensions.height ? 'width' : 'height';
896        var widerSide = smallerSide === 'height' ? 'width' : 'height';
897        dimensions[widerSide] = upscaleSize;
898        dimensions[smallerSide] = upscaleSize * (ratio < 1 ? ratio : 1 / ratio);
899
900        var canvas = document.createElement('canvas');
901        canvas.width = dimensions.width;
902        canvas.height = dimensions.height;
903
904        attrs = {
905          width: canvas.width,
906          height: canvas.height,
907          src: canvas.toDataURL('image/png')
908        };
909        try {
910          localStorage.setItem(ratioCacheKey, JSON.stringify(attrs));
911        } catch (err) {
912          console.log('Unable to save to localStorage');
913        }
914      }
915
916      $el.attr(attrs);
917    });
918  });
919
920  /**
921   * Configure lazysizes plugin
922   *
923   * 1. Wraps <img class="lazyload" /> with <div class="lazyload-component" />
924   * 2. Inserts a placeholder <img /> that matches size of
925   *    original image (taken from data-width / data-height).
926   *    This is superior to any percentage padding trick.
927   * 3. Shows loading indicator when it takes longer than transition-delay.
928   * 4. Triggers lazyload-loaded event on document
929   */
930  window.lazySizesConfig = window.lazySizesConfig || {
931    // Load all images when browser is idle for 1s
932    // Values below 50 disable the feature
933    ricTimeout: 1000,
934    // When browser is idling load images as far as
935    // (expFactor x expand)px
936    expFactor: 8,
937    expand: 1000
938  };
939
940  $('.lazyload').each(function() {
941    var $el = $(this).addClass('lazyload-component__image');
942    var $wrapper = $el.wrap($('<div class="lazyload-component" />')).parent();
943    var $loading = $(
944      '<div class="lazyload-component__loading translate-into-middle">'
945    );
946    $loading.append('<div class="fluxus-loading__indicator" />');
947    $wrapper.append($loading);
948  });
949
950  document.addEventListener('lazyloaded', function(event) {
951    var $parent = $(event.target).parent();
952    $parent.addClass('lazyload-component--loaded');
953    $(document).trigger('lazyload-loaded');
954  });
955})(jQuery, window.helpers);

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.