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.