PageSourceSearch

https://www.nik.gov.pl/p/fancybox3/jquery.fancybox.js

js nik.gov.pl collected 2026-10-02 03:09:09 UTC 141,213 bytes, 4,956 lines download raw bytes

1// ==================================================
2// fancyBox v3.1.27
3//
4// Licensed GPLv3 for open source use
5// or fancyBox Commercial License for commercial use
6//
7// http://fancyapps.com/fancybox/
8// Copyright 2017 fancyApps
9//
10// ==================================================
11;(function (window, document, $, undefined) {
12    'use strict';
13
14    // If there's no jQuery, fancyBox can't work
15    // =========================================
16
17    if ( !$ ) {
18        return;
19    }
20
21    // Check if fancyBox is already initialized
22    // ========================================
23
24    if ( $.fn.fancybox ) {
25
26        $.error('fancyBox already initialized');
27
28        return;
29    }
30
31    // Private default settings
32    // ========================
33
34    var defaults = {
35
36        // Enable infinite gallery navigation
37        loop : false,
38
39        // Space around image, ignored if zoomed-in or viewport width is smaller than 800px
40        margin : [44, 0],
41
42        // Horizontal space between slides
43        gutter : 50,
44
45        // Enable keyboard navigation
46        keyboard : true,
47
48        // Should display navigation arrows at the screen edges
49        arrows : true,
50
51        // Should display infobar (counter and arrows at the top)
52        infobar : false,
53
54        // Should display toolbar (buttons at the top)
55        toolbar : true,
56
57        // What buttons should appear in the top right corner.
58        // Buttons will be created using templates from `btnTpl` option
59        // and they will be placed into toolbar (class="fancybox-toolbar"` element)
60        buttons : [
61            'slideShow',
62            'fullScreen',
63            'thumbs',
64            'close'
65        ],
66
67        // Detect "idle" time in seconds
68        idleTime : 4,
69
70        // Should display buttons at top right corner of the content
71        // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax'
72        // Use template from `btnTpl.smallBtn` for customization
73        smallBtn : 'auto',
74
75        // Disable right-click and use simple image protection for images
76        protect : false,
77
78        // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc
79        modal : false,
80
81        image : {
82
83            // Wait for images to load before displaying
84            // Requires predefined image dimensions
85            // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found
86            preload : "auto",
87
88        },
89
90        ajax : {
91
92            // Object containing settings for ajax request
93            settings : {
94
95                // This helps to indicate that request comes from the modal
96                // Feel free to change naming
97                data : {
98                    fancybox : true
99                }
100            }
101
102        },
103
104        iframe : {
105
106            // Iframe template
107            tpl : '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen allowtransparency="true" src=""></iframe>',
108
109            // Preload iframe before displaying it
110            // This allows to calculate iframe content width and height
111            // (note: Due to "Same Origin Policy", you can't get cross domain data).
112            preload : true,
113
114            // Custom CSS styling for iframe wrapping element
115            // You can use this to set custom iframe dimensions
116            css : {},
117
118            // Iframe tag attributes
119            attr : {
120                scrolling : 'auto'
121            }
122
123        },
124
125        // Open/close animation type
126        // Possible values:
127        //   false            - disable
128        //   "zoom"           - zoom images from/to thumbnail
129        //   "fade"
130        //   "zoom-in-out"
131        //
132        animationEffect : "zoom",
133
134        // Duration in ms for open/close animation
135        animationDuration : 366,
136
137        // Should image change opacity while zooming
138        // If opacity is 'auto', then opacity will be changed if image and thumbnail have different aspect ratios
139        zoomOpacity : 'auto',
140
141        // Transition effect between slides
142        //
143        // Possible values:
144        //   false            - disable
145        //   "fade'
146        //   "slide'
147        //   "circular'
148        //   "tube'
149        //   "zoom-in-out'
150        //   "rotate'
151        //
152        transitionEffect : "fade",
153
154        // Duration in ms for transition animation
155        transitionDuration : 366,
156
157        // Custom CSS class for slide element
158        slideClass : '',
159
160        // Custom CSS class for layout
161        baseClass : '',
162
163        // Base template for layout
164        baseTpl	:
165            '<div class="fancybox-container" role="dialog" tabindex="-1">' +
166                '<div class="fancybox-bg"></div>' +
167                '<div class="fancybox-inner">' +
168                    '<div class="fancybox-infobar">' +
169                        '<button data-fancybox-prev title="{{PREV}}" class="fancybox-button fancybox-button--left"></button>' +
170                        '<div class="fancybox-infobar__body">' +
171                            '<span data-fancybox-index></span>&nbsp;/&nbsp;<span data-fancybox-count></span>' +
172                        '</div>' +
173                        '<button data-fancybox-next title="{{NEXT}}" class="fancybox-button fancybox-button--right"></button>' +
174                    '</div>' +
175                    '<div class="fancybox-toolbar">' +
176                        '{{BUTTONS}}' +
177                    '</div>' +
178                    '<div class="fancybox-navigation">' +
179                        '<button data-fancybox-prev title="{{PREV}}" class="fancybox-arrow fancybox-arrow--left" />' +
180                        '<button data-fancybox-next title="{{NEXT}}" class="fancybox-arrow fancybox-arrow--right" />' +
181                    '</div>' +
182                    '<div class="fancybox-stage"></div>' +
183                    '<div class="fancybox-caption-wrap">' +
184                        '<div class="fancybox-caption"></div>' +
185                    '</div>' +
186                '</div>' +
187            '</div>',
188
189        // Loading indicator template
190        spinnerTpl : '<div class="fancybox-loading"></div>',
191
192        // Error message template
193        errorTpl : '<div class="fancybox-error"><p>{{ERROR}}<p></div>',
194
195        btnTpl : {
196            slideShow  : '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}"></button>',
197            fullScreen : '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fullscreen" title="{{FULL_SCREEN}}"></button>',
198            thumbs     : '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}"></button>',
199            close      : '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}"></button>',
200
201            // This small close button will be appended to your html/inline/ajax content by default,
202            // if "smallBtn" option is not set to false
203            smallBtn   : '<button data-fancybox-close class="fancybox-close-small" title="{{CLOSE}}"></button>'
204        },
205
206        // Container is injected into this element
207        parentEl : 'body',
208
209
210        // Focus handling
211        // ==============
212
213        // Try to focus on the first focusable element after opening
214        autoFocus : true,
215
216        // Put focus back to active element after closing
217        backFocus : true,
218
219        // Do not let user to focus on element outside modal content
220        trapFocus : true,
221
222
223        // Module specific options
224        // =======================
225
226        fullScreen : {
227            autoStart : false,
228        },
229
230        // Set `touch: false` to disable dragging/swiping
231        touch : {
232            vertical : true,  // Allow to drag content vertically
233            momentum : true   // Continue movement after releasing mouse/touch when panning
234        },
235
236        // Hash value when initializing manually,
237        // set `false` to disable hash change
238        hash : null,
239
240        // Customize or add new media types
241        // Example:
242        /*
243        media : {
244            youtube : {
245                params : {
246                    autoplay : 0
247                }
248            }
249        }
250        */
251        media : {},
252
253        slideShow : {
254            autoStart : false,
255            speed     : 4000
256        },
257
258        thumbs : {
259            autoStart   : false,   // Display thumbnails on opening
260            hideOnClose : true     // Hide thumbnail grid when closing animation starts
261        },
262
263        // Callbacks
264        //==========
265
266        // See Documentation/API/Events for more information
267        // Example:
268        /*
269            afterShow: function( instance, current ) {
270                 console.info( 'Clicked element:' );
271                 console.info( current.opts.$orig );
272            }
273        */
274
275        onInit       : $.noop,  // When instance has been initialized
276
277        beforeLoad   : $.noop,  // Before the content of a slide is being loaded
278        afterLoad    : $.noop,  // When the content of a slide is done loading
279
280        beforeShow   : $.noop,  // Before open animation starts
281        afterShow    : $.noop,  // When content is done loading and animating
282
283        beforeClose  : $.noop,  // Before the instance attempts to close. Return false to cancel the close.
284        afterClose   : $.noop,  // After instance has been closed
285
286        onActivate   : $.noop,  // When instance is brought to front
287        onDeactivate : $.noop,  // When other instance has been activated
288
289
290        // Interaction
291        // ===========
292
293        // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
294        // each option can be string or method that returns value.
295        //
296        // Possible values:
297        //   "close"           - close instance
298        //   "next"            - move to next gallery item
299        //   "nextOrClose"     - move to next gallery item or close if gallery has only one item
300        //   "toggleControls"  - show/hide controls
301        //   "zoom"            - zoom image (if loaded)
302        //   false             - do nothing
303
304        // Clicked on the content
305        clickContent : function( current, event ) {
306            return current.type === 'image' ? 'zoom' : false;
307        },
308
309        // Clicked on the slide
310        clickSlide : 'close',
311
312        // Clicked on the background (backdrop) element
313        clickOutside : 'close',
314
315        // Same as previous two, but for double click
316        dblclickContent : false,
317        dblclickSlide   : false,
318        dblclickOutside : false,
319
320
321        // Custom options when mobile device is detected
322        // =============================================
323
324        mobile : {
325            clickContent : function( current, event ) {
326                return current.type === 'image' ? 'toggleControls' : false;
327            },
328            clickSlide : function( current, event ) {
329                return current.type === 'image' ? 'toggleControls' : 'close';
330            },
331            dblclickContent : function( current, event ) {
332                return current.type === 'image' ? 'zoom' : false;
333            },
334            dblclickSlide : function( current, event ) {
335                return current.type === 'image' ? 'zoom' : false;
336            }
337        },
338
339
340        // Internationalization
341        // ============
342
343        lang : 'en',
344        i18n : {
345            'en' : {
346                CLOSE       : 'Close',
347                NEXT        : 'Next',
348                PREV        : 'Previous',
349                ERROR       : 'The requested content cannot be loaded. <br/> Please try again later.',
350                PLAY_START  : 'Start slideshow',
351                PLAY_STOP   : 'Pause slideshow',
352                FULL_SCREEN : 'Full screen',
353                THUMBS      : 'Thumbnails'
354            },
355            'de' : {
356                CLOSE       : 'Schliessen',
357                NEXT        : 'Weiter',
358                PREV        : 'Zurück',
359                ERROR       : 'Die angeforderten Daten konnten nicht geladen werden. <br/>
359 Bitte versuchen Sie es später nochmal.',
360                PLAY_START  : 'Diaschau starten',
361                PLAY_STOP   : 'Diaschau beenden',
362                FULL_SCREEN : 'Vollbild',
363                THUMBS      : 'Vorschaubilder'
364            }
365        }
366
367    };
368
369    // Few useful variables and methods
370    // ================================
371
372    var $W = $(window);
373    var $D = $(document);
374
375    var called = 0;
376
377
378    // Check if an object is a jQuery object and not a native JavaScript object
379    // ========================================================================
380
381    var isQuery = function ( obj ) {
382        return obj && obj.hasOwnProperty && obj instanceof $;
383    };
384
385
386    // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame"
387    // ===============================================================================
388
389    var requestAFrame = (function () {
390        return window.requestAnimationFrame ||
391                window.webkitRequestAnimationFrame ||
392                window.mozRequestAnimationFrame ||
393                window.oRequestAnimationFrame ||
394                // if all else fails, use setTimeout
395                function (callback) {
396                    return window.setTimeout(callback, 1000 / 60);
397                };
398    })();
399
400
401    // Detect the supported transition-end event property name
402    // =======================================================
403
404    var transitionEnd = (function () {
405        var t, el = document.createElement("fakeelement");
406
407        var transitions = {
408            "transition"      : "transitionend",
409            "OTransition"     : "oTransitionEnd",
410            "MozTransition"   : "transitionend",
411            "WebkitTransition": "webkitTransitionEnd"
412        };
413
414        for (t in transitions) {
415            if (el.style[t] !== undefined){
416                return transitions[t];
417            }
418        }
419    })();
420
421
422    // Force redraw on an element.
423    // This helps in cases where the browser doesn't redraw an updated element properly.
424    // =================================================================================
425
426    var forceRedraw = function( $el ) {
427        return ( $el && $el.length && $el[0].offsetHeight );
428    };
429
430
431    // Class definition
432    // ================
433
434    var FancyBox = function( content, opts, index ) {
435        var self = this;
436
437        self.opts  = $.extend( true, { index : index }, defaults, opts || {} );
438
439        // Exclude buttons option from deep merging
440        if ( opts && $.isArray( opts.buttons ) ) {
441            self.opts.buttons = opts.buttons;
442        }
443
444        self.id    = self.opts.id || ++called;
445        self.group = [];
446
447        self.currIndex = parseInt( self.opts.index, 10 ) || 0;
448        self.prevIndex = null;
449
450        self.prevPos = null;
451        self.currPos = 0;
452
453        self.firstRun = null;
454
455        // Create group elements from original item collection
456        self.createGroup( content );
457
458        if ( !self.group.length ) {
459            return;
460        }
461
462        // Save last active element and current scroll position
463        self.$lastFocus = $(document.activeElement).blur();
464
465        // Collection of gallery objects
466        self.slides = {};
467
468        self.init( content );
469
470    };
471
472    $.extend(FancyBox.prototype, {
473
474        // Create DOM structure
475        // ====================
476
477        init : function() {
478            var self = this;
479
480            var testWidth, $container, buttonStr;
481
482            var firstItemOpts = self.group[ self.currIndex ].opts;
483
484            self.scrollTop  = $D.scrollTop();
485            self.scrollLeft = $D.scrollLeft();
486
487
488            // Hide scrollbars
489            // ===============
490
491            if ( !$.fancybox.getInstance() && !$.fancybox.isMobile && $( 'body' ).css('overflow') !== 'hidden' ) {
492                testWidth = $( 'body' ).width();
493
494                $( 'html' ).addClass( 'fancybox-enabled' );
495
496                // Compare body width after applying "overflow: hidden"
497                testWidth = $( 'body' ).width() - testWidth;
498
499                // If width has changed - compensate missing scrollbars by adding right margin
500                if ( testWidth > 1 ) {
501                    $( 'head' ).append( '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar, .fancybox-enabled body { margin-right: ' + testWidth + 'px; }</style>' );
502                }
503            }
504
505
506            // Build html markup and set references
507            // ====================================
508
509            // Build html code for buttons and insert into main template
510            buttonStr = '';
511
512            $.each( firstItemOpts.buttons, function( index, value ) {
513                buttonStr += ( firstItemOpts.btnTpl[ value ] || '' );
514            });
515
516            // Create markup from base template, it will be initially hidden to
517            // avoid unnecessary work like painting while initializing is not complete
518            $container = $( self.translate( self, firstItemOpts.baseTpl.replace( '\{\{BUTTONS\}\}', buttonStr ) ) )
519                .addClass( 'fancybox-is-hidden' )
520                .attr('id', 'fancybox-container-' + self.id)
521                .addClass( firstItemOpts.baseClass )
522                .data( 'FancyBox', self )
523                .prependTo( firstItemOpts.parentEl );
524
525            // Create object holding references to jQuery wrapped nodes
526            self.$refs = {
527                container : $container
528            };
529
530            [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption' ].forEach(function(item) {
531                self.$refs[ item ] = $container.find( '.fancybox-' + item );
532            });
533
534            // Check for redundant elements
535            if ( !firstItemOpts.arrows || self.group.length < 2 ) {
536                $container.find('.fancybox-navigation').remove();
537            }
538
539            if ( !firstItemOpts.infobar ) {
540                self.$refs.infobar.remove();
541            }
542
543            if ( !firstItemOpts.toolbar ) {
544                self.$refs.toolbar.remove();
545            }
546
547            self.trigger( 'onInit' );
548
549            // Bring to front and enable events
550            self.activate();
551
552            // Build slides, load and reveal content
553            self.jumpTo( self.currIndex );
554        },
555
556
557        // Simple i18n support - replaces object keys found in template
558        // with corresponding values
559        // ============================================================
560
561        translate : function( obj, str ) {
562            var arr = obj.opts.i18n[ obj.opts.lang ];
563
564            return str.replace(/\{\{(\w+)\}\}/g, function(match, n) {
565                var value = arr[n];
566
567                if ( value === undefined ) {
568                    return match;
569                }
570
571                return value;
572            });
573        },
574
575        // Create array of gally item objects
576        // Check if each object has valid type and content
577        // ===============================================
578
579        createGroup : function ( content ) {
580            var self  = this;
581            var items = $.makeArray( content );
582
583            $.each(items, function( i, item ) {
584                var obj  = {},
585                    opts = {},
586                    data = [],
587                    $item,
588                    type,
589                    src,
590                    srcParts;
591
592                // Step 1 - Make sure we have an object
593                // ====================================
594
595                if ( $.isPlainObject( item ) ) {
596
597                    // We probably have manual usage here, something like
598                    // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] )
599
600                    obj  = item;
601                    opts = item.opts || item;
602
603                } else if ( $.type( item ) === 'object' && $( item ).length ) {
604
605                    // Here we propbably have jQuery collection returned by some selector
606
607                    $item = $( item );
608                    data  = $item.data();
609
610                    opts = 'options' in data ? data.options : {};
611                    opts = $.type( opts ) === 'object' ? opts : {};
612
613                    obj.src  = 'src' in data ? data.src : ( opts.src || $item.attr( 'href' ) );
614
615                    [ 'width', 'height', 'thumb', 'type', 'filter' ].forEach(function(item) {
616                        if ( item in data ) {
617                            opts[ item ] = data[ item ];
618                        }
619                    });
620
621                    if ( 'srcset' in data ) {
622                        opts.image = { srcset : data.srcset };
623                    }
624
625                    opts.$orig = $item;
626
627                    if ( !obj.type && !obj.src ) {
628                        obj.type = 'inline';
629                        obj.src  = item;
630                    }
631
632                } else {
633
634                    // Assume we have a simple html code, for example:
635                    // $.fancybox.open( '<div><h1>Hi!</h1></div>' );
636
637                    obj = {
638                        type : 'html',
639                        src  : item + ''
640                    };
641
642                }
643
644                // Each gallery object has full collection of options
645                obj.opts = $.extend( true, {}, self.opts, opts );
646
647                if ( $.fancybox.isMobile ) {
648                    obj.opts = $.extend( true, {}, obj.opts, obj.opts.mobile );
649                }
650
651
652                // Step 2 - Make sure we have content type, if not - try to guess
653                // ==============================================================
654
655                type = obj.type || obj.opts.type;
656                src  = obj.src || '';
657
658                if ( !type && src ) {
659                    if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) {
660                        type = 'image';
661
662                    } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) {
663                        type = 'pdf';
664
665                    } else if ( src.charAt(0) === '#' ) {
666                        type = 'inline';
667                    }
668                }
669
670                obj.type = type;
671
672
673                // Step 3 - Some adjustments
674                // =========================
675
676                obj.index = self.group.length;
677
678                // Check if $orig and $thumb objects exist
679                if ( obj.opts.$orig && !obj.opts.$orig.length ) {
680                    delete obj.opts.$orig;
681                }
682
683                if ( !obj.opts.$thumb && obj.opts.$orig ) {
684                    obj.opts.$thumb = obj.opts.$orig.find( 'img:first' );
685                }
686
687                if ( obj.opts.$thumb && !obj.opts.$thumb.length ) {
688                    delete obj.opts.$thumb;
689                }
690
691                // Caption is a "special" option, it can be passed as a method
692                if ( $.type( obj.opts.caption ) === 'function' ) {
693                    obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] );
694
695                } else if ( 'caption' in data ) {
696                    obj.opts.caption = data.caption;
697                }
698
699                // Make sure we have caption as a string
700                obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + '';
701
702                // Check if url contains "filter" used to filter the content
703                // Example: "ajax.html #something"
704                if ( type === 'ajax' ) {
705                    srcParts = src.split(/\s+/, 2);
706
707                    if ( srcParts.length > 1 ) {
708                        obj.src = srcParts.shift();
709
710                        obj.opts.filter = srcParts.shift();
711                    }
712                }
713
714                if ( obj.opts.smallBtn == 'auto' ) {
715
716                    if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) {
717                        obj.opts.toolbar  = false;
718                        obj.opts.smallBtn = true;
719
720                    } else {
721                        obj.opts.smallBtn = false;
722                    }
723
724                }
725
726                // If the type is "pdf", then simply load file into iframe
727                if ( type === 'pdf' ) {
728                    obj.type = 'iframe';
729
730                    obj.opts.iframe.preload = false;
731                }
732
733                // Hide all buttons and disable interactivity for modal items
734                if ( obj.opts.modal ) {
735
736                    obj.opts = $.extend(true, obj.opts, {
737                        // Remove buttons
738                        infobar : 0,
739                        toolbar : 0,
740
741                        smallBtn : 0,
742
743                        // Disable keyboard navigation
744                        keyboard : 0,
745
746                        // Disable some modules
747                        slideShow  : 0,
748                        fullScreen : 0,
749                        thumbs     : 0,
750                        touch      : 0,
751
752                        // Disable click event handlers
753                        clickContent    : false,
754                        clickSlide      : false,
755                        clickOutside    : false,
756                        dblclickContent : false,
757                        dblclickSlide   : false,
758                        dblclickOutside : false
759                    });
760
761                }
762
763                // Step 4 - Add processed object to group
764                // ======================================
765
766                self.group.push( obj );
767
768            });
769
770        },
771
772
773        // Attach an event handler functions for:
774        //   - navigation buttons
775        //   - browser scrolling, resizing;
776        //   - focusing
777        //   - keyboard
778        //   - detect idle
779        // ======================================
780
781        addEvents : function() {
782            var self = this;
783
784            self.removeEvents();
785
786            // Make navigation elements clickable
787            self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) {
788                e.stopPropagation();
789                e.preventDefault();
790
791                self.close( e );
792
793            }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) {
794                e.stopPropagation();
795                e.preventDefault();
796
797                self.previous();
798
799            }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) {
800                e.stopPropagation();
801                e.preventDefault();
802
803                self.next();
804
805            });
806
807
808            // Handle page scrolling and browser resizing
809            $W.on('orientationchange.fb resize.fb', function(e) {
810
811                if ( e && e.originalEvent && e.originalEvent.type === "resize" ) {
812
813                    requestAFrame(function() {
814                        self.update();
815                    });
816
817                } else {
818
819                    self.$refs.stage.hide();
820
821                    setTimeout(function() {
822                        self.$refs.stage.show();
823
824                        self.update();
825                    }, 500);
826
827                }
828
829            });
830
831            // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal
832            // (a.k.a. "escaping the modal")
833            $D.on('focusin.fb', function(e) {
834                var instance = $.fancybox ? $.fancybox.getInstance() : null;
835
836                if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) {
837                    return;
838                }
839
840                if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) {
841                    e.stopPropagation();
842
843                    instance.focus();
844
845                    // Sometimes page gets scrolled, set it back
846                    $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft );
847                }
848            });
849
850
851            // Enable keyboard navigation
852            $D.on('keydown.fb', function (e) {
853                var current = self.current,
854                    keycode = e.keyCode || e.which;
855
856                if ( !current || !current.opts.keyboard ) {
857                    return;
858                }
859
860                if ( $(e.target).is('input') || $(e.target).is('textarea') ) {
861                    return;
862                }
863
864                // Backspace and Esc keys
865                if ( keycode === 8 || keycode === 27 ) {
866                    e.preventDefault();
867
868                    self.close( e );
869
870                    return;
871                }
872
873                // Left arrow and Up arrow
874                if ( keycode === 37 || keycode === 38 ) {
875                    e.preventDefault();
876
877                    self.previous();
878
879                    return;
880                }
881
882                // Righ arrow and Down arrow
883                if ( keycode === 39 || keycode === 40 ) {
884                    e.preventDefault();
885
886                    self.next();
887
888                    return;
889                }
890
891                self.trigger('afterKeydown', e, keycode);
892            });
893
894
895            // Hide controls after some inactivity period
896            if ( self.group[ self.currIndex ].opts.idleTime ) {
897                self.idleSecondsCounter = 0;
898
899                $D.on('mousemove.fb-idle mouseenter.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function() {
900                    self.idleSecondsCounter = 0;
901
902                    if ( self.isIdle ) {
903                        self.showControls();
904                    }
905
906                    self.isIdle = false;
907                });
908
909                self.idleInterval = window.setInterval(function() {
910
911                    self.idleSecondsCounter++;
912
913                    if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime ) {
914                        self.isIdle = true;
915                        self.idleSecondsCounter = 0;
916
917                        self.hideControls();
918                    }
919
920                }, 1000);
921            }
922
923        },
924
925
926        // Remove events added by the core
927        // ===============================
928
929        removeEvents : function () {
930            var self = this;
931
932            $W.off( 'orientationchange.fb resize.fb' );
933            $D.off( 'focusin.fb keydown.fb .fb-idle' );
934
935            this.$refs.container.off( '.fb-close .fb-prev .fb-next' );
936
937            if ( self.idleInterval ) {
938                window.clearInterval( self.idleInterval );
939
940                self.idleInterval = null;
941            }
942        },
943
944
945        // Change to previous gallery item
946        // ===============================
947
948        previous : function( duration ) {
949            return this.jumpTo( this.currPos - 1, duration );
950        },
951
952
953        // Change to next gallery item
954        // ===========================
955
956        next : function( duration ) {
957            return this.jumpTo( this.currPos + 1, duration );
958        },
959
960
961        // Switch to selected gallery item
962        // ===============================
963
964        jumpTo : function ( pos, duration, slide ) {
965            var self = this,
966                firstRun,
967                loop,
968                current,
969                previous,
970                canvasWidth,
971                currentPos,
972                transitionProps;
973
974            var groupLen = self.group.length;
975
976            if ( self.isSliding || self.isClosing || ( self.isAnimating && self.firstRun ) ) {
977                return;
978            }
979
980            pos  = parseInt( pos, 10 );
981            loop = self.current ? self.current.opts.loop : self.opts.loop;
982
983            if ( !loop && ( pos < 0 || pos >= groupLen ) ) {
984                return false;
985            }
986
987            firstRun = self.firstRun = ( self.firstRun === null );
988
989            if ( groupLen < 2 && !firstRun && !!self.isSliding ) {
990                return;
991            }
992
993            previous = self.current;
994
995            self.prevIndex = self.currIndex;
996            self.prevPos   = self.currPos;
997
998            // Create slides
999            current = self.createSlide( pos );
1000
1001            if ( groupLen > 1 ) {
1002                if ( loop || current.index > 0 ) {
1003                    self.createSlide( pos - 1 );
1004                }
1005
1006                if ( loop || current.index < groupLen - 1 ) {
1007                    self.createSlide( pos + 1 );
1008                }
1009            }
1010
1011            self.current   = current;
1012            self.currIndex = current.index;
1013            self.currPos   = current.pos;
1014
1015            self.trigger( 'beforeShow', firstRun );
1016
1017            self.updateControls();
1018
1019            currentPos = $.fancybox.getTranslate( current.$slide );
1020
1021            current.isMoved        = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' );
1022            current.forcedDuration = undefined;
1023
1024            if ( $.isNumeric( duration ) ) {
1025                current.forcedDuration = duration;
1026            } else {
1027                duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ];
1028            }
1029
1030            duration = parseInt( duration, 10 );
1031
1032            // Fresh start - reveal container, current slide and start loading content
1033            if ( firstRun ) {
1034
1035                if ( current.opts.animationEffect && duration ) {
1036                    self.$refs.container.css( 'transition-duration', duration + 'ms' );
1037                }
1038
1039                self.$refs.container.removeClass( 'fancybox-is-hidden' );
1040
1041                forceRedraw( self.$refs.container );
1042
1043                self.$refs.container.addClass( 'fancybox-is-open' );
1044
1045                // Make first slide visible (to display loading icon, if needed)
1046                current.$slide.addClass( 'fancybox-slide--current' );
1047
1048                self.loadSlide( current );
1049
1050                self.preload();
1051
1052                return;
1053            }
1054
1055            // Clean up
1056            $.each(self.slides, function( index, slide ) {
1057                $.fancybox.stop( slide.$slide );
1058            });
1059
1060            // Make current that slide is visible even if content is still loading
1061            current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' );
1062
1063            // If slides have been dragged, animate them to correct position
1064            if ( current.isMoved ) {
1065                canvasWidth = Math.round( current.$slide.width() );
1066
1067                $.each(self.slides, function( index, slide ) {
1068                    var pos = slide.pos - current.pos;
1069
1070                    $.fancybox.animate( slide.$slide, {
1071                        top  : 0,
1072                        left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter )
1073                    }, duration, function() {
1074
1075                        slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' );
1076
1077                        if ( slide.pos === self.currPos ) {
1078                            current.isMoved = false;
1079
1080                            self.complete();
1081                        }
1082                    });
1083                });
1084
1085            } else {
1086                self.$refs.stage.children().removeAttr( 'style' );
1087            }
1088
1089            // Start transition that reveals current content
1090            // or wait when it will be loaded
1091
1092            if ( current.isLoaded ) {
1093                self.revealContent( current );
1094
1095            } else {
1096                self.loadSlide( current );
1097            }
1098
1099            self.preload();
1100
1101            if ( previous.pos === current.pos ) {
1102                return;
1103            }
1104
1105            // Handle previous slide
1106            // =====================
1107
1108            transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' );
1109
1110            previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' );
1111
1112            previous.isComplete = false;
1113
1114            if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) {
1115                return;
1116            }
1117
1118            if ( current.isMoved ) {
1119                previous.$slide.addClass( transitionProps );
1120
1121            } else {
1122
1123                transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect;
1124
1125                $.fancybox.animate( previous.$slide, transitionProps, duration, function() {
1126                    previous.$slide.removeClass( transitionProps ).removeAttr( 'style' );
1127                });
1128
1129            }
1130
1131        },
1132
1133
1134        // Create new "slide" element
1135        // These are gallery items  that are actually added to DOM
1136        // =======================================================
1137
1138        createSlide : function( pos ) {
1139
1140            var self = this;
1141            var $slide;
1142            var index;
1143
1144            index = pos % self.group.length;
1145            index = index < 0 ? self.group.length + index : index;
1146
1147            if ( !self.slides[ pos ] && self.group[ index ] ) {
1148                $slide = $('<div class="fancybox-slide"></div>').appendTo( self.$refs.stage );
1149
1150                self.slides[ pos ] = $.extend( true, {}, self.group[ index ], {
1151                    pos      : pos,
1152                    $slide   : $slide,
1153                    isLoaded : false,
1154                });
1155
1156                self.updateSlide( self.slides[ pos ] );
1157            }
1158
1159            return self.slides[ pos ];
1160        },
1161
1162
1163        // Scale image to the actual size of the image
1164        // ===========================================
1165
1166        scaleToActual : function( x, y, duration ) {
1167
1168            var self = this;
1169
1170            var current = self.current;
1171            var $what   = current.$content;
1172
1173            var imgPos, posX, posY, scaleX, scaleY;
1174
1175            var canvasWidth  = parseInt( current.$slide.width(), 10 );
1176            var canvasHeight = parseInt( current.$slide.height(), 10 );
1177
1178            var newImgWidth  = current.width;
1179            var newImgHeight = current.height;
1180
1181            if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating) {
1182                return;
1183            }
1184
1185            $.fancybox.stop( $what );
1186
1187            self.isAnimating = true;
1188
1189            x = x === undefined ? canvasWidth  * 0.5  : x;
1190            y = y === undefined ? canvasHeight * 0.5  : y;
1191
1192            imgPos = $.fancybox.getTranslate( $what );
1193
1194            scaleX  = newImgWidth  / imgPos.width;
1195            scaleY  = newImgHeight / imgPos.height;
1196
1197            // Get center position for original image
1198            posX = ( canvasWidth * 0.5  - newImgWidth * 0.5 );
1199            posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 );
1200
1201            // Make sure image does not move away from edges
1202            if ( newImgWidth > canvasWidth ) {
1203                posX = imgPos.left * scaleX - ( ( x * scaleX ) - x );
1204
1205                if ( posX > 0 ) {
1206                    posX = 0;
1207                }
1208
1209                if ( posX <  canvasWidth - newImgWidth ) {
1210                    posX = canvasWidth - newImgWidth;
1211                }
1212            }
1213
1214            if ( newImgHeight > canvasHeight) {
1215                posY = imgPos.top  * scaleY - ( ( y * scaleY ) - y );
1216
1217                if ( posY > 0 ) {
1218                    posY = 0;
1219                }
1220
1221                if ( posY <  canvasHeight - newImgHeight ) {
1222                    posY = canvasHeight - newImgHeight;
1223                }
1224            }
1225
1226            self.updateCursor( newImgWidth, newImgHeight );
1227
1228            $.fancybox.animate( $what, {
1229                top    : posY,
1230                left   : posX,
1231                scaleX : scaleX,
1232                scaleY : scaleY
1233            }, duration || 330, function() {
1234                self.isAnimating = false;
1235            });
1236
1237            // Stop slideshow
1238            if ( self.SlideShow && self.SlideShow.isActive ) {
1239                self.SlideShow.stop();
1240            }
1241        },
1242
1243
1244        // Scale image to fit inside parent element
1245        // ========================================
1246
1247        scaleToFit : function( duration ) {
1248
1249            var self = this;
1250
1251            var current = self.current;
1252            var $what   = current.$content;
1253            var end;
1254
1255            if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) {
1256                return;
1257            }
1258
1259            $.fancybox.stop( $what );
1260
1261            self.isAnimating = true;
1262
1263            end = self.getFitPos( current );
1264
1265            self.updateCursor( end.width, end.height );
1266
1267            $.fancybox.animate( $what, {
1268                top    : end.top,
1269                left   : end.left,
1270                scaleX : end.width  / $what.width(),
1271                scaleY : end.height / $what.height()
1272            }, duration || 330, function() {
1273                self.isAnimating = false;
1274            });
1275
1276        },
1277
1278        // Calculate image size to fit inside viewport
1279        // ===========================================
1280
1281        getFitPos : function( slide ) {
1282            var self  = this;
1283            var $what = slide.$content;
1284
1285            var imgWidth  = slide.width;
1286            var imgHeight = slide.height;
1287
1288            var margin = slide.opts.margin;
1289
1290            var canvasWidth, canvasHeight, minRatio, width, height;
1291
1292            if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) {
1293                return false;
1294            }
1295
1296            // Convert "margin to CSS style: [ top, right, bottom, left ]
1297            if ( $.type( margin ) === "number" ) {
1298                margin = [ margin, margin ];
1299            }
1300
1301            if ( margin.length == 2 ) {
1302                margin = [ margin[0], margin[1], margin[0], margin[1] ];
1303            }
1304
1305            if ( $W.width() < 800 ) {
1306                margin = [ 0, 0, 0, 0 ];
1307            }
1308
1309            // We can not use $slide width here, because it can have different diemensions while in transiton
1310            canvasWidth  = parseInt( self.$refs.stage.width(), 10 )  - ( margin[ 1 ] + margin[ 3 ] );
1311            canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] );
1312
1313            minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight );
1314
1315            width  = Math.floor( minRatio * imgWidth );
1316            height = Math.floor( minRatio * imgHeight );
1317
1318            // Use floor rounding to make sure it really fits
1319            return {
1320                top    : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ],
1321                left   : Math.floor( ( canvasWidth  - width )  * 0.5 ) + margin[ 3 ],
1322                width  : width,
1323                height : height
1324            };
1325
1326        },
1327
1328
1329        // Update position and content of all slides
1330        // =========================================
1331
1332        update : function() {
1333
1334            var self = this;
1335
1336            $.each( self.slides, function( key, slide ) {
1337                self.updateSlide( slide );
1338            });
1339
1340        },
1341
1342
1343        // Update slide position and scale content to fit
1344        // ==============================================
1345
1346        updateSlide : function( slide ) {
1347
1348            var self  = this;
1349            var $what = slide.$content;
1350
1351            if ( $what && ( slide.width || slide.height ) ) {
1352                $.fancybox.stop( $what );
1353
1354                $.fancybox.setTranslate( $what, self.getFitPos( slide ) );
1355
1356                if ( slide.pos === self.currPos ) {
1357                    self.updateCursor();
1358                }
1359            }
1360
1361            slide.$slide.trigger( 'refresh' );
1362
1363            self.trigger( 'onUpdate', slide );
1364
1365        },
1366
1367        // Update cursor style depending if content can be zoomed
1368        // ======================================================
1369
1370        updateCursor : function( nextWidth, nextHeight ) {
1371
1372            var self = this;
1373            var isScaledDown;
1374
1375            var $container = self.$refs.container.removeClass('fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut');
1376
1377            if ( !self.current || self.isClosing ) {
1378                return;
1379            }
1380
1381            if ( self.isZoomable() ) {
1382
1383                $container.addClass( 'fancybox-is-zoomable' );
1384
1385                if ( nextWidth !== undefined && nextHeight !== undefined ) {
1386                    isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height;
1387
1388                } else {
1389                    isScaledDown = self.isScaledDown();
1390                }
1391
1392                if ( isScaledDown ) {
1393
1394                    // If image is scaled down, then, obviously, it can be zoomed to full size
1395                    $container.addClass('fancybox-can-zoomIn');
1396
1397                } else {
1398
1399                    if ( self.current.opts.touch ) {
1400
1401                        // If image size ir largen than available available and touch module is not disable,
1402                        // then user can do panning
1403                        $container.addClass('fancybox-can-drag');
1404
1405                    } else {
1406                        $container.addClass('fancybox-can-zoomOut');
1407                    }
1408
1409                }
1410
1411            } else if ( self.current.opts.touch ) {
1412                $container.addClass('fancybox-can-drag');
1413            }
1414
1415        },
1416
1417
1418        // Check if current slide is zoomable
1419        // ==================================
1420
1421        isZoomable : function() {
1422
1423            var self = this;
1424
1425            var current = self.current;
1426            var fitPos;
1427
1428            if ( !current || self.isClosing ) {
1429                return;
1430            }
1431
1432            // Assume that slide is zoomable if
1433            //   - image is loaded successfuly
1434            //   - click action is "zoom"
1435            //   - actual size of the image is smaller than available area
1436            if ( current.type === 'image' && current.isLoaded && !current.hasError &&
1437                ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) ===  "zoom" ) )
1438            ) {
1439
1440                fitPos = self.getFitPos( current );
1441
1442                if ( current.width > fitPos.width || current.height > fitPos.height ) {
1443                    return true;
1444                }
1445
1446            }
1447
1448            return false;
1449
1450        },
1451
1452
1453        // Check if current image dimensions are smaller than actual
1454        // =========================================================
1455
1456        isScaledDown : function() {
1457
1458            var self = this;
1459
1460            var current = self.current;
1461            var $what   = current.$content;
1462
1463            var rez = false;
1464
1465            if ( $what ) {
1466                rez = $.fancybox.getTranslate( $what );
1467                rez = rez.width < current.width || rez.height < current.height;
1468            }
1469
1470            return rez;
1471
1472        },
1473
1474
1475        // Check if image dimensions exceed parent element
1476        // ===============================================
1477
1478        canPan : function() {
1479
1480            var self = this;
1481
1482            var current = self.current;
1483            var $what   = current.$content;
1484
1485            var rez = false;
1486
1487            if ( $what ) {
1488                rez = self.getFitPos( current );
1489                rez = Math.abs( $what.width() - rez.width ) > 1  || Math.abs( $what.height() - rez.height ) > 1;
1490
1491            }
1492
1493            return rez;
1494
1495        },
1496
1497
1498        // Load content into the slide
1499        // ===========================
1500
1501        loadSlide : function( slide ) {
1502
1503            var self = this, type, $slide;
1504            var ajaxLoad;
1505
1506            if ( slide.isLoading ) {
1507                return;
1508            }
1509
1510            if ( slide.isLoaded ) {
1511                return;
1512            }
1513
1514            slide.isLoading = true;
1515
1516            self.trigger( 'beforeLoad', slide );
1517
1518            type   = slide.type;
1519            $slide = slide.$slide;
1520
1521            $slide
1522                .off( 'refresh' )
1523                .trigger( 'onReset' )
1524                .addClass( 'fancybox-slide--' + ( type || 'unknown' ) )
1525                .addClass( slide.opts.slideClass );
1526
1527            // Create content depending on the type
1528
1529            switch ( type ) {
1530
1531                case 'image':
1532
1533                    self.setImage( slide );
1534
1535                break;
1536
1537                case 'iframe':
1538
1539                    self.setIframe( slide );
1540
1541                break;
1542
1543                case 'html':
1544
1545                    self.setContent( slide, slide.src || slide.content );
1546
1547                break;
1548
1549                case 'inline':
1550
1551                    if ( $( slide.src ).length ) {
1552                        self.setContent( slide, $( slide.src ) );
1553
1554                    } else {
1555                        self.setError( slide );
1556                    }
1557
1558                break;
1559
1560                case 'ajax':
1561
1562                    self.showLoading( slide );
1563
1564                    ajaxLoad = $.ajax( $.extend( {}, slide.opts.ajax.settings, {
1565                        url : slide.src,
1566                        success : function ( data, textStatus ) {
1567
1568                            if ( textStatus === 'success' ) {
1569                                self.setContent( slide, data );
1570                            }
1571
1572                        },
1573                        error : function ( jqXHR, textStatus ) {
1574
1575                            if ( jqXHR && textStatus !== 'abort' ) {
1576                                self.setError( slide );
1577                            }
1578
1579                        }
1580                    }));
1581
1582                    $slide.one( 'onReset', function () {
1583                        ajaxLoad.abort();
1584                    });
1585
1586                break;
1587
1588                default:
1589
1590                    self.setError( slide );
1591
1592                break;
1593
1594            }
1595
1596            return true;
1597
1598        },
1599
1600
1601        // Use thumbnail image, if possible
1602        // ================================
1603
1604        setImage : function( slide ) {
1605
1606            var self   = this;
1607            var srcset = slide.opts.image.srcset;
1608
1609            var found, temp, pxRatio, windowWidth;
1610
1611            // If we have "srcset", then we need to find matching "src" value.
1612            // This is necessary, because when you set an src attribute, the browser will preload the image
1613            // before any javascript or even CSS is applied.
1614            if ( srcset ) {
1615                pxRatio     = window.devicePixelRatio || 1;
1616                windowWidth = window.innerWidth  * pxRatio;
1617
1618                temp = srcset.split(',').map(function ( el ) {
1619            		var ret = {};
1620
1621            		el.trim().split(/\s+/).forEach(function ( el, i ) {
1622                        var value = parseInt( el.substring(0, el.length - 1), 10 );
1623
1624            			if ( i === 0 ) {
1625            				return ( ret.url = el );
1626            			}
1627
1628                        if ( value ) {
1629                            ret.value   = value;
1630                            ret.postfix = el[ el.length - 1 ];
1631                        }
1632
1633            		});
1634
1635            		return ret;
1636            	});
1637
1638                // Sort by value
1639                temp.sort(function (a, b) {
1640                  return a.value - b.value;
1641                });
1642
1643                // Ok, now we have an array of all srcset values
1644                for ( var j = 0; j < temp.length; j++ ) {
1645                    var el = temp[ j ];
1646
1647                    if ( ( el.postfix === 'w' && el.value >= windowWidth ) || ( el.postfix === 'x' && el.value >= pxRatio ) ) {
1648                        found = el;
1649                        break;
1650                    }
1651                }
1652
1653                // If not found, take the last one
1654                if ( !found && temp.length ) {
1655                    found = temp[ temp.length - 1 ];
1656                }
1657
1658                if ( found ) {
1659                    slide.src = found.url;
1660
1661                    // If we have default width/height values, we can calculate height for matching source
1662                    if ( slide.width && slide.height && found.postfix == 'w' ) {
1663                        slide.height = ( slide.width / slide.height ) * found.value;
1664                        slide.width  = found.value;
1665                    }
1666                }
1667            }
1668
1669            // This will be wrapper containing both ghost and actual image
1670            slide.$content = $('<div class="fancybox-image-wrap"></div>')
1671                .addClass( 'fancybox-is-hidden' )
1672                .appendTo( slide.$slide );
1673
1674
1675            // If we have a thumbnail, we can display it while actual image is loading
1676            // Users will not stare at black screen and actual image will appear gradually
1677            if ( slide.opts.preload !== false && slide.opts.width && slide.opts.height && ( slide.opts.thumb || slide.opts.$thumb ) ) {
1678
1679                slide.width  = slide.opts.width;
1680                slide.height = slide.opts.height;
1681
1682                slide.$ghost = $('<img />')
1683                    .one('error', function() {
1684
1685                        $(this).remove();
1686
1687                        slide.$ghost = null;
1688
1689                        self.setBigImage( slide );
1690
1691                    })
1692                    .one('load', function() {
1693
1694                        self.afterLoad( slide );
1695
1696                        self.setBigImage( slide );
1697
1698                    })
1699                    .addClass( 'fancybox-image' )
1700                    .appendTo( slide.$content )
1701                    .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) );
1702
1703            } else {
1704
1705                self.setBigImage( slide );
1706
1707            }
1708
1709        },
1710
1711
1712        // Create full-size image
1713        // ======================
1714
1715        setBigImage : function ( slide ) {
1716            var self = this;
1717            var $img = $('<img />');
1718
1719            slide.$image = $img
1720                .one('error', function() {
1721
1722                    self.setError( slide );
1723
1724                })
1725                .one('load', function() {
1726
1727                    // Clear timeout that checks if loading icon needs to be displayed
1728                    clearTimeout( slide.timouts );
1729
1730                    slide.timouts = null;
1731
1732                    if ( self.isClosing ) {
1733                        return;
1734                    }
1735
1736                    slide.width  = this.naturalWidth;
1737                    slide.height = this.naturalHeight;
1738
1739                    if ( slide.opts.image.srcset ) {
1740                        $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset );
1741                    }
1742
1743                    self.hideLoading( slide );
1744
1745                    if ( slide.$ghost ) {
1746
1747                        slide.timouts = setTimeout(function() {
1748                            slide.timouts = null;
1749
1750                            slide.$ghost.hide();
1751
1752                        }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) );
1753
1754                    } else {
1755                        self.afterLoad( slide );
1756                    }
1757
1758                })
1759                .addClass( 'fancybox-image' )
1760                .attr('src', slide.src)
1761                .appendTo( slide.$content );
1762
1763            if ( ( $img[0].complete || $img[0].readyState == "complete" ) && $img[0].naturalWi
1763dth && $img[0].naturalHeight ) {
1764                  $img.trigger( 'load' );
1765
1766            } else if( $img[0].error ) {
1767                 $img.trigger( 'error' );
1768
1769            } else {
1770
1771                slide.timouts = setTimeout(function() {
1772                    if ( !$img[0].complete && !slide.hasError ) {
1773                        self.showLoading( slide );
1774                    }
1775
1776                }, 100);
1777
1778            }
1779
1780        },
1781
1782
1783        // Create iframe wrapper, iframe and bindings
1784        // ==========================================
1785
1786        setIframe : function( slide ) {
1787            var self	= this,
1788                opts    = slide.opts.iframe,
1789                $slide	= slide.$slide,
1790                $iframe;
1791
1792            slide.$content = $('<div class="fancybox-content' + ( opts.preload ? ' fancybox-is-hidden' : '' ) + '"></div>')
1793                .css( opts.css )
1794                .appendTo( $slide );
1795
1796            $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) )
1797                .attr( opts.attr )
1798                .appendTo( slide.$content );
1799
1800            if ( opts.preload ) {
1801
1802                self.showLoading( slide );
1803
1804                // Unfortunately, it is not always possible to determine if iframe is successfully loaded
1805                // (due to browser security policy)
1806
1807                $iframe.on('load.fb error.fb', function(e) {
1808                    this.isReady = 1;
1809
1810                    slide.$slide.trigger( 'refresh' );
1811
1812                    self.afterLoad( slide );
1813                });
1814
1815                // Recalculate iframe content size
1816                // ===============================
1817
1818                $slide.on('refresh.fb', function() {
1819                    var $wrap = slide.$content,
1820                        frameWidth  = opts.css.width,
1821                        frameHeight = opts.css.height,
1822                        scrollWidth,
1823                        $contents,
1824                        $body;
1825
1826                    if ( $iframe[0].isReady !== 1 ) {
1827                        return;
1828                    }
1829
1830                    // Check if content is accessible,
1831                    // it will fail if frame is not with the same origin
1832
1833                    try {
1834                        $contents = $iframe.contents();
1835                        $body     = $contents.find('body');
1836
1837                    } catch (ignore) {}
1838
1839                    // Calculate dimensions for the wrapper
1840                    if ( $body && $body.length ) {
1841
1842                        if ( frameWidth === undefined ) {
1843                            scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth;
1844
1845                            frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) );
1846                            frameWidth += $wrap.outerWidth() - $wrap.innerWidth();
1847                        }
1848
1849                        if ( frameHeight === undefined ) {
1850                            frameHeight = Math.ceil( $body.outerHeight(true) );
1851                            frameHeight += $wrap.outerHeight() - $wrap.innerHeight();
1852                        }
1853
1854                        // Resize wrapper to fit iframe content
1855                        if ( frameWidth ) {
1856                            $wrap.width( frameWidth );
1857                        }
1858
1859                        if ( frameHeight ) {
1860                            $wrap.height( frameHeight );
1861                        }
1862                    }
1863
1864                    $wrap.removeClass( 'fancybox-is-hidden' );
1865
1866                });
1867
1868            } else {
1869
1870                this.afterLoad( slide );
1871
1872            }
1873
1874            $iframe.attr( 'src', slide.src );
1875
1876            if ( slide.opts.smallBtn === true ) {
1877                slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) );
1878            }
1879
1880            // Remove iframe if closing or changing gallery item
1881            $slide.one( 'onReset', function () {
1882
1883                // This helps IE not to throw errors when closing
1884                try {
1885
1886                    $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' );
1887
1888                } catch ( ignore ) {}
1889
1890                $( this ).empty();
1891
1892                slide.isLoaded = false;
1893
1894            });
1895
1896        },
1897
1898
1899        // Wrap and append content to the slide
1900        // ======================================
1901
1902        setContent : function ( slide, content ) {
1903
1904            var self = this;
1905
1906            if ( self.isClosing ) {
1907                return;
1908            }
1909
1910            self.hideLoading( slide );
1911
1912            slide.$slide.empty();
1913
1914            if ( isQuery( content ) && content.parent().length ) {
1915
1916                // If content is a jQuery object, then it will be moved to the slide.
1917                // The placeholder is created so we will know where to put it back.
1918                // If user is navigating gallery fast, then the content might be already inside fancyBox
1919                // =====================================================================================
1920
1921                // Make sure content is not already moved to fancyBox
1922                content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' );
1923
1924                // Create temporary element marking original place of the content
1925                slide.$placeholder = $( '<div></div>' ).hide().insertAfter( content );
1926
1927                // Make sure content is visible
1928                content.css('display', 'inline-block');
1929
1930            } else if ( !slide.hasError ) {
1931
1932                // If content is just a plain text, try to convert it to html
1933                if ( $.type( content ) === 'string' ) {
1934                    content = $('<div>').append( $.trim( content ) ).contents();
1935
1936                    // If we have text node, then add wrapping element to make vertical alignment work
1937                    if ( content[0].nodeType === 3 ) {
1938                        content = $('<div>').html( content );
1939                    }
1940                }
1941
1942                // If "filter" option is provided, then filter content
1943                if ( slide.opts.filter ) {
1944                    content = $('<div>').html( content ).find( slide.opts.filter );
1945                }
1946
1947            }
1948
1949            slide.$slide.one('onReset', function () {
1950
1951                // Put content back
1952                if ( slide.$placeholder ) {
1953                    slide.$placeholder.after( content.hide() ).remove();
1954
1955                    slide.$placeholder = null;
1956                }
1957
1958                // Remove custom close button
1959                if ( slide.$smallBtn ) {
1960                    slide.$smallBtn.remove();
1961
1962                    slide.$smallBtn = null;
1963                }
1964
1965                // Remove content and mark slide as not loaded
1966                if ( !slide.hasError ) {
1967                    $(this).empty();
1968
1969                    slide.isLoaded = false;
1970                }
1971
1972            });
1973
1974            slide.$content = $( content ).appendTo( slide.$slide );
1975
1976            if ( slide.opts.smallBtn && !slide.$smallBtn ) {
1977                slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content.filter('div').first() );
1978            }
1979
1980            this.afterLoad( slide );
1981        },
1982
1983        // Display error message
1984        // =====================
1985
1986        setError : function ( slide ) {
1987
1988            slide.hasError = true;
1989
1990            slide.$slide.removeClass( 'fancybox-slide--' + slide.type );
1991
1992            this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) );
1993
1994        },
1995
1996
1997        // Show loading icon inside the slide
1998        // ==================================
1999
2000        showLoading : function( slide ) {
2001
2002            var self = this;
2003
2004            slide = slide || self.current;
2005
2006            if ( slide && !slide.$spinner ) {
2007                slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide );
2008            }
2009
2010        },
2011
2012        // Remove loading icon from the slide
2013        // ==================================
2014
2015        hideLoading : function( slide ) {
2016
2017            var self = this;
2018
2019            slide = slide || self.current;
2020
2021            if ( slide && slide.$spinner ) {
2022                slide.$spinner.remove();
2023
2024                delete slide.$spinner;
2025            }
2026
2027        },
2028
2029
2030        // Adjustments after slide content has been loaded
2031        // ===============================================
2032
2033        afterLoad : function( slide ) {
2034
2035            var self = this;
2036
2037            if ( self.isClosing ) {
2038                return;
2039            }
2040
2041            slide.isLoading = false;
2042            slide.isLoaded  = true;
2043
2044            self.trigger( 'afterLoad', slide );
2045
2046            self.hideLoading( slide );
2047
2048            if ( slide.opts.protect && slide.$content && !slide.hasError ) {
2049
2050                // Disable right click
2051                slide.$content.on( 'contextmenu.fb', function( e ) {
2052                     if ( e.button == 2 ) {
2053                         e.preventDefault();
2054                     }
2055
2056                    return true;
2057                });
2058
2059                // Add fake element on top of the image
2060                // This makes a bit harder for user to select image
2061                if ( slide.type === 'image' ) {
2062                    $( '<div class="fancybox-spaceball"></div>' ).appendTo( slide.$content );
2063                }
2064
2065            }
2066
2067            self.revealContent( slide );
2068
2069        },
2070
2071
2072        // Make content visible
2073        // This method is called right after content has been loaded or
2074        // user navigates gallery and transition should start
2075        // ============================================================
2076
2077        revealContent : function( slide ) {
2078
2079            var self   = this;
2080            var $slide = slide.$slide;
2081
2082            var effect, effectClassName, duration, opacity, end, start = false;
2083
2084            effect   = slide.opts[ self.firstRun ? 'animationEffect'   : 'transitionEffect' ];
2085            duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ];
2086
2087            duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 );
2088
2089            if ( slide.isMoved || slide.pos !== self.currPos || !duration ) {
2090                effect = false;
2091            }
2092
2093            // Check if can zoom
2094            if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) {
2095                effect = 'fade';
2096            }
2097
2098            // Zoom animation
2099            // ==============
2100
2101            if ( effect === 'zoom' ) {
2102                end = self.getFitPos( slide );
2103
2104                end.scaleX = end.width  / start.width;
2105                end.scaleY = end.height / start.height;
2106
2107                delete end.width;
2108                delete end.height;
2109
2110                // Check if we need to animate opacity
2111                opacity = slide.opts.zoomOpacity;
2112
2113                if ( opacity == 'auto' ) {
2114                    opacity = Math.abs( slide.width / slide.height - start.width / start.height ) > 0.1;
2115                }
2116
2117                if ( opacity ) {
2118                    start.opacity = 0.1;
2119                    end.opacity   = 1;
2120                }
2121
2122                // Draw image at start position
2123                $.fancybox.setTranslate( slide.$content.removeClass( 'fancybox-is-hidden' ), start );
2124
2125                forceRedraw( slide.$content );
2126
2127                // Start animation
2128                $.fancybox.animate( slide.$content, end, duration, function() {
2129                    self.complete();
2130                });
2131
2132                return;
2133            }
2134
2135
2136            self.updateSlide( slide );
2137
2138
2139            // Simply show content
2140            // ===================
2141
2142            if ( !effect ) {
2143                forceRedraw( $slide );
2144
2145                slide.$content.removeClass( 'fancybox-is-hidden' );
2146
2147                if ( slide.pos === self.currPos ) {
2148                    self.complete();
2149                }
2150
2151                return;
2152            }
2153
2154            $.fancybox.stop( $slide );
2155
2156            effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos > self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect;
2157
2158            $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName );
2159
2160            slide.$content.removeClass( 'fancybox-is-hidden' );
2161
2162            //Force reflow for CSS3 transitions
2163            forceRedraw( $slide );
2164
2165            $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) {
2166                $slide.removeClass( effectClassName ).removeAttr( 'style' );
2167
2168                if ( slide.pos === self.currPos ) {
2169                    self.complete();
2170                }
2171
2172            }, true);
2173
2174        },
2175
2176
2177        // Check if we can and have to zoom from thumbnail
2178        //================================================
2179
2180        getThumbPos : function( slide ) {
2181
2182            var self = this;
2183            var rez  = false;
2184
2185            // Check if element is inside the viewport by at least 1 pixel
2186            var isElementVisible = function( $el ) {
2187                var element = $el[0];
2188
2189                var elementRect = element.getBoundingClientRect();
2190                var parentRects = [];
2191
2192                var visibleInAllParents;
2193
2194                while ( element.parentElement !== null ) {
2195                    if ( $(element.parentElement).css('overflow') === 'hidden'  || $(element.parentElement).css('overflow') === 'auto' ) {
2196                        parentRects.push(element.parentElement.getBoundingClientRect());
2197                    }
2198
2199                    element = element.parentElement;
2200                }
2201
2202                visibleInAllParents = parentRects.every(function(parentRect){
2203                    var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
2204                    var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
2205
2206                    return visiblePixelX > 0 && visiblePixelY > 0;
2207                });
2208
2209                return visibleInAllParents &&
2210                    elementRect.bottom > 0 && elementRect.right > 0 &&
2211                    elementRect.left < $(window).width() && elementRect.top < $(window).height();
2212            };
2213
2214            var $thumb   = slide.opts.$thumb;
2215            var thumbPos = $thumb ? $thumb.offset() : 0;
2216            var slidePos;
2217
2218            if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) {
2219                slidePos = self.$refs.stage.offset();
2220
2221                rez = {
2222                    top    : thumbPos.top  - slidePos.top  + parseFloat( $thumb.css( "border-top-width" ) || 0 ),
2223                    left   : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ),
2224                    width  : $thumb.width(),
2225                    height : $thumb.height(),
2226                    scaleX : 1,
2227                    scaleY : 1
2228                };
2229            }
2230
2231            return rez;
2232        },
2233
2234
2235        // Final adjustments after current gallery item is moved to position
2236        // and it`s content is loaded
2237        // ==================================================================
2238
2239        complete : function() {
2240
2241            var self = this;
2242
2243            var current = self.current;
2244            var slides  = {};
2245
2246            if ( current.isMoved || !current.isLoaded || current.isComplete ) {
2247                return;
2248            }
2249
2250            current.isComplete = true;
2251
2252            current.$slide.siblings().trigger( 'onReset' );
2253
2254            // Trigger any CSS3 transiton inside the slide
2255            forceRedraw( current.$slide );
2256
2257            current.$slide.addClass( 'fancybox-slide--complete' );
2258
2259            // Remove unnecessary slides
2260            $.each( self.slides, function( key, slide ) {
2261                if ( slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1 ) {
2262                    slides[ slide.pos ] = slide;
2263
2264                } else if ( slide ) {
2265
2266                    $.fancybox.stop( slide.$slide );
2267
2268                    slide.$slide.off().remove();
2269                }
2270            });
2271
2272            self.slides = slides;
2273
2274            self.updateCursor();
2275
2276            self.trigger( 'afterShow' );
2277
2278            // Try to focus on the first focusable element
2279            if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) {
2280                self.focus();
2281            }
2282
2283        },
2284
2285
2286        // Preload next and previous slides
2287        // ================================
2288
2289        preload : function() {
2290            var self = this;
2291            var next, prev;
2292
2293            if ( self.group.length < 2 ) {
2294                return;
2295            }
2296
2297            next  = self.slides[ self.currPos + 1 ];
2298            prev  = self.slides[ self.currPos - 1 ];
2299
2300            if ( next && next.type === 'image' ) {
2301                self.loadSlide( next );
2302            }
2303
2304            if ( prev && prev.type === 'image' ) {
2305                self.loadSlide( prev );
2306            }
2307
2308        },
2309
2310
2311        // Try to find and focus on the first focusable element
2312        // ====================================================
2313
2314        focus : function() {
2315            var current = this.current;
2316            var $el;
2317
2318            if ( this.isClosing ) {
2319                return;
2320            }
2321
2322            if ( current && current.isComplete ) {
2323
2324                // Look for first input with autofocus attribute
2325                $el = current.$slide.find('input[autofocus]:enabled:visible:first');
2326
2327                if ( !$el.length ) {
2328                    $el = current.$slide.find('button,:input,[tabindex],a').filter(':enabled:visible:first');
2329                }
2330            }
2331
2332            $el = $el && $el.length ? $el : this.$refs.container;
2333
2334            $el.focus();
2335        },
2336
2337
2338        // Activates current instance - brings container to the front and enables keyboard,
2339        // notifies other instances about deactivating
2340        // =================================================================================
2341
2342        activate : function () {
2343            var self = this;
2344
2345            // Deactivate all instances
2346            $( '.fancybox-container' ).each(function () {
2347                var instance = $(this).data( 'FancyBox' );
2348
2349                // Skip self and closing instances
2350                if (instance && instance.uid !== self.uid && !instance.isClosing) {
2351                    instance.trigger( 'onDeactivate' );
2352                }
2353
2354            });
2355
2356            if ( self.current ) {
2357                if ( self.$refs.container.index() > 0 ) {
2358                    self.$refs.container.prependTo( document.body );
2359                }
2360
2361                self.updateControls();
2362            }
2363
2364            self.trigger( 'onActivate' );
2365
2366            self.addEvents();
2367
2368        },
2369
2370
2371        // Start closing procedure
2372        // This will start "zoom-out" animation if needed and clean everything up afterwards
2373        // =================================================================================
2374
2375        close : function( e, d ) {
2376
2377            var self    = this;
2378            var current = self.current;
2379
2380            var effect, duration;
2381            var $what, opacity, start, end;
2382
2383            var done = function() {
2384                self.cleanUp( e );
2385            };
2386
2387            if ( self.isClosing ) {
2388                return false;
2389            }
2390
2391            self.isClosing = true;
2392
2393            // If beforeClose callback prevents closing, make sure content is centered
2394            if ( self.trigger( 'beforeClose', e ) === false ) {
2395                self.isClosing = false;
2396
2397                requestAFrame(function() {
2398                    self.update();
2399                });
2400
2401                return false;
2402            }
2403
2404            // Remove all events
2405            // If there are multiple instances, they will be set again by "activate" method
2406            self.removeEvents();
2407
2408            if ( current.timouts ) {
2409                clearTimeout( current.timouts );
2410            }
2411
2412            $what    = current.$content;
2413            effect   = current.opts.animationEffect;
2414            duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 );
2415
2416            // Remove other slides
2417            current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' );
2418
2419            current.$slide.siblings().trigger( 'onReset' ).remove();
2420
2421            // Trigger animations
2422            if ( duration ) {
2423                self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' );
2424            }
2425
2426            // Clean up
2427            self.hideLoading( current );
2428
2429            self.hideControls();
2430
2431            self.updateCursor();
2432
2433            // Check if possible to zoom-out
2434            if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) {
2435                effect = 'fade';
2436            }
2437
2438            if ( effect === 'zoom' ) {
2439                $.fancybox.stop( $what );
2440
2441                start = $.fancybox.getTranslate( $what );
2442
2443                start.width  = start.width  * start.scaleX;
2444                start.height = start.height * start.scaleY;
2445
2446                // Check if we need to animate opacity
2447                opacity = current.opts.zoomOpacity;
2448
2449                if ( opacity == 'auto' ) {
2450                    opacity = Math.abs( current.width / current.height - end.width / end.height ) > 0.1;
2451                }
2452
2453                if ( opacity ) {
2454                    end.opacity = 0;
2455                }
2456
2457                start.scaleX = start.width  / end.width;
2458                start.scaleY = start.height / end.height;
2459
2460                start.width  = end.width;
2461                start.height = end.height;
2462
2463                $.fancybox.setTranslate( current.$content, start );
2464
2465                $.fancybox.animate( current.$content, end, duration, done );
2466
2467                return true;
2468            }
2469
2470            if ( effect && duration ) {
2471
2472                // If skip animation
2473                if ( e === true ) {
2474                    setTimeout( done, duration );
2475
2476                } else {
2477                    $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done );
2478                }
2479
2480            } else {
2481                done();
2482            }
2483
2484            return true;
2485        },
2486
2487
2488        // Final adjustments after removing the instance
2489        // =============================================
2490
2491        cleanUp : function( e ) {
2492            var self = this,
2493                instance;
2494
2495            self.current.$slide.trigger( 'onReset' );
2496
2497            self.$refs.container.empty().remove();
2498
2499            self.trigger( 'afterClose', e );
2500
2501            // Place back focus
2502            if ( self.$lastFocus && !!self.current.opts.backFocus ) {
2503                self.$lastFocus.focus();
2504            }
2505
2506            self.current = null;
2507
2508            // Check if there are other instances
2509            instance = $.fancybox.getInstance();
2510
2511            if ( instance ) {
2512                instance.activate();
2513
2514            } else {
2515                $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft );
2516
2517                $( 'html' ).removeClass( 'fancybox-enabled' );
2518
2519                $( '#fancybox-style-noscroll' ).remove();
2520            }
2521
2522        },
2523
2524
2525        // Call callback and trigger an event
2526        // ==================================
2527
2528        trigger : function( name, slide ) {
2529            var args  = Array.prototype.slice.call(arguments, 1),
2530                self  = this,
2531                obj   = slide && slide.opts ? slide : self.current,
2532                rez;
2533
2534            if ( obj ) {
2535                args.unshift( obj );
2536
2537            } else {
2538                obj = self;
2539            }
2540
2541            args.unshift( self );
2542
2543            if ( $.isFunction( obj.opts[ name ] ) ) {
2544                rez = obj.opts[ name ].apply( obj, args );
2545            }
2546
2547            if ( rez === false ) {
2548                return rez;
2549            }
2550
2551            if ( name === 'afterClose' ) {
2552                $D.trigger( name + '.fb', args );
2553
2554            } else {
2555                self.$refs.container.trigger( name + '.fb', args );
2556            }
2557
2558        },
2559
2560
2561        // Update infobar values, navigation button states and reveal caption
2562        // ==================================================================
2563
2564        updateControls : function ( force ) {
2565
2566            var self = this;
2567
2568            var current  = self.current;
2569            var index    = current.index;
2570            var opts     = current.opts;
2571            var caption  = opts.caption;
2572            var $caption = self.$refs.caption;
2573
2574            // Recalculate content dimensions
2575            current.$slide.trigger( 'refresh' );
2576
2577            self.$caption = caption && caption.length ? $caption.html( caption ) : null;
2578
2579            if ( !self.isHiddenControls ) {
2580                self.showControls();
2581            }
2582
2583            // Update info and navigation elements
2584            $('[data-fancybox-count]').html( self.group.length );
2585            $('[data-fancybox-index]').html( index + 1 );
2586
2587            $('[data-fancybox-prev]').prop('disabled', ( !opts.loop && index <= 0 ) );
2588            $('[data-fancybox-next]').prop('disabled', ( !opts.loop && index >= self.group.length - 1 ) );
2589
2590        },
2591
2592        // Hide toolbar and caption
2593        // ========================
2594
2595        hideControls : function () {
2596
2597            this.isHiddenControls = true;
2598
2599            this.$refs.container.removeClass('fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav');
2600
2601        },
2602
2603        showControls : function() {
2604
2605            var self = this;
2606            var opts = self.current ? self.current.opts : self.opts;
2607            var $container = self.$refs.container;
2608
2609            self.isHiddenControls   = false;
2610            self.idleSecondsCounter = 0;
2611
2612            $container
2613                .toggleClass('fancybox-show-toolbar', !!( opts.toolbar && opts.buttons ) )
2614                .toggleClass('fancybox-show-infobar', !!( opts.infobar && self.group.length > 1 ) )
2615                .toggleClass('fancybox-show-nav',     !!( opts.arrows && self.group.length > 1 ) )
2616                .toggleClass('fancybox-is-modal',     !!opts.modal );
2617
2618            if ( self.$caption ) {
2619                $container.addClass( 'fancybox-show-caption ');
2620
2621            } else {
2622               $container.removeClass( 'fancybox-show-caption' );
2623           }
2624
2625       },
2626
2627
2628       // Toggle toolbar and caption
2629       // ==========================
2630
2631       toggleControls : function() {
2632
2633           if ( this.isHiddenControls ) {
2634               this.showControls();
2635
2636           } else {
2637               this.hideControls();
2638           }
2639
2640       },
2641
2642
2643    });
2644
2645
2646    $.fancybox = {
2647
2648        version  : "3.1.27",
2649        defaults : defaults,
2650
2651
2652        // Get current instance and execute a command.
2653        //
2654        // Examples of usage:
2655        //
2656        //   $instance = $.fancybox.getInstance();
2657        //   $.fancybox.getInstance().jumpTo( 1 );
2658        //   $.fancybox.getInstance( 'jumpTo', 1 );
2659        //   $.fancybox.getInstance( function() {
2660        //       console.info( this.currIndex );
2661        //   });
2662        // ======================================================
2663
2664        getInstance : function ( command ) {
2665            var instance = $('.fancybox-container:not(".fancybox-is-closing"):first').data( 'FancyBox' );
2666            var args     = Array.prototype.slice.call(arguments, 1);
2667
2668            if ( instance instanceof FancyBox ) {
2669
2670                if ( $.type( command ) === 'string' ) {
2671                    instance[ command ].apply( instance, args );
2672
2673                } else if ( $.type( command ) === 'function' ) {
2674                    command.apply( instance, args );
2675
2676                }
2677
2678                return instance;
2679            }
2680
2681            return false;
2682
2683        },
2684
2685
2686        // Create new instance
2687        // ===================
2688
2689        open : function ( items, opts, index ) {
2690            return new FancyBox( items, opts, index );
2691        },
2692
2693
2694        // Close current or all instances
2695        // ==============================
2696
2697        close : function ( all ) {
2698            var instance = this.getInstance();
2699
2700            if ( instance ) {
2701                instance.close();
2702
2703                // Try to find and close next instance
2704
2705                if ( all === true ) {
2706                    this.close();
2707                }
2708            }
2709
2710        },
2711
2712        // Close instances and unbind all events
2713        // ==============================
2714
2715        destroy : function() {
2716
2717            this.close( true );
2718
2719            $D.off( 'click.fb-start' );
2720
2721        },
2722
2723
2724        // Try to detect mobile devices
2725        // ============================
2726
2727        isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent),
2728
2729
2730        // Detect if 'translate3d' support is available
2731        // ============================================
2732
2733        use3d : (function() {
2734            var div = document.createElement('div');
2735
2736            return window.getComputedStyle && window.getComputedStyle( div ).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11);
2737        }()),
2738
2739
2740        // Helper function to get current visual state of an element
2741        // returns array[ top, left, horizontal-scale, vertical-scale, opacity ]
2742        // =====================================================================
2743
2744        getTranslate : function( $el ) {
2745            var matrix;
2746
2747            if ( !$el || !$el.length ) {
2748                return false;
2749            }
2750
2751            matrix  = $el.eq( 0 ).css('transform');
2752
2753            if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) {
2754                matrix = matrix.split('(')[1];
2755                matrix = matrix.split(')')[0];
2756                matrix = matrix.split(',');
2757            } else {
2758                matrix = [];
2759            }
2760
2761            if ( matrix.length ) {
2762
2763                // If IE
2764                if ( matrix.length > 10 ) {
2765                    matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ];
2766
2767                } else {
2768                    matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]];
2769                }
2770
2771                matrix = matrix.map(parseFloat);
2772
2773            } else {
2774                matrix = [ 0, 0, 1, 1 ];
2775
2776                var transRegex = /\.*translate\((.*)px,(.*)px\)/i;
2777                var transRez = transRegex.exec( $el.eq( 0 ).attr('style') );
2778
2779                if ( transRez ) {
2780                    matrix[ 0 ] = parseFloat( transRez[2] );
2781                    matrix[ 1 ] = parseFloat( transRez[1] );
2782                }
2783            }
2784
2785            return {
2786                top     : matrix[ 0 ],
2787                left    : matrix[ 1 ],
2788                scaleX  : matrix[ 2 ],
2789                scaleY  : matrix[ 3 ],
2790                opacity : parseFloat( $el.css('opacity') ),
2791                width   : $el.width(),
2792                height  : $el.height()
2793            };
2794
2795        },
2796
2797
2798        // Shortcut for setting "translate3d" properties for element
2799        // Can set be used to set opacity, too
2800        // ========================================================
2801
2802        setTranslate : function( $el, props ) {
2803            var str  = '';
2804            var css  = {};
2805
2806            if ( !$el || !props ) {
2807                return;
2808            }
2809
2810            if ( props.left !== undefined || props.top !== undefined ) {
2811                str = ( props.left === undefined ? $el.position().left : props.left )  + 'px, ' + ( props.top === undefined ? $el.position().top : props.top ) + 'px';
2812
2813                if ( this.use3d ) {
2814                    str = 'translate3d(' + str + ', 0px)';
2815
2816                } else {
2817                    str = 'translate(' + str + ')';
2818                }
2819            }
2820
2821            if ( props.scaleX !== undefined && props.scaleY !== undefined ) {
2822                str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')';
2823            }
2824
2825            if ( str.length ) {
2826                css.transform = str;
2827            }
2828
2829            if ( props.opacity !== undefined ) {
2830                css.opacity = props.opacity;
2831            }
2832
2833            if ( props.width !== undefined ) {
2834                css.width = props.width;
2835            }
2836
2837            if ( props.height !== undefined ) {
2838                css.height = props.height;
2839            }
2840
2841            return $el.css( css );
2842        },
2843
2844
2845        // Simple CSS transition handler
2846        // =============================
2847
2848        animate : function ( $el, to, duration, callback, leaveAnimationName ) {
2849            var event = transitionEnd || 'transitionend';
2850
2851            if ( $.isFunction( duration ) ) {
2852                callback = duration;
2853                duration = null;
2854            }
2855
2856            if ( !$.isPlainObject( to ) ) {
2857                $el.removeAttr('style');
2858            }
2859
2860            $el.on( event, function(e) {
2861
2862                // Skip events from child elements and z-index change
2863                if ( e && e.originalEvent && ( !$el.is( e.originalEvent.target ) || e.originalEvent.propertyName == 'z-index' ) ) {
2864                    return;
2865                }
2866
2867                $el.off( event );
2868
2869                if ( $.isPlainObject( to ) ) {
2870
2871                    if ( to.scaleX !== undefined && to.scaleY !== undefined ) {
2872                        $el.css( 'transition-duration', '0ms' );
2873
2874                        to.width  = Math.round( $el.width()  * to.scaleX );
2875                        to.height = Math.round( $el.height() * to.scaleY );
2876
2877                        to.scaleX = 1;
2878                        to.scaleY = 1;
2879
2880                        $.fancybox.setTranslate( $el, to );
2881                    }
2882
2883                } else if ( leaveAnimationName !== true ) {
2884                    $el.removeClass( to );
2885                }
2886
2887                if ( $.isFunction( callback ) ) {
2888                    callback( e );
2889                }
2890
2891            });
2892
2893            if ( $.isNumeric( duration ) ) {
2894                $el.css( 'transition-duration', duration + 'ms' );
2895            }
2896
2897            if ( $.isPlainObject( to ) ) {
2898                $.fancybox.setTranslate( $el, to );
2899
2900            } else {
2901                $el.addClass( to );
2902            }
2903
2904            $el.data("timer", setTimeout(function() {
2905                $el.trigger( 'transitionend' );
2906            }, duration + 16));
2907
2908        },
2909
2910        stop : function( $el ) {
2911            clearTimeout( $el.data("timer") );
2912
2913            $el.off( transitionEnd );
2914        }
2915
2916    };
2917
2918
2919    // Default click handler for "fancyboxed" links
2920    // ============================================
2921
2922    function _run( e ) {
2923        var target	= e.currentTarget,
2924            opts	= e.data ? e.data.options : {},
2925            items	= opts.selector ? $( opts.selector ) : ( e.data ? e.data.items : [] ),
2926            value	= $(target).attr( 'data-fancybox' ) || '',
2927            index	= 0,
2928            active  = $.fancybox.getInstance();
2929
2930        e.preventDefault();
2931        e.stopPropagation();
2932
2933        // Avoid opening multiple times
2934        if ( active && active.current.opts.$orig.is( target ) ) {
2935            return;
2936        }
2937
2938        // Get all related items and find index for clicked one
2939        if ( value ) {
2940            items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' );
2941            index = items.index( target );
2942
2943            // Sometimes current item can not be found
2944            // (for example, when slider clones items)
2945            if ( index < 0 ) {
2946                index = 0;
2947            }
2948
2949        } else {
2950            items = [ target ];
2951        }
2952
2953        $.fancybox.open( items, opts, index );
2954    }
2955
2956
2957    // Create a jQuery plugin
2958    // ======================
2959
2960    $.fn.fancybox = function (options) {
2961        var selector;
2962
2963        options  = options || {};
2964        selector = options.selector || false;
2965
2966        if ( selector ) {
2967
2968            $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, {
2969                options : options
2970            }, _run );
2971
2972        } else {
2973
2974            this.off( 'click.fb-start' ).on( 'click.fb-start', {
2975                items   : this,
2976                options : options
2977            }, _run);
2978
2979        }
2980
2981        return this;
2982    };
2983
2984
2985    // Self initializing plugin
2986    // ========================
2987
2988    $D.on( 'click.fb-start', '[data-fancybox]', _run );
2989
2990}( window, document, window.jQuery || jQuery ));
2991
2992// ==========================================================================
2993//
2994// Media
2995// Adds additional media type support
2996//
2997// ==========================================================================
2998;(function ($) {
2999
3000	'use strict';
3001
3002	// Formats matching url to final form
3003
3004	var format = function (url, rez, params) {
3005		if ( !url ) {
3006			return;
3007		}
3008
3009		params = params || '';
3010
3011		if ( $.type(params) === "object" ) {
3012			params = $.param(params, true);
3013		}
3014
3015		$.each(rez, function (key, value) {
3016			url = url.replace('$' + key, value || '');
3017		});
3018
3019		if (params.length) {
3020			url += (url.indexOf('?') > 0 ? '&' : '?') + params;
3021		}
3022
3023		return url;
3024	};
3025
3026	// Object containing properties for each media type
3027
3028	var defaults = {
3029		youtube : {
3030			matcher : /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i,
3031			params  : {
3032				autoplay : 1,
3033				autohide : 1,
3034				fs  : 1,
3035				rel : 0,
3036				hd  : 1,
3037				wmode : 'transparent',
3038				enablejsapi : 1,
3039				html5 : 1
3040			},
3041			paramPlace : 8,
3042			type  : 'iframe',
3043			url   : '//www.youtube.com/embed/$4',
3044			thumb : '//img.youtube.com/vi/$4/hqdefault.jpg'
3045		},
3046
3047		vimeo : {
3048			matcher : /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/,
3049			params  : {
3050				autoplay : 1,
3051				hd : 1,
3052				show_title    : 1,
3053				show_byline   : 1,
3054				show_portrait : 0,
3055				fullscreen    : 1,
3056				api : 1
3057			},
3058			paramPlace : 3,
3059			type : 'iframe',
3060			url : '//player.vimeo.com/video/$2'
3061		},
3062
3063		metacafe : {
3064			matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/,
3065			type    : 'iframe',
3066			url     : '//www.metacafe.com/embed/$1/?ap=1'
3067		},
3068
3069		dailymotion : {
3070			matcher : /dailymotion.com\/video\/(.*)\/?(.*)/,
3071			params : {
3072				additionalInfos : 0,
3073				autoStart : 1
3074			},
3075			type : 'iframe',
3076			url  : '//www.dailymotion.com/embed/video/$1'
3077		},
3078
3079		vine : {
3080			matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/,
3081			type    : 'iframe',
3082			url     : '//vine.co/v/$1/embed/simple'
3083		},
3084
3085		instagram : {
3086			matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,
3087			type    : 'image',
3088			url     : '//$1/p/$2/media/?size=l'
3089		},
3090
3091		// Examples:
3092		// http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16
3093		// https://www.google.com/maps/@37.7852006,-122.4146355,14.65z
3094		// https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572
3095		gmap_place : {
3096			matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,
3097			type    : 'iframe',
3098			url     : function (rez) {
3099				return '//maps.google.' + rez[2] + '/?ll=' + ( rez[9] ? rez[9] + '&z=' + Math.floor(  rez[10]  ) + ( rez[12] ? rez[12].replace(/^\//, "&") : '' )  : rez[12] ) + '&output=' + ( rez[12] && rez[12].indexOf('layer=c') > 0 ? 'svembed' : 'embed' );
3100			}
3101		},
3102
3103		// Examples:
3104		// https://www.google.com/maps/search/Empire+State+Building/
3105		// https://www.google.com/maps/search/?api=1&query=centurylink+field
3106		// https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393
3107		gmap_search : {
3108			matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,
3109			type    : 'iframe',
3110			url     : function (rez) {
3111				return '//maps.google.' + rez[2] + '/maps?q=' + rez[5].replace('query=', 'q=').replace('api=1', '') + '&output=embed';
3112			}
3113		}
3114	};
3115
3116	$(document).on('onInit.fb', function (e, instance) {
3117
3118		$.each(instance.group, function( i, item ) {
3119
3120			var url	 = item.src || '',
3121				type = false,
3122				media,
3123				thumb,
3124				rez,
3125				params,
3126				urlParams,
3127				o,
3128				provider;
3129
3130			// Skip items that already have content type
3131			if ( item.type ) {
3132				return;
3133			}
3134
3135			media = $.extend( true, {}, defaults, item.opts.media );
3136
3137			// Look for any matching media type
3138			$.each(media, function ( n, el ) {
3139				rez = url.match(el.matcher);
3140				o   = {};
3141				provider = n;
3142
3143				if (!rez) {
3144					return;
3145				}
3146
3147				type = el.type;
3148
3149				if ( el.paramPlace && rez[ el.paramPlace ] ) {
3150					urlParams = rez[ el.paramPlace ];
3151
3152					if ( urlParams[ 0 ] == '?' ) {
3153						urlParams = urlParams.substring(1);
3154					}
3155
3156					urlParams = urlParams.split('&');
3157
3158					for ( var m = 0; m < urlParams.length; ++m ) {
3159						var p = urlParams[ m ].split('=', 2);
3160
3161						if ( p.length == 2 ) {
3162							o[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") );
3163						}
3164					}
3165				}
3166
3167				params = $.extend( true, {}, el.params, item.opts[ n ], o );
3168
3169				url   = $.type(el.url) === "function" ? el.url.call(this, rez, params, item) : format(el.url, rez, params);
3170				thumb = $.type(el.thumb) === "function" ? el.thumb.call(this, rez, params, item) : format(el.thumb, rez);
3171
3172				if ( provider === 'vimeo' ) {
3173					url = url.replace('&%23', '#');
3174				}
3175
3176				return false;
3177			});
3178
3179			// If it is found, then change content type and update the url
3180
3181			if ( type ) {
3182				item.src  = url;
3183				item.type = type;
3184
3185				if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) {
3186					item.opts.thumb = thumb;
3187				}
3188
3189				if ( type === 'iframe' ) {
3190					$.extend(true, item.opts, {
3191						iframe : {
3192							preload : false,
3193							attr : {
3194								scrolling : "no"
3195							}
3196						}
3197					});
3198
3199					item.contentProvider = provider;
3200
3201					item.opts.slideClass += ' fancybox-slide--' + ( provider == 'gmap_place' || provider == 'gmap_search' ? 'map' : 'video' );
3202				}
3203
3204			} else {
3205
3206				// If no content type is found, then set it to `image` as fallback
3207				item.type = 'image';
3208			}
3209
3210		});
3211
3212	});
3213
3214}(window.jQuery));
3215
3216// ==========================================================================
3217//
3218// Guestures
3219// Adds touch guestures, handles click and tap events
3220//
3221// ==========================================================================
3222;(function (window, document, $) {
3223	'use strict';
3224
3225	var requestAFrame = (function () {
3226        return window.requestAnimationFrame ||
3227                window.webkitRequestAnimationFrame ||
3228                window.mozRequestAnimationFrame ||
3229                window.oRequestAnimationFrame ||
3230                // if all else fails, use setTimeout
3231                function (callback) {
3232                    return window.setTimeout(callback, 1000 / 60);
3233                };
3234    })();
3235
3236
3237    var cancelAFrame = (function () {
3238        return window.cancelAnimationFrame ||
3239                window.webkitCancelAnimationFrame ||
3240                window.mozCancelAnimationFrame ||
3241                window.oCancelAnimationFrame ||
3242                function (id) {
3243                    window.clearTimeout(id);
3244                };
3245    })();
3246
3247
3248	var pointers = function( e ) {
3249		var result = [];
3250
3251		e = e.originalEvent || e || window.e;
3252		e = e.touches && e.touches.length ? e.touches : ( e.changedTouches && e.changedTouches.length ? e.changedTouches : [ e ] );
3253
3254		for ( var key in e ) {
3255
3256			if ( e[ key ].pageX ) {
3257				result.push( { x : e[ key ].pageX, y : e[ key ].pageY } );
3258
3259			} else if ( e[ key ].clientX ) {
3260				result.push( { x : e[ key ].clientX, y : e[ key ].clientY } );
3261			}
3262		}
3263
3264		return result;
3265	};
3266
3267	var distance = function( point2, point1, what ) {
3268		if ( !point1 || !point2 ) {
3269			return 0;
3270		}
3271
3272		if ( what === 'x' ) {
3273			return point2.x - point1.x;
3274
3275		} else if ( what === 'y' ) {
3276			return point2.y - point1.y;
3277		}
3278
3279		return Math.sqrt( Math.pow( point2.x - point1.x, 2 ) + Math.pow( point2.y - point1.y, 2 ) );
3280	};
3281
3282	var isClickable = function( $el ) {
3283
3284		if ( $el.is('a,button,input,select,textarea,label') || $.isFunction( $el.get(0).onclick ) || $el.data('selectable') ) {
3285			return true;
3286		}
3287
3288		// Check for attributes like data-fancybox-next or data-fancybox-close
3289		for ( var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++ ) {
3290            if ( atts[i].nodeName.substr(0, 14) === 'data-fancybox-' ) {
3291                return true;
3292            }
3293        }
3294
3295	 	return false;
3296	};
3297
3298	var hasScrollbars = function( el ) {
3299		var overflowY = window.getComputedStyle( el )['overflow-y'];
3300		var overflowX = window.getComputedStyle( el )['overflow-x'];
3301
3302		var vertical   = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight;
3303		var horizontal = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth;
3304
3305		return vertical || horizontal;
3306	};
3307
3308	var isScrollable = function ( $el ) {
3309		var rez = false;
3310
3311		while ( true ) {
3312			rez	= hasScrollbars( $el.get(0) );
3313
3314			if ( rez ) {
3315				break;
3316			}
3317
3318			$el = $el.parent();
3319
3320			if ( !$el.length || $el.hasClass( 'fancybox-stage' ) || $el.is( 'body' ) ) {
3321				break;
3322			}
3323		}
3324
3325		return rez;
3326	};
3327
3328
3329	var Guestures = function ( instance ) {
3330		var self = this;
3331
3332		self.instance = instance;
3333
3334		self.$bg        = instance.$refs.bg;
3335		self.$stage     = instance.$refs.stage;
3336		self.$container = instance.$refs.container;
3337
3338		self.destroy();
3339
3340		self.$container.on( 'touchstart.fb.touch mousedown.fb.touch', $.proxy(self, 'ontouchstart') );
3341	};
3342
3343	Guestures.prototype.destroy = function() {
3344		this.$container.off( '.fb.touch' );
3345	};
3346
3347	Guestures.prototype.ontouchstart = function( e ) {
3348		var self = this;
3349
3350		var $target  = $( e.target );
3351		var instance = self.instance;
3352		var current  = instance.current;
3353		var $content = current.$content;
3354
3355		var isTouchDevice = ( e.type == 'touchstart' );
3356
3357		// Do not respond to both events
3358		if ( isTouchDevice ) {
3359	        self.$container.off( 'mousedown.fb.touch' );
3360	    }
3361
3362		// Ignore clicks while zooming or closing
3363		if ( !current || self.instance.isAnimating || self.instance.isClosing ) {
3364			e.stopPropagation();
3365			e.preventDefault();
3366
3367			return;
3368		}
3369
3370		// Ignore right click
3371		if ( e.originalEvent && e.originalEvent.button == 2 ) {
3372			return;
3373		}
3374
3375		// Ignore taping on links, buttons, input elements
3376		if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) {
3377			return;
3378		}
3379
3380		// Ignore clicks on the scrollbar
3381		if ( e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) {
3382			return;
3383		}
3384
3385		self.startPoints = pointers( e );
3386
3387		// Prevent zooming if already swiping
3388		if ( !self.startPoints || ( self.startPoints.length > 1 && instance.isSliding ) ) {
3389			return;
3390		}
3391
3392		self.$target  = $target;
3393		self.$content = $content;
3394		self.canTap   = true;
3395
3396		$(document).off( '.fb.touch' );
3397
3398		$(document).on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch',  $.proxy(self, "ontouchend"));
3399		$(document).on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch',  $.proxy(self, "ontouchmove"));
3400
3401		if ( !(instance.current.opts.touch || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) {
3402
3403			// Prevent ghosting
3404			if ( $target.is('img') ) {
3405				e.preventDefault();
3406			}
3407
3408			return;
3409		}
3410
3411		e.stopPropagation();
3412
3413		if ( !( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) ) {
3414			e.preventDefault();
3415		}
3416
3417		self.canvasWidth  = Math.round( current.$slide[0].clientWidth );
3418		self.canvasHeight = Math.round( current.$slide[0].clientHeight );
3419
3420		self.startTime = new Date().getTime();
3421		self.distanceX = self.distanceY = self.distance = 0;
3422
3423		self.isPanning = false;
3424		self.isSwiping = false;
3425		self.isZooming = false;
3426
3427		self.sliderStartPos  = self.sliderLastPos || { top: 0, left: 0 };
3428		self.contentStartPos = $.fancybox.getTranslate( self.$content );
3429		self.contentLastPos  = null;
3430
3431		if ( self.startPoints.length === 1 && !self.isZooming ) {
3432			self.canTap = !instance.isSliding;
3433
3434			if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) {
3435
3436				$.fancybox.stop( self.$content );
3437
3438				self.$content.css( 'transition-duration', '0ms' );
3439
3440				self.isPanning = true;
3441
3442			} else {
3443
3444				self.isSwiping = true;
3445			}
3446
3447			self.$container.addClass('fancybox-controls--isGrabbing');
3448		}
3449
3450		if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) {
3451			self.isZooming = true;
3452
3453			self.isSwiping = false;
3454			self.isPanning = false;
3455
3456			$.fancybox.stop( self.$content );
3457
3458			self.$content.css( 'transition-duration', '0ms' );
3459
3460			self.centerPointStartX = ( ( self.startPoints[0].x + self.startPoints[1].x ) * 0.5 ) - $(window).scrollLeft();
3461			self.centerPointStartY = ( ( self.startPoints[0].y + self.startPoints[1].y ) * 0.5 ) - $(window).scrollTop();
3462
3463			self.percentageOfImageAtPinchPointX = ( self.centerPointStartX - self.contentStartPos.left ) / self.contentStartPos.width;
3464			self.percentageOfImageAtPinchPointY = ( self.centerPointStartY - self.contentStartPos.top  ) / self.contentStartPos.height;
3465
3466			self.startDistanceBetweenFingers = distance( self.startPoints[0], self.startPoints[1] );
3467		}
3468
3469	};
3470
3471	Guestures.prototype.ontouchmove = function( e ) {
3472
3473		var self = this;
3474
3475		self.newPoints = pointers( e );
3476
3477		if ( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) {
3478			e.stopPropagation();
3479
3480			self.canTap = false;
3481
3482			return;
3483		}
3484
3485		if ( !( self.instance.current.opts.touch || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) {
3486			return;
3487		}
3488
3489		self.distanceX = distance( self.newPoints[0], self.startPoints[0], 'x' );
3490		self.distanceY = distance( self.newPoints[0], self.startPoints[0], 'y' );
3491
3492		self.distance = distance( self.newPoints[0], self.startPoints[0] );
3493
3494		// Skip false ontouchmove events (Chrome)
3495		if ( self.distance > 0 ) {
3496
3497			if ( !( self.$target.is( self.$stage ) || self.$stage.find( self.$target ).length ) ) {
3498				return;
3499			}
3500
3501			e.stopPropagation();
3502			e.preventDefault();
3503
3504			if ( self.isSwiping ) {
3505				self.onSwipe();
3506
3507			} else if ( self.isPanning ) {
3508				self.onPan();
3509
3510			} else if ( self.isZooming ) {
3511				self.onZoom();
3512			}
3513
3514		}
3515
3516	};
3517
3518	Guestures.prototype.onSwipe = function() {
3519
3520		var self = this;
3521
3522		var swiping = self.isSwiping;
3523		var left    = self.sliderStartPos.left || 0;
3524		var angle;
3525
3526		if ( swiping === true ) {
3527
3528			if ( Math.abs( self.distance ) > 10 )  {
3529
3530				self.canTap = false;
3531
3532				if ( self.instance.group.length < 2 && self.instance.opts.touch.vertical ) {
3533					self.isSwiping  = 'y';
3534
3535				} else if ( self.instance.isSliding || self.instance.opts.touch.vertical === false || ( self.instance.opts.touch.vertical === 'auto' && $( window ).width() > 800 ) ) {
3536					self.isSwiping  = 'x';
3537
3538				} else {
3539					angle = Math.abs( Math.atan2( self.distanceY, self.distanceX ) * 180 / Math.PI );
3540
3541					self.isSwiping = ( angle > 45 && angle < 135 ) ? 'y' : 'x';
3542				}
3543
3544				self.instance.isSliding = self.isSwiping;
3545
3546				// Reset points to avoid jumping, because we dropped first swipes to calculate the angle
3547				self.startPoints = self.newPoints;
3548
3549				$.each(self.instance.slides, function( index, slide ) {
3550					$.fancybox.stop( slide.$slide );
3551
3552					slide.$slide.css( 'transition-duration', '0ms' );
3553
3554					slide.inTransition = false;
3555
3556					if ( slide.pos === self.instance.current.pos ) {
3557						self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left;
3558					}
3559				});
3560
3561				//self.instance.current.isMoved = true;
3562
3563				// Stop slideshow
3564				if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) {
3565					self.instance.SlideShow.stop();
3566				}
3567			}
3568
3569		} else {
3570
3571			if ( swiping == 'x' ) {
3572
3573				// Sticky edges
3574				if ( self.distanceX > 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === 0 && !self.instance.current.opts.loop ) ) ) {
3575					left = left + Math.pow( self.distanceX, 0.8 );
3576
3577				} else if ( self.distanceX < 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop ) ) ) {
3578					left = left - Math.pow( -self.distanceX, 0.8 );
3579
3580				} else {
3581					left = left + self.distanceX;
3582				}
3583
3584			}
3585
3586			self.sliderLastPos = {
3587				top  : swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY,
3588				left : left
3589			};
3590
3591			if ( self.requestId ) {
3592				cancelAFrame( self.requestId );
3593
3594				self.requestId = null;
3595			}
3596
3597			self.requestId = requestAFrame(function() {
3598
3599				if ( self.sliderLastPos ) {
3600					$.each(self.instance.slides, function( index, slide ) {
3601						var pos = slide.pos - self.instance.currPos;
3602
3603						$.fancybox.setTranslate( slide.$slide, {
3604							top  : self.sliderLastPos.top,
3605							left : self.sliderLastPos.left + ( pos * self.canvasWidth ) + ( pos * slide.opts.gutter )
3606						});
3607					});
3608
3609					self.$container.addClass( 'fancybox-is-sliding' );
3610				}
3611
3612			});
3613
3614		}
3615
3616	};
3617
3618	Guestures.prototype.onPan = function() {
3619
3620		var self = this;
3621
3622		var newOffsetX, newOffsetY, newPos;
3623
3624		self.canTap = false;
3625
3626		if ( self.contentStartPos.width > self.canvasWidth ) {
3627			newOffsetX = self.contentStartPos.left + self.distanceX;
3628
3629		} else {
3630			newOffsetX = self.contentStartPos.left;
3631		}
3632
3633		newOffsetY = self.contentStartPos.top + self.distanceY;
3634
3635		newPos = self.limitMovement( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height );
3636
3637		newPos.scaleX = self.contentStartPos.scaleX;
3638		newPos.scaleY = self.contentStartPos.scaleY;
3639
3640		self.contentLastPos = newPos;
3641
3642		if ( self.requestId ) {
3643			cancelAFrame( self.requestId );
3644
3645			self.requestId = null;
3646		}
3647
3648		self.requestId = requestAFrame(function() {
3649			$.fancybox.setTranslate( self.$content, self.contentLastPos );
3650		});
3651	};
3652
3653	// Make panning sticky to the edges
3654	Guestures.prototype.limitMovement = function( newOffsetX, newOffsetY, newWidth, newHeight ) {
3655
3656		var self = this;
3657
3658		var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY;
3659
3660		var canvasWidth  = self.canvasWidth;
3661		var canvasHeight = self.canvasHeight;
3662
3663		var currentOffsetX = self.contentStartPos.left;
3664		var currentOffsetY = self.contentStartPos.top;
3665
3666		var distanceX = self.distanceX;
3667		var distanceY = self.distanceY;
3668
3669		// Slow down proportionally to traveled distance
3670
3671		minTranslateX = Math.max(0, canvasWidth  * 0.5 - newWidth  * 0.5 );
3672		minTranslateY = Math.max(0, canvasHeight * 0.5 - newHeight * 0.5 );
3673
3674		maxTranslateX = Math.min( canvasWidth  - newWidth,  canvasWidth  * 0.5 - newWidth  * 0.5 );
3675		maxTranslateY = Math.min( canvasHeight - newHeight, canvasHeight * 0.5 - newHeight * 0.5 );
3676
3677		if ( newWidth > canvasWidth ) {
3678
3679			//   ->
3680			if ( distanceX > 0 && newOffsetX > minTranslateX ) {
3681				newOffsetX = minTranslateX - 1 + Math.pow( -minTranslateX + currentOffsetX + distanceX, 0.8 ) || 0;
3682			}
3683
3684			//    <-
3685			if ( distanceX  < 0 && newOffsetX < maxTranslateX ) {
3686				newOffsetX = maxTranslateX + 1 - Math.pow( maxTranslateX - currentOffsetX - distanceX, 0.8 ) || 0;
3687			}
3688
3689		}
3690
3691		if ( newHeight > canvasHeight ) {
3692
3693			//   \/
3694			if ( distanceY > 0 && newOffsetY > minTranslateY ) {
3695				newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8 ) || 0;
3696			}
3697
3698			//   /\
3699			if ( distanceY < 0 && newOffsetY < maxTranslateY ) {
3700				newOffsetY = maxTranslateY + 1 - Math.pow ( maxTranslateY - currentOffsetY - distanceY, 0.8 ) || 0;
3701			}
3702
3703		}
3704
3705		return {
3706			top  : newOffsetY,
3707			left : newOffsetX
3708		};
3709
3710	};
3711
3712
3713	Guestures.prototype.limitPosition = function( newOffsetX, newOffsetY, newWidth, newHeight ) {
3714
3715		var self = this;
3716
3717		var canvasWidth  = self.canvasWidth;
3718		var canvasHeight = self.canvasHeight;
3719
3720		if ( newWidth > canvasWidth ) {
3721			newOffsetX = newOffsetX > 0 ? 0 : newOffsetX;
3722			newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX;
3723
3724		} else {
3725
3726			// Center horizontally
3727			newOffsetX = Math.max( 0, canvasWidth / 2 - newWidth / 2 );
3728
3729		}
3730
3731		if ( newHeight > canvasHeight ) {
3732			newOffsetY = newOffsetY > 0 ? 0 : newOffsetY;
3733			newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY;
3734
3735		} else {
3736
3737			// Center vertically
3738			newOffsetY = Math.max( 0, canvasHeight / 2 - newHeight / 2 );
3739
3740		}
3741
3742		return {
3743			top  : newOffsetY,
3744			left : newOffsetX
3745		};
3746
3747	};
3748
3749	Guestures.prototype.onZoom = function() {
3750
3751		var self = this;
3752
3753		// Calculate current distance between points to get pinch ratio and new width and height
3754
3755		var currentWidth  = self.contentStartPos.width;
3756		var currentHeight = self.contentStartPos.height;
3757
3758		var currentOffsetX = self.contentStartPos.left;
3759		var currentOffsetY = self.contentStartPos.top;
3760
3761		var endDistanceBetweenFingers = distance( self.newPoints[0], self.newPoints[1] );
3762
3763		var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers;
3764
3765		var newWidth  = Math.floor( currentWidth  * pinchRatio );
3766		var newHeight = Math.floor( currentHeight * pinchRatio );
3767
3768		// This is the translation due to pinch-zooming
3769		var translateFromZoomingX = (currentWidth  - newWidth)  * self.percentageOfImageAtPinchPointX;
3770		var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY;
3771
3772		//Point between the two touches
3773
3774		var centerPointEndX = ((self.newPoints[0].x + self.newPoints[1].x) / 2) - $(window).scrollLeft();
3775		var centerPointEndY = ((self.newPoints[0].y + self.newPoints[1].y) / 2) - $(window).scrollTop();
3776
3777		// And this is the translation due to translation of the centerpoint
3778		// between the two fingers
3779
3780		var translateFromTranslatingX = centerPointEndX - self.centerPointStartX;
3781		var translateFromTranslatingY = centerPointEndY - self.centerPointStartY;
3782
3783		// The new offset is the old/current one plus the total translation
3784
3785		var newOffsetX = currentOffsetX + ( translateFromZoomingX + translateFromTranslatingX );
3786		var newOffsetY = currentOffsetY + ( translateFromZoomingY + translateFromTranslatingY );
3787
3788		var newPos = {
3789			top    : newOffsetY,
3790			left   : newOffsetX,
3791			scaleX : self.contentStartPos.scaleX * pinchRatio,
3792			scaleY : self.contentStartPos.scaleY * pinchRatio
3793		};
3794
3795		self.canTap = false;
3796
3797		self.newWidth  = newWidth;
3798		self.newHeight = newHeight;
3799
3800		self.contentLastPos = newPos;
3801
3802		if ( self.requestId ) {
3803			cancelAFrame( self.requestId );
3804
3805			self.requestId = null;
3806		}
3807
3808		self.requestId = requestAFrame(function() {
3809			$.fancybox.setTranslate( self.$content, self.contentLastPos );
3810		});
3811
3812	};
3813
3814	Guestures.prototype.ontouchend = function( e ) {
3815
3816		var self = this;
3817		var dMs  = Math.max( (new Date().getTime() ) - self.startTime, 1);
3818
3819		var swiping = self.isSwiping;
3820		var panning = self.isPanning;
3821		var zooming = self.isZooming;
3822
3823		self.endPoints = pointers( e );
3824
3825		self.$container.removeClass( 'fancybox-controls--isGrabbing' );
3826
3827		$(document).off( '.fb.touch' );
3828
3829		if ( self.requestId ) {
3830			cancelAFrame( self.requestId );
3831
3832			self.requestId = null;
3833		}
3834
3835		self.isSwiping = false;
3836		self.isPanning = false;
3837		self.isZooming = false;
3838
3839		if ( self.canTap )  {
3840			return self.onTap( e );
3841		}
3842
3843		self.speed = 366;
3844
3845		// Speed in px/ms
3846		self.velocityX = self.distanceX / dMs * 0.5;
3847		self.velocityY = self.distanceY / dMs * 0.5;
3848
3849		self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) );
3850
3851		if ( panning ) {
3852			self.endPanning();
3853
3854		} else if ( zooming ) {
3855			self.endZooming();
3856
3857		} else {
3858			self.endSwiping( swiping );
3859		}
3860
3861		return;
3862	};
3863
3864	Guestures.prototype.endSwiping = function( swiping ) {
3865
3866		var self = this;
3867		var ret = false;
3868
3869		self.instance.isSliding = false;
3870		self.sliderLastPos      = null;
3871
3872		// Close if swiped vertically / navigate if horizontally
3873		if ( swiping == 'y' && Math.abs( self.distanceY ) > 50 ) {
3874
3875			// Continue vertical movement
3876			$.fancybox.animate( self.instance.current.$slide, {
3877				top     : self.sliderStartPos.top + self.distanceY + ( self.velocityY * 150 ),
3878				opacity : 0
3879			}, 150 );
3880
3881			ret = self.instance.close( true, 300 );
3882
3883		} else if ( swiping == 'x' && self.distanceX > 50 && self.instance.group.length > 1 ) {
3884			ret = self.instance.previous( self.speedX );
3885
3886		} else if ( swiping == 'x' && self.distanceX < -50  && self.instance.group.length > 1 ) {
3887			ret = self.instance.next( self.speedX );
3888		}
3889
3890		if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) {
3891			self.instance.jumpTo( self.instance.current.index, 150 );
3892		}
3893
3894		self.$container.removeClass( 'fancybox-is-sliding' );
3895
3896	};
3897
3898	// Limit panning from edges
3899	// ========================
3900
3901	Guestures.prototype.endPanning = function() {
3902
3903		var self = this;
3904		var newOffsetX, newOffsetY, newPos;
3905
3906		if ( !self.contentLastPos ) {
3907			return;
3908		}
3909
3910		if ( self.instance.current.opts.touch.momentum === false ) {
3911			newOffsetX = self.contentLastPos.left;
3912			newOffsetY = self.contentLastPos.top;
3913
3914		} else {
3915
3916			// Continue movement
3917			newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed );
3918			newOffsetY = self.contentLastPos.top  + ( self.velocityY * self.speed );
3919		}
3920
3921		newPos = self.limitPosition( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height );
3922
3923		 newPos.width  = self.contentStartPos.width;
3924		 newPos.height = self.contentStartPos.height;
3925
3926		$.fancybox.animate( self.$content, newPos, 330 );
3927	};
3928
3929
3930	Guestures.prototype.endZooming = function() {
3931
3932		var self = this;
3933
3934		var current = self.instance.current;
3935
3936		var newOffsetX, newOffsetY, newPos, reset;
3937
3938		var newWidth  = self.newWidth;
3939		var newHeight = self.newHeight;
3940
3941		if ( !self.contentLastPos ) {
3942			return;
3943		}
3944
3945		newOffsetX = self.contentLastPos.left;
3946		newOffsetY = self.contentLastPos.top;
3947
3948		reset = {
3949		   	top    : newOffsetY,
3950		   	left   : newOffsetX,
3951		   	width  : newWidth,
3952		   	height : newHeight,
3953			scaleX : 1,
3954			scaleY : 1
3955	   };
3956
3957	   // Reset scalex/scaleY values; this helps for perfomance and does not break animation
3958	   $.fancybox.setTranslate( self.$content, reset );
3959
3960		if ( newWidth < self.canvasWidth && newHeight < self.canvasHeight ) {
3961			self.instance.scaleToFit( 150 );
3962
3963		} else if ( newWidth > current.width || newHeight > current.height ) {
3964			self.instance.scaleToActual( self.centerPointStartX, self.centerPointStartY, 150 );
3965
3966		} else {
3967
3968			newPos = self.limitPosition( newOffsetX, newOffsetY, newWidth, newHeight );
3969
3970			// Switch from scale() to width/height or animation will not work correctly
3971			$.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) );
3972
3973			$.fancybox.animate( self.$content, newPos, 150 );
3974		}
3975
3976	};
3977
3978	Guestures.prototype.onTap = function(e) {
3979		var self    = this;
3980		var $target = $( e.target );
3981
3982		var instance = self.instance;
3983		var current  = instance.current;
3984
3985		var endPoints = ( e && pointers( e ) ) || self.startPoints;
3986
3987		var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0;
3988		var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top  : 0;
3989
3990		var where;
3991
3992		var process = function ( prefix ) {
3993
3994			var action = current.opts[ prefix ];
3995
3996			if ( $.isFunction( action ) ) {
3997				action = action.apply( instance, [ current, e ] );
3998			}
3999
4000			if ( !action) {
4001				return;
4002			}
4003
4004			switch ( action ) {
4005
4006				case "close" :
4007
4008					instance.close( self.startEvent );
4009
4010				break;
4011
4012				case "toggleControls" :
4013
4014					instance.toggleControls( true );
4015
4016				break;
4017
4018				case "next" :
4019
4020					instance.next();
4021
4022				break;
4023
4024				case "nextOrClose" :
4025
4026					if ( instance.group.length > 1 ) {
4027						instance.next();
4028
4029					} else {
4030						instance.close( self.startEvent );
4031					}
4032
4033				break;
4034
4035				case "zoom" :
4036
4037					if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) {
4038
4039						if ( instance.canPan() ) {
4040							instance.scaleToFit();
4041
4042						} else if ( instance.isScaledDown() ) {
4043							instance.scaleToActual( tapX, tapY );
4044
4045						} else if ( instance.group.length < 2 ) {
4046							instance.close( self.startEvent );
4047						}
4048					}
4049
4050				break;
4051			}
4052
4053		};
4054
4055		// Ignore right click
4056		if ( e.originalEvent && e.originalEvent.button == 2 ) {
4057			return;
4058		}
4059
4060		// Skip if current slide is not in the center
4061		if ( instance.isSliding ) {
4062			return;
4063		}
4064
4065		// Skip if clicked on the scrollbar
4066		if ( tapX > $target[0].clientWidth + $target.offset().left ) {
4067			return;
4068		}
4069
4070		// Check where is clicked
4071		if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) {
4072			where = 'Outside';
4073
4074		} else if ( $target.is( '.fancybox-slide' ) ) {
4075			where = 'Slide';
4076
4077		} else if  ( instance.current.$content && instance.current.$content.has( e.target ).length ) {
4078		 	where = 'Content';
4079
4080		} else {
4081			return;
4082		}
4083
4084		// Check if this is a double tap
4085		if ( self.tapped ) {
4086
4087			// Stop previously created single tap
4088			clearTimeout( self.tapped );
4089			self.tapped = null;
4090
4091			// Skip if distance between taps is too big
4092			if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 || instance.isSliding ) {
4093				return this;
4094			}
4095
4096			// OK, now we assume that this is a double-tap
4097			process( 'dblclick' + where );
4098
4099		} else {
4100
4101			// Single tap will be processed if user has not clicked second time within 300ms
4102			// or there is no need to wait for double-tap
4103			self.tapX = tapX;
4104			self.tapY = tapY;
4105
4106			if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) {
4107				self.tapped = setTimeout(function() {
4108					self.tapped = null;
4109
4110					process( 'click' + where );
4111
4112				}, 300);
4113
4114			} else {
4115				process( 'click' + where );
4116			}
4117
4118		}
4119
4120		return this;
4121	};
4122
4123	$(document).on('onActivate.fb', function (e, instance) {
4124		if ( instance && !instance.Guestures ) {
4125			instance.Guestures = new Guestures( instance );
4126		}
4127	});
4128
4129	$(document).on('beforeClose.fb', function (e, instance) {
4130		if ( instance && instance.Guestures ) {
4131			instance.Guestures.destroy();
4132		}
4133	});
4134
4135
4136}(window, document, window.jQuery));
4137
4138// ==========================================================================
4139//
4140// SlideShow
4141// Enables slideshow functionality
4142//
4143// Example of usage:
4144// $.fancybox.getInstance().SlideShow.start()
4145//
4146// ==========================================================================
4147;(function (document, $) {
4148	'use strict';
4149
4150	var SlideShow = function( instance ) {
4151		this.instance = instance;
4152		this.init();
4153	};
4154
4155	$.extend( SlideShow.prototype, {
4156		timer    : null,
4157		isActive : false,
4158		$button  : null,
4159		speed    : 3000,
4160
4161		init : function() {
4162			var self = this;
4163
4164			self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function() {
4165				self.toggle();
4166			});
4167
4168			if ( self.instance.group.length < 2 || !self.instance.group[ self.instance.currIndex ].opts.slideShow ) {
4169				self.$button.hide();
4170			}
4171		},
4172
4173		set : function() {
4174			var self = this;
4175
4176			// Check if reached last element
4177			if ( self.instance && self.instance.current && (self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1 )) {
4178				self.timer = setTimeout(function() {
4179					self.instance.next();
4180
4181				}, self.instance.current.opts.slideShow.speed || self.speed);
4182
4183			} else {
4184				self.stop();
4185				self.instance.idleSecondsCounter = 0;
4186				self.instance.showControls();
4187			}
4188
4189		},
4190
4191		clear : function() {
4192			var self = this;
4193
4194			clearTimeout( self.timer );
4195
4196			self.timer = null;
4197		},
4198
4199		start : function() {
4200			var self = this;
4201			var current = self.instance.current;
4202
4203			if ( self.instance && current && ( current.opts.loop || current.index < self.instance.group.length - 1 )) {
4204
4205				self.isActive = true;
4206
4207				self.$button
4208					.attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_STOP )
4209					.addClass( 'fancybox-button--pause' );
4210
4211				if ( current.isComplete ) {
4212					self.set();
4213				}
4214			}
4215		},
4216
4217		stop : function() {
4218			var self = this;
4219			var current = self.instance.current;
4220
4221			self.clear();
4222
4223			self.$button
4224				.attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_START )
4225				.removeClass( 'fancybox-button--pause' );
4226
4227			self.isActive = false;
4228		},
4229
4230		toggle : function() {
4231			var self = this;
4232
4233			if ( self.isActive ) {
4234				self.stop();
4235
4236			} else {
4237				self.start();
4238			}
4239		}
4240
4241	});
4242
4243	$(document).on({
4244		'onInit.fb' : function(e, instance) {
4245			if ( instance && !instance.SlideShow ) {
4246				instance.SlideShow = new SlideShow( instance );
4247			}
4248		},
4249
4250		'beforeShow.fb' : function(e, instance, current, firstRun) {
4251			var SlideShow = instance && instance.SlideShow;
4252
4253			if ( firstRun ) {
4254
4255				if ( SlideShow && current.opts.slideShow.autoStart ) {
4256					SlideShow.start();
4257				}
4258
4259			} else if ( SlideShow && SlideShow.isActive )  {
4260				SlideShow.clear();
4261			}
4262		},
4263
4264		'afterShow.fb' : function(e, instance, current) {
4265			var SlideShow = instance && instance.SlideShow;
4266
4267			if ( SlideShow && SlideShow.isActive ) {
4268				SlideShow.set();
4269			}
4270		},
4271
4272		'afterKeydown.fb' : function(e, instance, current, keypress, keycode) {
4273			var SlideShow = instance && instance.SlideShow;
4274
4275			// "P" or Spacebar
4276			if ( SlideShow && current.opts.slideShow && ( keycode === 80 || keycode === 32 ) && !$(document.activeElement).is( 'button,a,input' ) ) {
4277				keypress.preventDefault();
4278
4279				SlideShow.toggle();
4280			}
4281		},
4282
4283		'beforeClose.fb onDeactivate.fb' : function(e, instance) {
4284			var SlideShow = instance && instance.SlideShow;
4285
4286			if ( SlideShow ) {
4287				SlideShow.stop();
4288			}
4289		}
4290	});
4291
4292	// Page Visibility API to pause slideshow when window is not active
4293	$(document).on("visibilitychange", function() {
4294		var instance  = $.fancybox.getInstance();
4295		var SlideShow = instance && instance.SlideShow;
4296
4297		if ( SlideShow && SlideShow.isActive ) {
4298			if ( document.hidden ) {
4299				SlideShow.clear();
4300
4301			} else {
4302				SlideShow.set();
4303			}
4304		}
4305	});
4306
4307}(document, window.jQuery));
4308
4309// ==========================================================================
4310//
4311// FullScreen
4312// Adds fullscreen functionality
4313//
4314// ==========================================================================
4315;(function (document, $) {
4316	'use strict';
4317
4318	// Collection of methods supported by user browser
4319	var fn = (function () {
4320
4321		var fnMap = [
4322			[
4323				'requestFullscreen',
4324				'exitFullscreen',
4325				'fullscreenElement',
4326				'fullscreenEnabled',
4327				'fullscreenchange',
4328				'fullscreenerror'
4329			],
4330			// new WebKit
4331			[
4332				'webkitRequestFullscreen',
4333				'webkitExitFullscreen',
4334				'webkitFullscreenElement',
4335				'webkitFullscreenEnabled',
4336				'webkitfullscreenchange',
4337				'webkitfullscreenerror'
4338
4339			],
4340			// old WebKit (Safari 5.1)
4341			[
4342				'webkitRequestFullScreen',
4343				'webkitCancelFullScreen',
4344				'webkitCurrentFullScreenElement',
4345				'webkitCancelFullScreen',
4346				'webkitfullscreenchange',
4347				'webkitfullscreenerror'
4348
4349			],
4350			[
4351				'mozRequestFullScreen',
4352				'mozCancelFullScreen',
4353				'mozFullScreenElement',
4354				'mozFullScreenEnabled',
4355				'mozfullscreenchange',
4356				'mozfullscreenerror'
4357			],
4358			[
4359				'msRequestFullscreen',
4360				'msExitFullscreen',
4361				'msFullscreenElement',
4362				'msFullscreenEnabled',
4363				'MSFullscreenChange',
4364				'MSFullscreenError'
4365			]
4366		];
4367
4368		var val;
4369		var ret = {};
4370		var i, j;
4371
4372		for ( i = 0; i < fnMap.length; i++ ) {
4373			val = fnMap[ i ];
4374
4375			if ( val && val[ 1 ] in document ) {
4376				for ( j = 0; j < val.length; j++ ) {
4377					ret[ fnMap[ 0 ][ j ] ] = val[ j ];
4378				}
4379
4380				return ret;
4381			}
4382		}
4383
4384		return false;
4385	})();
4386
4387	// If browser does not have Full Screen API, then simply unset default button template and stop
4388	if ( !fn ) {
4389
4390		if ( $ && $.fancybox ) {
4391			$.fancybox.defaults.btnTpl.fullScreen = false;
4392		}
4393
4394		return;
4395	}
4396
4397	var FullScreen = {
4398
4399		request : function ( elem ) {
4400
4401			elem = elem || document.documentElement;
4402
4403			elem[ fn.requestFullscreen ]( elem.ALLOW_KEYBOARD_INPUT );
4404
4405		},
4406		exit : function () {
4407
4408			document[ fn.exitFullscreen ]();
4409
4410		},
4411		toggle : function ( elem ) {
4412
4413			elem = elem || document.documentElement;
4414
4415			if ( this.isFullscreen() ) {
4416				this.exit();
4417
4418			} else {
4419				this.request( elem );
4420			}
4421
4422		},
4423		isFullscreen : function()  {
4424
4425			return Boolean( document[ fn.fullscreenElement ] );
4426
4427		},
4428		enabled : function()  {
4429
4430			return Boolean( document[ fn.fullscreenEnabled ] );
4431
4432		}
4433	};
4434
4435	$(document).on({
4436		'onInit.fb' : function(e, instance) {
4437			var $container;
4438
4439			var $button = instance.$refs.toolbar.find('[data-fancybox-fullscreen]');
4440
4441			if ( instance && !instance.FullScreen && instance.group[ instance.currIndex ].opts.fullScreen ) {
4442				$container = instance.$refs.container;
4443
4444				$container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function(e) {
4445
4446					e.stopPropagation();
4447					e.preventDefault();
4448
4449					FullScreen.toggle( $container[ 0 ] );
4450
4451				});
4452
4453				if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) {
4454					FullScreen.request( $container[ 0 ] );
4455				}
4456
4457				// Expose API
4458				instance.FullScreen = FullScreen;
4459
4460			} else {
4461				$button.hide();
4462			}
4463
4464		},
4465
4466		'afterKeydown.fb' : function(e, instance, current, keypress, keycode) {
4467
4468			// "P" or Spacebar
4469			if ( instance && instance.FullScreen && keycode === 70 ) {
4470				keypress.preventDefault();
4471
4472				instance.FullScreen.toggle( instance.$refs.container[ 0 ] );
4473			}
4474
4475		},
4476
4477		'beforeClose.fb' : function( instance ) {
4478			if ( instance && instance.FullScreen ) {
4479				FullScreen.exit();
4480			}
4481		}
4482	});
4483
4484	$(document).on(fn.fullscreenchange, function() {
4485		var instance = $.fancybox.getInstance();
4486
4487		// If image is zooming, then force to stop and reposition properly
4488		if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) {
4489			instance.current.$content.css( 'transition', 'none' );
4490
4491			instance.isAnimating = false;
4492
4493			instance.update( true, true, 0 );
4494		}
4495
4496		instance.trigger('onFullscreenChange', FullScreen.isFullscreen() );
4497
4498	});
4499
4500}(document, window.jQuery));
4501
4502// ==========================================================================
4503//
4504// Thumbs
4505// Displays thumbnails in a grid
4506//
4507// ==========================================================================
4508;(function (document, $) {
4509	'use strict';
4510
4511	var FancyThumbs = function( instance ) {
4512		this.instance = instance;
4513		this.init();
4514	};
4515
4516	$.extend( FancyThumbs.prototype, {
4517
4518		$button		: null,
4519		$grid		: null,
4520		$list		: null,
4521		isVisible	: false,
4522
4523		init : function() {
4524			var self = this;
4525
4526			var first  = self.instance.group[0],
4527				second = self.instance.group[1];
4528
4529			self.$button = self.instance.$refs.toolbar.find( '[data-fancybox-thumbs]' );
4530
4531			if ( self.instance.group.length > 1 && self.instance.group[ self.instance.currIndex ].opts.thumbs && (
4532		    		( first.type == 'image'  || first.opts.thumb  || first.opts.$thumb ) &&
4533		    		( second.type == 'image' || second.opts.thumb || second.opts.$thumb )
4534			)) {
4535
4536				self.$button.on('click', function() {
4537					self.toggle();
4538				});
4539
4540				self.isActive = true;
4541
4542			} else {
4543				self.$button.hide();
4544
4545				self.isActive = false;
4546			}
4547
4548		},
4549
4550		create : function() {
4551			var instance = this.instance,
4552				list,
4553				src;
4554
4555			this.$grid = $('<div class="fancybox-thumbs"></div>').appendTo( instance.$refs.container );
4556
4557			list = '<ul>';
4558
4559			$.each(instance.group, function( i, item ) {
4560
4561				src = item.opts.thumb || ( item.opts.$thumb ? item.opts.$thumb.attr('src') : null );
4562
4563				if ( !src && item.type === 'image' ) {
4564					src = item.src;
4565				}
4566
4567				if ( src && src.length ) {
4568					list += '<li data-index="' + i + '"  tabindex="0" class="fancybox-thumbs-loading"><img data-src="' + src + '" /></li>';
4569				}
4570
4571			});
4572
4573			list += '</ul>';
4574
4575			this.$list = $( list ).appendTo( this.$grid ).on('click', 'li', function() {
4576				instance.jumpTo( $(this).data('index') );
4577			});
4578
4579			this.$list.find('img').hide().one('load', function() {
4580
4581				var $parent		= $(this).parent().removeClass('fancybox-thumbs-loading'),
4582					thumbWidth	= $parent.outerWidth(),
4583					thumbHeight	= $parent.outerHeight(),
4584					width,
4585					height,
4586					widthRatio,
4587					heightRatio;
4588
4589				width  = this.naturalWidth	|| this.width;
4590				height = this.naturalHeight	|| this.height;
4591
4592				//Calculate thumbnail width/height and center it
4593
4594				widthRatio  = width  / thumbWidth;
4595				heightRatio = height / thumbHeight;
4596
4597				if (widthRatio >= 1 && heightRatio >= 1) {
4598					if (widthRatio > heightRatio) {
4599						width  = width / heightRatio;
4600						height = thumbHeight;
4601
4602					} else {
4603						width  = thumbWidth;
4604						height = height / widthRatio;
4605					}
4606				}
4607
4608				$(this).css({
4609					width         : Math.floor(width),
4610					height        : Math.floor(height),
4611					'margin-top'  : Math.min( 0, Math.floor(thumbHeight * 0.3 - height * 0.3 ) ),
4612					'margin-left' : Math.min( 0, Math.floor(thumbWidth  * 0.5 - width  * 0.5 ) )
4613				}).show();
4614
4615			})
4616			.each(function() {
4617				this.src = $( this ).data( 'src' );
4618			});
4619
4620		},
4621
4622		focus : function() {
4623
4624			if ( this.instance.current ) {
4625				this.$list
4626					.children()
4627					.removeClass('fancybox-thumbs-active')
4628					.filter('[data-index="' + this.instance.current.index  + '"]')
4629					.addClass('fancybox-thumbs-active')
4630					.focus();
4631			}
4632
4633		},
4634
4635		close : function() {
4636			this.$grid.hide();
4637		},
4638
4639		update : function() {
4640
4641			this.instance.$refs.container.toggleClass( 'fancybox-show-thumbs', this.isVisible );
4642
4643			if ( this.isVisible ) {
4644
4645				if ( !this.$grid ) {
4646					this.create();
4647				}
4648
4649				this.instance.trigger( 'onThumbsShow' );
4650
4651				this.focus();
4652
4653			} else if ( this.$grid ) {
4654				this.instance.trigger( 'onThumbsHide' );
4655			}
4656
4657			// Update content position
4658			this.instance.update();
4659
4660		},
4661
4662		hide : function() {
4663			this.isVisible = false;
4664			this.update();
4665		},
4666
4667		show : function() {
4668			this.isVisible = true;
4669			this.update();
4670		},
4671
4672		toggle : function() {
4673			this.isVisible = !this.isVisible;
4674			this.update();
4675		}
4676
4677	});
4678
4679	$(document).on({
4680
4681		'onInit.fb' : function(e, instance) {
4682			if ( instance && !instance.Thumbs ) {
4683				instance.Thumbs = new FancyThumbs( instance );
4684			}
4685		},
4686
4687		'beforeShow.fb' : function(e, instance, item, firstRun) {
4688			var Thumbs = instance && instance.Thumbs;
4689
4690			if ( !Thumbs || !Thumbs.isActive ) {
4691				return;
4692			}
4693
4694			if ( item.modal ) {
4695				Thumbs.$button.hide();
4696
4697				Thumbs.hide();
4698
4699				return;
4700			}
4701
4702			if ( firstRun && instance.opts.thumbs.autoStart === true ) {
4703				Thumbs.show();
4704			}
4705
4706			if ( Thumbs.isVisible ) {
4707				Thumbs.focus();
4708			}
4709		},
4710
4711		'afterKeydown.fb' : function(e, instance, current, keypress, keycode) {
4712			var Thumbs = instance && instance.Thumbs;
4713
4714			// "G"
4715			if ( Thumbs && Thumbs.isActive && keycode === 71 ) {
4716				keypress.preventDefault();
4717
4718				Thumbs.toggle();
4719			}
4720		},
4721
4722		'beforeClose.fb' : function( e, instance ) {
4723			var Thumbs = instance && instance.Thumbs;
4724
4725			if ( Thumbs && Thumbs.isVisible && instance.opts.thumbs.hideOnClose !== false ) {
4726				Thumbs.close();
4727			}
4728		}
4729
4730	});
4731
4732}(document, window.jQuery));
4733
4734// ==========================================================================
4735//
4736// Hash
4737// Enables linking to each modal
4738//
4739// ==========================================================================
4740;(function (document, window, $) {
4741	'use strict';
4742
4743	// Simple $.escapeSelector polyfill (for jQuery prior v3)
4744	if ( !$.escapeSelector ) {
4745		$.escapeSelector = function( sel ) {
4746			var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
4747			var fcssescape = function( ch, asCodePoint ) {
4748				if ( asCodePoint ) {
4749					// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
4750					if ( ch === "\0" ) {
4751						return "\uFFFD";
4752					}
4753
4754					// Control characters and (dependent upon position) numbers get escaped as code points
4755					return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
4756				}
4757
4758				// Other potentially-special ASCII characters get backslash-escaped
4759				return "\\" + ch;
4760			};
4761
4762			return ( sel + "" ).replace( rcssescape, fcssescape );
4763		};
4764	}
4765
4766	// Create new history entry only once
4767	var shouldCreateHistory = true;
4768
4769	// Variable containing last hash value set by fancyBox
4770	// It will be used to determine if fancyBox needs to close after hash change is detected
4771    var currentHash = null;
4772
4773	// Throttling the history change
4774	var timerID = null;
4775
4776	// Get info about gallery name and current index from url
4777    function parseUrl() {
4778        var hash    = window.location.hash.substr( 1 );
4779        var rez     = hash.split( '-' );
4780        var index   = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1;
4781        var gallery = rez.join( '-' );
4782
4783		// Index is starting from 1
4784		if ( index < 1 ) {
4785			index = 1;
4786		}
4787
4788        return {
4789            hash    : hash,
4790            index   : index,
4791            gallery : gallery
4792        };
4793    }
4794
4795	// Trigger click evnt on links to open new fancyBox instance
4796	function triggerFromUrl( url ) {
4797		var $el;
4798
4799        if ( url.gallery !== '' ) {
4800
4801			// If we can find element matching 'data-fancybox' atribute, then trigger click event for that ..
4802			$el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 );
4803
4804            if ( !$el.length ) {
4805				// .. if not, try finding element by ID
4806				$el = $( "#" + $.escapeSelector( url.gallery ) + "" );
4807			}
4808
4809			if ( $el.length ) {
4810				shouldCreateHistory = false;
4811
4812				$el.trigger( 'click' );
4813			}
4814
4815        }
4816	}
4817
4818	// Get gallery name from current instance
4819	function getGalleryID( instance ) {
4820		var opts;
4821
4822		if ( !instance ) {
4823			return false;
4824		}
4825
4826		opts = instance.current ? instance.current.opts : instance.opts;
4827
4828		return opts.hash || ( opts.$orig ? opts.$orig.data( 'fancybox' ) : ''  );
4829	}
4830
4831	// Star when DOM becomes ready
4832    $(function() {
4833
4834		// Small delay is used to allow other scripts to process "dom ready" event
4835		setTimeout(function() {
4836
4837			// Check if this module is not disabled
4838			if ( $.fancybox.defaults.hash === false ) {
4839				return;
4840			}
4841
4842			// Update hash when opening/closing fancyBox
4843		    $(document).on({
4844				'onInit.fb' : function( e, instance ) {
4845					var url, gallery;
4846
4847					if ( instance.group[ instance.currIndex ].opts.hash === false ) {
4848						return;
4849					}
4850
4851					url     = parseUrl();
4852					gallery = getGalleryID( instance );
4853
4854					// Make sure gallery start index matches index from hash
4855					if ( gallery && url.gallery && gallery == url.gallery ) {
4856						instance.currIndex = url.index - 1;
4857					}
4858
4859				},
4860
4861				'beforeShow.fb' : function( e, instance, current ) {
4862					var gallery;
4863
4864					if ( !current || current.opts.hash === false ) {
4865						return;
4866					}
4867
4868		            gallery = getGalleryID( instance );
4869
4870		            // Update window hash
4871		            if ( gallery && gallery !== '' ) {
4872
4873						if ( window.location.hash.indexOf( gallery ) < 0 ) {
4874			                instance.opts.origHash = window.location.hash;
4875			            }
4876
4877						currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' );
4878
4879						if ( 'replaceState' in window.history ) {
4880							if ( timerID ) {
4881								clearTimeout( timerID );
4882							}
4883
4884							timerID = setTimeout(function() {
4885								window.history[ shouldCreateHistory ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' +  currentHash );
4886
4887								timerID = null;
4888
4889								shouldCreateHistory = false;
4890
4891							}, 300);
4892
4893						} else {
4894							window.location.hash = currentHash;
4895						}
4896
4897		            }
4898
4899		        },
4900
4901				'beforeClose.fb' : function( e, instance, current ) {
4902					var gallery, origHash;
4903
4904					if ( timerID ) {
4905						clearTimeout( timerID );
4906					}
4907
4908					if ( current.opts.hash === false ) {
4909						return;
4910					}
4911
4912					gallery  = getGalleryID( instance );
4913					origHash = instance && instance.opts.origHash ? instance.opts.origHash : '';
4914
4915		            // Remove hash from location bar
4916		            if ( gallery && gallery !== '' ) {
4917
4918		                if ( 'replaceState' in history ) {
4919							window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash );
4920
4921		                } else {
4922							window.location.hash = origHash;
4923
4924							// Keep original scroll position
4925							$( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft );
4926		                }
4927		            }
4928
4929					currentHash = null;
4930		        }
4931		    });
4932
4933			// Check if need to close after url has changed
4934			$(window).on('hashchange.fb', function() {
4935				var url = parseUrl();
4936
4937				if ( $.fancybox.getInstance() ) {
4938					if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) {
4939						currentHash = null;
4940
4941						$.fancybox.close();
4942					}
4943
4944				} else if ( url.gallery !== '' ) {
4945					triggerFromUrl( url );
4946				}
4947			});
4948
4949			// Check current hash and trigger click event on matching element to start fancyBox, if needed
4950			triggerFromUrl( parseUrl() );
4951
4952		}, 50);
4953
4954    });
4955
4956}(document, window, window.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.