/**

 * Isotope v1.5.25

 * An exquisite jQuery plugin for magical layouts

 * http://isotope.metafizzy.co

 *

 * Commercial use requires one-time license fee

 * http://metafizzy.co/#licenses

 *

 * Copyright 2012 David DeSandro / Metafizzy

 */



/*jshint asi: true, browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true */

/*global jQuery: false */



(function( window, $, undefined ){



  'use strict';



  // get global vars

  var document = window.document;

  var Modernizr = window.Modernizr;



  // helper function

  var capitalize = function( str ) {

    return str.charAt(0).toUpperCase() + str.slice(1);

  };



  // ========================= getStyleProperty by kangax ===============================

  // http://perfectionkills.com/feature-testing-css-properties/



  var prefixes = 'Moz Webkit O Ms'.split(' ');



  var getStyleProperty = function( propName ) {

    var style = document.documentElement.style,

        prefixed;



    // test standard property first

    if ( typeof style[propName] === 'string' ) {

      return propName;

    }



    // capitalize

    propName = capitalize( propName );



    // test vendor specific properties

    for ( var i=0, len = prefixes.length; i < len; i++ ) {

      prefixed = prefixes[i] + propName;

      if ( typeof style[ prefixed ] === 'string' ) {

        return prefixed;

      }

    }

  };



  var transformProp = getStyleProperty('transform'),

      transitionProp = getStyleProperty('transitionProperty');





  // ========================= miniModernizr ===============================

  // <3<3<3 and thanks to Faruk and Paul for doing the heavy lifting



  /*!

   * Modernizr v1.6ish: miniModernizr for Isotope

   * http://www.modernizr.com

   *

   * Developed by:

   * - Faruk Ates  http://farukat.es/

   * - Paul Irish  http://paulirish.com/

   *

   * Copyright (c) 2009-2010

   * Dual-licensed under the BSD or MIT licenses.

   * http://www.modernizr.com/license/

   */



  /*

   * This version whittles down the script just to check support for

   * CSS transitions, transforms, and 3D transforms.

  */



  var tests = {

    csstransforms: function() {

      return !!transformProp;

    },



    csstransforms3d: function() {

      var test = !!getStyleProperty('perspective');

      // double check for Chrome's false positive

      if ( test ) {

        var vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '),

            mediaQuery = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)',

            $style = $('<style>' + mediaQuery + '{#modernizr{height:3px}}' + '</style>')

                        .appendTo('head'),

            $div = $('<div id="modernizr" />').appendTo('html');



        test = $div.height() === 3;



        $div.remove();

        $style.remove();

      }

      return test;

    },



    csstransitions: function() {

      return !!transitionProp;

    }

  };



  var testName;



  if ( Modernizr ) {

    // if there's a previous Modernzir, check if there are necessary tests

    for ( testName in tests) {

      if ( !Modernizr.hasOwnProperty( testName ) ) {

        // if test hasn't been run, use addTest to run it

        Modernizr.addTest( testName, tests[ testName ] );

      }

    }

  } else {

    // or create new mini Modernizr that just has the 3 tests

    Modernizr = window.Modernizr = {

      _version : '1.6ish: miniModernizr for Isotope'

    };



    var classes = ' ';

    var result;



    // Run through tests

    for ( testName in tests) {

      result = tests[ testName ]();

      Modernizr[ testName ] = result;

      classes += ' ' + ( result ?  '' : 'no-' ) + testName;

    }



    // Add the new classes to the <html> element.

    $('html').addClass( classes );

  }





  // ========================= isoTransform ===============================



  /**

   *  provides hooks for .css({ scale: value, translate: [x, y] })

   *  Progressively enhanced CSS transforms

   *  Uses hardware accelerated 3D transforms for Safari

   *  or falls back to 2D transforms.

   */



  if ( Modernizr.csstransforms ) {



        // i.e. transformFnNotations.scale(0.5) >> 'scale3d( 0.5, 0.5, 1)'

    var transformFnNotations = Modernizr.csstransforms3d ?

      { // 3D transform functions

        translate : function ( position ) {

          return 'translate3d(' + position[0] + 'px, ' + position[1] + 'px, 0) ';

        },

        scale : function ( scale ) {

          return 'scale3d(' + scale + ', ' + scale + ', 1) ';

        }

      } :

      { // 2D transform functions

        translate : function ( position ) {

          return 'translate(' + position[0] + 'px, ' + position[1] + 'px) ';

        },

        scale : function ( scale ) {

          return 'scale(' + scale + ') ';

        }

      }

    ;



    var setIsoTransform = function ( elem, name, value ) {

          // unpack current transform data

      var data =  $.data( elem, 'isoTransform' ) || {},

          newData = {},

          fnName,

          transformObj = {},

          transformValue;



      // i.e. newData.scale = 0.5

      newData[ name ] = value;

      // extend new value over current data

      $.extend( data, newData );



      for ( fnName in data ) {

        transformValue = data[ fnName ];

        transformObj[ fnName ] = transformFnNotations[ fnName ]( transformValue );

      }



      // get proper order

      // ideally, we could loop through this give an array, but since we only have

      // a couple transforms we're keeping track of, we'll do it like so

      var translateFn = transformObj.translate || '',

          scaleFn = transformObj.scale || '',

          // sorting so translate always comes first

          valueFns = translateFn + scaleFn;



      // set data back in elem

      $.data( elem, 'isoTransform', data );



      // set name to vendor specific property

      elem.style[ transformProp ] = valueFns;

    };



    // ==================== scale ===================



    $.cssNumber.scale = true;



    $.cssHooks.scale = {

      set: function( elem, value ) {

        // uncomment this bit if you want to properly parse strings

        // if ( typeof value === 'string' ) {

        //   value = parseFloat( value );

        // }

        setIsoTransform( elem, 'scale', value );

      },

      get: function( elem, computed ) {

        var transform = $.data( elem, 'isoTransform' );

        return transform && transform.scale ? transform.scale : 1;

      }

    };



    $.fx.step.scale = function( fx ) {

      $.cssHooks.scale.set( fx.elem, fx.now+fx.unit );

    };





    // ==================== translate ===================



    $.cssNumber.translate = true;



    $.cssHooks.translate = {

      set: function( elem, value ) {



        // uncomment this bit if you want to properly parse strings

        // if ( typeof value === 'string' ) {

        //   value = value.split(' ');

        // }

        //

        // var i, val;

        // for ( i = 0; i < 2; i++ ) {

        //   val = value[i];

        //   if ( typeof val === 'string' ) {

        //     val = parseInt( val );

        //   }

        // }



        setIsoTransform( elem, 'translate', value );

      },



      get: function( elem, computed ) {

        var transform = $.data( elem, 'isoTransform' );

        return transform && transform.translate ? transform.translate : [ 0, 0 ];

      }

    };



  }



  // ========================= get transition-end event ===============================

  var transitionEndEvent, transitionDurProp;



  if ( Modernizr.csstransitions ) {

    transitionEndEvent = {

      WebkitTransitionProperty: 'webkitTransitionEnd',  // webkit

      MozTransitionProperty: 'transitionend',

      OTransitionProperty: 'oTransitionEnd otransitionend',

      transitionProperty: 'transitionend'

    }[ transitionProp ];



    transitionDurProp = getStyleProperty('transitionDuration');

  }



  // ========================= smartresize ===============================



  /*

   * smartresize: debounced resize event for jQuery

   *

   * latest version and complete README available on Github:

   * https://github.com/louisremi/jquery.smartresize.js

   *

   * Copyright 2011 @louis_remi

   * Licensed under the MIT license.

   */



  var $event = $.event,

      dispatchMethod = $.event.handle ? 'handle' : 'dispatch',

      resizeTimeout;



  $event.special.smartresize = {

    setup: function() {

      $(this).bind( "resize", $event.special.smartresize.handler );

    },

    teardown: function() {

      $(this).unbind( "resize", $event.special.smartresize.handler );

    },

    handler: function( event, execAsap ) {

      // Save the context

      var context = this,

          args = arguments;



      // set correct event type

      event.type = "smartresize";



      if ( resizeTimeout ) { clearTimeout( resizeTimeout ); }

      resizeTimeout = setTimeout(function() {

        $event[ dispatchMethod ].apply( context, args );

      }, execAsap === "execAsap"? 0 : 100 );

    }

  };



  $.fn.smartresize = function( fn ) {

    return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );

  };







// ========================= Isotope ===============================





  // our "Widget" object constructor

  $.Isotope = function( options, element, callback ){

    this.element = $( element );



    this._create( options );

    this._init( callback );

  };



  // styles of container element we want to keep track of

  var isoContainerStyles = [ 'width', 'height' ];



  var $window = $(window);



  $.Isotope.settings = {

    resizable: true,

    layoutMode : 'masonry',

    containerClass : 'isotope',

    itemClass : 'isotope-item',

    hiddenClass : 'isotope-hidden',

    hiddenStyle: { opacity: 0, scale: 0.001 },

    visibleStyle: { opacity: 1, scale: 1 },

    containerStyle: {

      position: 'relative',

      overflow: 'hidden'

    },

    animationEngine: 'best-available',

    animationOptions: {

      queue: false,

      duration: 800

    },

    sortBy : 'original-order',

    sortAscending : true,

    resizesContainer : true,

    transformsEnabled: true,

    itemPositionDataEnabled: false

  };



  $.Isotope.prototype = {



    // sets up widget

    _create : function( options ) {



      this.options = $.extend( {}, $.Isotope.settings, options );



      this.styleQueue = [];

      this.elemCount = 0;



      // get original styles in case we re-apply them in .destroy()

      var elemStyle = this.element[0].style;

      this.originalStyle = {};

      // keep track of container styles

      var containerStyles = isoContainerStyles.slice(0);

      for ( var prop in this.options.containerStyle ) {

        containerStyles.push( prop );

      }

      for ( var i=0, len = containerStyles.length; i < len; i++ ) {

        prop = containerStyles[i];

        this.originalStyle[ prop ] = elemStyle[ prop ] || '';

      }

      // apply container style from options

      this.element.css( this.options.containerStyle );



      this._updateAnimationEngine();

      this._updateUsingTransforms();



      // sorting

      var originalOrderSorter = {

        'original-order' : function( $elem, instance ) {

          instance.elemCount ++;

          return instance.elemCount;

        },

        random : function() {

          return Math.random();

        }

      };



      this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter );



      // need to get atoms

      this.reloadItems();



      // get top left position of where the bricks should be

      this.offset = {

        left: parseInt( ( this.element.css('padding-left') || 0 ), 10 ),

        top: parseInt( ( this.element.css('padding-top') || 0 ), 10 )

      };



      // add isotope class first time around

      var instance = this;

      setTimeout( function() {

        instance.element.addClass( instance.options.containerClass );

      }, 0 );



      // bind resize method

      if ( this.options.resizable ) {

        $window.bind( 'smartresize.isotope', function() {

          instance.resize();

        });

      }



      // dismiss all click events from hidden events

      this.element.delegate( '.' + this.options.hiddenClass, 'click', function(){

        return false;

      });



    },



    _getAtoms : function( $elems ) {

      var selector = this.options.itemSelector,

          // filter & find

          $atoms = selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems,

          // base style for atoms

          atomStyle = { position: 'absolute' };



      // filter out text nodes

      $atoms = $atoms.filter( function( i, atom ) {

        return atom.nodeType === 1;

      });



      if ( this.usingTransforms ) {

        atomStyle.left = 0;

        atomStyle.top = 0;

      }



      $atoms.css( atomStyle ).addClass( this.options.itemClass );



      this.updateSortData( $atoms, true );



      return $atoms;

    },



    // _init fires when your instance is first created

    // (from the constructor above), and when you

    // attempt to initialize the widget again (by the bridge)

    // after it has already been initialized.

    _init : function( callback ) {



      this.$filteredAtoms = this._filter( this.$allAtoms );

      this._sort();

      this.reLayout( callback );



    },



    option : function( opts ){

      // change options AFTER initialization:

      // signature: $('#foo').bar({ cool:false });

      if ( $.isPlainObject( opts ) ){

        this.options = $.extend( true, this.options, opts );



        // trigger _updateOptionName if it exists

        var updateOptionFn;

        for ( var optionName in opts ) {

          updateOptionFn = '_update' + capitalize( optionName );

          if ( this[ updateOptionFn ] ) {

            this[ updateOptionFn ]();

          }

        }

      }

    },



    // ====================== updaters ====================== //

    // kind of like setters



    _updateAnimationEngine : function() {

      var animationEngine = this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, '');

      var isUsingJQueryAnimation;

      // set applyStyleFnName

      switch ( animationEngine ) {

        case 'css' :

        case 'none' :

          isUsingJQueryAnimation = false;

          break;

        case 'jquery' :

          isUsingJQueryAnimation = true;

          break;

        default : // best available

          isUsingJQueryAnimation = !Modernizr.csstransitions;

      }

      this.isUsingJQueryAnimation = isUsingJQueryAnimation;

      this._updateUsingTransforms();

    },



    _updateTransformsEnabled : function() {

      this._updateUsingTransforms();

    },



    _updateUsingTransforms : function() {

      var usingTransforms = this.usingTransforms = this.options.transformsEnabled &&

        Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;



      // prevent scales when transforms are disabled

      if ( !usingTransforms ) {

        delete this.options.hiddenStyle.scale;

        delete this.options.visibleStyle.scale;

      }



      this.getPositionStyles = usingTransforms ? this._translate : this._positionAbs;

    },





    // ====================== Filtering ======================



    _filter : function( $atoms ) {

      var filter = this.options.filter === '' ? '*' : this.options.filter;



      if ( !filter ) {

        return $atoms;

      }



      var hiddenClass    = this.options.hiddenClass,

          hiddenSelector = '.' + hiddenClass,

          $hiddenAtoms   = $atoms.filter( hiddenSelector ),

          $atomsToShow   = $hiddenAtoms;



      if ( filter !== '*' ) {

        $atomsToShow = $hiddenAtoms.filter( filter );

        var $atomsToHide = $atoms.not( hiddenSelector ).not( filter ).addClass( hiddenClass );

        this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });

      }



      this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });

      $atomsToShow.removeClass( hiddenClass );



      return $atoms.filter( filter );

    },



    // ====================== Sorting ======================



    updateSortData : function( $atoms, isIncrementingElemCount ) {

      var instance = this,

          getSortData = this.options.getSortData,

          $this, sortData;

      $atoms.each(function(){

        $this = $(this);

        sortData = {};

        // get value for sort data based on fn( $elem ) passed in

        for ( var key in getSortData ) {

          if ( !isIncrementingElemCount && key === 'original-order' ) {

            // keep original order original

            sortData[ key ] = $.data( this, 'isotope-sort-data' )[ key ];

          } else {

            sortData[ key ] = getSortData[ key ]( $this, instance );

          }

        }

        // apply sort data to element

        $.data( this, 'isotope-sort-data', sortData );

      });

    },



    // used on all the filtered atoms

    _sort : function() {



      var sortBy = this.options.sortBy,

          getSorter = this._getSorter,

          sortDir = this.options.sortAscending ? 1 : -1,

          sortFn = function( alpha, beta ) {

            var a = getSorter( alpha, sortBy ),

                b = getSorter( beta, sortBy );

            // fall back to original order if data matches

            if ( a === b && sortBy !== 'original-order') {

              a = getSorter( alpha, 'original-order' );

              b = getSorter( beta, 'original-order' );

            }

            return ( ( a > b ) ? 1 : ( a < b ) ? -1 : 0 ) * sortDir;

          };



      this.$filteredAtoms.sort( sortFn );

    },



    _getSorter : function( elem, sortBy ) {

      return $.data( elem, 'isotope-sort-data' )[ sortBy ];

    },



    // ====================== Layout Helpers ======================



    _translate : function( x, y ) {

      return { translate : [ x, y ] };

    },



    _positionAbs : function( x, y ) {

      return { left: x, top: y };

    },



    _pushPosition : function( $elem, x, y ) {

      x = Math.round( x + this.offset.left );

      y = Math.round( y + this.offset.top );

      var position = this.getPositionStyles( x, y );

      this.styleQueue.push({ $el: $elem, style: position });

      if ( this.options.itemPositionDataEnabled ) {

        $elem.data('isotope-item-position', {x: x, y: y} );

      }

    },





    // ====================== General Layout ======================



    // used on collection of atoms (should be filtered, and sorted before )

    // accepts atoms-to-be-laid-out to start with

    layout : function( $elems, callback ) {



      var layoutMode = this.options.layoutMode;



      // layout logic

      this[ '_' +  layoutMode + 'Layout' ]( $elems );



      // set the size of the container

      if ( this.options.resizesContainer ) {

        var containerStyle = this[ '_' +  layoutMode + 'GetContainerSize' ]();

        this.styleQueue.push({ $el: this.element, style: containerStyle });

      }



      this._processStyleQueue( $elems, callback );



      this.isLaidOut = true;

    },



    _processStyleQueue : function( $elems, callback ) {

      // are we animating the layout arrangement?

      // use plugin-ish syntax for css or animate

      var styleFn = !this.isLaidOut ? 'css' : (

            this.isUsingJQueryAnimation ? 'animate' : 'css'

          ),

          animOpts = this.options.animationOptions,

          onLayout = this.options.onLayout,

          objStyleFn, processor,

          triggerCallbackNow, callbackFn;



      // default styleQueue processor, may be overwritten down below

      processor = function( i, obj ) {

        obj.$el[ styleFn ]( obj.style, animOpts );

      };



      if ( this._isInserting && this.isUsingJQueryAnimation ) {

        // if using styleQueue to insert items

        processor = function( i, obj ) {

          // only animate if it not being inserted

          objStyleFn = obj.$el.hasClass('no-transition') ? 'css' : styleFn;

          obj.$el[ objStyleFn ]( obj.style, animOpts );

        };



      } else if ( callback || onLayout || animOpts.complete ) {

        // has callback

        var isCallbackTriggered = false,

            // array of possible callbacks to trigger

            callbacks = [ callback, onLayout, animOpts.complete ],

            instance = this;

        triggerCallbackNow = true;

        // trigger callback only once

        callbackFn = function() {

          if ( isCallbackTriggered ) {

            return;

          }

          var hollaback;

          for (var i=0, len = callbacks.length; i < len; i++) {

            hollaback = callbacks[i];

            if ( typeof hollaback === 'function' ) {

              hollaback.call( instance.element, $elems, instance );

            }

          }

          isCallbackTriggered = true;

        };



        if ( this.isUsingJQueryAnimation && styleFn === 'animate' ) {

          // add callback to animation options

          animOpts.complete = callbackFn;

          triggerCallbackNow = false;



        } else if ( Modernizr.csstransitions ) {

          // detect if first item has transition

          var i = 0,

              firstItem = this.styleQueue[0],

              testElem = firstItem && firstItem.$el,

              styleObj;

          // get first non-empty jQ object

          while ( !testElem || !testElem.length ) {

            styleObj = this.styleQueue[ i++ ];

            // HACK: sometimes styleQueue[i] is undefined

            if ( !styleObj ) {

              return;

            }

            testElem = styleObj.$el;

          }

          // get transition duration of the first element in that object

          // yeah, this is inexact

          var duration = parseFloat( getComputedStyle( testElem[0] )[ transitionDurProp ] );

          if ( duration > 0 ) {

            processor = function( i, obj ) {

              obj.$el[ styleFn ]( obj.style, animOpts )

                // trigger callback at transition end

                .one( transitionEndEvent, callbackFn );

            };

            triggerCallbackNow = false;

          }

        }

      }



      // process styleQueue

      $.each( this.styleQueue, processor );



      if ( triggerCallbackNow ) {

        callbackFn();

      }



      // clear out queue for next time

      this.styleQueue = [];

    },





    resize : function() {

      if ( this[ '_' + this.options.layoutMode + 'ResizeChanged' ]() ) {

        this.reLayout();

      }

    },





    reLayout : function( callback ) {



      this[ '_' +  this.options.layoutMode + 'Reset' ]();

      this.layout( this.$filteredAtoms, callback );



    },



    // ====================== Convenience methods ======================



    // ====================== Adding items ======================



    // adds a jQuery object of items to a isotope container

    addItems : function( $content, callback ) {

      var $newAtoms = this._getAtoms( $content );

      // add new atoms to atoms pools

      this.$allAtoms = this.$allAtoms.add( $newAtoms );



      if ( callback ) {

        callback( $newAtoms );

      }

    },



    // convienence method for adding elements properly to any layout

    // positions items, hides them, then animates them back in <--- very sezzy

    insert : function( $content, callback ) {

      // position items

      this.element.append( $content );



      var instance = this;

      this.addItems( $content, function( $newAtoms ) {

        var $newFilteredAtoms = instance._filter( $newAtoms );

        instance._addHideAppended( $newFilteredAtoms );

        instance._sort();

        instance.reLayout();

        instance._revealAppended( $newFilteredAtoms, callback );

      });



    },



    // convienence method for working with Infinite Scroll

    appended : function( $content, callback ) {

      var instance = this;

      this.addItems( $content, function( $newAtoms ) {

        instance._addHideAppended( $newAtoms );

        instance.layout( $newAtoms );

        instance._revealAppended( $newAtoms, callback );

      });

    },



    // adds new atoms, then hides them before positioning

    _addHideAppended : function( $newAtoms ) {

      this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );

      $newAtoms.addClass('no-transition');



      this._isInserting = true;



      // apply hidden styles

      this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle });

    },



    // sets visible style on new atoms

    _revealAppended : function( $newAtoms, callback ) {

      var instance = this;

      // apply visible style after a sec

      setTimeout( function() {

        // enable animation

        $newAtoms.removeClass('no-transition');

        // reveal newly inserted filtered elements

        instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle });

        instance._isInserting = false;

        instance._processStyleQueue( $newAtoms, callback );

      }, 10 );

    },



    // gathers all atoms

    reloadItems : function() {

      this.$allAtoms = this._getAtoms( this.element.children() );

    },



    // removes elements from Isotope widget

    remove: function( $content, callback ) {

      // remove elements immediately from Isotope instance

      this.$allAtoms = this.$allAtoms.not( $content );

      this.$filteredAtoms = this.$filteredAtoms.not( $content );

      // remove() as a callback, for after transition / animation

      var instance = this;

      var removeContent = function() {

        $content.remove();

        if ( callback ) {

          callback.call( instance.element );

        }

      };



      if ( $content.filter( ':not(.' + this.options.hiddenClass + ')' ).length ) {

        // if any non-hidden content needs to be removed

        this.styleQueue.push({ $el: $content, style: this.options.hiddenStyle });

        this._sort();

        this.reLayout( removeContent );

      } else {

        // remove it now

        removeContent();

      }



    },



    shuffle : function( callback ) {

      this.updateSortData( this.$allAtoms );

      this.options.sortBy = 'random';

      this._sort();

      this.reLayout( callback );

    },



    // destroys widget, returns elements and container back (close) to original style

    destroy : function() {



      var usingTransforms = this.usingTransforms;

      var options = this.options;



      this.$allAtoms

        .removeClass( options.hiddenClass + ' ' + options.itemClass )

        .each(function(){

          var style = this.style;

          style.position = '';

          style.top = '';

          style.left = '';

          style.opacity = '';

          if ( usingTransforms ) {

            style[ transformProp ] = '';

          }

        });



      // re-apply saved container styles

      var elemStyle = this.element[0].style;

      for ( var prop in this.originalStyle ) {

        elemStyle[ prop ] = this.originalStyle[ prop ];

      }



      this.element

        .unbind('.isotope')

        .undelegate( '.' + options.hiddenClass, 'click' )

        .removeClass( options.containerClass )

        .removeData('isotope');



      $window.unbind('.isotope');



    },





    // ====================== LAYOUTS ======================



    // calculates number of rows or columns

    // requires columnWidth or rowHeight to be set on namespaced object

    // i.e. this.masonry.columnWidth = 200

    _getSegments : function( isRows ) {

      var namespace = this.options.layoutMode,

          measure  = isRows ? 'rowHeight' : 'columnWidth',

          size     = isRows ? 'height' : 'width',

          segmentsName = isRows ? 'rows' : 'cols',

          containerSize = this.element[ size ](),

          segments,

                    // i.e. options.masonry && options.masonry.columnWidth

          segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] ||

                    // or use the size of the first item, i.e. outerWidth

                    this.$filteredAtoms[ 'outer' + capitalize(size) ](true) ||

                    // if there's no items, use size of container

                    containerSize;



      segments = Math.floor( containerSize / segmentSize );

      segments = Math.max( segments, 1 );



      // i.e. this.masonry.cols = ....

      this[ namespace ][ segmentsName ] = segments;

      // i.e. this.masonry.columnWidth = ...

      this[ namespace ][ measure ] = segmentSize;



    },



    _checkIfSegmentsChanged : function( isRows ) {

      var namespace = this.options.layoutMode,

          segmentsName = isRows ? 'rows' : 'cols',

          prevSegments = this[ namespace ][ segmentsName ];

      // update cols/rows

      this._getSegments( isRows );

      // return if updated cols/rows is not equal to previous

      return ( this[ namespace ][ segmentsName ] !== prevSegments );

    },



    // ====================== Masonry ======================



    _masonryReset : function() {

      // layout-specific props

      this.masonry = {};

      // FIXME shouldn't have to call this again

      this._getSegments();

      var i = this.masonry.cols;

      this.masonry.colYs = [];

      while (i--) {

        this.masonry.colYs.push( 0 );

      }

    },



    _masonryLayout : function( $elems ) {

      var instance = this,

          props = instance.masonry;

      $elems.each(function(){

        var $this  = $(this),

            //how many columns does this brick span

            colSpan = Math.ceil( $this.outerWidth(true) / props.columnWidth );

        colSpan = Math.min( colSpan, props.cols );



        if ( colSpan === 1 ) {

          // if brick spans only one column, just like singleMode

          instance._masonryPlaceBrick( $this, props.colYs );

        } else {

          // brick spans more than one column

          // how many different places could this brick fit horizontally

          var groupCount = props.cols + 1 - colSpan,

              groupY = [],

              groupColY,

              i;



          // for each group potential horizontal position

          for ( i=0; i < groupCount; i++ ) {

            // make an array of colY values for that one group

            groupColY = props.colYs.slice( i, i+colSpan );

            // and get the max value of the array

            groupY[i] = Math.max.apply( Math, groupColY );

          }



          instance._masonryPlaceBrick( $this, groupY );

        }

      });

    },



    // worker method that places brick in the columnSet

    //   with the the minY

    _masonryPlaceBrick : function( $brick, setY ) {

      // get the minimum Y value from the columns

      var minimumY = Math.min.apply( Math, setY ),

          shortCol = 0;



      // Find index of short column, the first from the left

      for (var i=0, len = setY.length; i < len; i++) {

        if ( setY[i] === minimumY ) {

          shortCol = i;

          break;

        }

      }



      // position the brick

      var x = this.masonry.columnWidth * shortCol,

          y = minimumY;

      this._pushPosition( $brick, x, y );



      // apply setHeight to necessary columns

      var setHeight = minimumY + $brick.outerHeight(true),

          setSpan = this.masonry.cols + 1 - len;

      for ( i=0; i < setSpan; i++ ) {

        this.masonry.colYs[ shortCol + i ] = setHeight;

      }



    },



    _masonryGetContainerSize : function() {

      var containerHeight = Math.max.apply( Math, this.masonry.colYs );

      return { height: containerHeight };

    },



    _masonryResizeChanged : function() {

      return this._checkIfSegmentsChanged();

    },



    // ====================== fitRows ======================



    _fitRowsReset : function() {

      this.fitRows = {

        x : 0,

        y : 0,

        height : 0

      };

    },



    _fitRowsLayout : function( $elems ) {

      var instance = this,

          containerWidth = this.element.width(),

          props = this.fitRows;



      $elems.each( function() {

        var $this = $(this),

            atomW = $this.outerWidth(true),

            atomH = $this.outerHeight(true);



        if ( props.x !== 0 && atomW + props.x > containerWidth ) {

          // if this element cannot fit in the current row

          props.x = 0;

          props.y = props.height;

        }



        // position the atom

        instance._pushPosition( $this, props.x, props.y );



        props.height = Math.max( props.y + atomH, props.height );

        props.x += atomW;



      });

    },



    _fitRowsGetContainerSize : function () {

      return { height : this.fitRows.height };

    },



    _fitRowsResizeChanged : function() {

      return true;

    },





    // ====================== cellsByRow ======================



    _cellsByRowReset : function() {

      this.cellsByRow = {

        index : 0

      };

      // get this.cellsByRow.columnWidth

      this._getSegments();

      // get this.cellsByRow.rowHeight

      this._getSegments(true);

    },



    _cellsByRowLayout : function( $elems ) {

      var instance = this,

          props = this.cellsByRow;

      $elems.each( function(){

        var $this = $(this),

            col = props.index % props.cols,

            row = Math.floor( props.index / props.cols ),

            x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2,

            y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2;

        instance._pushPosition( $this, x, y );

        props.index ++;

      });

    },



    _cellsByRowGetContainerSize : function() {

      return { height : Math.ceil( this.$filteredAtoms.length / this.cellsByRow.cols ) * this.cellsByRow.rowHeight + this.offset.top };

    },



    _cellsByRowResizeChanged : function() {

      return this._checkIfSegmentsChanged();

    },





    // ====================== straightDown ======================



    _straightDownReset : function() {

      this.straightDown = {

        y : 0

      };

    },



    _straightDownLayout : function( $elems ) {

      var instance = this;

      $elems.each( function( i ){

        var $this = $(this);

        instance._pushPosition( $this, 0, instance.straightDown.y );

        instance.straightDown.y += $this.outerHeight(true);

      });

    },



    _straightDownGetContainerSize : function() {

      return { height : this.straightDown.y };

    },



    _straightDownResizeChanged : function() {

      return true;

    },





    // ====================== masonryHorizontal ======================



    _masonryHorizontalReset : function() {

      // layout-specific props

      this.masonryHorizontal = {};

      // FIXME shouldn't have to call this again

      this._getSegments( true );

      var i = this.masonryHorizontal.rows;

      this.masonryHorizontal.rowXs = [];

      while (i--) {

        this.masonryHorizontal.rowXs.push( 0 );

      }

    },



    _masonryHorizontalLayout : function( $elems ) {

      var instance = this,

          props = instance.masonryHorizontal;

      $elems.each(function(){

        var $this  = $(this),

            //how many rows does this brick span

            rowSpan = Math.ceil( $this.outerHeight(true) / props.rowHeight );

        rowSpan = Math.min( rowSpan, props.rows );



        if ( rowSpan === 1 ) {

          // if brick spans only one column, just like singleMode

          instance._masonryHorizontalPlaceBrick( $this, props.rowXs );

        } else {

          // brick spans more than one row

          // how many different places could this brick fit horizontally

          var groupCount = props.rows + 1 - rowSpan,

              groupX = [],

              groupRowX, i;



          // for each group potential horizontal position

          for ( i=0; i < groupCount; i++ ) {

            // make an array of colY values for that one group

            groupRowX = props.rowXs.slice( i, i+rowSpan );

            // and get the max value of the array

            groupX[i] = Math.max.apply( Math, groupRowX );

          }



          instance._masonryHorizontalPlaceBrick( $this, groupX );

        }

      });

    },



    _masonryHorizontalPlaceBrick : function( $brick, setX ) {

      // get the minimum Y value from the columns

      var minimumX  = Math.min.apply( Math, setX ),

          smallRow  = 0;

      // Find index of smallest row, the first from the top

      for (var i=0, len = setX.length; i < len; i++) {

        if ( setX[i] === minimumX ) {

          smallRow = i;

          break;

        }

      }



      // position the brick

      var x = minimumX,

          y = this.masonryHorizontal.rowHeight * smallRow;

      this._pushPosition( $brick, x, y );



      // apply setHeight to necessary columns

      var setWidth = minimumX + $brick.outerWidth(true),

          setSpan = this.masonryHorizontal.rows + 1 - len;

      for ( i=0; i < setSpan; i++ ) {

        this.masonryHorizontal.rowXs[ smallRow + i ] = setWidth;

      }

    },



    _masonryHorizontalGetContainerSize : function() {

      var containerWidth = Math.max.apply( Math, this.masonryHorizontal.rowXs );

      return { width: containerWidth };

    },



    _masonryHorizontalResizeChanged : function() {

      return this._checkIfSegmentsChanged(true);

    },





    // ====================== fitColumns ======================



    _fitColumnsReset : function() {

      this.fitColumns = {

        x : 0,

        y : 0,

        width : 0

      };

    },



    _fitColumnsLayout : function( $elems ) {

      var instance = this,

          containerHeight = this.element.height(),

          props = this.fitColumns;

      $elems.each( function() {

        var $this = $(this),

            atomW = $this.outerWidth(true),

            atomH = $this.outerHeight(true);



        if ( props.y !== 0 && atomH + props.y > containerHeight ) {

          // if this element cannot fit in the current column

          props.x = props.width;

          props.y = 0;

        }



        // position the atom

        instance._pushPosition( $this, props.x, props.y );



        props.width = Math.max( props.x + atomW, props.width );

        props.y += atomH;



      });

    },



    _fitColumnsGetContainerSize : function () {

      return { width : this.fitColumns.width };

    },



    _fitColumnsResizeChanged : function() {

      return true;

    },







    // ====================== cellsByColumn ======================



    _cellsByColumnReset : function() {

      this.cellsByColumn = {

        index : 0

      };

      // get this.cellsByColumn.columnWidth

      this._getSegments();

      // get this.cellsByColumn.rowHeight

      this._getSegments(true);

    },



    _cellsByColumnLayout : function( $elems ) {

      var instance = this,

          props = this.cellsByColumn;

      $elems.each( function(){

        var $this = $(this),

            col = Math.floor( props.index / props.rows ),

            row = props.index % props.rows,

            x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2,

            y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2;

        instance._pushPosition( $this, x, y );

        props.index ++;

      });

    },



    _cellsByColumnGetContainerSize : function() {

      return { width : Math.ceil( this.$filteredAtoms.length / this.cellsByColumn.rows ) * this.cellsByColumn.columnWidth };

    },



    _cellsByColumnResizeChanged : function() {

      return this._checkIfSegmentsChanged(true);

    },



    // ====================== straightAcross ======================



    _straightAcrossReset : function() {

      this.straightAcross = {

        x : 0

      };

    },



    _straightAcrossLayout : function( $elems ) {

      var instance = this;

      $elems.each( function( i ){

        var $this = $(this);

        instance._pushPosition( $this, instance.straightAcross.x, 0 );

        instance.straightAcross.x += $this.outerWidth(true);

      });

    },



    _straightAcrossGetContainerSize : function() {

      return { width : this.straightAcross.x };

    },



    _straightAcrossResizeChanged : function() {

      return true;

    }



  };





  // ======================= imagesLoaded Plugin ===============================

  /*!

   * jQuery imagesLoaded plugin v1.1.0

   * http://github.com/desandro/imagesloaded

   *

   * MIT License. by Paul Irish et al.

   */





  // $('#my-container').imagesLoaded(myFunction)

  // or

  // $('img').imagesLoaded(myFunction)



  // execute a callback when all images have loaded.

  // needed because .load() doesn't work on cached images



  // callback function gets image collection as argument

  //  `this` is the container



  $.fn.imagesLoaded = function( callback ) {

    var $this = this,

        $images = $this.find('img').add( $this.filter('img') ),

        len = $images.length,

        blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==',

        loaded = [];



    function triggerCallback() {

      callback.call( $this, $images );

    }



    function imgLoaded( event ) {

      var img = event.target;

      if ( img.src !== blank && $.inArray( img, loaded ) === -1 ){

        loaded.push( img );

        if ( --len <= 0 ){

          setTimeout( triggerCallback );

          $images.unbind( '.imagesLoaded', imgLoaded );

        }

      }

    }



    // if no images, trigger immediately

    if ( !len ) {

      triggerCallback();

    }



    $images.bind( 'load.imagesLoaded error.imagesLoaded',  imgLoaded ).each( function() {

      // cached images don't fire load sometimes, so we reset src.

      var src = this.src;

      // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f

      // data uri bypasses webkit log warning (thx doug jones)

      this.src = blank;

      this.src = src;

    });



    return $this;

  };





  // helper function for logging errors

  // $.error breaks jQuery chaining

  var logError = function( message ) {

    if ( window.console ) {

      window.console.error( message );

    }

  };



  // =======================  Plugin bridge  ===============================

  // leverages data method to either create or return $.Isotope constructor

  // A bit from jQuery UI

  //   https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js

  // A bit from jcarousel

  //   https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js



  $.fn.isotope = function( options, callback ) {

    if ( typeof options === 'string' ) {

      // call method

      var args = Array.prototype.slice.call( arguments, 1 );



      this.each(function(){

        var instance = $.data( this, 'isotope' );

        if ( !instance ) {

          logError( "cannot call methods on isotope prior to initialization; " +

              "attempted to call method '" + options + "'" );

          return;

        }

        if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {

          logError( "no such method '" + options + "' for isotope instance" );

          return;

        }

        // apply method

        instance[ options ].apply( instance, args );

      });

    } else {

      this.each(function() {

        var instance = $.data( this, 'isotope' );

        if ( instance ) {

          // apply options & init

          instance.option( options );

          instance._init( callback );

        } else {

          // initialize new instance

          $.data( this, 'isotope', new $.Isotope( options, this, callback ) );

        }

      });

    }

    // return jQuery object

    // so plugin methods do not have to

    return this;

  };



})( window, jQuery );