Ubuntu

­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ /*! * Isotope PACKAGED v3.0.2 * * Licensed GPLv3 for open source use * or Isotope Commercial License for commercial use * * http://isotope.metafizzy.co * Copyright 2016 Metafizzy */ /** * Bridget makes jQuery widgets * v2.0.1 * MIT license */ /* jshint browser: true, strict: true, undef: true, unused: true */ (function (window, factory) { // universal module definition /*jshint strict: false */ /* globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('jquery-bridget/jquery-bridget', ['jquery'], function (jQuery) { return factory(window, jQuery); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( window, require('jquery') ); } else { // browser global window.jQueryBridget = factory( window, window.jQuery ); } }(window, function factory(window, jQuery) { 'use strict'; // ----- utils ----- // var arraySlice = Array.prototype.slice; // helper function for logging errors // $.error breaks jQuery chaining var console = window.console; var logError = typeof console == 'undefined' ? function () { } : function (message) { console.error(message); }; // ----- jQueryBridget ----- // function jQueryBridget(namespace, PluginClass, $) { $ = $ || jQuery || window.jQuery; if (!$) { return; } // add option method -> $().plugin('option', {...}) if (!PluginClass.prototype.option) { // option setter PluginClass.prototype.option = function (opts) { // bail out if not an object if (!$.isPlainObject(opts)) { return; } this.options = $.extend(true, this.options, opts); }; } // make jQuery plugin $.fn[namespace] = function (arg0 /*, arg1 */) { if (typeof arg0 == 'string') { // method call $().plugin( 'methodName', { options } ) // shift arguments by 1 var args = arraySlice.call(arguments, 1); return methodCall(this, arg0, args); } // just $().plugin({ options }) plainCall(this, arg0); return this; }; // $().plugin('methodName') function methodCall($elems, methodName, args) { var returnValue; var pluginMethodStr = '$().' + namespace + '("' + methodName + '")'; $elems.each(function (i, elem) { // get instance var instance = $.data(elem, namespace); if (!instance) { logError(namespace + ' not initialized. Cannot call methods, i.e. ' + pluginMethodStr); return; } var method = instance[methodName]; if (!method || methodName.charAt(0) == '_') { logError(pluginMethodStr + ' is not a valid method'); return; } // apply method, get return value var value = method.apply(instance, args); // set return value if value is returned, use only first value returnValue = returnValue === undefined ? value : returnValue; }); return returnValue !== undefined ? returnValue : $elems; } function plainCall($elems, options) { $elems.each(function (i, elem) { var instance = $.data(elem, namespace); if (instance) { // set options & init instance.option(options); instance._init(); } else { // initialize new instance instance = new PluginClass(elem, options); $.data(elem, namespace, instance); } }); } updateJQuery($); } // ----- updateJQuery ----- // // set $.bridget for v1 backwards compatibility function updateJQuery($) { if (!$ || ($ && $.bridget)) { return; } $.bridget = jQueryBridget; } updateJQuery(jQuery || window.jQuery); // ----- ----- // return jQueryBridget; })); /** * EvEmitter v1.0.3 * Lil' event emitter * MIT License */ /* jshint unused: true, undef: true, strict: true */ (function (global, factory) { // universal module definition /* jshint strict: false */ /* globals define, module, window */ if (typeof define == 'function' && define.amd) { // AMD - RequireJS define('ev-emitter/ev-emitter', factory); } else if (typeof module == 'object' && module.exports) { // CommonJS - Browserify, Webpack module.exports = factory(); } else { // Browser globals global.EvEmitter = factory(); } }(typeof window != 'undefined' ? window : this, function () { function EvEmitter() { } var proto = EvEmitter.prototype; proto.on = function (eventName, listener) { if (!eventName || !listener) { return; } // set events hash var events = this._events = this._events || {}; // set listeners array var listeners = events[eventName] = events[eventName] || []; // only add once if (listeners.indexOf(listener) == -1) { listeners.push(listener); } return this; }; proto.once = function (eventName, listener) { if (!eventName || !listener) { return; } // add event this.on(eventName, listener); // set once flag // set onceEvents hash var onceEvents = this._onceEvents = this._onceEvents || {}; // set onceListeners object var onceListeners = onceEvents[eventName] = onceEvents[eventName] || {}; // set flag onceListeners[listener] = true; return this; }; proto.off = function (eventName, listener) { var listeners = this._events && this._events[eventName]; if (!listeners || !listeners.length) { return; } var index = listeners.indexOf(listener); if (index != -1) { listeners.splice(index, 1); } return this; }; proto.emitEvent = function (eventName, args) { var listeners = this._events && this._events[eventName]; if (!listeners || !listeners.length) { return; } var i = 0; var listener = listeners[i]; args = args || []; // once stuff var onceListeners = this._onceEvents && this._onceEvents[eventName]; while (listener) { var isOnce = onceListeners && onceListeners[listener]; if (isOnce) { // remove listener // remove before trigger to prevent recursion this.off(eventName, listener); // unset once flag delete onceListeners[listener]; } // trigger listener listener.apply(this, args); // get next listener i += isOnce ? 0 : 1; listener = listeners[i]; } return this; }; return EvEmitter; })); /*! * getSize v2.0.2 * measure size of elements * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, module: false, console: false */ (function (window, factory) { 'use strict'; if (typeof define == 'function' && define.amd) { // AMD define('get-size/get-size', [], function () { return factory(); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // browser global window.getSize = factory(); } })(window, function factory() { 'use strict'; // -------------------------- helpers -------------------------- // // get a number from a string, not a percentage function getStyleSize(value) { var num = parseFloat(value); // not a percent like '100%', and a number var isValid = value.indexOf('%') == -1 && !isNaN(num); return isValid && num; } function noop() { } var logError = typeof console == 'undefined' ? noop : function (message) { console.error(message); }; // -------------------------- measurements -------------------------- // var measurements = [ 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth', 'borderBottomWidth' ]; var measurementsLength = measurements.length; function getZeroSize() { var size = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 }; for (var i = 0; i < measurementsLength; i++) { var measurement = measurements[i]; size[measurement] = 0; } return size; } // -------------------------- getStyle -------------------------- // /** * getStyle, get style of element, check for Firefox bug * https://bugzilla.mozilla.org/show_bug.cgi?id=548397 */ function getStyle(elem) { var style = getComputedStyle(elem); if (!style) { logError('Style returned ' + style + '. Are you running this code in a hidden iframe on Firefox? ' + 'See http://bit.ly/getsizebug1'); } return style; } // -------------------------- setup -------------------------- // var isSetup = false; var isBoxSizeOuter; /** * setup * check isBoxSizerOuter * do on first getSize() rather than on page load for Firefox bug */ function setup() { // setup once if (isSetup) { return; } isSetup = true; // -------------------------- box sizing -------------------------- // /** * WebKit measures the outer-width on style.width on border-box elems * IE & Firefox<29 measures the inner-width */ var div = document.createElement('div'); div.style.width = '200px'; div.style.padding = '1px 2px 3px 4px'; div.style.borderStyle = 'solid'; div.style.borderWidth = '1px 2px 3px 4px'; div.style.boxSizing = 'border-box'; var body = document.body || document.documentElement; body.appendChild(div); var style = getStyle(div); getSize.isBoxSizeOuter = isBoxSizeOuter = getStyleSize(style.width) == 200; body.removeChild(div); } // -------------------------- getSize -------------------------- // function getSize(elem) { setup(); // use querySeletor if elem is string if (typeof elem == 'string') { elem = document.querySelector(elem); } // do not proceed on non-objects if (!elem || typeof elem != 'object' || !elem.nodeType) { return; } var style = getStyle(elem); // if hidden, everything is 0 if (style.display == 'none') { return getZeroSize(); } var size = {}; size.width = elem.offsetWidth; size.height = elem.offsetHeight; var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box'; // get all measurements for (var i = 0; i < measurementsLength; i++) { var measurement = measurements[i]; var value = style[measurement]; var num = parseFloat(value); // any 'auto', 'medium' value will be 0 size[measurement] = !isNaN(num) ? num : 0; } var paddingWidth = size.paddingLeft + size.paddingRight; var paddingHeight = size.paddingTop + size.paddingBottom; var marginWidth = size.marginLeft + size.marginRight; var marginHeight = size.marginTop + size.marginBottom; var borderWidth = size.borderLeftWidth + size.borderRightWidth; var borderHeight = size.borderTopWidth + size.borderBottomWidth; var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; // overwrite width and height if we can get it from style var styleWidth = getStyleSize(style.width); if (styleWidth !== false) { size.width = styleWidth + // add padding and border unless it's already including it (isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth); } var styleHeight = getStyleSize(style.height); if (styleHeight !== false) { size.height = styleHeight + // add padding and border unless it's already including it (isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight); } size.innerWidth = size.width - (paddingWidth + borderWidth); size.innerHeight = size.height - (paddingHeight + borderHeight); size.outerWidth = size.width + marginWidth; size.outerHeight = size.height + marginHeight; return size; } return getSize; }); /** * matchesSelector v2.0.1 * matchesSelector( element, '.selector' ) * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ (function (window, factory) { /*global define: false, module: false */ 'use strict'; // universal module definition if (typeof define == 'function' && define.amd) { // AMD define('desandro-matches-selector/matches-selector', factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // browser global window.matchesSelector = factory(); } }(window, function factory() { 'use strict'; var matchesMethod = (function () { var ElemProto = Element.prototype; // check for the standard method name first if (ElemProto.matches) { return 'matches'; } // check un-prefixed if (ElemProto.matchesSelector) { return 'matchesSelector'; } // check vendor prefixes var prefixes = ['webkit', 'moz', 'ms', 'o']; for (var i = 0; i < prefixes.length; i++) { var prefix = prefixes[i]; var method = prefix + 'MatchesSelector'; if (ElemProto[method]) { return method; } } })(); return function matchesSelector(elem, selector) { return elem[matchesMethod](selector); }; })); /** * Fizzy UI utils v2.0.3 * MIT license */ /*jshint browser: true, undef: true, unused: true, strict: true */ (function (window, factory) { // universal module definition /*jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('fizzy-ui-utils/utils', [ 'desandro-matches-selector/matches-selector' ], function (matchesSelector) { return factory(window, matchesSelector); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( window, require('desandro-matches-selector') ); } else { // browser global window.fizzyUIUtils = factory( window, window.matchesSelector ); } }(window, function factory(window, matchesSelector) { var utils = {}; // ----- extend ----- // // extends objects utils.extend = function (a, b) { for (var prop in b) { a[prop] = b[prop]; } return a; }; // ----- modulo ----- // utils.modulo = function (num, div) { return ((num % div) + div) % div; }; // ----- makeArray ----- // // turn element or nodeList into an array utils.makeArray = function (obj) { var ary = []; if (Array.isArray(obj)) { // use object if already an array ary = obj; } else if (obj && typeof obj.length == 'number') { // convert nodeList to array for (var i = 0; i < obj.length; i++) { ary.push(obj[i]); } } else { // array of single index ary.push(obj); } return ary; }; // ----- removeFrom ----- // utils.removeFrom = function (ary, obj) { var index = ary.indexOf(obj); if (index != -1) { ary.splice(index, 1); } }; // ----- getParent ----- // utils.getParent = function (elem, selector) { while (elem != document.body) { elem = elem.parentNode; if (matchesSelector(elem, selector)) { return elem; } } }; // ----- getQueryElement ----- // // use element as selector string utils.getQueryElement = function (elem) { if (typeof elem == 'string') { return document.querySelector(elem); } return elem; }; // ----- handleEvent ----- // // enable .ontype to trigger from .addEventListener( elem, 'type' ) utils.handleEvent = function (event) { var method = 'on' + event.type; if (this[method]) { this[method](event); } }; // ----- filterFindElements ----- // utils.filterFindElements = function (elems, selector) { // make array of elems elems = utils.makeArray(elems); var ffElems = []; elems.forEach(function (elem) { // check that elem is an actual element if (!(elem instanceof HTMLElement)) { return; } // add elem if no selector if (!selector) { ffElems.push(elem); return; } // filter & find items if we have a selector // filter if (matchesSelector(elem, selector)) { ffElems.push(elem); } // find children var childElems = elem.querySelectorAll(selector); // concat childElems to filterFound array for (var i = 0; i < childElems.length; i++) { ffElems.push(childElems[i]); } }); return ffElems; }; // ----- debounceMethod ----- // utils.debounceMethod = function (_class, methodName, threshold) { // original method var method = _class.prototype[methodName]; var timeoutName = methodName + 'Timeout'; _class.prototype[methodName] = function () { var timeout = this[timeoutName]; if (timeout) { clearTimeout(timeout); } var args = arguments; var _this = this; this[timeoutName] = setTimeout(function () { method.apply(_this, args); delete _this[timeoutName]; }, threshold || 100); }; }; // ----- docReady ----- // utils.docReady = function (callback) { var readyState = document.readyState; if (readyState == 'complete' || readyState == 'interactive') { // do async to allow for other scripts to run. metafizzy/flickity#441 setTimeout(callback); } else { document.addEventListener('DOMContentLoaded', callback); } }; // ----- htmlInit ----- // // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ utils.toDashed = function (str) { return str.replace(/(.)([A-Z])/g, function (match, $1, $2) { return $1 + '-' + $2; }).toLowerCase(); }; var console = window.console; /** * allow user to initialize classes via [data-namespace] or .js-namespace class * htmlInit( Widget, 'widgetName' ) * options are parsed from data-namespace-options */ utils.htmlInit = function (WidgetClass, namespace) { utils.docReady(function () { var dashedNamespace = utils.toDashed(namespace); var dataAttr = 'data-' + dashedNamespace; var dataAttrElems = document.querySelectorAll('[' + dataAttr + ']'); var jsDashElems = document.querySelectorAll('.js-' + dashedNamespace); var elems = utils.makeArray(dataAttrElems) .concat(utils.makeArray(jsDashElems)); var dataOptionsAttr = dataAttr + '-options'; var jQuery = window.jQuery; elems.forEach(function (elem) { var attr = elem.getAttribute(dataAttr) || elem.getAttribute(dataOptionsAttr); var options; try { options = attr && JSON.parse(attr); } catch (error) { // log error, do not initialize if (console) { console.error('Error parsing ' + dataAttr + ' on ' + elem.className + ': ' + error); } return; } // initialize var instance = new WidgetClass(elem, options); // make available via $().data('namespace') if (jQuery) { jQuery.data(elem, namespace, instance); } }); }); }; // ----- ----- // return utils; })); /** * Outlayer Item */ (function (window, factory) { // universal module definition /* jshint strict: false */ /* globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD - RequireJS define('outlayer/item', [ 'ev-emitter/ev-emitter', 'get-size/get-size' ], factory ); } else if (typeof module == 'object' && module.exports) { // CommonJS - Browserify, Webpack module.exports = factory( require('ev-emitter'), require('get-size') ); } else { // browser global window.Outlayer = {}; window.Outlayer.Item = factory( window.EvEmitter, window.getSize ); } }(window, function factory(EvEmitter, getSize) { 'use strict'; // ----- helpers ----- // function isEmptyObj(obj) { for (var prop in obj) { return false; } prop = null; return true; } // -------------------------- CSS3 support -------------------------- // var docElemStyle = document.documentElement.style; var transitionProperty = typeof docElemStyle.transition == 'string' ? 'transition' : 'WebkitTransition'; var transformProperty = typeof docElemStyle.transform == 'string' ? 'transform' : 'WebkitTransform'; var transitionEndEvent = { WebkitTransition: 'webkitTransitionEnd', transition: 'transitionend' }[transitionProperty]; // cache all vendor properties that could have vendor prefix var vendorProperties = { transform: transformProperty, transition: transitionProperty, transitionDuration: transitionProperty + 'Duration', transitionProperty: transitionProperty + 'Property', transitionDelay: transitionProperty + 'Delay' }; // -------------------------- Item -------------------------- // function Item(element, layout) { if (!element) { return; } this.element = element; // parent layout class, i.e. Masonry, Isotope, or Packery this.layout = layout; this.position = { x: 0, y: 0 }; this._create(); } // inherit EvEmitter var proto = Item.prototype = Object.create(EvEmitter.prototype); proto.constructor = Item; proto._create = function () { // transition objects this._transn = { ingProperties: {}, clean: {}, onEnd: {} }; this.css({ position: 'absolute' }); }; // trigger specified handler for event type proto.handleEvent = function (event) { var method = 'on' + event.type; if (this[method]) { this[method](event); } }; proto.getSize = function () { this.size = getSize(this.element); }; /** * apply CSS styles to element * @param {Object} style */ proto.css = function (style) { var elemStyle = this.element.style; for (var prop in style) { // use vendor property if available var supportedProp = vendorProperties[prop] || prop; elemStyle[supportedProp] = style[prop]; } }; // measure position, and sets it proto.getPosition = function () { var style = getComputedStyle(this.element); var isOriginLeft = this.layout._getOption('originLeft'); var isOriginTop = this.layout._getOption('originTop'); var xValue = style[isOriginLeft ? 'left' : 'right']; var yValue = style[isOriginTop ? 'top' : 'bottom']; // convert percent to pixels var layoutSize = this.layout.size; var x = xValue.indexOf('%') != -1 ? (parseFloat(xValue) / 100) * layoutSize.width : parseInt(xValue, 10); var y = yValue.indexOf('%') != -1 ? (parseFloat(yValue) / 100) * layoutSize.height : parseInt(yValue, 10); // clean up 'auto' or other non-integer values x = isNaN(x) ? 0 : x; y = isNaN(y) ? 0 : y; // remove padding from measurement x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight; y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom; this.position.x = x; this.position.y = y; }; // set settled position, apply padding proto.layoutPosition = function () { var layoutSize = this.layout.size; var style = {}; var isOriginLeft = this.layout._getOption('originLeft'); var isOriginTop = this.layout._getOption('originTop'); // x var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight'; var xProperty = isOriginLeft ? 'left' : 'right'; var xResetProperty = isOriginLeft ? 'right' : 'left'; var x = this.position.x + layoutSize[xPadding]; // set in percentage or pixels style[xProperty] = this.getXValue(x); // reset other property style[xResetProperty] = ''; // y var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom'; var yProperty = isOriginTop ? 'top' : 'bottom'; var yResetProperty = isOriginTop ? 'bottom' : 'top'; var y = this.position.y + layoutSize[yPadding]; // set in percentage or pixels style[yProperty] = this.getYValue(y); // reset other property style[yResetProperty] = ''; this.css(style); this.emitEvent('layout', [this]); }; proto.getXValue = function (x) { var isHorizontal = this.layout._getOption('horizontal'); return this.layout.options.percentPosition && !isHorizontal ? ((x / this.layout.size.width) * 100) + '%' : x + 'px'; }; proto.getYValue = function (y) { var isHorizontal = this.layout._getOption('horizontal'); return this.layout.options.percentPosition && isHorizontal ? ((y / this.layout.size.height) * 100) + '%' : y + 'px'; }; proto._transitionTo = function (x, y) { this.getPosition(); // get current x & y from top/left var curX = this.position.x; var curY = this.position.y; var compareX = parseInt(x, 10); var compareY = parseInt(y, 10); var didNotMove = compareX === this.position.x && compareY === this.position.y; // save end position this.setPosition(x, y); // if did not move and not transitioning, just go to layout if (didNotMove && !this.isTransitioning) { this.layoutPosition(); return; } var transX = x - curX; var transY = y - curY; var transitionStyle = {}; transitionStyle.transform = this.getTranslate(transX, transY); this.transition({ to: transitionStyle, onTransitionEnd: { transform: this.layoutPosition }, isCleaning: true }); }; proto.getTranslate = function (x, y) { // flip cooridinates if origin on right or bottom var isOriginLeft = this.layout._getOption('originLeft'); var isOriginTop = this.layout._getOption('originTop'); x = isOriginLeft ? x : -x; y = isOriginTop ? y : -y; return 'translate3d(' + x + 'px, ' + y + 'px, 0)'; }; // non transition + transform support proto.goTo = function (x, y) { this.setPosition(x, y); this.layoutPosition(); }; proto.moveTo = proto._transitionTo; proto.setPosition = function (x, y) { this.position.x = parseInt(x, 10); this.position.y = parseInt(y, 10); }; // ----- transition ----- // /** * @param {Object} style - CSS * @param {Function} onTransitionEnd */ // non transition, just trigger callback proto._nonTransition = function (args) { this.css(args.to); if (args.isCleaning) { this._removeStyles(args.to); } for (var prop in args.onTransitionEnd) { args.onTransitionEnd[prop].call(this); } }; /** * proper transition * @param {Object} args - arguments * @param {Object} to - style to transition to * @param {Object} from - style to start transition from * @param {Boolean} isCleaning - removes transition styles after transition * @param {Function} onTransitionEnd - callback */ proto.transition = function (args) { // redirect to nonTransition if no transition duration if (!parseFloat(this.layout.options.transitionDuration)) { this._nonTransition(args); return; } var _transition = this._transn; // keep track of onTransitionEnd callback by css property for (var prop in args.onTransitionEnd) { _transition.onEnd[prop] = args.onTransitionEnd[prop]; } // keep track of properties that are transitioning for (prop in args.to) { _transition.ingProperties[prop] = true; // keep track of properties to clean up when transition is done if (args.isCleaning) { _transition.clean[prop] = true; } } // set from styles if (args.from) { this.css(args.from); // force redraw. http://blog.alexmaccaw.com/css-transitions var h = this.element.offsetHeight; // hack for JSHint to hush about unused var h = null; } // enable transition this.enableTransition(args.to); // set styles that are transitioning this.css(args.to); this.isTransitioning = true; }; // dash before all cap letters, including first for // WebkitTransform => -webkit-transform function toDashedAll(str) { return str.replace(/([A-Z])/g, function ($1) { return '-' + $1.toLowerCase(); }); } var transitionProps = 'opacity,' + toDashedAll(transformProperty); proto.enableTransition = function (/* style */) { // HACK changing transitionProperty during a transition // will cause transition to jump if (this.isTransitioning) { return; } // make `transition: foo, bar, baz` from style object // HACK un-comment this when enableTransition can work // while a transition is happening // var transitionValues = []; // for ( var prop in style ) { // // dash-ify camelCased properties like WebkitTransition // prop = vendorProperties[ prop ] || prop; // transitionValues.push( toDashedAll( prop ) ); // } // munge number to millisecond, to match stagger var duration = this.layout.options.transitionDuration; duration = typeof duration == 'number' ? duration + 'ms' : duration; // enable transition styles this.css({ transitionProperty: transitionProps, transitionDuration: duration, transitionDelay: this.staggerDelay || 0 }); // listen for transition end event this.element.addEventListener(transitionEndEvent, this, false); }; // ----- events ----- // proto.onwebkitTransitionEnd = function (event) { this.ontransitionend(event); }; proto.onotransitionend = function (event) { this.ontransitionend(event); }; // properties that I munge to make my life easier var dashedVendorProperties = { '-webkit-transform': 'transform' }; proto.ontransitionend = function (event) { // disregard bubbled events from children if (event.target !== this.element) { return; } var _transition = this._transn; // get property name of transitioned property, convert to prefix-free var propertyName = dashedVendorProperties[event.propertyName] || event.propertyName; // remove property that has completed transitioning delete _transition.ingProperties[propertyName]; // check if any properties are still transitioning if (isEmptyObj(_transition.ingProperties)) { // all properties have completed transitioning this.disableTransition(); } // clean style if (propertyName in _transition.clean) { // clean up style this.element.style[event.propertyName] = ''; delete _transition.clean[propertyName]; } // trigger onTransitionEnd callback if (propertyName in _transition.onEnd) { var onTransitionEnd = _transition.onEnd[propertyName]; onTransitionEnd.call(this); delete _transition.onEnd[propertyName]; } this.emitEvent('transitionEnd', [this]); }; proto.disableTransition = function () { this.removeTransitionStyles(); this.element.removeEventListener(transitionEndEvent, this, false); this.isTransitioning = false; }; /** * removes style property from element * @param {Object} style **/ proto._removeStyles = function (style) { // clean up transition styles var cleanStyle = {}; for (var prop in style) { cleanStyle[prop] = ''; } this.css(cleanStyle); }; var cleanTransitionStyle = { transitionProperty: '', transitionDuration: '', transitionDelay: '' }; proto.removeTransitionStyles = function () { // remove transition this.css(cleanTransitionStyle); }; // ----- stagger ----- // proto.stagger = function (delay) { delay = isNaN(delay) ? 0 : delay; this.staggerDelay = delay + 'ms'; }; // ----- show/hide/remove ----- // // remove element from DOM proto.removeElem = function () { this.element.parentNode.removeChild(this.element); // remove display: none this.css({display: ''}); this.emitEvent('remove', [this]); }; proto.remove = function () { // just remove element if no transition support or no transition if (!transitionProperty || !parseFloat(this.layout.options.transitionDuration)) { this.removeElem(); return; } // start transition this.once('transitionEnd', function () { this.removeElem(); }); this.hide(); }; proto.reveal = function () { delete this.isHidden; // remove display: none this.css({display: ''}); var options = this.layout.options; var onTransitionEnd = {}; var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle'); onTransitionEnd[transitionEndProperty] = this.onRevealTransitionEnd; this.transition({ from: options.hiddenStyle, to: options.visibleStyle, isCleaning: true, onTransitionEnd: onTransitionEnd }); }; proto.onRevealTransitionEnd = function () { // check if still visible // during transition, item may have been hidden if (!this.isHidden) { this.emitEvent('reveal'); } }; /** * get style property use for hide/reveal transition end * @param {String} styleProperty - hiddenStyle/visibleStyle * @returns {String} */ proto.getHideRevealTransitionEndProperty = function (styleProperty) { var optionStyle = this.layout.options[styleProperty]; // use opacity if (optionStyle.opacity) { return 'opacity'; } // get first property for (var prop in optionStyle) { return prop; } }; proto.hide = function () { // set flag this.isHidden = true; // remove display: none this.css({display: ''}); var options = this.layout.options; var onTransitionEnd = {}; var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle'); onTransitionEnd[transitionEndProperty] = this.onHideTransitionEnd; this.transition({ from: options.visibleStyle, to: options.hiddenStyle, // keep hidden stuff hidden isCleaning: true, onTransitionEnd: onTransitionEnd }); }; proto.onHideTransitionEnd = function () { // check if still hidden // during transition, item may have been un-hidden if (this.isHidden) { this.css({display: 'none'}); this.emitEvent('hide'); } }; proto.destroy = function () { this.css({ position: '', left: '', right: '', top: '', bottom: '', transition: '', transform: '' }); }; return Item; })); /*! * Outlayer v2.1.0 * the brains and guts of a layout library * MIT license */ (function (window, factory) { 'use strict'; // universal module definition /* jshint strict: false */ /* globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD - RequireJS define('outlayer/outlayer', [ 'ev-emitter/ev-emitter', 'get-size/get-size', 'fizzy-ui-utils/utils', './item' ], function (EvEmitter, getSize, utils, Item) { return factory(window, EvEmitter, getSize, utils, Item); } ); } else if (typeof module == 'object' && module.exports) { // CommonJS - Browserify, Webpack module.exports = factory( window, require('ev-emitter'), require('get-size'), require('fizzy-ui-utils'), require('./item') ); } else { // browser global window.Outlayer = factory( window, window.EvEmitter, window.getSize, window.fizzyUIUtils, window.Outlayer.Item ); } }(window, function factory(window, EvEmitter, getSize, utils, Item) { 'use strict'; // ----- vars ----- // var console = window.console; var jQuery = window.jQuery; var noop = function () { }; // -------------------------- Outlayer -------------------------- // // globally unique identifiers var GUID = 0; // internal store of all Outlayer intances var instances = {}; /** * @param {Element, String} element * @param {Object} options * @constructor */ function Outlayer(element, options) { var queryElement = utils.getQueryElement(element); if (!queryElement) { if (console) { console.error('Bad element for ' + this.constructor.namespace + ': ' + (queryElement || element)); } return; } this.element = queryElement; // add jQuery if (jQuery) { this.$element = jQuery(this.element); } // options this.options = utils.extend({}, this.constructor.defaults); this.option(options); // add id for Outlayer.getFromElement var id = ++GUID; this.element.outlayerGUID = id; // expando instances[id] = this; // associate via id // kick it off this._create(); var isInitLayout = this._getOption('initLayout'); if (isInitLayout) { this.layout(); } } // settings are for internal use only Outlayer.namespace = 'outlayer'; Outlayer.Item = Item; // default options Outlayer.defaults = { containerStyle: { position: 'relative' }, initLayout: true, originLeft: true, originTop: true, resize: true, resizeContainer: true, // item options transitionDuration: '0.4s', hiddenStyle: { opacity: 0, transform: 'scale(0.001)' }, visibleStyle: { opacity: 1, transform: 'scale(1)' } }; var proto = Outlayer.prototype; // inherit EvEmitter utils.extend(proto, EvEmitter.prototype); /** * set options * @param {Object} opts */ proto.option = function (opts) { utils.extend(this.options, opts); }; /** * get backwards compatible option value, check old name */ proto._getOption = function (option) { var oldOption = this.constructor.compatOptions[option]; return oldOption && this.options[oldOption] !== undefined ? this.options[oldOption] : this.options[option]; }; Outlayer.compatOptions = { // currentName: oldName initLayout: 'isInitLayout', horizontal: 'isHorizontal', layoutInstant: 'isLayoutInstant', originLeft: 'isOriginLeft', originTop: 'isOriginTop', resize: 'isResizeBound', resizeContainer: 'isResizingContainer' }; proto._create = function () { // get items from children this.reloadItems(); // elements that affect layout, but are not laid out this.stamps = []; this.stamp(this.options.stamp); // set container style utils.extend(this.element.style, this.options.containerStyle); // bind resize method var canBindResize = this._getOption('resize'); if (canBindResize) { this.bindResize(); } }; // goes through all children again and gets bricks in proper order proto.reloadItems = function () { // collection of item elements this.items = this._itemize(this.element.children); }; /** * turn elements into Outlayer.Items to be used in layout * @param {Array or NodeList or HTMLElement} elems * @returns {Array} items - collection of new Outlayer Items */ proto._itemize = function (elems) { var itemElems = this._filterFindItemElements(elems); var Item = this.constructor.Item; // create new Outlayer Items for collection var items = []; for (var i = 0; i < itemElems.length; i++) { var elem = itemElems[i]; var item = new Item(elem, this); items.push(item); } return items; }; /** * get item elements to be used in layout * @param {Array or NodeList or HTMLElement} elems * @returns {Array} items - item elements */ proto._filterFindItemElements = function (elems) { return utils.filterFindElements(elems, this.options.itemSelector); }; /** * getter method for getting item elements * @returns {Array} elems - collection of item elements */ proto.getItemElements = function () { return this.items.map(function (item) { return item.element; }); }; // ----- init & layout ----- // /** * lays out all items */ proto.layout = function () { this._resetLayout(); this._manageStamps(); // don't animate first layout var layoutInstant = this._getOption('layoutInstant'); var isInstant = layoutInstant !== undefined ? layoutInstant : !this._isLayoutInited; this.layoutItems(this.items, isInstant); // flag for initalized this._isLayoutInited = true; }; // _init is alias for layout proto._init = proto.layout; /** * logic before any new layout */ proto._resetLayout = function () { this.getSize(); }; proto.getSize = function () { this.size = getSize(this.element); }; /** * get measurement from option, for columnWidth, rowHeight, gutter * if option is String -> get element from selector string, & get size of element * if option is Element -> get size of element * else use option as a number * * @param {String} measurement * @param {String} size - width or height * @private */ proto._getMeasurement = function (measurement, size) { var option = this.options[measurement]; var elem; if (!option) { // default to 0 this[measurement] = 0; } else { // use option as an element if (typeof option == 'string') { elem = this.element.querySelector(option); } else if (option instanceof HTMLElement) { elem = option; } // use size of element, if element this[measurement] = elem ? getSize(elem)[size] : option; } }; /** * layout a collection of item elements * @api public */ proto.layoutItems = function (items, isInstant) { items = this._getItemsForLayout(items); this._layoutItems(items, isInstant); this._postLayout(); }; /** * get the items to be laid out * you may want to skip over some items * @param {Array} items * @returns {Array} items */ proto._getItemsForLayout = function (items) { return items.filter(function (item) { return !item.isIgnored; }); }; /** * layout items * @param {Array} items * @param {Boolean} isInstant */ proto._layoutItems = function (items, isInstant) { this._emitCompleteOnItems('layout', items); if (!items || !items.length) { // no items, emit event with empty array return; } var queue = []; items.forEach(function (item) { // get x/y object from method var position = this._getItemLayoutPosition(item); // enqueue position.item = item; position.isInstant = isInstant || item.isLayoutInstant; queue.push(position); }, this); this._processLayoutQueue(queue); }; /** * get item layout position * @param {Outlayer.Item} item * @returns {Object} x and y position */ proto._getItemLayoutPosition = function (/* item */) { return { x: 0, y: 0 }; }; /** * iterate over array and position each item * Reason being - separating this logic prevents 'layout invalidation' * thx @paul_irish * @param {Array} queue */ proto._processLayoutQueue = function (queue) { this.updateStagger(); queue.forEach(function (obj, i) { this._positionItem(obj.item, obj.x, obj.y, obj.isInstant, i); }, this); }; // set stagger from option in milliseconds number proto.updateStagger = function () { var stagger = this.options.stagger; if (stagger === null || stagger === undefined) { this.stagger = 0; return; } this.stagger = getMilliseconds(stagger); return this.stagger; }; /** * Sets position of item in DOM * @param {Outlayer.Item} item * @param {Number} x - horizontal position * @param {Number} y - vertical position * @param {Boolean} isInstant - disables transitions */ proto._positionItem = function (item, x, y, isInstant, i) { if (isInstant) { // if not transition, just set CSS item.goTo(x, y); } else { item.stagger(i * this.stagger); item.moveTo(x, y); } }; /** * Any logic you want to do after each layout, * i.e. size the container */ proto._postLayout = function () { this.resizeContainer(); }; proto.resizeContainer = function () { var isResizingContainer = this._getOption('resizeContainer'); if (!isResizingContainer) { return; } var size = this._getContainerSize(); if (size) { this._setContainerMeasure(size.width, true); this._setContainerMeasure(size.height, false); } }; /** * Sets width or height of container if returned * @returns {Object} size * @param {Number} width * @param {Number} height */ proto._getContainerSize = noop; /** * @param {Number} measure - size of width or height * @param {Boolean} isWidth */ proto._setContainerMeasure = function (measure, isWidth) { if (measure === undefined) { return; } var elemSize = this.size; // add padding and border width if border box if (elemSize.isBorderBox) { measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight + elemSize.borderLeftWidth + elemSize.borderRightWidth : elemSize.paddingBottom + elemSize.paddingTop + elemSize.borderTopWidth + elemSize.borderBottomWidth; } measure = Math.max(measure, 0); this.element.style[isWidth ? 'width' : 'height'] = measure + 'px'; }; /** * emit eventComplete on a collection of items events * @param {String} eventName * @param {Array} items - Outlayer.Items */ proto._emitCompleteOnItems = function (eventName, items) { var _this = this; function onComplete() { _this.dispatchEvent(eventName + 'Complete', null, [items]); } var count = items.length; if (!items || !count) { onComplete(); return; } var doneCount = 0; function tick() { doneCount++; if (doneCount == count) { onComplete(); } } // bind callback items.forEach(function (item) { item.once(eventName, tick); }); }; /** * emits events via EvEmitter and jQuery events * @param {String} type - name of event * @param {Event} event - original event * @param {Array} args - extra arguments */ proto.dispatchEvent = function (type, event, args) { // add original event to arguments var emitArgs = event ? [event].concat(args) : args; this.emitEvent(type, emitArgs); if (jQuery) { // set this.$element this.$element = this.$element || jQuery(this.element); if (event) { // create jQuery event var $event = jQuery.Event(event); $event.type = type; this.$element.trigger($event, args); } else { // just trigger with type if no event available this.$element.trigger(type, args); } } }; // -------------------------- ignore & stamps -------------------------- // /** * keep item in collection, but do not lay it out * ignored items do not get skipped in layout * @param {Element} elem */ proto.ignore = function (elem) { var item = this.getItem(elem); if (item) { item.isIgnored = true; } }; /** * return item to layout collection * @param {Element} elem */ proto.unignore = function (elem) { var item = this.getItem(elem); if (item) { delete item.isIgnored; } }; /** * adds elements to stamps * @param {NodeList, Array, Element, or String} elems */ proto.stamp = function (elems) { elems = this._find(elems); if (!elems) { return; } this.stamps = this.stamps.concat(elems); // ignore elems.forEach(this.ignore, this); }; /** * removes elements to stamps * @param {NodeList, Array, or Element} elems */ proto.unstamp = function (elems) { elems = this._find(elems); if (!elems) { return; } elems.forEach(function (elem) { // filter out removed stamp elements utils.removeFrom(this.stamps, elem); this.unignore(elem); }, this); }; /** * finds child elements * @param {NodeList, Array, Element, or String} elems * @returns {Array} elems */ proto._find = function (elems) { if (!elems) { return; } // if string, use argument as selector string if (typeof elems == 'string') { elems = this.element.querySelectorAll(elems); } elems = utils.makeArray(elems); return elems; }; proto._manageStamps = function () { if (!this.stamps || !this.stamps.length) { return; } this._getBoundingRect(); this.stamps.forEach(this._manageStamp, this); }; // update boundingLeft / Top proto._getBoundingRect = function () { // get bounding rect for container element var boundingRect = this.element.getBoundingClientRect(); var size = this.size; this._boundingRect = { left: boundingRect.left + size.paddingLeft + size.borderLeftWidth, top: boundingRect.top + size.paddingTop + size.borderTopWidth, right: boundingRect.right - (size.paddingRight + size.borderRightWidth), bottom: boundingRect.bottom - (size.paddingBottom + size.borderBottomWidth) }; }; /** * @param {Element} stamp **/ proto._manageStamp = noop; /** * get x/y position of element relative to container element * @param {Element} elem * @returns {Object} offset - has left, top, right, bottom */ proto._getElementOffset = function (elem) { var boundingRect = elem.getBoundingClientRect(); var thisRect = this._boundingRect; var size = getSize(elem); var offset = { left: boundingRect.left - thisRect.left - size.marginLeft, top: boundingRect.top - thisRect.top - size.marginTop, right: thisRect.right - boundingRect.right - size.marginRight, bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom }; return offset; }; // -------------------------- resize -------------------------- // // enable event handlers for listeners // i.e. resize -> onresize proto.handleEvent = utils.handleEvent; /** * Bind layout to window resizing */ proto.bindResize = function () { window.addEventListener('resize', this); this.isResizeBound = true; }; /** * Unbind layout to window resizing */ proto.unbindResize = function () { window.removeEventListener('resize', this); this.isResizeBound = false; }; proto.onresize = function () { this.resize(); }; utils.debounceMethod(Outlayer, 'onresize', 100); proto.resize = function () { // don't trigger if size did not change // or if resize was unbound. See #9 if (!this.isResizeBound || !this.needsResizeLayout()) { return; } this.layout(); }; /** * check if layout is needed post layout * @returns Boolean */ proto.needsResizeLayout = function () { var size = getSize(this.element); // check that this.size and size are there // IE8 triggers resize on body size change, so they might not be var hasSizes = this.size && size; return hasSizes && size.innerWidth !== this.size.innerWidth; }; // -------------------------- methods -------------------------- // /** * add items to Outlayer instance * @param {Array or NodeList or Element} elems * @returns {Array} items - Outlayer.Items **/ proto.addItems = function (elems) { var items = this._itemize(elems); // add items to collection if (items.length) { this.items = this.items.concat(items); } return items; }; /** * Layout newly-appended item elements * @param {Array or NodeList or Element} elems */ proto.appended = function (elems) { var items = this.addItems(elems); if (!items.length) { return; } // layout and reveal just the new items this.layoutItems(items, true); this.reveal(items); }; /** * Layout prepended elements * @param {Array or NodeList or Element} elems */ proto.prepended = function (elems) { var items = this._itemize(elems); if (!items.length) { return; } // add items to beginning of collection var previousItems = this.items.slice(0); this.items = items.concat(previousItems); // start new layout this._resetLayout(); this._manageStamps(); // layout new stuff without transition this.layoutItems(items, true); this.reveal(items); // layout previous items this.layoutItems(previousItems); }; /** * reveal a collection of items * @param {Array of Outlayer.Items} items */ proto.reveal = function (items) { this._emitCompleteOnItems('reveal', items); if (!items || !items.length) { return; } var stagger = this.updateStagger(); items.forEach(function (item, i) { item.stagger(i * stagger); item.reveal(); }); }; /** * hide a collection of items * @param {Array of Outlayer.Items} items */ proto.hide = function (items) { this._emitCompleteOnItems('hide', items); if (!items || !items.length) { return; } var stagger = this.updateStagger(); items.forEach(function (item, i) { item.stagger(i * stagger); item.hide(); }); }; /** * reveal item elements * @param {Array}, {Element}, {NodeList} items */ proto.revealItemElements = function (elems) { var items = this.getItems(elems); this.reveal(items); }; /** * hide item elements * @param {Array}, {Element}, {NodeList} items */ proto.hideItemElements = function (elems) { var items = this.getItems(elems); this.hide(items); }; /** * get Outlayer.Item, given an Element * @param {Element} elem * @param {Function} callback * @returns {Outlayer.Item} item */ proto.getItem = function (elem) { // loop through items to get the one that matches for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; if (item.element == elem) { // return item return item; } } }; /** * get collection of Outlayer.Items, given Elements * @param {Array} elems * @returns {Array} items - Outlayer.Items */ proto.getItems = function (elems) { elems = utils.makeArray(elems); var items = []; elems.forEach(function (elem) { var item = this.getItem(elem); if (item) { items.push(item); } }, this); return items; }; /** * remove element(s) from instance and DOM * @param {Array or NodeList or Element} elems */ proto.remove = function (elems) { var removeItems = this.getItems(elems); this._emitCompleteOnItems('remove', removeItems); // bail if no items to remove if (!removeItems || !removeItems.length) { return; } removeItems.forEach(function (item) { item.remove(); // remove item from collection utils.removeFrom(this.items, item); }, this); }; // ----- destroy ----- // // remove and disable Outlayer instance proto.destroy = function () { // clean up dynamic styles var style = this.element.style; style.height = ''; style.position = ''; style.width = ''; // destroy items this.items.forEach(function (item) { item.destroy(); }); this.unbindResize(); var id = this.element.outlayerGUID; delete instances[id]; // remove reference to instance by id delete this.element.outlayerGUID; // remove data for jQuery if (jQuery) { jQuery.removeData(this.element, this.constructor.namespace); } }; // -------------------------- data -------------------------- // /** * get Outlayer instance from element * @param {Element} elem * @returns {Outlayer} */ Outlayer.data = function (elem) { elem = utils.getQueryElement(elem); var id = elem && elem.outlayerGUID; return id && instances[id]; }; // -------------------------- create Outlayer class -------------------------- // /** * create a layout class * @param {String} namespace */ Outlayer.create = function (namespace, options) { // sub-class Outlayer var Layout = subclass(Outlayer); // apply new options and compatOptions Layout.defaults = utils.extend({}, Outlayer.defaults); utils.extend(Layout.defaults, options); Layout.compatOptions = utils.extend({}, Outlayer.compatOptions); Layout.namespace = namespace; Layout.data = Outlayer.data; // sub-class Item Layout.Item = subclass(Item); // -------------------------- declarative -------------------------- // utils.htmlInit(Layout, namespace); // -------------------------- jQuery bridge -------------------------- // // make into jQuery plugin if (jQuery && jQuery.bridget) { jQuery.bridget(namespace, Layout); } return Layout; }; function subclass(Parent) { function SubClass() { Parent.apply(this, arguments); } SubClass.prototype = Object.create(Parent.prototype); SubClass.prototype.constructor = SubClass; return SubClass; } // ----- helpers ----- // // how many milliseconds are in each unit var msUnits = { ms: 1, s: 1000 }; // munge time-like parameter into millisecond number // '0.4s' -> 40 function getMilliseconds(time) { if (typeof time == 'number') { return time; } var matches = time.match(/(^\d*\.?\d*)(\w*)/); var num = matches && matches[1]; var unit = matches && matches[2]; if (!num.length) { return 0; } num = parseFloat(num); var mult = msUnits[unit] || 1; return num * mult; } // ----- fin ----- // // back in global Outlayer.Item = Item; return Outlayer; })); /** * Isotope Item **/ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('isotope/js/item', [ 'outlayer/outlayer' ], factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( require('outlayer') ); } else { // browser global window.Isotope = window.Isotope || {}; window.Isotope.Item = factory( window.Outlayer ); } }(window, function factory(Outlayer) { 'use strict'; // -------------------------- Item -------------------------- // // sub-class Outlayer Item function Item() { Outlayer.Item.apply(this, arguments); } var proto = Item.prototype = Object.create(Outlayer.Item.prototype); var _create = proto._create; proto._create = function () { // assign id, used for original-order sorting this.id = this.layout.itemGUID++; _create.call(this); this.sortData = {}; }; proto.updateSortData = function () { if (this.isIgnored) { return; } // default sorters this.sortData.id = this.id; // for backward compatibility this.sortData['original-order'] = this.id; this.sortData.random = Math.random(); // go thru getSortData obj and apply the sorters var getSortData = this.layout.options.getSortData; var sorters = this.layout._sorters; for (var key in getSortData) { var sorter = sorters[key]; this.sortData[key] = sorter(this.element, this); } }; var _destroy = proto.destroy; proto.destroy = function () { // call super _destroy.apply(this, arguments); // reset display, #741 this.css({ display: '' }); }; return Item; })); /** * Isotope LayoutMode */ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('isotope/js/layout-mode', [ 'get-size/get-size', 'outlayer/outlayer' ], factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( require('get-size'), require('outlayer') ); } else { // browser global window.Isotope = window.Isotope || {}; window.Isotope.LayoutMode = factory( window.getSize, window.Outlayer ); } }(window, function factory(getSize, Outlayer) { 'use strict'; // layout mode class function LayoutMode(isotope) { this.isotope = isotope; // link properties if (isotope) { this.options = isotope.options[this.namespace]; this.element = isotope.element; this.items = isotope.filteredItems; this.size = isotope.size; } } var proto = LayoutMode.prototype; /** * some methods should just defer to default Outlayer method * and reference the Isotope instance as `this` **/ var facadeMethods = [ '_resetLayout', '_getItemLayoutPosition', '_manageStamp', '_getContainerSize', '_getElementOffset', 'needsResizeLayout', '_getOption' ]; facadeMethods.forEach(function (methodName) { proto[methodName] = function () { return Outlayer.prototype[methodName].apply(this.isotope, arguments); }; }); // ----- ----- // // for horizontal layout modes, check vertical size proto.needsVerticalResizeLayout = function () { // don't trigger if size did not change var size = getSize(this.isotope.element); // check that this.size and size are there // IE8 triggers resize on body size change, so they might not be var hasSizes = this.isotope.size && size; return hasSizes && size.innerHeight != this.isotope.size.innerHeight; }; // ----- measurements ----- // proto._getMeasurement = function () { this.isotope._getMeasurement.apply(this, arguments); }; proto.getColumnWidth = function () { this.getSegmentSize('column', 'Width'); }; proto.getRowHeight = function () { this.getSegmentSize('row', 'Height'); }; /** * get columnWidth or rowHeight * segment: 'column' or 'row' * size 'Width' or 'Height' **/ proto.getSegmentSize = function (segment, size) { var segmentName = segment + size; var outerSize = 'outer' + size; // columnWidth / outerWidth // rowHeight / outerHeight this._getMeasurement(segmentName, outerSize); // got rowHeight or columnWidth, we can chill if (this[segmentName]) { return; } // fall back to item of first element var firstItemSize = this.getFirstItemSize(); this[segmentName] = firstItemSize && firstItemSize[outerSize] || // or size of container this.isotope.size['inner' + size]; }; proto.getFirstItemSize = function () { var firstItem = this.isotope.filteredItems[0]; return firstItem && firstItem.element && getSize(firstItem.element); }; // ----- methods that should reference isotope ----- // proto.layout = function () { this.isotope.layout.apply(this.isotope, arguments); }; proto.getSize = function () { this.isotope.getSize(); this.size = this.isotope.size; }; // -------------------------- create -------------------------- // LayoutMode.modes = {}; LayoutMode.create = function (namespace, options) { function Mode() { LayoutMode.apply(this, arguments); } Mode.prototype = Object.create(proto); Mode.prototype.constructor = Mode; // default options if (options) { Mode.options = options; } Mode.prototype.namespace = namespace; // register in Isotope LayoutMode.modes[namespace] = Mode; return Mode; }; return LayoutMode; })); /*! * Masonry v4.1.1 * Cascading grid layout library * http://masonry.desandro.com * MIT License * by David DeSandro */ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('masonry/masonry', [ 'outlayer/outlayer', 'get-size/get-size' ], factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( require('outlayer'), require('get-size') ); } else { // browser global window.Masonry = factory( window.Outlayer, window.getSize ); } }(window, function factory(Outlayer, getSize) { // -------------------------- masonryDefinition -------------------------- // // create an Outlayer layout class var Masonry = Outlayer.create('masonry'); // isFitWidth -> fitWidth Masonry.compatOptions.fitWidth = 'isFitWidth'; Masonry.prototype._resetLayout = function () { this.getSize(); this._getMeasurement('columnWidth', 'outerWidth'); this._getMeasurement('gutter', 'outerWidth'); this.measureColumns(); // reset column Y this.colYs = []; for (var i = 0; i < this.cols; i++) { this.colYs.push(0); } this.maxY = 0; }; Masonry.prototype.measureColumns = function () { this.getContainerWidth(); // if columnWidth is 0, default to outerWidth of first item if (!this.columnWidth) { var firstItem = this.items[0]; var firstItemElem = firstItem && firstItem.element; // columnWidth fall back to item of first element this.columnWidth = firstItemElem && getSize(firstItemElem).outerWidth || // if first elem has no width, default to size of container this.containerWidth; } var columnWidth = this.columnWidth += this.gutter; // calculate columns var containerWidth = this.containerWidth + this.gutter; var cols = containerWidth / columnWidth; // fix rounding errors, typically with gutters var excess = columnWidth - containerWidth % columnWidth; // if overshoot is less than a pixel, round up, otherwise floor it var mathMethod = excess && excess < 1 ? 'round' : 'floor'; cols = Math[mathMethod](cols); this.cols = Math.max(cols, 1); }; Masonry.prototype.getContainerWidth = function () { // container is parent if fit width var isFitWidth = this._getOption('fitWidth'); var container = isFitWidth ? this.element.parentNode : this.element; // check that this.size and size are there // IE8 triggers resize on body size change, so they might not be var size = getSize(container); this.containerWidth = size && size.innerWidth; }; Masonry.prototype._getItemLayoutPosition = function (item) { item.getSize(); // how many columns does this brick span var remainder = item.size.outerWidth % this.columnWidth; var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; // round if off by 1 pixel, otherwise use ceil var colSpan = Math[mathMethod](item.size.outerWidth / this.columnWidth); colSpan = Math.min(colSpan, this.cols); var colGroup = this._getColGroup(colSpan); // get the minimum Y value from the columns var minimumY = Math.min.apply(Math, colGroup); var shortColIndex = colGroup.indexOf(minimumY); // position the brick var position = { x: this.columnWidth * shortColIndex, y: minimumY }; // apply setHeight to necessary columns var setHeight = minimumY + item.size.outerHeight; var setSpan = this.cols + 1 - colGroup.length; for (var i = 0; i < setSpan; i++) { this.colYs[shortColIndex + i] = setHeight; } return position; }; /** * @param {Number} colSpan - number of columns the element spans * @returns {Array} colGroup */ Masonry.prototype._getColGroup = function (colSpan) { if (colSpan < 2) { // if brick spans only one column, use all the column Ys return this.colYs; } var colGroup = []; // how many different places could this brick fit horizontally var groupCount = this.cols + 1 - colSpan; // for each group potential horizontal position for (var i = 0; i < groupCount; i++) { // make an array of colY values for that one group var groupColYs = this.colYs.slice(i, i + colSpan); // and get the max value of the array colGroup[i] = Math.max.apply(Math, groupColYs); } return colGroup; }; Masonry.prototype._manageStamp = function (stamp) { var stampSize = getSize(stamp); var offset = this._getElementOffset(stamp); // get the columns that this stamp affects var isOriginLeft = this._getOption('originLeft'); var firstX = isOriginLeft ? offset.left : offset.right; var lastX = firstX + stampSize.outerWidth; var firstCol = Math.floor(firstX / this.columnWidth); firstCol = Math.max(0, firstCol); var lastCol = Math.floor(lastX / this.columnWidth); // lastCol should not go over if multiple of columnWidth #425 lastCol -= lastX % this.columnWidth ? 0 : 1; lastCol = Math.min(this.cols - 1, lastCol); // set colYs to bottom of the stamp var isOriginTop = this._getOption('originTop'); var stampMaxY = (isOriginTop ? offset.top : offset.bottom) + stampSize.outerHeight; for (var i = firstCol; i <= lastCol; i++) { this.colYs[i] = Math.max(stampMaxY, this.colYs[i]); } }; Masonry.prototype._getContainerSize = function () { this.maxY = Math.max.apply(Math, this.colYs); var size = { height: this.maxY }; if (this._getOption('fitWidth')) { size.width = this._getContainerFitWidth(); } return size; }; Masonry.prototype._getContainerFitWidth = function () { var unusedCols = 0; // count unused columns var i = this.cols; while (--i) { if (this.colYs[i] !== 0) { break; } unusedCols++; } // fit container to columns that have been used return (this.cols - unusedCols) * this.columnWidth - this.gutter; }; Masonry.prototype.needsResizeLayout = function () { var previousWidth = this.containerWidth; this.getContainerWidth(); return previousWidth != this.containerWidth; }; return Masonry; })); /*! * Masonry layout mode * sub-classes Masonry * http://masonry.desandro.com */ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('isotope/js/layout-modes/masonry', [ '../layout-mode', 'masonry/masonry' ], factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( require('../layout-mode'), require('masonry-layout') ); } else { // browser global factory( window.Isotope.LayoutMode, window.Masonry ); } }(window, function factory(LayoutMode, Masonry) { 'use strict'; // -------------------------- masonryDefinition -------------------------- // // create an Outlayer layout class var MasonryMode = LayoutMode.create('masonry'); var proto = MasonryMode.prototype; var keepModeMethods = { _getElementOffset: true, layout: true, _getMeasurement: true }; // inherit Masonry prototype for (var method in Masonry.prototype) { // do not inherit mode methods if (!keepModeMethods[method]) { proto[method] = Masonry.prototype[method]; } } var measureColumns = proto.measureColumns; proto.measureColumns = function () { // set items, used if measuring first item this.items = this.isotope.filteredItems; measureColumns.call(this); }; // point to mode options for fitWidth var _getOption = proto._getOption; proto._getOption = function (option) { if (option == 'fitWidth') { return this.options.isFitWidth !== undefined ? this.options.isFitWidth : this.options.fitWidth; } return _getOption.apply(this.isotope, arguments); }; return MasonryMode; })); /** * fitRows layout mode */ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('isotope/js/layout-modes/fit-rows', [ '../layout-mode' ], factory); } else if (typeof exports == 'object') { // CommonJS module.exports = factory( require('../layout-mode') ); } else { // browser global factory( window.Isotope.LayoutMode ); } }(window, function factory(LayoutMode) { 'use strict'; var FitRows = LayoutMode.create('fitRows'); var proto = FitRows.prototype; proto._resetLayout = function () { this.x = 0; this.y = 0; this.maxY = 0; this._getMeasurement('gutter', 'outerWidth'); }; proto._getItemLayoutPosition = function (item) { item.getSize(); var itemWidth = item.size.outerWidth + this.gutter; // if this element cannot fit in the current row var containerWidth = this.isotope.size.innerWidth + this.gutter; if (this.x !== 0 && itemWidth + this.x > containerWidth) { this.x = 0; this.y = this.maxY; } var position = { x: this.x, y: this.y }; this.maxY = Math.max(this.maxY, this.y + item.size.outerHeight); this.x += itemWidth; return position; }; proto._getContainerSize = function () { return {height: this.maxY}; }; return FitRows; })); /** * vertical layout mode */ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('isotope/js/layout-modes/vertical', [ '../layout-mode' ], factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( require('../layout-mode') ); } else { // browser global factory( window.Isotope.LayoutMode ); } }(window, function factory(LayoutMode) { 'use strict'; var Vertical = LayoutMode.create('vertical', { horizontalAlignment: 0 }); var proto = Vertical.prototype; proto._resetLayout = function () { this.y = 0; }; proto._getItemLayoutPosition = function (item) { item.getSize(); var x = (this.isotope.size.innerWidth - item.size.outerWidth) * this.options.horizontalAlignment; var y = this.y; this.y += item.size.outerHeight; return {x: x, y: y}; }; proto._getContainerSize = function () { return {height: this.y}; }; return Vertical; })); /*! * Isotope v3.0.2 * * Licensed GPLv3 for open source use * or Isotope Commercial License for commercial use * * http://isotope.metafizzy.co * Copyright 2016 Metafizzy */ (function (window, factory) { // universal module definition /* jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define([ 'outlayer/outlayer', 'get-size/get-size', 'desandro-matches-selector/matches-selector', 'fizzy-ui-utils/utils', 'isotope/js/item', 'isotope/js/layout-mode', // include default layout modes 'isotope/js/layout-modes/masonry', 'isotope/js/layout-modes/fit-rows', 'isotope/js/layout-modes/vertical' ], function (Outlayer, getSize, matchesSelector, utils, Item, LayoutMode) { return factory(window, Outlayer, getSize, matchesSelector, utils, Item, LayoutMode); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory( window, require('outlayer'), require('get-size'), require('desandro-matches-selector'), require('fizzy-ui-utils'), require('isotope/js/item'), require('isotope/js/layout-mode'), // include default layout modes require('isotope/js/layout-modes/masonry'), require('isotope/js/layout-modes/fit-rows'), require('isotope/js/layout-modes/vertical') ); } else { // browser global window.Isotope = factory( window, window.Outlayer, window.getSize, window.matchesSelector, window.fizzyUIUtils, window.Isotope.Item, window.Isotope.LayoutMode ); } }(window, function factory(window, Outlayer, getSize, matchesSelector, utils, Item, LayoutMode) { // -------------------------- vars -------------------------- // var jQuery = window.jQuery; // -------------------------- helpers -------------------------- // var trim = String.prototype.trim ? function (str) { return str.trim(); } : function (str) { return str.replace(/^\s+|\s+$/g, ''); }; // -------------------------- isotopeDefinition -------------------------- // // create an Outlayer layout class var Isotope = Outlayer.create('isotope', { layoutMode: 'masonry', isJQueryFiltering: true, sortAscending: true }); Isotope.Item = Item; Isotope.LayoutMode = LayoutMode; var proto = Isotope.prototype; proto._create = function () { this.itemGUID = 0; // functions that sort items this._sorters = {}; this._getSorters(); // call super Outlayer.prototype._create.call(this); // create layout modes this.modes = {}; // start filteredItems with all items this.filteredItems = this.items; // keep of track of sortBys this.sortHistory = ['original-order']; // create from registered layout modes for (var name in LayoutMode.modes) { this._initLayoutMode(name); } }; proto.reloadItems = function () { // reset item ID counter this.itemGUID = 0; // call super Outlayer.prototype.reloadItems.call(this); }; proto._itemize = function () { var items = Outlayer.prototype._itemize.apply(this, arguments); // assign ID for original-order for (var i = 0; i < items.length; i++) { var item = items[i]; item.id = this.itemGUID++; } this._updateItemsSortData(items); return items; }; // -------------------------- layout -------------------------- // proto._initLayoutMode = function (name) { var Mode = LayoutMode.modes[name]; // set mode options // HACK extend initial options, back-fill in default options var initialOpts = this.options[name] || {}; this.options[name] = Mode.options ? utils.extend(Mode.options, initialOpts) : initialOpts; // init layout mode instance this.modes[name] = new Mode(this); }; proto.layout = function () { // if first time doing layout, do all magic if (!this._isLayoutInited && this._getOption('initLayout')) { this.arrange(); return; } this._layout(); }; // private method to be used in layout() & magic() proto._layout = function () { // don't animate first layout var isInstant = this._getIsInstant(); // layout flow this._resetLayout(); this._manageStamps(); this.layoutItems(this.filteredItems, isInstant); // flag for initalized this._isLayoutInited = true; }; // filter + sort + layout proto.arrange = function (opts) { // set any options pass this.option(opts); this._getIsInstant(); // filter, sort, and layout // filter var filtered = this._filter(this.items); this.filteredItems = filtered.matches; this._bindArrangeComplete(); if (this._isInstant) { this._noTransition(this._hideReveal, [filtered]); } else { this._hideReveal(filtered); } this._sort(); this._layout(); }; // alias to _init for main plugin method proto._init = proto.arrange; proto._hideReveal = function (filtered) { this.reveal(filtered.needReveal); this.hide(filtered.needHide); }; // HACK // Don't animate/transition first layout // Or don't animate/transition other layouts proto._getIsInstant = function () { var isLayoutInstant = this._getOption('layoutInstant'); var isInstant = isLayoutInstant !== undefined ? isLayoutInstant : !this._isLayoutInited; this._isInstant = isInstant; return isInstant; }; // listen for layoutComplete, hideComplete and revealComplete // to trigger arrangeComplete proto._bindArrangeComplete = function () { // listen for 3 events to trigger arrangeComplete var isLayoutComplete, isHideComplete, isRevealComplete; var _this = this; function arrangeParallelCallback() { if (isLayoutComplete && isHideComplete && isRevealComplete) { _this.dispatchEvent('arrangeComplete', null, [_this.filteredItems]); } } this.once('layoutComplete', function () { isLayoutComplete = true; arrangeParallelCallback(); }); this.once('hideComplete', function () { isHideComplete = true; arrangeParallelCallback(); }); this.once('revealComplete', function () { isRevealComplete = true; arrangeParallelCallback(); }); }; // -------------------------- filter -------------------------- // proto._filter = function (items) { var filter = this.options.filter; filter = filter || '*'; var matches = []; var hiddenMatched = []; var visibleUnmatched = []; var test = this._getFilterTest(filter); // test each item for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.isIgnored) { continue; } // add item to either matched or unmatched group var isMatched = test(item); // item.isFilterMatched = isMatched; // add to matches if its a match if (isMatched) { matches.push(item); } // add to additional group if item needs to be hidden or revealed if (isMatched && item.isHidden) { hiddenMatched.push(item); } else if (!isMatched && !item.isHidden) { visibleUnmatched.push(item); } } // return collections of items to be manipulated return { matches: matches, needReveal: hiddenMatched, needHide: visibleUnmatched }; }; // get a jQuery, function, or a matchesSelector test given the filter proto._getFilterTest = function (filter) { if (jQuery && this.options.isJQueryFiltering) { // use jQuery return function (item) { return jQuery(item.element).is(filter); }; } if (typeof filter == 'function') { // use filter as function return function (item) { return filter(item.element); }; } // default, use filter as selector string return function (item) { return matchesSelector(item.element, filter); }; }; // -------------------------- sorting -------------------------- // /** * @params {Array} elems * @public */ proto.updateSortData = function (elems) { // get items var items; if (elems) { elems = utils.makeArray(elems); items = this.getItems(elems); } else { // update all items if no elems provided items = this.items; } this._getSorters(); this._updateItemsSortData(items); }; proto._getSorters = function () { var getSortData = this.options.getSortData; for (var key in getSortData) { var sorter = getSortData[key]; this._sorters[key] = mungeSorter(sorter); } }; /** * @params {Array} items - of Isotope.Items * @private */ proto._updateItemsSortData = function (items) { // do not update if no items var len = items && items.length; for (var i = 0; len && i < len; i++) { var item = items[i]; item.updateSortData(); } }; // ----- munge sorter ----- // // encapsulate this, as we just need mungeSorter // other functions in here are just for munging var mungeSorter = (function () { // add a magic layer to sorters for convienent shorthands // `.foo-bar` will use the text of .foo-bar querySelector // `[foo-bar]` will use attribute // you can also add parser // `.foo-bar parseInt` will parse that as a number function mungeSorter(sorter) { // if not a string, return function or whatever it is if (typeof sorter != 'string') { return sorter; } // parse the sorter string var args = trim(sorter).split(' '); var query = args[0]; // check if query looks like [an-attribute] var attrMatch = query.match(/^\[(.+)\]$/); var attr = attrMatch && attrMatch[1]; var getValue = getValueGetter(attr, query); // use second argument as a parser var parser = Isotope.sortDataParsers[args[1]]; // parse the value, if there was a parser sorter = parser ? function (elem) { return elem && parser(getValue(elem)); } : // otherwise just return value function (elem) { return elem && getValue(elem); }; return sorter; } // get an attribute getter, or get text of the querySelector function getValueGetter(attr, query) { // if query looks like [foo-bar], get attribute if (attr) { return function getAttribute(elem) { return elem.getAttribute(attr); }; } // otherwise, assume its a querySelector, and get its text return function getChildText(elem) { var child = elem.querySelector(query); return child && child.textContent; }; } return mungeSorter; })(); // parsers used in getSortData shortcut strings Isotope.sortDataParsers = { 'parseInt': function (val) { return parseInt(val, 10); }, 'parseFloat': function (val) { return parseFloat(val); } }; // ----- sort method ----- // // sort filteredItem order proto._sort = function () { var sortByOpt = this.options.sortBy; if (!sortByOpt) { return; } // concat all sortBy and sortHistory var sortBys = [].concat.apply(sortByOpt, this.sortHistory); // sort magic var itemSorter = getItemSorter(sortBys, this.options.sortAscending); this.filteredItems.sort(itemSorter); // keep track of sortBy History if (sortByOpt != this.sortHistory[0]) { // add to front, oldest goes in last this.sortHistory.unshift(sortByOpt); } }; // returns a function used for sorting function getItemSorter(sortBys, sortAsc) { return function sorter(itemA, itemB) { // cycle through all sortKeys for (var i = 0; i < sortBys.length; i++) { var sortBy = sortBys[i]; var a = itemA.sortData[sortBy]; var b = itemB.sortData[sortBy]; if (a > b || a < b) { // if sortAsc is an object, use the value given the sortBy key var isAscending = sortAsc[sortBy] !== undefined ? sortAsc[sortBy] : sortAsc; var direction = isAscending ? 1 : -1; return (a > b ? 1 : -1) * direction; } } return 0; }; } // -------------------------- methods -------------------------- // // get layout mode proto._mode = function () { var layoutMode = this.options.layoutMode; var mode = this.modes[layoutMode]; if (!mode) { // TODO console.error throw new Error('No layout mode: ' + layoutMode); } // HACK sync mode's options // any options set after init for layout mode need to be synced mode.options = this.options[layoutMode]; return mode; }; proto._resetLayout = function () { // trigger original reset layout Outlayer.prototype._resetLayout.call(this); this._mode()._resetLayout(); }; proto._getItemLayoutPosition = function (item) { return this._mode()._getItemLayoutPosition(item); }; proto._manageStamp = function (stamp) { this._mode()._manageStamp(stamp); }; proto._getContainerSize = function () { return this._mode()._getContainerSize(); }; proto.needsResizeLayout = function () { return this._mode().needsResizeLayout(); }; // -------------------------- adding & removing -------------------------- // // HEADS UP overwrites default Outlayer appended proto.appended = function (elems) { var items = this.addItems(elems); if (!items.length) { return; } // filter, layout, reveal new items var filteredItems = this._filterRevealAdded(items); // add to filteredItems this.filteredItems = this.filteredItems.concat(filteredItems); }; // HEADS UP overwrites default Outlayer prepended proto.prepended = function (elems) { var items = this._itemize(elems); if (!items.length) { return; } // start new layout this._resetLayout(); this._manageStamps(); // filter, layout, reveal new items var filteredItems = this._filterRevealAdded(items); // layout previous items this.layoutItems(this.filteredItems); // add to items and filteredItems this.filteredItems = filteredItems.concat(this.filteredItems); this.items = items.concat(this.items); }; proto._filterRevealAdded = function (items) { var filtered = this._filter(items); this.hide(filtered.needHide); // reveal all new items this.reveal(filtered.matches); // layout new items, no transition this.layoutItems(filtered.matches, true); return filtered.matches; }; /** * Filter, sort, and layout newly-appended item elements * @param {Array or NodeList or Element} elems */ proto.insert = function (elems) { var items = this.addItems(elems); if (!items.length) { return; } // append item elements var i, item; var len = items.length; for (i = 0; i < len; i++) { item = items[i]; this.element.appendChild(item.element); } // filter new stuff var filteredInsertItems = this._filter(items).matches; // set flag for (i = 0; i < len; i++) { items[i].isLayoutInstant = true; } this.arrange(); // reset flag for (i = 0; i < len; i++) { delete items[i].isLayoutInstant; } this.reveal(filteredInsertItems); }; var _remove = proto.remove; proto.remove = function (elems) { elems = utils.makeArray(elems); var removeItems = this.getItems(elems); // do regular thing _remove.call(this, elems); // bail if no items to remove var len = removeItems && removeItems.length; // remove elems from filteredItems for (var i = 0; len && i < len; i++) { var item = removeItems[i]; // remove item from collection utils.removeFrom(this.filteredItems, item); } }; proto.shuffle = function () { // update random sortData for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; item.sortData.random = Math.random(); } this.options.sortBy = 'random'; this._sort(); this._layout(); }; /** * trigger fn without transition * kind of hacky to have this in the first place * @param {Function} fn * @param {Array} args * @returns ret * @private */ proto._noTransition = function (fn, args) { // save transitionDuration before disabling var transitionDuration = this.options.transitionDuration; // disable transition this.options.transitionDuration = 0; // do it var returnValue = fn.apply(this, args); // re-enable transition for reveal this.options.transitionDuration = transitionDuration; return returnValue; }; // ----- helper methods ----- // /** * getter method for getting filtered item elements * @returns {Array} elems - collection of item elements */ proto.getFilteredItemElements = function () { return this.filteredItems.map(function (item) { return item.element; }); }; // ----- ----- // return Isotope; }));