/**
 * Isotope v1.5.26
 * An exquisite jQuery plugin for magical layouts
 * http://isotope.metafizzy.co
 *
 * Commercial use requires one-time purchase of a commercial license
 * http://isotope.metafizzy.co/docs/license.html
 *
 * Non-commercial use is licensed under the MIT License
 *
 * Copyright 2014 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 docElem = document.documentElement;
	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 = docElem.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 && 'webkitPerspective' in docElem.style ) {
				var $style = $('<style>@media (transform-3d),(-webkit-transform-3d)' +
						'{#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 );