/*!
 * Packery layout mode PACKAGED v2.0.1
 * sub-classes Packery
 */

/**
 * Rect
 * low-level utility class for basic geometry
 */

(function (window, factory) {
	window.Packery = window.Packery || {};
	window.Packery.Rect = factory();
})(window, function factory() {
	// -------------------------- Rect -------------------------- //

	function Rect(props) {
		// extend properties from defaults
		for (var prop in Rect.defaults) {
			this[prop] = Rect.defaults[prop];
		}

		for (prop in props) {
			this[prop] = props[prop];
		}
	}

	Rect.defaults = {
		x: 0,
		y: 0,
		width: 0,
		height: 0,
	};

	var proto = Rect.prototype;

	/**
	 * Determines whether or not this rectangle wholly encloses another rectangle or point.
	 * @param {Rect} rect
	 * @returns {Boolean}
	 **/
	proto.contains = function (rect) {
		// points don't have width or height
		var otherWidth = rect.width || 0;
		var otherHeight = rect.height || 0;
		return (
			this.x <= rect.x &&
			this.y <= rect.y &&
			this.x + this.width >= rect.x + otherWidth &&
			this.y + this.height >= rect.y + otherHeight
		);
	};

	/**
	 * Determines whether or not the rectangle intersects with another.
	 * @param {Rect} rect
	 * @returns {Boolean}
	 **/
	proto.overlaps = function (rect) {
		var thisRight = this.x + this.width;
		var thisBottom = this.y + this.height;
		var rectRight = rect.x + rect.width;
		var rectBottom = rect.y + rect.height;

		// http://stackoverflow.com/a/306332
		return (
			this.x < rectRight &&
			thisRight > rect.x &&
			this.y < rectBottom &&
			thisBottom > rect.y
		);
	};

	/**
	 * @param {Rect} rect - the overlapping rect
	 * @returns {Array} freeRects - rects representing the area around the rect
	 **/
	proto.getMaximalFreeRects = function (rect) {
		// if no intersection, return false
		if (!this.overlaps(rect)) {
			return false;
		}

		var freeRects = [];
		var freeRect;

		var thisRight = this.x + this.width;
		var thisBottom = this.y + this.height;
		var rectRight = rect.x + rect.width;
		var rectBottom = rect.y + rect.height;

		// top
		if (this.y < rect.y) {
			freeRect = new Rect({
				x: this.x,
				y: this.y,
				width: this.width,
				height: rect.y - this.y,
			});
			freeRects.push(freeRect);
		}

		// right
		if (thisRight > rectRight) {
			freeRect = new Rect({
				x: rectRight,
				y: this.y,
				width: thisRight - rectRight,
				height: this.height,
			});
			freeRects.push(freeRect);
		}

		// bottom
		if (thisBottom > rectBottom) {
			freeRect = new Rect({
				x: this.x,
				y: rectBottom,
				width: this.width,
				height: thisBottom - rectBottom,
			});
			freeRects.push(freeRect);
		}

		// left
		if (this.x < rect.x) {
			freeRect = new Rect({
				x: this.x,
				y: this.y,
				width: rect.x - this.x,
				height: this.height,
			});
			freeRects.push(freeRect);
		}

		return freeRects;
	};

	proto.canFit = function (rect) {
		return this.width >= rect.width && this.height >= rect.height;
	};

	return Rect;
});

/**
 * Packer
 * bin-packing algorithm
 */

(function (window, factory) {
	// browser global
	var Packery = (window.Packery = window.Packery || {});
	Packery.Packer = factory(Packery.Rect);
})(window, function factory(Rect) {
	// -------------------------- Packer -------------------------- //

	/**
	 * @param {Number} width
	 * @param {Number} height
	 * @param {String} sortDirection
	 *   topLeft for vertical, leftTop for horizontal
	 */
	function Packer(width, height, sortDirection) {
		this.width = width || 0;
		this.height = height || 0;
		this.sortDirection = sortDirection || 'downwardLeftToRight';

		this.reset();
	}

	var proto = Packer.prototype;

	proto.reset = function () {
		this.spaces = [];

		var initialSpace = new Rect({
			x: 0,
			y: 0,
			width: this.width,
			height: this.height,
		});

		this.spaces.push(initialSpace);
		// set sorter
		this.sorter =
			sorters[this.sortDirection] || sorters.downwardLeftToRight;
	};

	// change x and y of rect to fit with in Packer's available spaces
	proto.pack = function (rect) {
		for (var i = 0; i < this.spaces.length; i++) {
			var space = this.spaces[i];
			if (space.canFit(rect)) {
				this.placeInSpace(rect, space);
				break;
			}
		}
	};

	proto.columnPack = function (rect) {
		for (var i = 0; i < this.spaces.length; i++) {
			var space = this.spaces[i];
			var canFitInSpaceColumn =
				space.x <= rect.x &&
				space.x + space.width >= rect.x + rect.width &&
				space.height >= rect.height - 0.01; // fudge number for rounding error
			if (canFitInSpaceColumn) {
				rect.y = space.y;
				this.placed(rect);
				break;
			}
		}
	};

	proto.rowPack = function (rect) {
		for (var i = 0; i < this.spaces.length; i++) {
			var space = this.spaces[i];
			var canFitInSpaceRow =
				space.y <= rect.y &&
				space.y + space.height >= rect.y + rect.height &&
				space.width >= rect.width - 0.01; // fudge number for rounding error
			if (canFitInSpaceRow) {
				rect.x = space.x;
				this.placed(rect);
				break;
			}
		}
	};

	proto.placeInSpace = function (rect, space) {
		// place rect in space
		rect.x = space.x;
		rect.y = space.y;

		this.placed(rect);
	};

	// update spaces with placed rect
	proto.placed = function (rect) {
		// update spaces
		var revisedSpaces = [];
		for (var i = 0; i < this.spaces.length; i++) {
			var space = this.spaces[i];
			var newSpaces = space.getMaximalFreeRects(rect);
			// add either the original space or the new spaces to the revised spaces
			if (newSpaces) {
				revisedSpaces.push.apply(revisedSpaces, newSpaces);
			} else {
				revisedSpaces.push(space);
			}
		}

		this.spaces = revisedSpaces;

		this.mergeSortSpaces();
	};

	proto.mergeSortSpaces = function () {
		// remove redundant spaces
		Packer.mergeRects(this.spaces);
		this.spaces.sort(this.sorter);
	};

	// add a space back
	proto.addSpace = function (rect) {
		this.spaces.push(rect);
		this.mergeSortSpaces();
	};

	// -------------------------- utility functions -------------------------- //

	/**
	 * Remove redundant rectangle from array of rectangles
	 * @param {Array} rects: an array of Rects
	 * @returns {Array} rects: an array of Rects
	 **/
	Packer.mergeRects = function (rects) {
		var i = 0;
		var rect = rects[i];

		rectLoop: while (rect) {
			var j = 0;
			var compareRect = rects[i + j];

			while (compareRect) {
				if (compareRect == rect) {
					j++; // next
				} else if (compareRect.contains(rect)) {
					// remove rect
					rects.splice(i, 1);
					rect = rects[i]; // set next rect
					continue rectLoop; // bail on compareLoop
				} else if (rect.contains(compareRect)) {
					// remove compareRect
					rects.splice(i + j, 1);
				} else {
					j++;
				}
				compareRect = rects[i + j]; // set next compareRect
			}
			i++;
			rect = rects[i];
		}

		return rects;
	};

	// -------------------------- sorters -------------------------- //

	// functions for sorting rects in order
	var sorters = {
		// top down, then left to right
		downwardLeftToRight: function (a, b) {
			return a.y - b.y || a.x - b.x;
		},
		// left to right, then top down
		rightwardTopToBottom: function (a, b) {
			return a.x - b.x || a.y - b.y;
		},
	};

	// --------------------------  -------------------------- //

	return Packer;
});

/**
 * Packery Item Element
 **/

(function (window, factory) {
	// browser global
	window.Packery.Item = factory(window.Outlayer, window.Packery.Rect);
})(window, function factory(Outlayer, Rect) {
	// -------------------------- Item -------------------------- //

	var docElemStyle = document.documentElement.style;

	var transformProperty =
		typeof docElemStyle.transform == 'string'
			? 'transform'
			: 'WebkitTransform';

	// sub-class Item
	var Item = function PackeryItem() {
		Outlayer.Item.apply(this, arguments);
	};

	var proto = (Item.prototype = Object.create(Outlayer.Item.prototype));

	var __create = proto._create;
	proto._create = function () {
		// call default _create logic
		__create.call(this);
		this.rect = new Rect();
	};

	var _moveTo = proto.moveTo;
	proto.moveTo = function (x, y) {
		// don't shift 1px while dragging
		var dx = Math.abs(this.position.x - x);
		var dy = Math.abs(this.position.y - y);

		var canHackGoTo =
			this.layout.dragItemCount &&
			!this.isPlacing &&
			!this.isTransitioning &&
			dx < 1 &&
			dy < 1;
		if (canHackGoTo) {
			this.goTo(x, y);
			return;
		}
		_moveTo.apply(this, arguments);
	};

	// -------------------------- placing -------------------------- //

	proto.enablePlacing = function () {
		this.removeTransitionStyles();
		// remove transform property from transition
		if (this.isTransitioning && transformProperty) {
			this.element.style[transformProperty] = 'none';
		}
		this.isTransitioning = false;
		this.getSize();
		this.layout._setRectSize(this.element, this.rect);
		this.isPlacing = true;
	};

	proto.disablePlacing = function () {
		this.isPlacing = false;
	};

	// -----  ----- //

	// remove element from DOM
	proto.removeElem = function () {
		this.element.parentNode.removeChild(this.element);
		// add space back to packer
		this.layout.packer.addSpace(this.rect);
		this.emitEvent('remove', [this]);
	};

	// ----- dropPlaceholder ----- //

	proto.showDropPlaceholder = function () {
		var dropPlaceholder = this.dropPlaceholder;
		if (!dropPlaceholder) {
			// create dropPlaceholder
			dropPlaceholder = this.dropPlaceholder =
				document.createElement('div');
			dropPlaceholder.className = 'packery-drop-placeholder';
			dropPlaceholder.style.position = 'absolute';
		}

		dropPlaceholder.style.width = this.size.width + 'px';
		dropPlaceholder.style.height = this.size.height + 'px';
		this.positionDropPlaceholder();
		this.layout.element.appendChild(dropPlaceholder);
	};

	proto.positionDropPlaceholder = function () {
		this.dropPlaceholder.style[transformProperty] =
			'translate(' + this.rect.x + 'px, ' + this.rect.y + 'px)';
	};

	proto.hideDropPlaceholder = function () {
		this.layout.element.removeChild(this.dropPlaceholder);
	};

	// -----  ----- //

	return Item;
});

/*!
 * Packery v2.0.0
 * Gapless, draggable grid layouts
 *
 * Licensed GPLv3 for open source use
 * or Packery Commercial License for commercial use
 *
 * http://packery.metafizzy.co
 * Copyright 2016 Metafizzy
 */

(function (window, factory) {
	// browser global
	window.Packery = factory(
		window.getSize,
		window.Outlayer,
		window.Packery.Rect,
		window.Packery.Packer,
		window.Packery.Item
	);
})(window, function factory(getSize, Outlayer, Rect, Packer, Item) {
	// ----- Rect ----- //

	// allow for pixel rounding errors IE8-IE11 & Firefox; #227
	Rect.prototype.canFit = function (rect) {
		return this.width >= rect.width - 1 && this.height >= rect.height - 1;
	};

	// -------------------------- Packery -------------------------- //

	// create an Outlayer layout class
	var Packery = Outlayer.create('packery');
	Packery.Item = Item;

	var proto = Packery.prototype;

	proto._create = function () {
		// call super
		Outlayer.prototype._create.call(this);

		// initial properties
		this.packer = new Packer();
		// packer for drop targets
		this.shiftPacker = new Packer();
		this.isEnabled = true;

		this.dragItemCount = 0;

		// create drag handlers
		var _this = this;
		this.handleDraggabilly = {
			dragStart: function () {
				_this.itemDragStart(this.element);
			},
			dragMove: function () {
				_this.itemDragMove(
					this.element,
					this.position.x,
					this.position.y
				);
			},
			dragEnd: function () {
				_this.itemDragEnd(this.element);
			},
		};

		this.handleUIDraggable = {
			start: function handleUIDraggableStart(event, ui) {
				// HTML5 may trigger dragstart, dismiss HTML5 dragging
				if (!ui) {
					return;
				}
				_this.itemDragStart(event.currentTarget);
			},
			drag: function handleUIDraggableDrag(event, ui) {
				if (!ui) {
					return;
				}
				_this.itemDragMove(
					event.currentTarget,
					ui.position.left,
					ui.position.top
				);
			},
			stop: function handleUIDraggableStop(event, ui) {
				if (!ui) {
					return;
				}
				_this.itemDragEnd(event.currentTarget);
			},
		};
	};

	// ----- init & layout ----- //

	/**
	 * logic before any new layout
	 */
	proto._resetLayout = function () {
		this.getSize();

		this._getMeasurements();

		// reset packer
		var width, height, sortDirection;
		// packer settings, if horizontal or vertical
		if (this._getOption('horizontal')) {
			width = Infinity;
			height = this.size.innerHeight + this.gutter;
			sortDirection = 'rightwardTopToBottom';
		} else {
			width = this.size.innerWidth + this.gutter;
			height = Infinity;
			sortDirection = 'downwardLeftToRight';
		}

		this.packer.width = this.shiftPacker.width = width;
		this.packer.height = this.shiftPacker.height = height;
		this.packer.sortDirection = this.shiftPacker.sortDirection =
			sortDirection;

		this.packer.reset();

		// layout
		this.maxY = 0;
		this.maxX = 0;
	};

	/**
	 * update columnWidth, rowHeight, & gutter
	 * @private
	 */
	proto._getMeasurements = function () {
		this._getMeasurement('columnWidth', 'width');
		this._getMeasurement('rowHeight', 'height');
		this._getMeasurement('gutter', 'width');
	};

	proto._getItemLayoutPosition = function (item) {
		this._setRectSize(item.element, item.rect);
		if (this.isShifting || this.dragItemCount > 0) {
			var packMethod = this._getPackMethod();
			this.packer[packMethod](item.rect);
		} else {
			this.packer.pack(item.rect);
		}

		this._setMaxXY(item.rect);
		return item.rect;
	};

	proto.shiftLayout = function () {
		this.isShifting = true;
		this.layout();
		delete this.isShifting;
	};

	proto._getPackMethod = function () {
		return this._getOption('horizontal') ? 'rowPack' : 'columnPack';
	};

	/**
	 * set max X and Y value, for size of container
	 * @param {Packery.Rect} rect
	 * @private
	 */
	proto._setMaxXY = function (rect) {
		this.maxX = Math.max(rect.x + rect.width, this.maxX);
		this.maxY = Math.max(rect.y + rect.height, this.maxY);
	};

	/**
	 * set the width and height of a rect, applying columnWidth and rowHeight
	 * @param {Element} elem
	 * @param {Packery.Rect} rect
	 */
	proto._setRectSize = function (elem, rect) {
		var size = getSize(elem);
		var w = size.outerWidth;
		var h = size.outerHeight;
		// size for columnWidth and rowHeight, if available
		// only check if size is non-zero, #177
		if (w || h) {
			w = this._applyGridGutter(w, this.columnWidth);
			h = this._applyGridGutter(h, this.rowHeight);
		}
		// rect must fit in packer
		rect.width = Math.min(w, this.packer.width);
		rect.height = Math.min(h, this.packer.height);
	};

	/**
	 * fits item to columnWidth/rowHeight and adds gutter
	 * @param {Number} measurement - item width or height
	 * @param {Number} gridSize - columnWidth or rowHeight
	 * @returns measurement
	 */
	proto._applyGridGutter = function (measurement, gridSize) {
		// just add gutter if no gridSize
		if (!gridSize) {
			return measurement + this.gutter;
		}
		gridSize += this.gutter;
		// fit item to columnWidth/rowHeight
		var remainder = measurement % gridSize;
		var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
		measurement = Math[mathMethod](measurement / gridSize) * gridSize;
		return measurement;
	};

	proto._getContainerSize = function () {
		if (this._getOption('horizontal')) {
			return {
				width: this.maxX - this.gutter,
			};
		} else {
			return {
				height: this.maxY - this.gutter,
			};
		}
	};

	// -------------------------- stamp -------------------------- //

	/**
	 * makes space for element
	 * @param {Element} elem
	 */
	proto._manageStamp = function (elem) {
		var item = this.getItem(elem);
		var rect;
		if (item && item.isPlacing) {
			rect = item.rect;
		} else {
			var offset = this._getElementOffset(elem);
			rect = new Rect({
				x: this._getOption('originLeft') ? offset.left : offset.right,
				y: this._getOption('originTop') ? offset.top : offset.bottom,
			});
		}

		this._setRectSize(elem, rect);
		// save its space in the packer
		this.packer.placed(rect);
		this._setMaxXY(rect);
	};

	// -------------------------- methods -------------------------- //

	function verticalSorter(a, b) {
		return a.position.y - b.position.y || a.position.x - b.position.x;
	}

	function horizontalSorter(a, b) {
		return a.position.x - b.position.x || a.position.y - b.position.y;
	}

	proto.sortItemsByPosition = function () {
		var sorter = this._getOption('horizontal')
			? horizontalSorter
			: verticalSorter;
		this.items.sort(sorter);
	};

	/**
	 * Fit item element in its current position
	 * Packery will position elements around it
	 * useful for expanding elements
	 *
	 * @param {Element} elem
	 * @param {Number} x - horizontal destination position, optional
	 * @param {Number} y - vertical destination position, optional
	 */
	proto.fit = function (elem, x, y) {
		var item = this.getItem(elem);
		if (!item) {
			return;
		}

		// stamp item to get it out of layout
		this.stamp(item.element);
		// set placing flag
		item.enablePlacing();
		this.updateShiftTargets(item);
		// fall back to current position for fitting
		x = x === undefined ? item.rect.x : x;
		y = y === undefined ? item.rect.y : y;
		// position it best at its destination
		this.shift(item, x, y);
		this._bindFitEvents(item);
		item.moveTo(item.rect.x, item.rect.y);
		// layout everything else
		this.shiftLayout();
		// return back to regularly scheduled programming
		this.unstamp(item.element);
		this.sortItemsByPosition();
		item.disablePlacing();
	};

	/**
	 * emit event when item is fit and other items are laid out
	 * @param {Packery.Item} item
	 * @private
	 */
	proto._bindFitEvents = function (item) {
		var _this = this;
		var ticks = 0;
		function onLayout() {
			ticks++;
			if (ticks != 2) {
				return;
			}
			_this.dispatchEvent('fitComplete', null, [item]);
		}
		// when item is laid out
		item.once('layout', onLayout);
		// when all items are laid out
		this.once('layoutComplete', onLayout);
	};

	// -------------------------- resize -------------------------- //

	// debounced, layout on resize
	proto.resize = function () {
		// don't trigger if size did not change
		// or if resize was unbound. See #285, outlayer#9
		if (!this.isResizeBound || !this.needsResizeLayout()) {
			return;
		}

		if (this.options.shiftPercentResize) {
			this.resizeShiftPercentLayout();
		} else {
			this.layout();
		}
	};

	/**
	 * check if layout is needed post layout
	 * @returns Boolean
	 */
	proto.needsResizeLayout = function () {
		var size = getSize(this.element);
		var innerSize = this._getOption('horizontal')
			? 'innerHeight'
			: 'innerWidth';
		return size[innerSize] != this.size[innerSize];
	};

	proto.resizeShiftPercentLayout = function () {
		var items = this._getItemsForLayout(this.items);

		var isHorizontal = this._getOption('horizontal');
		var coord = isHorizontal ? 'y' : 'x';
		var measure = isHorizontal ? 'height' : 'width';
		var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth';
		var innerSize = isHorizontal ? 'innerHeight' : 'innerWidth';

		// proportional re-align items
		var previousSegment = this[segmentName];
		previousSegment = previousSegment && previousSegment + this.gutter;

		if (previousSegment) {
			this._getMeasurements();
			var currentSegment = this[segmentName] + this.gutter;
			items.forEach(function (item) {
				var seg = Math.round(item.rect[coord] / previousSegment);
				item.rect[coord] = seg * currentSegment;
			});
		} else {
			var currentSize = getSize(this.element)[innerSize] + this.gutter;
			var previousSize = this.packer[measure];
			items.forEach(function (item) {
				item.rect[coord] =
					(item.rect[coord] / previousSize) * currentSize;
			});
		}

		this.shiftLayout();
	};

	// -------------------------- drag -------------------------- //

	/**
	 * handle an item drag start event
	 * @param {Element} elem
	 */
	proto.itemDragStart = function (elem) {
		if (!this.isEnabled) {
			return;
		}
		this.stamp(elem);
		// this.ignore( elem );
		var item = this.getItem(elem);
		if (!item) {
			return;
		}

		item.enablePlacing();
		item.showDropPlaceholder();
		this.dragItemCount++;
		this.updateShiftTargets(item);
	};

	proto.updateShiftTargets = function (dropItem) {
		this.shiftPacker.reset();

		// pack stamps
		this._getBoundingRect();
		var isOriginLeft = this._getOption('originLeft');
		var isOriginTop = this._getOption('originTop');
		this.stamps.forEach(function (stamp) {
			// ignore dragged item
			var item = this.getItem(stamp);
			if (item && item.isPlacing) {
				return;
			}
			var offset = this._getElementOffset(stamp);
			var rect = new Rect({
				x: isOriginLeft ? offset.left : offset.right,
				y: isOriginTop ? offset.top : offset.bottom,
			});
			this._setRectSize(stamp, rect);
			// save its space in the packer
			this.shiftPacker.placed(rect);
		}, this);

		// reset shiftTargets
		var isHorizontal = this._getOption('horizontal');
		var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth';
		var measure = isHorizontal ? 'height' : 'width';

		this.shiftTargetKeys = [];
		this.shiftTargets = [];
		var boundsSize;
		var segment = this[segmentName];
		segment = segment && segment + this.gutter;

		if (segment) {
			var segmentSpan = Math.ceil(dropItem.rect[measure] / segment);
			var segs = Math.floor(
				(this.shiftPacker[measure] + this.gutter) / segment
			);
			boundsSize = (segs - segmentSpan) * segment;
			// add targets on top
			for (var i = 0; i < segs; i++) {
				this._addShiftTarget(i * segment, 0, boundsSize);
			}
		} else {
			boundsSize =
				this.shiftPacker[measure] +
				this.gutter -
				dropItem.rect[measure];
			this._addShiftTarget(0, 0, boundsSize);
		}

		// pack each item to measure where shiftTargets are
		var items = this._getItemsForLayout(this.items);
		var packMethod = this._getPackMethod();
		items.forEach(function (item) {
			var rect = item.rect;
			this._setRectSize(item.element, rect);
			this.shiftPacker[packMethod](rect);

			// add top left corner
			this._addShiftTarget(rect.x, rect.y, boundsSize);
			// add bottom left / top right corner
			var cornerX = isHorizontal ? rect.x + rect.width : rect.x;
			var cornerY = isHorizontal ? rect.y : rect.y + rect.height;
			this._addShiftTarget(cornerX, cornerY, boundsSize);

			if (segment) {
				// add targets for each column on bottom / row on right
				var segSpan = Math.round(rect[measure] / segment);
				for (var i = 1; i < segSpan; i++) {
					var segX = isHorizontal ? cornerX : rect.x + segment * i;
					var segY = isHorizontal ? rect.y + segment * i : cornerY;
					this._addShiftTarget(segX, segY, boundsSize);
				}
			}
		}, this);
	};

	proto._addShiftTarget = function (x, y, boundsSize) {
		var checkCoord = this._getOption('horizontal') ? y : x;
		if (checkCoord !== 0 && checkCoord > boundsSize) {
			return;
		}
		// create string for a key, easier to keep track of what targets
		var key = x + ',' + y;
		var hasKey = this.shiftTargetKeys.indexOf(key) != -1;
		if (hasKey) {
			return;
		}
		this.shiftTargetKeys.push(key);
		this.shiftTargets.push({ x: x, y: y });
	};

	// -------------------------- drop -------------------------- //

	proto.shift = function (item, x, y) {
		var shiftPosition;
		var minDistance = Infinity;
		var position = { x: x, y: y };
		this.shiftTargets.forEach(function (target) {
			var distance = getDistance(target, position);
			if (distance < minDistance) {
				shiftPosition = target;
				minDistance = distance;
			}
		});
		item.rect.x = shiftPosition.x;
		item.rect.y = shiftPosition.y;
	};

	function getDistance(a, b) {
		var dx = b.x - a.x;
		var dy = b.y - a.y;
		return Math.sqrt(dx * dx + dy * dy);
	}

	// -------------------------- drag move -------------------------- //

	var DRAG_THROTTLE_TIME = 120;

	/**
	 * handle an item drag move event
	 * @param {Element} elem
	 * @param {Number} x - horizontal change in position
	 * @param {Number} y - vertical change in position
	 */
	proto.itemDragMove = function (elem, x, y) {
		var item = this.isEnabled && this.getItem(elem);
		if (!item) {
			return;
		}

		x -= this.size.paddingLeft;
		y -= this.size.paddingTop;

		var _this = this;
		function onDrag() {
			_this.shift(item, x, y);
			item.positionDropPlaceholder();
			_this.layout();
		}

		// throttle
		var now = new Date();
		if (
			this._itemDragTime &&
			now - this._itemDragTime < DRAG_THROTTLE_TIME
		) {
			clearTimeout(this.dragTimeout);
			this.dragTimeout = setTimeout(onDrag, DRAG_THROTTLE_TIME);
		} else {
			onDrag();
			this._itemDragTime = now;
		}
	};

	// -------------------------- drag end -------------------------- //

	/**
	 * handle an item drag end event
	 * @param {Element} elem
	 */
	proto.itemDragEnd = function (elem) {
		var item = this.isEnabled && this.getItem(elem);
		if (!item) {
			return;
		}

		clearTimeout(this.dragTimeout);
		item.element.classList.add('is-positioning-post-drag');

		var completeCount = 0;
		var _this = this;
		function onDragEndLayoutComplete() {
			completeCount++;
			if (completeCount != 2) {
				return;
			}
			// reset drag item
			item.element.classList.remove('is-positioning-post-drag');
			item.hideDropPlaceholder();
			_this.dispatchEvent('dragItemPositioned', null, [item]);
		}

		item.once('layout', onDragEndLayoutComplete);
		this.once('layoutComplete', onDragEndLayoutComplete);
		item.moveTo(item.rect.x, item.rect.y);
		this.layout();
		this.dragItemCount = Math.max(0, this.dragItemCount - 1);
		this.sortItemsByPosition();
		item.disablePlacing();
		this.unstamp(item.element);
	};

	/**
	 * binds Draggabilly events
	 * @param {Draggabilly} draggie
	 */
	proto.bindDraggabillyEvents = function (draggie) {
		this._bindDraggabillyEvents(draggie, 'on');
	};

	proto.unbindDraggabillyEvents = function (draggie) {
		this._bindDraggabillyEvents(draggie, 'off');
	};

	proto._bindDraggabillyEvents = function (draggie, method) {
		var handlers = this.handleDraggabilly;
		draggie[method]('dragStart', handlers.dragStart);
		draggie[method]('dragMove', handlers.dragMove);
		draggie[method]('dragEnd', handlers.dragEnd);
	};

	/**
	 * binds jQuery UI Draggable events
	 * @param {jQuery} $elems
	 */
	proto.bindUIDraggableEvents = function ($elems) {
		this._bindUIDraggableEvents($elems, 'on');
	};

	proto.unbindUIDraggableEvents = function ($elems) {
		this._bindUIDraggableEvents($elems, 'off');
	};

	proto._bindUIDraggableEvents = function ($elems, method) {
		var handlers = this.handleUIDraggable;
		$elems[method]('dragstart', handlers.start)
			[method]('drag', handlers.drag)
			[method]('dragstop', handlers.stop);
	};

	// ----- destroy ----- //

	var _destroy = proto.destroy;
	proto.destroy = function () {
		_destroy.apply(this, arguments);
		// disable flag; prevent drag events from triggering. #72
		this.isEnabled = false;
	};

	// -----  ----- //

	Packery.Rect = Rect;
	Packery.Packer = Packer;

	return Packery;
});

/*!
 * Packery layout mode v2.0.1
 * sub-classes Packery
 */

/*jshint browser: true, strict: true, undef: true, unused: true */

(function (window, factory) {
	// browser global
	factory(window.ModulaIsotope.LayoutMode, window.Packery);
})(window, function factor(LayoutMode, Packery) {
	// create an Outlayer layout class
	var PackeryMode = LayoutMode.create('packery');
	var proto = PackeryMode.prototype;

	var keepModeMethods = {
		_getElementOffset: true,
		_getMeasurement: true,
	};

	// inherit Packery prototype
	for (var method in Packery.prototype) {
		// do not inherit mode methods
		if (!keepModeMethods[method]) {
			proto[method] = Packery.prototype[method];
		}
	}

	// set packer in _resetLayout
	var _resetLayout = proto._resetLayout;
	proto._resetLayout = function () {
		this.packer = this.packer || new Packery.Packer();
		this.shiftPacker = this.shiftPacker || new Packery.Packer();
		_resetLayout.apply(this, arguments);
	};

	var _getItemLayoutPosition = proto._getItemLayoutPosition;
	proto._getItemLayoutPosition = function (item) {
		// set packery rect
		item.rect = item.rect || new Packery.Rect();
		return _getItemLayoutPosition.call(this, item);
	};

	// needsResizeLayout for vertical or horizontal
	var _needsResizeLayout = proto.needsResizeLayout;
	proto.needsResizeLayout = function () {
		if (this._getOption('horizontal')) {
			return this.needsVerticalResizeLayout();
		} else {
			return _needsResizeLayout.call(this);
		}
	};

	// point to mode options for horizontal
	var _getOption = proto._getOption;
	proto._getOption = function (option) {
		if (option == 'horizontal') {
			return this.options.isHorizontal !== undefined
				? this.options.isHorizontal
				: this.options.horizontal;
		}
		return _getOption.apply(this.isotope, arguments);
	};

	return PackeryMode;
});
