text: /*!
   * Shuffle.js by @Vestride
   * Categorize, sort, and filter a responsive grid of items.
   * Dependencies: jQuery 1.9+, Modernizr 2.6.2+
   * @license MIT license
   * @version 3.0.0
   */
  (function (factory) {
    if (typeof define === 'function' && define.amd) {
      define(['jquery', 'modernizr'], factory);
    } else {
      window.Shuffle = factory(window.jQuery, window.Modernizr);
    }
  })(function($, Modernizr, undefined) {
  
  'use strict';
  
  
  // Validate Modernizr exists.
  // Shuffle requires `csstransitions`, `csstransforms`, `csstransforms3d`,
  // and `prefixed` to exist on the Modernizr object.
  if (typeof Modernizr !== 'object') {
    throw new Error('Shuffle.js requires Modernizr.\n' +
        'http://vestride.github.io/Shuffle/#dependencies');
  }
  
  
  /**
   * Returns css prefixed properties like `-webkit-transition` or `box-sizing`
   * from `transition` or `boxSizing`, respectively.
   * @param {(string|boolean)} prop Property to be prefixed.
   * @return {string} The prefixed css property.
   */
  function dashify( prop ) {
    if (!prop) {
      return '';
    }
  
    // Replace upper case with dash-lowercase,
    // then fix ms- prefixes because they're not capitalized.
    return prop.replace(/([A-Z])/g, function( str, m1 ) {
      return '-' + m1.toLowerCase();
    }).replace(/^ms-/,'-ms-');
  }
  
  // Constant, prefixed variables.
  var TRANSITION = Modernizr.prefixed('transition');
  var TRANSITION_DELAY = Modernizr.prefixed('transitionDelay');
  var TRANSITION_DURATION = Modernizr.prefixed('transitionDuration');
  
  // Note(glen): Stock Android 4.1.x browser will fail here because it wrongly
  // says it supports non-prefixed transitions.
  // https://github.com/Modernizr/Modernizr/issues/897
  var TRANSITIONEND = {
    'WebkitTransition' : 'webkitTransitionEnd',
    'transition' : 'transitionend'
  }[ TRANSITION ];
  
  var TRANSFORM = Modernizr.prefixed('transform');
  var CSS_TRANSFORM = dashify(TRANSFORM);
  
  // Constants
  var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions;
  var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d;
  var SHUFFLE = 'shuffle';
  var COLUMN_THRESHOLD = 0.3;
  
  // Configurable. You can change these constants to fit your application.
  // The default scale and concealed scale, however, have to be different values.
  var ALL_ITEMS = 'all';
  var FILTER_ATTRIBUTE_KEY = 'groups';
  var DEFAULT_SCALE = 1;
  var CONCEALED_SCALE = 0.001;
  
  
  // Underscore's throttle function.
  function throttle(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    options = options || {};
    var later = function() {
      previous = options.leading === false ? 0 : $.now();
      timeout = null;
      result = func.apply(context, args);
      context = args = null;
    };
    return function() {
      var now = $.now();
      if (!previous && options.leading === false) {
        previous = now;
      }
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        clearTimeout(timeout);
        timeout = null;
        previous = now;
        result = func.apply(context, args);
        context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  }
  
  function each(obj, iterator, context) {
    for (var i = 0, length = obj.length; i < length; i++) {
      if (iterator.call(context, obj[i], i, obj) === {}) {
        return;
      }
    }
  }
  
  function defer(fn, context, wait) {
    return setTimeout( $.proxy( fn, context ), wait );
  }
  
  function arrayMax( array ) {
    return Math.max.apply( Math, array );
  }
  
  function arrayMin( array ) {
    return Math.min.apply( Math, array );
  }
  
  
  /**
   * Always returns a numeric value, given a value.
   * @param {*} value Possibly numeric value.
   * @return {number} `value` or zero if `value` isn't numeric.
   * @private
   */
  function getNumber(value) {
    return $.isNumeric(value) ? value : 0;
  }
  
  
  /**
   * Represents a coordinate pair.
   * @param {number} [x=0] X.
   * @param {number} [y=0] Y.
   */
  var Point = function(x, y) {
    this.x = getNumber( x );
    this.y = getNumber( y );
  };
  
  
  /**
   * Whether two points are equal.
   * @param {Point} a Point A.
   * @param {Point} b Point B.
   * @return {boolean}
   */
  Point.equals = function(a, b) {
    return a && b && a.x === b.x && a.y === b.y;
  };
  
  
  // Used for unique instance variables
  var id = 0;
  var $window = $( window );
  
  
  /**
   * Categorize, sort, and filter a responsive grid of items.
   *
   * @param {Element} element An element which is the parent container for the grid items.
   * @param {Object} [options=Shuffle.options] Options object.
   * @constructor
   */
  var Shuffle = function( element, options ) {
    options = options || {};
    $.extend( this, Shuffle.options, options, Shuffle.settings );
  
    this.$el = $(element);
    this.element = element;
    this.unique = 'shuffle_' + id++;
  
    this._fire( Shuffle.EventType.LOADING );
    this._init();
  
    // Dispatch the done event asynchronously so that people can bind to it after
    // Shuffle has been initialized.
    defer(function() {
      this.initialized = true;
      this._fire( Shuffle.EventType.DONE );
    }, this, 16);
  };
  
  
  /**
   * Events the container element emits with the .shuffle namespace.
   * For example, "done.shuffle".
   * @enum {string}
   */
  Shuffle.EventType = {
    LOADING: 'loading',
    DONE: 'done',
    LAYOUT: 'layout',
    REMOVED: 'removed'
  };
  
  
  /** @enum {string} */
  Shuffle.ClassName = {
    BASE: SHUFFLE,
    SHUFFLE_ITEM: 'shuffle-item',
    FILTERED: 'filtered',
    CONCEALED: 'concealed'
  };
  
  
  // Overrideable options
  Shuffle.options = {
    group: ALL_ITEMS, // Initial filter group.
    speed: 250, // Transition/animation speed (milliseconds).
    easing: 'ease-out', // CSS easing function to use.
    itemSelector: '', // e.g. '.picture-item'.
    sizer: null, // Sizer element. Use an element to determine the size of columns and gutters.
    gutterWidth: 0, // A static number or function that tells the plugin how wide the gutters between columns are (in pixels).
    columnWidth: 0, // A static number or function that returns a number which tells the plugin how wide the columns are (in pixels).
    delimeter: null, // If your group is not json, and is comma delimeted, you could set delimeter to ','.
    buffer: 0, // Useful for percentage based heights when they might not always be exactly the same (in pixels).
    initialSort: null, // Shuffle can be initialized with a sort object. It is the same object given to the sort method.
    throttle: throttle, // By default, shuffle will throttle resize events. This can be changed or removed.
    throttleTime: 300, // How often shuffle can be called on resize (in milliseconds).
    sequentialFadeDelay: 150, // Delay between each item that fades in when adding items.
    supported: CAN_TRANSITION_TRANSFORMS // Whether to use transforms or absolute positioning.
  };
  
  
  // Not overrideable
  Shuffle.settings = {
    useSizer: false,
    itemCss : { // default CSS for each item
      position: 'absolute',
      top: 0,
      left: 0,
      visibility: 'visible'
    },
    revealAppendedDelay: 300,
    lastSort: {},
    lastFilter: ALL_ITEMS,
    enabled: true,
    destroyed: false,
    initialized: false,
    _animations: [],
    styleQueue: []
  };
  
  
  // Expose for testing.
  Shuffle.Point = Point;
  
  
  /**
   * Static methods.
   */
  
  /**
   * If the browser has 3d transforms available, build a string with those,
   * otherwise use 2d transforms.
   * @param {Point} point X and Y positions.
   * @param {number} scale Scale amount.
   * @return {string} A normalized string which can be used with the transform style.
   * @private
   */
  Shuffle._getItemTransformString = function(point, scale) {
    if ( HAS_TRANSFORMS_3D ) {
      return 'translate3d(' + point.x + 'px, ' + point.y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)';
    } else {
      return 'translate(' + point.x + 'px, ' + point.y + 'px) scale(' + scale + ')';
    }
  };
  
  
  /**
   * Retrieve the computed style for an element, parsed as a float. This should
   * not be used for width or height values because jQuery mangles them and they
   * are not precise enough.
   * @param {Element} element Element to get style for.
   * @param {string} style Style property.
   * @return {number} The parsed computed value or zero if that fails because IE
   *     will return 'auto' when the element doesn't have margins instead of
   *     the computed style.
   * @private
   */
  Shuffle._getNumberStyle = function( element, style ) {
    return Shuffle._getFloat( $( element ).css( style )  );
  };
  
  
  /**
   * Parse a string as an integer.
   * @param {string} value String integer.
   * @return {number} The string as an integer or zero.
   * @private
   */
  Shuffle._getInt = function(value) {
    return getNumber( parseInt( value, 10 ) );
  };
  
  /**
   * Parse a string as an float.
   * @param {string} value String float.
   * @return {number} The string as an float or zero.
   * @private
   */
  Shuffle._getFloat = function(value) {
    return getNumber( parseFloat( value ) );
  };
  
  
  /**
   * Returns the outer width of an element, optionally including its margins.
   * The `offsetWidth` property must be used because having a scale transform
   * on the element affects the bounding box. Sadly, Firefox doesn't return an
   * integer value for offsetWidth (yet).
   * @param {Element} element The element.
   * @param {boolean} [includeMargins] Whether to include margins. Default is false.
   * @return {number} The width.
   */
  Shuffle._getOuterWidth = function( element, includeMargins ) {
    var width = element.offsetWidth;
  
    // Use jQuery here because it uses getComputedStyle internally and is
    // cross-browser. Using the style property of the element will only work
    // if there are inline styles.
    if ( includeMargins ) {
      var marginLeft = Shuffle._getNumberStyle( element, 'marginLeft');
      var marginRight = Shuffle._getNumberStyle( element, 'marginRight');
      width += marginLeft + marginRight;
    }
  
    return width;
  };
  
  
  /**
   * Returns the outer height of an element, optionally including its margins.
   * @param {Element} element The element.
   * @param {boolean} [includeMargins] Whether to include margins. Default is false.
   * @return {number} The height.
   */
  Shuffle._getOuterHeight = function( element, includeMargins ) {
    var height = element.offsetHeight;
  
    if ( includeMargins ) {
      var marginTop = Shuffle._getNumberStyle( element, 'marginTop');
      var marginBottom = Shuffle._getNumberStyle( element, 'marginBottom');
      height += marginTop + marginBottom;
    }
  
    return height;
  };
  
  
  /**
   * Change a property or execute a function which will not have a transition
   * @param {Element} element DOM element that won't be transitioned
   * @param {Function} callback A function which will be called while transition
   *     is set to 0ms.
   * @param {Object} [context] Optional context for the callback function.
   * @private
   */
  Shuffle._skipTransition = function( element, callback, context ) {
    var duration = element.style[ TRANSITION_DURATION ];
  
    // Set the duration to zero so it happens immediately
    element.style[ TRANSITION_DURATION ] = '0ms'; // ms needed for firefox!
  
    callback.call( context );
  
    // Force reflow
    var reflow = element.offsetWidth;
    // Avoid jshint warnings: unused variables and expressions.
    reflow = null;
  
    // Put the duration back
    element.style[ TRANSITION_DURATION ] = duration;
  };
  
  
  /**
   * Instance methods.
   */
  
  Shuffle.prototype._init = function() {
    this.$items = this._getItems();
  
    this.sizer = this._getElementOption( this.sizer );
  
    if ( this.sizer ) {
      this.useSizer = true;
    }
  
    // Add class and invalidate styles
    this.$el.addClass( Shuffle.ClassName.BASE );
  
    // Set initial css for each item
    this._initItems();
  
    // Bind resize events
    // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
    $window.on('resize.' + SHUFFLE + '.' + this.unique, this._getResizeFunction());
  
    // Get container css all in one request. Causes reflow
    var containerCSS = this.$el.css(['position', 'overflow']);
    var containerWidth = Shuffle._getOuterWidth( this.element );
  
    // Add styles to the container if it doesn't have them.
    this._validateStyles( containerCSS );
  
    // We already got the container's width above, no need to cause another reflow getting it again...
    // Calculate the number of columns there will be
    this._setColumns( containerWidth );
  
    // Kick off!
    this.shuffle( this.group, this.initialSort );
  
    // The shuffle items haven't had transitions set on them yet
    // so the user doesn't see the first layout. Set them now that the first layout is done.
    if ( this.supported ) {
      defer(function() {
        this._setTransitions();
        this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing;
      }, this);
    }
  };
  
  
  /**
   * Returns a throttled and proxied function for the resize handler.
   * @return {Function}
   * @private
   */
  Shuffle.prototype._getResizeFunction = function() {
    var resizeFunction = $.proxy( this._onResize, this );
    return this.throttle ?
        this.throttle( resizeFunction, this.throttleTime ) :
        resizeFunction;
  };
  
  
  /**
   * Retrieve an element from an option.
   * @param {string|jQuery|Element} option The option to check.
   * @return {?Element} The plain element or null.
   * @private
   */
  Shuffle.prototype._getElementOption = function( option ) {
    // If column width is a string, treat is as a selector and search for the
    // sizer element within the outermost container
    if ( typeof option === 'string' ) {
      return this.$el.find( option )[0] || null;
  
    // Check for an element
    } else if ( option && option.nodeType && option.nodeType === 1 ) {
      return option;
  
    // Check for jQuery object
    } else if ( option && option.jquery ) {
      return option[0];
    }
  
    return null;
  };
  
  
  /**
   * Ensures the shuffle container has the css styles it needs applied to it.
   * @param {Object} styles Key value pairs for position and overflow.
   * @private
   */
  Shuffle.prototype._validateStyles = function(styles) {
    // Position cannot be static.
    if ( styles.position === 'static' ) {
      this.element.style.position = 'relative';
    }
  
    // Overflow has to be hidden
    if ( styles.overflow !== 'hidden' ) {
      this.element.style.overflow = 'hidden';
    }
  };
  
  
  /**
   * Filter the elements by a category.
   * @param {string} [category] Category to filter by. If it's given, the last
   *     category will be used to filter the items.
   * @param {ArrayLike} [$collection] Optionally filter a collection. Defaults to
   *     all the items.
   * @return {jQuery} Filtered items.
   * @private
   */
  Shuffle.prototype._filter = function( category, $collection ) {
    category = category || this.lastFilter;
    $collection = $collection || this.$items;
  
    var set = this._getFilteredSets( category, $collection );
  
    // Individually add/remove concealed/filtered classes
    this._toggleFilterClasses( set.filtered, set.concealed );
  
    // Save the last filter in case elements are appended.
    this.lastFilter = category;
  
    // This is saved mainly because providing a filter function (like searching)
    // will overwrite the `lastFilter` property every time its called.
    if ( typeof category === 'string' ) {
      this.group = category;
    }
  
    return set.filtered;
  };
  
  
  /**
   * Returns an object containing the filtered and concealed elements.
   * @param {string|Function} category Category or function to filter by.
   * @param {ArrayLike.<Element>} $items A collection of items to filter.
   * @return {!{filtered: jQuery, concealed: jQuery}}
   * @private
   */
  Shuffle.prototype._getFilteredSets = function( category, $items ) {
    var $filtered = $();
    var $concealed = $();
  
    // category === 'all', add filtered class to everything
    if ( category === ALL_ITEMS ) {
      $filtered = $items;
  
    // Loop through each item and use provided function to determine
    // whether to hide it or not.
    } else {
      each($items, function( el ) {
        var $item = $(el);
        if ( this._doesPassFilter( category, $item ) ) {
          $filtered = $filtered.add( $item );
        } else {
          $concealed = $concealed.add( $item );
        }
      }, this);
    }
  
    return {
      filtered: $filtered,
      concealed: $concealed
    };
  };
  
  
  /**
   * Test an item to see if it passes a category.
   * @param {string|Function} category Category or function to filter by.
   * @param {jQuery} $item A single item, wrapped with jQuery.
   * @return {boolean} Whether it passes the category/filter.
   * @private
   */
  Shuffle.prototype._doesPassFilter = function( category, $item ) {
    if ( $.isFunction( category ) ) {
      return category.call( $item[0], $item, this );
  
    // Check each element's data-groups attribute against the given category.
    } else {
      var groups = $item.data( FILTER_ATTRIBUTE_KEY );
      var keys = this.delimeter && !$.isArray( groups ) ?
          groups.split( this.delimeter ) :
          groups;
      return $.inArray(category, keys) > -1;
    }
  };
  
  
  /**
   * Toggles the filtered and concealed class names.
   * @param {jQuery} $filtered Filtered set.
   * @param {jQuery} $concealed Concealed set.
   * @private
   */
  Shuffle.prototype._toggleFilterClasses = function( $filtered, $concealed ) {
    $filtered
      .removeClass( Shuffle.ClassName.CONCEALED )
      .addClass( Shuffle.ClassName.FILTERED );
    $concealed
      .removeClass( Shuffle.ClassName.FILTERED )
      .addClass( Shuffle.ClassName.CONCEALED );
  };
  
  
  /**
   * Set the initial css for each item
   * @param {jQuery} [$items] Optionally specifiy at set to initialize
   */
  Shuffle.prototype._initItems = function( $items ) {
    $items = $items || this.$items;
    $items.addClass([
      Shuffle.ClassName.SHUFFLE_ITEM,
      Shuffle.ClassName.FILTERED
    ].join(' '));
    $items.css( this.itemCss ).data('point', new Point()).data('scale', DEFAULT_SCALE);
  };
  
  
  /**
   * Updates the filtered item count.
   * @private
   */
  Shuffle.prototype._updateItemCount = function() {
    this.visibleItems = this._getFilteredItems().length;
  };
  
  
  /**
   * Sets css transform transition on a an element.
   * @param {Element} element Element to set transition on.
   * @private
   */
  Shuffle.prototype._setTransition = function( element ) {
    element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' +
      this.easing + ', opacity ' + this.speed + 'ms ' + this.easing;
  };
  
  
  /**
   * Sets css transform transition on a group of elements.
   * @param {ArrayLike.<Element>} $items Elements to set transitions on.
   * @private
   */
  Shuffle.prototype._setTransitions = function( $items ) {
    $items = $items || this.$items;
    each($items, function( el ) {
      this._setTransition( el );
    }, this);
  };
  
  
  /**
   * Sets a transition delay on a collection of elements, making each delay
   * greater than the last.
   * @param {ArrayLike.<Element>} $collection Array to iterate over.
   */
  Shuffle.prototype._setSequentialDelay = function( $collection ) {
    if ( !this.supported ) {
      return;
    }
  
    // $collection can be an array of dom elements or jquery object
    each($collection, function( el, i ) {
      // This works because the transition-property: transform, opacity;
      el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * this.sequentialFadeDelay) + 'ms';
    }, this);
  };
  
  
  Shuffle.prototype._getItems = function() {
    return this.$el.children( this.itemSelector );
  };
  
  
  Shuffle.prototype._getFilteredItems = function() {
    return this.$items.filter('.' + Shuffle.ClassName.FILTERED);
  };
  
  
  Shuffle.prototype._getConcealedItems = function() {
    return this.$items.filter('.' + Shuffle.ClassName.CONCEALED);
  };
  
  
  /**
   * Returns the column size, based on column width and sizer options.
   * @param {number} containerWidth Size of the parent container.
   * @param {number} gutterSize Size of the gutters.
   * @return {number}
   * @private
   */
  Shuffle.prototype._getColumnSize = function( containerWidth, gutterSize ) {
    var size;
  
    // If the columnWidth property is a function, then the grid is fluid
    if ( $.isFunction( this.columnWidth ) ) {
      size = this.columnWidth(containerWidth);
  
    // columnWidth option isn't a function, are they using a sizing element?
    } else if ( this.useSizer ) {
      size = Shuffle._getOuterWidth(this.sizer);
  
    // if not, how about the explicitly set option?
    } else if ( this.columnWidth ) {
      size = this.columnWidth;
  
    // or use the size of the first item
    } else if ( this.$items.length > 0 ) {
      size = Shuffle._getOuterWidth(this.$items[0], true);
  
    // if there's no items, use size of container
    } else {
      size = containerWidth;
    }
  
    // Don't let them set a column width of zero.
    if ( size === 0 ) {
      size = containerWidth;
    }
  
    return size + gutterSize;
  };
  
  
  /**
   * Returns the gutter size, based on gutter width and sizer options.
   * @param {number} containerWidth Size of the parent container.
   * @return {number}
   * @private
   */
  Shuffle.prototype._getGutterSize = function( containerWidth ) {
    var size;
    if ( $.isFunction( this.gutterWidth ) ) {
      size = this.gutterWidth(containerWidth);
    } else if ( this.useSizer ) {
      size = Shuffle._getNumberStyle(this.sizer, 'marginLeft');
    } else {
      size = this.gutterWidth;
    }
  
    return size;
  };
  
  
  /**
   * Calculate the number of columns to be used. Gets css if using sizer element.
   * @param {number} [theContainerWidth] Optionally specify a container width if it's already available.
   */
  Shuffle.prototype._setColumns = function( theContainerWidth ) {
    var containerWidth = theContainerWidth || Shuffle._getOuterWidth( this.element );
    var gutter = this._getGutterSize( containerWidth );
    var columnWidth = this._getColumnSize( containerWidth, gutter );
    var calculatedColumns = (containerWidth + gutter) / columnWidth;
  
    // Widths given from getComputedStyle are not precise enough...
    if ( Math.abs(Math.round(calculatedColumns) - calculatedColumns) < COLUMN_THRESHOLD ) {
      // e.g. calculatedColumns = 11.998876
      calculatedColumns = Math.round( calculatedColumns );
    }
  
    this.cols = Math.max( Math.floor(calculatedColumns), 1 );
    this.containerWidth = containerWidth;
    this.colWidth = columnWidth;
  };
  
  /**
   * Adjust the height of the grid
   */
  Shuffle.prototype._setContainerSize = function() {
    this.$el.css( 'height', this._getContainerSize() );
  };
  
  
  /**
   * Based on the column heights, it returns the biggest one.
   * @return {number}
   * @private
   */
  Shuffle.prototype._getContainerSize = function() {
    return arrayMax( this.positions );
  };
  
  
  /**
   * Fire events with .shuffle namespace
   */
  Shuffle.prototype._fire = function( name, args ) {
    this.$el.trigger( name + '.' + SHUFFLE, args && args.length ? args : [ this ] );
  };
  
  
  /**
   * Zeros out the y columns array, which is used to determine item placement.
   * @private
   */
  Shuffle.prototype._resetCols = function() {
    var i = this.cols;
    this.positions = [];
    while (i--) {
      this.positions.push( 0 );
    }
  };
  
  
  /**
   * Loops through each item that should be shown and calculates the x, y position.
   * @param {Array.<Element>} items Array of items that will be shown/layed out in order in their array.
   *     Because jQuery collection are always ordered in DOM order, we can't pass a jq collection.
   * @param {boolean} [isOnlyPosition=false] If true this will position the items with zero opacity.
   */
  Shuffle.prototype._layout = function( items, isOnlyPosition ) {
    each(items, function( item ) {
      this._layoutItem( item, !!isOnlyPosition );
    }, this);
  
    // `_layout` always happens after `_shrink`, so it's safe to process the style
    // queue here with styles from the shrink method.
    this._processStyleQueue();
  
    // Adjust the height of the container.
    this._setContainerSize();
  };
  
  
  /**
   * Calculates the position of the item and pushes it onto the style queue.
   * @param {Element} item Element which is being positioned.
   * @param {boolean} isOnlyPosition Whether to position the item, but with zero
   *     opacity so that it can fade in later.
   * @private
   */
  Shuffle.prototype._layoutItem = function( item, isOnlyPosition ) {
    var $item = $(item);
    var itemData = $item.data();
    var currPos = itemData.point;
    var currScale = itemData.scale;
    var itemSize = {
      width: Shuffle._getOuterWidth( item, true ),
      height: Shuffle._getOuterHeight( item, true )
    };
    var pos = this._getItemPosition( itemSize );
  
    // If the item will not change its position, do not add it to the render
    // queue. Transitions don't fire when setting a property to the same value.
    if ( Point.equals(currPos, pos) && currScale === DEFAULT_SCALE ) {
      return;
    }
  
    // Save data for shrink
    itemData.point = pos;
    itemData.scale = DEFAULT_SCALE;
  
    this.styleQueue.push({
      $item: $item,
      point: pos,
      scale: DEFAULT_SCALE,
      opacity: isOnlyPosition ? 0 : 1,
      skipTransition: isOnlyPosition,
      callfront: function() {
        if ( !isOnlyPosition ) {
          $item.css( 'visibility', 'visible' );
        }
      },
      callback: function() {
        if ( isOnlyPosition ) {
          $item.css( 'visibility', 'hidden' );
        }
      }
    });
  };
  
  
  /**
   * Determine the location of the next item, based on its size.
   * @param {{width: number, height: number}} itemSize Object with width and height.
   * @return {Point}
   * @private
   */
  Shuffle.prototype._getItemPosition = function( itemSize ) {
    var columnSpan = this._getColumnSpan( itemSize.width, this.colWidth, this.cols );
  
    var setY = this._getColumnSet( columnSpan, this.cols );
  
    // Finds the index of the smallest number in the set.
    var shortColumnIndex = this._getShortColumn( setY, this.buffer );
  
    // Position the item
    var point = new Point(
      Math.round( this.colWidth * shortColumnIndex ),
      Math.round( setY[shortColumnIndex] ));
  
    // Update the columns array with the new values for each column.
    // e.g. before the update the columns could be [250, 0, 0, 0] for an item
    // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0].
    var setHeight = setY[shortColumnIndex] + itemSize.height;
    var setSpan = this.cols + 1 - setY.length;
    for ( var i = 0; i < setSpan; i++ ) {
      this.positions[ shortColumnIndex + i ] = setHeight;
    }
  
    return point;
  };
  
  
  /**
   * Determine the number of columns an items spans.
   * @param {number} itemWidth Width of the item.
   * @param {number} columnWidth Width of the column (includes gutter).
   * @param {number} columns Total number of columns
   * @return {number}
   * @private
   */
  Shuffle.prototype._getColumnSpan = function( itemWidth, columnWidth, columns ) {
    var columnSpan = itemWidth / columnWidth;
  
    // If the difference between the rounded column span number and the
    // calculated column span number is really small, round the number to
    // make it fit.
    if ( Math.abs(Math.round( columnSpan ) - columnSpan ) < COLUMN_THRESHOLD ) {
      // e.g. columnSpan = 4.0089945390298745
      columnSpan = Math.round( columnSpan );
    }
  
    // Ensure the column span is not more than the amount of columns in the whole layout.
    return Math.min( Math.ceil( columnSpan ), columns );
  };
  
  
  /**
   * Retrieves the column set to use for placement.
   * @param {number} columnSpan The number of columns this current item spans.
   * @param {number} columns The total columns in the grid.
   * @return {Array.<number>} An array of numbers represeting the column set.
   * @private
   */
  Shuffle.prototype._getColumnSet = function( columnSpan, columns ) {
    // The item spans only one column.
    if ( columnSpan === 1 ) {
      return this.positions;
  
    // The item spans more than one column, figure out how many different
    // places it could fit horizontally.
    // The group count is the number of places within the positions this block
    // could fit, ignoring the current positions of items.
    // Imagine a 2 column brick as the second item in a 4 column grid with
    // 10px height each. Find the places it would fit:
    // [10, 0, 0, 0]
    //  |   |  |
    //  *   *  *
    //
    // Then take the places which fit and get the bigger of the two:
    // max([10, 0]), max([0, 0]), max([0, 0]) = [10, 0, 0]
    //
    // Next, find the first smallest number (the short column).
    // [10, 0, 0]
    //      |
    //      *
    //
    // And that's where it should be placed!
    } else {
      var groupCount = columns + 1 - columnSpan;
      var groupY = [];
  
      // For how many possible positions for this item there are.
      for ( var i = 0; i < groupCount; i++ ) {
        // Find the bigger value for each place it could fit.
        groupY[i] = arrayMax( this.positions.slice( i, i + columnSpan ) );
      }
  
      return groupY;
    }
  };
  
  
  /**
   * Find index of short column, the first from the left where this item will go.
   *
   * @param {Array.<number>} positions The array to search for the smallest number.
   * @param {number} buffer Optional buffer which is very useful when the height
   *     is a percentage of the width.
   * @return {number} Index of the short column.
   * @private
   */
  Shuffle.prototype._getShortColumn = function( positions, buffer ) {
    var minPosition = arrayMin( positions );
    for (var i = 0, len = positions.length; i < len; i++) {
      if ( positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer ) {
        return i;
      }
    }
    return 0;
  };
  
  
  /**
   * Hides the elements that don't match our filter.
   * @param {jQuery} $collection jQuery collection to shrink.
   * @private
   */
  Shuffle.prototype._shrink = function( $collection ) {
    var $concealed = $collection || this._getConcealedItems();
  
    each($concealed, function( item ) {
      var $item = $(item);
      var itemData = $item.data();
  
      // Continuing would add a transitionend event listener to the element, but
      // that listener would not execute because the transform and opacity would
      // stay the same.
      if ( itemData.scale === CONCEALED_SCALE ) {
        return;
      }
  
      itemData.scale = CONCEALED_SCALE;
  
      this.styleQueue.push({
        $item: $item,
        point: itemData.point,
        scale : CONCEALED_SCALE,
        opacity: 0,
        callback: function() {
          $item.css( 'visibility', 'hidden' );
        }
      });
    }, this);
  };
  
  
  /**
   * Resize handler.
   * @private
   */
  Shuffle.prototype._onResize = function() {
    // If shuffle is disabled, destroyed, don't do anything
    if ( !this.enabled || this.destroyed || this.isTransitioning ) {
      return;
    }
  
    // Will need to check height in the future if it's layed out horizontaly
    var containerWidth = Shuffle._getOuterWidth( this.element );
  
    // containerWidth hasn't changed, don't do anything
    if ( containerWidth === this.containerWidth ) {
      return;
    }
  
    this.update();
  };
  
  
  /**
   * Returns styles for either jQuery animate or transition.
   * @param {Object} opts Transition options.
   * @return {!Object} Transforms for transitions, left/top for animate.
   * @private
   */
  Shuffle.prototype._getStylesForTransition = function( opts ) {
    var styles = {
      opacity: opts.opacity
    };
  
    if ( this.supported ) {
      styles[ TRANSFORM ] = Shuffle._getItemTransformString( opts.point, opts.scale );
    } else {
      styles.left = opts.point.x;
      styles.top = opts.point.y;
    }
  
    return styles;
  };
  
  
  /**
   * Transitions an item in the grid
   *
   * @param {Object} opts options.
   * @param {jQuery} opts.$item jQuery object representing the current item.
   * @param {Point} opts.point A point object with the x and y coordinates.
   * @param {number} opts.scale Amount to scale the item.
   * @param {number} opts.opacity Opacity of the item.
   * @param {Function} opts.callback Complete function for the animation.
   * @param {Function} opts.callfront Function to call before transitioning.
   * @private
   */
  Shuffle.prototype._transition = function( opts ) {
    var styles = this._getStylesForTransition( opts );
    this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop );
  };
  
  
  Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) {
    // Transition end handler removes its listener.
    function handleTransitionEnd( evt ) {
      // Make sure this event handler has not bubbled up from a child.
      if ( evt.target === evt.currentTarget ) {
        $( evt.target ).off( TRANSITIONEND, handleTransitionEnd );
        callback();
      }
    }
  
    callfront();
  
    // Transitions are not set until shuffle has loaded to avoid the initial transition.
    if ( !this.initialized ) {
      $item.css( styles );
      callback();
      return;
    }
  
    // Use CSS Transforms if we have them
    if ( this.supported ) {
      $item.css( styles );
      $item.on( TRANSITIONEND, handleTransitionEnd );
  
    // Use jQuery to animate left/top
    } else {
      // Save the deferred object which jQuery returns.
      var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback );
      // Push the animation to the list of pending animations.
      this._animations.push( anim.promise() );
    }
  };
  
  
  /**
   * Execute the styles gathered in the style queue. This applies styles to elements,
   * triggering transitions.
   * @param {boolean} noLayout Whether to trigger a layout event.
   * @private
   */
  Shuffle.prototype._processStyleQueue = function( noLayout ) {
    var $transitions = $();
  
    // Iterate over the queue and keep track of ones that use transitions.
    each(this.styleQueue, function( transitionObj ) {
      if ( transitionObj.skipTransition ) {
        this._styleImmediately( transitionObj );
      } else {
        $transitions = $transitions.add( transitionObj.$item );
        this._transition( transitionObj );
      }
    }, this);
  
  
    if ( $transitions.length > 0 && this.initialized ) {
      // Set flag that shuffle is currently in motion.
      this.isTransitioning = true;
  
      if ( this.supported ) {
        this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished );
  
      // The _transition function appends a promise to the animations array.
      // When they're all complete, do things.
      } else {
        this._whenAnimationsDone( this._movementFinished );
      }
  
    // A call to layout happened, but none of the newly filtered items will
    // change position. Asynchronously fire the callback here.
    } else if ( !noLayout ) {
      defer( this._layoutEnd, this );
    }
  
    // Remove everything in the style queue
    this.styleQueue.length = 0;
  };
  
  
  /**
   * Apply styles without a transition.
   * @param {Object} opts Transitions options object.
   * @private
   */
  Shuffle.prototype._styleImmediately = function( opts ) {
    Shuffle._skipTransition(opts.$item[0], function() {
      opts.$item.css( this._getStylesForTransition( opts ) );
    }, this);
  };
  
  Shuffle.prototype._movementFinished = function() {
    this.isTransitioning = false;
    this._layoutEnd();
  };
  
  Shuffle.prototype._layoutEnd = function() {
    this._fire( Shuffle.EventType.LAYOUT );
  };
  
  Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) {
    // Add classes and set initial positions.
    this._initItems( $newItems );
  
    // Add transition to each item.
    this._setTransitions( $newItems );
  
    // Update the list of
    this.$items = this._getItems();
  
    // Shrink all items (without transitions).
    this._shrink( $newItems );
    each(this.styleQueue, function( transitionObj ) {
      transitionObj.skipTransition = true;
    });
  
    // Apply shrink positions, but do not cause a layout event.
    this._processStyleQueue( true );
  
    if ( addToEnd ) {
      this._addItemsToEnd( $newItems, isSequential );
    } else {
      this.shuffle( this.lastFilter );
    }
  };
  
  
  Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) {
    // Get ones that passed the current filter
    var $passed = this._filter( null, $newItems );
    var passed = $passed.get();
  
    // How many filtered elements?
    this._updateItemCount();
  
    this._layout( passed, true );
  
    if ( isSequential && this.supported ) {
      this._setSequentialDelay( passed );
    }
  
    this._revealAppended( passed );
  };
  
  
  /**
   * Triggers appended elements to fade in.
   * @param {ArrayLike.<Element>} $newFilteredItems Collection of elements.
   * @private
   */
  Shuffle.prototype._revealAppended = function( newFilteredItems ) {
    defer(function() {
      each(newFilteredItems, function( el ) {
        var $item = $( el );
        this._transition({
          $item: $item,
          opacity: 1,
          point: $item.data('point'),
          scale: DEFAULT_SCALE
        });
      }, this);
  
      this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() {
        $(newFilteredItems).css( TRANSITION_DELAY, '0ms' );
        this._movementFinished();
      });
    }, this, this.revealAppendedDelay);
  };
  
  
  /**
   * Execute a function when an event has been triggered for every item in a collection.
   * @param {jQuery} $collection Collection of elements.
   * @param {string} eventName Event to listen for.
   * @param {Function} callback Callback to execute when they're done.
   * @private
   */
  Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callback ) {
    var done = 0;
    var items = $collection.length;
    var self = this;
  
    function handleEventName( evt ) {
      if ( evt.target === evt.currentTarget ) {
        $( evt.target ).off( eventName, handleEventName );
        done++;
  
        // Execute callback if all items have emitted the correct event.
        if ( done === items ) {
          callback.call( self );
        }
      }
    }
  
    // Bind the event to all items.
    $collection.on( eventName, handleEventName );
  };
  
  
  /**
   * Execute a callback after jQuery `animate` for a collection has finished.
   * @param {Function} callback Callback to execute when they're done.
   * @private
   */
  Shuffle.prototype._whenAnimationsDone = function( callback ) {
    $.when.apply( null, this._animations ).always( $.proxy( function() {
      this._animations.length = 0;
      callback.call( this );
    }, this ));
  };
  
  
  /**
   * Public Methods
   */
  
  /**
   * The magic. This is what makes the plugin 'shuffle'
   * @param {string|Function} [category] Category to filter by. Can be a function
   * @param {Object} [sortObj] A sort object which can sort the filtered set
   */
  Shuffle.prototype.shuffle = function( category, sortObj ) {
    if ( !this.enabled || this.isTransitioning ) {
      return;
    }
  
    if ( !category ) {
      category = ALL_ITEMS;
    }
  
    this._filter( category );
  
    // How many filtered elements?
    this._updateItemCount();
  
    // Shrink each concealed item
    this._shrink();
  
    // Update transforms on .filtered elements so they will animate to their new positions
    this.sort( sortObj );
  };
  
  
  /**
   * Gets the .filtered elements, sorts them, and passes them to layout.
   * @param {Object} opts the options object for the sorted plugin
   */
  Shuffle.prototype.sort = function( opts ) {
    if ( this.enabled && !this.isTransitioning ) {
      this._resetCols();
  
      var sortOptions = opts || this.lastSort;
      var items = this._getFilteredItems().sorted( sortOptions );
  
      this._layout( items );
  
      this.lastSort = sortOptions;
    }
  };
  
  
  /**
   * Reposition everything.
   * @param {boolean} isOnlyLayout If true, column and gutter widths won't be
   *     recalculated.
   */
  Shuffle.prototype.update = function( isOnlyLayout ) {
    if ( this.enabled && !this.isTransitioning ) {
  
      if ( !isOnlyLayout ) {
        // Get updated colCount
        this._setColumns();
      }
  
      // Layout items
      this.sort();
    }
  };
  
  
  /**
   * Use this instead of `update()` if you don't need the columns and gutters updated
   * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations
   * could be off.
   */
  Shuffle.prototype.layout = function() {
    this.update( true );
  };
  
  
  /**
   * New items have been appended to shuffle. Fade them in sequentially
   * @param {jQuery} $newItems jQuery collection of new items
   * @param {boolean} [addToEnd=false] If true, new items will be added to the end / bottom
   *     of the items. If not true, items will be mixed in with the current sort order.
   * @param {boolean} [isSequential=true] If false, new items won't sequentially fade in
   */
  Shuffle.prototype.appended = function( $newItems, addToEnd, isSequential ) {
    this._addItems( $newItems, addToEnd === true, isSequential !== false );
  };
  
  
  /**
   * Disables shuffle from updating dimensions and layout on resize
   */
  Shuffle.prototype.disable = function() {
    this.enabled = false;
  };
  
  
  /**
   * Enables shuffle again
   * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters
   */
  Shuffle.prototype.enable = function( isUpdateLayout ) {
    this.enabled = true;
    if ( isUpdateLayout !== false ) {
      this.update();
    }
  };
  
  
  /**
   * Remove 1 or more shuffle items
   * @param {jQuery} $collection A jQuery object containing one or more element in shuffle
   * @return {Shuffle} The shuffle object
   */
  Shuffle.prototype.remove = function( $collection ) {
  
    // If this isn't a jquery object, exit
    if ( !$collection.length || !$collection.jquery ) {
      return;
    }
  
    function handleRemoved() {
      // Remove the collection in the callback
      $collection.remove();
  
      // Update things now that elements have been removed.
      this.$items = this._getItems();
      this._updateItemCount();
  
      this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] );
  
      // Let it get garbage collected
      $collection = null;
    }
  
    // Hide collection first.
    this._toggleFilterClasses( $(), $collection );
    this._shrink( $collection );
  
    this.sort();
  
    this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) );
  };
  
  
  /**
   * Destroys shuffle, removes events, styles, and classes
   */
  Shuffle.prototype.destroy = function() {
    // If there is more than one shuffle instance on the page,
    // removing the resize handler from the window would remove them
    // all. This is why a unique value is needed.
    $window.off('.' + this.unique);
  
    // Reset container styles
    this.$el
        .removeClass( SHUFFLE )
        .removeAttr('style')
        .removeData( SHUFFLE );
  
    // Reset individual item styles
    this.$items
        .removeAttr('style')
        .removeData('point')
        .removeData('scale')
        .removeClass([
          Shuffle.ClassName.CONCEALED,
          Shuffle.ClassName.FILTERED,
          Shuffle.ClassName.SHUFFLE_ITEM
        ].join(' '));
  
    // Null DOM references
    this.$items = null;
    this.$el = null;
    this.sizer = null;
    this.element = null;
  
    // Set a flag so if a debounced resize has been triggered,
    // it can first check if it is actually destroyed and not doing anything
    this.destroyed = true;
  };
  
  
  // Plugin definition
  $.fn.shuffle = function( opts ) {
    var args = Array.prototype.slice.call( arguments, 1 );
    return this.each(function() {
      var $this = $( this );
      var shuffle = $this.data( SHUFFLE );
  
      // If we don't have a stored shuffle, make a new one and save it
      if ( !shuffle ) {
        shuffle = new Shuffle( this, opts );
        $this.data( SHUFFLE, shuffle );
      } else if ( typeof opts === 'string' && shuffle[ opts ] ) {
        shuffle[ opts ].apply( shuffle, args );
      }
    });
  };
  
  
  // http://stackoverflow.com/a/962890/373422
  function randomize( array ) {
    var tmp, current;
    var top = array.length;
  
    if ( !top ) {
      return array;
    }
  
    while ( --top ) {
      current = Math.floor( Math.random() * (top + 1) );
      tmp = array[ current ];
      array[ current ] = array[ top ];
      array[ top ] = tmp;
    }
  
    return array;
  }
  
  
  // You can return `undefined` from the `by` function to revert to DOM order
  // This plugin does NOT return a jQuery object. It returns a plain array because
  // jQuery sorts everything in DOM order.
  $.fn.sorted = function(options) {
    var opts = $.extend({}, $.fn.sorted.defaults, options);
    var arr = this.get();
    var revert = false;
  
    if ( !arr.length ) {
      return [];
    }
  
    if ( opts.randomize ) {
      return randomize( arr );
    }
  
    // Sort the elements by the opts.by function.
    // If we don't have opts.by, default to DOM order
    if ( $.isFunction( opts.by ) ) {
      arr.sort(function(a, b) {
  
        // Exit early if we already know we want to revert
        if ( revert ) {
          return 0;
        }
  
        var valA = opts.by($(a));
        var valB = opts.by($(b));
  
        // If both values are undefined, use the DOM order
        if ( valA === undefined && valB === undefined ) {
          revert = true;
          return 0;
        }
  
        if ( valA < valB || valA === 'sortFirst' || valB === 'sortLast' ) {
          return -1;
        }
  
        if ( valA > valB || valA === 'sortLast' || valB === 'sortFirst' ) {
          return 1;
        }
  
        return 0;
      });
    }
  
    // Revert to the original array if necessary
    if ( revert ) {
      return this.get();
    }
  
    if ( opts.reverse ) {
      arr.reverse();
    }
  
    return arr;
  };
  
  
  $.fn.sorted.defaults = {
    reverse: false, // Use array.reverse() to reverse the results
    by: null, // Sorting function
    randomize: false // If true, this will skip the sorting and return a randomized order in the array
  };
  
  return Shuffle;
  
  });