PageSourceSearch

https://www.modricainfo.com/wp-content/plugins/fotorama/fotorama.js

js modricainfo.com collected 2026-10-03 18:06:21 UTC 102,789 bytes, 3,705 lines download raw bytes

vendor: 7,057 bytes, lines 1-245
1/*!
2 * Fotorama 4.6.3 | http://fotorama.io/license/
3 */
4fotoramaVersion = '4.6.3';
5(function (window, document, location, $, undefined) {
6  "use strict";
7var _fotoramaClass = 'fotorama',
8    _fullscreenClass = 'fullscreen',
9
10    wrapClass = _fotoramaClass + '__wrap',
11    wrapCss2Class = wrapClass + '--css2',
12    wrapCss3Class = wrapClass + '--css3',
13    wrapVideoClass = wrapClass + '--video',
14    wrapFadeClass = wrapClass + '--fade',
15    wrapSlideClass = wrapClass + '--slide',
16    wrapNoControlsClass = wrapClass + '--no-controls',
17    wrapNoShadowsClass = wrapClass + '--no-shadows',
18    wrapPanYClass = wrapClass + '--pan-y',
19    wrapRtlClass = wrapClass + '--rtl',
20    wrapOnlyActiveClass = wrapClass + '--only-active',
21    wrapNoCaptionsClass = wrapClass + '--no-captions',
22    wrapToggleArrowsClass = wrapClass + '--toggle-arrows',
23
24    stageClass = _fotoramaClass + '__stage',
25    stageFrameClass = stageClass + '__frame',
26    stageFrameVideoClass = stageFrameClass + '--video',
27    stageShaftClass = stageClass + '__shaft',
28
29    grabClass = _fotoramaClass + '__grab',
30    pointerClass = _fotoramaClass + '__pointer',
31
32    arrClass = _fotoramaClass + '__arr',
33    arrDisabledClass = arrClass + '--disabled',
34    arrPrevClass = arrClass + '--prev',
35    arrNextClass = arrClass + '--next',
36    arrArrClass = arrClass + '__arr',
37
38    navClass = _fotoramaClass + '__nav',
39    navWrapClass = navClass + '-wrap',
40    navShaftClass = navClass + '__shaft',
41    navDotsClass = navClass + '--dots',
42    navThumbsClass = navClass + '--thumbs',
43    navFrameClass = navClass + '__frame',
44    navFrameDotClass = navFrameClass + '--dot',
45    navFrameThumbClass = navFrameClass + '--thumb',
46
47    fadeClass = _fotoramaClass + '__fade',
48    fadeFrontClass = fadeClass + '-front',
49    fadeRearClass = fadeClass + '-rear',
50
51    shadowClass = _fotoramaClass + '__shadow',
52    shadowsClass = shadowClass + 's',
53    shadowsLeftClass = shadowsClass + '--left',
54    shadowsRightClass = shadowsClass + '--right',
55
56    activeClass = _fotoramaClass + '__active',
57    selectClass = _fotoramaClass + '__select',
58
59    hiddenClass = _fotoramaClass + '--hidden',
60
61    fullscreenClass = _fotoramaClass + '--fullscreen',
62    fullscreenIconClass = _fotoramaClass + '__fullscreen-icon',
63
64    errorClass = _fotoramaClass + '__error',
65    loadingClass = _fotoramaClass + '__loading',
66    loadedClass = _fotoramaClass + '__loaded',
67    loadedFullClass = loadedClass + '--full',
68    loadedImgClass = loadedClass + '--img',
69
70    grabbingClass = _fotoramaClass + '__grabbing',
71
72    imgClass = _fotoramaClass + '__img',
73    imgFullClass = imgClass + '--full',
74
75    dotClass = _fotoramaClass + '__dot',
76    thumbClass = _fotoramaClass + '__thumb',
77    thumbBorderClass = thumbClass + '-border',
78
79    htmlClass = _fotoramaClass + '__html',
80
81    videoClass = _fotoramaClass + '__video',
82    videoPlayClass = videoClass + '-play',
83    videoCloseClass = videoClass + '-close',
84
85    captionClass = _fotoramaClass + '__caption',
86    captionWrapClass = _fotoramaClass + '__caption__wrap',
87
88    spinnerClass = _fotoramaClass + '__spinner',
89
90    buttonAttributes = '" tabindex="0" role="button';
91var JQUERY_VERSION = $ && $.fn.jquery.split('.');
92
93if (!JQUERY_VERSION
94    || JQUERY_VERSION[0] < 1
95    || (JQUERY_VERSION[0] == 1 && JQUERY_VERSION[1] < 8)) {
96  throw 'Fotorama requires jQuery 1.8 or later and will not run without it.';
97}
98// My Underscore :-)
99var _ = {};
100/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
101 * Build: http://modernizr.com/download/#-csstransforms3d-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes
102 */
103
104var Modernizr = (function (window, document, undefined) {
105
106  var version = '2.6.2',
107
108      Modernizr = {},
109
110      docElement = document.documentElement,
111
112      mod = 'modernizr',
113      modElem = document.createElement(mod),
114      mStyle = modElem.style,
115
116      inputElem,
117
118      toString = {}.toString,
119
120      prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
121
122      omPrefixes = 'Webkit Moz O ms',
123
124      cssomPrefixes = omPrefixes.split(' '),
125
126      domPrefixes = omPrefixes.toLowerCase().split(' '),
127
128      tests = {},
129      inputs = {},
130      attrs = {},
131
132      classes = [],
133
134      slice = classes.slice,
135
136      featureName,
137
138      injectElementWithStyles = function (rule, callback, nodes, testnames) {
139
140        var style, ret, node, docOverflow,
141            div = document.createElement('div'),
142            body = document.body,
143            fakeBody = body || document.createElement('body');
144
145        if (parseInt(nodes, 10)) {
146          while (nodes--) {
147            node = document.createElement('div');
148            node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
149            div.appendChild(node);
150          }
151        }
152
153        style = ['&#173;', '<style id="s', mod, '">', rule, '</style>'].join('');
154        div.id = mod;
155        (body ? div : fakeBody).innerHTML += style;
156        fakeBody.appendChild(div);
157        if (!body) {
158          fakeBody.style.background = '';
159          fakeBody.style.overflow = 'hidden';
160          docOverflow = docElement.style.overflow;
161          docElement.style.overflow = 'hidden';
162          docElement.appendChild(fakeBody);
163        }
164
165        ret = callback(div, rule);
166        if (!body) {
167          fakeBody.parentNode.removeChild(fakeBody);
168          docElement.style.overflow = docOverflow;
169        } else {
170          div.parentNode.removeChild(div);
171        }
172
173        return !!ret;
174
175      },
176      _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
177
178  if (!is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined')) {
179    hasOwnProp = function (object, property) {
180      return _hasOwnProperty.call(object, property);
181    };
182  }
183  else {
184    hasOwnProp = function (object, property) {
185      return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
186    };
187  }
188
189
190  if (!Function.prototype.bind) {
191    Function.prototype.bind = function bind (that) {
192
193      var target = this;
194
195      if (typeof target != "function") {
196        throw new TypeError();
197      }
198
199      var args = slice.call(arguments, 1),
200          bound = function () {
201
202            if (this instanceof bound) {
203
204              var F = function () {
205              };
206              F.prototype = target.prototype;
207              var self = new F();
208
209              var result = target.apply(
210                  self,
211                  args.concat(slice.call(arguments))
212              );
213              if (Object(result) === result) {
214                return result;
215              }
216              return self;
217
218            } else {
219
220              return target.apply(
221                  that,
222                  args.concat(slice.call(arguments))
223              );
224
225            }
226
227          };
228
229      return bound;
230    };
231  }
232
233  function setCss (str) {
234    mStyle.cssText = str;
235  }
236
237  function setCssAll (str1, str2) {
238    return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
239  }
240
241  function is (obj, type) {
242    return typeof obj === type;
243  }
244
245  function contains (str, substr) {
246    return !!~('' + str).indexOf(substr);
247  }
248
249  function testProps (props, prefixed) {
250    for (var i in props) {
251      var prop = props[i];
252      if (!contains(prop, "-") && mStyle[prop] !== undefined) {
253        return prefixed == 'pfx' ? prop : true;
254      }
255    }
256    return false;
257  }
258
259  function testDOMProps (props, obj, elem) {
260    for (var i in props) {
261      var item = obj[props[i]];
262      if (item !== undefined) {
263
264        if (elem === false) return props[i];
265
266        if (is(item, 'function')) {
267          return item.bind(elem || obj);
268        }
269
270        return item;
271      }
272    }
273    return false;
274  }
275
276  function testPropsAll (prop, prefixed, elem) {
277
278    var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
279        props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
280
281    if (is(prefixed, "string") || is(prefixed, "undefined")) {
282      return testProps(props, prefixed);
283
284    } else {
285      props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
286      return testDOMProps(props, prefixed, elem);
287    }
288  }
289
290  tests['csstransforms3d'] = function () {
291
292    var ret = !!testPropsAll('perspective');
293
294// Chrome fails that test, ignore
295//		if (ret && 'webkitPerspective' in docElement.style) {
296//
297//			injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function (node, rule) {
298//				ret = node.offsetLeft === 9 && node.offsetHeight === 3;
299//			});
300//		}
301    return ret;
302  };
303
304  for (var feature in tests) {
305    if (hasOwnProp(tests, feature)) {
306      featureName = feature.toLowerCase();
307      Modernizr[featureName] = tests[feature]();
308
309      classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
310    }
311  }
312
313  Modernizr.addTest = function (feature, test) {
314    if (typeof feature == 'object') {
315      for (var key in feature) {
316        if (hasOwnProp(feature, key)) {
317          Modernizr.addTest(key, feature[ key ]);
318        }
319      }
320    } else {
321
322      feature = feature.toLowerCase();
323
324      if (Modernizr[feature] !== undefined) {
325        return Modernizr;
326      }
327
328      test = typeof test == 'function' ? test() : test;
329
330      if (typeof enableClasses !== "undefined" && enableClasses) {
331        docElement.className += ' ' + (test ? '' : 'no-') + feature;
332      }
333      Modernizr[feature] = test;
334
335    }
336
337    return Modernizr;
338  };
339
340
341  setCss('');
342  modElem = inputElem = null;
343
344
345  Modernizr._version = version;
346
347  Modernizr._prefixes = prefixes;
348  Modernizr._domPrefixes = domPrefixes;
349  Modernizr._cssomPrefixes = cssomPrefixes;
350
351  Modernizr.testProp = function (prop) {
352    return testProps([prop]);
353  };
354
355  Modernizr.testAllProps = testPropsAll;
356
357  Modernizr.testStyles = injectElementWithStyles;
358  Modernizr.prefixed = function (prop, obj, elem) {
359    if (!obj) {
360      return testPropsAll(prop, 'pfx');
361    } else {
362      return testPropsAll(prop, obj, elem);
363    }
364  };
365
366  return Modernizr;
367})(window, document);
368var fullScreenApi = {
369      ok: false,
370      is: function () {
371        return false;
372      },
373      request: function () {
374      },
375      cancel: function () {
376      },
377      event: '',
378      prefix: ''
379    },
380    browserPrefixes = 'webkit moz o ms khtml'.split(' ');
381
382// check for native support
383if (typeof document.cancelFullScreen != 'undefined') {
384  fullScreenApi.ok = true;
385} else {
386  // check for fullscreen support by vendor prefix
387  for (var i = 0, il = browserPrefixes.length; i < il; i++) {
388    fullScreenApi.prefix = browserPrefixes[i];
389    if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] != 'undefined') {
390      fullScreenApi.ok = true;
391      break;
392    }
393  }
394}
395
396// update methods to do something useful
397if (fullScreenApi.ok) {
398  fullScreenApi.event = fullScreenApi.prefix + 'fullscreenchange';
399  fullScreenApi.is = function () {
400    switch (this.prefix) {
401      case '':
402        return document.fullScreen;
403      case 'webkit':
404        return document.webkitIsFullScreen;
405      default:
406        return document[this.prefix + 'FullScreen'];
407    }
408  };
409  fullScreenApi.request = function (el) {
410    return (this.prefix === '') ? el.requestFullScreen() : el[this.prefix + 'RequestFullScreen']();
411  };
412  fullScreenApi.cancel = function (el) {
413    return (this.prefix === '') ? document.cancelFullScreen() : document[this.prefix + 'CancelFullScreen']();
414  };
415}
416//fgnass.github.com/spin.js#v1.3.2
417
418/**
419 * Copyright (c) 2011-2013 Felix Gnass
420 * Licensed under the MIT license
421 */
422
423var Spinner,
424    spinnerDefaults = {
425      lines: 12, // The number of lines to draw
426      length: 5, // The length of each line
427      width: 2, // The line thickness
428      radius: 7, // The radius of the inner circle
429      corners: 1, // Corner roundness (0..1)
430      rotate: 15, // The rotation offset
431      color: 'rgba(128, 128, 128, .75)',
432      hwaccel: true
433    },
434    spinnerOverride = {
435      top: 'auto',
436      left: 'auto',
437      className: ''
438    };
439
440(function(root, factory) {
441
442  /* CommonJS */
443  //if (typeof exports == 'object')  module.exports = factory()
444
445  /* AMD module */
446  //else if (typeof define == 'function' && define.amd) define(factory)
447
448  /* Browser global */
449  //else root.Spinner = factory()
450
451  Spinner = factory();
452}
453(this, function() {
454  "use strict";
455
456  var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
457    , animations = {} /* Animation rules keyed by their name */
458    , useCssAnimations /* Whether to use CSS animations or setTimeout */
459
460  /**
461   * Utility function to create elements. If no tag name is given,
462   * a DIV is created. Optionally properties can be passed.
463   */
464  function createEl(tag, prop) {
465    var el = document.createElement(tag || 'div')
466      , n
467
468    for(n in prop) el[n] = prop[n]
469    return el
470  }
471
472  /**
473   * Appends children and returns the parent.
474   */
475  function ins(parent /* child1, child2, ...*/) {
476    for (var i=1, n=arguments.length; i<n; i++)
477      parent.appendChild(arguments[i])
478
479    return parent
480  }
481
482  /**
483   * Insert a new stylesheet to hold the @keyframe or VML rules.
484   */
485  var sheet = (function() {
486    var el = createEl('style', {type : 'text/css'})
487    ins(document.getElementsByTagName('head')[0], el)
488    return el.sheet || el.styleSheet
489  }())
490
491  /**
492   * Creates an opacity keyframe animation rule and returns its name.
493   * Since most mobile Webkits have timing issues with animation-delay,
494   * we create separate rules for each line/segment.
495   */
496  function addAnimation(alpha, trail, i, lines) {
497    var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
498      , start = 0.01 + i/lines * 100
499      , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
500      , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
501      , pre = prefix && '-' + prefix + '-' || ''
502
503    if (!animations[name]) {
504      sheet.insertRule(
505        '@' + pre + 'keyframes ' + name + '{' +
506        '0%{opacity:' + z + '}' +
507        start + '%{opacity:' + alpha + '}' +
508        (start+0.01) + '%{opacity:1}' +
509        (start+trail) % 100 + '%{opacity:' + alpha + '}' +
510        '100%{opacity:' + z + '}' +
511        '}', sheet.cssRules.length)
512
513      animations[name] = 1
514    }
515
516    return name
517  }
518
519  /**
520   * Tries various vendor prefixes and returns the first supported property.
521   */
522  function vendor(el, prop) {
523    var s = el.style
524      , pp
525      , i
526
527    prop = prop.charAt(0).toUpperCase() + prop.slice(1)
528    for(i=0; i<prefixes.length; i++) {
529      pp = prefixes[i]+prop
530      if(s[pp] !== undefined) return pp
531    }
532    if(s[prop] !== undefined) return prop
533  }
534
535  /**
536   * Sets multiple style properties at once.
537   */
538  function css(el, prop) {
539    for (var n in prop)
540      el.style[vendor(el, n)||n] = prop[n]
541
542    return el
543  }
544
545  /**
546   * Fills in default values.
547   */
548  function merge(obj) {
549    for (var i=1; i < arguments.length; i++) {
550      var def = arguments[i]
551      for (var n in def)
552        if (obj[n] === undefined) obj[n] = def[n]
553    }
554    return obj
555  }
556
557  /**
558   * Returns the absolute page-offset of the given element.
559   */
560  function pos(el) {
561    var o = { x:el.offsetLeft, y:el.offsetTop }
562    while((el = el.offsetParent))
563      o.x+=el.offsetLeft, o.y+=el.offsetTop
564
565    return o
566  }
567
568  /**
569   * Returns the line color from the given string or array.
570   */
571  function getColor(color, idx) {
572    return typeof color == 'string' ? color : color[idx % color.length]
573  }
574
575  // Built-in defaults
576
577  var defaults = {
578    lines: 12,            // The number of lines to draw
579    length: 7,            // The length of each line
580    width: 5,             // The line thickness
581    radius: 10,           // The radius of the inner circle
582    rotate: 0,            // Rotation offset
583    corners: 1,           // Roundness (0..1)
584    color: '#000',        // #rgb or #rrggbb
585    direction: 1,         // 1: clockwise, -1: counterclockwise
586    speed: 1,             // Rounds per second
587    trail: 100,           // Afterglow percentage
588    opacity: 1/4,         // Opacity of the lines
589    fps: 20,              // Frames per second when using setTimeout()
590    zIndex: 2e9,          // Use a high z-index by default
591    className: 'spinner', // CSS class to assign to the element
592    top: 'auto',          // center vertically
593    left: 'auto',         // center horizontally
594    position: 'relative'  // element position
595  }
596
597  /** The constructor */
598  function Spinner(o) {
599    if (typeof this == 'undefined') return new Spinner(o)
600    this.opts = merge(o || {}, Spinner.defaults, defaults)
601  }
602
603  // Global defaults that override the built-ins:
604  Spinner.defaults = {}
605
606  merge(Spinner.prototype, {
607
608    /**
609     * Adds the spinner to the given target element. If this instance is already
610     * spinning, it is automatically removed from its previous target b calling
611     * stop() internally.
612     */
613    spin: function(target) {
614      this.stop()
615
616      var self = this
617        , o = self.opts
618        , el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
619        , mid = o.radius+o.length+o.width
620        , ep // element position
621        , tp // target position
622
623      if (target) {
624        target.insertBefore(el, target.firstChild||null)
625        tp = pos(target)
626        ep = pos(el)
627        css(el, {
628          left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
629          top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid)  + 'px'
630        })
631      }
632
633      el.setAttribute('role', 'progressbar')
634      self.lines(el, self.opts)
635
636      if (!useCssAnimations) {
637        // No CSS animation support, use setTimeout() instead
638        var i = 0
639          , start = (o.lines - 1) * (1 - o.direction) / 2
640          , alpha
641          , fps = o.fps
642          , f = fps/o.speed
643          , ostep = (1-o.opacity) / (f*o.trail / 100)
644          , astep = f/o.lines
645
646        ;(function anim() {
647          i++;
648          for (var j = 0; j < o.lines; j++) {
649            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
650
651            self.opacity(el, j * o.direction + start, alpha, o)
652          }
653          self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
654        })()
655      }
656      return self
657    },
658
659    /**
660     * Stops and removes the Spinner.
661     */
662    stop: function() {
663      var el = this.el
664      if (el) {
665        clearTimeout(this.timeout)
666        if (el.parentNode) el.parentNode.removeChild(el)
667        this.el = undefined
668      }
669      return this
670    },
671
672    /**
673     * Internal method that draws the individual lines. Will be overwritten
674     * in VML fallback mode below.
675     */
676    lines: function(el, o) {
677      var i = 0
678        , start = (o.lines - 1) * (1 - o.direction) / 2
679        , seg
680
681      function fill(color, shadow) {
682        return css(createEl(), {
683          position: 'absolute',
684          width: (o.length+o.width) + 'px',
685          height: o.width + 'px',
686          background: color,
687          boxShadow: shadow,
688          transformOrigin: 'left',
689          transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
690          borderRadius: (o.corners * o.width>>1) + 'px'
691        })
692      }
693
694      for (; i < o.lines; i++) {
695        seg = css(createEl(), {
696          position: 'absolute',
697          top: 1+~(o.width/2) + 'px',
698          transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
699          opacity: o.opacity,
700          animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
701        })
702
703        if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
704        ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')))
705      }
706      return el
707    },
708
709    /**
710     * Internal method that adjusts the opacity of a single line.
711     * Will be overwritten in VML fallback mode below.
712     */
713    opacity: function(el, i, val) {
714      if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
715    }
716
717  })
718
719
720  function initVML() {
721
722    /* Utility function to create a VML tag */
723    function vml(tag, attr) {
724      return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
725    }
726
727    // No CSS transforms but VML support, add a CSS rule for VML elements:
728    sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
729
730    Spinner.prototype.lines = function(el, o) {
731      var r = o.length+o.width
732        , s = 2*r
733
734      function grp() {
735        return css(
736          vml('group', {
737            coordsize: s + ' ' + s,
738            coordorigin: -r + ' ' + -r
739          }),
740          { width: s, height: s }
741        )
742      }
743
744      var margin = -(o.width+o.length)*2 + 'px'
745        , g = css(grp(), {position: 'absolute', top: margin, left: margin})
746        , i
747
748      function seg(i, dx, filter) {
749        ins(g,
750          ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
751            ins(css(vml('roundrect', {arcsize: o.corners}), {
752                width: r,
753                height: o.width,
754                left: o.radius,
755                top: -o.width>>1,
756                filter: filter
757              }),
758              vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
759              vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
760            )
761          )
762        )
763      }
764
765      if (o.shadow)
766        for (i = 1; i <= o.lines; i++)
767          seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
768
769      for (i = 1; i <= o.lines; i++) seg(i)
770      return ins(el, g)
771    }
772
773    Spinner.prototype.opacity = function(el, i, val, o) {
774      var c = el.firstChild
775      o = o.shadow && o.lines || 0
776      if (c && i+o < c.childNodes.length) {
777        c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
778        if (c) c.opacity = val
779      }
780    }
781  }
782
783  var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
784
785  if (!vendor(probe, 'transform') && probe.adj) initVML()
786  else useCssAnimations = vendor(probe, 'animation')
787
788  return Spinner
789
790}));
791
792/* Bez v1.0.10-g5ae0136
793 * http://github.com/rdallasgray/bez
794 *
795 * A plugin to convert CSS3 cubic-bezier co-ordinates to jQuery-compatible easing functions
796 *
797 * With thanks to Nikolay Nemshilov for clarification on the cubic-bezier maths
798 * See http://st-on-it.blogspot.com/2011/05/calculating-cubic-bezier-function.html
799 *
800 * Copyright 2011 Robert Dallas Gray. All rights reserved.
801 * Provided under the FreeBSD license: https://github.com/rdallasgray/bez/blob/master/LICENSE.txt
802 */
803function bez (coOrdArray) {
804  var encodedFuncName = "bez_" + $.makeArray(arguments).join("_").replace(".", "p");
805  if (typeof $['easing'][encodedFuncName] !== "function") {
806    var polyBez = function (p1, p2) {
807      var A = [null, null],
808          B = [null, null],
809          C = [null, null],
810          bezCoOrd = function (t, ax) {
811            C[ax] = 3 * p1[ax];
812            B[ax] = 3 * (p2[ax] - p1[ax]) - C[ax];
813            A[ax] = 1 - C[ax] - B[ax];
814            return t * (C[ax] + t * (B[ax] + t * A[ax]));
815          },
816          xDeriv = function (t) {
817            return C[0] + t * (2 * B[0] + 3 * A[0] * t);
818          },
819          xForT = function (t) {
820            var x = t, i = 0, z;
821            while (++i < 14) {
822              z = bezCoOrd(x, 0) - t;
823              if (Math.abs(z) < 1e-3) break;
824              x -= z / xDeriv(x);
825            }
826            return x;
827          };
828      return function (t) {
829        return bezCoOrd(xForT(t), 1);
830      }
831    };
832    $['easing'][encodedFuncName] = function (x, t, b, c, d) {
833      return c * polyBez([coOrdArray[0], coOrdArray[1]], [coOrdArray[2], coOrdArray[3]])(t / d) + b;
834    }
835  }
836  return encodedFuncName;
837}
838var $WINDOW = $(window),
839    $DOCUMENT = $(document),
840    $HTML,
841    $BODY,
842
843    QUIRKS_FORCE = location.hash.replace('#', '') === 'quirks',
844    TRANSFORMS3D = Modernizr.csstransforms3d,
845    CSS3 = TRANSFORMS3D && !QUIRKS_FORCE,
846    COMPAT = TRANSFORMS3D || document.compatMode === 'CSS1Compat',
847    FULLSCREEN = fullScreenApi.ok,
848
849    MOBILE = navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|W
849indows Phone/i),
850    SLOW = !CSS3 || MOBILE,
851
852    MS_POINTER = navigator.msPointerEnabled,
853
854    WHEEL = "onwheel" in document.createElement("div") ? "wheel" : document.onmousewheel !== undefined ? "mousewheel" : "DOMMouseScroll",
855
856    TOUCH_TIMEOUT = 250,
857    TRANSITION_DURATION = 300,
858
859    SCROLL_LOCK_TIMEOUT = 1400,
860
861    AUTOPLAY_INTERVAL = 5000,
862    MARGIN = 2,
863    THUMB_SIZE = 64,
864
865    WIDTH = 500,
866    HEIGHT = 333,
867
868    STAGE_FRAME_KEY = '$stageFrame',
869    NAV_DOT_FRAME_KEY = '$navDotFrame',
870    NAV_THUMB_FRAME_KEY = '$navThumbFrame',
871
872    AUTO = 'auto',
873
874    BEZIER = bez([.1, 0, .25, 1]),
875
876    MAX_WIDTH = 99999,
877
878    FIFTYFIFTY = '50%',
879
880    OPTIONS = {
881      // dimensions
882      width: null, // 500 || '100%'
883      minwidth: null,
884      maxwidth: '100%', // '100%'
885      height: null,
886      minheight: null,
887      maxheight: null,
888
889      ratio: null, // '16/9' || 500/333 || 1.5
890
891      margin: MARGIN,
892      glimpse: 0,
893
894      fit: 'contain', // 'cover' || 'scaledown' || 'none'
895
896      position: FIFTYFIFTY,
897      thumbposition: FIFTYFIFTY,
898
899      // navigation, thumbs
900      nav: 'dots', // 'thumbs' || false
901      navposition: 'bottom', // 'top'
902      navwidth: null,
903      thumbwidth: THUMB_SIZE,
904      thumbheight: THUMB_SIZE,
905      thumbmargin: MARGIN,
906      thumbborderwidth: MARGIN,
907      thumbfit: 'cover', // 'contain' || 'scaledown' || 'none'
908
909      allowfullscreen: false, // true || 'native'
910
911      transition: 'slide', // 'crossfade' || 'dissolve'
912      clicktransition: null,
913      transitionduration: TRANSITION_DURATION,
914
915      captions: true,
916
917      hash: false,
918      startindex: 0,
919
920      loop: false,
921
922      autoplay: false,
923      stopautoplayontouch: true,
924
925      keyboard: false,
926
927      arrows: true,
928      click: true,
929      swipe: true,
930      trackpad: false,
931
932      enableifsingleframe: false,
933
934      controlsonstart: true,
935
936      shuffle: false,
937
938      direction: 'ltr', // 'rtl'
939
940      shadows: true,
941      spinner: null
942    },
943
944    KEYBOARD_OPTIONS = {
945      left: true,
946      right: true,
947      down: false,
948      up: false,
949      space: false,
950      home: false,
951      end: false
952    };
953function noop () {}
954
955function minMaxLimit (value, min, max) {
956  return Math.max(isNaN(min) ? -Infinity : min, Math.min(isNaN(max) ? Infinity : max, value));
957}
958
959function readTransform (css) {
960  return css.match(/ma/) && css.match(/-?\d+(?!d)/g)[css.match(/3d/) ? 12 : 4];
961}
962
963function readPosition ($el) {
964  if (CSS3) {
965    return +readTransform($el.css('transform'));
966  } else {
967    return +$el.css('left').replace('px', '');
968  }
969}
970
971function getTranslate (pos/*, _001*/) {
972  var obj = {};
973  if (CSS3) {
974    obj.transform = 'translate3d(' + (pos/* + (_001 ? 0.001 : 0)*/) + 'px,0,0)'; // 0.001 to remove Retina artifacts
975  } else {
976    obj.left = pos;
977  }
978  return obj;
979}
980
981function getDuration (time) {
982  return {'transition-duration': time + 'ms'};
983}
984
985function unlessNaN (value, alternative) {
986  return isNaN(value) ? alternative : value;
987}
988
989function numberFromMeasure (value, measure) {
990  return unlessNaN(+String(value).replace(measure || 'px', ''));
991}
992
993function numberFromPercent (value) {
994  return /%$/.test(value) ? numberFromMeasure(value, '%') : undefined;
995}
996
997function numberFromWhatever (value, whole) {
998  return unlessNaN(numberFromPercent(value) / 100 * whole, numberFromMeasure(value));
999}
1000
1001function measureIsValid (value) {
1002  return (!isNaN(numberFromMeasure(value)) || !isNaN(numberFromMeasure(value, '%'))) && value;
1003}
1004
1005function getPosByIndex (index, side, margin, baseIndex) {
1006  ////console.log('getPosByIndex', index, side, margin, baseIndex);
1007  ////console.log((index - (baseIndex || 0)) * (side + (margin || 0)));
1008
1009  return (index - (baseIndex || 0)) * (side + (margin || 0));
1010}
1011
1012function getIndexByPos (pos, side, margin, baseIndex) {
1013  return -Math.round(pos / (side + (margin || 0)) - (baseIndex || 0));
1014}
1015
1016function bindTransitionEnd ($el) {
1017  var elData = $el.data();
1018
1019  if (elData.tEnd) return;
1020
1021  var el = $el[0],
1022      transitionEndEvent = {
1023        WebkitTransition: 'webkitTransitionEnd',
1024        MozTransition: 'transitionend',
1025        OTransition: 'oTransitionEnd otransitionend',
1026        msTransition: 'MSTransitionEnd',
1027        transition: 'transitionend'
1028      };
1029  addEvent(el, transitionEndEvent[Modernizr.prefixed('transition')], function (e) {
1030    elData.tProp && e.propertyName.match(elData.tProp) && elData.onEndFn();
1031  });
1032  elData.tEnd = true;
1033}
1034
1035function afterTransition ($el, property, fn, time) {
1036  var ok,
1037      elData = $el.data();
1038
1039  if (elData) {
1040    elData.onEndFn = function () {
1041      if (ok) return;
1042      ok = true;
1043      clearTimeout(elData.tT);
1044      fn();
1045    };
1046    elData.tProp = property;
1047
1048    // Passive call, just in case of fail of native transition-end event
1049    clearTimeout(elData.tT);
1050    elData.tT = setTimeout(function () {
1051      elData.onEndFn();
1052    }, time * 1.5);
1053
1054    bindTransitionEnd($el);
1055  }
1056}
1057
1058
1059function stop ($el, left/*, _001*/) {
1060  if ($el.length) {
1061    var elData = $el.data();
1062    if (CSS3) {
1063      $el.css(getDuration(0));
1064      elData.onEndFn = noop;
1065      clearTimeout(elData.tT);
1066    } else {
1067      $el.stop();
1068    }
1069    var lockedLeft = getNumber(left, function () {
1070      return readPosition($el);
1071    });
1072
1073    $el.css(getTranslate(lockedLeft/*, _001*/));//.width(); // `.width()` for reflow
1074    return lockedLeft;
1075  }
1076}
1077
1078function getNumber () {
1079  var number;
1080  for (var _i = 0, _l = arguments.length; _i < _l; _i++) {
1081    number = _i ? arguments[_i]() : arguments[_i];
1082    if (typeof number === 'number') {
1083      break;
1084    }
1085  }
1086
1087  return number;
1088}
1089
1090function edgeResistance (pos, edge) {
1091  return Math.round(pos + ((edge - pos) / 1.5));
1092}
1093
1094function getProtocol () {
1095  getProtocol.p = getProtocol.p || (location.protocol === 'https:' ? 'https://' : 'http://');
1096  return getProtocol.p;
1097}
1098
1099function parseHref (href) {
1100  var a = document.createElement('a');
1101  a.href = href;
1102  return a;
1103}
1104
1105function findVideoId (href, forceVideo) {
1106  if (typeof href !== 'string') return href;
1107  href = parseHref(href);
1108
1109  var id,
1110      type;
1111
1112  if (href.host.match(/youtube\.com/) && href.search) {
1113    //.log();
1114    id = href.search.split('v=')[1];
1115    if (id) {
1116      var ampersandPosition = id.indexOf('&');
1117      if (ampersandPosition !== -1) {
1118        id = id.substring(0, ampersandPosition);
1119      }
1120      type = 'youtube';
1121    }
1122  } else if (href.host.match(/youtube\.com|youtu\.be/)) {
1123    id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, '');
1124    type = 'youtube';
1125  } else if (href.host.match(/vimeo\.com/)) {
1126    type = 'vimeo';
1127    id = href.pathname.replace(/^\/(video\/)?/, '').replace(/\/.*/, '');
1128  }
1129
1130  if ((!id || !type) && forceVideo) {
1131    id = href.href;
1132    type = 'custom';
1133  }
1134
1135  return id ? {id: id, type: type, s: href.search.replace(/^\?/, ''), p: getProtocol()} : false;
1136}
1137
1138function getVideoThumbs (dataFrame, data, fotorama) {
1139  var img, thumb, video = dataFrame.video;
1140  if (video.type === 'youtube') {
1141    thumb = getProtocol() + 'img.youtube.com/vi/' + video.id + '/default.jpg';
1142    img = thumb.replace(/\/default.jpg$/, '/hqdefault.jpg');
1143    dataFrame.thumbsReady = true;
1144  } else if (video.type === 'vimeo') {
1145    $.ajax({
1146      url: getProtocol() + 'vimeo.com/api/v2/video/' + video.id + '.json',
1147      dataType: 'jsonp',
1148      success: function (json) {
1149        dataFrame.thumbsReady = true;
1150        updateData(data, {img: json[0].thumbnail_large, thumb: json[0].thumbnail_small}, dataFrame.i, fotorama);
1151      }
1152    });
1153  } else {
1154    dataFrame.thumbsReady = true;
1155  }
1156
1157  return {
1158    img: img,
1159    thumb: thumb
1160  }
1161}
1162
1163function updateData (data, _dataFrame, i, fotorama) {
1164  for (var _i = 0, _l = data.length; _i < _l; _i++) {
1165    var dataFrame = data[_i];
1166
1167    if (dataFrame.i === i && dataFrame.thumbsReady) {
1168      var clear = {videoReady: true};
1169      clear[STAGE_FRAME_KEY] = clear[NAV_THUMB_FRAME_KEY] = clear[NAV_DOT_FRAME_KEY] = false;
1170
1171      fotorama.splice(_i, 1, $.extend(
1172          {},
1173          dataFrame,
1174          clear,
1175          _dataFrame
1176      ));
1177
1178      break;
1179    }
1180  }
1181}
1182
1183function getDataFromHtml ($el) {
1184  var data = [];
1185
1186  function getDataFromImg ($img, imgData, checkVideo) {
1187    var $child = $img.children('img').eq(0),
1188        _imgHref = $img.attr('href'),
1189        _imgSrc = $img.attr('src'),
1190        _thumbSrc = $child.attr('src'),
1191        _video = imgData.video,
1192        video = checkVideo ? findVideoId(_imgHref, _video === true) : false;
1193
1194    if (video) {
1195      _imgHref = false;
1196    } else {
1197      video = _video;
1198    }
1199
1200    getDimensions($img, $child, $.extend(imgData, {
1201      video: video,
1202      img: imgData.img || _imgHref || _imgSrc || _thumbSrc,
1203      thumb: imgData.thumb || _thumbSrc || _imgSrc || _imgHref
1204    }));
1205  }
1206
1207  function getDimensions ($img, $child, imgData) {
1208    var separateThumbFLAG = imgData.thumb && imgData.img !== imgData.thumb,
1209        width = numberFromMeasure(imgData.width || $img.attr('width')),
1210        height = numberFromMeasure(imgData.height || $img.attr('height'));
1211
1212    $.extend(imgData, {
1213      width: width,
1214      height: height,
1215      thumbratio: getRatio(imgData.thumbratio || (numberFromMeasure(imgData.thumbwidth || ($child && $child.attr('width')) || separateThumbFLAG || width) / numberFromMeasure(imgData.thumbheight || ($child && $child.attr('height')) || separateThumbFLAG || height)))
1216    });
1217  }
1218
1219  $el.children().each(function () {
1220    var $this = $(this),
1221        dataFrame = optionsToLowerCase($.extend($this.data(), {id: $this.attr('id')}));
1222    if ($this.is('a, img')) {
1223      getDataFromImg($this, dataFrame, true);
1224    } else if (!$this.is(':empty')) {
1225      getDimensions($this, null, $.extend(dataFrame, {
1226        html: this,
1227        _html: $this.html() // Because of IE
1228      }));
1229    } else return;
1230
1231    data.push(dataFrame);
1232  });
1233
1234  return data;
1235}
1236
1237function isHidden (el) {
1238  return el.offsetWidth === 0 && el.offsetHeight === 0;
1239}
1240
1241function isDetached (el) {
1242  return !$.contains(document.documentElement, el);
1243}
1244
1245function waitFor (test, fn, timeout, i) {
1246  if (!waitFor.i) {
1247    waitFor.i = 1;
1248    waitFor.ii = [true];
1249  }
1250
1251  i = i || waitFor.i;
1252
1253  if (typeof waitFor.ii[i] === 'undefined') {
1254    waitFor.ii[i] = true;
1255  }
1256
1257  if (test()) {
1258    fn();
1259  } else {
1260    waitFor.ii[i] && setTimeout(function () {
1261      waitFor.ii[i] && waitFor(test, fn, timeout, i);
1262    }, timeout || 100);
1263  }
1264
1265  return waitFor.i++;
1266}
1267
1268waitFor.stop = function (i) {
1269  waitFor.ii[i] = false;
1270};
1271
1272function setHash (hash) {
1273  //////console.time('setHash ' + hash);
1274  location.replace(location.protocol
1275      + '//'
1276      + location.host
1277      + location.pathname.replace(/^\/?/, '/')
1278      + location.search
1279      + '#' + hash);
1280  //////console.timeEnd('setHash ' + hash);
1281}
1282
1283function fit ($el, measuresToFit, method, position) {
1284  var elData = $el.data(),
1285      measures = elData.measures;
1286
1287  if (measures && (!elData.l ||
1288      elData.l.W !== measures.width ||
1289      elData.l.H !== measures.height ||
1290      elData.l.r !== measures.ratio ||
1291      elData.l.w !== measuresToFit.w ||
1292      elData.l.h !== measuresToFit.h ||
1293      elData.l.m !== method ||
1294      elData.l.p !== position)) {
1295
1296    //console.log('fit');
1297
1298    var width = measures.width,
1299        height = measures.height,
1300        ratio = measuresToFit.w / measuresToFit.h,
1301        biggerRatioFLAG = measures.ratio >= ratio,
1302        fitFLAG = method === 'scaledown',
1303        containFLAG = method === 'contain',
1304        coverFLAG = method === 'cover',
1305        pos = parsePosition(position);
1306
1307    if (biggerRatioFLAG && (fitFLAG || containFLAG) || !biggerRatioFLAG && coverFLAG) {
1308      width = minMaxLimit(measuresToFit.w, 0, fitFLAG ? width : Infinity);
1309      height = width / measures.ratio;
1310    } else if (biggerRatioFLAG && coverFLAG || !biggerRatioFLAG && (fitFLAG || containFLAG)) {
1311      height = minMaxLimit(measuresToFit.h, 0, fitFLAG ? height : Infinity);
1312      width = height * measures.ratio;
1313    }
1314
1315    $el.css({
1316      width: width,
1317      height: height,
1318      left: numberFromWhatever(pos.x, measuresToFit.w - width),
1319      top: numberFromWhatever(pos.y, measuresToFit.h- height)
1320    });
1321
1322    elData.l = {
1323      W: measures.width,
1324      H: measures.height,
1325      r: measures.ratio,
1326      w: measuresToFit.w,
1327      h: measuresToFit.h,
1328      m: method,
1329      p: position
1330    };
1331  }
1332
1333  return true;
1334}
1335
1336function setStyle ($el, style) {
1337  var el = $el[0];
1338  if (el.styleSheet) {
1339    el.styleSheet.cssText = style;
1340  } else {
1341    $el.html(style);
1342  }
1343}
1344
1345function findShadowEdge (pos, min, max) {
1346  return min === max ? false : pos <= min ? 'left' : pos >= max ? 'right' : 'left right';
1347}
1348
1349function getIndexFromHash (hash, data, ok, startindex) {
1350  if (!ok) return false;
1351  if (!isNaN(hash)) return hash - (startindex ? 0 : 1);
1352
1353  var index;
1354
1355  for (var _i = 0, _l = data.length; _i < _l; _i++) {
1356    var dataFrame = data[_i];
1357
1358    if (dataFrame.id === hash) {
1359      index = _i;
1360      break;
1361    }
1362  }
1363
1364  return index;
1365}
1366
1367function smartClick ($el, fn, _options) {
1368  _options = _options || {};
1369
1370  $el.each(function () {
1371    var $this = $(this),
1372        thisData = $this.data(),
1373        startEvent;
1374
1375    if (thisData.clickOn) return;
1376
1377    thisData.clickOn = true;
1378
1379    $.extend(touch($this, {
1380      onStart: function (e) {
1381        startEvent = e;
1382        (_options.onStart || noop).call(this, e);
1383      },
1384      onMove: _options.onMove || noop,
1385      onTouchEnd: _options.onTouchEnd || noop,
1386      onEnd: function (result) {
1387        ////console.log('smartClick → result.moved', result.moved);
1388        if (result.moved) return;
1389        fn.call(this, startEvent);
1390      }
1391    }), {noMove: true});
1392  });
1393}
1394
1395function div (classes, child) {
1396  return '<div class="' + classes + '">' + (child || '') + '</div>';
1397}
1398
1399// Fisher–Yates Shuffle
1400// http://bost.ocks.org/mike/shuffle/
1401function shuffle (array) {
1402  // While there remain elements to shuffle
1403  var l = array.length;
1404  while (l) {
1405    // Pick a remaining element
1406    var i = Math.floor(Math.random() * l--);
1407
1408    // And swap it with the current element
1409    var t = array[l];
1410    array[l] = array[i];
1411    array[i] = t;
1412  }
1413
1414  return array;
1415}
1416
1417function clone (array) {
1418  return Object.prototype.toString.call(array) == '[object Array]'
1419      && $.map(array, function (frame) {
1420       return $.extend({}, frame);
1421      });
1422}
1423
1424function lockScroll ($el, left, top) {
1425  $el
1426    .scrollLeft(left || 0)
1427    .scrollTop(top || 0);
1428}
1429
1430function optionsToLowerCase (options) {
1431  if (options) {
1432    var opts = {};
1433    $.each(options, function (key, value) {
1434      opts[key.toLowerCase()] = value;
1435    });
1436
1437    return opts;
1438  }
1439}
1440
1441function getRatio (_ratio) {
1442  if (!_ratio) return;
1443  var ratio = +_ratio;
1444  if (!isNaN(ratio)) {
1445    return ratio;
1446  } else {
1447    ratio = _ratio.split('/');
1448    return +ratio[0] / +ratio[1] || undefined;
1449  }
1450}
1451
1452function addEvent (el, e, fn, bool) {
1453  if (!e) return;
1454  el.addEventListener ? el.addEventListener(e, fn, !!bool) : el.attachEvent('on'+e, fn);
1455}
1456
1457function elIsDisabled (el) {
1458  return !!el.getAttribute('disabled');
1459}
1460
1461function disableAttr (FLAG) {
1462  return {tabindex: FLAG * -1 + '', disabled: FLAG};
1463}
1464
1465function addEnterUp (el, fn) {
1466  addEvent(el, 'keyup', function (e) {
1467    elIsDisabled(el) || e.keyCode == 13 && fn.call(el, e);
1468  });
1469}
1470
1471function addFocus (el, fn) {
1472  addEvent(el, 'focus', el.onfocusin = function (e) {
1473    fn.call(el, e);
1474  }, true);
1475}
1476
1477function stopEvent (e, stopPropagation) {
1478  e.preventDefault ? e.preventDefault() : (e.returnValue = false);
1479  stopPropagation && e.stopPropagation && e.stopPropagation();
1480}
1481
1482function getDirectionSign (forward) {
1483  return forward ? '>' : '<';
1484}
1485
1486function parsePosition (rule) {
1487  rule = (rule + '').split(/\s+/);
1488  return {
1489    x: measureIsValid(rule[0]) || FIFTYFIFTY,
1490    y: measureIsValid(rule[1]) || FIFTYFIFTY
1491  }
1492}
1493function slide ($el, options) {
1494  var elData = $el.data(),
1495      elPos = Math.round(options.pos),
1496      onEndFn = function () {
1497        elData.sliding = false;
1498        (options.onEnd || noop)();
1499      };
1500
1501  if (typeof options.overPos !== 'undefined' && options.overPos !== options.pos) {
1502    elPos = options.overPos;
1503    onEndFn = function () {
1504      slide($el, $.extend({}, options, {overPos: options.pos, time: Math.max(TRANSITION_DURATION, options.time / 2)}))
1505    };
1506  }
1507
1508  ////////console.time('var translate = $.extend');
1509  var translate = $.extend(getTranslate(elPos/*, options._001*/), options.width && {width: options.width});
1510  ////////console.timeEnd('var translate = $.extend');
1511
1512  elData.sliding = true;
1513
1514  if (CSS3) {
1515    $el.css($.extend(getDuration(options.time), translate));
1516    if (options.time > 10) {
1517      ////////console.time('afterTransition');
1518      afterTransition($el, 'transform', onEndFn, options.time);
1519      ////////console.timeEnd('afterTransition');
1520    } else {
1521      onEndFn();
1522    }
1523  } else {
1524    $el.stop().animate(translate, options.time, BEZIER, onEndFn);
1525  }
1526}
1527
1528function fade ($el1, $el2, $frames, options, fadeStack, chain) {
1529  var chainedFLAG = typeof chain !== 'undefined';
1530  if (!chainedFLAG) {
1531    fadeStack.push(arguments);
1532    Array.prototype.push.call(arguments, fadeStack.length);
1533    if (fadeStack.length > 1) return;
1534  }
1535
1536  $el1 = $el1 || $($el1);
1537  $el2 = $el2 || $($el2);
1538
1539  var _$el1 = $el1[0],
1540      _$el2 = $el2[0],
1541      crossfadeFLAG = options.method === 'crossfade',
1542      onEndFn = function () {
1543        if (!onEndFn.done) {
1544          onEndFn.done = true;
1545          var args = (chainedFLAG || fadeStack.shift()) && fadeStack.shift();
1546          args && fade.apply(this, args);
1547          (options.onEnd || noop)(!!args);
1548        }
1549      },
1550      time = options.time / (chain || 1);
1551
1552  $frames.removeClass(fadeRearClass + ' ' + fadeFrontClass);
1553
1554  $el1
1555      .stop()
1556      .addClass(fadeRearClass);
1557  $el2
1558      .stop()
1559      .addClass(fadeFrontClass);
1560
1561  crossfadeFLAG && _$el2 && $el1.fadeTo(0, 0);
1562
1563  $el1.fadeTo(crossfadeFLAG ? time : 0, 1, crossfadeFLAG && onEndFn);
1564  $el2.fadeTo(time, 0, onEndFn);
1565
1566  (_$el1 && crossfadeFLAG) || _$el2 || onEndFn();
1567}
1568var lastEvent,
1569    moveEventType,
1570    preventEvent,
1571    preventEventTimeout;
1572
1573function extendEvent (e) {
1574  var touch = (e.touches || [])[0] || e;
1575  e._x = touch.pageX;
1576  e._y = touch.clientY;
1577  e._now = $.now();
1578}
1579
1580function touch ($el, options) {
1581  var el = $el[0],
1582      tail = {},
1583      touchEnabledFLAG,
1584      startEvent,
1585      $target,
1586      controlTouch,
1587      touchFLAG,
1588      targetIsSelectFLAG,
1589      targetIsLinkFlag,
1590      tolerance,
1591      moved;
1592
1593  function onStart (e) {
1594    $target = $(e.target);
1595    tail.checked = targetIsSelectFLAG = targetIsLinkFlag = moved = false;
1596
1597    if (touchEnabledFLAG
1598        || tail.flow
1599        || (e.touches && e.touches.length > 1)
1600        || e.which > 1
1601        || (lastEvent && lastEvent.type !== e.type && preventEvent)
1602        || (targetIsSelectFLAG = options.select && $target.is(options.select, el))) return targetIsSelectFLAG;
1603
1604    touchFLAG = e.type === 'touchstart';
1605    targetIsLinkFlag = $target.is('a, a *', el);
1606    controlTouch = tail.control;
1607
1608    tolerance = (tail.noMove || tail.noSwipe || controlTouch) ? 16 : !tail.snap ? 4 : 0;
1609
1610    extendEvent(e);
1611
1612    startEvent = lastEvent = e;
1613    moveEventType = e.type.replace(/down|start/, 'move').replace(/Down/, 'Move');
1614
1615    (options.onStart || noop).call(el, e, {control: controlTouch, $target: $target});
1616
1617    touchEnabledFLAG = tail.flow = true;
1618
1619    if (!touchFLAG || tail.go) stopEvent(e);
1620  }
1621
1622  function onMove (e) {
1623    if ((e.touches && e.touches.length > 1)
1624        || (MS_POINTER && !e.isPrimary)
1625        || moveEventType !== e.type
1626        || !touchEnabledFLAG) {
1627      touchEnabledFLAG && onEnd();
1628      (options.onTouchEnd || noop)();
1629      return;
1630    }
1631
1632    extendEvent(e);
1633
1634    var xDiff = Math.abs(e._x - startEvent._x), // opt _x → _pageX
1635        yDiff = Math.abs(e._y - startEvent._y),
1636        xyDiff = xDiff - yDiff,
1637        xWin = (tail.go || tail.x || xyDiff >= 0) && !tail.noSwipe,
1638        yWin = xyDiff < 0;
1639
1640    if (touchFLAG && !tail.checked) {
1641      if (touchEnabledFLAG = xWin) {
1642        stopEvent(e);
1643      }
1644    } else {
1645      ////console.log('onMove e.preventDefault');
1646      stopEvent(e);
1647      (options.onMove || noop).call(el, e, {touch: touchFLAG});
1648    }
1649
1650    if (!moved && Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2)) > tolerance) {
1651      moved = true;
1652    }
1653
1654    tail.checked = tail.checked || xWin || yWin;
1655  }
1656
1657  function onEnd (e) {
1658    //////console.time('touch.js onEnd');
1659
1660    (options.onTouchEnd || noop)();
1661
1662    var _touchEnabledFLAG = touchEnabledFLAG;
1663    tail.control = touchEnabledFLAG = false;
1664
1665    if (_touchEnabledFLAG) {
1666      tail.flow = false;
1667    }
1668
1669    if (!_touchEnabledFLAG || (targetIsLinkFlag && !tail.checked)) return;
1670
1671    e && stopEvent(e);
1672
1673    preventEvent = true;
1674    clearTimeout(preventEventTimeout);
1675    preventEventTimeout = setTimeout(function () {
1676      preventEvent = false;
1677    }, 1000);
1678
1679    (options.onEnd || noop).call(el, {moved: moved, $target: $target, control: controlTouch, touch: touchFLAG, startEvent: startEvent, aborted: !e || e.type === 'MSPointerCancel'});
1680    //////console.timeEnd('touch.js onEnd');
1681  }
1682
1683  function onOtherStart () {
1684    if (tail.flow) return;
1685    setTimeout(function () {
1686      tail.flow = true;
1687    }, 10);
1688  }
1689
1690  function onOtherEnd () {
1691    if (!tail.flow) return;
1692    setTimeout(function () {
1693      tail.flow = false;
1694    }, TOUCH_TIMEOUT);
1695  }
1696
1697  if (MS_POINTER) {
1698    addEvent(el, 'MSPointerDown', onStart);
1699    addEvent(document, 'MSPointerMove', onMove);
1700    addEvent(document,'MSPointerCancel', onEnd);
1701    addEvent(document, 'MSPointerUp', onEnd);
1702  } else {
1703    addEvent(el, 'touchstart', onStart);
1704    addEvent(el, 'touchmove', onMove);
1705    addEvent(el, 'touchend', onEnd);
1706
1707    addEvent(document, 'touchstart', onOtherStart);
1708    addEvent(document, 'touchend', onOtherEnd);
1709    addEvent(document, 'touchcancel', onOtherEnd);
1710
1711    $WINDOW.on('scroll', onOtherEnd);
1712
1713    $el.on('mousedown', onStart);
1714    $DOCUMENT
1715        .on('mousemove', onMove)
1716        .on('mouseup', onEnd);
1717  }
1718
1719  $el.on('click', 'a', function (e) {
1720    tail.checked && stopEvent(e);
1721  });
1722
1723  return tail;
1724}
1725
1726function moveOnTouch ($el, options) {
1727  var el = $el[0],
1728      elData = $el.data(),
1729      tail = {},
1730      startCoo,
1731      coo,
1732      startElPos,
1733      moveElPos,
1734      edge,
1735      moveTrack,
1736      startTime,
1737      endTime,
1738      min,
1739      max,
1740      snap,
1741      slowFLAG,
1742      controlFLAG,
1743      moved,
1744      tracked;
1745
1746  function startTracking (e, noStop) {
1747    tracked = true;
1748    startCoo = coo = e._x;
1749    startTime = e._now;
1750
1751    moveTrack = [
1752      [startTime, startCoo]
1753    ];
1754
1755    startElPos = moveElPos = tail.noMove || noStop ? 0 : stop($el, (options.getPos || noop)()/*, options._001*/);
1756
1757    (options.onStart || noop).call(el, e);
1758  }
1759
1760  function onStart (e, result) {
1761    min = tail.min;
1762    max = tail.max;
1763    snap = tail.snap;
1764
1765    slowFLAG = e.altKey;
1766    tracked = moved = false;
1767
1768    controlFLAG = result.control;
1769
1770    if (!controlFLAG && !elData.sliding) {
1771      startTracking(e);
1772    }
1773  }
1774
1775  function onMove (e, result) {
1776    if (!tail.noSwipe) {
1777      if (!tracked) {
1778        startTracking(e);
1779      }
1780
1781      coo = e._x;
1782
1783      moveTrack.push([e._now, coo]);
1784
1785      moveElPos = startElPos - (startCoo - coo);
1786
1787      edge = findShadowEdge(moveElPos, min, max);
1788
1789      if (moveElPos <= min) {
1790        moveElPos = edgeResistance(moveElPos, min);
1791      } else if (moveElPos >= max) {
1792        moveElPos = edgeResistance(moveElPos, max);
1793      }
1794
1795      if (!tail.noMove) {
1796        $el.css(getTranslate(moveElPos/*, options._001*/));
1797        if (!moved) {
1798          moved = true;
1799          // only for mouse
1800          result.touch || MS_POINTER || $el.addClass(grabbingClass);
1801        }
1802
1803        (options.onMove || noop).call(el, e, {pos: moveElPos, edge: edge});
1804      }
1805    }
1806  }
1807
1808  function onEnd (result) {
1809    //////console.time('moveontouch.js onEnd');
1810    if (tail.noSwipe && result.moved) return;
1811
1812    if (!tracked) {
1813      startTracking(result.startEvent, true);
1814    }
1815
1816    ////console.log('onEnd');
1817
1818    result.touch || MS_POINTER || $el.removeClass(grabbingClass);
1819
1820    endTime = $.now();
1821
1822    var _backTimeIdeal = endTime - TOUCH_TIMEOUT,
1823        _backTime,
1824        _timeDiff,
1825        _timeDiffLast,
1826        backTime = null,
1827        backCoo,
1828        virtualPos,
1829        limitPos,
1830        newPos,
1831        overPos,
1832        time = TRANSITION_DURATION,
1833        speed,
1834        friction = options.friction;
1835
1836    for (var _i = moveTrack.length - 1; _i >= 0; _i--) {
1837      _backTime = moveTrack[_i][0];
1838      _timeDiff = Math.abs(_backTime - _backTimeIdeal);
1839      if (backTime === null || _timeDiff < _timeDiffLast) {
1840        backTime = _backTime;
1841        backCoo = moveTrack[_i][1];
1842      } else if (backTime === _backTimeIdeal || _timeDiff > _timeDiffLast) {
1843        break;
1844      }
1845      _timeDiffLast = _timeDiff;
1846    }
1847
1848    newPos = minMaxLimit(moveElPos, min, max);
1849
1850    var cooDiff = backCoo - coo,
1851        forwardFLAG = cooDiff >= 0,
1852        timeDiff = endTime - backTime,
1853        longTouchFLAG = timeDiff > TOUCH_TIMEOUT,
1854        swipeFLAG = !longTouchFLAG && moveElPos !== startElPos && newPos === moveElPos;
1855
1856    if (snap) {
1857      newPos = minMaxLimit(Math[swipeFLAG ? (forwardFLAG ? 'floor' : 'ceil') : 'round'](moveElPos / snap) * snap, min, max);
1858      min = max = newPos;
1859    }
1860
1861    if (swipeFLAG && (snap || newPos === moveElPos)) {
1862      speed = -(cooDiff / timeDiff);
1863      time *= minMaxLimit(Math.abs(speed), options.timeLow, options.timeHigh);
1864      virtualPos = Math.round(moveElPos + speed * time / friction);
1865
1866      if (!snap) {
1867        newPos = virtualPos;
1868      }
1869
1870      if (!forwardFLAG && virtualPos > max || forwardFLAG && virtualPos < min) {
1871        limitPos = forwardFLAG ? min : max;
1872        overPos = virtualPos - limitPos;
1873        if (!snap) {
1874          newPos = limitPos;
1875        }
1876        overPos = minMaxLimit(newPos + overPos * .03, limitPos - 50, limitPos + 50);
1877        time = Math.abs((moveElPos - overPos) / (speed / friction));
1878      }
1879    }
1880
1881    time *= slowFLAG ? 10 : 1;
1882
1883    (options.onEnd || noop).call(el, $.extend(result, {moved: result.moved || longTouchFLAG && snap, pos: moveElPos, newPos: newPos, overPos: overPos, time: time}));
1884  }
1885
1886  tail = $.extend(touch(options.$wrap, $.extend({}, options, {
1887    onStart: onStart,
1888    onMove: onMove,
1889    onEnd: onEnd
1890  })), tail);
1891
1892  return tail;
1893}
1894function wheel ($el, options) {
1895  var el = $el[0],
1896      lockFLAG,
1897      lastDirection,
1898      lastNow,
1899      tail = {
1900        prevent: {}
1901      };
1902
1903  addEvent(el, WHEEL, function (e) {
1904    var yDelta = e.wheelDeltaY || -1 * e.deltaY || 0,
1905        xDelta = e.wheelDeltaX || -1 * e.deltaX || 0,
1906        xWin = Math.abs(xDelta) && !Math.abs(yDelta),
1907        direction = getDirectionSign(xDelta < 0),
1908        sameDirection = lastDirection === direction,
1909        now = $.now(),
1910        tooFast = now - lastNow < TOUCH_TIMEOUT;
1911
1912    lastDirection = direction;
1913    lastNow = now;
1914
1915    if (!xWin || !tail.ok || tail.prevent[direction] && !lockFLAG) {
1916      return;
1917    } else {
1918      stopEvent(e, true);
1919      if (lockFLAG && sameDirection && tooFast) {
1920        return;
1921      }
1922    }
1923
1924    if (options.shift) {
1925      lockFLAG = true;
1926      clearTimeout(tail.t);
1927      tail.t = setTimeout(function () {
1928        lockFLAG = false;
1929      }, SCROLL_LOCK_TIMEOUT);
1930    }
1931
1932    (options.onEnd || noop)(e, options.shift ? direction : xDelta);
1933
1934  });
1935
1936  return tail;
1937}
1938jQuery.Fotorama = function ($fotorama, opts) {
1939  $HTML = $('html');
1940  $BODY = $('body');
1941
1942  var that = this,
1943      stamp = $.now(),
1944      stampClass = _fotoramaClass + stamp,
1945      fotorama = $fotorama[0],
1946      data,
1947      dataFrameCount = 1,
1948      fotoramaData = $fotorama.data(),
1949      size,
1950
1951      $style = $('<style></style>'),
1952
1953      $anchor = $(div(hiddenClass)),
1954      $wrap = $(div(wrapClass)),
1955      $stage = $(div(stageClass)).appendTo($wrap),
1956      stage = $stage[0],
1957
1958      $stageShaft = $(div(stageShaftClass)).appendTo($stage),
1959      $stageFrame = $(),
1960      $arrPrev = $(div(arrClass + ' ' + arrPrevClass + buttonAttributes)),
1961      $arrNext = $(div(arrClass + ' ' + arrNextClass + buttonAttributes)),
1962      $arrs = $arrPrev.add($arrNext).appendTo($stage),
1963      $navWrap = $(div(navWrapClass)),
1964      $nav = $(div(navClass)).appendTo($navWrap),
1965      $navShaft = $(div(navShaftClass)).appendTo($nav),
1966      $navFrame,
1967      $navDotFrame = $(),
1968      $navThumbFrame = $(),
1969
1970      stageShaftData = $stageShaft.data(),
1971      navShaftData = $navShaft.data(),
1972
1973      $thumbBorder = $(div(thumbBorderClass)).appendTo($navShaft),
1974
1975      $fullscreenIcon = $(div(fullscreenIconClass + buttonAttributes)),
1976      fullscreenIcon = $fullscreenIcon[0],
1977      $videoPlay = $(div(videoPlayClass)),
1978      $videoClose = $(div(videoCloseClass)).appendTo($stage),
1979      videoClose = $videoClose[0],
1980
1981      spinner,
1982      $spinner = $(div(spinnerClass)),
1983
1984      $videoPlaying,
1985
1986      activeIndex = false,
1987      activeFrame,
1988      activeIndexes,
1989      repositionIndex,
1990      dirtyIndex,
1991      lastActiveIndex,
1992      prevIndex,
1993      nextIndex,
1994      nextAutoplayIndex,
1995      startIndex,
1996
1997      o_loop,
1998      o_nav,
1999      o_navThumbs,
2000      o_navTop,
2001      o_allowFullScreen,
2002      o_nativeFullScreen,
2003      o_fade,
2004      o_thumbSide,
2005      o_thumbSide2,
2006      o_transitionDuration,
2007      o_transition,
2008      o_shadows,
2009      o_rtl,
2010      o_keyboard,
2011      lastOptions = {},
2012
2013      measures = {},
2014      measuresSetFLAG,
2015
2016      stageShaftTouchTail = {},
2017      stageWheelTail = {},
2018      navShaftTouchTail = {},
2019      navWheelTail = {},
2020
2021      scrollTop,
2022      scrollLeft,
2023
2024      showedFLAG,
2025      pausedAutoplayFLAG,
2026      stoppedAutoplayFLAG,
2027
2028      toDeactivate = {},
2029      toDetach = {},
2030
2031      measuresStash,
2032
2033      touchedFLAG,
2034
2035      hoverFLAG,
2036
2037      navFrameKey,
2038      stageLeft = 0,
2039
2040      fadeStack = [];
2041
2042  $wrap[STAGE_FRAME_KEY] = $(div(stageFrameClass));
2043  $wrap[NAV_THUMB_FRAME_KEY] = $(div(navFrameClass + ' ' + navFrameThumbClass + buttonAttributes, div(thumbClass)));
2044  $wrap[NAV_DOT_FRAME_KEY] = $(div(navFrameClass + ' ' + navFrameDotClass + buttonAttributes, div(dotClass)));
2045
2046  toDeactivate[STAGE_FRAME_KEY] = [];
2047  toDeactivate[NAV_THUMB_FRAME_KEY] = [];
2048  toDeactivate[NAV_DOT_FRAME_KEY] = [];
2049  toDetach[STAGE_FRAME_KEY] = {};
2050
2051  $wrap
2052      .addClass(CSS3 ? wrapCss3Class : wrapCss2Class)
2053      .toggleClass(wrapNoControlsClass, !opts.controlsonstart);
2054
2055  fotoramaData.fotorama = this;
2056
2057  function checkForVideo () {
2058    $.each(data, function (i, dataFrame) {
2059      if (!dataFrame.i) {
2060        dataFrame.i = dataFrameCount++;
2061        var video = findVideoId(dataFrame.video, true);
2062        if (video) {
2063          var thumbs = {};
2064          dataFrame.video = video;
2065          if (!dataFrame.img && !dataFrame.thumb) {
2066            thumbs = getVideoThumbs(dataFrame, data, that);
2067          } else {
2068            dataFrame.thumbsReady = true;
2069          }
2070          updateData(data, {img: thumbs.img, thumb: thumbs.thumb}, dataFrame.i, that);
2071        }
2072      }
2073    });
2074  }
2075
2076  function allowKey (key) {
2077    return o_keyboard[key] || that.fullScreen;
2078  }
2079
2080  function bindGlobalEvents (FLAG) {
2081    var keydownCommon = 'keydown.' + _fotoramaClass,
2082        localStamp = _fotoramaClass + stamp,
2083        keydownLocal = 'keydown.' + localStamp,
2084        resizeLocal = 'resize.' + localStamp + ' ' + 'orientationchange.' + localStamp;
2085
2086    if (FLAG) {
2087      $DOCUMENT
2088          .on(keydownLocal, function (e) {
2089            var catched,
2090                index;
2091
2092            if ($videoPlaying && e.keyCode === 27) {
2093              catched = true;
2094              unloadVideo($videoPlaying, true, true);
2095            } else if (that.fullScreen || (opts.keyboard && !that.index)) {
2096              if (e.keyCode === 27) {
2097                catched = true;
2098                that.cancelFullScreen();
2099              } else if ((e.shiftKey && e.keyCode === 32 && allowKey('space')) || (e.keyCode === 37 && allowKey('left')) || (e.keyCode === 38 && allowKey('up'))) {
2100                index = '<';
2101              } else if ((e.keyCode === 32 && allowKey('space')) || (e.keyCode === 39 && allowKey('right')) || (e.keyCode === 40 && allowKey('down'))) {
2102                index = '>';
2103              } else if (e.keyCode === 36 && allowKey('home')) {
2104                index = '<<';
2105              } else if (e.keyCode === 35 && allowKey('end')) {
2106                index = '>>';
2107              }
2108            }
2109
2110            (catched || index) && stopEvent(e);
2111            index && that.show({index: index, slow: e.altKey, user: true});
2112          });
2113
2114      if (!that.index) {
2115        $DOCUMENT
2116            .off(keydownCommon)
2117            .on(keydownCommon, 'textarea, input, select', function (e) {
2118              !$BODY.hasClass(_fullscreenClass) && e.stopPropagation();
2119            });
2120      }
2121
2122      $WINDOW.on(resizeLocal, that.resize);
2123    } else {
2124      $DOCUMENT.off(keydownLocal);
2125      $WINDOW.off(resizeLocal);
2126    }
2127  }
2128
2129  function appendElements (FLAG) {
2130    if (FLAG === appendElements.f) return;
2131
2132    if (FLAG) {
2133      $fotorama
2134          .html('')
2135          .addClass(_fotoramaClass + ' ' + stampClass)
2136          .append($wrap)
2137          .before($style)
2138          .before($anchor);
2139
2140      addInstance(that);
2141    } else {
2142      $wrap.detach();
2143      $style.detach();
2144      $anchor.detach();
2145      $fotorama
2146          .html(fotoramaData.urtext)
2147          .removeClass(stampClass);
2148
2149      hideInstance(that);
2150    }
2151
2152    bindGlobalEvents(FLAG);
2153    appendElements.f = FLAG;
2154  }
2155
2156  function setData () {
2157    data = that.data = data || clone(opts.data) || getDataFromHtml($fotorama);
2158    size = that.size = data.length;
2159
2160    !ready.ok && opts.shuffle && shuffle(data);
2161
2162    checkForVideo();
2163
2164    activeIndex = limitIndex(activeIndex);
2165
2166    size && appendElements(true);
2167  }
2168
2169  function stageNoMove () {
2170    var _noMove = (size < 2 && !opts.enableifsingleframe) || $videoPlaying;
2171    stageShaftTouchTail.noMove = _noMove || o_fade;
2172    stageShaftTouchTail.noSwipe = _noMove || !opts.swipe;
2173
2174    !o_transition && $stageShaft.toggleClass(grabClass, !opts.click && !stageShaftTouchTail.noMove && !stageShaftTouchTail.noSwipe);
2175    MS_POINTER && $wrap.toggleClass(wrapPanYClass, !stageShaftTouchTail.noSwipe);
2176  }
2177
2178  function setAutoplayInterval (interval) {
2179    if (interval === true) interval = '';
2180    opts.autoplay = Math.max(+interval || AUTOPLAY_INTERVAL, o_transitionDuration * 1.5);
2181  }
2182
2183  /**
2184   * Options on the fly
2185   * */
2186  function setOptions () {
2187    that.options = opts = optionsToLowerCase(opts);
2188
2189    o_fade = (opts.transition === 'crossfade' || opts.transition === 'dissolve');
2190
2191    o_loop = opts.loop && (size > 2 || (o_fade && (!o_transition || o_transition !== 'slide')));
2192
2193    o_transitionDuration = +opts.transitionduration || TRANSITION_DURATION;
2194
2195    o_rtl = opts.direction === 'rtl';
2196
2197    o_keyboard = $.extend({}, opts.keyboard && KEYBOARD_OPTIONS, opts.keyboard);
2198
2199    var classes = {add: [], remove: []};
2200
2201    function addOrRemoveClass (FLAG, value) {
2202      classes[FLAG ? 'add' : 'remove'].push(value);
2203    }
2204
2205    if (size > 1 || opts.enableifsingleframe) {
2206      o_nav = opts.nav;
2207      o_navTop = opts.navposition === 'top';
2208      classes.remove.push(selectClass);
2209
2210      $arrs.toggle(!!opts.arrows);
2211    } else {
2212      o_nav = false;
2213      $arrs.hide();
2214    }
2215
2216    spinnerStop();
2217    spinner = new Spinner($.extend(spinnerDefaults, opts.spinner, spinnerOverride, {direction: o_rtl ? -1 : 1}));
2218
2219    arrsUpdate();
2220    stageWheelUpdate();
2221
2222    if (opts.autoplay) setAutoplayInterval(opts.autoplay);
2223
2224    o_thumbSide = numberFromMeasure(opts.thumbwidth) || THUMB_SIZE;
2225    o_thumbSide2 = numberFromMeasure(opts.thumbheight) || THUMB_SIZE;
2226
2227    stageWheelTail.ok = navWheelTail.ok = opts.trackpad && !SLOW;
2228
2229    stageNoMove();
2230
2231    extendMeasures(opts, [measures]);
2232
2233    o_navThumbs = o_nav === 'thumbs';
2234
2235    if (o_navThumbs) {
2236      frameDraw(size, 'navThumb');
2237
2238      $navFrame = $navThumbFrame;
2239      navFrameKey = NAV_THUMB_FRAME_KEY;
2240
2241      setStyle($style, $.Fotorama.jst.style({w: o_thumbSide, h: o_thumbSide2, b: opts.thumbborderwidth, m: opts.thumbmargin, s: stamp, q: !COMPAT}));
2242
2243      $nav
2244          .addClass(navThumbsClass)
2245          .removeClass(navDotsClass);
2246    } else if (o_nav === 'dots') {
2247      frameDraw(size, 'navDot');
2248
2249      $navFrame = $navDotFrame;
2250      navFrameKey = NAV_DOT_FRAME_KEY;
2251
2252      $nav
2253          .addClass(navDotsClass)
2254          .removeClass(navThumbsClass);
2255    } else {
2256      o_nav = false;
2257      $nav.removeClass(navThumbsClass + ' ' + navDotsClass);
2258    }
2259
2260    if (o_nav) {
2261      if (o_navTop) {
2262        $navWrap.insertBefore($stage);
2263      } else {
2264        $navWrap.insertAfter($stage);
2265      }
2266      frameAppend.nav = false;
2267      frameAppend($navFrame, $navShaft, 'nav');
2268    }
2269
2270    o_allowFullScreen = opts.allowfullscreen;
2271
2272    if (o_allowFullScreen) {
2273      $fullscreenIcon.prependTo($stage);
2274      o_nativeFullScreen = FULLSCREEN && o_allowFullScreen === 'native';
2275    } else {
2276      $fullscreenIcon.detach();
2277      o_nativeFullScreen = false;
2278    }
2279
2280    addOrRemoveClass(o_fade, wrapFadeClass);
2281    addOrRemoveClass(!o_fade, wrapSlideClass);
2282    addOrRemoveClass(!opts.captions, wrapNoCaptionsClass);
2283    addOrRemoveClass(o_rtl, wrapRtlClass);
2284    addOrRemoveClass(opts.arrows !== 'always', wrapToggleArrowsClass);
2285
2286    o_shadows = opts.shadows && !SLOW;
2287    addOrRemoveClass(!o_shadows, wrapNoShadowsClass);
2288
2289    $wrap
2290        .addClass(classes.add.join(' '))
2291        .removeClass(classes.remove.join(' '));
2292
2293    lastOptions = $.extend({}, opts);
2294  }
2295
2296  function normalizeIndex (index) {
2297    return index < 0 ? (size + (index % size)) % size : index >= size ? index % size : index;
2298  }
2299
2300  function limitIndex (index) {
2301    return minMaxLimit(index, 0, size - 1);
2302  }
2303
2304  function edgeIndex (index) {
2305    return o_loop ? normalizeIndex(index) : limitIndex(index);
2306  }
2307
2308  function getPrevIndex (index) {
2309    return index > 0 || o_loop ? index - 1 : false;
2310  }
2311
2312  function getNextIndex (index) {
2313    return index < size - 1 || o_loop ? index + 1 : false;
2314  }
2315
2316  function setStageShaftMinmaxAndSnap () {
2317    stageShaftTouchTail.min = o_loop ? -Infinity : -getPosByIndex(size - 1, measures.w, opts.margin, repositionIndex);
2318    stageShaftTouchTail.max = o_loop ? Infinity : -getPosByIndex(0, measures.w, opts.margin, repositionIndex);
2319    stageShaftTouchTail.snap = measures.w + opts.margin;
2320  }
2321
2322  function setNavShaftMinMax () {
2323    ////////console.log('setNavShaftMinMax', measures.nw);
2324    navShaftTouchTail.min = Math.min(0, measures.nw - $navShaft.width());
2325    navShaftTouchTail.max = 0;
2326    $navShaft.toggleClass(grabClass, !(navShaftTouchTail.noMove = navShaftTouchTail.min === navShaftTouchTail.max));
2327  }
2328
2329  function eachIndex (indexes, type, fn) {
2330    if (typeof indexes === 'number') {
2331      indexes = new Array(indexes);
2332      var rangeFLAG = true;
2333    }
2334    return $.each(indexes, function (i, index) {
2335      if (rangeFLAG) index = i;
2336      if (typeof index === 'number') {
2337        var dataFrame = data[normalizeIndex(index)];
2338
2339        if (dataFrame) {
2340          var key = '$' + type + 'Frame',
2341              $frame = dataFrame[key];
2342
2343          fn.call(this, i, index, dataFrame, $frame, key, $frame && $frame.data());
2344        }
2345      }
2346    });
2347  }
2348
2349  function setMeasures (width, height, ratio, index) {
2350    if (!measuresSetFLAG || (measuresSetFLAG === '*' && index === startIndex)) {
2351
2352      //////console.log('setMeasures', index, opts.width, opts.height);
2353
2354      width = measureIsValid(opts.width) || measureIsValid(width) || WIDTH;
2355      height = measureIsValid(opts.height) || measureIsValid(height) || HEIGHT;
2356      that.resize({
2357        width: width,
2358        ratio: opts.ratio || ratio || width / height
2359      }, 0, index !== startIndex && '*');
2360    }
2361  }
2362
2363  function loadImg (indexes, type, specialMeasures, method, position, again) {
2364    eachIndex(indexes, type, function (i, index, dataFrame, $frame, key, frameData) {
2365
2366      if (!$frame) return;
2367
2368      var fullFLAG = that.fullScreen && dataFrame.full && dataFrame.full !== dataFrame.img && !frameData.$full && type === 'stage';
2369
2370      if (frameData.$img && !again && !fullFLAG) return;
2371
2372      var img = new Image(),
2373          $img = $(img),
2374          imgData = $img.data();
2375
2376      frameData[fullFLAG ? '$full' : '$img'] = $img;
2377
2378      var srcKey = type === 'stage' ? (fullFLAG ? 'full' : 'img') : 'thumb',
2379          src = dataFrame[srcKey],
2380          dummy = fullFLAG ? null : dataFrame[type === 'stage' ? 'thumb' : 'img'];
2381
2382      if (type === 'navThumb') $frame = frameData.$wrap;
2383
2384      function triggerTriggerEvent (event) {
2385        var _index = normalizeIndex(index);
2386        triggerEvent(event, {
2387          index: _index,
2388          src: src,
2389          frame: data[_index]
2390        });
2391      }
2392
2393      function error () {
2394        $img.remove();
2395
2396        $.Fotorama.cache[src] = 'error';
2397
2398        if ((!dataFrame.html || type !== 'stage') && dummy && dummy !== src) {
2399          dataFrame[srcKey] = src = dummy;
2400          loadImg([index], type, specialMeasures, method, position, true);
2401        } else {
2402          if (src && !dataFrame.html && !fullFLAG) {
2403            $frame
2404                .trigger('f:error')
2405                .removeClass(loadingClass)
2406                .addClass(errorClass);
2407
2408            triggerTriggerEvent('error');
2409          } else if (type === 'stage') {
2410            $frame
2411                .trigger('f:load')
2412                .removeClass(loadingClass + ' ' + errorClass)
2413                .addClass(loadedClass);
2414
2415            triggerTriggerEvent('load');
2416            setMeasures();
2417          }
2418
2419          frameData.state = 'error';
2420
2421          if (size > 1 && data[index] === dataFrame && !dataFrame.html && !dataFrame.deleted && !dataFrame.video && !fullFLAG) {
2422            dataFrame.deleted = true;
2423            that.splice(index, 1);
2424          }
2425        }
2426      }
2427
2428      function loaded () {
2429        //////console.log('loaded: ' + src);
2430
2431        ////console.log('$.Fotorama.measures[src]', $.Fotorama.measures[src]);
2432
2433        $.Fotorama.measures[src] = imgData.measures = $.Fotorama.measures[src] || {
2434          width: img.width,
2435          height: img.height,
2436          ratio: img.width / img.height
2437        };
2438
2439        setMeasures(imgData.measures.width, imgData.measures.height, imgData.measures.ratio, index);
2440
2441        $img
2442            .off('load error')
2443            .addClass(imgClass + (fullFLAG ? ' ' + imgFullClass : ''))
2444            .prependTo($frame);
2445
2446        fit($img, ($.isFunction(specialMeasures) ? specialMeasures() : specialMeasures) || measures, method || dataFrame.fit || opts.fit, position || dataFrame.position || opts.position);
2447
2448        $.Fotorama.cache[src] = frameData.state = 'loaded';
2449
2450        setTimeout(function () {
2451          $frame
2452              .trigger('f:load')
2453              .removeClass(loadingClass + ' ' + errorClass)
2454              .addClass(loadedClass + ' ' + (fullFLAG ? loadedFullClass : loadedImgClass));
2455
2456          if (type === 'stage') {
2457            triggerTriggerEvent('load');
2458          } else if (dataFrame.thumbratio === AUTO || !dataFrame.thumbratio && opts.thumbratio === AUTO) {
2459            // danger! reflow for all thumbnails
2460            dataFrame.thumbratio = imgData.measures.ratio;
2461            reset();
2462          }
2463        }, 0);
2464      }
2465
2466      if (!src) {
2467        error();
2468        return;
2469      }
2470
2471      function waitAndLoad () {
2472        var _i = 10;
2473        waitFor(function () {
2474          return !touchedFLAG || !_i-- && !SLOW;
2475        }, function () {
2476          loaded();
2477        });
2478      }
2479
2480      if (!$.Fotorama.cache[src]) {
2481        $.Fotorama.cache[src] = '*';
2482
2483        $img
2484            .on('load', waitAndLoad)
2485            .on('error', error);
2486      } else {
2487        (function justWait () {
2488          if ($.Fotorama.cache[src] === 'error') {
2489            error();
2490          } else if ($.Fotorama.cache[src] === 'loaded') {
2491            ////console.log('take from cache: ' + src);
2492            setTimeout(waitAndLoad, 0);
2493          } else {
2494            setTimeout(justWait, 100);
2495          }
2496        })();
2497      }
2498
2499      frameData.state = '';
2500      img.src = src;
2501    });
2502  }
2503
2504  function spinnerSpin ($el) {
2505    $spinner.append(spinner.spin().el).appendTo($el);
2506  }
2507
2508  function spinnerStop () {
2509    $spinner.detach();
2510    spinner && spinner.stop();
2511  }
2512
2513  function updateFotoramaState () {
2514    var $frame = activeFrame[STAGE_FRAME_KEY];
2515
2516    if ($frame && !$frame.data().state) {
2517      spinnerSpin($frame);
2518      $frame.on('f:load f:error', function () {
2519        $frame.off('f:load f:error');
2520        spinnerStop();
2521      });
2522    }
2523  }
2524
2525  function addNavFrameEvents (frame) {
2526    addEnterUp(frame, onNavFrameClick);
2527    addFocus(frame, function () {
2528
2529      setTimeout(function () {
2530        lockScroll($nav);
2531      }, 0);
2532      slideNavShaft({time: o_transitionDuration, guessIndex: $(this).data().eq, minMax: navShaftTouchTail});
2533    });
2534  }
2535
2536  function frameDraw (indexes, type) {
2537    eachIndex(indexes, type, function (i, index, dataFrame, $frame, key, frameData) {
2538      if ($frame) return;
2539
2540      $frame = dataFrame[key] = $wrap[key].clone();
2541      frameData = $frame.data();
2542      frameData.data = dataFrame;
2543      var frame = $frame[0];
2544
2545      if (type === 'stage') {
2546
2547        if (dataFrame.html) {
2548          $('<div class="' + htmlClass + '"></div>')
2549              .append(
2550                  dataFrame._html ? $(dataFrame.html)
2551                      .removeAttr('id')
2552                      .html(dataFrame._html) // Because of IE
2553                  : dataFrame.html
2554              )
2555              .appendTo($frame);
2556        }
2557
2558        dataFrame.caption && $(div(captionClass, div(captionWrapClass, dataFrame.caption))).appendTo($frame);
2559
2560        dataFrame.video && $frame
2561          .addClass(stageFrameVideoClass)
2562          .append($videoPlay.clone());
2563
2564        // This solves tabbing problems
2565        addFocus(frame, function () {
2566          setTimeout(function () {
2567            lockScroll($stage);
2568          }, 0);
2569          clickToShow({index: frameData.eq, user: true});
2570        });
2571
2572        $stageFrame = $stageFrame.add($frame);
2573      } else if (type === 'navDot') {
2574        addNavFrameEvents(frame);
2575        $navDotFrame = $navDotFrame.add($frame);
2576      } else if (type === 'navThumb') {
2577        addNavFrameEvents(frame);
2578        frameData.$wrap = $frame.children(':first');
2579        $navThumbFrame = $navThumbFrame.add($frame);
2580        if (dataFrame.video) {
2581          frameData.$wrap.append($videoPlay.clone());
2582        }
2583      }
2584    });
2585  }
2586
2587  function callFit ($img, measuresToFit, method, position) {
2588    return $img && $img.length && fit($img, measuresToFit, method, position);
2589  }
2590
2591  function stageFramePosition (indexes) {
2592    eachIndex(indexes, 'stage', function (i, index, dataFrame, $frame, key, frameData) {
2593      if (!$frame) return;
2594
2595      var normalizedIndex = normalizeIndex(index),
2596          method = dataFrame.fit || opts.fit,
2597          position = dataFrame.position || opts.position;
2598      frameData.eq = normalizedIndex;
2599
2600      toDetach[STAGE_FRAME_KEY][normalizedIndex] = $frame.css($.extend({left: o_fade ? 0 : getPosByIndex(index, measures.w, opts.margin, repositionIndex)}, o_fade && getDuration(0)));
2601
2602      if (isDetached($frame[0])) {
2603        $frame.appendTo($stageShaft);
2604        unloadVideo(dataFrame.$video);
2605      }
2606
2607      callFit(frameData.$img, measures, method, position);
2608      callFit(frameData.$full, measures, method, position);
2609    });
2610  }
2611
2612  function thumbsDraw (pos, loadFLAG) {
2613    if (o_nav !== 'thumbs' || isNaN(pos)) return;
2614
2615    var leftLimit = -pos,
2616        rightLimit = -pos + measures.nw;
2617
2618    $navThumbFrame.each(function () {
2619      var $this = $(this),
2620          thisData = $this.data(),
2621          eq = thisData.eq,
2622          getSpecialMeasures = function () {
2623            return {
2624              h: o_thumbSide2,
2625              w: thisData.w
2626            }
2627          },
2628          specialMeasures = getSpecialMeasures(),
2629          dataFrame = data[eq] || {},
2630          method = dataFrame.thumbfit || opts.thumbfit,
2631          position = dataFrame.thumbposition || opts.thumbposition;
2632
2633      specialMeasures.w = thisData.w;
2634
2635      if (thisData.l + thisData.w < leftLimit
2636          || thisData.l > rightLimit
2637          || callFit(thisData.$img, specialMeasures, method, position)) return;
2638
2639      loadFLAG && loadImg([eq], 'navThumb', getSpecialMeasures, method, position);
2640    });
2641  }
2642
2643  function frameAppend ($frames, $shaft, type) {
2644    if (!frameAppend[type]) {
2645
2646      var thumbsFLAG = type === 'nav' && o_navThumbs,
2647          left = 0;
2648
2649      $shaft.append(
2650        $frames
2651            .filter(function () {
2652              var actual,
2653                  $this = $(this),
2654                  frameData = $this.data();
2655              for (var _i = 0, _l = data.length; _i < _l; _i++) {
2656                if (frameData.data === data[_i]) {
2657                  actual = true;
2658                  frameData.eq = _i;
2659                  break;
2660                }
2661              }
2662              return actual || $this.remove() && false;
2663            })
2664            .sort(function (a, b) {
2665              return $(a).data().eq - $(b).data().eq;
2666            })
2667            .each(function () {
2668
2669              if (!thumbsFLAG) return;
2670
2671              var $this = $(this),
2672                  frameData = $this.data(),
2673                  thumbwidth = Math.round(o_thumbSide2 * frameData.data.thumbratio) || o_thumbSide;
2674
2675              frameData.l = left;
2676              frameData.w = thumbwidth;
2677
2678              $this.css({width: thumbwidth});
2679
2680              left += thumbwidth + opts.thumbmargin;
2681            })
2682      );
2683
2684      frameAppend[type] = true;
2685    }
2686  }
2687
2688  function getDirection (x) {
2689    return x - stageLeft > measures.w / 3;
2690  }
2691
2692  function disableDirrection (i) {
2693    return !o_loop && (!(activeIndex + i) || !(activeIndex - size + i)) && !$videoPlaying;
2694  }
2695
2696  function arrsUpdate () {
2697    var disablePrev = disableDirrection(0),
2698        disableNext = disableDirrection(1);
2699    $arrPrev
2700        .toggleClass(arrDisabledClass, disablePrev)
2701        .attr(disableAttr(disablePrev));
2702    $arrNext
2703        .toggleClass(arrDisabledClass, disableNext)
2704        .attr(disableAttr(disableNext));
2705  }
2706
2707  function stageWheelUpdate () {
2708    if (stageWheelTail.ok) {
2709      stageWheelTail.prevent = {'<': disableDirrection(0), '>': disableDirrection(1)};
2710    }
2711  }
2712
2713  function getNavFrameBounds ($navFrame) {
2714    var navFrameData = $navFrame.data(),
2715        left,
2716        width;
2717
2718    if (o_navThumbs) {
2719      left = navFrameData.l;
2720      width = navFrameData.w;
2721    } else {
2722      left = $navFrame.position().left;
2723      width = $navFrame.width();
2724    }
2725
2726    return {
2727      c: left + width / 2,
2728      min: -left + opts.thumbmargin * 10,
2729      max: -left + measures.w - width - opts.thumbmargin * 10
2730    };
2731  }
2732
2733  function slideThumbBorder (time) {
2734    var navFrameData = activeFrame[navFrameKey].data();
2735    slide($thumbBorder, {
2736      time: time * 1.2,
2737      pos: navFrameData.l,
2738      width: navFrameData.w - opts.thumbborderwidth * 2
2739    });
2740  }
2741
2742  function slideNavShaft (options) {
2743    ////console.log('slideNavShaft', options.guessIndex, options.keep, slideNavShaft.l);
2744    var $guessNavFrame = data[options.guessIndex][navFrameKey];
2745    if ($guessNavFrame) {
2746      var overflowFLAG = navShaftTouchTail.min !== navShaftTouchTail.max,
2747          minMax = options.minMax || overflowFLAG && getNavFrameBounds(activeFrame[navFrameKey]),
2748          l = overflowFLAG && (options.keep && slideNavShaft.l ? slideNavShaft.l : minMaxLimit((options.coo || measures.nw / 2) - getNavFrameBounds($guessNavFrame).c, minMax.min, minMax.max)),
2749          pos = overflowFLAG && minMaxLimit(l, navShaftTouchTail.min, navShaftTouchTail.max),
2750          time = options.time * 1.1;
2751
2752      slide($navShaft, {
2753        time: time,
2754        pos: pos || 0,
2755        onEnd: function () {
2756          thumbsDraw(pos, true);
2757        }
2758      });
2759
2760      //if (time) thumbsDraw(pos);
2761
2762      setShadow($nav, findShadowEdge(pos, navShaftTouchTail.min, navShaftTouchTail.max));
2763      slideNavShaft.l = l;
2764    }
2765  }
2766
2767  function navUpdate () {
2768    deactivateFrames(navFrameKey);
2769    toDeactivate[navFrameKey].push(activeFrame[navFrameKey].addClass(activeClass));
2770  }
2771
2772  function deactivateFrames (key) {
2773    var _toDeactivate = toDeactivate[key];
2774
2775    while (_toDeactivate.length) {
2776      _toDeactivate.shift().removeClass(activeClass);
2777    }
2778  }
2779
2780  function detachFrames (key) {
2781    var _toDetach = toDetach[key];
2782
2783    //////console.log('_toDetach', _toDetach);
2784    //////console.log('activeIndexes', activeIndexes);
2785
2786    $.each(activeIndexes, function (i, index) {
2787      delete _toDetach[normalizeIndex(index)];
2788    });
2789
2790    $.each(_toDetach, function (index, $frame) {
2791      delete _toDetach[index];
2792      //////console.log('Detach', index);
2793      $frame.detach();
2794    });
2795  }
2796
2797  function stageShaftReposition (skipOnEnd) {
2798
2799    repositionIndex = dirtyIndex = activeIndex;
2800
2801    var $frame = activeFrame[STAGE_FRAME_KEY];
2802
2803    if ($frame) {
2804      deactivateFrames(STAGE_FRAME_KEY);
2805      toDeactivate[STAGE_FRAME_KEY].push($frame.addClass(activeClass));
2806
2807      skipOnEnd || that.show.onEnd(true);
2808      stop($stageShaft, 0, true);
2809
2810      detachFrames(STAGE_FRAME_KEY);
2811      stageFramePosition(activeIndexes);
2812      setStageShaftMinmaxAndSnap();
2813      setNavShaftMinMax();
2814    }
2815  }
2816
2817  function extendMeasures (options, measuresArray) {
2818    if (!options) return;
2819
2820    $.each(measuresArray, function (i, measures) {
2821      if (!measures) return;
2822
2823      $.extend(measures, {
2824        width: options.width || measures.width,
2825        height: options.height,
2826        minwidth: options.minwidth,
2827        maxwidth: options.maxwidth,
2828        minheight: options.minheight,
2829        maxheight: options.maxheight,
2830        ratio: getRatio(options.ratio)
2831      })
2832    });
2833  }
2834
2835  function triggerEvent (event, extra) {
2836    $fotorama.trigger(_fotoramaClass + ':' + event, [that, extra]);
2837  }
2838
2839  function onTouchStart () {
2840    clearTimeout(onTouchEnd.t);
2841    touchedFLAG = 1;
2842
2843    if (opts.stopautoplayontouch) {
2844      that.stopAutoplay();
2845    } else {
2846      pausedAutoplayFLAG = true;
2847    }
2848  }
2849
2850  function onTouchEnd () {
2851    if (!touchedFLAG) return;
2852    if (!opts.stopautoplayontouch) {
2853      releaseAutoplay();
2854      changeAutoplay();
2855    }
2856
2857    onTouchEnd.t = setTimeout(function () {
2858      touchedFLAG = 0;
2859    }, TRANSITION_DURATION + TOUCH_TIMEOUT);
2860    //////console.timeEnd('onTouchEnd');
2861  }
2862
2863  function releaseAutoplay () {
2864    ////console.log('releaseAutoplay');
2865    pausedAutoplayFLAG = !!($videoPlaying || stoppedAutoplayFLAG);
2866  }
2867
2868  function changeAutoplay () {
2869    ////console.log('changeAutoplay');
2870
2871    clearTimeout(changeAutoplay.t);
2872    waitFor.stop(changeAutoplay.w);
2873
2874    if (!opts.autoplay || pausedAutoplayFLAG) {
2875      if (that.autoplay) {
2876        that.autoplay = false;
2877        triggerEvent('stopautoplay');
2878      }
2879
2880      return;
2881    }
2882
2883    ////console.log('changeAutoplay continue');
2884
2885    if (!that.autoplay) {
2886      that.autoplay = true;
2887      triggerEvent('startautoplay');
2888    }
2889
2890    var _activeIndex = activeIndex;
2891
2892
2893    var frameData = activeFrame[STAGE_FRAME_KEY].data();
2894    changeAutoplay.w = waitFor(function () {
2895      ////console.log('wait for the state of the current frame');
2896      return frameData.state || _activeIndex !== activeIndex;
2897    }, function () {
2898      ////console.log('the current frame is ready');
2899      changeAutoplay.t = setTimeout(function () {
2900        ////console.log('changeAutoplay.t setTimeout', pausedAutoplayFLAG, _activeIndex !== activeIndex);
2901        if (pausedAutoplayFLAG || _activeIndex !== activeIndex) return;
2902
2903        var _nextAutoplayIndex = nextAutoplayIndex,
2904            nextFrameData = data[_nextAutoplayIndex][STAGE_FRAME_KEY].data();
2905
2906        changeAutoplay.w = waitFor(function () {
2907          ////console.log('wait for the state of the next frame');
2908          return nextFrameData.state || _nextAutoplayIndex !== nextAutoplayIndex;
2909        }, function () {
2910          if (pausedAutoplayFLAG || _nextAutoplayIndex !== nextAutoplayIndex) return;
2911          that.show(o_loop ? getDirectionSign(!o_rtl) : nextAutoplayIndex);
2912        });
2913      }, opts.autoplay);
2914    });
2915
2916  }
2917
2918  that.startAutoplay = function (interval) {
2919    if (that.autoplay) return this;
2920    pausedAutoplayFLAG = stoppedAutoplayFLAG = false;
2921    setAutoplayInterval(interval || opts.autoplay);
2922    changeAutoplay();
2923
2924    return this;
2925  };
2926
2927  that.stopAutoplay = function () {
2928    if (that.autoplay) {
2929      pausedAutoplayFLAG = stoppedAutoplayFLAG = true;
2930      changeAutoplay();
2931    }
2932    return this;
2933  };
2934
2935  that.show = function (options) {
2936    ////console.log('that.show');
2937    //////console.time('that.show prepare');
2938    var index;
2939
2940    if (typeof options !== 'object') {
2941      index = options;
2942      options = {};
2943    } else {
2944      index = options.index;
2945    }
2946
2947    index = index === '>' ? dirtyIndex + 1 : index === '<' ? dirtyIndex - 1 : index === '<<' ? 0 : index === '>>' ? size - 1 : index;
2948    index = isNaN(index) ? getIndexFromHash(index, data, true) : index;
2949    index = typeof index === 'undefined' ? activeIndex || 0 : index;
2950
2951    that.activeIndex = activeIndex = edgeIndex(index);
2952    prevIndex = getPrevIndex(activeIndex);
2953    nextIndex = getNextIndex(activeIndex);
2954    nextAutoplayIndex = normalizeIndex(activeIndex + (o_rtl ? -1 : 1));
2955    activeIndexes = [activeIndex, prevIndex, nextIndex];
2956
2957    dirtyIndex = o_loop ? index : activeIndex;
2958
2959    var diffIndex = Math.abs(lastActiveIndex - dirtyIndex),
2960        time = getNumber(options.time, function () {
2961          return Math.min(o_transitionDuration * (1 + (diffIndex - 1) / 12), o_transitionDuration * 2);
2962        }),
2963        overPos = options.overPos;
2964
2965    if (options.slow) time *= 10;
2966
2967    var _activeFrame = activeFrame;
2968    that.activeFrame = activeFrame = data[activeIndex];
2969    //////console.timeEnd('that.show prepare');
2970
2971    var silent = _activeFrame === activeFrame && !options.user;
2972
2973    //setTimeout(function () {
2974    //////console.time('unloadVideo');
2975    unloadVideo($videoPlaying, activeFrame.i !== data[normalizeIndex(repositionIndex)].i);
2976    //////console.timeEnd('unloadVideo');
2977    //////console.time('frameDraw');
2978    frameDraw(activeIndexes, 'stage');
2979    //////console.timeEnd('frameDraw');
2980    //////console.time('stageFramePosition');
2981    stageFramePosition(SLOW ? [dirtyIndex] : [dirtyIndex, getPrevIndex(dirtyIndex), getNextIndex(dirtyIndex)]);
2982    //////console.timeEnd('stageFramePosition');
2983    //////console.time('updateTouchTails');
2984    updateTouchTails('go', true);
2985    //////console.timeEnd('updateTouchTails');
2986    //////console.time('triggerEvent');
2987
2988    silent || triggerEvent('show', {
2989      user: options.user,
2990      time: time
2991    });
2992    //////console.timeEnd('triggerEvent');
2993    //}, 0);
2994
2995    //////console.time('bind onEnd');
2996
2997    pausedAutoplayFLAG = true;
2998
2999    var onEnd = that.show.onEnd = function (skipReposition) {
3000      if (onEnd.ok) return;
3001      onEnd.ok = true;
3002
3003      skipReposition || stageShaftReposition(true);
3004
3005      if (!silent) {
3006        triggerEvent('showend', {
3007          user: options.user
3008        });
3009      }
3010
3011      ////console.log('o_transition', o_transition);
3012
3013      if (!skipReposition && o_transition && o_transition !== opts.transition) {
3014        ////console.log('set transition back to: ' + o_transition);
3015        that.setOptions({transition: o_transition});
3016        o_transition = false;
3017        return;
3018      }
3019
3020      updateFotoramaState();
3021      loadImg(activeIndexes, 'stage');
3022
3023      updateTouchTails('go', false);
3024      stageWheelUpdate();
3025
3026      stageCursor();
3027      releaseAutoplay();
3028      changeAutoplay();
3029    };
3030    //////console.timeEnd('bind onEnd');
3031
3032    if (!o_fade) {
3033      //////console.time('slide');
3034      slide($stageShaft, {
3035        pos: -getPosByIndex(dirtyIndex, measures.w, opts.margin, repositionIndex),
3036        overPos: overPos,
3037        time: time,
3038        onEnd: onEnd/*,
3039        _001: true*/
3040      });
3041      //////console.timeEnd('slide');
3042    } else {
3043      var $activeFrame = activeFrame[STAGE_FRAME_KEY],
3044          $prevActiveFrame = activeIndex !== lastActiveIndex ? data[lastActiveIndex][STAGE_FRAME_KEY] : null;
3045
3046      fade($activeFrame, $prevActiveFrame, $stageFrame, {
3047        time: time,
3048        method: opts.transition,
3049        onEnd: onEnd
3050      }, fadeStack);
3051    }
3052
3053    //////console.time('arrsUpdate');
3054    arrsUpdate();
3055    //////console.timeEnd('arrsUpdate');
3056
3057    if (o_nav) {
3058      //////console.time('navUpdate');
3059      navUpdate();
3060      //////console.timeEnd('navUpdate');
3061
3062      //////console.time('slideNavShaft');
3063      var guessIndex = limitIndex(activeIndex + minMaxLimit(dirtyIndex - lastActiveIndex, -1, 1));
3064      slideNavShaft({time: time, coo: guessIndex !== activeIndex && options.coo, guessIndex: typeof options.coo !== 'undefined' ? guessIndex : activeIndex, keep: silent});
3065      //////console.timeEnd('slideNavShaft');
3066
3067      //////console.time('slideThumbBorder');
3068      if (o_navThumbs) slideThumbBorder(time);
3069      //////console.timeEnd('slideThumbBorder');
3070    }
3071
3072    //////console.time('that.show end');
3073    showedFLAG = typeof lastActiveIndex !== 'undefined' && lastActiveIndex !== activeIndex;
3074    lastActiveIndex = activeIndex;
3075    opts.hash && showedFLAG && !that.eq && setHash(activeFrame.id || activeIndex + 1);
3076    //////console.timeEnd('that.show end');
3077
3078    //////console.timeEnd('that.show');
3079
3080    return this;
3081  };
3082
3083  that.requestFullScreen = function () {
3084    if (o_allowFullScreen && !that.fullScreen) {
3085      scrollTop = $WINDOW.scrollTop();
3086      scrollLeft = $WINDOW.scrollLeft();
3087
3088      lockScroll($WINDOW);
3089
3090      updateTouchTails('x', true);
3091
3092      measuresStash = $.extend({}, measures);
3093
3094      $fotorama
3095          .addClass(fullscreenClass)
3096          .appendTo($BODY.addClass(_fullscreenClass));
3097
3098      $HTML.addClass(_fullscreenClass);
3099
3100      unloadVideo($videoPlaying, true, true);
3101
3102      that.fullScreen = true;
3103
3104      if (o_nativeFullScreen) {
3105        fullScreenApi.request(fotorama);
3106      }
3107
3108      that.resize();
3109      loadImg(activeIndexes, 'stage');
3110      updateFotoramaState();
3111
3112      triggerEvent('fullscreenenter');
3113    }
3114
3115    return this;
3116  };
3117
3118  function cancelFullScreen () {
3119    if (that.fullScreen) {
3120      that.fullScreen = false;
3121
3122      if (FULLSCREEN) {
3123        fullScreenApi.cancel(fotorama);
3124      }
3125
3126      $BODY.removeClass(_fullscreenClass);
3127      $HTML.removeClass(_fullscreenClass);
3128
3129      $fotorama
3130          .removeClass(fullscreenClass)
3131          .insertAfter($anchor);
3132
3133      measures = $.extend({}, measuresStash);
3134
3135      unloadVideo($videoPlaying, true, true);
3136
3137      updateTouchTails('x', false);
3138
3139      that.resize();
3140      loadImg(activeIndexes, 'stage');
3141
3142      lockScroll($WINDOW, scrollLeft, scrollTop);
3143
3144      triggerEvent('fullscreenexit');
3145    }
3146  }
3147
3148  that.cancelFullScreen = function () {
3149    if (o_nativeFullScreen && fullScreenApi.is()) {
3150      fullScreenApi.cancel(document);
3151    } else {
3152      cancelFullScreen();
3153    }
3154
3155    return this;
3156  };
3157
3158  that.toggleFullScreen = function () {
3159    return that[(that.fullScreen ? 'cancel' : 'request') + 'FullScreen']();
3160  };
3161
3162  addEvent(document, fullScreenApi.event, function () {
3163    if (data && !fullScreenApi.is() && !$videoPlaying) {
3164      cancelFullScreen();
3165    }
3166  });
3167
3168  that.resize = function (options) {
3169    if (!data) return this;
3170
3171    var time = arguments[1] || 0,
3172        setFLAG = arguments[2];
3173
3174    extendMeasures(!that.fullScreen ? optionsToLowerCase(options) : {width: '100%', maxwidth: null, minwidth: null, height: '100%', maxheight: null, minheight: null}, [measures, setFLAG || that.fullScreen || opts]);
3175
3176    var width = measures.width,
3177        height = measures.height,
3178        ratio = measures.ratio,
3179        windowHeight = $WINDOW.height() - (o_nav ? $nav.height() : 0);
3180
3181    if (measureIsValid(width)) {
3182      $wrap
3183          .addClass(wrapOnlyActiveClass)
3184          .css({width: width, minWidth: measures.minwidth || 0, maxWidth: measures.maxwidth || MAX_WIDTH});
3185
3186      width = measures.W = measures.w = $wrap.width();
3187      measures.nw = o_nav && numberFromWhatever(opts.navwidth, width) || width;
3188
3189      if (opts.glimpse) {
3190        // Glimpse
3191        measures.w -= Math.round((numberFromWhatever(opts.glimpse, width) || 0) * 2);
3192      }
3193
3194      $stageShaft.css({width: measures.w, marginLeft: (measures.W - measures.w) / 2});
3195
3196      //////console.log('measures.W', measures.W);
3197      //////console.log('measures.w', measures.w);
3198
3199      height = numberFromWhatever(height, windowHeight);
3200
3201      height = height || (ratio && width / ratio);
3202
3203      if (height) {
3204        width = Math.round(width);
3205        height = measures.h = Math.round(minMaxLimit(height, numberFromWhatever(measures.minheight, windowHeight), numberFromWhatever(measures.maxheight, windowHeight)));
3206
3207        $stage
3208            .stop()
3209            .animate({width: width, height: height}, time, function () {
3210              $wrap.removeClass(wrapOnlyActiveClass);
3211            });
3212
3213        stageShaftReposition();
3214
3215        if (o_nav) {
3216          $nav
3217              .stop()
3218              .animate({width: measures.nw}, time);
3219
3220          slideNavShaft({guessIndex: activeIndex, time: time, keep: true});
3221          if (o_navThumbs && frameAppend.nav) slideThumbBorder(time);
3222        }
3223
3224        measuresSetFLAG = setFLAG || true;
3225
3226        ready();
3227      }
3228    }
3229
3230    stageLeft = $stage.offset().left;
3231
3232    return this;
3233  };
3234
3235  that.setOptions = function (options) {
3236    $.extend(opts, options);
3237    reset();
3238    return this;
3239  };
3240
3241  that.shuffle = function () {
3242    data && shuffle(data) && reset();
3243    return this;
3244  };
3245
3246  function setShadow ($el, edge) {
3247    //////console.time('setShadow');
3248    if (o_shadows) {
3249      $el.removeClass(shadowsLeftClass + ' ' + shadowsRightClass);
3250      edge && !$videoPlaying && $el.addClass(edge.replace(/^|\s/g, ' ' + shadowsClass + '--'));
3251    }
3252    //////console.timeEnd('setShadow');
3253  }
3254
3255  that.destroy = function () {
3256    that.cancelFullScreen();
3257    that.stopAutoplay();
3258
3259    data = that.data = null;
3260
3261    appendElements();
3262
3263    activeIndexes = [];
3264    detachFrames(STAGE_FRAME_KEY);
3265
3266    reset.ok = false;
3267
3268    return this;
3269  };
3270
3271  that.playVideo = function () {
3272    var dataFrame = activeFrame,
3273        video = dataFrame.video,
3274        _activeIndex = activeIndex;
3275
3276    if (typeof video === 'object' && dataFrame.videoReady) {
3277      o_nativeFullScreen && that.fullScreen && that.cancelFullScreen();
3278
3279      waitFor(function () {
3280        return !fullScreenApi.is() || _activeIndex !== activeIndex;
3281      }, function () {
3282        if (_activeIndex === activeIndex) {
3283          dataFrame.$video = dataFrame.$video || $($.Fotorama.jst.video(video));
3284          dataFrame.$video.appendTo(dataFrame[STAGE_FRAME_KEY]);
3285
3286          $wrap.addClass(wrapVideoClass);
3287          $videoPlaying = dataFrame.$video;
3288
3289          stageNoMove();
3290
3291          $arrs.blur();
3292          $fullscreenIcon.blur();
3293
3294          triggerEvent('loadvideo');
3295        }
3296      });
3297    }
3298
3299    return this;
3300  };
3301
3302  that.stopVideo = function () {
3303    unloadVideo($videoPlaying, true, true);
3304    return this;
3305  };
3306
3307  function unloadVideo ($video, unloadActiveFLAG, releaseAutoplayFLAG) {
3308    if (unloadActiveFLAG) {
3309      $wrap.removeClass(wrapVideoClass);
3310      $videoPlaying = false;
3311
3312      stageNoMove();
3313    }
3314
3315    if ($video && $video !== $videoPlaying) {
3316      $video.remove();
3317      triggerEvent('unloadvideo');
3318    }
3319
3320    if (releaseAutoplayFLAG) {
3321      releaseAutoplay();
3322      changeAutoplay();
3323    }
3324  }
3325
3326  function toggleControlsClass (FLAG) {
3327    $wrap.toggleClass(wrapNoControlsClass, FLAG);
3328  }
3329
3330  function stageCursor (e) {
3331    if (stageShaftTouchTail.flow) return;
3332
3333    var x = e ? e.pageX : stageCursor.x,
3334        pointerFLAG = x && !disableDirrection(getDirection(x)) && opts.click;
3335
3336    if (stageCursor.p !== pointerFLAG
3337        && $stage.toggleClass(pointerClass, pointerFLAG)) {
3338      stageCursor.p = pointerFLAG;
3339      stageCursor.x = x;
3340    }
3341  }
3342
3343  $stage.on('mousemove', stageCursor);
3344
3345  function clickToShow (showOptions) {
3346    clearTimeout(clickToShow.t);
3347
3348    if (opts.clicktransition && opts.clicktransition !== opts.transition) {
3349      ////console.log('change transition to: ' + opts.clicktransition);
3350
3351      // this timeout is for yield events flow
3352      setTimeout(function () {
3353        // save original transition for later
3354        var _o_transition = opts.transition;
3355
3356        that.setOptions({transition: opts.clicktransition});
3357
3358        // now safe to pass base transition to o_transition, so that.show will restor it
3359        o_transition = _o_transition;
3360        // this timeout is here to prevent jerking in some browsers
3361        clickToShow.t = setTimeout(function () {
3362          that.show(showOptions);
3363        }, 10);
3364      }, 0);
3365    } else {
3366      that.show(showOptions);
3367    }
3368  }
3369
3370  function onStageTap (e, toggleControlsFLAG) {
3371    //////console.time('onStageTap');
3372    var target = e.target,
3373        $target = $(target);
3374
3375    if ($target.hasClass(videoPlayClass)) {
3376      that.playVideo();
3377    } else if (target === fullscreenIcon) {
3378      that.toggleFullScreen();
3379    } else if ($videoPlaying) {
3380      target === videoClose && unloadVideo($videoPlaying, true, true);
3381    } else {
3382      if (toggleControlsFLAG) {
3383        toggleControlsClass();
3384      } else if (opts.click) {
3385
3386        clickToShow({index: e.shiftKey || getDirectionSign(getDirection(e._x)), slow: e.altKey, user: true});
3387      }
3388    }
3389    //////console.timeEnd('onStageTap');
3390  }
3391
3392  function updateTouchTails (key, value) {
3393    stageShaftTouchTail[key] = navShaftTouchTail[key] = value;
3394  }
3395
3396  stageShaftTouchTail = moveOnTouch($stageShaft, {
3397    onStart: onTouchStart,
3398    onMove: function (e, result) {
3399      setShadow($stage, result.edge);
3400    },
3401    onTouchEnd: onTouchEnd,
3402    onEnd: function (result) {
3403      //////console.time('stageShaftTouchTail.onEnd');
3404      setShadow($stage);
3405
3406      //////console.log('result', result);
3407
3408      var toggleControlsFLAG = (MS_POINTER && !hoverFLAG || result.touch) && opts.arrows && opts.arrows !== 'always';
3409
3410      if (result.moved || (toggleControlsFLAG && result.pos !== result.newPos && !result.control)) {
3411        var index = getIndexByPos(result.newPos, measures.w, opts.margin, repositionIndex);
3412        that.show({
3413          index: index,
3414          time: o_fade ? o_transitionDuration : result.time,
3415          overPos: result.overPos,
3416          user: true
3417        });
3418      } else if (!result.aborted && !result.control) {
3419        onStageTap(result.startEvent, toggleControlsFLAG);
3420      }
3421      //////console.timeEnd('stageShaftTouchTail.onEnd');
3422    },
3423//    getPos: function () {
3424//      return -getPosByIndex(dirtyIndex, measures.w, opts.margin, repositionIndex);
3425//    },
3426    //_001: true,
3427    timeLow: 1,
3428    timeHigh: 1,
3429    friction: 2,
3430    select: '.' + selectClass + ', .' + selectClass + ' *',
3431    $wrap: $stage
3432  });
3433
3434  navShaftTouchTail = moveOnTouch($navShaft, {
3435    onStart: onTouchStart,
3436    onMove: function (e, result) {
3437      setShadow($nav, result.edge);
3438    },
3439    onTouchEnd: onTouchEnd,
3440    onEnd: function (result) {
3441
3442      function onEnd () {
3443        slideNavShaft.l = result.newPos;
3444        releaseAutoplay();
3445        changeAutoplay();
3446        thumbsDraw(result.newPos, true);
3447      }
3448
3449      if (!result.moved) {
3450        var target = result.$target.closest('.' + navFrameClass, $navShaft)[0];
3451        target && onNavFrameClick.call(target, result.startEvent);
3452      } else if (result.pos !== result.newPos) {
3453        pausedAutoplayFLAG = true;
3454        slide($navShaft, {
3455          time: result.time,
3456          pos: result.newPos,
3457          overPos: result.overPos,
3458          onEnd: onEnd
3459        });
3460        thumbsDraw(result.newPos);
3461        o_shadows && setShadow($nav, findShadowEdge(result.newPos, navShaftTouchTail.min, navShaftTouchTail.max));
3462      } else {
3463        onEnd();
3464      }
3465    },
3466    timeLow: .5,
3467    timeHigh: 2,
3468    friction: 5,
3469    $wrap: $nav
3470  });
3471
3472  stageWheelTail = wheel($stage, {
3473    shift: true,
3474    onEnd: function (e, direction) {
3475      //////console.log('wheel $stage onEnd', direction);
3476      onTouchStart();
3477      onTouchEnd();
3478      that.show({index: direction, slow: e.altKey})
3479    }
3480  });
3481
3482  navWheelTail = wheel($nav, {
3483    onEnd: function (e, direction) {
3484      //////console.log('wheel $nav onEnd', direction);
3485      onTouchStart();
3486      onTouchEnd();
3487      var newPos = stop($navShaft) + direction * .25;
3488      $navShaft.css(getTranslate(minMaxLimit(newPos, navShaftTouchTail.min, navShaftTouchTail.max)));
3489      o_shadows && setShadow($nav, findShadowEdge(newPos, navShaftTouchTail.min, navShaftTouchTail.max));
3490      navWheelTail.prevent = {'<': newPos >= navShaftTouchTail.max, '>': newPos <= navShaftTouchTail.min};
3491      clearTimeout(navWheelTail.t);
3492      navWheelTail.t = setTimeout(function () {
3493        slideNavShaft.l = newPos;
3494        thumbsDraw(newPos, true)
3495      }, TOUCH_TIMEOUT);
3496      thumbsDraw(newPos);
3497    }
3498  });
3499
3500  $wrap.hover(
3501      function () {
3502        setTimeout(function () {
3503          if (touchedFLAG) return;
3504          toggleControlsClass(!(hoverFLAG = true));
3505        }, 0);
3506      }, function () {
3507        if (!hoverFLAG) return;
3508        toggleControlsClass(!(hoverFLAG = false));
3509      }
3510  );
3511
3512  function onNavFrameClick (e) {
3513    var index = $(this).data().eq;
3514    clickToShow({index: index, slow: e.altKey, user: true, coo: e._x - $nav.offset().left});
3515  }
3516
3517  function onArrClick (e) {
3518    clickToShow({index: $arrs.index(this) ? '>' : '<', slow: e.altKey, user: true});
3519  }
3520
3521  smartClick($arrs, function (e) {
3522    stopEvent(e);
3523    onArrClick.call(this, e);
3524  }, {
3525    onStart: function () {
3526      onTouchStart();
3527      stageShaftTouchTail.control = true;
3528    },
3529    onTouchEnd: onTouchEnd
3530  });
3531
3532  function addFocusOnControls (el) {
3533    addFocus(el, function () {
3534      setTimeout(function () {
3535        lockScroll($stage);
3536      }, 0);
3537      toggleControlsClass(false);
3538    });
3539  }
3540
3541  $arrs.each(function () {
3542    addEnterUp(this, function (e) {
3543      onArrClick.call(this, e);
3544    });
3545    addFocusOnControls(this);
3546  });
3547
3548  addEnterUp(fullscreenIcon, that.toggleFullScreen);
3549  addFocusOnControls(fullscreenIcon);
3550
3551  function reset () {
3552    setData();
3553    setOptions();
3554
3555    if (!reset.i) {
3556      reset.i = true;
3557      // Only once
3558      var _startindex = opts.startindex;
3559      if (_startindex || opts.hash && location.hash) {
3560        startIndex = getIndexFromHash(_startindex || location.hash.replace(/^#/, ''), data, that.index === 0 || _startindex, _startindex);
3561      }
3562      activeIndex = repositionIndex = dirtyIndex = lastActiveIndex = startIndex = edgeIndex(startIndex) || 0;/*(o_rtl ? size - 1 : 0)*///;
3563    }
3564
3565    if (size) {
3566      if (changeToRtl()) return;
3567
3568      if ($videoPlaying) {
3569        unloadVideo($videoPlaying, true);
3570      }
3571
3572      activeIndexes = [];
3573      detachFrames(STAGE_FRAME_KEY);
3574
3575      reset.ok = true;
3576
3577      that.show({index: activeIndex, time: 0});
3578      that.resize();
3579    } else {
3580      that.destroy();
3581    }
3582  }
3583
3584  function changeToRtl () {
3585    //////console.log('changeToRtl');
3586    if (!changeToRtl.f === o_rtl) {
3587      changeToRtl.f = o_rtl;
3588      activeIndex = size - 1 - activeIndex;
3589      //////console.log('changeToRtl execute, activeIndex is', activeIndex);
3590      that.reverse();
3591
3592      return true;
3593    }
3594  }
3595
3596  $.each('load push pop shift unshift reverse sort splice'.split(' '), function (i, method) {
3597    that[method] = function () {
3598      data = data || [];
3599      if (method !== 'load') {
3600        Array.prototype[method].apply(data, arguments);
3601      } else if (arguments[0] && typeof arguments[0] === 'object' && arguments[0].length) {
3602        data = clone(arguments[0]);
3603      }
3604      reset();
3605      return that;
3606    }
3607  });
3608
3609  function ready () {
3610    if (!ready.ok) {
3611      ready.ok = true;
3612      triggerEvent('ready');
3613    }
3614  }
3615
3616  reset();
3617};
3618
3619
3620$.fn.fotorama = function (opts) {
3621  return this.each(function () {
3622    var that = this,
3623        $fotorama = $(this),
3624        fotoramaData = $fotorama.data(),
3625        fotorama = fotoramaData.fotorama;
3626
3627    if (!fotorama) {
3628      waitFor(function () {
3629        return !isHidden(that);
3630      }, function () {
3631        fotoramaData.urtext = $fotorama.html();
3632        new $.Fotorama($fotorama,
3633            /* Priority for options:
3634             * 1. <div data-loop="true"></div>
3635             * 2. $('div').fotorama({loop: false})
3636             * 3. Defaults */
3637            $.extend(
3638                {},
3639                OPTIONS,
3640                window.fotoramaDefaults,
3641                opts,
3642                fotoramaData
3643            )
3644        );
3645      });
3646    } else {
3647      fotorama.setOptions(opts, true);
3648    }
3649  });
3650};
3651$.Fotorama.instances = [];
3652
3653function calculateIndexes () {
3654  $.each($.Fotorama.instances, function (index, instance) {
3655    instance.index = index;
3656  });
3657}
3658
3659function addInstance (instance) {
3660  $.Fotorama.instances.push(instance);
3661  calculateIndexes();
3662}
3663
3664function hideInstance (instance) {
3665  $.Fotorama.instances.splice(instance.index, 1);
3666  calculateIndexes();
3667}
3668$.Fotorama.cache = {};
3669$.Fotorama.measures = {};
3670$ = $ || {};
3671$.Fotorama = $.Fotorama || {};
3672$.Fotorama.jst = $.Fotorama.jst || {};
3673
3674$.Fotorama.jst.style = function(v) {
3675var __t, __p = '', __e = _.escape;
3676__p += '.fotorama' +
3677((__t = ( v.s )) == null ? '' : __t) +
3678' .fotorama__nav--thumbs .fotorama__nav__frame{\npadding:' +
3679((__t = ( v.m )) == null ? '' : __t) +
3680'px;\nheight:' +
3681((__t = ( v.h )) == null ? '' : __t) +
3682'px}\n.fotorama' +
3683((__t = ( v.s )) == null ? '' : __t) +
3684' .fotorama__thumb-border{\nheight:' +
3685((__t = ( v.h - v.b * (v.q ? 0 : 2) )) == null ? '' : __t) +
3686'px;\nborder-width:' +
3687((__t = ( v.b )) == null ? '' : __t) +
3688'px;\nmargin-top:' +
3689((__t = ( v.m )) == null ? '' : __t) +
3690'px}';
3691return __p
3692};
3693
3694$.Fotorama.jst.video = function(v) {
3695var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
3696function print() { __p += __j.call(arguments, '') }
3697__p += '<div class="fotorama__video"><iframe src="';
3698 print((v.type == 'youtube' ? v.p + 'youtube.com/embed/' + v.id +'?autoplay=1' : v.type == 'vimeo' ? v.p + 'player.vimeo.com/video/' + v.id + '?autoplay=1&badge=0' : v.id)  + (v.s && v.type != 'custom' ? '&' + v.s : '')) ;
3699__p += '" frameborder="0" allowfullscreen></iframe></div>\n';
3700return __p
3701};
3702$(function () {
3703  $('.' + _fotoramaClass + ':not([data-auto="false"])').fotorama();
3704});
3705})(window, document, location, typeof jQuery !== 'undefined' && 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.