JAVASCRIPT   35

strip.js

Guest on 12th August 2021 06:47:54 PM

  1. /*!
  2.  * Strip - An Unobtrusive Responsive Lightbox - v1.7.0
  3.  * (c)  Nick Stakenburg
  4.  *
  5.  * http://www.stripjs.com
  6.  *
  7.  * @license: https://creativecommons.org/licenses/by/4.0
  8.  */
  9.  
  10. // UMD wrapper
  11. (function(root, factory) {
  12.         if (typeof define === "function" && define.amd) {
  13.                 // AMD
  14.                 define(["jquery"], factory);
  15.         } else if (typeof module === "object" && module.exports) {
  16.                 // Node/CommonJS
  17.                 module.exports = factory(require("jquery"));
  18.         } else {
  19.                 // Browser global
  20.                 root.Strip = factory(jQuery);
  21.         }
  22. })(this, function($) {
  23.         var Strip = {
  24.                 version: "1.7.0"
  25.         };
  26.  
  27.         Strip.Skins = {
  28.                 // the default skin
  29.                 strip: {}
  30.         };
  31.  
  32.         var Browser = (function(uA) {
  33.                 function getVersion(identifier) {
  34.                         var version = new RegExp(identifier + "([\\d.]+)").exec(uA);
  35.                         return version ? parseFloat(version[1]) : true;
  36.                 }
  37.  
  38.                 return {
  39.                         IE:
  40.                                 !!(window.attachEvent && uA.indexOf("Opera") === -1) &&
  41.                                 getVersion("MSIE "),
  42.                         Opera:
  43.                                 uA.indexOf("Opera") > -1 &&
  44.                                 ((!!window.opera && opera.version && parseFloat(opera.version())) ||
  45.                                         7.55),
  46.                         WebKit: uA.indexOf("AppleWebKit/") > -1 && getVersion("AppleWebKit/"),
  47.                         Gecko:
  48.                                 uA.indexOf("Gecko") > -1 &&
  49.                                 uA.indexOf("KHTML") === -1 &&
  50.                                 getVersion("rv:"),
  51.                         MobileSafari: !!uA.match(/Apple.*Mobile.*Safari/),
  52.                         Chrome: uA.indexOf("Chrome") > -1 && getVersion("Chrome/"),
  53.                         ChromeMobile: uA.indexOf("CrMo") > -1 && getVersion("CrMo/"),
  54.                         Android: uA.indexOf("Android") > -1 && getVersion("Android "),
  55.                         IEMobile: uA.indexOf("IEMobile") > -1 && getVersion("IEMobile/")
  56.                 };
  57.         })(navigator.userAgent);
  58.  
  59.         var _slice = Array.prototype.slice;
  60.  
  61.         // Fit
  62.         var Fit = {
  63.                 within: function(bounds, dimensions) {
  64.                         var options = $.extend(
  65.                                 {
  66.                                         height: true,
  67.                                         width: true
  68.                                 },
  69.                                 arguments[2] || {}
  70.                         );
  71.  
  72.                         var size = $.extend({}, dimensions),
  73.                                 scale = 1,
  74.                                 attempts = 5;
  75.  
  76.                         var fit = { width: options.width, height: options.height };
  77.  
  78.                         // adjust the bounds depending on what to fit (width/height)
  79.                         // start
  80.                         while (
  81.                                 attempts > 0 &&
  82.                                 ((fit.width && size.width > bounds.width) ||
  83.                                         (fit.height && size.height > bounds.height))
  84.                                 ) {
  85.                                 // if both dimensions fall underneath a minimum, then don't event continue
  86.                                 //if (size.width < 100 && size.height < 100) {
  87.                                 var scaleX = 1,
  88.                                         scaleY = 1;
  89.  
  90.                                 if (fit.width && size.width > bounds.width) {
  91.                                         scaleX = bounds.width / size.width;
  92.                                 }
  93.                                 if (fit.height && size.height > bounds.height) {
  94.                                         scaleY = bounds.height / size.height;
  95.                                 }
  96.  
  97.                                 // we'll end up using the largest scaled down factor
  98.                                 var scale = Math.min(scaleX, scaleY);
  99.  
  100.                                 // adjust current size, based on original dimensions
  101.                                 size = {
  102.                                         width: Math.round(dimensions.width * scale),
  103.                                         height: Math.round(dimensions.height * scale)
  104.                                 };
  105.                                 //}
  106.  
  107.                                 attempts--;
  108.                         }
  109.  
  110.                         // make sure size is never pressed into negative
  111.                         size.width = Math.max(size.width, 0);
  112.                         size.height = Math.max(size.height, 0);
  113.  
  114.                         return size;
  115.                 }
  116.         };
  117.  
  118.         // we only uses some of the jQueryUI easing functions
  119.         // add those with a prefix to prevent conflicts
  120.         $.extend($.easing, {
  121.                 stripEaseInCubic: function(x, t, b, c, d) {
  122.                         return c * (t /= d) * t * t + b;
  123.                 },
  124.  
  125.                 stripEaseInSine: function(x, t, b, c, d) {
  126.                         return -c * Math.cos((t / d) * (Math.PI / 2)) + c + b;
  127.                 },
  128.  
  129.                 stripEaseOutSine: function(x, t, b, c, d) {
  130.                         return c * Math.sin((t / d) * (Math.PI / 2)) + b;
  131.                 }
  132.         });
  133.  
  134.         var Support = (function() {
  135.                 var testElement = document.createElement("div"),
  136.                         domPrefixes = "Webkit Moz O ms Khtml".split(" ");
  137.  
  138.                 function prefixed(property) {
  139.                         return testAllProperties(property, "prefix");
  140.                 }
  141.  
  142.                 function testProperties(properties, prefixed) {
  143.                         for (var i in properties) {
  144.                                 if (testElement.style[properties[i]] !== undefined) {
  145.                                         return prefixed == "prefix" ? properties[i] : true;
  146.                                 }
  147.                         }
  148.                         return false;
  149.                 }
  150.  
  151.                 function testAllProperties(property, prefixed) {
  152.                         var ucProperty = property.charAt(0).toUpperCase() + property.substr(1),
  153.                                 properties = (
  154.                                         property +
  155.                                         " " +
  156.                                         domPrefixes.join(ucProperty + " ") +
  157.                                         ucProperty
  158.                                 ).split(" ");
  159.  
  160.                         return testProperties(properties, prefixed);
  161.                 }
  162.  
  163.                 // feature detect
  164.                 return {
  165.                         css: {
  166.                                 animation: testAllProperties("animation"),
  167.                                 transform: testAllProperties("transform"),
  168.                                 prefixed: prefixed
  169.                         },
  170.  
  171.                         svg:
  172.                                 !!document.createElementNS &&
  173.                                 !!document.createElementNS("http://www.w3.org/2000/svg", "svg")
  174.                                         .createSVGRect,
  175.  
  176.                         touch: (function() {
  177.                                 try {
  178.                                         return !!(
  179.                                                 "ontouchstart" in window ||
  180.                                                 (window.DocumentTouch && document instanceof DocumentTouch)
  181.                                         ); // firefox on Android
  182.                                 } catch (e) {
  183.                                         return false;
  184.                                 }
  185.                         })()
  186.                 };
  187.         })();
  188.  
  189.         // add mobile touch to support
  190.         Support.mobileTouch =
  191.                 Support.touch &&
  192.                 (Browser.MobileSafari ||
  193.                         Browser.Android ||
  194.                         Browser.IEMobile ||
  195.                         Browser.ChromeMobile ||
  196.                         !/^(Win|Mac|Linux)/.test(navigator.platform)); // otherwise, assume anything not on Windows, Mac or Linux is a mobile device
  197.  
  198.         var Bounds = {
  199.                 viewport: function() {
  200.                         var dimensions = {
  201.                                 width: $(window).width()
  202.                         };
  203.  
  204.                         // Mobile Safari has a bugged viewport height after scrolling
  205.                         // Firefox on Android also has problems with height
  206.                         if (Browser.MobileSafari || (Browser.Android && Browser.Gecko)) {
  207.                                 var zoom = document.documentElement.clientWidth / window.innerWidth;
  208.                                 dimensions.height = window.innerHeight * zoom;
  209.                         } else {
  210.                                 // default
  211.                                 dimensions.height = $(window).height();
  212.                         }
  213.  
  214.                         return dimensions;
  215.                 }
  216.         };
  217.  
  218.         /* ImageReady (standalone) - part of VoilÃ
  219.          * http://voila.nickstakenburg.com
  220.          * MIT License
  221.          *//
  222.         var ImageReady = (function($) {
  223.                 var Poll = function() {
  224.                         return this.initialize.apply(this, Array.prototype.slice.call(arguments));
  225.                 };
  226.                 $.extend(Poll.prototype, {
  227.                         initialize: function() {
  228.                                 this.options = $.extend(
  229.                                         {
  230.                                                 test: function() {},
  231.                                                 success: function() {},
  232.                                                 timeout: function() {},
  233.                                                 callAt: false,
  234.                                                 intervals: [
  235.                                                         [0, 0],
  236.                                                         [1 * 1000, 10],
  237.                                                         [2 * 1000, 50],
  238.                                                         [4 * 1000, 100],
  239.                                                         [20 * 1000, 500]
  240.                                                 ]
  241.                                         },
  242.                                         arguments[0] || {}
  243.                                 );
  244.  
  245.                                 this._test = this.options.test;
  246.                                 this._success = this.options.success;
  247.                                 this._timeout = this.options.timeout;
  248.  
  249.                                 this._ipos = 0;
  250.                                 this._time = 0;
  251.                                 this._delay = this.options.intervals[this._ipos][1];
  252.                                 this._callTimeouts = [];
  253.  
  254.                                 this.poll();
  255.                                 this._createCallsAt();
  256.                         },
  257.  
  258.                         poll: function() {
  259.                                 this._polling = setTimeout(
  260.                                         $.proxy(function() {
  261.                                                 if (this._test()) {
  262.                                                         this.success();
  263.                                                         return;
  264.                                                 }
  265.  
  266.                                         // update time
  267.  
  268.                                                 this._time += this._delay;
  269.  
  270.                                         // next i within the interval
  271.  
  272.                                                 if (this._time >= this.options.intervals[this._ipos][0]) {
  273.                                                 // timeout when no next interval
  274.  
  275.                                                         if (!this.options.intervals[this._ipos + 1]) {
  276.                                                                 if ($.type(this._timeout) == "function") {
  277.                                                                         this._timeout();
  278.                                                                 }
  279.                                                                 return;
  280.                                                         }
  281.  
  282.                                                         this._ipos++;
  283.  
  284.                                                 // update to the new bracket
  285.  
  286.                                                         this._delay = this.options.intervals[this._ipos][1];
  287.                                                 }
  288.  
  289.                                                 this.poll();
  290.                                         }, this),
  291.                                         this._delay
  292.                                 );
  293.                         },
  294.  
  295.                         success: function() {
  296.                                 this.abort();
  297.                                 this._success();
  298.                         },
  299.  
  300.                         _createCallsAt: function() {
  301.                                 if (!this.options.callAt) return;
  302.  
  303.                         // start a timer for each call
  304.  
  305.                                 $.each(
  306.                                         this.options.callAt,
  307.                                         $.proxy(function(i, at) {
  308.                                                 var time = at[0],
  309.                                                         fn = at[1];
  310.  
  311.                                                 var timeout = setTimeout(
  312.                                                         $.proxy(function() {
  313.                                                                 fn();
  314.                                                         }, this),
  315.                                                         time
  316.                                                 );
  317.  
  318.                                                 this._callTimeouts.push(timeout);
  319.                                         }, this)
  320.                                 );
  321.                         },
  322.  
  323.                         _stopCallTimeouts: function() {
  324.                                 $.each(this._callTimeouts, function(i, timeout) {
  325.                                         clearTimeout(timeout);
  326.                                 });
  327.                                 this._callTimeouts = [];
  328.                         },
  329.  
  330.                         abort: function() {
  331.                                 this._stopCallTimeouts();
  332.  
  333.                                 if (this._polling) {
  334.                                         clearTimeout(this._polling);
  335.                                         this._polling = null;
  336.                                 }
  337.                         }
  338.                 });
  339.  
  340.                 var ImageReady = function() {
  341.                         return this.initialize.apply(this, Array.prototype.slice.call(arguments));
  342.                 };
  343.                 $.extend(ImageReady.prototype, {
  344.                         supports: {
  345.                                 naturalWidth: (function() {
  346.                                         return "naturalWidth" in new Image();
  347.                                 })()
  348.                         },
  349.  
  350.                 // NOTE: setTimeouts allow callbacks to be attached
  351.  
  352.                         initialize: function(img, successCallback, errorCallback) {
  353.                                 this.img = $(img)[0];
  354.                                 this.successCallback = successCallback;
  355.                                 this.errorCallback = errorCallback;
  356.                                 this.isLoaded = false;
  357.  
  358.                                 this.options = $.extend(
  359.                                         {
  360.                                                 method: "onload",
  361.                                                 pollFallbackAfter: 1000
  362.                                         },
  363.                                         arguments[3] || {}
  364.                                 );
  365.  
  366.                         // onload and a fallback for no naturalWidth support (IE6-7)
  367.  
  368.                                 if (this.options.method == "onload" || !this.supports.naturalWidth) {
  369.                                         this.load();
  370.                                         return;
  371.                                 }
  372.  
  373.                         // start polling
  374.  
  375.                                 this.poll();
  376.                         },
  377.  
  378.                 // NOTE: Polling for naturalWidth is only reliable if the
  379.  
  380.                 // <img>.src never changes. naturalWidth isn't always reset
  381.  
  382.                 // to 0 after the src changes (depending on how the spec
  383.  
  384.                 // was implemented). The spec even seems to be against
  385.  
  386.                 // this, making polling unreliable in those cases.
  387.  
  388.                         poll: function() {
  389.                                 this._poll = new Poll({
  390.                                         test: $.proxy(function() {
  391.                                                 return this.img.naturalWidth > 0;
  392.                                         }, this),
  393.  
  394.                                         success: $.proxy(function() {
  395.                                                 this.success();
  396.                                         }, this),
  397.  
  398.                                         timeout: $.proxy(function() {
  399.                                         // error on timeout
  400.  
  401.                                                 this.error();
  402.                                         }, this),
  403.  
  404.                                         callAt: [
  405.                                                 [
  406.                                                         this.options.pollFallbackAfter,
  407.                                                         $.proxy(function() {
  408.                                                                 this.load();
  409.                                                         }, this)
  410.                                                 ]
  411.                                         ]
  412.                                 });
  413.                         },
  414.  
  415.                         load: function() {
  416.                                 this._loading = setTimeout(
  417.                                         $.proxy(function() {
  418.                                                 var image = new Image();
  419.                                                 this._onloadImage = image;
  420.  
  421.                                                 image.onload = $.proxy(function() {
  422.                                                         image.onload = function() {};
  423.  
  424.                                                         if (!this.supports.naturalWidth) {
  425.                                                                 this.img.naturalWidth = image.width;
  426.                                                                 this.img.naturalHeight = image.height;
  427.                                                                 image.naturalWidth = image.width;
  428.                                                                 image.naturalHeight = image.height;
  429.                                                         }
  430.  
  431.                                                         this.success();
  432.                                                 }, this);
  433.  
  434.                                                 image.onerror = $.proxy(this.error, this);
  435.  
  436.                                                 image.src = this.img.src;
  437.                                         }, this)
  438.                                 );
  439.                         },
  440.  
  441.                         success: function() {
  442.                                 if (this._calledSuccess) return;
  443.  
  444.                                 this._calledSuccess = true;
  445.  
  446.                         // stop loading/polling
  447.  
  448.                                 this.abort();
  449.  
  450.                         // some time to allow layout updates, IE requires this!
  451.  
  452.                                 this.waitForRender(
  453.                                         $.proxy(function() {
  454.                                                 this.isLoaded = true;
  455.                                                 this.successCallback(this);
  456.                                         }, this)
  457.                                 );
  458.                         },
  459.  
  460.                         error: function() {
  461.                                 if (this._calledError) return;
  462.  
  463.                                 this._calledError = true;
  464.  
  465.                         // stop loading/polling
  466.  
  467.                                 this.abort();
  468.  
  469.                         // don't wait for an actual render on error, just timeout
  470.  
  471.                         // to give the browser some time to render a broken image icon
  472.  
  473.                                 this._errorRenderTimeout = setTimeout(
  474.                                         $.proxy(function() {
  475.                                                 if (this.errorCallback) this.errorCallback(this);
  476.                                         }, this)
  477.                                 );
  478.                         },
  479.  
  480.                         abort: function() {
  481.                                 this.stopLoading();
  482.                                 this.stopPolling();
  483.                                 this.stopWaitingForRender();
  484.                         },
  485.  
  486.                         stopPolling: function() {
  487.                                 if (this._poll) {
  488.                                         this._poll.abort();
  489.                                         this._poll = null;
  490.                                 }
  491.                         },
  492.  
  493.                         stopLoading: function() {
  494.                                 if (this._loading) {
  495.                                         clearTimeout(this._loading);
  496.                                         this._loading = null;
  497.                                 }
  498.  
  499.                                 if (this._onloadImage) {
  500.                                         this._onloadImage.onload = function() {};
  501.                                         this._onloadImage.onerror = function() {};
  502.                                 }
  503.                         },
  504.  
  505.                 // used by success() only
  506.  
  507.                         waitForRender: function(callback) {
  508.                                 this._renderTimeout = setTimeout(callback);
  509.                         },
  510.  
  511.                         stopWaitingForRender: function() {
  512.                                 if (this._renderTimeout) {
  513.                                         clearTimeout(this._renderTimeout);
  514.                                         this._renderTimeout = null;
  515.                                 }
  516.  
  517.                                 if (this._errorRenderTimeout) {
  518.                                         clearTimeout(this._errorRenderTimeout);
  519.                                         this._errorRenderTimeout = null;
  520.                                 }
  521.                         }
  522.                 });
  523.  
  524.                 return ImageReady;
  525.         })(jQuery);
  526.  
  527. // Spinner
  528.  
  529. // Create pure CSS based spinners
  530.  
  531.         function Spinner() {
  532.                 return this.initialize.apply(this, _slice.call(arguments));
  533.         }
  534.  
  535. // mark as supported
  536.  
  537.         Spinner.supported = Support.css.transform && Support.css.animation;
  538.  
  539.         $.extend(Spinner.prototype, {
  540.                 initialize: function(element) {
  541.                         this.element = $(element);
  542.                         if (!this.element[0]) return;
  543.  
  544.                         this.classPrefix = "strp-";
  545.  
  546.                         this.setOptions(arguments[1] || {});
  547.  
  548.                         this.element.addClass(this.classPrefix + "spinner");
  549.                         this.element.append(
  550.                                 (this._rotate = $("<div>").addClass(
  551.                                         this.classPrefix + "spinner-rotate"
  552.                                 ))
  553.                         );
  554.  
  555.                         this.build();
  556.                         this.start();
  557.                 },
  558.  
  559.                 setOptions: function(options) {
  560.                         this.options = $.extend(
  561.                                 {
  562.                                         show: 200,
  563.                                         hide: 200
  564.                                 },
  565.                                 options || {}
  566.                         );
  567.                 },
  568.  
  569.                 build: function() {
  570.                         if (this._build) return;
  571.  
  572.                         this._rotate.html(");
  573.  
  574.                         var d = (this.options.length + this.options.radius) * 2,
  575.                                 dimensions = { height: d, width: d };
  576.  
  577.                         // we parse stuff below so make sure that happens with a visible spinner
  578.                         var is_vis = this.element.is("":visible");
  579.                         if (!is_vis) this.element.show();
  580.  
  581.                 // find the amount of lines
  582.  
  583.                         var frame, line;
  584.                         this._rotate.append(
  585.                                 (frame = $("<div>")
  586.                                         .addClass(this.classPrefix + "spinner-frame")
  587.                                         .append(
  588.                                                 (line = $("<div>").addClass(this.classPrefix + "spinner-line"))
  589.                                         ))
  590.                         );
  591.  
  592.                         var lines = parseInt($(line).css("z-index"));
  593.                         this.lines = lines;
  594.                 // now reset that z-index
  595.  
  596.                         line.css({ "z-index": "inherit" });
  597.  
  598.                         frame.remove();
  599.  
  600.                 // reset visibility
  601.  
  602.                         if (!is_vis) this.element.hide();
  603.  
  604.                 // insert frames
  605.  
  606.                         var color;
  607.                         for (var i = 0; i < lines; i++) {
  608.                                 var frame, line;
  609.                                 this._rotate.append(
  610.                                         (frame = $("<div>")
  611.                                                 .addClass(this.classPrefix + "spinner-frame")
  612.                                                 .append(
  613.                                                         (line = $("<div>").addClass(this.classPrefix + "spinner-line"))
  614.                                                 ))
  615.                                 );
  616.  
  617.                                 color = color || line.css("color");
  618.                                 line.css({ background: color });
  619.  
  620.                                 frame.css({ opacity: ((1 / lines) * (i + 1)).toFixed(2) });
  621.  
  622.                                 var transformCSS = {};
  623.                                 transformCSS[Support.css.prefixed("transform")] =
  624.                                         "rotate(" + (360 / lines) * (i + 1) + "deg)";
  625.                                 frame.css(transformCSS);
  626.                         }
  627.  
  628.                         this._build = true;
  629.                 },
  630.  
  631.                 start: function() {
  632.                         var rotateCSS = {};
  633.                         rotateCSS[Support.css.prefixed("animation")] =
  634.                                 this.classPrefix + "spinner-spin 1s infinite steps(" + this.lines + ")";
  635.                         this._rotate.css(rotateCSS);
  636.                 },
  637.  
  638.                 stop: function() {
  639.                         var rotateCSS = {};
  640.                         rotateCSS[Support.css.prefixed("animation")] = "none";
  641.                         this._rotate.css(rotateCSS);
  642.                 },
  643.  
  644.                 show: function(callback) {
  645.                         this.build();
  646.                         this.start();
  647.  
  648.                         this.element.stop(true).fadeTo(this.options.show, 1, callback);//deferred.resolve);
  649.  
  650.                 },
  651.  
  652.                 hide: function(callback) {
  653.                         this.element.stop(true).fadeOut(
  654.                                 this.options.hide,
  655.                                 $.proxy(function() {
  656.                                         this.stop();
  657.                                         if (callback) callback();
  658.                                 }, this)
  659.                         );
  660.                 },
  661.  
  662.                 refresh: function() {
  663.                         this._build = false;
  664.                         this.build();
  665.                 }
  666.         });
  667.  
  668.         function Timers() {
  669.                 return this.initialize.apply(this, _slice.call(arguments));
  670.         }
  671.         $.extend(Timers.prototype, {
  672.                 initialize: function() {
  673.                         this._timers = {};
  674.                 },
  675.  
  676.                 set: function(name, handler, ms) {
  677.                         this._timers[name] = setTimeout(handler, ms);
  678.                 },
  679.  
  680.                 get: function(name) {
  681.                         return this._timers[name];
  682.                 },
  683.  
  684.                 clear: function(name) {
  685.                         if (name) {
  686.                                 if (this._timers[name]) {
  687.                                         clearTimeout(this._timers[name]);
  688.                                         delete this._timers[name];
  689.                                 }
  690.                         } else {
  691.                                 this.clearAll();
  692.                         }
  693.                 },
  694.  
  695.                 clearAll: function() {
  696.                         $.each(this._timers, function(i, timer) {
  697.                                 clearTimeout(timer);
  698.                         });
  699.                         this._timers = {};
  700.                 }
  701.         });
  702.  
  703. // uses Types to scan a URI for info
  704.  
  705.         function getURIData(url) {
  706.                 var result = { type: "image" };
  707.                 $.each(Types, function(i, type) {
  708.                         var data = type.data(url);
  709.                         if (data) {
  710.                                 result = data;
  711.                                 result.type = i;
  712.                                 result.url = url;
  713.                         }
  714.                 });
  715.  
  716.                 return result;
  717.         }
  718.  
  719.         function detectExtension(url) {
  720.                 var ext = (url || ").replace(/\\.*/g, "").match(/\\([^.]{3,4})$/);
  721.                 return ext ? ext[1].toLowerCase() : null;
  722.         }
  723.  
  724.         var Types = {
  725.                 image: {
  726.                         extensions: ""bmp gif jpeg jpg png webp",
  727.                         detect: function(url) {
  728.                                 return $.inArray(detectExtension(url), this.extensions.split(" ")) > -1;
  729.                         },
  730.                         data: function(url) {
  731.                                 if (!this.detect()) return false;
  732.  
  733.                                 return {
  734.                                         extension: detectExtension(url)
  735.                                 };
  736.                         }
  737.                 },
  738.  
  739.                 youtube: {
  740.                         detect: function(url) {
  741.                                 var res = (youtube\.com|youtu\.be)\/watch\?(?=.*vi?=([a-zA-Z0-9-_]+))(?:\S+)?$/..exec(
  742.                                         url
  743.                                 );
  744.                                 if (res && res[2]) return res[2];
  745.  
  746.                                 res = (youtube\.com|youtu\.be)\/(vi?\/|u\/|embed\/)?([a-zA-Z0-9-_]+)(?:\S+)?$/i..exec(
  747.                                         url
  748.                                 );
  749.                                 if (res && res[3]) return res[3];
  750.  
  751.                                 return false;
  752.                         },
  753.                         data: function(url) {
  754.                                 var id = this.detect(url);
  755.                                 if (!id) return false;
  756.  
  757.                                 return {
  758.                                         id: id
  759.                                 };
  760.                         }
  761.                 },
  762.  
  763.                 vimeo: {
  764.                         detect: function(url) {
  765.                                 var res = (vimeo\.com)\/([a-zA-Z0-9-_]+)(?:\S+)?$/i..exec(url);
  766.                                 if (res && res[2]) return res[2];
  767.  
  768.                                 return false;
  769.                         },
  770.                         data: function(url) {
  771.                                 var id = this.detect(url);
  772.                                 if (!id) return false;
  773.  
  774.                                 return {
  775.                                         id: id
  776.                                 };
  777.                         }
  778.                 }
  779.         };
  780.  
  781.         var VimeoReady = (function() {
  782.                 var VimeoReady = function() {
  783.                         return this.initialize.apply(this, _slice.call(arguments));
  784.                 };
  785.                 $.extend(VimeoReady.prototype, {
  786.                         initialize: function(url, callback) {
  787.                                 this.url = url;
  788.                                 this.callback = callback;
  789.  
  790.                                 this.load();
  791.                         },
  792.  
  793.                         load: function() {
  794.                         // first try the cache
  795.  
  796.                                 var cache = Cache.get(this.url);
  797.  
  798.                                 if (cache) {
  799.                                         return this.callback(cache.data);
  800.                                 }
  801.  
  802.                                 var protocol =
  803.                                         "http" +
  804.                                         (window.location && window.location.protocol == "https:"
  805.                                                 ? "s"
  806.                                                 : ") +
  807.                                         "":",
  808.                                         video_id = getURIData(this.url).id;
  809.  
  810.                                 this._xhr = $.getJSON(
  811.                                         protocol +
  812.                                         //vimeo.com/api/oembed.json?url=" +
  813.  
  814.                                         protocol +
  815.                                         //vimeo.com/" +
  816.  
  817.                                         video_id +
  818.                                         "&maxwidth=9999999&maxheight=9999999&callback=?",
  819.                                         $.proxy(function(_data) {
  820.                                                 var data = {
  821.                                                         dimensions: {
  822.                                                                 width: _data.width,
  823.                                                                 height: _data.height
  824.                                                         }
  825.                                                 };
  826.  
  827.                                                 Cache.set(this.url, data);
  828.  
  829.                                                 if (this.callback) this.callback(data);
  830.                                         }, this)
  831.                                 );
  832.                         },
  833.  
  834.                         abort: function() {
  835.                                 if (this._xhr) {
  836.                                         this._xhr.abort();
  837.                                         this._xhr = null;
  838.                                 }
  839.                         }
  840.                 });
  841.  
  842.                 var Cache = {
  843.                         cache: [],
  844.  
  845.                         get: function(url) {
  846.                                 var entry = null;
  847.                                 for (var i = 0; i < this.cache.length; i++) {
  848.                                         if (this.cache[i] && this.cache[i].url == url) entry = this.cache[i];
  849.                                 }
  850.                                 return entry;
  851.                         },
  852.  
  853.                         set: function(url, data) {
  854.                                 this.remove(url);
  855.                                 this.cache.push({ url: url, data: data });
  856.                         },
  857.  
  858.                         remove: function(url) {
  859.                                 for (var i = 0; i < this.cache.length; i++) {
  860.                                         if (this.cache[i] && this.cache[i].url == url) {
  861.                                                 delete this.cache[i];
  862.                                         }
  863.                                 }
  864.                         }
  865.                 };
  866.  
  867.                 return VimeoReady;
  868.         })();
  869.  
  870.         var Options = {
  871.                 defaults: {
  872.                         effects: {
  873.                                 spinner: { show: 200, hide: 200 },
  874.                                 transition: { min: 175, max: 250 },
  875.                                 ui: { show: 0, hide: 200 },
  876.                                 window: { show: 300, hide: 300 }
  877.                         },
  878.                         hideOnClickOutside: true,
  879.                         keyboard: {
  880.                                 left: true,
  881.                                 right: true,
  882.                                 esc: true
  883.                         },
  884.                         loop: true,
  885.                         overlap: true,
  886.                         preload: [1, 2],
  887.                         position: true,
  888.                         skin: "strip",
  889.                         side: "right",
  890.                         spinner: true,
  891.                         toggle: true,
  892.                         uiDelay: 3000,
  893.                         vimeo: {
  894.                                 autoplay: 1,
  895.                                 api: 1,
  896.                                 title: 1,
  897.                                 byline: 1,
  898.                                 portrait: 0,
  899.                                 loop: 0
  900.                         },
  901.                         youtube: {
  902.                                 autoplay: 1,
  903.                                 controls: 1,
  904.                                 enablejsapi: 1,
  905.                                 hd: 1,
  906.                                 iv_load_policy: 3,
  907.                                 loop: 0,
  908.                                 modestbranding: 1,
  909.                                 rel: 0,
  910.                                 vq: "hd1080"// force hd: http://stackoverflow.com/a/12467865
  911.  
  912.                         },
  913.  
  914.                         initialTypeOptions: {
  915.                                 image: {},
  916.                                 vimeo: {
  917.                                         width: 1280
  918.                                 },
  919.                         // Youtube needs both dimensions, it doesn't support fetching video dimensions like Vimeo yet.
  920.  
  921.                         // Star this ticket if you'd like to get support for it at some point:
  922.  
  923.                         // https://code.google.com/p/gdata-issues/issues/detail?id=4329
  924.  
  925.                                 youtube: {
  926.                                         width: 1280,
  927.                                         height: 720
  928.                                 }
  929.                         }
  930.                 },
  931.  
  932.                 create: function(opts, type, data) {
  933.                         opts = opts || {};
  934.                         data = data || {};
  935.  
  936.                         opts.skin = opts.skin || this.defaults.skin;
  937.  
  938.                         var selected = opts.skin
  939.                                 ? $.extend(
  940.                                         {},
  941.                                         Strip.Skins[opts.skin] || Strip.Skins[this.defaults.skin]
  942.                                 )
  943.                                 : {},
  944.                                 merged = $.extend(true, {}, this.defaults, selected);
  945.  
  946.                 // merge initial type options
  947.  
  948.                         if (merged.initialTypeOptions) {
  949.                                 if (type && merged.initialTypeOptions[type]) {
  950.                                         merged = $.extend(true, {}, merged.initialTypeOptions[type], merged);
  951.                                 }
  952.                         // these aren't used further, so remove them
  953.  
  954.                                 delete merged.initialTypeOptions;
  955.                         }
  956.  
  957.                 // safe options to work with
  958.  
  959.                         var options = $.extend(true, {}, merged, opts);
  960.  
  961.                 // set all effect duration to 0 for effects: false
  962.  
  963.                 // IE8 and below never use effects
  964.  
  965.                         if (!options.effects || (Browser.IE && Browser.IE < 9)) {
  966.                                 options.effects = {};
  967.                                 $.each(this.defaults.effects, function(name, effect) {
  968.                                         $.each((options.effects[name] = $.extend({}, effect)), function(
  969.                                                 option
  970.                                         ) {
  971.                                                 options.effects[name][option] = 0;
  972.                                         });
  973.                                 });
  974.  
  975.                         // disable the spinner when effects are disabled
  976.  
  977.                                 options.spinner = false;
  978.                         }
  979.  
  980.                 // keyboard
  981.  
  982.                         if (options.keyboard) {
  983.                         // when keyboard is true, enable all keys
  984.  
  985.                                 if ($.type(options.keyboard) === "boolean") {
  986.                                         options.keyboard = {};
  987.                                         $.each(this.defaults.keyboard, function(key, bool) {
  988.                                                 options.keyboard[key] = true;
  989.                                         });
  990.                                 }
  991.  
  992.                         // disable left and right keys for video, because players like
  993.  
  994.                         // youtube use these keys
  995.  
  996.                                 if (type === "vimeo" || type === "youtube") {
  997.                                         $.extend(options.keyboard, { left: false, right: false });
  998.                                 }
  999.                         }
  1000.  
  1001.                 // vimeo & youtube always have no overlap
  1002.  
  1003.                         if (type === "vimeo" || type === "youtube") {
  1004.                                 options.overlap = false;
  1005.                         }
  1006.  
  1007.                         return options;
  1008.                 }
  1009.         };
  1010.  
  1011.         function View() {
  1012.                 this.initialize.apply(this, _slice.call(arguments));
  1013.         }
  1014.         $.extend(View.prototype, {
  1015.                 initialize: function(object) {
  1016.                         var options = arguments[1] || {},
  1017.                                 data = {};
  1018.  
  1019.                 // string -> element
  1020.  
  1021.                         if ($.type(object) === "string") {
  1022.                         // turn the string into an element
  1023.  
  1024.                                 object = { url: object };
  1025.                         }
  1026.  
  1027.                 // element -> object
  1028.  
  1029.                         else if (object && object.nodeType === 1) {
  1030.                                 var element = $(object);
  1031.  
  1032.                                 object = {
  1033.                                         element: element[0],
  1034.                                         url: element.attr("href"),
  1035.                                         caption: element.attr("data-strip-caption"),
  1036.                                         group: element.attr("data-strip-group"),
  1037.                                         extension: element.attr("data-strip-extension"),
  1038.                                         type: element.attr("data-strip-type"),
  1039.                                         options:
  1040.                                                 (element.attr("data-strip-options") &&
  1041.                                                         eval("({" + element.attr("data-strip-options") + "})")) ||
  1042.                                                 {}
  1043.                                 };
  1044.                         }
  1045.  
  1046.                         if (object) {
  1047.                         // detect type if none is set
  1048.  
  1049.                                 if (!object.extension) {
  1050.                                         object.extension = detectExtension(object.url);
  1051.                                 }
  1052.  
  1053.                                 if (!object.type) {
  1054.                                         var data = getURIData(object.url);
  1055.                                         object._data = data;
  1056.                                         object.type = data.type;
  1057.                                 }
  1058.                         }
  1059.  
  1060.                         if (!object._data) {
  1061.                                 object._data = getURIData(object.url);
  1062.                         }
  1063.  
  1064.                         if (object && object.options) {
  1065.                                 object.options = $.extend(
  1066.                                         true,
  1067.                                         $.extend({}, options),
  1068.                                         $.extend({}, object.options)
  1069.                                 );
  1070.                         } else {
  1071.                                 object.options = $.extend({}, options);
  1072.                         }
  1073.                 // extend the options
  1074.  
  1075.                         object.options = Options.create(
  1076.                                 object.options,
  1077.                                 object.type,
  1078.                                 object._data
  1079.                         );
  1080.  
  1081.                 // extend this with data
  1082.  
  1083.                         $.extend(this, object);
  1084.  
  1085.                         return this;
  1086.                 }
  1087.         });
  1088.  
  1089.         var Pages = {
  1090.                 initialize: function(element) {
  1091.                         this.element = element;
  1092.                         this.pages = {};
  1093.                         this.uid = 1;
  1094.                 },
  1095.  
  1096.                 add: function(views) {
  1097.                         this.uid++;
  1098.  
  1099.                         this.views = views;
  1100.  
  1101.                         this.pages[this.uid] = [];// create room for these pages
  1102.  
  1103.  
  1104.                 // switched pages, so show the UI on the next resize
  1105.  
  1106.                         Window._showUIOnResize = true;
  1107.  
  1108.                 // add pages for all these views
  1109.  
  1110.                         $.each(
  1111.                                 views,
  1112.                                 $.proxy(function(i, view) {
  1113.                                         this.pages[this.uid].push(new Page(view, i + 1, this.views.length));
  1114.                                 }, this)
  1115.                         );
  1116.                 },
  1117.  
  1118.                 show: function(position, callback) {
  1119.                         var page = this.pages[this.uid][position - 1];
  1120.  
  1121.                 // never try to reload the exact same frame
  1122.  
  1123.                         if (this.page && this.page.uid == page.uid) {
  1124.                         // also hide the window if toggle is enabled
  1125.  
  1126.                                 if (page.view.options.toggle) {
  1127.                                         Window.hide();
  1128.                                 // clear the page so double clicking when hiding will
  1129.  
  1130.                                 // re-open the window even if it's in a hide animation
  1131.  
  1132.                                         this.page = null;
  1133.                                 }
  1134.  
  1135.                                 return;
  1136.                         }
  1137.  
  1138.                 // set class names to indicate active state
  1139.  
  1140.                         Pages.setActiveClass(page);
  1141.  
  1142.                 // update the page
  1143.  
  1144.                         this.page = page;
  1145.  
  1146.                         this.removeHiddenAndLoadingInactive();
  1147.                         page.show(
  1148.                                 $.proxy(function() {
  1149.                                 // once a page has been fully shown we mark Pages as not being switched anymore
  1150.  
  1151.                                         this._switched = false;
  1152.                                         if (callback) callback();
  1153.                                 }, this)
  1154.                         );
  1155.                 },
  1156.  
  1157.                 getLoadingCount: function() {
  1158.                 // we only stop loading if all the frames we have are not loading anymore
  1159.  
  1160.                         var count = 0;
  1161.                         $.each(this.pages, function(id, pages) {
  1162.                                 $.each(pages, function(j, page) {
  1163.                                         if (page.loading) count++;
  1164.                                 });
  1165.                         });
  1166.                         return count;
  1167.                 },
  1168.  
  1169.         // used by the API when opening
  1170.  
  1171.         // checks if the page is in the currently open group
  1172.  
  1173.                 getPositionInActivePageGroup: function(element) {
  1174.                         var position = 0,
  1175.                                 activeGroup = this.pages[this.uid];
  1176.  
  1177.                         if (activeGroup) {
  1178.                                 $.each(activeGroup, function(i, page) {
  1179.                                         if (page.view.element && page.view.element == element) {
  1180.                                                 position = i + 1;
  1181.                                         }
  1182.                                 });
  1183.                         }
  1184.  
  1185.                         return position;
  1186.                 },
  1187.  
  1188.         // remove pages not matching the current id
  1189.  
  1190.                 removeExpired: function(instantly) {
  1191.                         $.each(this.pages, function(id, pages) {
  1192.                                 if (id != this._id) {
  1193.                                         $.each(pages, function(j, page) {
  1194.                                                 page.remove(instantly);
  1195.                                         });
  1196.                                 }
  1197.                         });
  1198.                 },
  1199.  
  1200.         // Window.hide will call this when fully closed
  1201.  
  1202.                 removeAll: function() {
  1203.                         $.each(this.pages, function(id, pages) {
  1204.                                 $.each(pages, function(j, page) {
  1205.                                         page.remove();
  1206.                                 });
  1207.                         });
  1208.  
  1209.                 // empty out pages
  1210.  
  1211.                         this.pages = {};
  1212.                 },
  1213.  
  1214.                 hideVisibleInactive: function(alternateDuration) {
  1215.                         $.each(
  1216.                                 this.pages,
  1217.                                 $.proxy(function(id, pages) {
  1218.                                         $.each(
  1219.                                                 pages,
  1220.                                                 $.proxy(function(j, page) {
  1221.                                                         if (page.uid != this.page.uid) {
  1222.                                                                 page.hide(null, alternateDuration);
  1223.                                                         }
  1224.                                                 }, this)
  1225.                                         );
  1226.                                 }, this)
  1227.                         );
  1228.                 },
  1229.  
  1230.                 stopInactive: function() {
  1231.                         $.each(
  1232.                                 this.pages,
  1233.                                 $.proxy(function(id, pages) {
  1234.                                         $.each(
  1235.                                                 pages,
  1236.                                                 $.proxy(function(j, page) {
  1237.                                                         if (page.uid != this.page.uid && !page.preloading) {
  1238.                                                                 page.stop();
  1239.                                                         }
  1240.                                                 }, this)
  1241.                                         );
  1242.                                 }, this)
  1243.                         );
  1244.                 },
  1245.  
  1246.         // TODO: might be nice to have a hide animation before removal, it's instant now
  1247.  
  1248.                 removeHiddenAndLoadingInactive: function() {
  1249.                 // track which inactive page groups are empty
  1250.  
  1251.                         var empty = [];
  1252.  
  1253.                         $.each(
  1254.                                 this.pages,
  1255.                                 $.proxy(function(uid, pages) {
  1256.                                 // only remove pages in the groups that are currently not active
  1257.  
  1258.                                         if (uid != this.uid) {
  1259.                                                 var removed = 0;
  1260.  
  1261.                                                 $.each(
  1262.                                                         pages,
  1263.                                                         $.proxy(function(j, page) {
  1264.                                                         // remove hidden or loading, but dont'remove frames in animation
  1265.  
  1266.                                                                 if ((!page.visible || page.loading) && !page.animatingWindow) {
  1267.                                                                         page.remove();
  1268.                                                                 }
  1269.  
  1270.                                                                 if (page.removed) removed++;// count all, not those we remove now
  1271.  
  1272.                                                         }, this)
  1273.                                                 );
  1274.  
  1275.                                         // if we've removed all pages from this group it's safe to remove it
  1276.  
  1277.                                         // we don't do this in the loop but below
  1278.  
  1279.                                                 if (removed == pages.length) {
  1280.                                                         empty.push(uid);
  1281.                                                 }
  1282.                                         }
  1283.                                 }, this)
  1284.                         );
  1285.  
  1286.                 // now remove all empty page groups
  1287.  
  1288.                         $.each(
  1289.                                 empty,
  1290.                                 $.proxy(function(i, uid) {
  1291.                                         delete this.pages[uid];
  1292.                                 }, this)
  1293.                         );
  1294.                 },
  1295.  
  1296.                 stop: function() {
  1297.                         $.each(this.pages, function(id, pages) {
  1298.                                 $.each(pages, function(j, page) {
  1299.                                         page.stop();
  1300.                                 });
  1301.                         });
  1302.                 },
  1303.  
  1304.                 setActiveClass: function(page) {
  1305.                 // switch the active element class
  1306.  
  1307.                         this.removeActiveClasses();
  1308.  
  1309.                 // add the active class if the new page is bound to an element
  1310.  
  1311.                         var element = page.view.element;
  1312.                         if (element) {
  1313.                                 $(element).addClass("strip-active-element strip-active-group");
  1314.  
  1315.                         // also give other items in the group the active group class
  1316.  
  1317.                                 var group = $(element).attr("data-strip-group");
  1318.                                 if (group) {
  1319.                                         $('.strip[data-strip-group="' + group + '"]').addClass(
  1320.                                                 "strip-active-group"
  1321.                                         );
  1322.                                 }
  1323.                         }
  1324.                 },
  1325.  
  1326.                 removeActiveClasses: function() {
  1327.                         $(".strip-active-group").removeClass(
  1328.                                 "strip-active-group strip-active-element"
  1329.                         );
  1330.                 }
  1331.         };
  1332.  
  1333.         var Page = (function() {
  1334.                 var uid = 0,
  1335.                         loadedUrlCache = {};
  1336.  
  1337.                 function Page() {
  1338.                         return this.initialize.apply(this, _slice.call(arguments));
  1339.                 }
  1340.                 $.extend(Page.prototype, {
  1341.                         initialize: function(view, position, total) {
  1342.                                 this.view = view;
  1343.                                 this.dimensions = { width: 0, height: 0 };
  1344.                                 this.uid = uid++;
  1345.  
  1346.                         // store position/total views for later use
  1347.  
  1348.                                 this._position = position;
  1349.                                 this._total = total;
  1350.  
  1351.                                 this.animated = false;
  1352.                                 this.visible = false;
  1353.  
  1354.                                 this.queues = {};
  1355.                                 this.queues.showhide = $({});
  1356.                         },
  1357.  
  1358.                 // create the page, this doesn't mean it's loaded
  1359.  
  1360.                 // should happen instantly
  1361.  
  1362.                         create: function() {
  1363.                                 if (this._created) return;
  1364.  
  1365.                                 Pages.element.append(
  1366.                                         (this.element = $("<div>")
  1367.                                                 .addClass("strp-page")
  1368.                                                 .append((this.container = $("<div>").addClass("strp-container")))
  1369.                                                 .css({ opacity: 0 })
  1370.                                                 .hide())
  1371.                                 );
  1372.  
  1373.                                 var hasPosition = this.view.options.position && this._total > 1;
  1374.                                 if (this.view.caption || hasPosition) {
  1375.                                         this.element.append(
  1376.                                                 (this.info = $("<div>")
  1377.                                                         .addClass("strp-info")
  1378.                                                         .append(
  1379.                                                                 (this.info_padder = $("<div>").addClass("strp-info-padder"))
  1380.                                                         ))
  1381.                                         );
  1382.  
  1383.                                 // insert caption first because it floats right
  1384.  
  1385.                                         if (hasPosition) {
  1386.                                                 this.element.addClass("strp-has-position");
  1387.  
  1388.                                                 this.info_padder.append(
  1389.                                                         $("<div>")
  1390.                                                                 .addClass("strp-position")
  1391.                                                                 .html(this._position + " / " + this._total)
  1392.                                                 );
  1393.                                         }
  1394.  
  1395.                                         if (this.view.caption) {
  1396.                                                 this.info_padder.append(
  1397.                                                         (this.caption = $("<div>")
  1398.                                                                 .addClass("strp-caption")
  1399.                                                                 .html(this.view.caption))
  1400.                                                 );
  1401.                                         }
  1402.                                 }
  1403.  
  1404.                                 switch (this.view.type) {
  1405.                                         case "image":
  1406.                                                 this.container.append(
  1407.                                                         (this.content = $("<img>").attr({ src: this.view.url }))
  1408.                                                 );
  1409.                                                 break;
  1410.  
  1411.                                         case "vimeo":
  1412.                                         case "youtube":
  1413.                                                 this.container.append((this.content = $("<div>")));
  1414.                                                 break;
  1415.                                 }
  1416.  
  1417.                         // ui
  1418.  
  1419.                                 this.element.addClass(
  1420.                                         "strp" + (this.view.options.overlap ? " : ""-no") + "-overlap"
  1421.                                 );
  1422.  
  1423.                         // no sides
  1424.  
  1425.                                 if (this._total < 2) {
  1426.                                         this.element.addClass("strp-no-sides");
  1427.                                 }
  1428.  
  1429.                                 this.content.addClass("strp-content-element");
  1430.  
  1431.                                 this._created = true;
  1432.                         },
  1433.  
  1434.                 // surrounding
  1435.  
  1436.                         _getSurroundingPages: function() {
  1437.                                 var preload;
  1438.                                 if (!(preload = this.view.options.preload)) return [];
  1439.  
  1440.                                 var pages = [],
  1441.                                         begin = Math.max(1, this._position - preload[0]),
  1442.                                         end = Math.min(this._position + preload[1], this._total),
  1443.                                         pos = this._position;
  1444.  
  1445.                         // add the pages after this one first for the preloading order
  1446.  
  1447.                                 for (var i = pos; i <= end; i++) {
  1448.                                         var page = Pages.pages[Pages.uid][i - 1];
  1449.                                         if (page._position != pos) pages.push(page);
  1450.                                 }
  1451.  
  1452.                                 for (var i = pos; i >= begin; i--) {
  1453.                                         var page = Pages.pages[Pages.uid][i - 1];
  1454.                                         if (page._position != pos) pages.push(page);
  1455.                                 }
  1456.  
  1457.                                 return pages;
  1458.                         },
  1459.  
  1460.                         preloadSurroundingImages: function() {
  1461.                                 var pages = this._getSurroundingPages();
  1462.  
  1463.                                 $.each(
  1464.                                         pages,
  1465.                                         $.proxy(function(i, page) {
  1466.                                                 page.preload();
  1467.                                         }, this)
  1468.                                 );
  1469.                         },
  1470.  
  1471.                 // preload is a non-abortable preloader,
  1472.  
  1473.                 // so that it doesn't interfere with our regular load
  1474.  
  1475.                         preload: function() {
  1476.                                 if (
  1477.                                         this.preloading ||
  1478.                                         this.preloaded ||
  1479.                                         this.view.type !== "image" ||
  1480.                                         !this.view.options.preload ||
  1481.                                         this.loaded// page might be loaded before it's preloaded so also stop there
  1482.  
  1483.                                 ) {
  1484.                                         return;
  1485.                                 }
  1486.  
  1487.                         // make sure the page is created
  1488.  
  1489.                                 this.create();
  1490.  
  1491.                                 this.preloading = true;
  1492.  
  1493.                                 new ImageReady(
  1494.                                         this.content[0],
  1495.                                         $.proxy(function(imageReady) {
  1496.                                                 this.loaded = true;
  1497.                                                 this.preloading = false;
  1498.                                                 this.preloaded = true;
  1499.  
  1500.                                                 this.dimensions = {
  1501.                                                         width: imageReady.img.naturalWidth,
  1502.                                                         height: imageReady.img.naturalHeight
  1503.                                                 };
  1504.                                         }, this),
  1505.                                         null,
  1506.                                         { method: "naturalWidth" }
  1507.                                 );
  1508.                         },
  1509.  
  1510.                 // the purpose of load is to set dimensions
  1511.  
  1512.                 // we use it to set dimensions even for content that doesn't load like youtube
  1513.  
  1514.                         load: function(callback, isPreload) {
  1515.                         // make sure the page is created
  1516.  
  1517.                                 this.create();
  1518.  
  1519.                         // exit early if already loaded
  1520.  
  1521.                                 if (this.loaded) {
  1522.                                         if (callback) callback();
  1523.                                         return;
  1524.                                 }
  1525.  
  1526.                         // abort possible previous (pre)load
  1527.  
  1528.                                 this.abort();
  1529.  
  1530.                         // loading indicator, we don't show it when preloading frames
  1531.  
  1532.                                 this.loading = true;
  1533.  
  1534.                         // start spinner
  1535.  
  1536.                         // only when this url hasn't been loaded before
  1537.  
  1538.                                 if (this.view.options.spinner && !loadedUrlCache[this.view.url]) {
  1539.                                         Window.startLoading();
  1540.                                 }
  1541.  
  1542.                                 switch (this.view.type) {
  1543.                                         case "image":
  1544.                                         // if we had an error before just go through
  1545.  
  1546.                                                 if (this.error) {
  1547.                                                         if (callback) callback();
  1548.                                                         return;
  1549.                                                 }
  1550.  
  1551.                                                 this.imageReady = new ImageReady(
  1552.                                                         this.content[0],
  1553.                                                         $.proxy(function(imageReady) {
  1554.                                                         // mark as loaded
  1555.  
  1556.                                                                 this._markAsLoaded();
  1557.  
  1558.                                                                 this.dimensions = {
  1559.                                                                         width: imageReady.img.naturalWidth,
  1560.                                                                         height: imageReady.img.naturalHeight
  1561.                                                                 };
  1562.  
  1563.                                                                 if (callback) callback();
  1564.                                                         }, this),
  1565.                                                         $.proxy(function() {
  1566.                                                         // mark as loaded
  1567.  
  1568.                                                                 this._markAsLoaded();
  1569.  
  1570.                                                                 this.content.hide();
  1571.                                                                 this.container.append(
  1572.                                                                         (this.error = $("<div>").addClass("strp-error"))
  1573.                                                                 );
  1574.                                                                 this.element.addClass("strp-has-error");
  1575.  
  1576.                                                                 this.dimensions = {
  1577.                                                                         width: this.error.outerWidth(),
  1578.                                                                         height: this.error.outerHeight()
  1579.                                                                 };
  1580.  
  1581.                                                                 if (callback) callback();
  1582.                                                         }, this),
  1583.                                                         { method: "naturalWidth" }
  1584.                                                 );
  1585.  
  1586.                                                 break;
  1587.  
  1588.                                         case "vimeo":
  1589.                                                 this.vimeoReady = new VimeoReady(
  1590.                                                         this.view.url,
  1591.                                                         $.proxy(function(data) {
  1592.                                                         // mark as loaded
  1593.  
  1594.                                                                 this._markAsLoaded();
  1595.  
  1596.                                                                 this.dimensions = {
  1597.                                                                         width: data.dimensions.width,
  1598.                                                                         height: data.dimensions.height
  1599.                                                                 };
  1600.  
  1601.                                                                 if (callback) callback();
  1602.                                                         }, this)
  1603.                                                 );
  1604.  
  1605.                                                 break;
  1606.  
  1607.                                         case "youtube":
  1608.                                         // mark as loaded
  1609.  
  1610.                                                 this._markAsLoaded();
  1611.  
  1612.                                                 this.dimensions = {
  1613.                                                         width: this.view.options.width,
  1614.                                                         height: this.view.options.height
  1615.                                                 };
  1616.  
  1617.                                                 if (callback) callback();
  1618.                                                 break;
  1619.                                 }
  1620.                         },
  1621.  
  1622.                 // helper for load()
  1623.  
  1624.                         _markAsLoaded: function() {
  1625.                                 this.loading = false;
  1626.                                 this.loaded = true;
  1627.  
  1628.                         // mark url as loaded so we can avoid
  1629.  
  1630.                         // showing the spinner again
  1631.  
  1632.                                 loadedUrlCache[this.view.url] = true;
  1633.  
  1634.                                 Window.stopLoading();
  1635.                         },
  1636.  
  1637.                         isVideo: function() {
  1638.                                 return ^(youtube|vimeo)$/..test(this.view.type);
  1639.                         },
  1640.  
  1641.                         insertVideo: function(callback) {
  1642.                         // don't insert a video twice
  1643.  
  1644.                         // and stop if not a video
  1645.  
  1646.                                 if (this.playerIframe || !this.isVideo()) {
  1647.                                         if (callback) callback();
  1648.                                         return;
  1649.                                 }
  1650.  
  1651.                                 var protocol =
  1652.                                         "http" +
  1653.                                         (window.location && window.location.protocol === "https:"
  1654.                                                 ? "s"
  1655.                                                 : ") +
  1656.                                         "":",
  1657.                                         playerVars = $.extend({}, this.view.options[this.view.type] || {}),
  1658.                                         queryString = $.param(playerVars),
  1659.                                         urls = {
  1660.                                                 vimeo: protocol + //player.vimeo.com/video/{id}?{queryString}",
  1661.  
  1662.                                                 youtube: protocol + //www.youtube.com/embed/{id}?{queryString}"
  1663.  
  1664.                                         },
  1665.                                         src = urls[this.view.type]
  1666.                                                 .replace("{id}", this.view._data.id)
  1667.                                                 .replace("{queryString}", queryString);
  1668.  
  1669.                                 this.content.append(
  1670.                                         (this.playerIframe = $(
  1671.                                                 "<iframe webkitAllowFullScreen mozallowfullscreen allowFullScreen>"
  1672.                                         ).attr({
  1673.                                                 src: src,
  1674.                                                 height: this.contentDimensions.height,
  1675.                                                 width: this.contentDimensions.width,
  1676.                                                 frameborder: 0
  1677.                                         }))
  1678.                                 );
  1679.  
  1680.                                 if (callback) callback();
  1681.                         },
  1682.  
  1683.                         raise: function() {
  1684.                         // no need to raise if we're already the topmost element
  1685.  
  1686.                         // this helps avoid unnecessary detaching of the element
  1687.  
  1688.                                 var lastChild = Pages.element[0].lastChild;
  1689.                                 if (lastChild && lastChild === this.element[0]) {
  1690.                                         return;
  1691.                                 }
  1692.  
  1693.                                 Pages.element.append(this.element);
  1694.                         },
  1695.  
  1696.                         show: function(callback) {
  1697.                                 var shq = this.queues.showhide;
  1698.                                 shq.queue([]);// clear queue
  1699.  
  1700.  
  1701.                                 this.animated = true;
  1702.                                 this.animatingWindow = false;
  1703.  
  1704.                                 shq.queue(function(next_stopped_inactive) {
  1705.                                         Pages.stopInactive();
  1706.                                         next_stopped_inactive();
  1707.                                 });
  1708.  
  1709.                                 shq.queue(
  1710.                                         $.proxy(function(next_side) {
  1711.                                                 Window.setSide(this.view.options.side, next_side);
  1712.                                         }, this)
  1713.                                 );
  1714.  
  1715.                         // make sure the current page is inserted
  1716.  
  1717.                                 shq.queue(
  1718.                                         $.proxy(function(next_loaded) {
  1719.                                         // give the spinner the options of this page
  1720.  
  1721.                                                 if (this.view.options.spinner && Window._spinner) {
  1722.                                                         Window.setSpinnerSkin(this.view.options.skin);
  1723.                                                         Window._spinner.setOptions(this.view.options.effects.spinner);
  1724.                                                         Window._spinner.refresh();
  1725.                                                 }
  1726.  
  1727.                                         // load
  1728.  
  1729.                                                 this.load(
  1730.                                                         $.proxy(function() {
  1731.                                                                 this.preloadSurroundingImages();
  1732.                                                                 next_loaded();
  1733.                                                         }, this)
  1734.                                                 );
  1735.                                         }, this)
  1736.                                 );
  1737.  
  1738.                                 shq.queue(
  1739.                                         $.proxy(function(next_utility) {
  1740.                                                 this.raise();
  1741.  
  1742.                                                 Window.setSkin(this.view.options.skin);
  1743.                                                 Window.bindUI();// enable ui controls
  1744.  
  1745.  
  1746.                                         // keyboard
  1747.  
  1748.                                                 Keyboard.enable(this.view.options.keyboard);
  1749.  
  1750.                                                 this.fitToWindow();
  1751.  
  1752.                                                 next_utility();
  1753.                                         }, this)
  1754.                                 );
  1755.  
  1756.                         // we bind hide on click outside with a delay so API calls can pass through.
  1757.  
  1758.                         // more on this in api.js
  1759.  
  1760.                                 shq.queue(
  1761.                                         $.proxy(function(next_bind_hide_on_click_outside) {
  1762.                                                 Window.timers.set(
  1763.                                                         "bind-hide-on-click-outside",
  1764.                                                         $.proxy(function() {
  1765.                                                                 Window.bindHideOnClickOutside();
  1766.                                                                 next_bind_hide_on_click_outside();
  1767.                                                         }, this),
  1768.                                                         1
  1769.                                                 );
  1770.                                         }, this)
  1771.                                 );
  1772.  
  1773.                         // vimeo and youtube use this for insertion
  1774.  
  1775.                                 if (this.isVideo()) {
  1776.                                         shq.queue(
  1777.                                                 $.proxy(function(next_video_inserted) {
  1778.                                                         this.insertVideo(
  1779.                                                                 $.proxy(function() {
  1780.                                                                         next_video_inserted();
  1781.                                                                 })
  1782.                                                         );
  1783.                                                 }, this)
  1784.                                         );
  1785.                                 }
  1786.  
  1787.                                 shq.queue(
  1788.                                         $.proxy(function(next_shown_and_resized) {
  1789.                                                 this.animatingWindow = true;// we're modifying Window size
  1790.  
  1791.  
  1792.                                                 var fx = 3;
  1793.  
  1794.                                         // store duration on resize and use it for the other animations
  1795.  
  1796.                                                 var z = this.getOrientation() === "horizontal" ? "width" : "height";
  1797.  
  1798.                                         // onShow callback
  1799.  
  1800.                                                 var onShow = this.view && this.view.options.onShow;
  1801.                                                 if ($.type(onShow) === "function") {
  1802.                                                         onShow.call(Strip);
  1803.                                                 }
  1804.  
  1805.                                                 var duration = Window.resize(
  1806.                                                         this[z],
  1807.                                                         function() {
  1808.                                                                 if (--fx < 1) next_shown_and_resized();
  1809.                                                         },
  1810.                                                         duration
  1811.                                                 );
  1812.  
  1813.                                                 this._show(function() {
  1814.                                                         if (--fx < 1) next_shown_and_resized();
  1815.                                                 }, duration);
  1816.  
  1817.                                                 Window.adjustPrevNext(function() {
  1818.                                                         if (--fx < 1) next_shown_and_resized();
  1819.                                                 }, duration);
  1820.  
  1821.                                                 if (Window._showUIOnResize) {
  1822.                                                         Window.showUI(null, duration);
  1823.  
  1824.                                                 // don't show the UI the next time, it'll show up
  1825.  
  1826.                                                 // when we set this flag again
  1827.  
  1828.                                                         Window._showUIOnResize = false;
  1829.                                                 }
  1830.  
  1831.                                         // we also don't track this
  1832.  
  1833.                                                 Pages.hideVisibleInactive(duration);
  1834.                                         }, this)
  1835.                                 );
  1836.  
  1837.                                 shq.queue(
  1838.                                         $.proxy(function(next_set_visible) {
  1839.                                         // Window.resize takes this into account
  1840.  
  1841.                                                 this.animatingWindow = false;
  1842.  
  1843.                                                 this.animated = false;
  1844.  
  1845.                                                 this.visible = true;
  1846.  
  1847.                                         // NOTE: disabled to allow the UI to fade out at all times
  1848.  
  1849.                                         //Window.startUITimer();
  1850.  
  1851.  
  1852.                                                 if (callback) callback();
  1853.  
  1854.                                                 next_set_visible();
  1855.                                         }, this)
  1856.                                 );
  1857.                         },
  1858.  
  1859.                         _show: function(callback, alternateDuration) {
  1860.                                 var duration = !Window.visible
  1861.                                         ? 0
  1862.                                         : $.type(alternateDuration) === "number"
  1863.                                                 ? alternateDuration
  1864.                                                 : this.view.options.effects.transition.min;
  1865.  
  1866.                                 this.element
  1867.                                         .stop(true)
  1868.                                         .show()
  1869.                                         .fadeTo(duration || 0, 1, callback);
  1870.                         },
  1871.  
  1872.                         hide: function(callback, alternateDuration) {
  1873.                                 if (!this.element) return;// nothing to hide yet
  1874.  
  1875.  
  1876.                                 this.removeVideo();
  1877.  
  1878.                         // abort possible loading
  1879.  
  1880.                                 this.abort();
  1881.  
  1882.                                 var duration = this.view.options.effects.transition.min;
  1883.                                 if ($.type(alternateDuration) === "number")
  1884.                                         duration = alternateDuration;
  1885.  
  1886.                         // hide video instantly
  1887.  
  1888.                                 var isVideo = this.isVideo();
  1889.                                 if (isVideo) duration = 0;
  1890.  
  1891.                         // stop, delay & effect
  1892.  
  1893.                                 this.element
  1894.                                         .stop(true)
  1895.                                 // we use alternative easing to minize background color showing through a lowered opacity fade
  1896.  
  1897.                                 // while images are trading places
  1898.  
  1899.                                         .fadeTo(
  1900.                                                 duration,
  1901.                                                 0,
  1902.                                                 "stripEaseInCubic",
  1903.                                                 $.proxy(function() {
  1904.                                                         this.element.hide();
  1905.                                                         this.visible = false;
  1906.                                                         if (callback) callback();
  1907.                                                 }, this)
  1908.                                         );
  1909.                         },
  1910.  
  1911.                 // stop everything
  1912.  
  1913.                         stop: function() {
  1914.                                 var shq = this.queues.showhide;
  1915.                                 shq.queue([]);// clear queue
  1916.  
  1917.  
  1918.                         // stop animations
  1919.  
  1920.                                 if (this.element) this.element.stop(true);
  1921.  
  1922.                         // stop possible loading
  1923.  
  1924.                                 this.abort();
  1925.                         },
  1926.  
  1927.                         removeVideo: function() {
  1928.                                 if (this.playerIframe) {
  1929.                                 // this fixes a bug where sound keep playing after
  1930.  
  1931.                                 // removing the iframe in IE10+
  1932.  
  1933.                                         this.playerIframe[0].src = //about:blank";
  1934.  
  1935.  
  1936.                                         this.playerIframe.remove();
  1937.                                         this.playerIframe = null;
  1938.                                 }
  1939.                         },
  1940.  
  1941.                         remove: function() {
  1942.                                 this.stop();
  1943.                                 this.removeVideo();
  1944.                                 if (this.element) this.element.remove();
  1945.                                 this.visible = false;
  1946.                                 this.removed = true;
  1947.                         },
  1948.  
  1949.                         abort: function() {
  1950.                                 if (this.imageReady && !this.preloading) {
  1951.                                         this.imageReady.abort();
  1952.                                         this.imageReady = null;
  1953.                                 }
  1954.  
  1955.                                 if (this.vimeoReady) {
  1956.                                         this.vimeoReady.abort();
  1957.                                         this.vimeoReady = null;
  1958.                                 }
  1959.  
  1960.                                 this.loading = false;
  1961.                                 Window.stopLoading();
  1962.                         },
  1963.  
  1964.                         _getDimensionsFitToView: function() {
  1965.                                 var bounds = $.extend({}, this.dimensions),
  1966.                                         dimensions = $.extend({}, this.dimensions);
  1967.  
  1968.                                 var options = this.view.options;
  1969.                                 if (options.maxWidth) bounds.width = options.maxWidth;
  1970.                                 if (options.maxHeight) bounds.heigth = options.maxHeight;
  1971.  
  1972.                                 dimensions = Fit.within(bounds, dimensions);
  1973.  
  1974.                                 return dimensions;
  1975.                         },
  1976.  
  1977.                         getOrientation: function(side) {
  1978.                                 return this.view.options.side === "left" ||
  1979.                                 this.view.options.side === "right"
  1980.                                         ? "horizontal"
  1981.                                         : "vertical";
  1982.                         },
  1983.  
  1984.                         fitToWindow: function() {
  1985.                                 var page = this.element,
  1986.                                         dimensions = this._getDimensionsFitToView(),
  1987.                                         viewport = Bounds.viewport(),
  1988.                                         bounds = $.extend({}, viewport),
  1989.                                         orientation = this.getOrientation(),
  1990.                                         z = orientation === "horizontal" ? "width" : "height";
  1991.  
  1992.                                 var container = page.find(".strp-container");
  1993.  
  1994.                         // add the safety
  1995.  
  1996.                                 Window.element.removeClass("strp-measured");
  1997.                                 var win = Window.element,
  1998.                                         isFullscreen =
  1999.                                                 z === "width"
  2000.                                                         ? parseInt(win.css("min-width")) > 0
  2001.                                                         : parseInt(win.css("min-height")) > 0,
  2002.                                         safety = isFullscreen
  2003.                                                 ? 0
  2004.                                                 : parseInt(
  2005.                                                         win.css("margin-" + (z === "width" ? "left" : "bottom"))
  2006.                                                 );
  2007.                                 Window.element.addClass("strp-measured");
  2008.  
  2009.                                 bounds[z] -= safety;
  2010.  
  2011.                                 var paddingX =
  2012.                                         parseInt(container.css("padding-left")) +
  2013.                                         parseInt(container.css("padding-right")),
  2014.                                         paddingY =
  2015.                                                 parseInt(container.css("padding-top")) +
  2016.                                                 parseInt(container.css("padding-bottom")),
  2017.                                         padding = { x: paddingX, y: paddingY };
  2018.  
  2019.                                 bounds.width -= paddingX;
  2020.                                 bounds.height -= paddingY;
  2021.  
  2022.                                 var fitted = Fit.within(bounds, dimensions),
  2023.                                         contentDimensions = $.extend({}, fitted),
  2024.                                         content = this.content;
  2025.  
  2026.                         // if we have an error message, use that as content instead
  2027.  
  2028.                                 if (this.error) {
  2029.                                         content = this.error;
  2030.                                 }
  2031.  
  2032.                                 var info = this.info,
  2033.                                         cH = 0;
  2034.  
  2035.                         // when there's an info bar size has to be adjusted
  2036.  
  2037.                                 if (info) {
  2038.                                 // make sure the window and the page are visible during all of this
  2039.  
  2040.                                         var windowVisible = Window.element.is(":visible");
  2041.                                         if (!windowVisible) Window.element.show();
  2042.  
  2043.                                         var pageVisible = page.is(":visible");
  2044.                                         if (!pageVisible) page.show();
  2045.  
  2046.                                 // width
  2047.  
  2048.                                         if (z === "width") {
  2049.                                                 page.css({
  2050.                                                         width: isFullscreen ? viewport.width : fitted.width + paddingX
  2051.                                                 });
  2052.  
  2053.                                                 var initialBoundsHeight = bounds.height;
  2054.  
  2055.                                                 content.hide();
  2056.                                                 cH = info.outerHeight();
  2057.                                                 content.show();
  2058.  
  2059.                                                 bounds.height = initialBoundsHeight - cH;
  2060.  
  2061.                                                 contentDimensions = Fit.within(bounds, dimensions);
  2062.  
  2063.                                         // left/right requires further adjustment of the caption
  2064.  
  2065.                                                 var initialImageSize = $.extend({}, contentDimensions),
  2066.                                                         initialCH = cH,
  2067.                                                         newCW,
  2068.                                                         previousCH,
  2069.                                                         shrunkW;
  2070.  
  2071.                                                 var attempts = isFullscreen ? 0 : 4;// fullscreen doesn't need extra resizing
  2072.  
  2073.  
  2074.                                                 while (
  2075.                                                         attempts > 0 &&
  2076.                                                         (shrunkW = fitted.width - contentDimensions.width)
  2077.                                                         ) {
  2078.                                                         page.css({ width: fitted.width + paddingX - shrunkW });
  2079.  
  2080.                                                         previousCH = cH;
  2081.  
  2082.                                                         content.hide();
  2083.                                                         cH = info.outerHeight();
  2084.  
  2085.                                                         newCW = Math.max(
  2086.                                                                 this.caption ? this.caption.outerWidth() + paddingX : 0,
  2087.                                                                 this.position ? this.position.outerWidth() + paddingX : 0
  2088.                                                         );
  2089.                                                         content.show();
  2090.  
  2091.                                                         if (
  2092.                                                                 cH === previousCH &&
  2093.                                                                 newCW <= fitted.width + paddingX - shrunkW
  2094.                                                         ) {
  2095.                                                         // safe to keep this width, so store it
  2096.  
  2097.                                                                 fitted.width -= shrunkW;
  2098.                                                         } else {
  2099.                                                         // we try again with the increased caption
  2100.  
  2101.                                                                 bounds.height = initialBoundsHeight - cH;
  2102.  
  2103.                                                                 contentDimensions = Fit.within(bounds, dimensions);
  2104.  
  2105.                                                         // restore if the last attempt failed
  2106.  
  2107.                                                                 if (attempts - 1 <= 0) {
  2108.                                                                 // otherwise the caption increased in height, go back
  2109.  
  2110.                                                                         page.css({ width: fitted.width + paddingX });
  2111.                                                                         contentDimensions = initialImageSize;
  2112.                                                                         cH = initialCH;
  2113.                                                                 }
  2114.                                                         }
  2115.  
  2116.                                                         attempts--;
  2117.                                                 }
  2118.                                         } else {
  2119.                                         // fix IE7 not respecting width:100% in the CSS
  2120.  
  2121.                                         // so info height is measured correctly
  2122.  
  2123.                                                 if (Browser.IE && Browser.IE < 8) {
  2124.                                                         page.css({ width: viewport.width });
  2125.                                                 }
  2126.  
  2127.                                         // height
  2128.  
  2129.                                                 content.hide();
  2130.                                                 cH = info.outerHeight();
  2131.                                                 content.show();
  2132.  
  2133.                                                 bounds.height -= cH;
  2134.                                                 contentDimensions = Fit.within(bounds, dimensions);
  2135.                                                 fitted.height = contentDimensions.height;
  2136.                                         }
  2137.  
  2138.                                 // restore visibility
  2139.  
  2140.                                         if (!pageVisible) page.hide();
  2141.                                         if (!windowVisible) Window.element.hide();
  2142.                                 }
  2143.  
  2144.                         // page needs a fixed width to remain properly static during animation
  2145.  
  2146.                                 var pageDimensions = {
  2147.                                         width: fitted.width + paddingX,
  2148.                                         height: fitted.height + paddingY + cH
  2149.                                 };
  2150.                         // fullscreen mode uses viewport dimensions for the page
  2151.  
  2152.                                 if (isFullscreen) pageDimensions = viewport;
  2153.  
  2154.                                 if (z === "width") {
  2155.                                         page.css({ width: pageDimensions.width });
  2156.                                 } else {
  2157.                                         page.css({ height: pageDimensions.height });
  2158.                                 }
  2159.  
  2160.                                 container.css({ bottom: cH });
  2161.  
  2162.                         // margins
  2163.  
  2164.                                 var mLeft = -0.5 * contentDimensions.width,
  2165.                                         mTop = -0.5 * contentDimensions.height;
  2166.  
  2167.                         // floor margins on IE6-7 because it doesn't render .5px properly
  2168.  
  2169.                                 if (Browser.IE && Browser.IE < 8) {
  2170.                                         mLeft = Math.floor(mLeft);
  2171.                                         mTop = Math.floor(mTop);
  2172.                                 }
  2173.  
  2174.                                 content.css(
  2175.                                         $.extend({}, contentDimensions, {
  2176.                                                 "margin-left": mLeft,
  2177.                                                 "margin-top": mTop
  2178.                                         })
  2179.                                 );
  2180.  
  2181.                                 if (this.playerIframe) {
  2182.                                         this.playerIframe.attr(contentDimensions);
  2183.                                 }
  2184.  
  2185.                                 this.contentDimensions = contentDimensions;
  2186.  
  2187.                         // store for later use within animation
  2188.  
  2189.                                 this.width = pageDimensions.width;
  2190.                                 this.height = pageDimensions.height;
  2191.  
  2192.                                 this.z = this[z];
  2193.                         }
  2194.                 });
  2195.  
  2196.                 return Page;
  2197.         })();
  2198.  
  2199.         var Window = {
  2200.                 initialize: function() {
  2201.                         this.queues = [];
  2202.                         this.queues.hide = $({});
  2203.  
  2204.                         this.pages = [];
  2205.  
  2206.                         this.timers = new Timers();
  2207.  
  2208.                         this.build();
  2209.                         this.setSkin(Options.defaults.skin);
  2210.                 },
  2211.  
  2212.                 build: function() {
  2213.                 // spinner
  2214.  
  2215.                         if (Spinner.supported) {
  2216.                                 $(document.body).append(
  2217.                                         (this.spinnerMove = $("<div>")
  2218.                                                 .addClass("strp-spinner-move")
  2219.                                                 .hide()
  2220.                                                 .append((this.spinner = $("<div>").addClass("strp-spinner"))))
  2221.                                 );
  2222.                                 this._spinner = new Spinner(this.spinner);
  2223.  
  2224.                                 this._spinnerMoveSkinless = this.spinnerMove[0].className;
  2225.                         }
  2226.  
  2227.                 // window
  2228.  
  2229.                         $(document.body).append(
  2230.                                 (this.element = $("<div>")
  2231.                                         .addClass("strp-window strp-measured")
  2232.  
  2233.                                         .append((this._pages = $("<div>").addClass("strp-pages")))
  2234.  
  2235.                                         .append(
  2236.                                                 (this._previous = $("<div>")
  2237.                                                         .addClass("strp-nav strp-nav-previous")
  2238.                                                         .append(
  2239.                                                                 $("<div>")
  2240.                                                                         .addClass("strp-nav-button")
  2241.                                                                         .append($("<div>").addClass("strp-nav-button-background"))
  2242.                                                                         .append($("<div>").addClass("strp-nav-button-icon"))
  2243.                                                         )
  2244.                                                         .hide())
  2245.                                         )
  2246.  
  2247.                                         .append(
  2248.                                                 (this._next = $("<div>")
  2249.                                                         .addClass("strp-nav strp-nav-next")
  2250.                                                         .append(
  2251.                                                                 $("<div>")
  2252.                                                                         .addClass("strp-nav-button")
  2253.                                                                         .append($("<div>").addClass("strp-nav-button-background"))
  2254.                                                                         .append($("<div>").addClass("strp-nav-button-icon"))
  2255.                                                         )
  2256.                                                         .hide())
  2257.                                         )
  2258.  
  2259.                                 // close
  2260.  
  2261.                                         .append(
  2262.                                                 (this._close = $("<div>")
  2263.                                                         .addClass("strp-close")
  2264.                                                         .append($("<div>").addClass("strp-close-background"))
  2265.                                                         .append($("<div>").addClass("strp-close-icon")))
  2266.                                         ))
  2267.                         );
  2268.  
  2269.                         Pages.initialize(this._pages);
  2270.  
  2271.                 // support classes
  2272.  
  2273.                         if (Support.mobileTouch) this.element.addClass("strp-mobile-touch");
  2274.                         if (!Support.svg) this.element.addClass("strp-no-svg");
  2275.  
  2276.                 // events
  2277.  
  2278.                         this._close.on(
  2279.                                 "click",
  2280.                                 $.proxy(function(event) {
  2281.                                         event.preventDefault();
  2282.                                         this.hide();
  2283.                                 }, this)
  2284.                         );
  2285.  
  2286.                         this._previous.on(
  2287.                                 "click",
  2288.                                 $.proxy(function(event) {
  2289.                                         this.previous();
  2290.                                         this._onMouseMove(event);// update cursor
  2291.  
  2292.                                 }, this)
  2293.                         );
  2294.  
  2295.                         this._next.on(
  2296.                                 "click",
  2297.                                 $.proxy(function(event) {
  2298.                                         this.next();
  2299.                                         this._onMouseMove(event);// update cursor
  2300.  
  2301.                                 }, this)
  2302.                         );
  2303.  
  2304.                         this.hideUI(null, 0);// start with hidden <>
  2305.  
  2306.                 },
  2307.  
  2308.                 setSkin: function(skin) {
  2309.                         if (this._skin) {
  2310.                                 this.element.removeClass("strp-window-skin-" + this._skin);
  2311.                         }
  2312.                         this.element.addClass("strp-window-skin-" + skin);
  2313.  
  2314.                         this._skin = skin;
  2315.                 },
  2316.  
  2317.                 setSpinnerSkin: function(skin) {
  2318.                         if (!this.spinnerMove) return;
  2319.  
  2320.                         if (this._spinnerSkin) {
  2321.                                 this.spinnerMove.removeClass(
  2322.                                         "strp-spinner-move-skin-" + this._spinnerSkin
  2323.                                 );
  2324.                         }
  2325.  
  2326.                         this.spinnerMove.addClass("strp-spinner-move-skin-" + skin);
  2327.                 // refresh in case of styling updates
  2328.  
  2329.                         this._spinner.refresh();
  2330.  
  2331.                         this._spinnerSkin = skin;
  2332.                 },
  2333.  
  2334.         // Resize
  2335.  
  2336.                 startObservingResize: function() {
  2337.                         if (this._isObservingResize) return;
  2338.  
  2339.                         this._onWindowResizeHandler = $.proxy(this._onWindowResize, this);
  2340.                         $(window).on("resize orientationchange", this._onWindowResizeHandler);
  2341.  
  2342.                         this._isObservingResize = true;
  2343.                 },
  2344.  
  2345.                 stopObservingResize: function() {
  2346.                         if (this._onWindowResizeHandler) {
  2347.                                 $(window).off("resize orientationchange", this._onWindowResizeHandler);
  2348.                                 this._onWindowResizeHandler = null;
  2349.                         }
  2350.  
  2351.                         this._isObservingResize = false;
  2352.                 },
  2353.  
  2354.                 _onWindowResize: function() {
  2355.                         var page;
  2356.                         if (!(page = Pages.page)) return;
  2357.  
  2358.                         if (page.animated || page.animatingWindow) {
  2359.                         // we're animating, don't stop the animation,
  2360.  
  2361.                         // instead update dimensions and restart/continue showing
  2362.  
  2363.                                 page.fitToWindow();
  2364.                                 page.show();
  2365.                         } else {
  2366.                         // we're not in an animation, resize instantly
  2367.  
  2368.                                 page.fitToWindow();
  2369.                                 this.resize(page.z, null, 0);
  2370.                                 this.adjustPrevNext(null, true);
  2371.                         }
  2372.                 },
  2373.  
  2374.                 resize: function(wh, callback, alternateDuration) {
  2375.                         var orientation = this.getOrientation(),
  2376.                                 Z = orientation === "vertical" ? "Height" : "Width",
  2377.                                 z = Z.toLowerCase();
  2378.  
  2379.                         if (wh > 0) {
  2380.                                 this.visible = true;
  2381.                                 this.startObservingResize();
  2382.                         }
  2383.  
  2384.                         var fromZ = Window.element["outer" + Z](),
  2385.                                 duration;
  2386.  
  2387.                 // if we're opening use the show duration
  2388.  
  2389.                         if (fromZ === 0) {
  2390.                                 duration = this.view.options.effects.window.show;
  2391.  
  2392.                         // add opening class
  2393.  
  2394.                                 this.element.addClass("strp-opening");
  2395.                                 this.opening = true;
  2396.                         } else if ($.type(alternateDuration) === "number") {
  2397.                         // alternate when set
  2398.  
  2399.                                 duration = alternateDuration;
  2400.                         } else {
  2401.                         // otherwise decide on a duration for the transition
  2402.  
  2403.                         // based on distance
  2404.  
  2405.                                 var transition = this.view.options.effects.transition,
  2406.                                         min = transition.min,
  2407.                                         max = transition.max,
  2408.                                         tdiff = max - min,
  2409.                                         viewport = Bounds.viewport(),
  2410.                                         distance = Math.abs(fromZ - wh),
  2411.                                         percentage = Math.min(1, distance / viewport[z]);
  2412.  
  2413.                                 duration = Math.round(min + percentage * tdiff);
  2414.                         }
  2415.  
  2416.                         if (wh === 0) {
  2417.                                 this.closing = true;
  2418.                         // we only add the closing class if we're not currently animating the window
  2419.  
  2420.                                 if (!this.element.is(":animated")) {
  2421.                                         this.element.addClass("strp-closing");
  2422.                                 }
  2423.                         }
  2424.  
  2425.                 // the animations
  2426.  
  2427.                         var css = { overflow: "visible" };
  2428.                         css[z] = wh;
  2429.  
  2430.                         var fx = 1;
  2431.  
  2432.                 // _getEventSide checks this.element.outerWidth() on mousemove only when
  2433.  
  2434.                 // this._outerWidth isn't set, we need that during animation,
  2435.  
  2436.                 // afterResize will set it back along with the cached offsetLeft
  2437.  
  2438.                         this._outerWidth = null;
  2439.                         this._offsetLeft = null;
  2440.  
  2441.                         var onResize = this.view.options.onResize,
  2442.                                 hasOnResize = $.type(onResize) === "function";
  2443.  
  2444.                         this.element.stop(true).animate(
  2445.                                 css,
  2446.                                 $.extend(
  2447.                                         {
  2448.                                                 duration: duration,
  2449.                                                 complete: $.proxy(function() {
  2450.                                                         if (--fx < 1) this._afterResize(callback);
  2451.                                                 }, this)
  2452.                                         },
  2453.                                         !hasOnResize
  2454.                                                 ? {}
  2455.                                                 : {
  2456.                                                 // we only add step if there's an onResize callback
  2457.  
  2458.                                                         step: $.proxy(function(now, fx) {
  2459.                                                                 if (fx.prop === z) {
  2460.                                                                         onResize.call(Strip, fx.prop, now, this.side);
  2461.                                                                 }
  2462.                                                         }, this)
  2463.                                                 }
  2464.                                 )
  2465.                         );
  2466.  
  2467.                         if (this.spinnerMove) {
  2468.                                 fx++;// sync this effect
  2469.  
  2470.                                 this.spinnerMove.stop(true).animate(
  2471.                                         css,
  2472.                                         duration,
  2473.                                         $.proxy(function() {
  2474.                                                 if (--fx < 1) this._afterResize(callback);
  2475.                                         }, this)
  2476.                                 );
  2477.                         }
  2478.  
  2479.                 // return the duration for later use in synced animations
  2480.  
  2481.                         return duration;
  2482.                 },
  2483.  
  2484.                 _afterResize: function(callback) {
  2485.                         this.opening = false;
  2486.                         this.closing = false;
  2487.                         this.element.removeClass("strp-opening strp-closing");
  2488.  
  2489.                 // cache outerWidth and offsetLeft for _getEventSide on mousemove
  2490.  
  2491.                         this._outerWidth = this.element.outerWidth();
  2492.                         this._offsetLeft = this.element.offset().left;
  2493.  
  2494.                         if (callback) callback();
  2495.                 },
  2496.  
  2497.                 adjustPrevNext: function(callback, alternateDuration) {
  2498.                         if (!this.view || !Pages.page) return;
  2499.                         var page = Pages.page;
  2500.  
  2501.                 // offset <>
  2502.  
  2503.                         var windowVisible = this.element.is(":visible");
  2504.                         if (!windowVisible) this.element.show();
  2505.  
  2506.                         var pRestoreStyle = this._previous.attr("style");
  2507.                 //this._previous.attr({ style: '' });
  2508.  
  2509.                         this._previous.removeAttr("style");
  2510.                         var pnMarginTop = parseInt(this._previous.css("margin-top"));// the original margin top
  2511.  
  2512.                         this._previous.attr({ style: pRestoreStyle });
  2513.  
  2514.                         if (!windowVisible) this.element.hide();
  2515.  
  2516.                         var iH = page.info ? page.info.outerHeight() : 0;
  2517.  
  2518.                         var buttons = this._previous.add(this._next),
  2519.                                 css = { "margin-top": pnMarginTop - iH * 0.5 };
  2520.  
  2521.                         var duration = this.view.options.effects.transition.min;
  2522.                         if ($.type(alternateDuration) === "number") duration = alternateDuration;
  2523.  
  2524.                 // adjust <> instantly when opening
  2525.  
  2526.                         if (this.opening) duration = 0;
  2527.  
  2528.                         buttons.stop(true).animate(css, duration, callback);
  2529.  
  2530.                         this._previous[this.mayPrevious() ? "show" : "hide"]();
  2531.                         this._next[this.mayNext() ? "show" : "hide"]();
  2532.                 },
  2533.  
  2534.                 resetPrevNext: function() {
  2535.                         var buttons = this._previous.add(this._next);
  2536.                         buttons.stop(true).removeAttr("style");
  2537.                 },
  2538.  
  2539.         // Load
  2540.  
  2541.                 load: function(views, position) {
  2542.                         this.views = views;
  2543.  
  2544.                         Pages.add(views);
  2545.  
  2546.                         if (position) {
  2547.                                 this.setPosition(position);
  2548.                         }
  2549.                 },
  2550.  
  2551.         // adjust the size based on the current view
  2552.  
  2553.         // this might require closing the window first
  2554.  
  2555.                 setSide: function(side, callback) {
  2556.                         if (this.side === side) {
  2557.                                 if (callback) callback();
  2558.                                 return;
  2559.                         }
  2560.  
  2561.                 // side has change, first close the window if it isn't already closed
  2562.  
  2563.                         if (this.visible) {
  2564.                         // NOTE: side should be set here since the window was visible
  2565.  
  2566.                         // so using resize should be safe
  2567.  
  2568.  
  2569.                         // hide the UI
  2570.  
  2571.                                 var duration = this.view ? this.view.options.effects.window.hide : 0;
  2572.                                 this.hideUI(null, duration);
  2573.  
  2574.                         // avoid tracking mouse movement while the window is closing
  2575.  
  2576.                                 this.unbindUI();
  2577.  
  2578.                         // hide
  2579.  
  2580.                                 this.resize(
  2581.                                         0,
  2582.                                         $.proxy(function() {
  2583.                                         // some of the things we'd normally do in hide
  2584.  
  2585.                                                 this._safeResetsAfterSwitchSide();
  2586.  
  2587.                                         // we instantly hide the other views here
  2588.  
  2589.                                                 Pages.hideVisibleInactive(0);
  2590.  
  2591.                                                 this._setSide(side, callback);
  2592.                                         }, this)
  2593.                                 );
  2594.  
  2595.                         // show the UI on the next resize
  2596.  
  2597.                                 this._showUIOnResize = true;
  2598.                         } else {
  2599.                                 this._setSide(side, callback);
  2600.                         }
  2601.                 },
  2602.  
  2603.                 _setSide: function(side, callback) {
  2604.                         this.side = side;
  2605.  
  2606.                         var orientation = this.getOrientation();
  2607.  
  2608.                         var elements = this.element;
  2609.                         if (this.spinnerMove) elements = elements.add(this.spinnerMove);
  2610.  
  2611.                         elements
  2612.                                 .removeClass("strp-horizontal strp-vertical")
  2613.                                 .addClass("strp-" + orientation);
  2614.  
  2615.                         var ss = "strp-side-";
  2616.                         elements
  2617.                                 .removeClass(ss + "top " + ss + "right " + ss + "bottom " + ss + "left")
  2618.                                 .addClass(ss + side);
  2619.  
  2620.                         if (callback) callback();
  2621.                 },
  2622.  
  2623.                 getOrientation: function(side) {
  2624.                         return this.side === "left" || this.side === "right"
  2625.                                 ? "horizontal"
  2626.                                 : "vertical";
  2627.                 },
  2628.  
  2629.         // loading indicator
  2630.  
  2631.                 startLoading: function() {
  2632.                         if (!this._spinner) return;
  2633.  
  2634.                         this.spinnerMove.show();
  2635.                         this._spinner.show();
  2636.                 },
  2637.  
  2638.                 stopLoading: function() {
  2639.                         if (!this._spinner) return;
  2640.  
  2641.                 // we only stop loading if there are no loading pages anymore
  2642.  
  2643.                         var loadingCount = Pages.getLoadingCount();
  2644.  
  2645.                         if (loadingCount < 1) {
  2646.                                 this._spinner.hide(
  2647.                                         $.proxy(function() {
  2648.                                                 this.spinnerMove.hide();
  2649.                                         }, this)
  2650.                                 );
  2651.                         }
  2652.                 },
  2653.  
  2654.                 setPosition: function(position, callback) {
  2655.                         this._position = position;
  2656.  
  2657.                 // store the current view
  2658.  
  2659.                         this.view = this.views[position - 1];
  2660.  
  2661.                 // we need to make sure that a possible hide effect doesn't
  2662.  
  2663.                 // trigger its callbacks, as that would cancel the showing/loading
  2664.  
  2665.                 // of the page started below
  2666.  
  2667.                         this.stopHideQueue();
  2668.  
  2669.                 // store the page and show it
  2670.  
  2671.                         this.page = Pages.show(
  2672.                                 position,
  2673.                                 $.proxy(function() {
  2674.                                         var afterPosition = this.view.options.afterPosition;
  2675.                                         if ($.type(afterPosition) === "function") {
  2676.                                                 afterPosition.call(Strip, position);
  2677.                                         }
  2678.                                         if (callback) callback();
  2679.                                 }, this)
  2680.                         );
  2681.                 },
  2682.  
  2683.                 hide: function(callback) {
  2684.                         if (!this.view) return;
  2685.  
  2686.                         var hideQueue = this.queues.hide;
  2687.                         hideQueue.queue([]);// clear queue
  2688.  
  2689.  
  2690.                         hideQueue.queue(
  2691.                                 $.proxy(function(next_stop) {
  2692.                                         Pages.stop();
  2693.                                         next_stop();
  2694.                                 }, this)
  2695.                         );
  2696.  
  2697.                         hideQueue.queue(
  2698.                                 $.proxy(function(next_unbinds) {
  2699.                                 // ui
  2700.  
  2701.                                         var duration = this.view ? this.view.options.effects.window.hide : 0;
  2702.                                         this.unbindUI();
  2703.                                         this.hideUI(null, duration);
  2704.  
  2705.                                 // close on click outside
  2706.  
  2707.                                         this.unbindHideOnClickOutside();
  2708.  
  2709.                                 // keyboard
  2710.  
  2711.                                         Keyboard.disable();
  2712.  
  2713.                                         next_unbinds();
  2714.                                 }, this)
  2715.                         );
  2716.  
  2717.                         hideQueue.queue(
  2718.                                 $.proxy(function(next_zero) {
  2719.                                 // active classes should removed right as the closing effect starts
  2720.  
  2721.                                 // because clicking an element as it closes will re-open it,
  2722.  
  2723.                                 // that needs to be reflected in the class
  2724.  
  2725.                                         Pages.removeActiveClasses();
  2726.  
  2727.                                         this.resize(0, next_zero, this.view.options.effects.window.hide);
  2728.  
  2729.                                 // after we initiate the hide resize, the next resize should bring up the UI again
  2730.  
  2731.                                         this._showUIOnResize = true;
  2732.                                 }, this)
  2733.                         );
  2734.  
  2735.                 // callbacks after resize in a separate queue
  2736.  
  2737.                 // so we can stop the hideQueue without stopping the resize
  2738.  
  2739.                         hideQueue.queue(
  2740.                                 $.proxy(function(next_after_resize) {
  2741.                                         this._safeResetsAfterSwitchSide();
  2742.  
  2743.                                         this.stopObservingResize();
  2744.  
  2745.                                         Pages.removeAll();
  2746.  
  2747.                                         this.timers.clear();
  2748.  
  2749.                                         this._position = -1;
  2750.  
  2751.                                 // afterHide callback
  2752.  
  2753.                                         var afterHide = this.view && this.view.options.afterHide;
  2754.                                         if ($.type(afterHide) === "function") {
  2755.                                                 afterHide.call(Strip);
  2756.                                         }
  2757.  
  2758.                                         this.view = null;
  2759.  
  2760.                                         next_after_resize();
  2761.                                 }, this)
  2762.                         );
  2763.  
  2764.                         if ($.type(callback) === "function") {
  2765.                                 hideQueue.queue(
  2766.                                         $.proxy(function(next_callback) {
  2767.                                                 callback();
  2768.                                                 next_callback();
  2769.                                         }, this)
  2770.                                 );
  2771.                         }
  2772.                 },
  2773.  
  2774.         // stop all callbacks possibly queued up into a hide animation
  2775.  
  2776.         // this allows the hide animation to finish as we start showing/loading
  2777.  
  2778.         // a new page, a callback could otherwise interrupt this
  2779.  
  2780.                 stopHideQueue: function() {
  2781.                         this.queues.hide.queue([]);
  2782.                 },
  2783.  
  2784.         // these are things we can safely call when switching side as well
  2785.  
  2786.                 _safeResetsAfterSwitchSide: function() {
  2787.                 // remove styling from window, so no width: 100%; height: 0 issues
  2788.  
  2789.                         this.element.removeAttr("style");
  2790.                         if (this.spinnerMove) this.spinnerMove.removeAttr("style");
  2791.  
  2792.                 //Pages.removeExpired();
  2793.  
  2794.                         this.visible = false;
  2795.                         this.hideUI(null, 0);
  2796.                         this.timers.clear("ui");
  2797.                         this.resetPrevNext();
  2798.  
  2799.                 // clear cached mousemove
  2800.  
  2801.                         this._x = -1;
  2802.                         this._y = -1;
  2803.                 },
  2804.  
  2805.         // Previous / Next
  2806.  
  2807.                 mayPrevious: function() {
  2808.                         return (
  2809.                                 (this.view &&
  2810.                                         this.view.options.loop &&
  2811.                                         this.views &&
  2812.                                         this.views.length > 1) ||
  2813.                                 this._position !== 1
  2814.                         );
  2815.                 },
  2816.  
  2817.                 previous: function(force) {
  2818.                         var mayPrevious = this.mayPrevious();
  2819.  
  2820.                         if (force || mayPrevious) {
  2821.                                 this.setPosition(this.getSurroundingIndexes().previous);
  2822.                         }
  2823.                 },
  2824.  
  2825.                 mayNext: function() {
  2826.                         var hasViews = this.views && this.views.length > 1;
  2827.  
  2828.                         return (
  2829.                                 (this.view && this.view.options.loop && hasViews) ||
  2830.                                 (hasViews && this.getSurroundingIndexes().next !== 1)
  2831.                         );
  2832.                 },
  2833.  
  2834.                 next: function(force) {
  2835.                         var mayNext = this.mayNext();
  2836.  
  2837.                         if (force || mayNext) {
  2838.                                 this.setPosition(this.getSurroundingIndexes().next);
  2839.                         }
  2840.                 },
  2841.  
  2842.         // surrounding
  2843.  
  2844.                 getSurroundingIndexes: function() {
  2845.                         if (!this.views) return {};
  2846.  
  2847.                         var pos = this._position,
  2848.                                 length = this.views.length;
  2849.  
  2850.                         var previous = pos <= 1 ? length : pos - 1,
  2851.                                 next = pos >= length ? 1 : pos + 1;
  2852.  
  2853.                         return {
  2854.                                 previous: previous,
  2855.                                 next: next
  2856.                         };
  2857.                 },
  2858.  
  2859.         // close when clicking outside of strip or an element opening strip
  2860.  
  2861.                 bindHideOnClickOutside: function() {
  2862.                         this.unbindHideOnClickOutside();
  2863.                         $(document.documentElement).on(
  2864.                                 "click",
  2865.                                 (this._delegateHideOutsideHandler = $.proxy(
  2866.                                         this._delegateHideOutside,
  2867.                                         this
  2868.                                 ))
  2869.                         );
  2870.                 },
  2871.  
  2872.                 unbindHideOnClickOutside: function() {
  2873.                         if (this._delegateHideOutsideHandler) {
  2874.                                 $(document.documentElement).off(
  2875.                                         "click",
  2876.                                         this._delegateHideOutsideHandler
  2877.                                 );
  2878.                                 this._delegateHideOutsideHandler = null;
  2879.                         }
  2880.                 },
  2881.  
  2882.                 _delegateHideOutside: function(event) {
  2883.                         var page = Pages.page;
  2884.                         if (!this.visible || !(page && page.view.options.hideOnClickOutside))
  2885.                                 return;
  2886.  
  2887.                         var element = event.target;
  2888.  
  2889.                         if (!$(element).closest(".strip, .strp-window")[0]) {
  2890.                                 this.hide();
  2891.                         }
  2892.                 },
  2893.  
  2894.         // UI
  2895.  
  2896.                 bindUI: function() {
  2897.                         this.unbindUI();
  2898.  
  2899.                         if (!Support.mobileTouch) {
  2900.                                 this.element
  2901.                                         .on("mouseenter", (this._showUIHandler = $.proxy(this.showUI, this)))
  2902.                                         .on("mouseleave", (this._hideUIHandler = $.proxy(this.hideUI, this)));
  2903.  
  2904.                                 this.element.on(
  2905.                                         "mousemove",
  2906.                                         (this._mousemoveUIHandler = $.proxy(function(event) {
  2907.                                         // Chrome has a bug that triggers mousemove events incorrectly
  2908.  
  2909.                                         // we have to work around this by comparing cursor positions
  2910.  
  2911.                                         // so only true mousemove events pass through:
  2912.  
  2913.                                         // https://code.google.com/p/chromium/issues/detail?id=420032
  2914.  
  2915.                                                 var x = event.pageX,
  2916.                                                         y = event.pageY;
  2917.  
  2918.                                                 if (this._hoveringNav || (y === this._y && x === this._x)) {
  2919.                                                         return;
  2920.                                                 }
  2921.  
  2922.                                         // cache x/y
  2923.  
  2924.                                                 this._x = x;
  2925.                                                 this._y = y;
  2926.  
  2927.                                                 this.showUI();
  2928.                                                 this.startUITimer();
  2929.                                         }, this))
  2930.                                 );
  2931.  
  2932.                         // delegate <> mousemove/click states
  2933.  
  2934.                                 this._pages
  2935.                                         .on(
  2936.                                                 "mousemove",
  2937.                                                 ".strp-container",
  2938.                                                 (this._onMouseMoveHandler = $.proxy(this._onMouseMove, this))
  2939.                                         )
  2940.                                         .on(
  2941.                                                 "mouseleave",
  2942.                                                 ".strp-container",
  2943.                                                 (this._onMouseLeaveHandler = $.proxy(this._onMouseLeave, this))
  2944.                                         )
  2945.                                         .on(
  2946.                                                 "mouseenter",
  2947.                                                 ".strp-container",
  2948.                                                 (this._onMouseEnterHandler = $.proxy(this._onMouseEnter, this))
  2949.                                         );
  2950.  
  2951.                         // delegate moving onto the <> buttons
  2952.  
  2953.                         // keeping the mouse on them should keep the buttons visible
  2954.  
  2955.                                 this.element
  2956.                                         .on(
  2957.                                                 "mouseenter",
  2958.                                                 ".strp-nav",
  2959.                                                 (this._onNavMouseEnterHandler = $.proxy(
  2960.                                                         this._onNavMouseEnter,
  2961.                                                         this
  2962.                                                 ))
  2963.                                         )
  2964.                                         .on(
  2965.                                                 "mouseleave",
  2966.                                                 ".strp-nav",
  2967.                                                 (this._onNavMouseLeaveHandler = $.proxy(
  2968.                                                         this._onNavMouseLeave,
  2969.                                                         this
  2970.                                                 ))
  2971.                                         );
  2972.  
  2973.                                 $(window).on(
  2974.                                         "scroll",
  2975.                                         (this._onScrollHandler = $.proxy(this._onScroll, this))
  2976.                                 );
  2977.                         }
  2978.  
  2979.                         this._pages.on(
  2980.                                 "click",
  2981.                                 ".strp-container",
  2982.                                 (this._onClickHandler = $.proxy(this._onClick, this))
  2983.                         );
  2984.                 },
  2985.  
  2986.         // TODO: switch to jQuery.on/off
  2987.  
  2988.                 unbindUI: function() {
  2989.                         if (this._showUIHandler) {
  2990.                                 this.element
  2991.                                         .off("mouseenter", this._showUIHandler)
  2992.                                         .off("mouseleave", this._hideUIHandler)
  2993.                                         .off("mousemove", this._mousemoveUIHandler);
  2994.  
  2995.                                 this._pages
  2996.                                         .off("mousemove", ".strp-container", this._onMouseMoveHandler)
  2997.                                         .off("mouseleave", ".strp-container", this._onMouseLeaveHandler)
  2998.                                         .off("mouseenter", ".strp-container", this._onMouseEnterHandler);
  2999.  
  3000.                                 this.element
  3001.                                         .off("mouseenter", ".strp-nav", this._onNavMouseEnterHandler)
  3002.                                         .off("mouseleave", ".strp-nav", this._onNavMouseLeaveHandler);
  3003.  
  3004.                                 $(window).off("scroll", this._onScrollHandler);
  3005.  
  3006.                                 this._showUIHandler = null;
  3007.                         }
  3008.  
  3009.                         if (this._onClickHandler) {
  3010.                                 this._pages.off("click", ".strp-container", this._onClickHandler);
  3011.                                 this._onClickHandler = null;
  3012.                         }
  3013.                 },
  3014.  
  3015.         // reset cached offsetLeft and outerWidth so they are recalculated after scrolling,
  3016.  
  3017.         // the cached values might be incorrect after scrolling left/right
  3018.  
  3019.                 _onScroll: function() {
  3020.                         this._offsetLeft = this._outerWidth = null;
  3021.                 },
  3022.  
  3023.         // events bounds by bindUI
  3024.  
  3025.                 _onMouseMove: function(event) {
  3026.                         var Side = this._getEventSide(event),
  3027.                                 side = Side.toLowerCase();
  3028.  
  3029.                         this.element[(this["may" + Side]() ? "add" : "remove") + "Class"](
  3030.                                 "strp-hovering-clickable"
  3031.                         );
  3032.                         this._previous[(side !== "next" ? "add" : "remove") + "Class"](
  3033.                                 "strp-nav-previous-hover strp-nav-hover"
  3034.                         );
  3035.                         this._next[(side === "next" ? "add" : "remove") + "Class"](
  3036.                                 "strp-nav-next-hover strp-nav-hover"
  3037.                         );
  3038.                 },
  3039.  
  3040.                 _onMouseLeave: function(event) {
  3041.                         this.element.removeClass("strp-hovering-clickable");
  3042.                         this._previous
  3043.                                 .removeClass("strp-nav-previous-hover")
  3044.                                 .add(this._next.removeClass("strp-nav-next-hover"))
  3045.                                 .removeClass("strp-nav-hover");
  3046.                 },
  3047.  
  3048.                 _onClick: function(event) {
  3049.                         var Side = this._getEventSide(event),
  3050.                                 side = Side.toLowerCase();
  3051.  
  3052.                         this[side]();
  3053.  
  3054.                 // adjust cursor, doesn't work with effects
  3055.  
  3056.                 // but _onMouseEnter is used to fix that
  3057.  
  3058.                         this._onMouseMove(event);
  3059.                 },
  3060.  
  3061.                 _onMouseEnter: function(event) {
  3062.                 // this solves clicking an area and not having an updating cursor
  3063.  
  3064.                 // when not moving cursor after click. When an overlapping page comes into view
  3065.  
  3066.                 // it'll trigger a mouseenter after the mouseout on the disappearing page
  3067.  
  3068.                 // that would normally remove the clickable class
  3069.  
  3070.                         this._onMouseMove(event);
  3071.                 },
  3072.  
  3073.                 _getEventSide: function(event) {
  3074.                         var offsetLeft = this._offsetLeft || this.element.offset().left,
  3075.                                 left = event.pageX - offsetLeft,
  3076.                                 width = this._outerWidth || this.element.outerWidth();
  3077.  
  3078.                         return left < 0.5 * width ? "Previous" : "Next";
  3079.                 },
  3080.  
  3081.                 _onNavMouseEnter: function(event) {
  3082.                         this._hoveringNav = true;
  3083.                         this.clearUITimer();
  3084.                 },
  3085.  
  3086.                 _onNavMouseLeave: function(event) {
  3087.                         this._hoveringNav = false;
  3088.                         this.startUITimer();
  3089.                 },
  3090.  
  3091.         // Actual UI actions
  3092.  
  3093.                 showUI: function(callback, alternateDuration) {
  3094.                 // clear the timer everytime so we can keep clicking elements and fading
  3095.  
  3096.                 // in the ui while not having the timer interupt that with a hide
  3097.  
  3098.                         this.clearUITimer();
  3099.  
  3100.                 // we're only fading the inner button icons since the margin on their wrapper divs might change
  3101.  
  3102.                         var elements = this.element.find(".strp-nav-button");
  3103.  
  3104.                         var duration = this.view ? this.view.options.effects.ui.show : 0;
  3105.                         if ($.type(alternateDuration) === "number") duration = alternateDuration;
  3106.  
  3107.                         elements.stop(true).fadeTo(
  3108.                                 duration,
  3109.                                 1,
  3110.                                 "stripEaseInSine",
  3111.                                 $.proxy(function() {
  3112.                                         this.startUITimer();
  3113.                                         if ($.type(callback) === "function") callback();
  3114.                                 }, this)
  3115.                         );
  3116.                 },
  3117.  
  3118.                 hideUI: function(callback, alternateDuration) {
  3119.                         var elements = this.element.find(".strp-nav-button");
  3120.  
  3121.                         var duration = this.view ? this.view.options.effects.ui.hide : 0;
  3122.                         if ($.type(alternateDuration) === "number") duration = alternateDuration;
  3123.  
  3124.                         elements.stop(true).fadeOut(duration, "stripEaseOutSine", function() {
  3125.                                 if ($.type(callback) === "function") callback();
  3126.                         });
  3127.                 },
  3128.  
  3129.         // UI Timer
  3130.  
  3131.         // not used on mobile-touch based devices
  3132.  
  3133.                 clearUITimer: function() {
  3134.                         if (Support.mobileTouch) return;
  3135.  
  3136.                         this.timers.clear("ui");
  3137.                 },
  3138.  
  3139.                 startUITimer: function() {
  3140.                         if (Support.mobileTouch) return;
  3141.  
  3142.                         this.clearUITimer();
  3143.                         this.timers.set(
  3144.                                 "ui",
  3145.                                 $.proxy(function() {
  3146.                                         this.hideUI();
  3147.                                 }, this),
  3148.                                 this.view ? this.view.options.uiDelay : 0
  3149.                         );
  3150.                 }
  3151.         };
  3152.  
  3153. //  Keyboard
  3154.  
  3155. //  keeps track of keyboard events when enabled
  3156.  
  3157.         var Keyboard = {
  3158.                 enabled: false,
  3159.  
  3160.                 keyCode: {
  3161.                         left: 37,
  3162.                         right: 39,
  3163.                         esc: 27
  3164.                 },
  3165.  
  3166.         // enable is passed the keyboard option of a page, which can be false
  3167.  
  3168.         // or contains multiple buttons to toggle
  3169.  
  3170.                 enable: function(enabled) {
  3171.                         this.disable();
  3172.  
  3173.                         if (!enabled) return;
  3174.  
  3175.                         $(document)
  3176.                                 .on("keydown", (this._onKeyDownHandler = $.proxy(this.onKeyDown, this)))
  3177.                                 .on("keyup", (this._onKeyUpHandler = $.proxy(this.onKeyUp, this)));
  3178.  
  3179.                         this.enabled = enabled;
  3180.                 },
  3181.  
  3182.                 disable: function() {
  3183.                         this.enabled = false;
  3184.  
  3185.                         if (this._onKeyUpHandler) {
  3186.                                 $(document)
  3187.                                         .off("keyup", this._onKeyUpHandler)
  3188.                                         .off("keydown", this._onKeyDownHandler);
  3189.                                 this._onKeyUpHandler = this._onKeyDownHandler = null;
  3190.                         }
  3191.                 },
  3192.  
  3193.                 onKeyDown: function(event) {
  3194.                         if (!this.enabled || !Window.visible) return;
  3195.  
  3196.                         var key = this.getKeyByKeyCode(event.keyCode);
  3197.  
  3198.                         if (!key || (key && this.enabled && !this.enabled[key])) return;
  3199.  
  3200.                         event.preventDefault();
  3201.                         event.stopPropagation();
  3202.  
  3203.                         switch (key) {
  3204.                                 case "left":
  3205.                                         Window.previous();
  3206.                                         break;
  3207.                                 case "right":
  3208.                                         Window.next();
  3209.                                         break;
  3210.                         }
  3211.                 },
  3212.  
  3213.                 onKeyUp: function(event) {
  3214.                         if (!this.enabled || !Window.visible) return;
  3215.  
  3216.                         var key = this.getKeyByKeyCode(event.keyCode);
  3217.  
  3218.                         if (!key || (key && this.enabled && !this.enabled[key])) return;
  3219.  
  3220.                         switch (key) {
  3221.                                 case "esc":
  3222.                                         Window.hide();
  3223.                                         break;
  3224.                         }
  3225.                 },
  3226.  
  3227.                 getKeyByKeyCode: function(keyCode) {
  3228.                         for (var key in this.keyCode) {
  3229.                                 if (this.keyCode[key] === keyCode) return key;
  3230.                         }
  3231.                         return null;
  3232.                 }
  3233.         };
  3234.  
  3235. // API
  3236.  
  3237.  
  3238. // an unexposed object for internal use
  3239.  
  3240.         var _Strip = {
  3241.                 _disabled: false,
  3242.                 _fallback: true,
  3243.  
  3244.                 initialize: function() {
  3245.                         Window.initialize();
  3246.                         if (!this._disabled) this.startDelegating();
  3247.                 },
  3248.  
  3249.         // click delegation
  3250.  
  3251.                 startDelegating: function() {
  3252.                         this.stopDelegating();
  3253.                         $(document.documentElement).on(
  3254.                                 "click",
  3255.                                 ".strip[href]",
  3256.                                 (this._delegateHandler = $.proxy(this.delegate, this))
  3257.                         );
  3258.                 },
  3259.  
  3260.                 stopDelegating: function() {
  3261.                         if (this._delegateHandler) {
  3262.                                 $(document.documentElement).off(
  3263.                                         "click",
  3264.                                         ".strip[href]",
  3265.                                         this._delegateHandler
  3266.                                 );
  3267.                                 this._delegateHandler = null;
  3268.                         }
  3269.                 },
  3270.  
  3271.                 delegate: function(event) {
  3272.                         if (this._disabled) return;
  3273.  
  3274.                         event.stopPropagation();
  3275.                         event.preventDefault();
  3276.  
  3277.                         var element = event.currentTarget;
  3278.  
  3279.                         _Strip.show(element);
  3280.                 },
  3281.  
  3282.                 show: function(object) {
  3283.                         if (this._disabled) {
  3284.                                 this.showFallback.apply(_Strip, _slice.call(arguments));
  3285.                                 return;
  3286.                         }
  3287.  
  3288.                         var options = arguments[1] || {},
  3289.                                 position = arguments[2];
  3290.  
  3291.                         if (arguments[1] && $.type(arguments[1]) === "number") {
  3292.                                 position = arguments[1];
  3293.                                 options = {};
  3294.                         }
  3295.  
  3296.                         var views = [],
  3297.                                 object_type,
  3298.                                 isElement = object && object.nodeType === 1;
  3299.  
  3300.                         switch ((object_type = $.type(object))) {
  3301.                                 case "string":
  3302.                                 case "object":
  3303.                                         var view = new View(object, options),
  3304.                                                 _dgo = "data-strip-group-options";
  3305.  
  3306.                                         if (view.group) {
  3307.                                         // extend the entire group
  3308.  
  3309.  
  3310.                                         // if we have an element, look for other elements
  3311.  
  3312.                                                 if (isElement) {
  3313.                                                         var elements = $(
  3314.                                                                 '.strip[data-strip-group="' +
  3315.                                                                 $(object).attr("data-strip-group") +
  3316.                                                                 '"]'
  3317.                                                         );
  3318.  
  3319.                                                 // find possible group options
  3320.  
  3321.                                                         var groupOptions = {};
  3322.  
  3323.                                                         elements.filter("[" + _dgo + "]").each(function(i, element) {
  3324.                                                                 $.extend(
  3325.                                                                         groupOptions,
  3326.                                                                         eval("({" + ($(element).attr(_dgo) || ") + ""})")
  3327.                                                                 );
  3328.                                                         });
  3329.  
  3330.                                                         elements.each(function(i, element) {
  3331.                                                         // adjust the position if we find the given object position
  3332.  
  3333.                                                                 if (!position && element === object) position = i + 1;
  3334.                                                                 views.push(
  3335.                                                                         new View(element, $.extend({}, groupOptions, options))
  3336.                                                                 );
  3337.                                                         });
  3338.                                                 }
  3339.                                         } else {
  3340.                                                 var groupOptions = {};
  3341.                                                 if (isElement && $(object).is("[" + _dgo + "]")) {
  3342.                                                         $.extend(
  3343.                                                                 groupOptions,
  3344.                                                                 eval("({" + ($(object).attr(_dgo) || ") + ""})")
  3345.                                                         );
  3346.                                                 // reset the view with group options applied
  3347.  
  3348.                                                         view = new View(object, $.extend({}, groupOptions, options));
  3349.                                                 }
  3350.  
  3351.                                                 views.push(view);
  3352.                                         }
  3353.                                         break;
  3354.  
  3355.                                 case "array":
  3356.                                         $.each(object, function(i, item) {
  3357.                                                 var view = new View(item, options);
  3358.                                                 views.push(view);
  3359.                                         });
  3360.                                         break;
  3361.                         }
  3362.  
  3363.                 // if we haven't found a position by now, load the first view
  3364.  
  3365.                         if (!position || position < 1) {
  3366.                                 position = 1;
  3367.                         }
  3368.                         if (position > views.length) position = views.length;
  3369.  
  3370.                 // Allow API events to pass through by disabling hideOnClickOutside.
  3371.  
  3372.                 // It is re-enabled when bringing a page into view using a slight delay
  3373.  
  3374.                 // allowing a possible click event that triggers this show() function to
  3375.  
  3376.                 // fully bubble up. This is needed when Strip is visible and Strip.show()
  3377.  
  3378.                 // is called, the click would otherwise bubble down and instantly hide,
  3379.  
  3380.                 // cancelling the show()
  3381.  
  3382.                         Window.unbindHideOnClickOutside();
  3383.  
  3384.                 // if we've clicked an element, search for it in the currently open pagegroup
  3385.  
  3386.                         var positionInAPG;
  3387.                         if (
  3388.                                 isElement &&
  3389.                                 (positionInAPG = Pages.getPositionInActivePageGroup(object))
  3390.                         ) {
  3391.                         // if we've clicked the exact same element it'll never re-enable
  3392.  
  3393.                         // hideOnClickOutside delegation because Pages.show() won't let it
  3394.  
  3395.                         // through, we re-enable it here in that case
  3396.  
  3397.                                 if (positionInAPG === Window._position) {
  3398.                                         Window.bindHideOnClickOutside();
  3399.                                 }
  3400.  
  3401.                                 Window.setPosition(positionInAPG);
  3402.                         } else {
  3403.                         // otherwise start loading and open
  3404.  
  3405.                                 Window.load(views, position);
  3406.                         }
  3407.                 },
  3408.  
  3409.                 showFallback: (function() {
  3410.                         function getUrl(object) {
  3411.                                 var url,
  3412.                                         type = $.type(object);
  3413.  
  3414.                                 if (type === "string") {
  3415.                                         url = object;
  3416.                                 } else if (type === "array" && object[0]) {
  3417.                                         url = getUrl(object[0]);
  3418.                                 } else if (_.isElement(object) && $(object).attr("href")) {
  3419.                                         url = $(object).attr("href");
  3420.                                 } else if (object.url) {
  3421.                                         url = object.url;
  3422.                                 } else {
  3423.                                         url = false;
  3424.                                 }
  3425.  
  3426.                                 return url;
  3427.                         }
  3428.  
  3429.                         return function(object) {
  3430.                                 if (!_Strip._fallback) return;
  3431.                                 var url = getUrl(object);
  3432.                                 if (url) window.location.href = url;
  3433.                         };
  3434.                 })()
  3435.         };
  3436.  
  3437.         $.extend(Strip, {
  3438.                 show: function(object) {
  3439.                         _Strip.show.apply(_Strip, _slice.call(arguments));
  3440.                         return this;
  3441.                 },
  3442.  
  3443.                 hide: function() {
  3444.                         Window.hide();
  3445.                         return this;
  3446.                 },
  3447.  
  3448.                 disable: function() {
  3449.                         _Strip.stopDelegating();
  3450.                         _Strip._disabled = true;
  3451.                         return this;
  3452.                 },
  3453.  
  3454.                 enable: function() {
  3455.                         _Strip._disabled = false;
  3456.                         _Strip.startDelegating();
  3457.                         return this;
  3458.                 },
  3459.  
  3460.                 fallback: function(fallback) {
  3461.                         _Strip._fallback = fallback;
  3462.                         return this;
  3463.                 },
  3464.  
  3465.                 setDefaultSkin: function(skin) {
  3466.                         Options.defaults.skin = skin;
  3467.                         return this;
  3468.                 }
  3469.         });
  3470.  
  3471. // fallback for old browsers without full position:fixed support
  3472.  
  3473.         if (
  3474.         // IE6
  3475.  
  3476.                 (Browser.IE && Browser.IE < 7) ||
  3477.         // old Android
  3478.  
  3479.         // added a version check because Firefox on Android doesn't have a
  3480.  
  3481.         // version number above 4.2 anymore
  3482.  
  3483.                 ($.type(Browser.Android) === "number" && Browser.Android < 3) ||
  3484.         // old WebKit
  3485.  
  3486.                 (Browser.MobileSafari &&
  3487.                         ($.type(Browser.WebKit) === "number" && Browser.WebKit < 533.18))
  3488.         ) {
  3489.         // we'll reset the show function
  3490.  
  3491.                 _Strip.show = _Strip.showFallback;
  3492.  
  3493.         // disable some functions we don't want to run
  3494.  
  3495.                 $.each("startDelegating stopDelegating initialize".split(" "), function(
  3496.                         i,
  3497.                         fn
  3498.                 ) {
  3499.                         _Strip[fn] = function() {};
  3500.                 });
  3501.  
  3502.                 Strip.hide = function() {
  3503.                         return this;
  3504.                 };
  3505.         }
  3506.  
  3507. // start
  3508.  
  3509.         $(document).ready(function(event) {
  3510.                 _Strip.initialize();
  3511.         });
  3512.  
  3513.         return Strip;
  3514. })

Raw Paste


Login or Register to edit or fork this paste. It's free.