PageSourceSearch

https://www.entosense.com/wp-content/plugins/gallery-by-supsystic/…/assets/js/jquery.photobox.js?ver=1.16.0

js entosense.com collected 2026-10-02 11:09:21 UTC 40,229 bytes, 1,114 lines download raw bytes

1/*!
2    photobox v1.9.9
3    (c) 2013 Yair Even Or <http://dropthebit.com>
4
5    MIT-style license.
6*/
7
8(function ($, doc, win) {
9  'use strict';
10
11  var Photobox,
12    photobox,
13    options,
14    images = [],
15    imageLinks,
16    activeImage = -1,
17    activeURL,
18    lastActive,
19    activeType,
20    prevImage,
21    nextImage,
22    thumbsStripe,
23    docElm,
24    APControl,
25    changeImage,
26    transitionend = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd',
27    isOldIE = !('placeholder' in doc.createElement('input')),
28    noPointerEvents = (function () {
29      var el = $('<p>')[0];
30      el.style.cssText = 'pointer-events:auto';
31      return !el.style.pointerEvents;
32    })(),
33    isTouchDevice = false, // assume "false" unless there's a touch
34    thumbsContainerWidth,
35    thumbsTotalWidth,
36    activeThumb = $(),
37    blankImg = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
38    transformOrigin = getPrefixed('transformOrigin'),
39    transition = getPrefixed('transition'),
40    // Normalize rAF
41    raf =
42      window.requestAnimationFrame ||
43      window.webkitRequestAnimationFrame ||
44      window.mozRequestAnimationFrame ||
45      window.msRequestAnimationFrame ||
46      function (cb) {
47        return window.setTimeout(cb, 1000 / 60);
48      },
49    // Preload images
50    preload = {},
51    preloadPrev = new Image(),
52    preloadNext = new Image(),
53    // DOM elements
54    closeBtn,
55    image,
56    video,
57    prevBtn,
58    nextBtn,
59    thumbsToggler,
60    caption,
61    captionText,
62    pbLoader,
63    autoplayBtn,
64    thumbs,
65    wrapper,
66    defaults = {
67      single: false, // if "true" - gallery will only show a single image, with no way to navigate
68      beforeShow: null, // Callback before showing an image
69      afterClose: null, // Callback after closing the gallery
70      getTitle: null, // Callback for getting title
71      loop: true, // Allows to navigate between first and last images
72      thumb: null, // A relative path from the link to the thumbnail (if it's not inside the link)
73      thumbs: true, // Show gallery thumbnails below the presented photo
74      thumbAttr: 'data-src', // Attribute to get the image for the thumbnail from
75      counter: '(A/B)', // Counts which piece of content is being viewed, relative to the total count of items in the photobox set. ["false","String"]
76      title: true, // show the original alt or title attribute of the image's thumbnail. (path to image, relative to the element which triggers photobox)
77      autoplay: false, // should autoplay on first time or not
78      time: 3000, // autoplay interval, in miliseconds (less than 1000 will hide the autoplay button)
79      history: false, // should use history hashing if possible (HTML5 API)
80      hideFlash: true, // Hides flash elements on the page when photobox is activated. NOTE: flash elements must have wmode parameter set to "opaque" or "transparent" if this is set to false
81      zoomable: true, // disable/enable mousewheel image zooming
82      wheelNextPrev: true, // change image using mousewheel left/right
83      keys: {
84        close: [27, 88, 67], // keycodes to close photobox, default: esc (27), 'x' (88), 'c' (67)
85        prev: [37, 80], // keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80)
86        next: [39, 78], // keycodes to navigate to the next image, default: Right arrow (39), 'n' (78)
87      },
88    },
89    // DOM structure
90    overlay = $('<div id="pbOverlay">').append(
91      (thumbsToggler = $('<input type="checkbox" id="pbThumbsToggler" checked hidden>')),
92      (pbLoader = $('<div class="pbLoader"><b></b><b></b><b></b></div>')),
93      (prevBtn = $('<div id="pbPrevBtn" class="prevNext"><b></b></div>').on('click', next_prev)),
94      (nextBtn = $('<div id="pbNextBtn" class="prevNext"><b></b></div>').on('click', next_prev)),
95      (wrapper = $('<div class="pbWrapper">').append(
96        // gives Perspective
97        (image = $('<img>')),
98        (video = $('<div>'))
99      )),
100      (closeBtn = $('<div id="pbCloseBtn">').on('click', close)[0]),
101      (autoplayBtn = $('<div id="pbAutoplayBtn">').append($('<div class="pbProgress">'))),
102      (caption = $('<div id="pbCaption">').append('<label for="pbThumbsToggler" title="thumbnails on/off"></label>', (captionText = $('<div class="pbCaptionText">').append('<div class="title"></div><div class="counter">')), (thumbs = $('<div>').addClass('pbThumbs'))))
103    );
104
105  ///////////////////////////////////////////////
106  // Should remove this and use underscore/lodash if possible
107
108  function throttle(callback, duration) {
109    var wait = false;
110    return function () {
111      if (!wait) {
112        callback.call();
113        wait = true;
114        setTimeout(function () {
115          wait = false;
116        }, duration);
117      }
118    };
119  }
120
121  ///////////////////////////////////////////////
122  // Initialization (on DOM ready)
123
124  function prepareDOM() {
125    noPointerEvents && overlay.hide();
126
127    $(doc).on('touchstart.testMouse', function () {
128      $(doc).off('touchstart.testMouse');
129      isTouchDevice = true;
130      overlay.addClass('mobile');
131    });
132
133    autoplayBtn.off().on('click', APControl.toggle);
134    // attach a delegated event on the thumbs container
135    thumbs.off().on('click', 'a', thumbsStripe.click);
136    // if useragent is IE < 10 (user deserves a slap on the face, but I gotta support them still...)
137    isOldIE && overlay.addClass('msie');
138
139    // cancel prorogation up to the overlay container so it won't close
140    overlay.off().on('click', 'img', function (e) {
141      e.stopPropagation();
142    });
143
144    $(doc.body).append(overlay);
145
146    // need this for later:
147    docElm = doc.documentElement;
148  }
149
150  // @param [List of elements to work on, Custom settings, Callback after image is loaded]
151  $.fn.photobox = function (target, settings, callback) {
152    return this.each(function () {
153      var o,
154        PB_data = $(this).data('_photobox');
155
156      if (PB_data) {
157        // don't initiate the plugin more than once on the same element
158        if (target === 'destroy') PB_data.destroy();
159
160        return this;
161      }
162
163      if (typeof target != 'string') target = 'a';
164
165      if (target === 'prepareDOM') {
166        prepareDOM();
167        return this;
168      }
169
170      // disable contextmenu
171      if (settings.isDisableRightClick) {
172        overlay.off('contextmenu').on('contextmenu', function (e) {
173          return false;
174        });
175      }
176
177      o = $.extend({}, defaults, settings || {});
178      photobox = new Photobox(o, this, target);
179
180      // Saves the insance on the gallery's target element
181      $(this).data('_photobox', photobox);
182      // add a callback to the specific gallery
183      photobox.callback = callback;
184    });
185  };
186
187  Photobox = function (_options, object, target) {
188    this.options = $.extend({}, _options);
189    this.target = target;
190    this.selector = $(object || doc);
191
192    this.thumbsList = null;
193    // filter the links which actually HAS an image as a child
194    var filtered = this.imageLinksFilter(this.selector.find(target));
195    this.imageLinks = filtered[0]; // Array of jQuery links
196    this.images = filtered[1]; // 2D Array of image URL & title
197    this.init();
198  };
199
200  Photobox.prototype = {
201    init: function () {
202      var that = this;
203
204      if (!that.thumbsList) {
205        that.thumbsList = thumbsStripe.generate.apply(that);
206      }
207
208      this.selector.on('click.photobox', this.target, function (e) {
209        e.preventDefault();
210        that.open(this);
211      });
212
213      // if any node was added or removed from the Selector of the gallery
214      this.observerTimeout = null;
215
216      if (this.selector[0].nodeType == 1)
217        // observe normal nodes
218        this.observeDOM(this.selector[0], this.onDOMchanges.bind(this));
219    },
220
221    onDOMchanges: function () {
222      var that = this;
223      // use a timeout to prevent more than one DOM change event firing at once, and also to overcome the fact that IE's DOMNodeRemoved is fired BEFORE elements were actually removed
224      clearTimeout(this.observerTimeout);
225      that.observerTimeout = setTimeout(function () {
226        var filtered = that.imageLinksFilter(that.selector.find(that.target)),
227          activeIndex = 0,
228          isActiveUrl = false,
229          i;
230
231        // Make sure that ONLY DOM changes in the photobox number of items will trigger a change
232        if (that.imageLinks.length == filtered[0].length) return;
233
234        that.imageLinks = filtered[0];
235        that.images = filtered[1];
236
237        // if photobox is opened
238        if (photobox) {
239          // if gallery which was changed is the currently viewed one:
240          if (that.selector == photobox.selector) {
241            images = that.images;
242            imageLinks = that.imageLinks;
243            // check if the currently VIEWED photo has been detached from a photobox set
244            // if so, remove navigation arrows
245            // TODO: fix the "images" to be an object and not an array.
246            for (i = images.length; i--; ) {
247              if (images[i][0] == activeURL) isActiveUrl = true;
248              // if not exits any more
249            }
250            // if( isActiveUrl ){
251            //     overlay.removeClass('hasArrows');
252            // }
253          }
254        }
255
256        // if this gallery has thumbs
257        //if( that.options.thumbs ){
258        that.thumbsList = thumbsStripe.generate.apply(that);
259        thumbs.html(that.thumbsList);
260        //}
261
262        if (that.images.length && activeURL && that.options.thumbs) {
263          activeIndex = that.thumbsList
264            .find('a[href="' + activeURL + '"]')
265            .eq(0)
266            .parent()
267            .index();
268
269          if (activeIndex == -1) activeIndex = 0;
270
271          // updateIndexes(activeIndex);
272          thumbsStripe.changeActive(activeIndex, 0);
273        }
274      }, 50);
275    },
276
277    open: function (link) {
278      var startImage = $.inArray(link, this.imageLinks);
279      // if image link does not exist in the imageLinks array (probably means it's not a valid part of the gallery)
280      if (startImage == -1) return false;
281
282      // load the right gallery selector...
283      options = this.options;
284      images = this.images;
285      imageLinks = this.imageLinks;
286
287      photobox = this;
288      this.setup(1);
289
290      overlay
291        .on(transitionend, function () {
292          overlay.off(transitionend).addClass('on'); // class 'on' is set when the initial fade-in of the overlay is done
293          changeImage(startImage, true);
294        })
295        .addClass('show');
296
297      if (isOldIE) overlay.trigger('MSTransitionEnd');
298
299      return false;
300    },
301
302    imageLinksFilter: function (obj) {
303      var that = this,
304        images = [],
305        caption = {},
306        captionlink;
307
308      return [
309        obj.filter(function (i) {
310          // search for the thumb inside the link, if not found then see if there's a 'that.settings.thumb' pointer to the thumbnail
311          var link = $(this),
312            thumbImg,
313            thumbSrc = '';
314
315          caption.content = link[0].getAttribute('title') || '';
316
317          if (that.options.thumb && typeof that.options.thumb === 'string') thumbImg = link.find(that.options.thumb)[0];
318
319          if (typeof that.options.thumb === 'function' && link && link.length && link.closest) {
320            var funcRetThumbs = that.options.thumb(link);
321            if (funcRetThumbs) {
322              thumbImg = funcRetThumbs;
323            }
324          }
325
326          // try a direct child lookup
327          if (!that.options.thumb || !thumbImg) thumbImg = link.find('img')[0];
328
329          // if no img child found in the link
330          if (thumbImg) {
331            captionlink = thumbImg.getAttribute('data-pb-captionlink');
332            thumbSrc = thumbImg.getAttribute(that.options.thumbAttr) || thumbImg.getAttribute('src');
333
334            caption.content = thumbImg.getAttribute('title') || thumbImg.getAttribute('alt') || '';
335          }
336
337          // if there is a caption link to be added:
338          if (captionlink) {
339            captionlink = captionlink.split('[');
340            // parse complex links: text[www.site.com]
341            if (captionlink.length == 2) {
342              caption.linkText = captionlink[0];
343              caption.linkHref = captionlink[1].slice(0, -1);
344            } else {
345              caption.linkText = captionlink;
346              caption.linkHref = captionlink;
347            }
348            caption.content += ' <a href="' + caption.linkHref + '">' + caption.linkText + '</a>';
349          }
350
351          images.push([link[0].href, caption.content, thumbSrc]);
352
353          return true;
354        }),
355        images,
356      ];
357    },
358
359    //check if DOM nodes were added or removed, to re-build the imageLinks and thumbnails
360    observeDOM: (function () {
361      var MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
362        eventListenerSupported = win.addEventListener;
363
364      return function (obj, callback) {
365        if (MutationObserver) {
366          var that = this;
367          // define a new observer
368          var obs = new MutationObserver(function (mutations, observer) {
369            if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) callback(that);
370          });
371          // have the observer observe for changes in children
372          obs.observe(obj, { childList: true, subtree: true });
373        } else if (eventListenerSupported) {
374          obj.addEventListener('DOMNodeInserted', callback.bind(that), false);
375          obj.addEventListener('DOMNodeRemoved', callback.bind(that), false);
376        }
377      };
378    })(),
379
380    // things that should happen every time the gallery opens or closes (some messed up code below..)
381    setup: function (open) {
382      var fn = open ? 'on' : 'off';
383
384      // a hack to change the image src to nothing, because you can't do that in CHROME
385      image[0].src = blankImg;
386
387      // thumbs stuff
388      if (options.thumbs) {
389        if (!isTouchDevice) {
390          thumbs[fn]('mouseenter.photobox', thumbsStripe.calc)[fn]('mousemove.photobox', thumbsStripe.move);
391        }
392      }
393
394      if (open) {
395        image.css({ transition: '0s' }).removeAttr('style'); // reset any transition that might be on the element (yes it's ugly)
396        overlay.show();
397        // Clean up if another gallery was viewed before, which had a thumbsList
398        thumbs.html(this.thumbsList).trigger('mouseenter.photobox');
399
400        if (options.thumbs) {
401          overlay.addClass('thumbs');
402        } else {
403          thumbsToggler.prop('checked', false);
404          overlay.removeClass('thumbs');
405        }
406
407        // things to hide if there are less than 2 images
408        if (this.images.length < 2 || options.single) overlay.removeClass('thumbs hasArrows hasCounter hasAutoplay');
409        else {
410          overlay.addClass('hasArrows hasCounter');
411
412          // check is the autoplay button should be visible (per gallery) and if so, should it autoplay or not.
413          if (options.time > 1000) {
414            overlay.addClass('hasAutoplay');
415            if (options.autoplay) APControl.progress.start();
416            else APControl.pause();
417          } else overlay.removeClass('hasAutoplay');
418        }
419
420        options.hideFlash && $('iframe, object, embed').css('visibility', 'hidden');
421      } else {
422        $(win).off('resize.photobox');
423      }
424
425      $(doc).off('keydown.photobox')[fn]({ 'keydown.photobox': keyDown });
426
427      if (isTouchDevice) {
428        //overlay.removeClass('hasArrows'); // no need for Arrows on touch-enabled
429        wrapper[fn]('swipe', onSwipe);
430      }
431
432      if (options.zoomable) {
433        overlay[fn]({ 'mousewheel.photobox': scrollZoom });
434        if (!isOldIE) thumbs[fn]({ 'mousewheel.photobox': thumbsResize });
435      }
436
437      if (!options.single && options.wheelNextPrev) {
438        overlay[fn]({ 'mousewheel.photobox': throttle(wheelNextPrev, 1000) });
439      }
440    },
441
442    destroy: function () {
443      options = this.options;
444      this.selector.off('click.photobox', this.target).removeData('_photobox');
445
446      close();
447    },
448  };
449
450  // on touch-devices only
451  function onSwipe(e, Dx, Dy) {
452    if (Dx == 1) {
453      image.css({ transform: 'translateX(25%)', transition: '.2s', opacity: 0 });
454      setTimeout(function () {
455        changeImage(prevImage);
456      }, 200);
457    } else if (Dx == -1) {
458      image.css({ transform: 'translateX(-25%)', transition: '.2s', opacity: 0 });
459      setTimeout(function () {
460        changeImage(nextImage);
461      }, 200);
462    }
463
464    if (Dy == 1) thumbsToggler.prop('checked', true);
465    else if (Dy == -1) thumbsToggler.prop('checked', false);
466  }
467
468  // manage the (bottom) thumbs strip
469  thumbsStripe = (function () {
470    var containerWidth = 0,
471      scrollWidth = 0,
472      posFromLeft = 0, // Stripe position from the left of the screen
473      stripePos = 0, // When relative mouse position inside the thumbs stripe
474      animated = null,
475      padding, // in percentage to the containerWidth
476      el,
477      $el,
478      ratio,
479      scrollPos,
480      pos;
481
482    return {
483      // returns a <ul> element which is populated with all the gallery links and thumbs
484      generate: function () {
485        var thumbsList = $('<ul>'),
486          elements = [],
487          len = this.imageLinks.length,
488          title,
489          thumbSrc,
490          link,
491          type,
492          i;
493
494        for (i = 0; i < len; i++) {
495          link = this.imageLinks[i];
496
497          thumbSrc = this.images[i][2];
498          // continue if has thumb
499          if (!thumbSrc) continue;
500
501          title = this.images[i][1];
502          type = link.rel ? " class='" + link.rel + "'" : '';
503          elements.push('<li' + type + '><a href="' + link.href + '"><img src="' + thumbSrc + '" alt="" title="' + $('<div />').html(title).text() + '" /></a></li>');
504        }
505        thumbsList.html(elements.join(''));
506        return thumbsList;
507      },
508
509      click: function (e) {
510        e.preventDefault();
511
512        activeThumb.removeClass('active');
513        $(document).trigger('pboxPreviewClicked');
514        activeThumb = $(this).parent().addClass('active');
515
516        var imageIndex = $(this.parentNode).index();
517        return changeImage(imageIndex, 0, 1);
518      },
519
520      changeActiveTimeout: null,
521      /** Highlights the thumb which represents the photo and centres the thumbs viewer on it.
522       **  @thumbClick - if a user clicked on a thumbnail, don't center on it
523       */
524      changeActive: function (index, delay, thumbClick) {
525        if (!options.thumbs) return;
526
527        var lastIndex = activeThumb.index();
528        activeThumb.removeClass('active');
529        activeThumb = thumbs.find('li').eq(index).addClass('active');
530
531        if (thumbClick || !activeThumb[0]) return;
532        // set the scrollLeft position of the thumbs list to show the active thumb
533        clearTimeout(this.changeActiveTimeout);
534        // give the images time to to settle on their new sizes (because of css transition) and then calculate the center...
535        this.changeActiveTimeout = setTimeout(function () {
536          var pos = activeThumb[0].offsetLeft + activeThumb[0].clientWidth / 2 - docElm.clientWidth / 2;
537          delay ? thumbs.delay(800) : thumbs.stop();
538          thumbs.animate({ scrollLeft: pos }, 500, 'swing');
539        }, 200);
540      },
541
542      // calculate the thumbs container width, if the window has been resized
543      calc: function (e) {
544        el = thumbs[0];
545
546        containerWidth = el.clientWidth;
547        scrollWidth = el.scrollWidth;
548        padding = 0.15 * containerWidth;
549
550        posFromLeft = thumbs.offset().left;
551        stripePos = e.pageX - padding - posFromLeft;
552        pos = stripePos / (containerWidth - padding * 2);
553        scrollPos = (scrollWidth - containerWidth) * pos;
554
555        thumbs.animate({ scrollLeft: scrollPos }, 200);
556
557        clearTimeout(animated);
558        animated = setTimeout(function () {
559          animated = null;
560        }, 200);
561
562        return this;
563      },
564
565      // move the stripe left or right according to mouse position
566      move: function (e) {
567        // don't move anything until initial movement on 'mouseenter' has finished
568        if (animated) return;
569
570        var ratio = scrollWidth / containerWidth,
571          stripePos = e.pageX - padding - posFromLeft, // the mouse X position, "normalized" to the carousel position
572          pos,
573          scrollPos;
574
575        if (stripePos < 0) stripePos = 0; //
576
577        pos = stripePos / (containerWidth - padding * 2); // calculated position between 0 to 1
578        // calculate the percentage of the mouse position within the carousel
579        scrollPos = (scrollWidth - containerWidth) * pos;
580
581        raf(function () {
582          el.scrollLeft = scrollPos;
583        });
584      },
585    };
586  })();
587
588  // Autoplay controller
589  APControl = {
590    autoPlayTimer: false,
591    play: function () {
592      APControl.autoPlayTimer = setTimeout(function () {
593        changeImage(nextImage);
594      }, options.time);
595      APControl.progress.start();
596      autoplayBtn.removeClass('play');
597      APControl.setTitle('Click to stop autoplay');
598      options.autoplay = true;
599    },
600    pause: function () {
601      clearTimeout(APControl.autoPlayTimer);
602      APControl.progress.reset();
603      autoplayBtn.addClass('play');
604      APControl.setTitle('Click to resume autoplay');
605      options.autoplay = false;
606    },
607    progress: {
608      reset: function () {
609        autoplayBtn.find('div').removeAttr('style');
610        setTimeout(function () {
611          autoplayBtn.removeClass('playing');
612        }, 200);
613      },
614      start: function () {
615        if (!isOldIE) autoplayBtn.find('div').css(transition, options.time + 'ms');
616        autoplayBtn.addClass('playing');
617      },
618    },
619    // sets the button Title property
620    setTitle: function (text) {
621      if (text) autoplayBtn.prop('title', text + ' (every ' + options.time / 1000 + ' seconds)');
622    },
623    // the button onClick handler
624    toggle: function (e) {
625      e.stopPropagation();
626      APControl[options.autoplay ? 'pause' : 'play']();
627    },
628  };
629
630  function getPrefixed(prop) {
631    var i,
632      s = doc.createElement('p').style,
633      v = ['ms', 'O', 'Moz', 'Webkit'];
634    if (s[prop] == '') return prop;
635    prop = prop.charAt(0).toUpperCase() + prop.slice(1);
636    for (i = v.length; i--; ) if (s[v[i] + prop] == '') return v[i] + prop;
637  }
638
639  function keyDown(event) {
640    var code = event.keyCode,
641      ok = options.keys,
642      result;
643    // Prevent default keyboard action (like navigating inside the page)
644    return ($.inArray(code, ok.close) >= 0 && close()) || ($.inArray(code, ok.next) >= 0 && !options.single && loophole(nextImage)) || ($.inArray(code, ok.prev) >= 0 && !options.single && loophole(prevImage)) || true;
645  }
646
647  function wheelNextPrev(e, dY, dX) {
648    if (dX == 1) loophole(nextImage);
649    else if (dX == -1) loophole(prevImage);
650  }
651
652  // serves as a callback for pbPrevBtn / pbNextBtn buttons but also is called on keypress events
653  function next_prev() {
654    // don't get crazy when user clicks next or prev buttons rapidly
655    //if( !image.hasClass('zoomable') )
656    //  return false;
657
658    var idx = this.id == 'pbPrevBtn' ? prevImage : nextImage;
659
660    loophole(idx);
661    return false;
662  }
663
664  function updateIndexes(idx) {
665    lastActive = activeImage;
666    activeImage = idx;
667    activeURL = images[idx][0];
668    prevImage = (activeImage || (options.loop ? images.length : 0)) - 1;
669    nextImage = (activeImage + 1) % images.length || (options.loop ? 0 : -1);
670  }
671
672  // check if looping is allowed before changing image/video.
673  // A pre-changeImage function, only for linear changes
674  function loophole(idx) {
675    if (!options.loop) {
676      var afterLast = activeImage == images.length - 1 && idx == nextImage,
677        beforeFirst = activeImage == 0 && idx == prevImage;
678
679      if (afterLast || beforeFirst) return;
680    }
681
682    changeImage(idx);
683  }
684
685  changeImage = (function () {
686    var timer;
687
688    return function (imageIndex, firstTime, thumbClick) {
689      // throttle mechanism
690      if (timer) return;
691
692      timer = setTimeout(function () {
693        timer = null;
694      }, 150);
695
696      if (!imageIndex || imageIndex < 0) imageIndex = 0;
697
698      // hide/show next-prev buttons
699      if (!options.loop) {
700        nextBtn[imageIndex == images.length - 1 ? 'addClass' : 'removeClass']('hide');
701        prevBtn[imageIndex == 0 ? 'addClass' : 'removeClass']('hide');
702      }
703
704      // if there's a callback for this point:
705      if (typeof options.beforeShow == 'function') options.beforeShow(imageLinks[imageIndex]);
706
707      overlay.removeClass('error').addClass(imageIndex > activeImage ? 'next' : 'prev');
708
709      updateIndexes(imageIndex);
710
711      // reset things
712      stop();
713      video.empty();
714      preload.onerror = null;
715      image.add(video).data('zoom', 1);
716
717      activeType = imageLinks[imageIndex].rel == 'video' ? 'video' : 'image';
718
719      // check if current link is a video
720      if (activeType == 'video') {
721        if (
722          $(imageLinks[activeImage])
723            .attr('href')
724            .match(/<iframe(.+)<\/iframe>/i)
725        ) {
726          video.html(newVideo(true)).addClass('hide').attr('data-iframe', 'original_frame');
727        } else {
728          video.html(newVideo()).addClass('hide');
729        }
730
731        showContent(firstTime);
732      } else {
733        // give a tiny delay to the preloader, so it won't be showed when images load very quickly
734        var loaderTimeout = setTimeout(function () {
735          overlay.addClass('pbLoading');
736        }, 50);
737
738        if (isOldIE) overlay.addClass('hide'); // should wait for the image onload. just hide the image while old IE display the preloader
739
740        options.autoplay && APControl.progress.reset();
741        preload = new Image();
742        preload.onload = function () {
743          preload.onload = null;
744
745          if (prevImage >= 0) preloadPrev.src = images[prevImage][0];
746          if (nextImage >= 0) preloadNext.src = images[nextImage][0];
747
748          clearTimeout(loaderTimeout);
749          showContent(firstTime);
750        };
751        preload.onerror = imageError;
752        preload.src = activeURL;
753      }
754
755      // Show Caption text
756      captionText.on(transitionend, captionTextChange).addClass('change');
757      if (firstTime || isOldIE) captionTextChange();
758
759      thumbsStripe.changeActive(imageIndex, firstTime, thumbClick);
760      // Save url hash for current image
761      history.save();
762    };
763  })();
764
765  function newVideo(iframe) {
766    if (!iframe) {
767      iframe = false;
768    }
769    if (iframe) {
770      var url = $(imageLinks[activeImage]).attr('href');
771      return url;
772    }
773
774    var url = images[activeImage][0],
775      sign = $('<a>').prop('href', images[activeImage][0])[0].search ? '&' : '?';
776    url += sign + 'vq=hd720&wmode=opaque';
777
778    return $('<iframe>').prop({ scrolling: 'no', frameborder: 0, allowTransparency: true, src: url }).attr({ webkitAllowFullScreen: true, mozallowfullscreen: true, allowFullScreen: true });
779  }
780
781  // show the item's Title & Counter
782  function captionTextChange() {
783    captionText.off(transitionend).removeClass('change');
784    // change caption's text
785    if (options.counter) {
786      try {
787        var value = options.counter.replace('A', activeImage + 1).replace('B', images.length);
788      } catch (err) {
789        // if, for some reason, the above has failed from a bad "counter" value, reset and retry
790        options.counter = '(A/B)';
791        captionTextChange();
792      }
793      caption.find('.counter').text(value);
794    }
795    var title = images[activeImage][1];
796    // if there's a callback for this point:
797    if (typeof options.getTitle == 'function') title = options.getTitle(imageLinks[activeImage]);
798
799    if (options.title) caption.find('.title').html('<span>' + title + '</span>');
800  }
801
802  // Handles the history states when changing images
803  var history = {
804    save: function () {
805      // only save to history urls which are not already in the hash
806      if ('pushState' in window.history && decodeURIComponent(window.location.hash.slice(1)) != activeURL && options.history) {
807        window.history.pushState('photobox', doc.title + '-' + images[activeImage][1], window.location.pathname + window.location.search + '#' + encodeURIComponent(activeURL));
808      }
809    },
810    load: function () {
811      if (options && !options.history) return false;
812      var hash = decodeURIComponent(window.location.hash.slice(1)),
813        i,
814        j;
815      if (!hash && overlay.hasClass('show')) close();
816
817      $('a[href="' + hash + '"]').trigger('click.photobox');
818    },
819    clear: function () {
820      if (options.history && 'pushState' in window.history) window.history.pushState('photobox', doc.title, window.location.pathname + window.location.search);
821    },
822  };
823
824  // Add Photobox special `onpopstate` to the `onpopstate` function
825  window.onpopstate = (function () {
826    var cached = window.onpopstate;
827    return function (event) {
828      cached && cached.apply(this, arguments);
829      if (event.state == 'photobox') history.load();
830    };
831  })();
832
833  // handles all image loading error (if image is dead)
834  function imageError() {
835    overlay.addClass('error');
836    image[0].src = blankImg; // set the source to a blank image
837    preload.onerror = null;
838  }
839
840  function getImages() {
841    return images;
842  }
843
844  // Shows the content (image/video) on the screen
845  function showContent(firstTime) {
846    var out, showSaftyTimer;
847    showSaftyTimer = setTimeout(show, 2000);
848
849    // hides the current image and prepare ground for an image change
850    pbLoader.fadeOut(300, function () {
851      overlay.removeClass('pbLoading');
852      pbLoader.removeAttr('style');
853    });
854    overlay.addClass('hide');
855
856    image.add(video).removeAttr('style').removeClass('zoomable'); // while transitioning an image, do not apply the 'zoomable' class
857
858    // check which element needs to transition-out:
859    if (!firstTime && imageLinks[lastActive].rel == 'video') {
860      out = video;
861      image.addClass('prepare');
862    } else out = image;
863
864    if (firstTime || isOldIE) show();
865    else out.on(transitionend, show);
866
867    // in case the 'transitionend' didn't fire
868    // after hiding the last seen image, show the new one
869    function show() {
870      clearTimeout(showSaftyTimer);
871      out.off(transitionend).css({ transition: 'none' });
872      overlay.removeClass('video');
873      if (activeType == 'video') {
874        image[0].src = blankImg;
875        video.addClass('prepare');
876        overlay.addClass('video');
877      } else image.prop({ src: activeURL, class: 'prepare' });
878
879      // filthy hack for the transitionend event, but cannot work without it:
880      setTimeout(function () {
881        image.add(video).removeAttr('style').removeClass('prepare');
882        overlay.removeClass('hide next prev');
883        setTimeout(function () {
884          image.add(video).on(transitionend, showDone);
885          if (isOldIE) showDone(); // IE9 and below don't support transitionEnd...
886        }, 0);
887      }, 50);
888    }
889  }
890
891  // a callback whenever a transition of an image or a video is done
892  function showDone() {
893    image.add(video).off(transitionend).addClass('zoomable');
894    if (activeType == 'video') video.removeClass('hide');
895    else {
896      autoplayBtn && options.autoplay && APControl.play();
897    }
898    if (photobox && typeof photobox.callback == 'function') photobox.callback.apply(imageLinks[activeImage]);
899  }
900
901  function scrollZoom(e, deltaY, deltaX) {
902    if (deltaX) return false;
903
904    if (activeType == 'video') {
905      var zoomLevel = video.data('zoom') || 1;
906      zoomLevel += deltaY / 10;
907      if (zoomLevel < 0.5) return false;
908
909      video.data('zoom', zoomLevel).css({ width: 624 * zoomLevel, height: 351 * zoomLevel });
910    } else {
911      var zoomLevel = image.data('zoom') || 1,
912        getSize = image[0].getBoundingClientRect();
913
914      zoomLevel += deltaY / 10;
915
916      if (zoomLevel < 0.1) zoomLevel = 0.1;
917
918      raf(function () {
919        image.data('zoom', zoomLevel).css({ transform: 'scale(' + zoomLevel + ')' });
920      });
921
922      // check if image (by mouse) movement should take effect (if image is larger than the window
923      if (getSize.height > docElm.clientHeight || getSize.width > docElm.clientWidth) {
924        $(doc).on('mousemove.photobox', imageReposition);
925      } else {
926        $(doc).off('mousemove.photobox');
927        image[0].style[transformOrigin] = '50% 50%';
928      }
929    }
930    return false;
931  }
932
933  function thumbsResize(e, delta) {
934    e.preventDefault();
935    e.stopPropagation(); // stop the event from bubbling up to the Overlay and enlarge the content itself
936    var thumbList = photobox.thumbsList,
937      h;
938    thumbList.css('height', thumbList[0].clientHeight + delta * 10);
939    h = caption[0].clientHeight / 2;
940    wrapper[0].style.cssText = 'margin-top: -' + h + 'px; padding: ' + h + 'px 0;';
941    thumbs.hide().show(0);
942    //thumbs.trigger('mouseenter').trigger('mousemove');
943  }
944
945  // moves the image around during zoom mode on mousemove event
946  function imageReposition(e) {
947    var y = (e.clientY / docElm.clientHeight) * (docElm.clientHeight + 200) - 100, // extend the range of the Y axis by 100 each side
948      yDelta = (y / docElm.clientHeight) * 100,
949      xDelta = (e.clientX / docElm.clientWidth) * 100,
950      origin = xDelta.toFixed(2) + '% ' + yDelta.toFixed(2) + '%';
951
952    raf(function () {
953      image[0].style[transformOrigin] = origin;
954    });
955  }
956
957  function stop() {
958    clearTimeout(APControl.autoPlayTimer);
959    $(doc).off('mousemove.photobox');
960    preload.onload = function () {};
961    preload.src = preloadPrev.src = preloadNext.src = activeURL;
962  }
963
964  function close() {
965    if (!overlay.hasClass('show')) return false;
966
967    stop();
968    video.find('iframe').prop('src', '').empty();
969    Photobox.prototype.setup();
970    history.clear();
971
972    overlay.removeClass('on video').addClass('hide');
973
974    image.on(transitionend, hide);
975    isOldIE && hide();
976
977    // the "photobox" instance might be needed for async transitionEnd functions, so give it some time before clearing it
978    setTimeout(function () {
979      photobox = null;
980    }, 1000);
981
982    function hide() {
983      if (overlay[0].className == '') return; // if already hidden
984      overlay.removeClass('show hide error pbLoading');
985      image.removeAttr('class').removeAttr('style').off().data('zoom', 1);
986      caption.find('.title').empty();
987
988      if (noPointerEvents)
989        // pointer-events lack support in IE, so just hide the overlay
990        setTimeout(function () {
991          overlay.hide();
992        }, 200);
993
994      options.hideFlash && $('iframe, object, embed').css('visibility', 'visible');
995    }
996
997    // fall-back if the 'transitionend' event didn't fire
998    setTimeout(hide, 500);
999    // callback after closing the gallery
1000    if (typeof options.afterClose === 'function') options.afterClose(overlay);
1001  }
1002
1003  /*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh)
1004   * Licensed under the MIT License (LICENSE.txt).
1005   *
1006   * Version: 3.1.11
1007   *
1008   * Requires: jQuery 1.2.2+
1009   */
1010  !(function (a) {
1011    'function' == typeof define && define.amd ? define(['jquery'], a) : 'object' == typeof exports && typeof module != 'undefined' && module.exports ? (module.exports = a) : a(jQuery);
1012  })(function (a) {
1013    function b(b) {
1014      var g = b || window.event,
1015        h = i.call(arguments, 1),
1016        j = 0,
1017        l = 0,
1018        m = 0,
1019        n = 0,
1020        o = 0,
1021        p = 0;
1022      if (
1023        ((b = a.event.fix(g)),
1024        (b.type = 'mousewheel'),
1025        'detail' in g && (m = -1 * g.detail),
1026        'wheelDelta' in g && (m = g.wheelDelta),
1027        'wheelDeltaY' in g && (m = g.wheelDeltaY),
1028        'wheelDeltaX' in g && (l = -1 * g.wheelDeltaX),
1029        'axis' in g && g.axis === g.HORIZONTAL_AXIS && ((l = -1 * m), (m = 0)),
1030        (j = 0 === m ? l : m),
1031        'deltaY' in g && ((m = -1 * g.deltaY), (j = m)),
1032        'deltaX' in g && ((l = g.deltaX), 0 === m && (j = -1 * l)),
1033        0 !== m || 0 !== l)
1034      ) {
1035        if (1 === g.deltaMode) {
1036          var q = a.data(this, 'mousewheel-line-height');
1037          ((j *= q), (m *= q), (l *= q));
1038        } else if (2 === g.deltaMode) {
1039          var r = a.data(this, 'mousewheel-page-height');
1040          ((j *= r), (m *= r), (l *= r));
1041        }
1042        if (
1043          ((n = Math.max(Math.abs(m), Math.abs(l))),
1044          (!f || f > n) && ((f = n), d(g, n) && (f /= 40)),
1045          d(g, n) && ((j /= 40), (l /= 40), (m /= 40)),
1046          (j = Math[j >= 1 ? 'floor' : 'ceil'](j / f)),
1047          (l = Math[l >= 1 ? 'floor' : 'ceil'](l / f)),
1048          (m = Math[m >= 1 ? 'floor' : 'ceil'](m / f)),
1049          k.settings.normalizeOffset && this.getBoundingClientRect)
1050        ) {
1051          var s = this.getBoundingClientRect();
1052          ((o = b.clientX - s.left), (p = b.clientY - s.top));
1053        }
1054        return ((b.deltaX = l), (b.deltaY = m), (b.deltaFactor = f), (b.offsetX = o), (b.offsetY = p), (b.deltaMode = 0), h.unshift(b, j, l, m), e && clearTimeout(e), (e = setTimeout(c, 200)), (a.event.dispatch || a.event.handle).apply(this, h));
1055      }
1056    }
1057    function c() {
1058      f = null;
1059    }
1060    function d(a, b) {
1061      return k.settings.adjustOldDeltas && 'mousewheel' === a.type && b % 120 === 0;
1062    }
1063    var e,
1064      f,
1065      g = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
1066      h = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
1067      i = Array.prototype.slice;
1068    if (a.event.fixHooks) for (var j = g.length; j; ) a.event.fixHooks[g[--j]] = a.event.mouseHooks;
1069    var k = (a.event.special.mousewheel = {
1070      version: '3.1.11',
1071      setup: function () {
1072        if (this.addEventListener) for (var c = h.length; c; ) this.addEventListener(h[--c], b, !1);
1073        else this.onmousewheel = b;
1074        (a.data(this, 'mousewheel-line-height', k.getLineHeight(this)), a.data(this, 'mousewheel-page-height', k.getPageHeight(this)));
1075      },
1076      teardown: function () {
1077        if (this.removeEventListener) for (var c = h.length; c; ) this.removeEventListener(h[--c], b, !1);
1078        else this.onmousewheel = null;
1079        (a.removeData(this, 'mousewheel-line-height'), a.removeData(this, 'mousewheel-page-height'));
1080      },
1081      getLineHeight: function (b) {
1082        var c = a(b)['offsetParent' in a.fn ? 'offsetParent' : 'parent']();
1083        return (c.length || (c = a('body')), parseInt(c.css('fontSize'), 10));
1084      },
1085      getPageHeight: function (b) {
1086        return a(b).height();
1087      },
1088      settings: { adjustOldDeltas: !0, normalizeOffset: !0 },
1089    });
1090    a.fn.extend({
1091      mousewheel: function (a) {
1092        return a ? this.bind('mousewheel', a) : this.trigger('mousewheel');
1093      },
1094      unmousewheel: function (a) {
1095        return this.unbind('mousewheel', a);
1096      },
1097    });
1098  });
1099
1100  ////////////// ON DOCUMENT READY /////////////////
1101  $(doc).ready(prepareDOM);
1102
1103  // Expose:
1104  window._photobox = {
1105    DOM: {
1106      overlay: overlay,
1107    },
1108    close: close,
1109    getImages: getImages,
1110    changeImage: changeImage,
1111    history: history,
1112    defaults: defaults,
1113  };
1114})(jQuery, document, window);

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.