/**
 * Isotope v1.3.110525
 * An exquisite jQuery plugin for magical layouts
 * http://isotope.metafizzy.co
 *
 * Commercial use requires one-time license fee
 * http://metafizzy.co/#licenses
 *
 * Copyright 2011 David DeSandro / Metafizzy
 */

(function (window, $, undefined)
{

    // ========================= getStyleProperty by kangax ===============================
    // http://perfectionkills.com/feature-testing-css-properties/

    var getStyleProperty = (function ()
    {

        var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'Ms'];

        function getStyleProperty(propName, element)
        {
            element = element || document.documentElement;
            var style = element.style,
          prefixed;

            // test standard property first
            if (typeof style[propName] === 'string')
            {
                return propName;
            }

            // capitalize
            propName = propName.charAt(0).toUpperCase() + propName.slice(1);

            // test vendor specific properties
            for (var i = 0, l = prefixes.length; i < l; i++)
            {
                prefixed = prefixes[i] + propName;
                if (typeof style[prefixed] === 'string')
                {
                    return prefixed;
                }
            }
        }

        return getStyleProperty;
    })();

    var transformProp = getStyleProperty('transform');

    // ========================= 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 docElement = document.documentElement,
      vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '),
      tests = [
        {
            name: 'csstransforms',
            getResult: function ()
            {
                return !!transformProp;
            }
        },
        {
            name: 'csstransforms3d',
            getResult: function ()
            {
                var test = !!getStyleProperty('perspective');
                // double check for Chrome's false positive
                if (test)
                {
                    var st = document.createElement('style'),
                  div = document.createElement('div'),
                  mq = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)';

                    st.textContent = mq + '{#modernizr{height:3px}}';
                    (document.head || document.getElementsByTagName('head')[0]).appendChild(st);
                    div.id = 'modernizr';
                    docElement.appendChild(div);

                    test = div.offsetHeight === 3;

                    st.parentNode.removeChild(st);
                    div.parentNode.removeChild(div);
                }
                return !!test;
            }
        },
        {
            name: 'csstransitions',
            getResult: function ()
            {
                return !!getStyleProperty('transitionProperty');
            }
        }
      ],

      i, len = tests.length
  ;

    if (window.Modernizr)
    {
        // if there's a previous Modernzir, check if there are necessary tests
        for (i = 0; i < len; i++)
        {
            var test = tests[i];
            if (!Modernizr.hasOwnProperty(test.name))
            {
                // if test hasn't been run, use addTest to run it
                Modernizr.addTest(test.name, test.getResult);
            }
        }
    } else
    {
        // or create new mini Modernizr that just has the 3 tests
        window.Modernizr = (function ()
        {

            var miniModernizr = {
                _version: '1.6ish: miniModernizr for Isotope'
            },
          classes = [],
          test, result, className;

            // Run through tests
            for (i = 0; i < len; i++)
            {
                test = tests[i];
                result = test.getResult();
                miniModernizr[test.name] = result;
                className = (result ? '' : 'no-') + test.name;
                classes.push(className);
            }

            // Add the new classes to the <html> element.
            docElement.className += ' ' + classes.join(' ');

            return miniModernizr;
        })();
    }



    // ========================= 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)
            {
                // 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)
            {

                // all this would be for public use
                // properly parsing strings and whatnot
                // 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];
            }
        };

    }



    /*
    * 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,
      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 ()
            {
                jQuery.event.handle.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)
    {
        this.element = $(element);

        this._create(options);
        this._init();
    };

    // styles of container element we want to keep track of
    var isoContainerStyles = ['overflow', 'position', 'width', 'height'];

    $.Isotope.settings = {
        resizable: true,
        layoutMode: 'masonry',
        containerClass: 'isotope',
        itemClass: 'isotope-item',
        hiddenClass: 'isotope-hidden',
        hiddenStyle: Modernizr.csstransforms && !$.browser.opera ?
      { opacity: 0, scale: 0.001} : // browsers support CSS transforms, not Opera
      {opacity: 0 }, // other browsers, including Opera
        visibleStyle: Modernizr.csstransforms && !$.browser.opera ?
      { opacity: 1, scale: 1} : // browsers support CSS transforms, not Opera
      {opacity: 1 },  // other browsers, including Opera
        animationEngine: $.browser.opera ? 'jquery' : '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(true, {}, $.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 = {};
            for (var i = 0, len = isoContainerStyles.length; i < len; i++)
            {
                var prop = isoContainerStyles[i];
                this.originalStyle[prop] = elemStyle[prop] || null;
            }

            this.element.css({
                overflow: 'hidden',
                position: 'relative'
            });

            this._updateAnimationEngine();
            this._updateUsingTransforms();

            // sorting
            var originalOrderSorter = {
                'original-order': function ($elem, instance)
                {
                    return instance.elemCount;
                }
            };

            this.options.getSortData = $.extend(this.options.getSortData, originalOrderSorter);

            // need to get atoms
            this.reloadItems();

            // get top left position of where the bricks should be
            var $cursor = $(document.createElement('div')).prependTo(this.element);
            this.offset = $cursor.position();
            $cursor.remove();

            // 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();
                });
            }

        },

        _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' };

            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);
                for (var optionName in opts)
                {
                    this._updateOption(optionName);
                }
            }
        },

        // ====================== updaters ====================== //
        // kind of like setters

        // trigger _updateOptionName if it exists
        _updateOption: function (optionName)
        {
            var updateOptionFn = '_update' + optionName.charAt(0).toUpperCase() + optionName.slice(1);
            if (this[updateOptionFn])
            {
                this[updateOptionFn]();
            }
        },

        _updateAnimationEngine: function ()
        {
            var animationEngine = this.options.animationEngine.toLowerCase().replace(/[ _\-]/g, '');
            // set applyStyleFnName
            switch (animationEngine)
            {
                case 'css':
                case 'none':
                    this.isUsingJQueryAnimation = false;
                    break;
                case 'jquery':
                    this.isUsingJQueryAnimation = true;
                    break;
                default: // best available
                    this.isUsingJQueryAnimation = !Modernizr.csstransitions;
            }

            this._updateUsingTransforms();
        },

        _updateTransformsEnabled: function ()
        {
            this._updateUsingTransforms();
        },

        _updateUsingTransforms: function ()
        {
            this.usingTransforms = this.options.transformsEnabled && Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;

            this.getPositionStyles = this.usingTransforms ? this._translate : this._positionAbs;
        },


        // ====================== Filtering ======================

        _filter: function ($atoms)
        {
            var $filteredAtoms,
          filter = this.options.filter === '' ? '*' : this.options.filter;

            if (!filter)
            {
                $filteredAtoms = $atoms;
            } else
            {
                var hiddenClass = this.options.hiddenClass,
            hiddenSelector = '.' + hiddenClass,
            $visibleAtoms = $atoms.not(hiddenSelector),
            $hiddenAtoms = $atoms.filter(hiddenSelector),
            $atomsToShow = $hiddenAtoms;

                $filteredAtoms = $atoms.filter(filter);

                if (filter !== '*')
                {
                    $atomsToShow = $hiddenAtoms.filter(filter);

                    var $atomsToHide = $visibleAtoms.not(filter).toggleClass(hiddenClass);
                    $atomsToHide.addClass(hiddenClass);
                    this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });
                }

                this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });
                $atomsToShow.removeClass(hiddenClass);
            }

            return $filteredAtoms;
        },

        // ====================== 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)
                {
                    sortData[key] = getSortData[key]($this, instance);
                }
                // apply sort data to element
                $.data(this, 'isotope-sort-data', sortData);
                if (isIncrementingElemCount)
                {
                    instance.elemCount++;
                }
            });
        },

        // 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 += this.offset.left;
            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 });
            }

            // 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;

            // process styleQueue
            $.each(this.styleQueue, function (i, obj)
            {
                obj.$el[styleFn](obj.style, animOpts);
            });

            // clear out queue for next time
            this.styleQueue = [];

            // provide $elems as context for the callback
            if (callback)
            {
                callback.call($elems);
            }

            this.isLaidOut = true;
        },


        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 ======================

        // 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
            // FIXME : this breaks shuffle order and returns to original order
            this.$allAtoms = this.$allAtoms.add($newAtoms);

            if (callback)
            {
                callback($newAtoms);
            }
        },

        // convienence method for adding elements properly to any layout
        insert: function ($content, callback)
        {
            this.element.append($content);

            var instance = this;
            this.addItems($content, function ($newAtoms)
            {
                var $filteredAtoms = instance._filter($newAtoms);
                instance.$filteredAtoms = instance.$filteredAtoms.add($filteredAtoms);
            });

            this._sort();
            this.reLayout(callback);

        },

        // convienence method for working with Infinite Scroll
        appended: function ($content, callback)
        {
            var instance = this;
            this.addItems($content, function ($newAtoms)
            {
                instance.$filteredAtoms = instance.$filteredAtoms.add($newAtoms);
                instance.layout($newAtoms, callback);
            });
        },

        // gathers all atoms
        reloadItems: function ()
        {
            this.$allAtoms = this._getAtoms(this.element.children());
        },

        // removes elements from Isotope widget
        remove: function ($content)
        {

            this.$allAtoms = this.$allAtoms.not($content);
            this.$filteredAtoms = this.$filteredAtoms.not($content);

            $content.remove();

        },

        _shuffleArray: function (array)
        {
            var tmp, current, i = array.length;

            if (i)
            {
                while (--i)
                {
                    current = ~ ~(Math.random() * (i + 1));
                    tmp = array[current];
                    array[current] = array[i];
                    array[i] = tmp;
                }
            }
            return array;
        },

        // HACKy should probably remove
        shuffle: function (callback)
        {
            this.options.sortBy = 'shuffle';

            this.$allAtoms = this._shuffleArray(this.$allAtoms);
            this.$filteredAtoms = this._filter(this.$allAtoms);

            this.reLayout(callback);
        },

        // destroys widget, returns elements and container back (close) to original style
        destroy: function ()
        {

            var usingTransforms = this.usingTransforms;

            this.$allAtoms
        .removeClass(this.options.hiddenClass + ' ' + this.options.itemClass)
        .each(function ()
        {
            this.style.position = null;
            this.style.top = null;
            this.style.left = null;
            this.style.opacity = null;
            if (usingTransforms)
            {
                this.style[transformProp] = null;
            }
        });

            // re-apply saved container styles
            var elemStyle = this.element[0].style;
            for (var i = 0, len = isoContainerStyles.length; i < len; i++)
            {
                var prop = isoContainerStyles[i];
                elemStyle[prop] = this.originalStyle[prop];
            }

            this.element
        .unbind('.isotope')
        .removeClass(this.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',
          UCSize = isRows ? 'Height' : 'Width',
          segmentsName = isRows ? 'rows' : 'cols',
          containerSize = this.element[size](),
          segments,
          segmentSize;

            // i.e. options.masonry && options.masonry.columnWidth
            segmentSize = this.options[namespace] && this.options[namespace][measure] ||
            // or use the size of the first item
                    this.$filteredAtoms['outer' + UCSize](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
            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 = ~ ~(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
            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 = ~ ~(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  ===============================
    // A fork of http://gist.github.com/268257 by Paul Irish

    // mit license. paul irish. 2010.
    // webkit fix from Oren Solomianik. thx!

    $.fn.imagesLoaded = function (callback)
    {
        var elems = this.find('img'),
        len = elems.length,
        _this = this;
        if (!elems.length)
        {
            callback.call(this);
        }

        elems.bind('load', function ()
        {
            if (--len <= 0)
            {
                callback.call(_this);
            }
        }).each(function ()
        {
            // cached images don't fire load sometimes, so we reset src.
            if (this.complete || this.complete === undefined)
            {
                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 = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
                this.src = src;
            }
        });

        return this;
    };



    // =======================  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)
    {
        if (typeof options === 'string')
        {
            // call method
            var args = Array.prototype.slice.call(arguments, 1);

            this.each(function ()
            {
                var instance = $.data(this, 'isotope');
                if (!instance)
                {
                    return $.error("cannot call methods on isotope prior to initialization; " +
            "attempted to call method '" + options + "'");
                }
                if (!$.isFunction(instance[options]) || options.charAt(0) === "_")
                {
                    return $.error("no such method '" + options + "' for isotope instance");
                }
                // 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();
                } else
                {
                    // initialize new instance
                    $.data(this, 'isotope', new $.Isotope(options, this));
                }
            });
        }
        // return jQuery object
        // so plugin methods do not have to
        return this;
    };

})(window, jQuery);
