PageSourceSearch

https://simonsleoni.com/templates/24/themes/leadpops/js/fancybox3/js/core.js?ver=1.3.7

js simonsleoni.com collected 2026-09-30 20:29:36 UTC 96,599 bytes, 3,425 lines download raw bytes

vendor: 96,599 bytes, lines 1-3425
1(function (window, document, $, undefined) {
2  "use strict";
3
4  window.console = window.console || {
5    info: function (stuff) {}
6  };
7
8  // If there's no jQuery, fancyBox can't work
9  // =========================================
10
11  if (!$) {
12    return;
13  }
14
15  // Check if fancyBox is already initialized
16  // ========================================
17
18  if ($.fn.fancybox) {
19    console.info("fancyBox already initialized");
20
21    return;
22  }
23
24  // Private default settings
25  // ========================
26
27  var defaults = {
28    // Close existing modals
29    // Set this to false if you do not need to stack multiple instances
30    closeExisting: false,
31
32    // Enable infinite gallery navigation
33    loop: false,
34
35    // Horizontal space between slides
36    gutter: 50,
37
38    // Enable keyboard navigation
39    keyboard: true,
40
41    // Should allow caption to overlap the content
42    preventCaptionOverlap: true,
43
44    // Should display navigation arrows at the screen edges
45    arrows: true,
46
47    // Should display counter at the top left corner
48    infobar: true,
49
50    // Should display close button (using `btnTpl.smallBtn` template) over the content
51    // Can be true, false, "auto"
52    // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items
53    smallBtn: "auto",
54
55    // Should display toolbar (buttons at the top)
56    // Can be true, false, "auto"
57    // If "auto" - will be automatically hidden if "smallBtn" is enabled
58    toolbar: "auto",
59
60    // What buttons should appear in the top right corner.
61    // Buttons will be created using templates from `btnTpl` option
62    // and they will be placed into toolbar (class="fancybox-toolbar"` element)
63    buttons: [
64      "zoom",
65      //"share",
66      "slideShow",
67      //"fullScreen",
68      //"download",
69      "thumbs",
70      "close"
71    ],
72
73    // Detect "idle" time in seconds
74    idleTime: 3,
75
76    // Disable right-click and use simple image protection for images
77    protect: false,
78
79    // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc
80    modal: false,
81
82    image: {
83      // Wait for images to load before displaying
84      //   true  - wait for image to load and then display;
85      //   false - display thumbnail and load the full-sized image over top,
86      //           requires predefined image dimensions (`data-width` and `data-height` attributes)
87      preload: false
88    },
89
90    ajax: {
91      // Object containing settings for ajax request
92      settings: {
93        // This helps to indicate that request comes from the modal
94        // Feel free to change naming
95        data: {
96          fancybox: true
97        }
98      }
99    },
100
101    iframe: {
102      // Iframe template
103      tpl: '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" allowfullscreen="allowfullscreen" allow="autoplay; fullscreen" src=""></iframe>',
104
105      // Preload iframe before displaying it
106      // This allows to calculate iframe content width and height
107      // (note: Due to "Same Origin Policy", you can't get cross domain data).
108      preload: true,
109
110      // Custom CSS styling for iframe wrapping element
111      // You can use this to set custom iframe dimensions
112      css: {},
113
114      // Iframe tag attributes
115      attr: {
116        scrolling: "auto"
117      }
118    },
119
120    // For HTML5 video only
121    video: {
122      tpl: '<video class="fancybox-video" controls controlsList="nodownload" poster="{{poster}}">' +
123        '<source src="{{src}}" type="{{format}}" />' +
124        'Sorry, your browser doesn\'t support embedded videos, <a href="{{src}}">download</a> and watch with your favorite video player!' +
125        "</video>",
126      format: "", // custom video format
127      autoStart: true
128    },
129
130    // Default content type if cannot be detected automatically
131    defaultType: "image",
132
133    // Open/close animation type
134    // Possible values:
135    //   false            - disable
136    //   "zoom"           - zoom images from/to thumbnail
137    //   "fade"
138    //   "zoom-in-out"
139    //
140    animationEffect: "zoom",
141
142    // Duration in ms for open/close animation
143    animationDuration: 366,
144
145    // Should image change opacity while zooming
146    // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios
147    zoomOpacity: "auto",
148
149    // Transition effect between slides
150    //
151    // Possible values:
152    //   false            - disable
153    //   "fade'
154    //   "slide'
155    //   "circular'
156    //   "tube'
157    //   "zoom-in-out'
158    //   "rotate'
159    //
160    transitionEffect: "fade",
161
162    // Duration in ms for transition animation
163    transitionDuration: 366,
164
165    // Custom CSS class for slide element
166    slideClass: "",
167
168    // Custom CSS class for layout
169    baseClass: "",
170
171    // Base template for layout
172    baseTpl: '<div class="fancybox-container" role="dialog" tabindex="-1">' +
173      '<div class="fancybox-bg"></div>' +
174      '<div class="fancybox-inner">' +
175      '<div class="fancybox-infobar"><span data-fancybox-index></span>&nbsp;/&nbsp;<span data-fancybox-count></span></div>' +
176      '<div class="fancybox-toolbar">{{buttons}}</div>' +
177      '<div class="fancybox-navigation">{{arrows}}</div>' +
178      '<div class="fancybox-stage"></div>' +
179      '<div class="fancybox-caption"><div class="fancybox-caption__body"></div></div>' +
180      "</div>" +
181      "</div>",
182
183    // Loading indicator template
184    spinnerTpl: '<div class="fancybox-loading"></div>',
185
186    // Error message template
187    errorTpl: '<div class="fancybox-error"><p>{{ERROR}}</p></div>',
188
189    btnTpl: {
190      download: '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}" href="javascript:;">' +
191        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.62 17.09V19H5.38v-1.91zm-2.97-6.96L17 11.45l-5 4.87-5-4.87 1.36-1.32 2.68 2.64V5h1.92v7.77z"/></svg>' +
192        "</a>",
193
194      zoom: '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' +
195        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.7 17.3l-3-3a5.9 5.9 0 0 0-.6-7.6 5.9 5.9 0 0 0-8.4 0 5.9 5.9 0 0 0 0 8.4 5.9 5.9 0 0 0 7.7.7l3 3a1 1 0 0 0 1.3 0c.4-.5.4-1 0-1.5zM8.1 13.8a4 4 0 0 1 0-5.7 4 4 0 0 1 5.7 0 4 4 0 0 1 0 5.7 4 4 0 0 1-5.7 0z"/></svg>' +
196        "</button>",
197
198      close: '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' +
199        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 10.6L6.6 5.2 5.2 6.6l5.4 5.4-5.4 5.4 1.4 1.4 5.4-5.4 5.4 5.4 1.4-1.4-5.4-5.4 5.4-5.4-1.4-1.4-5.4 5.4z"/></svg>' +
200        "</button>",
201
202      // Arrows
203      arrowLeft: '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' +
204        '<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.28 15.7l-1.34 1.37L5 12l4.94-5.07 1.34 1.38-2.68 2.72H19v1.94H8.6z"/></svg></div>' +
205        "</button>",
206
207      arrowRight: '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' +
208        '<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.4 12.97l-2.68 2.72 1.34 1.38L19 12l-4.94-5.07-1.34 1.38 2.68 2.72H5v1.94z"/></svg></div>' +
209        "</button>",
210
211      // This small close button will be appended to your html/inline/ajax content by default,
212      // if "smallBtn" option is not set to false
213      smallBtn: '<button type="button" data-fancybox-close class="fancybox-button fancybox-close-small" title="{{CLOSE}}">' +
214        '<svg xmlns="http://www.w3.org/2000/svg" version="1" viewBox="0 0 24 24"><path d="M13 12l5-5-1-1-5 5-5-5-1 1 5 5-5 5 1 1 5-5 5 5 1-1z"/></svg>' +
215        "</button>"
216    },
217
218    // Container is injected into this element
219    parentEl: "body",
220
221    // Hide browser vertical scrollbars; use at your own risk
222    hideScrollbar: true,
223
224    // Focus handling
225    // ==============
226
227    // Try to focus on the first focusable element after opening
228    autoFocus: true,
229
230    // Put focus back to active element after closing
231    backFocus: true,
232
233    // Do not let user to focus on element outside modal content
234    trapFocus: true,
235
236    // Module specific options
237    // =======================
238
239    fullScreen: {
240      autoStart: false
241    },
242
243    // Set `touch: false` to disable panning/swiping
244    touch: {
245      vertical: true, // Allow to drag content vertically
246      momentum: true // Continue movement after releasing mouse/touch when panning
247    },
248
249    // Hash value when initializing manually,
250    // set `false` to disable hash change
251    hash: null,
252
253    // Customize or add new media types
254    // Example:
255    /*
256      media : {
257        youtube : {
258          params : {
259            autoplay : 0
260          }
261        }
262      }
263    */
264    media: {},
265
266    slideShow: {
267      autoStart: false,
268      speed: 3000
269    },
270
271    thumbs: {
272      autoStart: false, // Display thumbnails on opening
273      hideOnClose: true, // Hide thumbnail grid when closing animation starts
274      parentEl: ".fancybox-container", // Container is injected into this element
275      axis: "y" // Vertical (y) or horizontal (x) scrolling
276    },
277
278    // Use mousewheel to navigate gallery
279    // If 'auto' - enabled for images only
280    wheel: "auto",
281
282    // Callbacks
283    //==========
284
285    // See Documentation/API/Events for more information
286    // Example:
287    /*
288      afterShow: function( instance, current ) {
289        console.info( 'Clicked element:' );
290        console.info( current.opts.$orig );
291      }
292    */
293
294    onInit: $.noop, // When instance has been initialized
295
296    beforeLoad: $.noop, // Before the content of a slide is being loaded
297    afterLoad: $.noop, // When the content of a slide is done loading
298
299    beforeShow: $.noop, // Before open animation starts
300    afterShow: $.noop, // When content is done loading and animating
301
302    beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close.
303    afterClose: $.noop, // After instance has been closed
304
305    onActivate: $.noop, // When instance is brought to front
306    onDeactivate: $.noop, // When other instance has been activated
307
308    // Interaction
309    // ===========
310
311    // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
312    // each option can be string or method that returns value.
313    //
314    // Possible values:
315    //   "close"           - close instance
316    //   "next"            - move to next gallery item
317    //   "nextOrClose"     - move to next gallery item or close if gallery has only one item
318    //   "toggleControls"  - show/hide controls
319    //   "zoom"            - zoom image (if loaded)
320    //   false             - do nothing
321
322    // Clicked on the content
323    clickContent: function (current, event) {
324      return current.type === "image" ? "zoom" : false;
325    },
326
327    // Clicked on the slide
328    clickSlide: "close",
329
330    // Clicked on the background (backdrop) element;
331    // if you have not changed the layout, then most likely you need to use `clickSlide` option
332    clickOutside: "close",
333
334    // Same as previous two, but for double click
335    dblclickContent: false,
336    dblclickSlide: false,
337    dblclickOutside: false,
338
339    // Custom options when mobile device is detected
340    // =============================================
341
342    mobile: {
343      preventCaptionOverlap: false,
344      idleTime: false,
345      clickContent: function (current, event) {
346        return current.type === "image" ? "toggleControls" : false;
347      },
348      clickSlide: function (current, event) {
349        return current.type === "image" ? "toggleControls" : "close";
350      },
351      dblclickContent: function (current, event) {
352        return current.type === "image" ? "zoom" : false;
353      },
354      dblclickSlide: function (current, event) {
355        return current.type === "image" ? "zoom" : false;
356      }
357    },
358
359    // Internationalization
360    // ====================
361
362    lang: "en",
363    i18n: {
364      en: {
365        CLOSE: "Close",
366        NEXT: "Next",
367        PREV: "Previous",
368        ERROR: "The requested content cannot be loaded. <br/> Please try again later.",
369        PLAY_START: "Start slideshow",
370        PLAY_STOP: "Pause slideshow",
371        FULL_SCREEN: "Full screen",
372        THUMBS: "Thumbnails",
373        DOWNLOAD: "Download",
374        SHARE: "Share",
375        ZOOM: "Zoom"
376      },
377      de: {
378        CLOSE: "Schlie&szlig;en",
379        NEXT: "Weiter",
380        PREV: "Zur&uuml;ck",
381        ERROR: "Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es sp&auml;ter nochmal.",
382        PLAY_START: "Diaschau starten",
383        PLAY_STOP: "Diaschau beenden",
384        FULL_SCREEN: "Vollbild",
385        THUMBS: "Vorschaubilder",
386        DOWNLOAD: "Herunterladen",
387        SHARE: "Teilen",
388        ZOOM: "Vergr&ouml;&szlig;ern"
389      }
390    }
391  };
392
393  // Few useful variables and methods
394  // ================================
395
396  var $W = $(window);
397  var $D = $(document);
398
399  var called = 0;
400
401  // Check if an object is a jQuery object and not a native JavaScript object
402  // ========================================================================
403  var isQuery = function (obj) {
404    return obj && obj.hasOwnProperty && obj instanceof $;
405  };
406
407  // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame"
408  // ===============================================================================
409  var requestAFrame = (function () {
410    return (
411      window.requestAnimationFrame ||
412      window.webkitRequestAnimationFrame ||
413      window.mozRequestAnimationFrame ||
414      window.oRequestAnimationFrame ||
415      // if all else fails, use setTimeout
416      function (callback) {
417        return window.setTimeout(callback, 1000 / 60);
418      }
419    );
420  })();
421
422  var cancelAFrame = (function () {
423    return (
424      window.cancelAnimationFrame ||
425      window.webkitCancelAnimationFrame ||
426      window.mozCancelAnimationFrame ||
427      window.oCancelAnimationFrame ||
428      function (id) {
429        window.clearTimeout(id);
430      }
431    );
432  })();
433
434  // Detect the supported transition-end event property name
435  // =======================================================
436  var transitionEnd = (function () {
437    var el = document.createElement("fakeelement"),
438      t;
439
440    var transitions = {
441      transition: "transitionend",
442      OTransition: "oTransitionEnd",
443      MozTransition: "transitionend",
444      WebkitTransition: "webkitTransitionEnd"
445    };
446
447    for (t in transitions) {
448      if (el.style[t] !== undefined) {
449        return transitions[t];
450      }
451    }
452
453    return "transitionend";
454  })();
455
456  // Force redraw on an element.
457  // This helps in cases where the browser doesn't redraw an updated element properly
458  // ================================================================================
459  var forceRedraw = function ($el) {
460    return $el && $el.length && $el[0].offsetHeight;
461  };
462
463  // Exclude array (`buttons`) options from deep merging
464  // ===================================================
465  var mergeOpts = function (opts1, opts2) {
466    var rez = $.extend(true, {}, opts1, opts2);
467
468    $.each(opts2, function (key, value) {
469      if ($.isArray(value)) {
470        rez[key] = value;
471      }
472    });
473
474    return rez;
475  };
476
477  // How much of an element is visible in viewport
478  // =============================================
479
480  var inViewport = function (elem) {
481    var elemCenter, rez;
482
483    if (!elem || elem.ownerDocument !== document) {
484      return false;
485    }
486
487    $(".fancybox-container").css("pointer-events", "none");
488
489    elemCenter = {
490      x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
491      y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
492    };
493
494    rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem;
495
496    $(".fancybox-container").css("pointer-events", "");
497
498    return rez;
499  };
500
501  // Class definition
502  // ================
503
504  var FancyBox = function (content, opts, index) {
505    var self = this;
506
507    self.opts = mergeOpts({
508      index: index
509    }, $.fancybox.defaults);
510
511    if ($.isPlainObject(opts)) {
512      self.opts = mergeOpts(self.opts, opts);
513    }
514
515    if ($.fancybox.isMobile) {
516      self.opts = mergeOpts(self.opts, self.opts.mobile);
517    }
518
519    self.id = self.opts.id || ++called;
520
521    self.currIndex = parseInt(self.opts.index, 10) || 0;
522    self.prevIndex = null;
523
524    self.prevPos = null;
525    self.currPos = 0;
526
527    self.firstRun = true;
528
529    // All group items
530    self.group = [];
531
532    // Existing slides (for current, next and previous gallery items)
533    self.slides = {};
534
535    // Create group elements
536    self.addContent(content);
537
538    if (!self.group.length) {
539      return;
540    }
541
542    self.init();
543  };
544
545  $.extend(FancyBox.prototype, {
546    // Create DOM structure
547    // ====================
548
549    init: function () {
550      var self = this,
551        firstItem = self.group[self.currIndex],
552        firstItemOpts = firstItem.opts,
553        $container,
554        buttonStr;
555
556      if (firstItemOpts.closeExisting) {
557        $.fancybox.close(true);
558      }
559
560      // Hide scrollbars
561      // ===============
562
563      $("body").addClass("fancybox-active");
564
565      if (
566        !$.fancybox.getInstance() &&
567        firstItemOpts.hideScrollbar !== false &&
568        !$.fancybox.isMobile &&
569        document.body.scrollHeight > window.innerHeight
570      ) {
571        $("head").append(
572          '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar{margin-right:' +
573          (window.innerWidth - document.documentElement.clientWidth) +
574          "px;}</style>"
575        );
576
577        $("body").addClass("compensate-for-scrollbar");
578      }
579
580      // Build html markup and set references
581      // ====================================
582
583      // Build html code for buttons and insert into main template
584      buttonStr = "";
585
586      $.each(firstItemOpts.buttons, function (index, value) {
587        buttonStr += firstItemOpts.btnTpl[value] || "";
588      });
589
590      // Create markup from base template, it will be initially hidden to
591      // avoid unnecessary work like painting while initializing is not complete
592      $container = $(
593          self.translate(
594            self,
595            firstItemOpts.baseTpl
596            .replace("{{buttons}}", buttonStr)
597            .replace("{{arrows}}", firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight)
598          )
599        )
600        .attr("id", "fancybox-container-" + self.id)
601        .addClass(firstItemOpts.baseClass)
602        .data("FancyBox", self)
603        .appendTo(firstItemOpts.parentEl);
604
605      // Create object holding references to jQuery wrapped nodes
606      self.$refs = {
607        container: $container
608      };
609
610      ["bg", "inner", "infobar", "toolbar", "stage", "caption", "navigation"].forEach(function (item) {
611        self.$refs[item] = $container.find(".fancybox-" + item);
612      });
613
614      self.trigger("onInit");
615
616      // Enable events, deactive previous instances
617      self.activate();
618
619      // Build slides, load and reveal content
620      self.jumpTo(self.currIndex);
621    },
622
623    // Simple i18n support - replaces object keys found in template
624    // with corresponding values
625    // ============================================================
626
627    translate: function (obj, str) {
628      var arr = obj.opts.i18n[obj.opts.lang] || obj.opts.i18n.en;
629
630      return str.replace(/\{\{(\w+)\}\}/g, function (match, n) {
631        return arr[n] === undefined ? match : arr[n];
632      });
633    },
634
635    // Populate current group with fresh content
636    // Check if each object has valid type and content
637    // ===============================================
638
639    addContent: function (content) {
640      var self = this,
641        items = $.makeArray(content),
642        thumbs;
643
644      $.each(items, function (i, item) {
645        var obj = {},
646          opts = {},
647          $item,
648          type,
649          found,
650          src,
651          srcParts;
652
653        // Step 1 - Make sure we have an object
654        // ====================================
655
656        if ($.isPlainObject(item)) {
657          // We probably have manual usage here, something like
658          // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] )
659
660          obj = item;
661          opts = item.opts || item;
662        } else if ($.type(item) === "object" && $(item).length) {
663          // Here we probably have jQuery collection returned by some selector
664          $item = $(item);
665
666          // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'`
667          opts = $item.data() || {};
668          opts = $.extend(true, {}, opts, opts.options);
669
670          // Here we store clicked element
671          opts.$orig = $item;
672
673          obj.src = self.opts.src || opts.src || $item.attr("href");
674
675          // Assume that simple syntax is used, for example:
676          //   `$.fancybox.open( $("#test"), {} );`
677          if (!obj.type && !obj.src) {
678            obj.type = "inline";
679            obj.src = item;
680          }
681        } else {
682          // Assume we have a simple html code, for example:
683          //   $.fancybox.open( '<div><h1>Hi!</h1></div>' );
684          obj = {
685            type: "html",
686            src: item + ""
687          };
688        }
689
690        // Each gallery object has full collection of options
691        obj.opts = $.extend(true, {}, self.opts, opts);
692
693        // Do not merge buttons array
694        if ($.isArray(opts.buttons)) {
695          obj.opts.buttons = opts.buttons;
696        }
697
698        if ($.fancybox.isMobile && obj.opts.mobile) {
699          obj.opts = mergeOpts(obj.opts, obj.opts.mobile);
700        }
701
702        // Step 2 - Make sure we have content type, if not - try to guess
703        // ==============================================================
704
705        type = obj.type || obj.opts.type;
706        src = obj.src || "";
707
708        if (!type && src) {
709          if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) {
710            type = "video";
711
712            if (!obj.opts.video.format) {
713              obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]);
714            }
715          } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) {
716            type = "image";
717          } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) {
718            type = "iframe";
719            obj = $.extend(true, obj, {
720              contentType: "pdf",
721              opts: {
722                iframe: {
723                  preload: false
724                }
725              }
726            });
727          } else if (src.charAt(0) === "#") {
728            type = "inline";
729          }
730        }
731
732        if (type) {
733          obj.type = type;
734        } else {
735          self.trigger("objectNeedsType", obj);
736        }
737
738        if (!obj.contentType) {
739          obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type;
740        }
741
742        // Step 3 - Some adjustments
743        // =========================
744
745        obj.index = self.group.length;
746
747        if (obj.opts.smallBtn == "auto") {
748          obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1;
749        }
750
751        if (obj.opts.toolbar === "auto") {
752          obj.opts.toolbar = !obj.opts.smallBtn;
753        }
754
755        // Find thumbnail image, check if exists and if is in the viewport
756        obj.$thumb = obj.opts.$thumb || null;
757
758        if (obj.opts.$trigger && obj.index === self.opts.index) {
759          obj.$thumb = obj.opts.$trigger.find("img:first");
760
761          if (obj.$thumb.length) {
762            obj.opts.$orig = obj.opts.$trigger;
763          }
764        }
765
766        if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) {
767          obj.$thumb = obj.opts.$orig.find("img:first");
768        }
769
770        if (obj.$thumb && !obj.$thumb.length) {
771          obj.$thumb = null;
772        }
773
774        obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null);
775
776        // "caption" is a "special" option, it can be used to customize caption per gallery item
777        if ($.type(obj.opts.caption) === "function") {
778          obj.opts.caption = obj.opts.caption.apply(item, [self, obj]);
779        }
780
781        if ($.type(self.opts.caption) === "function") {
782          obj.opts.caption = self.opts.caption.apply(item, [self, obj]);
783        }
784
785        // Make sure we have caption as a string or jQuery object
786        if (!(obj.opts.caption instanceof $)) {
787          obj.opts.caption = obj.opts.caption === undefined ? "" : obj.opts.caption + "";
788        }
789
790        // Check if url contains "filter" used to filter the content
791        // Example: "ajax.html #something"
792        if (obj.type === "ajax") {
793          srcParts = src.split(/\s+/, 2);
794
795          if (srcParts.length > 1) {
796            obj.src = srcParts.shift();
797
798            obj.opts.filter = srcParts.shift();
799          }
800        }
801
802        // Hide all buttons and disable interactivity for modal items
803        if (obj.opts.modal) {
804          obj.opts = $.extend(true, obj.opts, {
805            trapFocus: true,
806            // Remove buttons
807            infobar: 0,
808            toolbar: 0,
809
810            smallBtn: 0,
811
812            // Disable keyboard navigation
813            keyboard: 0,
814
815            // Disable some modules
816            slideShow: 0,
817            fullScreen: 0,
818            thumbs: 0,
819            touch: 0,
820
821            // Disable click event handlers
822            clickContent: false,
823            clickSlide: false,
824            clickOutside: false,
825            dblclickContent: false,
826            dblclickSlide: false,
827            dblclickOutside: false
828          });
829        }
830
831        // Step 4 - Add processed object to group
832        // ======================================
833
834        self.group.push(obj);
835      });
836
837      // Update controls if gallery is already opened
838      if (Object.keys(self.slides).length) {
839        self.updateControls();
840
841        // Update thumbnails, if needed
842        thumbs = self.Thumbs;
843
844        if (thumbs && thumbs.isActive) {
845          thumbs.create();
846
847          thumbs.focus();
848        }
849      }
850    },
851
852    // Attach an event handler functions for:
853    //   - navigation buttons
854    //   - browser scrolling, resizing;
855    //   - focusing
856    //   - keyboard
857    //   - detecting inactivity
858    // ======================================
859
860    addEvents: function () {
861      var self = this;
862
863      self.removeEvents();
864
865      // Make navigation elements clickable
866      // ==================================
867
868      self.$refs.container
869        .on("click.fb-close", "[data-fancybox-close]", function (e) {
870          e.stopPropagation();
871          e.preventDefault();
872
873          self.close(e);
874        })
875        .on("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function (e) {
876          e.stopPropagation();
877          e.preventDefault();
878
879          self.previous();
880        })
881        .on("touchstart.fb-next click.fb-next", "[data-fancybox-next]", function (e) {
882          e.stopPropagation();
883          e.preventDefault();
884
885          self.next();
886        })
887        .on("click.fb", "[data-fancybox-zoom]", function (e) {
888          // Click handler for zoom button
889          self[self.isScaledDown() ? "scaleToActual" : "scaleToFit"]();
890        });
891
892      // Handle page scrolling and browser resizing
893      // ==========================================
894
895      $W.on("orientationchange.fb resize.fb", function (e) {
896        if (e && e.originalEvent && e.originalEvent.type === "resize") {
897          if (self.requestId) {
898            cancelAFrame(self.requestId);
899          }
900
901          self.requestId = requestAFrame(function () {
902            self.update(e);
903          });
904        } else {
905          if (self.current && self.current.type === "iframe") {
906            self.$refs.stage.hide();
907          }
908
909          setTimeout(
910            function () {
911              self.$refs.stage.show();
912
913              self.update(e);
914            },
915            $.fancybox.isMobile ? 600 : 250
916          );
917        }
918      });
919
920      $D.on("keydown.fb", function (e) {
921        var instance = $.fancybox ? $.fancybox.getInstance() : null,
922          current = instance.current,
923          keycode = e.keyCode || e.which;
924
925        // Trap keyboard focus inside of the modal
926        // =======================================
927
928        if (keycode == 9) {
929          if (current.opts.trapFocus) {
930            self.focus(e);
931          }
932
933          return;
934        }
935
936        // Enable keyboard navigation
937        // ==========================
938
939        if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio,select")) {
940          return;
941        }
942
943        // Backspace and Esc keys
944        if (keycode === 8 || keycode === 27) {
945          e.preventDefault();
946
947          self.close(e);
948
949          return;
950        }
951
952        // Left arrow and Up arrow
953        if (keycode === 37 || keycode === 38) {
954          e.preventDefault();
955
956          self.previous();
957
958          return;
959        }
960
961        // Righ arrow and Down arrow
962        if (keycode === 39 || keycode === 40) {
963          e.preventDefault();
964
965          self.next();
966
967          return;
968        }
969
970        self.trigger("afterKeydown", e, keycode);
971      });
972
973      // Hide controls after some inactivity period
974      if (self.group[self.currIndex].opts.idleTime) {
975        self.idleSecondsCounter = 0;
976
977        $D.on(
978          "mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",
979          function (e) {
980            self.idleSecondsCounter = 0;
981
982            if (self.isIdle) {
983              self.showControls();
984            }
985
986            self.isIdle = false;
987          }
988        );
989
990        self.idleInterval = window.setInterval(function () {
991          self.idleSecondsCounter++;
992
993          if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) {
994            self.isIdle = true;
995            self.idleSecondsCounter = 0;
996
997            self.hideControls();
998          }
999        }, 1000);
1000      }
1001    },
1002
1003    // Remove events added by the core
1004    // ===============================
1005
1006    removeEvents: function () {
1007      var self = this;
1008
1009      $W.off("orientationchange.fb resize.fb");
1010      $D.off("keydown.fb .fb-idle");
1011
1012      this.$refs.container.off(".fb-close .fb-prev .fb-next");
1013
1014      if (self.idleInterval) {
1015        window.clearInterval(self.idleInterval);
1016
1017        self.idleInterval = null;
1018      }
1019    },
1020
1021    // Change to previous gallery item
1022    // ===============================
1023
1024    previous: function (duration) {
1025      return this.jumpTo(this.currPos - 1, duration);
1026    },
1027
1028    // Change to next gallery item
1029    // ===========================
1030
1031    next: function (duration) {
1032      return this.jumpTo(this.currPos + 1, duration);
1033    },
1034
1035    // Switch to selected gallery item
1036    // ===============================
1037
1038    jumpTo: function (pos, duration) {
1039      var self = this,
1040        groupLen = self.group.length,
1041        firstRun,
1042        isMoved,
1043        loop,
1044        current,
1045        previous,
1046        slidePos,
1047        stagePos,
1048        prop,
1049        diff;
1050
1051      if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) {
1052        return;
1053      }
1054
1055      // Should loop?
1056      pos = parseInt(pos, 10);
1057      loop = self.current ? self.current.opts.loop : self.opts.loop;
1058
1059      if (!loop && (pos < 0 || pos >= groupLen)) {
1060        return false;
1061      }
1062
1063      // Check if opening for the first time; this helps to speed things up
1064      firstRun = self.firstRun = !Object.keys(self.slides).length;
1065
1066      // Create slides
1067      previous = self.current;
1068
1069      self.prevIndex = self.currIndex;
1070      self.prevPos = self.currPos;
1071
1072      current = self.createSlide(pos);
1073
1074      if (groupLen > 1) {
1075        if (loop || current.index < groupLen - 1) {
1076          self.createSlide(pos + 1);
1077        }
1078
1079        if (loop || current.index > 0) {
1080          self.createSlide(pos - 1);
1081        }
1082      }
1083
1084      self.current = current;
1085      self.currIndex = current.index;
1086      self.currPos = current.pos;
1087
1088      self.trigger("beforeShow", firstRun);
1089
1090      self.updateControls();
1091
1092      // Validate duration length
1093      current.forcedDuration = undefined;
1094
1095      if ($.isNumeric(duration)) {
1096        current.forcedDuration = duration;
1097      } else {
1098        duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"];
1099      }
1100
1101      duration = parseInt(duration, 10);
1102
1103      // Check if user has swiped the slides or if still animating
1104      isMoved = self.isMoved(current);
1105
1106      // Make sure current slide is visible
1107      current.$slide.addClass("fancybox-slide--current");
1108
1109      // Fresh start - reveal container, current slide and start loading content
1110      if (firstRun) {
1111        if (current.opts.animationEffect && duration) {
1112          self.$refs.container.css("transition-duration", duration + "ms");
1113        }
1114
1115        self.$refs.container.addClass("fancybox-is-open").trigger("focus");
1116
1117        // Attempt to load content into slide
1118        // This will later call `afterLoad` -> `revealContent`
1119        self.loadSlide(current);
1120
1121        self.preload("image");
1122
1123        return;
1124      }
1125
1126      // Get actual slide/stage positions (before cleaning up)
1127      slidePos = $.fancybox.getTranslate(previous.$slide);
1128      stagePos = $.fancybox.getTranslate(self.$refs.stage);
1129
1130      // Clean up all slides
1131      $.each(self.slides, function (index, slide) {
1132        $.fancybox.stop(slide.$slide, true);
1133      });
1134
1135      if (previous.pos !== current.pos) {
1136        previous.isComplete = false;
1137      }
1138
1139      previous.$slide.removeClass("fancybox-slide--complete fancybox-slide--current");
1140
1141      // If slides are out of place, then animate them to correct position
1142      if (isMoved) {
1143        // Calculate horizontal swipe distance
1144        diff = slidePos.left - (previous.pos * slidePos.width + previous.pos * previous.opts.gutter);
1145
1146        $.each(self.slides, function (index, slide) {
1147          slide.$slide.removeClass("fancybox-animated").removeClass(function (index, className) {
1148            return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" ");
1149          });
1150
1151          // Make sure that each slide is in equal distance
1152          // This is mostly needed for freshly added slides, because they are not yet positioned
1153          var leftPos = slide.pos * slidePos.width + slide.pos * slide.opts.gutter;
1154
1155          $.fancybox.setTranslate(slide.$slide, {
1156            top: 0,
1157            left: leftPos - stagePos.left + diff
1158          });
1159
1160          if (slide.pos !== current.pos) {
1161            slide.$slide.addClass("fancybox-slide--" + (slide.pos > current.pos ? "next" : "previous"));
1162          }
1163
1164          // Redraw to make sure that transition will start
1165          forceRedraw(slide.$slide);
1166
1167          // Animate the slide
1168          $.fancybox.animate(
1169            slide.$slide, {
1170              top: 0,
1171              left: (slide.pos - current.pos) * slidePos.width + (slide.pos - current.pos) * slide.opts.gutter
1172            },
1173            duration,
1174            function () {
1175              slide.$slide
1176                .css({
1177                  transform: "",
1178                  opacity: ""
1179                })
1180                .removeClass("fancybox-slide--next fancybox-slide--previous");
1181
1182              if (slide.pos === self.currPos) {
1183                self.complete();
1184              }
1185            }
1186          );
1187        });
1188      } else if (duration && current.opts.transitionEffect) {
1189        // Set transition effect for previously active slide
1190        prop = "fancybox-animated fancybox-fx-" + current.opts.transitionEffect;
1191
1192        previous.$slide.addClass("fancybox-slide--" + (previous.pos > current.pos ? "next" : "previous"));
1193
1194        $.fancybox.animate(
1195          previous.$slide,
1196          prop,
1197          duration,
1198          function () {
1199            previous.$slide.removeClass(prop).removeClass("fancybox-slide--next fancybox-slide--previous");
1200          },
1201          false
1202        );
1203      }
1204
1205      if (current.isLoaded) {
1206        self.revealContent(current);
1207      } else {
1208        self.loadSlide(current);
1209      }
1210
1211      self.preload("image");
1212    },
1213
1214    // Create new "slide" element
1215    // These are gallery items  that are actually added to DOM
1216    // =======================================================
1217
1218    createSlide: function (pos) {
1219      var self = this,
1220        $slide,
1221        index;
1222
1223      index = pos % self.group.length;
1224      index = index < 0 ? self.group.length + index : index;
1225
1226      if (!self.slides[pos] && self.group[index]) {
1227        $slide = $('<div class="fancybox-slide"></div>').appendTo(self.$refs.stage);
1228
1229        self.slides[pos] = $.extend(true, {}, self.group[index], {
1230          pos: pos,
1231          $slide: $slide,
1232          isLoaded: false
1233        });
1234
1235        self.updateSlide(self.slides[pos]);
1236      }
1237
1238      return self.slides[pos];
1239    },
1240
1241    // Scale image to the actual size of the image;
1242    // x and y values should be relative to the slide
1243    // ==============================================
1244
1245    scaleToActual: function (x, y, duration) {
1246      var self = this,
1247        current = self.current,
1248        $content = current.$content,
1249        canvasWidth = $.fancybox.getTranslate(current.$slide).width,
1250        canvasHeight = $.fancybox.getTranslate(current.$slide).height,
1251        newImgWidth = current.width,
1252        newImgHeight = current.height,
1253        imgPos,
1254        posX,
1255        posY,
1256        scaleX,
1257        scaleY;
1258
1259      if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) {
1260        return;
1261      }
1262
1263      self.isAnimating = true;
1264
1265      $.fancybox.stop($content);
1266
1267      x = x === undefined ? canvasWidth * 0.5 : x;
1268      y = y === undefined ? canvasHeight * 0.5 : y;
1269
1270      imgPos = $.fancybox.getTranslate($content);
1271
1272      imgPos.top -= $.fancybox.getTranslate(current.$slide).top;
1273      imgPos.left -= $.fancybox.getTranslate(current.$slide).left;
1274
1275      scaleX = newImgWidth / imgPos.width;
1276      scaleY = newImgHeight / imgPos.height;
1277
1278      // Get center position for original image
1279      posX = canvasWidth * 0.5 - newImgWidth * 0.5;
1280      posY = canvasHeight * 0.5 - newImgHeight * 0.5;
1281
1282      // Make sure image does not move away from edges
1283      if (newImgWidth > canvasWidth) {
1284        posX = imgPos.left * scaleX - (x * scaleX - x);
1285
1286        if (posX > 0) {
1287          posX = 0;
1288        }
1289
1290        if (posX < canvasWidth - newImgWidth) {
1291          posX = canvasWidth - newImgWidth;
1292        }
1293      }
1294
1295      if (newImgHeight > canvasHeight) {
1296        posY = imgPos.top * scaleY - (y * scaleY - y);
1297
1298        if (posY > 0) {
1299          posY = 0;
1300        }
1301
1302        if (posY < canvasHeight - newImgHeight) {
1303          posY = canvasHeight - newImgHeight;
1304        }
1305      }
1306
1307      self.updateCursor(newImgWidth, newImgHeight);
1308
1309      $.fancybox.animate(
1310        $content, {
1311          top: posY,
1312          left: posX,
1313          scaleX: scaleX,
1314          scaleY: scaleY
1315        },
1316        duration || 366,
1317        function () {
1318          self.isAnimating = false;
1319        }
1320      );
1321
1322      // Stop slideshow
1323      if (self.SlideShow && self.SlideShow.isActive) {
1324        self.SlideShow.stop();
1325      }
1326    },
1327
1328    // Scale image to fit inside parent element
1329    // ========================================
1330
1331    scaleToFit: function (duration) {
1332      var self = this,
1333        current = self.current,
1334        $content = current.$content,
1335        end;
1336
1337      if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) {
1338        return;
1339      }
1340
1341      self.isAnimating = true;
1342
1343      $.fancybox.stop($content);
1344
1345      end = self.getFitPos(current);
1346
1347      self.updateCursor(end.width, end.height);
1348
1349      $.fancybox.animate(
1350        $content, {
1351          top: end.top,
1352          left: end.left,
1353          scaleX: end.width / $content.width(),
1354          scaleY: end.height / $content.height()
1355        },
1356        duration || 366,
1357        function () {
1358          self.isAnimating = false;
1359        }
1360      );
1361    },
1362
1363    // Calculate image size to fit inside viewport
1364    // ===========================================
1365
1366    getFitPos: function (slide) {
1367      var self = this,
1368        $content = slide.$content,
1369        $slide = slide.$slide,
1370        width = slide.width || slide.opts.width,
1371        height = slide.height || slide.opts.height,
1372        maxWidth,
1373        maxHeight,
1374        minRatio,
1375        aspectRatio,
1376        rez = {};
1377
1378      if (!slide.isLoaded || !$content || !$content.length) {
1379        return false;
1380      }
1381
1382      maxWidth = $.fancybox.getTranslate(self.$refs.stage).width;
1383      maxHeight = $.fancybox.getTranslate(self.$refs.stage).height;
1384
1385      maxWidth -=
1386        parseFloat($slide.css("paddingLeft")) +
1387        parseFloat($slide.css("paddingRight")) +
1388        parseFloat($content.css("marginLeft")) +
1389        parseFloat($content.css("marginRight"));
1390
1391      maxHeight -=
1392        parseFloat($slide.css("paddingTop")) +
1393        parseFloat($slide.css("paddingBottom")) +
1394        parseFloat($content.css("marginTop")) +
1395        parseFloat($content.css("marginBottom"));
1396
1397      if (!width || !height) {
1398        width = maxWidth;
1399        height = maxHeight;
1400      }
1401
1402      minRatio = Math.min(1, maxWidth / width, maxHeight / height);
1403
1404      width = minRatio * width;
1405      height = minRatio * height;
1406
1407      // Adjust width/height to precisely fit into container
1408      if (width > maxWidth - 0.5) {
1409        width = maxWidth;
1410      }
1411
1412      if (height > maxHeight - 0.5) {
1413        height = maxHeight;
1414      }
1415
1416      if (slide.type === "image") {
1417        rez.top = Math.floor((maxHeight - height) * 0.5) + parseFloat($slide.css("paddingTop"));
1418        rez.left = Math.floor((maxWidth - width) * 0.5) + parseFloat($slide.css("paddingLeft"));
1419      } else if (slide.contentType === "video") {
1420        // Force aspect ratio for the video
1421        // "I say the whole world must learn of our peaceful ways… by force!"
1422        aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9;
1423
1424        if (height > width / aspectRatio) {
1425          height = width / aspectRatio;
1426        } else if (width > height * aspectRatio) {
1427          width = height * aspectRatio;
1428        }
1429      }
1430
1431      rez.width = width;
1432      rez.height = height;
1433
1434      return rez;
1435    },
1436
1437    // Update content size and position for all slides
1438    // ==============================================
1439
1440    update: function (e) {
1441      var self = this;
1442
1443      $.each(self.slides, function (key, slide) {
1444        self.updateSlide(slide, e);
1445      });
1446    },
1447
1448    // Update slide content position and size
1449    // ======================================
1450
1451    updateSlide: function (slide, e) {
1452      var self = this,
1453        $content = slide && slide.$content,
1454        width = slide.width || slide.opts.width,
1455        height = slide.height || slide.opts.height,
1456        $slide = slide.$slide;
1457
1458      // First, prevent caption overlap, if needed
1459      self.adjustCaption(slide);
1460
1461      // Then resize content to fit inside the slide
1462      if ($content && (width || height || slide.contentType === "video") && !slide.hasError) {
1463        $.fancybox.stop($content);
1464
1465        $.fancybox.setTranslate($content, self.getFitPos(slide));
1466
1467        if (slide.pos === self.currPos) {
1468          self.isAnimating = false;
1469
1470          self.updateCursor();
1471        }
1472      }
1473
1474      // Then some adjustments
1475      self.adjustLayout(slide);
1476
1477      if ($slide.length) {
1478        $slide.trigger("refresh");
1479
1480        if (slide.pos === self.currPos) {
1481          self.$refs.toolbar
1482            .add(self.$refs.navigation.find(".fancybox-button--arrow_right"))
1483            .toggleClass("compensate-for-scrollbar", $slide.get(0).scrollHeight > $slide.get(0).clientHeight);
1484        }
1485      }
1486
1487      self.trigger("onUpdate", slide, e);
1488    },
1489
1490    // Horizontally center slide
1491    // =========================
1492
1493    centerSlide: function (duration) {
1494      var self = this,
1495        current = self.current,
1496        $slide = current.$slide;
1497
1498      if (self.isClosing || !current) {
1499        return;
1500      }
1501
1502      $slide.siblings().css({
1503        transform: "",
1504        opacity: ""
1505      });
1506
1507      $slide
1508        .parent()
1509        .children()
1510        .removeClass("fancybox-slide--previous fancybox-slide--next");
1511
1512      $.fancybox.animate(
1513        $slide, {
1514          top: 0,
1515          left: 0,
1516          opacity: 1
1517        },
1518        duration === undefined ? 0 : duration,
1519        function () {
1520          // Clean up
1521          $slide.css({
1522            transform: "",
1523            opacity: ""
1524          });
1525
1526          if (!current.isComplete) {
1527            self.complete();
1528          }
1529        },
1530        false
1531      );
1532    },
1533
1534    // Check if current slide is moved (swiped)
1535    // ========================================
1536
1537    isMoved: function (slide) {
1538      var current = slide || this.current,
1539        slidePos,
1540        stagePos;
1541
1542      if (!current) {
1543        return false;
1544      }
1545
1546      stagePos = $.fancybox.getTranslate(this.$refs.stage);
1547      slidePos = $.fancybox.getTranslate(current.$slide);
1548
1549      return (
1550        !current.$slide.hasClass("fancybox-animated") &&
1551        (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5)
1552      );
1553    },
1554
1555    // Update cursor style depending if content can be zoomed
1556    // ======================================================
1557
1558    updateCursor: function (nextWidth, nextHeight) {
1559      var self = this,
1560        current = self.current,
1561        $container = self.$refs.container,
1562        canPan,
1563        isZoomable;
1564
1565      if (!current || self.isClosing || !self.Guestures) {
1566        return;
1567      }
1568
1569      $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan");
1570
1571      canPan = self.canPan(nextWidth, nextHeight);
1572
1573      isZoomable = canPan ? true : self.isZoomable();
1574
1575      $container.toggleClass("fancybox-is-zoomable", isZoomable);
1576
1577      $("[data-fancybox-zoom]").prop("disabled", !isZoomable);
1578
1579      if (canPan) {
1580        $container.addClass("fancybox-can-pan");
1581      } else if (
1582        isZoomable &&
1583        (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) == "zoom"))
1584      ) {
1585        $container.addClass("fancybox-can-zoomIn");
1586      } else if (current.opts.touch && (current.opts.touch.vertical || self.group.length > 1) && current.contentType !== "video") {
1587        $container.addClass("fancybox-can-swipe");
1588      }
1589    },
1590
1591    // Check if current slide is zoomable
1592    // ==================================
1593
1594    isZoomable: function () {
1595      var self = this,
1596        current = self.current,
1597        fitPos;
1598
1599      // Assume that slide is zoomable if:
1600      //   - image is still loading
1601      //   - actual size of the image is smaller than available area
1602      if (current && !self.isClosing && current.type === "image" && !current.hasError) {
1603        if (!current.isLoaded) {
1604          return true;
1605        }
1606
1607        fitPos = self.getFitPos(current);
1608
1609        if (fitPos && (current.width > fitPos.width || current.height > fitPos.height)) {
1610          return true;
1611        }
1612      }
1613
1614      return false;
1615    },
1616
1617    // Check if current image dimensions are smaller than actual
1618    // =========================================================
1619
1620    isScaledDown: function (nextWidth, nextHeight) {
1621      var self = this,
1622        rez = false,
1623        current = self.current,
1624        $content = current.$content;
1625
1626      if (nextWidth !== undefined && nextHeight !== undefined) {
1627        rez = nextWidth < current.width && nextHeight < current.height;
1628      } else if ($content) {
1629        rez = $.fancybox.getTranslate($content);
1630        rez = rez.width < current.width && rez.height < current.height;
1631      }
1632
1633      return rez;
1634    },
1635
1636    // Check if image dimensions exceed parent element
1637    // ===============================================
1638
1639    canPan: function (nextWidth, nextHeight) {
1640      var self = this,
1641        current = self.current,
1642        pos = null,
1643        rez = false;
1644
1645      if (current.type === "image" && (current.isComplete || (nextWidth && nextHeight)) && !current.hasError) {
1646        rez = self.getFitPos(current);
1647
1648        if (nextWidth !== undefined && nextHeight !== undefined) {
1649          pos = {
1650            width: nextWidth,
1651            height: nextHeight
1652          };
1653        } else if (current.isComplete) {
1654          pos = $.fancybox.getTranslate(current.$content);
1655        }
1656
1657        if (pos && rez) {
1658          rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5;
1659        }
1660      }
1661
1662      return rez;
1663    },
1664
1665    // Load content into the slide
1666    // ===========================
1667
1668    loadSlide: function (slide) {
1669      var self = this,
1670        type,
1671        $slide,
1672        ajaxLoad;
1673
1674      if (slide.isLoading || slide.isLoaded) {
1675        return;
1676      }
1677
1678      slide.isLoading = true;
1679
1680      if (self.trigger("beforeLoad", slide) === false) {
1681        slide.isLoading = false;
1682
1683        return false;
1684      }
1685
1686      type = slide.type;
1687      $slide = slide.$slide;
1688
1689      $slide
1690        .off("refresh")
1691        .trigger("onReset")
1692        .addClass(slide.opts.slideClass);
1693
1694      // Create content depending on the type
1695      switch (type) {
1696        case "image":
1697          self.setImage(slide);
1698
1699          break;
1700
1701        case "iframe":
1702          self.setIframe(slide);
1703
1704          break;
1705
1706        case "html":
1707          self.setContent(slide, slide.src || slide.content);
1708
1709          break;
1710
1711        case "video":
1712          self.setContent(
1713            slide,
1714            slide.opts.video.tpl
1715            .replace(/\{\{src\}\}/gi, slide.src)
1716            .replace("{{format}}", slide.opts.videoFormat || slide.opts.video.format || "")
1717            .replace("{{poster}}", slide.thumb || "")
1718          );
1719
1720          break;
1721
1722        case "inline":
1723          if ($(slide.src).length) {
1724            self.setContent(slide, $(slide.src));
1725          } else {
1726            self.setError(slide);
1727          }
1728
1729          break;
1730
1731        case "ajax":
1732          self.showLoading(slide);
1733
1734          ajaxLoad = $.ajax(
1735            $.extend({}, slide.opts.ajax.settings, {
1736              url: slide.src,
1737              success: function (data, textStatus) {
1738                if (textStatus === "success") {
1739                  self.setContent(slide, data);
1740                }
1741              },
1742              error: function (jqXHR, textStatus) {
1743                if (jqXHR && textStatus !== "abort") {
1744                  self.setError(slide);
1745                }
1746              }
1747            })
1748          );
1749
1750          $slide.one("onReset", function () {
1751            ajaxLoad.abort();
1752          });
1753
1754          break;
1755
1756        default:
1757          self.setError(slide);
1758
1759          break;
1760      }
1761
1762      return true;
1763    },
1764
1765    // Use thumbnail image, if possible
1766    // ================================
1767
1768    setImage: function (slide) {
1769      var self = this,
1770        ghost;
1771
1772      // Check if need to show loading icon
1773      setTimeout(function () {
1774        var $img = slide.$image;
1775
1776        if (!self.isClosing && slide.isLoading && (!$img || !$img.length || !$img[0].complete) && !slide.hasError) {
1777          self.showLoading(slide);
1778        }
1779      }, 50);
1780
1781      //Check if image has srcset
1782      self.checkSrcset(slide);
1783
1784      // This will be wrapper containing both ghost and actual image
1785      slide.$content = $('<div class="fancybox-content"></div>')
1786        .addClass("fancybox-is-hidden")
1787        .appendTo(slide.$slide.addClass("fancybox-slide--image"));
1788
1789      // If we have a thumbnail, we can display it while actual image is loading
1790      // Users will not stare at black screen and actual image will appear gradually
1791      if (slide.opts.preload !== false && slide.opts.width && slide.opts.height && slide.thumb) {
1792        slide.width = slide.opts.width;
1793        slide.height = slide.opts.height;
1794
1795        ghost = document.createElement("img");
1796
1797        ghost.onerror = function () {
1798          $(this).remove();
1799
1800          slide.$ghost = null;
1801        };
1802
1803        ghost.onload = function () {
1804          self.afterLoad(slide);
1805        };
1806
1807        slide.$ghost = $(ghost)
1808          .addClass("fancybox-image")
1809          .appendTo(slide.$content)
1810          .attr("src", slide.thumb);
1811      }
1812
1813      // Start loading actual image
1814      self.setBigImage(slide);
1815    },
1816
1817    // Check if image has srcset and get the source
1818    // ============================================
1819    checkSrcset: function (slide) {
1820      var srcset = slide.opts.srcset || slide.opts.image.srcset,
1821        found,
1822        temp,
1823        pxRatio,
1824        windowWidth;
1825
1826      // If we have "srcset", then we need to find first matching "src" value.
1827      // This is necessary, because when you set an src attribute, the browser will preload the image
1828      // before any javascript or even CSS is applied.
1829      if (srcset) {
1830        pxRatio = window.devicePixelRatio || 1;
1831        windowWidth = window.innerWidth * pxRatio;
1832
1833        temp = srcset.split(",").map(function (el) {
1834          var ret = {};
1835
1836          el.trim()
1837            .split(/\s+/)
1838            .forEach(function (el, i) {
1839              var value = parseInt(el.substring(0, el.length - 1), 10);
1840
1841              if (i === 0) {
1842                return (ret.url = el);
1843              }
1844
1845              if (value) {
1846                ret.value = value;
1847                ret.postfix = el[el.length - 1];
1848              }
1849            });
1850
1851          return ret;
1852        });
1853
1854        // Sort by value
1855        temp.sort(function (a, b) {
1856          return a.value - b.value;
1857        });
1858
1859        // Ok, now we have an array of all srcset values
1860        for (var j = 0; j < temp.length; j++) {
1861          var el = temp[j];
1862
1863          if ((el.postfix === "w" && el.value >= windowWidth) || (el.postfix === "x" && el.value >= pxRatio)) {
1864            found = el;
1865            break;
1866          }
1867        }
1868
1869        // If not found, take the last one
1870        if (!found && temp.length) {
1871          found = temp[temp.length - 1];
1872        }
1873
1874        if (found) {
1875          slide.src = found.url;
1876
1877          // If we have default width/height values, we can calculate height for matching source
1878          if (slide.width && slide.height && found.postfix == "w") {
1879            slide.height = (slide.width / slide.height) * found.value;
1880            slide.width = found.value;
1881          }
1882
1883          slide.opts.srcset = srcset;
1884        }
1885      }
1886    },
1887
1888    // Create full-size image
1889    // ======================
1890
1891    setBigImage: function (slide) {
1892      var self = this,
1893        img = document.createElement("img"),
1894        $img = $(img);
1895
1896      slide.$image = $img
1897        .one("error", function () {
1898          self.setError(slide);
1899        })
1900        .one("load", function () {
1901          var sizes;
1902
1903          if (!slide.$ghost) {
1904            self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight);
1905
1906            self.afterLoad(slide);
1907          }
1908
1909          if (self.isClosing) {
1910            return;
1911          }
1912
1913          if (slide.opts.srcset) {
1914            sizes = slide.opts.sizes;
1915
1916            if (!sizes || sizes === "auto") {
1917              sizes =
1918                (slide.width / slide.height > 1 && $W.width() / $W.height() > 1 ? "100" : Math.round((slide.width / slide.height) * 100)) +
1919                "vw";
1920            }
1921
1922            $img.attr("sizes", sizes).attr("srcset", slide.opts.srcset);
1923          }
1924
1925          // Hide temporary image after some delay
1926          if (slide.$ghost) {
1927            setTimeout(function () {
1928              if (slide.$ghost && !self.isClosing) {
1929                slide.$ghost.hide();
1930              }
1931            }, Math.min(300, Math.max(1000, slide.height / 1600)));
1932          }
1933
1934          self.hideLoading(slide);
1935        })
1936        .addClass("fancybox-image")
1937        .attr("src", slide.src)
1938        .appendTo(slide.$content);
1939
1940      if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) {
1941        $img.trigger("load");
1942      } else if (img.error) {
1943        $img.trigger("error");
1944      }
1945    },
1946
1947    // Computes the slide size from image size and maxWidth/maxHeight
1948    // ==============================================================
1949
1950    resolveImageSlideSize: function (slide, imgWidth, imgHeight) {
1951      var maxWidth = parseInt(slide.opts.width, 10),
1952        maxHeight = parseInt(slide.opts.height, 10);
1953
1954      // Sets the default values from the image
1955      slide.width = imgWidth;
1956      slide.height = imgHeight;
1957
1958      if (maxWidth > 0) {
1959        slide.width = maxWidth;
1960        slide.height = Math.floor((maxWidth * imgHeight) / imgWidth);
1961      }
1962
1963      if (maxHeight > 0) {
1964        slide.width = Math.floor((maxHeight * imgWidth) / imgHeight);
1965        slide.height = maxHeight;
1966      }
1967    },
1968
1969    // Create iframe wrapper, iframe and bindings
1970    // ==========================================
1971
1972    setIframe: function (slide) {
1973      var self = this,
1974        opts = slide.opts.iframe,
1975        $slide = slide.$slide,
1976        $iframe;
1977
1978      slide.$content = $('<div class="fancybox-content' + (opts.preload ? " fancybox-is-hidden" : "") + '"></div>')
1979        .css(opts.css)
1980        .appendTo($slide);
1981
1982      $slide.addClass("fancybox-slide--" + slide.contentType);
1983
1984      slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime()))
1985        .attr(opts.attr)
1986        .appendTo(slide.$content);
1987
1988      if (opts.preload) {
1989        self.showLoading(slide);
1990
1991        // Unfortunately, it is not always possible to determine if iframe is successfully loaded
1992        // (due to browser security policy)
1993
1994        $iframe.on("load.fb error.fb", function (e) {
1995          this.isReady = 1;
1996
1997          slide.$slide.trigger("refresh");
1998
1999          self.afterLoad(slide);
2000        });
2001
2002        // Recalculate iframe content size
2003        // ===============================
2004
2005        $slide.on("refresh.fb", function () {
2006          var $content = slide.$content,
2007            frameWidth = opts.css.width,
2008            frameHeight = opts.css.height,
2009            $contents,
2010            $body;
2011
2012          if ($iframe[0].isReady !== 1) {
2013            return;
2014          }
2015
2016          try {
2017            $contents = $iframe.contents();
2018            $body = $contents.find("body");
2019          } catch (ignore) {}
2020
2021          // Calculate content dimensions, if it is accessible
2022          if ($body && $body.length && $body.children().length) {
2023            // Avoid scrolling to top (if multiple instances)
2024            $slide.css("overflow", "visible");
2025
2026            $content.css({
2027              width: "100%",
2028              "max-width": "100%",
2029              height: "9999px"
2030            });
2031
2032            if (frameWidth === undefined) {
2033              frameWidth = Math.ceil(Math.max($body[0].clientWidth, $body.outerWidth(true)));
2034            }
2035
2036            $content.css("width", frameWidth ? frameWidth : "").css("max-width", "");
2037
2038            if (frameHeight === undefined) {
2039              frameHeight = Math.ceil(Math.max($body[0].clientHeight, $body.outerHeight(true)));
2040            }
2041
2042            $content.css("height", frameHeight ? frameHeight : "");
2043
2044            $slide.css("overflow", "auto");
2045          }
2046
2047          $content.removeClass("fancybox-is-hidden");
2048        });
2049      } else {
2050        self.afterLoad(slide);
2051      }
2052
2053      $iframe.attr("src", slide.src);
2054
2055      // Remove iframe if closing or changing gallery item
2056      $slide.one("onReset", function () {
2057        // This helps IE not to throw errors when closing
2058        try {
2059          $(this)
2060            .find("iframe")
2061            .hide()
2062            .unbind()
2063            .attr("src", "//about:blank");
2064        } catch (ignore) {}
2065
2066        $(this)
2067          .off("refresh.fb")
2068          .empty();
2069
2070        slide.isLoaded = false;
2071        slide.isRevealed = false;
2072      });
2073    },
2074
2075    // Wrap and append content to the slide
2076    // ======================================
2077
2078    setContent: function (slide, content) {
2079      var self = this;
2080
2081      if (self.isClosing) {
2082        return;
2083      }
2084
2085      self.hideLoading(slide);
2086
2087      if (slide.$content) {
2088        $.fancybox.stop(slide.$content);
2089      }
2090
2091      slide.$slide.empty();
2092
2093      // If content is a jQuery object, then it will be moved to the slide.
2094      // The placeholder is created so we will know where to put it back.
2095      if (isQuery(content) && content.parent().length) {
2096        // Make sure content is not already moved to fancyBox
2097        if (content.hasClass("fancybox-content") || content.parent().hasClass("fancybox-content")) {
2098          content.parents(".fancybox-slide").trigger("onReset");
2099        }
2100
2101        // Create temporary element marking original place of the content
2102        slide.$placeholder = $("<div>")
2103          .hide()
2104          .insertAfter(content);
2105
2106        // Make sure content is visible
2107        content.css("display", "inline-block");
2108      } else if (!slide.hasError) {
2109        // If content is just a plain text, try to convert it to html
2110        if ($.type(content) === "string") {
2111          content = $("<div>")
2112            .append($.trim(content))
2113            .contents();
2114        }
2115
2116        // If "filter" option is provided, then filter content
2117        if (slide.opts.filter) {
2118          content = $("<div>")
2119            .html(content)
2120            .find(slide.opts.filter);
2121        }
2122      }
2123
2124      slide.$slide.one("onReset", function () {
2125        // Pause all html5 video/audio
2126        $(this)
2127          .find("video,audio")
2128          .trigger("pause");
2129
2130        // Put content back
2131        if (slide.$placeholder) {
2132          slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove();
2133
2134          slide.$placeholder = null;
2135        }
2136
2137        // Remove custom close button
2138        if (slide.$smallBtn) {
2139          slide.$smallBtn.remove();
2140
2141          slide.$smallBtn = null;
2142        }
2143
2144        // Remove content and mark slide as not loaded
2145        if (!slide.hasError) {
2146          $(this).empty();
2147
2148          slide.isLoaded = false;
2149          slide.isRevealed = false;
2150        }
2151      });
2152
2153      $(content).appendTo(slide.$slide);
2154
2155      if ($(content).is("video,audio")) {
2156        $(content).addClass("fancybox-video");
2157
2158        $(content).wrap("<div></div>");
2159
2160        slide.contentType = "video";
2161
2162        slide.opts.width = slide.opts.width || $(content).attr("width");
2163        slide.opts.height = slide.opts.height || $(content).attr("height");
2164      }
2165
2166      slide.$content = slide.$slide
2167        .children()
2168        .filter("div,form,main,video,audio,article,.fancybox-content")
2169        .first();
2170
2171      slide.$content.siblings().hide();
2172
2173      // Re-check if there is a valid content
2174      // (in some cases, ajax response can contain various elements or plain text)
2175      if (!slide.$content.length) {
2176        slide.$content = slide.$slide
2177          .wrapInner("<div></div>")
2178          .children()
2179          .first();
2180      }
2181
2182      slide.$content.addClass("fancybox-content");
2183
2184      slide.$slide.addClass("fancybox-slide--" + slide.contentType);
2185
2186      self.afterLoad(slide);
2187    },
2188
2189    // Display error message
2190    // =====================
2191
2192    setError: function (slide) {
2193      slide.hasError = true;
2194
2195      slide.$slide
2196        .trigger("onReset")
2197        .removeClass("fancybox-slide--" + slide.contentType)
2198        .addClass("fancybox-slide--error");
2199
2200      slide.contentType = "html";
2201
2202      this.setContent(slide, this.translate(slide, slide.opts.errorTpl));
2203
2204      if (slide.pos === this.currPos) {
2205        this.isAnimating = false;
2206      }
2207    },
2208
2209    // Show loading icon inside the slide
2210    // ==================================
2211
2212    showLoading: function (slide) {
2213      var self = this;
2214
2215      slide = slide || self.current;
2216
2217      if (slide && !slide.$spinner) {
2218        slide.$spinner = $(self.translate(self, self.opts.spinnerTpl))
2219          .appendTo(slide.$slide)
2220          .hide()
2221          .fadeIn("fast");
2222      }
2223    },
2224
2225    // Remove loading icon from the slide
2226    // ==================================
2227
2228    hideLoading: function (slide) {
2229      var self = this;
2230
2231      slide = slide || self.current;
2232
2233      if (slide && slide.$spinner) {
2234        slide.$spinner.stop().remove();
2235
2236        delete slide.$spinner;
2237      }
2238    },
2239
2240    // Adjustments after slide content has been loaded
2241    // ===============================================
2242
2243    afterLoad: function (slide) {
2244      var self = this;
2245
2246      if (self.isClosing) {
2247        return;
2248      }
2249
2250      slide.isLoading = false;
2251      slide.isLoaded = true;
2252
2253      self.trigger("afterLoad", slide);
2254
2255      self.hideLoading(slide);
2256
2257      // Add small close button
2258      if (slide.opts.smallBtn && (!slide.$smallBtn || !slide.$smallBtn.length)) {
2259        slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).appendTo(slide.$content);
2260      }
2261
2262      // Disable right click
2263      if (slide.opts.protect && slide.$content && !slide.hasError) {
2264        slide.$content.on("contextmenu.fb", function (e) {
2265          if (e.button == 2) {
2266            e.preventDefault();
2267          }
2268
2269          return true;
2270        });
2271
2272        // Add fake element on top of the image
2273        // This makes a bit harder for user to select image
2274        if (slide.type === "image") {
2275          $('<div class="fancybox-spaceball"></div>').appendTo(slide.$content);
2276        }
2277      }
2278
2279      self.adjustCaption(slide);
2280
2281      self.adjustLayout(slide);
2282
2283      if (slide.pos === self.currPos) {
2284        self.updateCursor();
2285      }
2286
2287      self.revealContent(slide);
2288    },
2289
2290    // Prevent caption overlap,
2291    // fix css inconsistency across browsers
2292    // =====================================
2293
2294    adjustCaption: function (slide) {
2295      var self = this,
2296        current = slide || self.current,
2297        caption = current.opts.caption,
2298        preventOverlap = current.opts.preventCaptionOverlap,
2299        $caption = self.$refs.caption,
2300        $clone,
2301        captionH = false;
2302
2303      $caption.toggleClass("fancybox-caption--separate", preventOverlap);
2304
2305      if (preventOverlap && caption && caption.length) {
2306        if (current.pos !== self.currPos) {
2307          $clone = $caption.clone().appendTo($caption.parent());
2308
2309          $clone
2310            .children()
2311            .eq(0)
2312            .empty()
2313            .html(caption);
2314
2315          captionH = $clone.outerHeight(true);
2316
2317          $clone.empty().remove();
2318        } else if (self.$caption) {
2319          captionH = self.$caption.outerHeight(true);
2320        }
2321
2322        current.$slide.css("padding-bottom", captionH || "");
2323      }
2324    },
2325
2326    // Simple hack to fix inconsistency across browsers, described here (affects Edge, too):
2327    // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
2328    // ====================================================================================
2329
2330    adjustLayout: function (slide) {
2331      var self = this,
2332        current = slide || self.current,
2333        scrollHeight,
2334        marginBottom,
2335        inlinePadding,
2336        actualPadding;
2337
2338      if (current.isLoaded && current.opts.disableLayoutFix !== true) {
2339        current.$content.css("margin-bottom", "");
2340
2341        // If we would always set margin-bottom for the content,
2342        // then it would potentially break vertical align
2343        if (current.$content.outerHeight() > current.$slide.height() + 0.5) {
2344          inlinePadding = current.$slide[0].style["padding-bottom"];
2345          actualPadding = current.$slide.css("padding-bottom");
2346
2347          if (parseFloat(actualPadding) > 0) {
2348            scrollHeight = current.$slide[0].scrollHeight;
2349
2350            current.$slide.css("padding-bottom", 0);
2351
2352            if (Math.abs(scrollHeight - current.$slide[0].scrollHeight) < 1) {
2353              marginBottom = actualPadding;
2354            }
2355
2356            current.$slide.css("padding-bottom", inlinePadding);
2357          }
2358        }
2359
2360        current.$content.css("margin-bottom", marginBottom);
2361      }
2362    },
2363
2364    // Make content visible
2365    // This method is called right after content has been loaded or
2366    // user navigates gallery and transition should start
2367    // ============================================================
2368
2369    revealContent: function (slide) {
2370      var self = this,
2371        $slide = slide.$slide,
2372        end = false,
2373        start = false,
2374        isMoved = self.isMoved(slide),
2375        isRevealed = slide.isRevealed,
2376        effect,
2377        effectClassName,
2378        duration,
2379        opacity;
2380
2381      slide.isRevealed = true;
2382
2383      effect = slide.opts[self.firstRun ? "animationEffect" : "transitionEffect"];
2384      duration = slide.opts[self.firstRun ? "animationDuration" : "transitionDuration"];
2385
2386      duration = parseInt(slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10);
2387
2388      if (isMoved || slide.pos !== self.currPos || !duration) {
2389        effect = false;
2390      }
2391
2392      // Check if can zoom
2393      if (effect === "zoom") {
2394        if (slide.pos === self.currPos && duration && slide.type === "image" && !slide.hasError && (start = self.getThumbPos(slide))) {
2395          end = self.getFitPos(slide);
2396        } else {
2397          effect = "fade";
2398        }
2399      }
2400
2401      // Zoom animation
2402      // ==============
2403      if (effect === "zoom") {
2404        self.isAnimating = true;
2405
2406        end.scaleX = end.width / start.width;
2407        end.scaleY = end.height / start.height;
2408
2409        // Check if we need to animate opacity
2410        opacity = slide.opts.zoomOpacity;
2411
2412        if (opacity == "auto") {
2413          opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1;
2414        }
2415
2416        if (opacity) {
2417          start.opacity = 0.1;
2418          end.opacity = 1;
2419        }
2420
2421        // Draw image at start position
2422        $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start);
2423
2424        forceRedraw(slide.$content);
2425
2426        // Start animation
2427        $.fancybox.animate(slide.$content, end, duration, function () {
2428          self.isAnimating = false;
2429
2430          self.complete();
2431        });
2432
2433        return;
2434      }
2435
2436      self.updateSlide(slide);
2437
2438      // Simply show content if no effect
2439      // ================================
2440      if (!effect) {
2441        slide.$content.removeClass("fancybox-is-hidden");
2442
2443        if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) {
2444          slide.$content.hide().fadeIn("fast");
2445        }
2446
2447        if (slide.pos === self.currPos) {
2448          self.complete();
2449        }
2450
2451        return;
2452      }
2453
2454      // Prepare for CSS transiton
2455      // =========================
2456      $.fancybox.stop($slide);
2457
2458      //effectClassName = "fancybox-animated fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-fx-" + effect;
2459      effectClassName = "fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-animated fancybox-fx-" + effect;
2460
2461      $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName);
2462
2463      slide.$content.removeClass("fancybox-is-hidden");
2464
2465      // Force reflow
2466      forceRedraw($slide);
2467
2468      if (slide.type !== "image") {
2469        slide.$content.hide().show(0);
2470      }
2471
2472      $.fancybox.animate(
2473        $slide,
2474        "fancybox-slide--current",
2475        duration,
2476        function () {
2477          $slide.removeClass(effectClassName).css({
2478            transform: "",
2479            opacity: ""
2480          });
2481
2482          if (slide.pos === self.currPos) {
2483            self.complete();
2484          }
2485        },
2486        true
2487      );
2488    },
2489
2490    // Check if we can and have to zoom from thumbnail
2491    //================================================
2492
2493    getThumbPos: function (slide) {
2494      var rez = false,
2495        $thumb = slide.$thumb,
2496        thumbPos,
2497        btw,
2498        brw,
2499        bbw,
2500        blw;
2501
2502      if (!$thumb || !inViewport($thumb[0])) {
2503        return false;
2504      }
2505
2506      thumbPos = $.fancybox.getTranslate($thumb);
2507
2508      btw = parseFloat($thumb.css("border-top-width") || 0);
2509      brw = parseFloat($thumb.css("border-right-width") || 0);
2510      bbw = parseFloat($thumb.css("border-bottom-width") || 0);
2511      blw = parseFloat($thumb.css("border-left-width") || 0);
2512
2513      rez = {
2514        top: thumbPos.top + btw,
2515        left: thumbPos.left + blw,
2516        width: thumbPos.width - brw - blw,
2517        height: thumbPos.height - btw - bbw,
2518        scaleX: 1,
2519        scaleY: 1
2520      };
2521
2522      return thumbPos.width > 0 && thumbPos.height > 0 ? rez : false;
2523    },
2524
2525    // Final adjustments after current gallery item is moved to position
2526    // and it`s content is loaded
2527    // ==================================================================
2528
2529    complete: function () {
2530      var self = this,
2531        current = self.current,
2532        slides = {},
2533        $el;
2534
2535      if (self.isMoved() || !current.isLoaded) {
2536        return;
2537      }
2538
2539      if (!current.isComplete) {
2540        current.isComplete = true;
2541
2542        current.$slide.siblings().trigger("onReset");
2543
2544        self.preload("inline");
2545
2546        // Trigger any CSS transiton inside the slide
2547        forceRedraw(current.$slide);
2548
2549        current.$slide.addClass("fancybox-slide--complete");
2550
2551        // Remove unnecessary slides
2552        $.each(self.slides, function (key, slide) {
2553          if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) {
2554            slides[slide.pos] = slide;
2555          } else if (slide) {
2556            $.fancybox.stop(slide.$slide);
2557
2558            slide.$slide.off().remove();
2559          }
2560        });
2561
2562        self.slides = slides;
2563      }
2564
2565      self.isAnimating = false;
2566
2567      self.updateCursor();
2568
2569      self.trigger("afterShow");
2570
2571      // Autoplay first html5 video/audio
2572      if (!!current.opts.video.autoStart) {
2573        current.$slide
2574          .find("video,audio")
2575          .filter(":visible:first")
2576          .trigger("play")
2577          .one("ended", function () {
2578            if (Document.exitFullscreen) {
2579              Document.exitFullscreen();
2580            } else if (this.webkitExitFullscreen) {
2581              this.webkitExitFullscreen();
2582            }
2583
2584            self.next();
2585          });
2586      }
2587
2588      // Try to focus on the first focusable element
2589      if (current.opts.autoFocus && current.contentType === "html") {
2590        // Look for the first input with autofocus attribute
2591        $el = current.$content.find("input[autofocus]:enabled:visible:first");
2592
2593        if ($el.length) {
2594          $el.trigger("focus");
2595        } else {
2596          self.focus(null, true);
2597        }
2598      }
2599
2600      // Avoid jumping
2601      current.$slide.scrollTop(0).scrollLeft(0);
2602    },
2603
2604    // Preload next and previous slides
2605    // ================================
2606
2607    preload: function (type) {
2608      var self = this,
2609        prev,
2610        next;
2611
2612      if (self.group.length < 2) {
2613        return;
2614      }
2615
2616      next = self.slides[self.currPos + 1];
2617      prev = self.slides[self.currPos - 1];
2618
2619      if (prev && prev.type === type) {
2620        self.loadSlide(prev);
2621      }
2622
2623      if (next && next.type === type) {
2624        self.loadSlide(next);
2625      }
2626    },
2627
2628    // Try to find and focus on the first focusable element
2629    // ====================================================
2630
2631    focus: function (e, firstRun) {
2632      var self = this,
2633        focusableStr = [
2634          "a[href]",
2635          "area[href]",
2636          'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
2637          "select:not([disabled]):not([aria-hidden])",
2638          "textarea:not([disabled]):not([aria-hidden])",
2639          "button:not([disabled]):not([aria-hidden])",
2640          "iframe",
2641          "object",
2642          "embed",
2643          "video",
2644          "audio",
2645          "[contenteditable]",
2646          '[tabindex]:not([tabindex^="-"])'
2647        ].join(","),
2648        focusableItems,
2649        focusedItemIndex;
2650
2651      if (self.isClosing) {
2652        return;
2653      }
2654
2655      if (e || !self.current || !self.current.isComplete) {
2656        // Focus on any element inside fancybox
2657        focusableItems = self.$refs.container.find("*:visible");
2658      } else {
2659        // Focus inside current slide
2660        focusableItems = self.current.$slide.find("*:visible" + (firstRun ? ":not(.fancybox-close-small)" : ""));
2661      }
2662
2663      focusableItems = focusableItems.filter(focusableStr).filter(function () {
2664        return $(this).css("visibility") !== "hidden" && !$(this).hasClass("disabled");
2665      });
2666
2667      if (focusableItems.length) {
2668        focusedItemIndex = focusableItems.index(document.activeElement);
2669
2670        if (e && e.shiftKey) {
2671          // Back tab
2672          if (focusedItemIndex < 0 || focusedItemIndex == 0) {
2673            e.preventDefault();
2674
2675            focusableItems.eq(focusableItems.length - 1).trigger("focus");
2676          }
2677        } else {
2678          // Outside or Forward tab
2679          if (focusedItemIndex < 0 || focusedItemIndex == focusableItems.length - 1) {
2680            if (e) {
2681              e.preventDefault();
2682            }
2683
2684            focusableItems.eq(0).trigger("focus");
2685          }
2686        }
2687      } else {
2688        self.$refs.container.trigger("focus");
2689      }
2690    },
2691
2692    // Activates current instance - brings container to the front and enables keyboard,
2693    // notifies other instances about deactivating
2694    // =================================================================================
2695
2696    activate: function () {
2697      var self = this;
2698
2699      // Deactivate all instances
2700      $(".fancybox-container").each(function () {
2701        var instance = $(this).data("FancyBox");
2702
2703        // Skip self and closing instances
2704        if (instance && instance.id !== self.id && !instance.isClosing) {
2705          instance.trigger("onDeactivate");
2706
2707          instance.removeEvents();
2708
2709          instance.isVisible = false;
2710        }
2711      });
2712
2713      self.isVisible = true;
2714
2715      if (self.current || self.isIdle) {
2716        self.update();
2717
2718        self.updateControls();
2719      }
2720
2721      self.trigger("onActivate");
2722
2723      self.addEvents();
2724    },
2725
2726    // Start closing procedure
2727    // This will start "zoom-out" animation if needed and clean everything up afterwards
2728    // =================================================================================
2729
2730    close: function (e, d) {
2731      var self = this,
2732        current = self.current,
2733        effect,
2734        duration,
2735        $content,
2736        domRect,
2737        opacity,
2738        start,
2739        end;
2740
2741      var done = function () {
2742        self.cleanUp(e);
2743      };
2744
2745      if (self.isClosing) {
2746        return false;
2747      }
2748
2749      self.isClosing = true;
2750
2751      // If beforeClose callback prevents closing, make sure content is centered
2752      if (self.trigger("beforeClose", e) === false) {
2753        self.isClosing = false;
2754
2755        requestAFrame(function () {
2756          self.update();
2757        });
2758
2759        return false;
2760      }
2761
2762      // Remove all events
2763      // If there are multiple instances, they will be set again by "activate" method
2764      self.removeEvents();
2765
2766      $content = current.$content;
2767      effect = current.opts.animationEffect;
2768      duration = $.isNumeric(d) ? d : effect ? current.opts.animationDuration : 0;
2769
2770      current.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated");
2771
2772      if (e !== true) {
2773        $.fancybox.stop(current.$slide);
2774      } else {
2775        effect = false;
2776      }
2777
2778      // Remove other slides
2779      current.$slide
2780        .siblings()
2781        .trigger("onReset")
2782        .remove();
2783
2784      // Trigger animations
2785      if (duration) {
2786        self.$refs.container
2787          .removeClass("fancybox-is-open")
2788          .addClass("fancybox-is-closing")
2789          .css("transition-duration", duration + "ms");
2790      }
2791
2792      // Clean up
2793      self.hideLoading(current);
2794
2795      self.hideControls(true);
2796
2797      self.updateCursor();
2798
2799      // Check if possible to zoom-out
2800      if (
2801        effect === "zoom" &&
2802        !($content && duration && current.type === "image" && !self.isMoved() && !current.hasError && (end = self.getThumbPos(current)))
2803      ) {
2804        effect = "fade";
2805      }
2806
2807      if (effect === "zoom") {
2808        $.fancybox.stop($content);
2809
2810        domRect = $.fancybox.getTranslate($content);
2811
2812        start = {
2813          top: domRect.top,
2814          left: domRect.left,
2815          scaleX: domRect.width / end.width,
2816          scaleY: domRect.height / end.height,
2817          width: end.width,
2818          height: end.height
2819        };
2820
2821        // Check if we need to animate opacity
2822        opacity = current.opts.zoomOpacity;
2823
2824        if (opacity == "auto") {
2825          opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1;
2826        }
2827
2828        if (opacity) {
2829          end.opacity = 0;
2830        }
2831
2832        $.fancybox.setTranslate($content, start);
2833
2834        forceRedraw($content);
2835
2836        $.fancybox.animate($content, end, duration, done);
2837
2838        return true;
2839      }
2840
2841      if (effect && duration) {
2842        $.fancybox.animate(
2843          current.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"),
2844          "fancybox-animated fancybox-fx-" + effect,
2845          duration,
2846          done
2847        );
2848      } else {
2849        // If skip animation
2850        if (e === true) {
2851          setTimeout(done, duration);
2852        } else {
2853          done();
2854        }
2855      }
2856
2857      return true;
2858    },
2859
2860    // Final adjustments after removing the instance
2861    // =============================================
2862
2863    cleanUp: function (e) {
2864      var self = this,
2865        instance,
2866        $focus = self.current.opts.$orig,
2867        x,
2868        y;
2869
2870      self.current.$slide.trigger("onReset");
2871
2872      self.$refs.container.empty().remove();
2873
2874      self.trigger("afterClose", e);
2875
2876      // Place back focus
2877      if (!!self.current.opts.backFocus) {
2878        if (!$focus || !$focus.length || !$focus.is(":visible")) {
2879          $focus = self.$trigger;
2880        }
2881
2882        if ($focus && $focus.length) {
2883          x = window.scrollX;
2884          y = window.scrollY;
2885
2886          $focus.trigger("focus");
2887
2888          $("html, body")
2889            .scrollTop(y)
2890            .scrollLeft(x);
2891        }
2892      }
2893
2894      self.current = null;
2895
2896      // Check if there are other instances
2897      instance = $.fancybox.getInstance();
2898
2899      if (instance) {
2900        instance.activate();
2901      } else {
2902        $("body").removeClass("fancybox-active compensate-for-scrollbar");
2903
2904        $("#fancybox-style-noscroll").remove();
2905      }
2906    },
2907
2908    // Call callback and trigger an event
2909    // ==================================
2910
2911    trigger: function (name, slide) {
2912      var args = Array.prototype.slice.call(arguments, 1),
2913        self = this,
2914        obj = slide && slide.opts ? slide : self.current,
2915        rez;
2916
2917      if (obj) {
2918        args.unshift(obj);
2919      } else {
2920        obj = self;
2921      }
2922
2923      args.unshift(self);
2924
2925      if ($.isFunction(obj.opts[name])) {
2926        rez = obj.opts[name].apply(obj, args);
2927      }
2928
2929      if (rez === false) {
2930        return rez;
2931      }
2932
2933      if (name === "afterClose" || !self.$refs) {
2934        $D.trigger(name + ".fb", args);
2935      } else {
2936        self.$refs.container.trigger(name + ".fb", args);
2937      }
2938    },
2939
2940    // Update infobar values, navigation button states and reveal caption
2941    // ==================================================================
2942
2943    updateControls: function () {
2944      var self = this,
2945        current = self.current,
2946        index = current.index,
2947        $container = self.$refs.container,
2948        $caption = self.$refs.caption,
2949        caption = current.opts.caption;
2950
2951      // Recalculate content dimensions
2952      current.$slide.trigger("refresh");
2953
2954      // Set caption
2955      if (caption && caption.length) {
2956        self.$caption = $caption;
2957
2958        $caption
2959          .children()
2960          .eq(0)
2961          .html(caption);
2962      } else {
2963        self.$caption = null;
2964      }
2965
2966      if (!self.hasHiddenControls && !self.isIdle) {
2967        self.showControls();
2968      }
2969
2970      // Update info and navigation elements
2971      $container.find("[data-fancybox-count]").html(self.group.length);
2972      $container.find("[data-fancybox-index]").html(index + 1);
2973
2974      $container.find("[data-fancybox-prev]").prop("disabled", !current.opts.loop && index <= 0);
2975      $container.find("[data-fancybox-next]").prop("disabled", !current.opts.loop && index >= self.group.length - 1);
2976
2977      if (current.type === "image") {
2978        // Re-enable buttons; update download button source
2979        $container
2980          .find("[data-fancybox-zoom]")
2981          .show()
2982          .end()
2983          .find("[data-fancybox-download]")
2984          .attr("href", current.opts.image.src || current.src)
2985          .show();
2986      } else if (current.opts.toolbar) {
2987        $container.find("[data-fancybox-download],[data-fancybox-zoom]").hide();
2988      }
2989
2990      // Make sure focus is not on disabled button/element
2991      if ($(document.activeElement).is(":hidden,[disabled]")) {
2992        self.$refs.container.trigger("focus");
2993      }
2994    },
2995
2996    // Hide toolbar and caption
2997    // ========================
2998
2999    hideControls: function (andCaption) {
3000      var self = this,
3001        arr = ["infobar", "toolbar", "nav"];
3002
3003      if (andCaption || !self.current.opts.preventCaptionOverlap) {
3004        arr.push("caption");
3005      }
3006
3007      this.$refs.container.removeClass(
3008        arr
3009        .map(function (i) {
3010          return "fancybox-show-" + i;
3011        })
3012        .join(" ")
3013      );
3014
3015      this.hasHiddenControls = true;
3016    },
3017
3018    showControls: function () {
3019      var self = this,
3020        opts = self.current ? self.current.opts : self.opts,
3021        $container = self.$refs.container;
3022
3023      self.hasHiddenControls = false;
3024      self.idleSecondsCounter = 0;
3025
3026      $container
3027        .toggleClass("fancybox-show-toolbar", !!(opts.toolbar && opts.buttons))
3028        .toggleClass("fancybox-show-infobar", !!(opts.infobar && self.group.length > 1))
3029        .toggleClass("fancybox-show-caption", !!self.$caption)
3030        .toggleClass("fancybox-show-nav", !!(opts.arrows && self.group.length > 1))
3031        .toggleClass("fancybox-is-modal", !!opts.modal);
3032    },
3033
3034    // Toggle toolbar and caption
3035    // ==========================
3036
3037    toggleControls: function () {
3038      if (this.hasHiddenControls) {
3039        this.showControls();
3040      } else {
3041        this.hideControls();
3042      }
3043    }
3044  });
3045
3046  $.fancybox = {
3047    version: "{fancybox-version}",
3048    defaults: defaults,
3049
3050    // Get current instance and execute a command.
3051    //
3052    // Examples of usage:
3053    //
3054    //   $instance = $.fancybox.getInstance();
3055    //   $.fancybox.getInstance().jumpTo( 1 );
3056    //   $.fancybox.getInstance( 'jumpTo', 1 );
3057    //   $.fancybox.getInstance( function() {
3058    //       console.info( this.currIndex );
3059    //   });
3060    // ======================================================
3061
3062    getInstance: function (command) {
3063      var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),
3064        args = Array.prototype.slice.call(arguments, 1);
3065
3066      if (instance instanceof FancyBox) {
3067        if ($.type(command) === "string") {
3068          instance[command].apply(instance, args);
3069        } else if ($.type(command) === "function") {
3070          command.apply(instance, args);
3071        }
3072
3073        return instance;
3074      }
3075
3076      return false;
3077    },
3078
3079    // Create new instance
3080    // ===================
3081
3082    open: function (items, opts, index) {
3083      return new FancyBox(items, opts, index);
3084    },
3085
3086    // Close current or all instances
3087    // ==============================
3088
3089    close: function (all) {
3090      var instance = this.getInstance();
3091
3092      if (instance) {
3093        instance.close();
3094
3095        // Try to find and close next instance
3096        if (all === true) {
3097          this.close(all);
3098        }
3099      }
3100    },
3101
3102    // Close all instances and unbind all events
3103    // =========================================
3104
3105    destroy: function () {
3106      this.close(true);
3107
3108      $D.add("body").off("click.fb-start", "**");
3109    },
3110
3111    // Try to detect mobile devices
3112    // ============================
3113
3114    isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
3115
3116    // Detect if 'translate3d' support is available
3117    // ============================================
3118
3119    use3d: (function () {
3120      var div = document.createElement("div");
3121
3122      return (
3123        window.getComputedStyle &&
3124        window.getComputedStyle(div) &&
3125        window.getComputedStyle(div).getPropertyValue("transform") &&
3126        !(document.documentMode && document.documentMode < 11)
3127      );
3128    })(),
3129
3130    // Helper function to get current visual state of an element
3131    // returns array[ top, left, horizontal-scale, vertical-scale, opacity ]
3132    // =====================================================================
3133
3134    getTranslate: function ($el) {
3135      var domRect;
3136
3137      if (!$el || !$el.length) {
3138        return false;
3139      }
3140
3141      domRect = $el[0].getBoundingClientRect();
3142
3143      return {
3144        top: domRect.top || 0,
3145        left: domRect.left || 0,
3146        width: domRect.width,
3147        height: domRect.height,
3148        opacity: parseFloat($el.css("opacity"))
3149      };
3150    },
3151
3152    // Shortcut for setting "translate3d" properties for element
3153    // Can set be used to set opacity, too
3154    // ========================================================
3155
3156    setTranslate: function ($el, props) {
3157      var str = "",
3158        css = {};
3159
3160      if (!$el || !props) {
3161        return;
3162      }
3163
3164      if (props.left !== undefined || props.top !== undefined) {
3165        str =
3166          (props.left === undefined ? $el.position().left : props.left) +
3167          "px, " +
3168          (props.top === undefined ? $el.position().top : props.top) +
3169          "px";
3170
3171        if (this.use3d) {
3172          str = "translate3d(" + str + ", 0px)";
3173        } else {
3174          str = "translate(" + str + ")";
3175        }
3176      }
3177
3178      if (props.scaleX !== undefined && props.scaleY !== undefined) {
3179        str += " scale(" + props.scaleX + ", " + props.scaleY + ")";
3180      } else if (props.scaleX !== undefined) {
3181        str += " scaleX(" + props.scaleX + ")";
3182      }
3183
3184      if (str.length) {
3185        css.transform = str;
3186      }
3187
3188      if (props.opacity !== undefined) {
3189        css.opacity = props.opacity;
3190      }
3191
3192      if (props.width !== undefined) {
3193        css.width = props.width;
3194      }
3195
3196      if (props.height !== undefined) {
3197        css.height = props.height;
3198      }
3199
3200      return $el.css(css);
3201    },
3202
3203    // Simple CSS transition handler
3204    // =============================
3205
3206    animate: function ($el, to, duration, callback, leaveAnimationName) {
3207      var self = this,
3208        from;
3209
3210      if ($.isFunction(duration)) {
3211        callback = duration;
3212        duration = null;
3213      }
3214
3215      self.stop($el);
3216
3217      from = self.getTranslate($el);
3218
3219      $el.on(transitionEnd, function (e) {
3220        // Skip events from child elements and z-index change
3221        if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == "z-index")) {
3222          return;
3223        }
3224
3225        self.stop($el);
3226
3227        if ($.isNumeric(duration)) {
3228          $el.css("transition-duration", "");
3229        }
3230
3231        if ($.isPlainObject(to)) {
3232          if (to.scaleX !== undefined && to.scaleY !== undefined) {
3233            self.setTranslate($el, {
3234              top: to.top,
3235              left: to.left,
3236              width: from.width * to.scaleX,
3237              height: from.height * to.scaleY,
3238              scaleX: 1,
3239              scaleY: 1
3240            });
3241          }
3242        } else if (leaveAnimationName !== true) {
3243          $el.removeClass(to);
3244        }
3245
3246        if ($.isFunction(callback)) {
3247          callback(e);
3248        }
3249      });
3250
3251      if ($.isNumeric(duration)) {
3252        $el.css("transition-duration", duration + "ms");
3253      }
3254
3255      // Start animation by changing CSS properties or class name
3256      if ($.isPlainObject(to)) {
3257        if (to.scaleX !== undefined && to.scaleY !== undefined) {
3258          delete to.width;
3259          delete to.height;
3260
3261          if ($el.parent().hasClass("fancybox-slide--image")) {
3262            $el.parent().addClass("fancybox-is-scaling");
3263          }
3264        }
3265
3266        $.fancybox.setTranslate($el, to);
3267      } else {
3268        $el.addClass(to);
3269      }
3270
3271      // Make sure that `transitionend` callback gets fired
3272      $el.data(
3273        "timer",
3274        setTimeout(function () {
3275          $el.trigger(transitionEnd);
3276        }, duration + 33)
3277      );
3278    },
3279
3280    stop: function ($el, callCallback) {
3281      if ($el && $el.length) {
3282        clearTimeout($el.data("timer"));
3283
3284        if (callCallback) {
3285          $el.trigger(transitionEnd);
3286        }
3287
3288        $el.off(transitionEnd).css("transition-duration", "");
3289
3290        $el.parent().removeClass("fancybox-is-scaling");
3291      }
3292    }
3293  };
3294
3295  // Default click handler for "fancyboxed" links
3296  // ============================================
3297
3298  function _run(e, opts) {
3299    var items = [],
3300      index = 0,
3301      $target,
3302      value,
3303      instance;
3304
3305    // Avoid opening multiple times
3306    if (e && e.isDefaultPrevented()) {
3307      return;
3308    }
3309
3310    e.preventDefault();
3311
3312    opts = opts || {};
3313
3314    if (e && e.data) {
3315      opts = mergeOpts(e.data.options, opts);
3316    }
3317
3318    $target = opts.$target || $(e.currentTarget).trigger("blur");
3319    instance = $.fancybox.getInstance();
3320
3321    if (instance && instance.$trigger && instance.$trigger.is($target)) {
3322      return;
3323    }
3324
3325    if (opts.selector) {
3326      items = $(opts.selector);
3327    } else {
3328      // Get all related items and find index for clicked one
3329      value = $target.attr("data-fancybox") || "";
3330
3331      if (value) {
3332        items = e.data ? e.data.items : [];
3333        items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]');
3334      } else {
3335        items = [$target];
3336      }
3337    }
3338
3339    index = $(items).index($target);
3340
3341    // Sometimes current item can not be found
3342    if (index < 0) {
3343      index = 0;
3344    }
3345
3346    instance = $.fancybox.open(items, opts, index);
3347
3348    // Save last active element
3349    instance.$trigger = $target;
3350  }
3351
3352  // Create a jQuery plugin
3353  // ======================
3354
3355  $.fn.fancybox = function (options) {
3356    var selector;
3357
3358    options = options || {};
3359    selector = options.selector || false;
3360
3361    if (selector) {
3362      // Use body element instead of document so it executes first
3363      $("body")
3364        .off("click.fb-start", selector)
3365        .on("click.fb-start", selector, {
3366          options: options
3367        }, _run);
3368    } else {
3369      this.off("click.fb-start").on(
3370        "click.fb-start", {
3371          items: this,
3372          options: options
3373        },
3374        _run
3375      );
3376    }
3377
3378    return this;
3379  };
3380
3381  // Self initializing plugin for all elements having `data-fancybox` attribute
3382  // ==========================================================================
3383
3384  $D.on("click.fb-start", "[data-fancybox]", _run);
3385
3386  // Enable "trigger elements"
3387  // =========================
3388
3389  $D.on("click.fb-start", "[data-fancybox-trigger]", function (e) {
3390    $('[data-fancybox="' + $(this).attr("data-fancybox-trigger") + '"]')
3391      .eq($(this).attr("data-fancybox-index") || 0)
3392      .trigger("click.fb-start", {
3393        $trigger: $(this)
3394      });
3395  });
3396
3397  // Track focus event for better accessibility styling
3398  // ==================================================
3399  (function () {
3400    var buttonStr = ".fancybox-button",
3401      focusStr = "fancybox-focus",
3402      $pressed = null;
3403
3404    $D.on("mousedown mouseup focus blur", buttonStr, function (e) {
3405      switch (e.type) {
3406        case "mousedown":
3407          $pressed = $(this);
3408          break;
3409        case "mouseup":
3410          $pressed = null;
3411          break;
3412        case "focusin":
3413          $(buttonStr).removeClass(focusStr);
3414
3415          if (!$(this).is($pressed) && !$(this).is("[disabled]")) {
3416            $(this).addClass(focusStr);
3417          }
3418          break;
3419        case "focusout":
3420          $(buttonStr).removeClass(focusStr);
3421          break;
3422      }
3423    });
3424  })();
3425})(window, document, jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.