Index: /binary-improvements/MapRendering/ModInfo.xml
===================================================================
--- /binary-improvements/MapRendering/ModInfo.xml	(revision 248)
+++ /binary-improvements/MapRendering/ModInfo.xml	(revision 249)
@@ -5,5 +5,5 @@
 		<Description value="Render the game map to image map tiles as it is uncovered" />
 		<Author value="Christian 'Alloc' Illy" />
-		<Version value="7" />
+		<Version value="8" />
 		<Website value="http://7dtd.illy.bz" />
 	</ModInfo>
Index: /binary-improvements/MapRendering/Web/Web.cs
===================================================================
--- /binary-improvements/MapRendering/Web/Web.cs	(revision 248)
+++ /binary-improvements/MapRendering/Web/Web.cs	(revision 249)
@@ -225,6 +225,7 @@
 					WebConnection con = connectionHandler.LogIn (id, _req.RemoteEndPoint.Address.ToString ());
 					_con = con;
-					//Log.Out ("Logged in with session id: {0}", con.SessionID);
-					return GameManager.Instance.adminTools.GetAdminToolsClientInfo (id.ToString ()).PermissionLevel;
+					int level = GameManager.Instance.adminTools.GetAdminToolsClientInfo (id.ToString ()).PermissionLevel;
+					Log.Out ("Steam OpenID login from {0} with ID {1}, permission level {2}", _req.RemoteEndPoint.ToString (), con.SteamID, level);
+					return level;
 				} else {
 					Log.Out ("Steam OpenID login failed from {0}", _req.RemoteEndPoint.ToString ());
Index: /binary-improvements/server-fixes.userprefs
===================================================================
--- /binary-improvements/server-fixes.userprefs	(revision 248)
+++ /binary-improvements/server-fixes.userprefs	(revision 249)
@@ -1,11 +1,11 @@
 ﻿<Properties>
   <MonoDevelop.Ide.Workspace ActiveConfiguration="Release_Version" />
-  <MonoDevelop.Ide.Workbench ActiveDocument="MapRendering/Web/API/GetLandClaims.cs">
+  <MonoDevelop.Ide.Workbench ActiveDocument="7dtd-server-fixes/ModInfo.xml">
     <Files>
       <File FileName="MapRendering/MapRendering/Constants.cs" Line="1" Column="1" />
-      <File FileName="7dtd-server-fixes/ModInfo.xml" Line="2" Column="6" />
+      <File FileName="7dtd-server-fixes/ModInfo.xml" Line="7" Column="20" />
       <File FileName="AllocsCommands/ModInfo.xml" Line="7" Column="20" />
       <File FileName="MapRendering/ModInfo.xml" Line="7" Column="20" />
-      <File FileName="MapRendering/Web/Web.cs" Line="69" Column="19" />
+      <File FileName="MapRendering/Web/Web.cs" Line="168" Column="13" />
       <File FileName="MapRendering/Web/API/ExecuteConsoleCommand.cs" Line="36" Column="114" />
       <File FileName="MapRendering/Web/API/GetStats.cs" Line="21" Column="84" />
Index: /binary-improvements/webserver/ThirdParty-Libs.txt
===================================================================
--- /binary-improvements/webserver/ThirdParty-Libs.txt	(revision 248)
+++ /binary-improvements/webserver/ThirdParty-Libs.txt	(revision 249)
@@ -6,3 +6,4 @@
 	- Zoomslider: http://kartena.github.io/Leaflet.zoomslider/
 	- Leaflet MiniMap: https://github.com/Norkart/Leaflet-MiniMap
+	- Leaflet Measure: https://github.com/ljagis/leaflet-measure
 
Index: /binary-improvements/webserver/css/style.css
===================================================================
--- /binary-improvements/webserver/css/style.css	(revision 248)
+++ /binary-improvements/webserver/css/style.css	(revision 249)
@@ -6,4 +6,19 @@
 	margin: 0;
 	padding: 0;
+	background-image: url('/static/img/background.jpg');
+	background-repeat: no-repeat;
+	background-attachment: fixed;
+	background-position: center; 
+	color: orange;
+}
+
+a {
+	color: #ff6000;
+	text-decoration: none;
+}
+
+a:visited {
+	color: #ff6000;
+	text-decoration: none;
 }
 
@@ -11,5 +26,5 @@
 	width: 100%;
 	height: 100vh;
-	background-color: #408040;
+	/*background-color: #408040;*/
 }
 
@@ -19,4 +34,8 @@
 	top: 0;
 	bottom: 0;
+}
+
+.invalidinput {
+	background-color: #f00;
 }
 
@@ -29,5 +48,6 @@
 	width: 200px;
 	left: 0;
-	background-color: #408040;
+	border-right: 1px solid rgba(0,0,0,0.3);
+	box-shadow: 3px 0px 14px rgba(0,0,0,0.9);
 }
 
@@ -53,5 +73,5 @@
 
 .adminnavbar #adminmenu .menu_button a {
-	color: #000;
+	color: orange;
 	text-decoration: none;
 }
@@ -69,5 +89,5 @@
 	margin-bottom: 20px;
 	display: none;
-	color: #000;
+	/* color: orange; */
 	text-decoration: none;
 }
@@ -86,5 +106,5 @@
 	left: 0px;
 	right: 0px;
-	background-color: #408040;
+	/*background-color: #408040;*/
 }
 
@@ -106,5 +126,5 @@
 	right: 0;
 	left: 200px;
-	background-color: #408040;
+	/*background-color: #408040;*/
 }
 
@@ -125,11 +145,4 @@
 }
 
-/* Individual tabs */
-
-.adminmap {
-	background-color: #408040;
-	bottom: 0;
-}
-
 
 
@@ -144,4 +157,40 @@
 	left: 10px;
 }
+
+.inventoryButton {
+	cursor: pointer;
+}
+#playerInventoryDialog {
+	display:none;
+}
+.playerInventoryDialog {
+	text-shadow:
+		-1px -1px 0 black,  
+		1px -1px 0 black,
+		-1px 1px 0 black,
+		1px 1px 0 black;
+	box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+	color: orange;
+	background: rgba(100,100,100,0.6);
+	border: none;
+}
+.playerInventoryDialog .ui-dialog-buttonpane {
+	display: none;
+}
+.playerInventoryDialog.ui-dialog {
+	z-index:1011 !important;
+}
+.ui-widget-overlay {
+	z-index:1010 !important;
+	opacity: 0.4 !important;
+}
+.playerInventoryDialog .ui-widget-content {
+	background: transparent;
+	color: orange;
+}
+.playerInventoryDialog td {
+	vertical-align: top;
+}
+
 .invTable {
 	table-layout: fixed;
@@ -150,5 +199,8 @@
 	border-collapse: collapse;
 }
-.invField {
+#equipmentTable .invFieldText {
+	display: none;
+}
+.playerInventoryDialog td.invField {
 	width: 58px;
 	height: 40px;
@@ -156,5 +208,5 @@
 	margin: 0px;
 	border: 1px solid gray;
-	background-color: #303030;
+	background-color: black;
 	background-size: 58px;
 	background-repeat: no-repeat;
@@ -163,48 +215,48 @@
 	text-align: right;
 	font-size: 14pt;
-	color: #ffffff;
 	text-shadow:
-		-1px -1px 0 #000,  
-		1px -1px 0 #000,
-		-1px 1px 0 #000,
-		1px 1px 0 #000;
-}
-
-#equipmentTable .invFieldText {
-	display: none;
-}
-
-.inventoryButton {
-	color: #00ff00;
-	cursor: pointer;
-}
-#playerInventoryDialog {
-	display:none;
-}
-
-.ui-dialog {
-	z-index:1011 !important;
-}
-.ui-widget-overlay {
-	z-index:1010 !important;
-}
-
-
-
-/* Map controls */
-
-
-.webmap-control a,
-.webmap-control a:hover {
-	text-decoration: none;
+		-1px -1px 0 black,  
+		1px -1px 0 black,
+		-1px 1px 0 black,
+		1px 1px 0 black;
+}
+
+
+
+
+/*========================================
+-   Map
+*/
+
+.adminmap {
+	/*background-color: #408040;*/
+	background-color: transparent;
+	bottom: 0;
+}
+
+.adminmap .leaflet-control {
+	background-color: rgba(50,50,50,0.6);
+	color: orange;
+	box-shadow: 0 3px 14px rgba(0,0,0,0.5);
+}
+
+.adminmap a,
+.adminmap a:hover,
+.adminmap .leaflet-container a,
+.adminmap .leaflet-container a:hover {
+	text-decoration: none;
+	color: #ff6000;
 }
 
 .webmap-control {
-	box-shadow: 0 1px 5px rgba(0,0,0,0.4);
-	background: #fff;
 	border-radius: 5px;
 	padding: 6px 10px 6px 6px;
-	color: #333;
 	white-space: nowrap;
 }
 
+.adminmap .leaflet-popup-tip,
+.adminmap .leaflet-popup-content-wrapper {
+	background-color: rgba(50,50,50,0.8);
+	color: orange;
+}
+
Index: /binary-improvements/webserver/index.html
===================================================================
--- /binary-improvements/webserver/index.html	(revision 248)
+++ /binary-improvements/webserver/index.html	(revision 249)
@@ -29,9 +29,15 @@
 	<script type="text/javascript" src="leaflet/minimap/Control.MiniMap.js"></script>
 	
+	<!-- Leaflet Measure -->
+	<link rel="stylesheet" href="leaflet/measure/leaflet-measure.css" />
+	<script type="text/javascript" src="leaflet/measure/leaflet-measure.min.js"></script>
+	
 	<!-- Own JS stuff -->
+	<script type="text/javascript" src="js/leaflet.layer.sdtdtiles.js"></script>
 	<script type="text/javascript" src="js/leaflet.regionlayer.js"></script>
+	<script type="text/javascript" src="js/leaflet.layer.landclaims.js"></script>
 	<script type="text/javascript" src="js/leaflet.control.coordinates.js"></script>
 	<script type="text/javascript" src="js/leaflet.control.reloadtiles.js"></script>
-	<script type="text/javascript" src="js/leaflet.layer.landclaims.js"></script>
+	<script type="text/javascript" src="js/leaflet.control.gametime.js"></script>
 	<script type="text/javascript" src="js/inventory_dialog.js"></script>
 	<script type="text/javascript" src="js/util.js"></script>
@@ -89,4 +95,5 @@
 	<div id="playerInventoryDialog" title="Player inventory">
 		Player: <span id="invPlayerName"></span><br/>
+		<br/>
 		<table>
 			<tr>
@@ -95,4 +102,5 @@
 					<table class="invTable" id="bagTable">
 					</table>
+					<br/>
 				</td>
 				<td rowspan="2">
Index: /binary-improvements/webserver/js/inventory_dialog.js
===================================================================
--- /binary-improvements/webserver/js/inventory_dialog.js	(revision 248)
+++ /binary-improvements/webserver/js/inventory_dialog.js	(revision 249)
@@ -57,4 +57,5 @@
 
 		$( "#playerInventoryDialog" ).css("z-index", "1010").dialog({
+			dialogClass: "playerInventoryDialog",
 			modal: true,
 			width: BAG_COLS*(INV_ITEM_WIDTH+14) + 3*(INV_ITEM_WIDTH+14) + 20,
Index: /binary-improvements/webserver/js/leaflet.control.gametime.js
===================================================================
--- /binary-improvements/webserver/js/leaflet.control.gametime.js	(revision 249)
+++ /binary-improvements/webserver/js/leaflet.control.gametime.js	(revision 249)
@@ -0,0 +1,47 @@
+L.Control.GameTime = L.Control.extend({
+        options: {
+                position: 'bottomright'
+        },
+
+        onAdd: function (map) {
+                var name = 'control-gametime',
+                    container = L.DomUtil.create('div', name + ' webmap-control');
+
+                container.innerHTML = "Loading ..."
+                L.DomEvent.on (container, 'mousemove', L.DomEvent.stopPropagation);
+
+                this._map = map;
+                this._div = container;
+
+                window.setTimeout($.proxy(this._updateGameTimeEvent, this), 0);
+
+                return container;
+        },
+
+        onRemove: function (map) {
+        },
+
+        _updateGameTimeEvent: function() {
+                var div = this._div;
+                $.getJSON( "../api/getstats")
+                .done(function(data) {
+                        var time = "Day " + data.gametime.days + ", ";
+                        if (data.gametime.hours < 10)
+                                time += "0";
+                        time += data.gametime.hours;
+                        time += ":";
+                        if (data.gametime.minutes < 10)
+                                time += "0";
+                        time += data.gametime.minutes;
+                        div.innerHTML = time;
+                })
+                .fail(function(jqxhr, textStatus, error) {
+                        console.log("Error fetching game stats");
+                })
+                .always(function() {
+                });
+                window.setTimeout($.proxy(this._updateGameTimeEvent, this), 2000);
+        }
+
+});
+
Index: /binary-improvements/webserver/js/leaflet.control.login.js
===================================================================
--- /binary-improvements/webserver/js/leaflet.control.login.js	(revision 249)
+++ /binary-improvements/webserver/js/leaflet.control.login.js	(revision 249)
@@ -0,0 +1,35 @@
+L.Control.Login = L.Control.extend({
+	options: {
+		position: 'bottomleft'
+	},
+
+	onAdd: function (map) {
+		var name = 'control-login',
+		    container = L.DomUtil.create('div', name + ' leaflet-bar');
+	
+		this._map = map;
+		this._div = container;
+		
+		if (userdata.loggedin == true) {
+			container.innerHTML =
+				"Logged in as: " + userdata.username + "<br/>\
+				<a href=\"/session/logout\">Sign&nbsp;out</a>";
+		} else {
+			container.innerHTML =
+				"Not logged in<br/>\
+				<a href=\"/session/login\">\
+				<img src=\"img/steamlogin.png\" title=\"Sign in through Steam\"></a>";
+		}
+
+		
+		return container;
+	},
+
+	onRemove: function (map) {
+	},
+
+	_onMouseMove: function (e) {
+		this._div.innerHTML = FormatCoord(e.latlng);
+	}
+});
+
Index: /binary-improvements/webserver/js/leaflet.control.reloadtiles.js
===================================================================
--- /binary-improvements/webserver/js/leaflet.control.reloadtiles.js	(revision 248)
+++ /binary-improvements/webserver/js/leaflet.control.reloadtiles.js	(revision 249)
@@ -2,4 +2,8 @@
 	options: {
 		position: 'bottomleft',
+		autoreload_enable: true,
+		autoreload_minInterval: 30,
+		autoreload_interval: 120,
+		autoreload_defaultOn: false,
 		layers: []
 	},
@@ -9,12 +13,58 @@
 		    container = L.DomUtil.create('div', name + ' webmap-control');
 
-		L.DomEvent.on (container, 'mousemove', L.DomEvent.stopPropagation);
+		var stop = L.DomEvent.stopPropagation;
+		L.DomEvent
+		    .on (container, 'mousemove', stop)
+		    .on (container, 'click', stop)
+		    .on (container, 'mousedown', stop)
+		    .on (container, 'dblclick', stop);
 
 		this._map = map;
+		
+		this._reloadbutton = $("<a>")
+			.addClass (name+"-btn")
+			.text ("Reload tiles now")
+			.attr ("href", "#")
+			.attr ("title", "Reload tiles now")
+			.on ("click.action", null, this, this._reload);
+		$(container).append (this._reloadbutton);
 
-		this._reloadbutton = this._createButton(
-			"Reload tiles", "Reload tiles",
-			name + "-btn", container, this._reload, this);
+		if (this.options.autoreload_enable) {
+			$(container).append ("<br>");
+		
+			this._autocheck = $("<input>")
+				.addClass (name + "-chk")
+				.attr ("type", "checkbox")
+				.attr ("name", "map_reloadtiles_autoreload")
+				.attr ("id", "map_reloadtiles_autoreload")
+				.attr ("value", "1")
+				.on ("change", null, this, this._selectreload);
+			if (this.options.autoreload_defaultOn) {
+				this._autocheck.attr ("checked", "checked");
+				this._timeout = window.setTimeout ($.proxy (this._reloadTilesEvent, this), this.options.autoreload_interval*1000);
+			}
+			$(container).append (this._autocheck);
+		
+			var label1 = $("<label>")
+				.attr ("for", "map_reloadtiles_autoreload");
+			label1.append ("Reload every ");
+			$(container).append (label1);
+		
+			this._reloadinterval = $("<input>")
+				.addClass (name + "-txt")
+				.attr ("name", "map_reloadtiles_autoreload_time")
+				.attr ("type", "text")
+				.attr ("size", "4")
+				.attr ("maxlength", "5")
+				.attr ("value", this.options.autoreload_interval)
+				.on ("input", null, this, this._verifyinterval);
+			$(container).append (this._reloadinterval);
 
+			var label2 = $("<label>")
+				.attr ("for", "map_reloadtiles_autoreload");
+			label2.append (" seconds");
+			$(container).append (label2);
+		}
+		
 		return container;
 	},
@@ -22,32 +72,45 @@
 	onRemove: function (map) {
 	},
+	
+	_selectreload: function (e) {
+		if (e.data._autocheck.prop ("checked")) {
+			e.data._timeout = window.setTimeout ($.proxy (e.data._reloadTilesEvent, e.data), e.data.options.autoreload_interval*1000);
+		} else {
+			window.clearTimeout (e.data._timeout);
+		}
+	},
+	
+	_verifyinterval: function (e) {
+		var val = e.data._reloadinterval.val ();
+		if (/^[\d]+$/.test (val)) {
+			if (val >= e.data.options.autoreload_minInterval) {
+				e.data._reloadinterval.removeClass ("invalidinput");
+				e.data.options.autoreload_interval = val;
+				if (e.data._autocheck.prop ("checked")) {
+					window.clearTimeout (e.data._timeout);
+					e.data._timeout = window.setTimeout ($.proxy (e.data._reloadTilesEvent, e.data), e.data.options.autoreload_interval*1000);
+				}
+			} else {
+				e.data._reloadinterval.addClass ("invalidinput");
+			}
+		} else {
+			e.data._reloadinterval.addClass ("invalidinput");
+		}
+	},
 
 	_reload: function (e) {
 		var newTileTime = new Date().getTime();
 		
-		for (var i = 0; i < this.options.layers.length; i++) {
-			this.options.layers [i].options.time = newTileTime;
-			this.options.layers [i].redraw ();
+		for (var i = 0; i < e.data.options.layers.length; i++) {
+			e.data.options.layers [i].options.time = newTileTime;
+			e.data.options.layers [i].redraw ();
 		}
 	},
 
-	_createButton: function (html, title, className, container, fn, context) {
-		var link = L.DomUtil.create('a', className, container);
-		link.innerHTML = html;
-		link.href = '#';
-		link.title = title;
-
-		var stop = L.DomEvent.stopPropagation;
-
-		L.DomEvent
-		    .on(link, 'click', stop)
-		    .on(link, 'mousedown', stop)
-		    .on(link, 'dblclick', stop)
-		    .on(link, 'click', L.DomEvent.preventDefault)
-		    .on(link, 'click', fn, context)
-		    .on(link, 'click', this._refocusOnMap, context);
-		   
-		return link;
-	}
+	_reloadTilesEvent: function() {
+		var div = this._div;
+		this._reload ({data: this});
+		this._timeout = window.setTimeout ($.proxy (this._reloadTilesEvent, this), this.options.autoreload_interval*1000);
+	},
 
 });
Index: /binary-improvements/webserver/js/leaflet.layer.sdtdtiles.js
===================================================================
--- /binary-improvements/webserver/js/leaflet.layer.sdtdtiles.js	(revision 249)
+++ /binary-improvements/webserver/js/leaflet.layer.sdtdtiles.js	(revision 249)
@@ -0,0 +1,22 @@
+function GetSdtdTileLayer (mapinfo, initTime, isMiniMap) {
+	if (typeof isMiniMap == 'undefined') isMiniMap = false;
+	
+	var tileLayer = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
+		maxZoom: isMiniMap ? mapinfo.maxzoom : mapinfo.maxzoom + 1,
+		minZoom: isMiniMap ? 0 : Math.max(0, mapinfo.maxzoom - 5),
+		maxNativeZoom: mapinfo.maxzoom,
+		tileSize: mapinfo.tilesize,
+		continuousWorld: true,
+		tms: true,
+		unloadInvisibleTiles: false,
+		time: initTime
+	});
+	
+	// TileLayer w/ TMS=true fix for zoomlevel >= 8
+	tileLayer._getWrapTileNum = function () {
+		return L.point(0, 0);
+	};
+	
+	return tileLayer;
+}
+
Index: /binary-improvements/webserver/js/map.js
===================================================================
--- /binary-improvements/webserver/js/map.js	(revision 248)
+++ /binary-improvements/webserver/js/map.js	(revision 249)
@@ -33,11 +33,6 @@
 	});
 
-
-
-
 	// ===============================================================================================
 	// Map and basic tile layers
-
-	var initTime = new Date().getTime();
 
 	map = L.map('tab_map', {
@@ -48,33 +43,8 @@
 	}).setView([0, 0], Math.max(0, mapinfo.maxzoom - 5));
 
-	var tileLayer = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
-		maxZoom: mapinfo.maxzoom + 1,
-		minZoom: Math.max(0, mapinfo.maxzoom - 5),
-		maxNativeZoom: mapinfo.maxzoom,
-		tileSize: mapinfo.tilesize,
-		continuousWorld: true,
-		tms: true,
-		unloadInvisibleTiles: false,
-		time: initTime
-	});
-	
-	// TileLayer w/ TMS=true fix for zoomlevel >= 8
-	tileLayer._getWrapTileNum = function () {
-		return L.point(0, 0);
-	};
-
-	var tileLayerMiniMap = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
-		maxZoom: mapinfo.maxzoom,
-		minZoom: 0,
-		maxNativeZoom: mapinfo.maxzoom,
-		tileSize: mapinfo.tilesize,
-		continuousWorld: true,
-		tms: true,
-		unloadInvisibleTiles: false,
-		time: initTime
-	});
-
-
-
+
+	var initTime = new Date().getTime();
+	var tileLayer = GetSdtdTileLayer (mapinfo, initTime);
+	var tileLayerMiniMap = GetSdtdTileLayer (mapinfo, initTime, true);
 
 
@@ -92,12 +62,10 @@
 
 
-
-	var baseLayers = {
-		//"Map": tileLayer
-	};
-
-	var layerControl = L.control.layers(baseLayers, null, {
-		collapsed: false
-	});
+	var layerControl = L.control.layers({
+			//"Map": tileLayer
+		}, null, {
+			collapsed: false
+		}
+	);
 	
 	var layerCount = 0;
@@ -105,7 +73,17 @@
 
 	tileLayer.addTo(map);
+
 	new L.Control.Coordinates({}).addTo(map);
-	new L.Control.ReloadTiles({layers: [tileLayer, tileLayerMiniMap]}).addTo(map);
+	
+	new L.Control.ReloadTiles({
+		autoreload_enable: true,
+		autoreload_minInterval: 30,
+		autoreload_interval: 120,
+		autoreload_defaultOn: false,
+		layers: [tileLayer, tileLayerMiniMap]
+	}).addTo(map);
+	
 	layerControl.addOverlay (GetRegionLayer (mapinfo), "Region files");
+	
 	var miniMap = new L.Control.MiniMap(tileLayerMiniMap, {
 		zoomLevelOffset: -6,
@@ -113,8 +91,20 @@
 	}).addTo(map);
 
+	var measure = L.control.measure({
+		//primaryLengthUnit: "meters",
+		//primaryAreaUnit: "sqmeters",
+		//activeColor: "#ABE67E",
+		//completedColor: "#C8F2BE",
+		position: "bottomleft"
+	});
+	//measure.addTo(map);
+
+	//new L.Control.GameTime({}).addTo(map);
+	
 	if (HasPermission ("webapi.getlandclaims")) {
 		layerControl.addOverlay (GetLandClaimsLayer (map, mapinfo), "Land claims");
 		layerCount++;
 	}
+	
 	if (HasPermission ("webapi.getplayerslocation")) {
 		layerControl.addOverlay (playersOfflineMarkerGroup, "Players (offline) (<span id='mapControlOfflineCount'>0</span>)");
@@ -148,5 +138,4 @@
 			if (playersMappingList.hasOwnProperty(val.steamid)) {
 				marker = playersMappingList[val.steamid].currentPosMarker;
-				marker.setLatLng([val.position.x, val.position.z]);
 			} else {
 				marker = L.marker([val.position.x, val.position.z]).bindPopup(
@@ -158,15 +147,18 @@
 				playersMappingList[val.steamid] = { online: !val.online };
 			}
-			if (playersMappingList[val.steamid].online != val.online) {
-				if (val.online) {
+			if (playersMappingList[val.steamid].online) {
+				playersOnlineMarkerGroup.removeLayer(marker);
+			} else {
+				playersOfflineMarkerGroup.removeLayer(marker);
+			}
+			marker.setLatLng([val.position.x, val.position.z]);
+			if (val.online) {
 					marker.setOpacity(1.0);
-					playersOfflineMarkerGroup.removeLayer(marker);
 					playersOnlineMarkerGroup.addLayer(marker);
-				} else {
+			} else {
 					marker.setOpacity(0.5);
-					playersOnlineMarkerGroup.removeLayer(marker);
 					playersOfflineMarkerGroup.addLayer(marker);
-				}
 			}
+
 			val.currentPosMarker = marker;
 			playersMappingList[val.steamid] = val;
Index: /binary-improvements/webserver/js/permissions.js
===================================================================
--- /binary-improvements/webserver/js/permissions.js	(revision 248)
+++ /binary-improvements/webserver/js/permissions.js	(revision 249)
@@ -36,5 +36,7 @@
 	})
 	.always(function () {
-		$("#nopermissionwarning").attr ("style", "display: block");
+		if (PermissionCount () == 0) {
+			$("#nopermissionwarning").attr ("style", "display: block");
+		}
 	})
 }
@@ -49,2 +51,10 @@
 }
 
+function PermissionCount () {
+	var cnt = 0;
+	for (var i = 0; i < userdata.permissions.length; i++) {
+		if (userdata.permissions [i].allowed) cnt++;
+	}
+	return cnt;
+}
+
Index: /binary-improvements/webserver/js/tabs.js
===================================================================
--- /binary-improvements/webserver/js/tabs.js	(revision 248)
+++ /binary-improvements/webserver/js/tabs.js	(revision 249)
@@ -1,2 +1,15 @@
+var tabElements = {};
+var currentTabClass = "current_tab";
+
+function OpenTab () {
+	var menuElement = $(this);
+	var linkElement = menuElement.children ("a");
+	var linkName = linkElement.attr ("href");
+
+	$("*").removeClass (currentTabClass);
+	menuElement.addClass (currentTabClass);
+	$(linkName).addClass (currentTabClass);
+}
+
 function InitializeTabs () {
 	$("#adminmenu > ul > li").addClass ("menu_button");
Index: /binary-improvements/webserver/leaflet/measure/leaflet-measure.css
===================================================================
--- /binary-improvements/webserver/leaflet/measure/leaflet-measure.css	(revision 249)
+++ /binary-improvements/webserver/leaflet/measure/leaflet-measure.css	(revision 249)
@@ -0,0 +1,1 @@
+.leaflet-control-measure h3,.leaflet-measure-resultpopup h3{margin:0 0 12px 0;padding-bottom:10px;line-height:1em;font-weight:normal;font-size:1.1em;border-bottom:solid 1px #DDD}.leaflet-control-measure p,.leaflet-measure-resultpopup p{margin:10px 0 0;line-height:1em}.leaflet-control-measure p:first-child,.leaflet-measure-resultpopup p:first-child{margin-top:0}.leaflet-control-measure a,.leaflet-measure-resultpopup a{color:#5E66CC;text-decoration:none}.leaflet-control-measure a:hover,.leaflet-measure-resultpopup a:hover{opacity:0.5;text-decoration:none}.leaflet-control-measure .tasks,.leaflet-measure-resultpopup .tasks{margin:12px 0 0;padding:10px 0 0;border-top:solid 1px #DDD;list-style:none;list-style-image:none}.leaflet-control-measure .tasks li,.leaflet-measure-resultpopup .tasks li{display:inline;margin:0 10px 0 0}.leaflet-control-measure .tasks li:last-child,.leaflet-measure-resultpopup .tasks li:last-child{margin-right:0}.leaflet-control-measure .coorddivider,.leaflet-measure-resultpopup .coorddivider{color:#999}.leaflet-control-measure{background:#fff;border-radius:5px;box-shadow:0 1px 5px rgba(0,0,0,0.4)}.leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-control-measure .leaflet-control-measure-toggle:hover{display:block;width:36px;height:36px;background-position:50% 50%;background-repeat:no-repeat;background-image:url(images/rulers.png);border-radius:5px;text-indent:100%;white-space:nowrap;overflow:hidden}.leaflet-retina .leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-retina .leaflet-control-measure .leaflet-control-measure-toggle:hover{background-image:url(images/rulers_@2X.png);background-size:16px 16px}.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle:hover{width:44px;height:44px}.leaflet-control-measure .startprompt h3{margin-bottom:10px}.leaflet-control-measure .startprompt .tasks{margin-top:0;padding-top:0;border-top:0}.leaflet-control-measure .leaflet-control-measure-interaction{padding:10px 12px}.leaflet-control-measure .results .group{margin-top:10px;padding-top:10px;border-top:dotted 1px #eaeaea}.leaflet-control-measure .results .group:first-child{margin-top:0;padding-top:0;border-top:0}.leaflet-control-measure .results .heading{margin-right:5px;color:#999}.leaflet-control-measure a.start{padding-left:18px;background-repeat:no-repeat;background-position:0% 50%;background-image:url(images/start.png)}.leaflet-retina .leaflet-control-measure a.start{background-image:url(images/start_@2X.png);background-size:12px 12px}.leaflet-control-measure a.cancel{padding-left:18px;background-repeat:no-repeat;background-position:0% 50%;background-image:url(images/cancel.png)}.leaflet-retina .leaflet-control-measure a.cancel{background-image:url(images/cancel_@2X.png);background-size:12px 12px}.leaflet-control-measure a.finish{padding-left:18px;background-repeat:no-repeat;background-position:0% 50%;background-image:url(images/check.png)}.leaflet-retina .leaflet-control-measure a.finish{background-image:url(images/check_@2X.png);background-size:12px 12px}.leaflet-measure-resultpopup a.zoomto{padding-left:18px;background-repeat:no-repeat;background-position:0% 50%;background-image:url(images/focus.png)}.leaflet-retina .leaflet-measure-resultpopup a.zoomto{background-image:url(images/focus_@2X.png);background-size:12px 12px}.leaflet-measure-resultpopup a.deletemarkup{padding-left:18px;background-repeat:no-repeat;background-position:0% 50%;background-image:url(images/trash.png)}.leaflet-retina .leaflet-measure-resultpopup a.deletemarkup{background-image:url(images/trash_@2X.png);background-size:11px 12px}
Index: /binary-improvements/webserver/leaflet/measure/leaflet-measure.js
===================================================================
--- /binary-improvements/webserver/leaflet/measure/leaflet-measure.js	(revision 249)
+++ /binary-improvements/webserver/leaflet/measure/leaflet-measure.js	(revision 249)
@@ -0,0 +1,5627 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/* MIT license */
+var convert = require("color-convert"),
+    string = require("color-string");
+
+var Color = function(obj) {
+  if (obj instanceof Color) return obj;
+  if (! (this instanceof Color)) return new Color(obj);
+
+   this.values = {
+      rgb: [0, 0, 0],
+      hsl: [0, 0, 0],
+      hsv: [0, 0, 0],
+      hwb: [0, 0, 0],
+      cmyk: [0, 0, 0, 0],
+      alpha: 1
+   }
+
+   // parse Color() argument
+   if (typeof obj == "string") {
+      var vals = string.getRgba(obj);
+      if (vals) {
+         this.setValues("rgb", vals);
+      }
+      else if(vals = string.getHsla(obj)) {
+         this.setValues("hsl", vals);
+      }
+      else if(vals = string.getHwb(obj)) {
+         this.setValues("hwb", vals);
+      }
+      else {
+        throw new Error("Unable to parse color from string \"" + obj + "\"");
+      }
+   }
+   else if (typeof obj == "object") {
+      var vals = obj;
+      if(vals["r"] !== undefined || vals["red"] !== undefined) {
+         this.setValues("rgb", vals)
+      }
+      else if(vals["l"] !== undefined || vals["lightness"] !== undefined) {
+         this.setValues("hsl", vals)
+      }
+      else if(vals["v"] !== undefined || vals["value"] !== undefined) {
+         this.setValues("hsv", vals)
+      }
+      else if(vals["w"] !== undefined || vals["whiteness"] !== undefined) {
+         this.setValues("hwb", vals)
+      }
+      else if(vals["c"] !== undefined || vals["cyan"] !== undefined) {
+         this.setValues("cmyk", vals)
+      }
+      else {
+        throw new Error("Unable to parse color from object " + JSON.stringify(obj));
+      }
+   }
+}
+
+Color.prototype = {
+   rgb: function (vals) {
+      return this.setSpace("rgb", arguments);
+   },
+   hsl: function(vals) {
+      return this.setSpace("hsl", arguments);
+   },
+   hsv: function(vals) {
+      return this.setSpace("hsv", arguments);
+   },
+   hwb: function(vals) {
+      return this.setSpace("hwb", arguments);
+   },
+   cmyk: function(vals) {
+      return this.setSpace("cmyk", arguments);
+   },
+
+   rgbArray: function() {
+      return this.values.rgb;
+   },
+   hslArray: function() {
+      return this.values.hsl;
+   },
+   hsvArray: function() {
+      return this.values.hsv;
+   },
+   hwbArray: function() {
+      if (this.values.alpha !== 1) {
+        return this.values.hwb.concat([this.values.alpha])
+      }
+      return this.values.hwb;
+   },
+   cmykArray: function() {
+      return this.values.cmyk;
+   },
+   rgbaArray: function() {
+      var rgb = this.values.rgb;
+      return rgb.concat([this.values.alpha]);
+   },
+   hslaArray: function() {
+      var hsl = this.values.hsl;
+      return hsl.concat([this.values.alpha]);
+   },
+   alpha: function(val) {
+      if (val === undefined) {
+         return this.values.alpha;
+      }
+      this.setValues("alpha", val);
+      return this;
+   },
+
+   red: function(val) {
+      return this.setChannel("rgb", 0, val);
+   },
+   green: function(val) {
+      return this.setChannel("rgb", 1, val);
+   },
+   blue: function(val) {
+      return this.setChannel("rgb", 2, val);
+   },
+   hue: function(val) {
+      return this.setChannel("hsl", 0, val);
+   },
+   saturation: function(val) {
+      return this.setChannel("hsl", 1, val);
+   },
+   lightness: function(val) {
+      return this.setChannel("hsl", 2, val);
+   },
+   saturationv: function(val) {
+      return this.setChannel("hsv", 1, val);
+   },
+   whiteness: function(val) {
+      return this.setChannel("hwb", 1, val);
+   },
+   blackness: function(val) {
+      return this.setChannel("hwb", 2, val);
+   },
+   value: function(val) {
+      return this.setChannel("hsv", 2, val);
+   },
+   cyan: function(val) {
+      return this.setChannel("cmyk", 0, val);
+   },
+   magenta: function(val) {
+      return this.setChannel("cmyk", 1, val);
+   },
+   yellow: function(val) {
+      return this.setChannel("cmyk", 2, val);
+   },
+   black: function(val) {
+      return this.setChannel("cmyk", 3, val);
+   },
+
+   hexString: function() {
+      return string.hexString(this.values.rgb);
+   },
+   rgbString: function() {
+      return string.rgbString(this.values.rgb, this.values.alpha);
+   },
+   rgbaString: function() {
+      return string.rgbaString(this.values.rgb, this.values.alpha);
+   },
+   percentString: function() {
+      return string.percentString(this.values.rgb, this.values.alpha);
+   },
+   hslString: function() {
+      return string.hslString(this.values.hsl, this.values.alpha);
+   },
+   hslaString: function() {
+      return string.hslaString(this.values.hsl, this.values.alpha);
+   },
+   hwbString: function() {
+      return string.hwbString(this.values.hwb, this.values.alpha);
+   },
+   keyword: function() {
+      return string.keyword(this.values.rgb, this.values.alpha);
+   },
+
+   rgbNumber: function() {
+      return (this.values.rgb[0] << 16) | (this.values.rgb[1] << 8) | this.values.rgb[2];
+   },
+
+   luminosity: function() {
+      // http://www.w3.org/TR/WCAG20/#relativeluminancedef
+      var rgb = this.values.rgb;
+      var lum = [];
+      for (var i = 0; i < rgb.length; i++) {
+         var chan = rgb[i] / 255;
+         lum[i] = (chan <= 0.03928) ? chan / 12.92
+                  : Math.pow(((chan + 0.055) / 1.055), 2.4)
+      }
+      return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
+   },
+
+   contrast: function(color2) {
+      // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
+      var lum1 = this.luminosity();
+      var lum2 = color2.luminosity();
+      if (lum1 > lum2) {
+         return (lum1 + 0.05) / (lum2 + 0.05)
+      };
+      return (lum2 + 0.05) / (lum1 + 0.05);
+   },
+
+   level: function(color2) {
+     var contrastRatio = this.contrast(color2);
+     return (contrastRatio >= 7.1)
+       ? 'AAA'
+       : (contrastRatio >= 4.5)
+        ? 'AA'
+        : '';
+   },
+
+   dark: function() {
+      // YIQ equation from http://24ways.org/2010/calculating-color-contrast
+      var rgb = this.values.rgb,
+          yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
+      return yiq < 128;
+   },
+
+   light: function() {
+      return !this.dark();
+   },
+
+   negate: function() {
+      var rgb = []
+      for (var i = 0; i < 3; i++) {
+         rgb[i] = 255 - this.values.rgb[i];
+      }
+      this.setValues("rgb", rgb);
+      return this;
+   },
+
+   lighten: function(ratio) {
+      this.values.hsl[2] += this.values.hsl[2] * ratio;
+      this.setValues("hsl", this.values.hsl);
+      return this;
+   },
+
+   darken: function(ratio) {
+      this.values.hsl[2] -= this.values.hsl[2] * ratio;
+      this.setValues("hsl", this.values.hsl);
+      return this;
+   },
+
+   saturate: function(ratio) {
+      this.values.hsl[1] += this.values.hsl[1] * ratio;
+      this.setValues("hsl", this.values.hsl);
+      return this;
+   },
+
+   desaturate: function(ratio) {
+      this.values.hsl[1] -= this.values.hsl[1] * ratio;
+      this.setValues("hsl", this.values.hsl);
+      return this;
+   },
+
+   whiten: function(ratio) {
+      this.values.hwb[1] += this.values.hwb[1] * ratio;
+      this.setValues("hwb", this.values.hwb);
+      return this;
+   },
+
+   blacken: function(ratio) {
+      this.values.hwb[2] += this.values.hwb[2] * ratio;
+      this.setValues("hwb", this.values.hwb);
+      return this;
+   },
+
+   greyscale: function() {
+      var rgb = this.values.rgb;
+      // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
+      var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
+      this.setValues("rgb", [val, val, val]);
+      return this;
+   },
+
+   clearer: function(ratio) {
+      this.setValues("alpha", this.values.alpha - (this.values.alpha * ratio));
+      return this;
+   },
+
+   opaquer: function(ratio) {
+      this.setValues("alpha", this.values.alpha + (this.values.alpha * ratio));
+      return this;
+   },
+
+   rotate: function(degrees) {
+      var hue = this.values.hsl[0];
+      hue = (hue + degrees) % 360;
+      hue = hue < 0 ? 360 + hue : hue;
+      this.values.hsl[0] = hue;
+      this.setValues("hsl", this.values.hsl);
+      return this;
+   },
+
+   mix: function(color2, weight) {
+      weight = 1 - (weight == null ? 0.5 : weight);
+
+      // algorithm from Sass's mix(). Ratio of first color in mix is
+      // determined by the alphas of both colors and the weight
+      var t1 = weight * 2 - 1,
+          d = this.alpha() - color2.alpha();
+
+      var weight1 = (((t1 * d == -1) ? t1 : (t1 + d) / (1 + t1 * d)) + 1) / 2;
+      var weight2 = 1 - weight1;
+
+      var rgb = this.rgbArray();
+      var rgb2 = color2.rgbArray();
+
+      for (var i = 0; i < rgb.length; i++) {
+         rgb[i] = rgb[i] * weight1 + rgb2[i] * weight2;
+      }
+      this.setValues("rgb", rgb);
+
+      var alpha = this.alpha() * weight + color2.alpha() * (1 - weight);
+      this.setValues("alpha", alpha);
+
+      return this;
+   },
+
+   toJSON: function() {
+     return this.rgb();
+   },
+
+   clone: function() {
+     return new Color(this.rgb());
+   }
+}
+
+
+Color.prototype.getValues = function(space) {
+   var vals = {};
+   for (var i = 0; i < space.length; i++) {
+      vals[space.charAt(i)] = this.values[space][i];
+   }
+   if (this.values.alpha != 1) {
+      vals["a"] = this.values.alpha;
+   }
+   // {r: 255, g: 255, b: 255, a: 0.4}
+   return vals;
+}
+
+Color.prototype.setValues = function(space, vals) {
+   var spaces = {
+      "rgb": ["red", "green", "blue"],
+      "hsl": ["hue", "saturation", "lightness"],
+      "hsv": ["hue", "saturation", "value"],
+      "hwb": ["hue", "whiteness", "blackness"],
+      "cmyk": ["cyan", "magenta", "yellow", "black"]
+   };
+
+   var maxes = {
+      "rgb": [255, 255, 255],
+      "hsl": [360, 100, 100],
+      "hsv": [360, 100, 100],
+      "hwb": [360, 100, 100],
+      "cmyk": [100, 100, 100, 100]
+   };
+
+   var alpha = 1;
+   if (space == "alpha") {
+      alpha = vals;
+   }
+   else if (vals.length) {
+      // [10, 10, 10]
+      this.values[space] = vals.slice(0, space.length);
+      alpha = vals[space.length];
+   }
+   else if (vals[space.charAt(0)] !== undefined) {
+      // {r: 10, g: 10, b: 10}
+      for (var i = 0; i < space.length; i++) {
+        this.values[space][i] = vals[space.charAt(i)];
+      }
+      alpha = vals.a;
+   }
+   else if (vals[spaces[space][0]] !== undefined) {
+      // {red: 10, green: 10, blue: 10}
+      var chans = spaces[space];
+      for (var i = 0; i < space.length; i++) {
+        this.values[space][i] = vals[chans[i]];
+      }
+      alpha = vals.alpha;
+   }
+   this.values.alpha = Math.max(0, Math.min(1, (alpha !== undefined ? alpha : this.values.alpha) ));
+   if (space == "alpha") {
+      return;
+   }
+
+   // cap values of the space prior converting all values
+   for (var i = 0; i < space.length; i++) {
+      var capped = Math.max(0, Math.min(maxes[space][i], this.values[space][i]));
+      this.values[space][i] = Math.round(capped);
+   }
+
+   // convert to all the other color spaces
+   for (var sname in spaces) {
+      if (sname != space) {
+         this.values[sname] = convert[space][sname](this.values[space])
+      }
+
+      // cap values
+      for (var i = 0; i < sname.length; i++) {
+         var capped = Math.max(0, Math.min(maxes[sname][i], this.values[sname][i]));
+         this.values[sname][i] = Math.round(capped);
+      }
+   }
+   return true;
+}
+
+Color.prototype.setSpace = function(space, args) {
+   var vals = args[0];
+   if (vals === undefined) {
+      // color.rgb()
+      return this.getValues(space);
+   }
+   // color.rgb(10, 10, 10)
+   if (typeof vals == "number") {
+      vals = Array.prototype.slice.call(args);
+   }
+   this.setValues(space, vals);
+   return this;
+}
+
+Color.prototype.setChannel = function(space, index, val) {
+   if (val === undefined) {
+      // color.red()
+      return this.values[space][index];
+   }
+   // color.red(100)
+   this.values[space][index] = val;
+   this.setValues(space, this.values[space]);
+   return this;
+}
+
+module.exports = Color;
+
+},{"color-convert":3,"color-string":4}],2:[function(require,module,exports){
+/* MIT license */
+
+module.exports = {
+  rgb2hsl: rgb2hsl,
+  rgb2hsv: rgb2hsv,
+  rgb2hwb: rgb2hwb,
+  rgb2cmyk: rgb2cmyk,
+  rgb2keyword: rgb2keyword,
+  rgb2xyz: rgb2xyz,
+  rgb2lab: rgb2lab,
+  rgb2lch: rgb2lch,
+
+  hsl2rgb: hsl2rgb,
+  hsl2hsv: hsl2hsv,
+  hsl2hwb: hsl2hwb,
+  hsl2cmyk: hsl2cmyk,
+  hsl2keyword: hsl2keyword,
+
+  hsv2rgb: hsv2rgb,
+  hsv2hsl: hsv2hsl,
+  hsv2hwb: hsv2hwb,
+  hsv2cmyk: hsv2cmyk,
+  hsv2keyword: hsv2keyword,
+
+  hwb2rgb: hwb2rgb,
+  hwb2hsl: hwb2hsl,
+  hwb2hsv: hwb2hsv,
+  hwb2cmyk: hwb2cmyk,
+  hwb2keyword: hwb2keyword,
+
+  cmyk2rgb: cmyk2rgb,
+  cmyk2hsl: cmyk2hsl,
+  cmyk2hsv: cmyk2hsv,
+  cmyk2hwb: cmyk2hwb,
+  cmyk2keyword: cmyk2keyword,
+
+  keyword2rgb: keyword2rgb,
+  keyword2hsl: keyword2hsl,
+  keyword2hsv: keyword2hsv,
+  keyword2hwb: keyword2hwb,
+  keyword2cmyk: keyword2cmyk,
+  keyword2lab: keyword2lab,
+  keyword2xyz: keyword2xyz,
+
+  xyz2rgb: xyz2rgb,
+  xyz2lab: xyz2lab,
+  xyz2lch: xyz2lch,
+
+  lab2xyz: lab2xyz,
+  lab2rgb: lab2rgb,
+  lab2lch: lab2lch,
+
+  lch2lab: lch2lab,
+  lch2xyz: lch2xyz,
+  lch2rgb: lch2rgb
+}
+
+
+function rgb2hsl(rgb) {
+  var r = rgb[0]/255,
+      g = rgb[1]/255,
+      b = rgb[2]/255,
+      min = Math.min(r, g, b),
+      max = Math.max(r, g, b),
+      delta = max - min,
+      h, s, l;
+
+  if (max == min)
+    h = 0;
+  else if (r == max)
+    h = (g - b) / delta;
+  else if (g == max)
+    h = 2 + (b - r) / delta;
+  else if (b == max)
+    h = 4 + (r - g)/ delta;
+
+  h = Math.min(h * 60, 360);
+
+  if (h < 0)
+    h += 360;
+
+  l = (min + max) / 2;
+
+  if (max == min)
+    s = 0;
+  else if (l <= 0.5)
+    s = delta / (max + min);
+  else
+    s = delta / (2 - max - min);
+
+  return [h, s * 100, l * 100];
+}
+
+function rgb2hsv(rgb) {
+  var r = rgb[0],
+      g = rgb[1],
+      b = rgb[2],
+      min = Math.min(r, g, b),
+      max = Math.max(r, g, b),
+      delta = max - min,
+      h, s, v;
+
+  if (max == 0)
+    s = 0;
+  else
+    s = (delta/max * 1000)/10;
+
+  if (max == min)
+    h = 0;
+  else if (r == max)
+    h = (g - b) / delta;
+  else if (g == max)
+    h = 2 + (b - r) / delta;
+  else if (b == max)
+    h = 4 + (r - g) / delta;
+
+  h = Math.min(h * 60, 360);
+
+  if (h < 0)
+    h += 360;
+
+  v = ((max / 255) * 1000) / 10;
+
+  return [h, s, v];
+}
+
+function rgb2hwb(rgb) {
+  var r = rgb[0],
+      g = rgb[1],
+      b = rgb[2],
+      h = rgb2hsl(rgb)[0],
+      w = 1/255 * Math.min(r, Math.min(g, b)),
+      b = 1 - 1/255 * Math.max(r, Math.max(g, b));
+
+  return [h, w * 100, b * 100];
+}
+
+function rgb2cmyk(rgb) {
+  var r = rgb[0] / 255,
+      g = rgb[1] / 255,
+      b = rgb[2] / 255,
+      c, m, y, k;
+
+  k = Math.min(1 - r, 1 - g, 1 - b);
+  c = (1 - r - k) / (1 - k) || 0;
+  m = (1 - g - k) / (1 - k) || 0;
+  y = (1 - b - k) / (1 - k) || 0;
+  return [c * 100, m * 100, y * 100, k * 100];
+}
+
+function rgb2keyword(rgb) {
+  return reverseKeywords[JSON.stringify(rgb)];
+}
+
+function rgb2xyz(rgb) {
+  var r = rgb[0] / 255,
+      g = rgb[1] / 255,
+      b = rgb[2] / 255;
+
+  // assume sRGB
+  r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
+  g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
+  b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
+
+  var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
+  var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
+  var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
+
+  return [x * 100, y *100, z * 100];
+}
+
+function rgb2lab(rgb) {
+  var xyz = rgb2xyz(rgb),
+        x = xyz[0],
+        y = xyz[1],
+        z = xyz[2],
+        l, a, b;
+
+  x /= 95.047;
+  y /= 100;
+  z /= 108.883;
+
+  x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+  y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+  z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+  l = (116 * y) - 16;
+  a = 500 * (x - y);
+  b = 200 * (y - z);
+
+  return [l, a, b];
+}
+
+function rgb2lch(args) {
+  return lab2lch(rgb2lab(args));
+}
+
+function hsl2rgb(hsl) {
+  var h = hsl[0] / 360,
+      s = hsl[1] / 100,
+      l = hsl[2] / 100,
+      t1, t2, t3, rgb, val;
+
+  if (s == 0) {
+    val = l * 255;
+    return [val, val, val];
+  }
+
+  if (l < 0.5)
+    t2 = l * (1 + s);
+  else
+    t2 = l + s - l * s;
+  t1 = 2 * l - t2;
+
+  rgb = [0, 0, 0];
+  for (var i = 0; i < 3; i++) {
+    t3 = h + 1 / 3 * - (i - 1);
+    t3 < 0 && t3++;
+    t3 > 1 && t3--;
+
+    if (6 * t3 < 1)
+      val = t1 + (t2 - t1) * 6 * t3;
+    else if (2 * t3 < 1)
+      val = t2;
+    else if (3 * t3 < 2)
+      val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
+    else
+      val = t1;
+
+    rgb[i] = val * 255;
+  }
+
+  return rgb;
+}
+
+function hsl2hsv(hsl) {
+  var h = hsl[0],
+      s = hsl[1] / 100,
+      l = hsl[2] / 100,
+      sv, v;
+  l *= 2;
+  s *= (l <= 1) ? l : 2 - l;
+  v = (l + s) / 2;
+  sv = (2 * s) / (l + s);
+  return [h, sv * 100, v * 100];
+}
+
+function hsl2hwb(args) {
+  return rgb2hwb(hsl2rgb(args));
+}
+
+function hsl2cmyk(args) {
+  return rgb2cmyk(hsl2rgb(args));
+}
+
+function hsl2keyword(args) {
+  return rgb2keyword(hsl2rgb(args));
+}
+
+
+function hsv2rgb(hsv) {
+  var h = hsv[0] / 60,
+      s = hsv[1] / 100,
+      v = hsv[2] / 100,
+      hi = Math.floor(h) % 6;
+
+  var f = h - Math.floor(h),
+      p = 255 * v * (1 - s),
+      q = 255 * v * (1 - (s * f)),
+      t = 255 * v * (1 - (s * (1 - f))),
+      v = 255 * v;
+
+  switch(hi) {
+    case 0:
+      return [v, t, p];
+    case 1:
+      return [q, v, p];
+    case 2:
+      return [p, v, t];
+    case 3:
+      return [p, q, v];
+    case 4:
+      return [t, p, v];
+    case 5:
+      return [v, p, q];
+  }
+}
+
+function hsv2hsl(hsv) {
+  var h = hsv[0],
+      s = hsv[1] / 100,
+      v = hsv[2] / 100,
+      sl, l;
+
+  l = (2 - s) * v;
+  sl = s * v;
+  sl /= (l <= 1) ? l : 2 - l;
+  sl = sl || 0;
+  l /= 2;
+  return [h, sl * 100, l * 100];
+}
+
+function hsv2hwb(args) {
+  return rgb2hwb(hsv2rgb(args))
+}
+
+function hsv2cmyk(args) {
+  return rgb2cmyk(hsv2rgb(args));
+}
+
+function hsv2keyword(args) {
+  return rgb2keyword(hsv2rgb(args));
+}
+
+// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
+function hwb2rgb(hwb) {
+  var h = hwb[0] / 360,
+      wh = hwb[1] / 100,
+      bl = hwb[2] / 100,
+      ratio = wh + bl,
+      i, v, f, n;
+
+  // wh + bl cant be > 1
+  if (ratio > 1) {
+    wh /= ratio;
+    bl /= ratio;
+  }
+
+  i = Math.floor(6 * h);
+  v = 1 - bl;
+  f = 6 * h - i;
+  if ((i & 0x01) != 0) {
+    f = 1 - f;
+  }
+  n = wh + f * (v - wh);  // linear interpolation
+
+  switch (i) {
+    default:
+    case 6:
+    case 0: r = v; g = n; b = wh; break;
+    case 1: r = n; g = v; b = wh; break;
+    case 2: r = wh; g = v; b = n; break;
+    case 3: r = wh; g = n; b = v; break;
+    case 4: r = n; g = wh; b = v; break;
+    case 5: r = v; g = wh; b = n; break;
+  }
+
+  return [r * 255, g * 255, b * 255];
+}
+
+function hwb2hsl(args) {
+  return rgb2hsl(hwb2rgb(args));
+}
+
+function hwb2hsv(args) {
+  return rgb2hsv(hwb2rgb(args));
+}
+
+function hwb2cmyk(args) {
+  return rgb2cmyk(hwb2rgb(args));
+}
+
+function hwb2keyword(args) {
+  return rgb2keyword(hwb2rgb(args));
+}
+
+function cmyk2rgb(cmyk) {
+  var c = cmyk[0] / 100,
+      m = cmyk[1] / 100,
+      y = cmyk[2] / 100,
+      k = cmyk[3] / 100,
+      r, g, b;
+
+  r = 1 - Math.min(1, c * (1 - k) + k);
+  g = 1 - Math.min(1, m * (1 - k) + k);
+  b = 1 - Math.min(1, y * (1 - k) + k);
+  return [r * 255, g * 255, b * 255];
+}
+
+function cmyk2hsl(args) {
+  return rgb2hsl(cmyk2rgb(args));
+}
+
+function cmyk2hsv(args) {
+  return rgb2hsv(cmyk2rgb(args));
+}
+
+function cmyk2hwb(args) {
+  return rgb2hwb(cmyk2rgb(args));
+}
+
+function cmyk2keyword(args) {
+  return rgb2keyword(cmyk2rgb(args));
+}
+
+
+function xyz2rgb(xyz) {
+  var x = xyz[0] / 100,
+      y = xyz[1] / 100,
+      z = xyz[2] / 100,
+      r, g, b;
+
+  r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
+  g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
+  b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
+
+  // assume sRGB
+  r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
+    : r = (r * 12.92);
+
+  g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
+    : g = (g * 12.92);
+
+  b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
+    : b = (b * 12.92);
+
+  r = Math.min(Math.max(0, r), 1);
+  g = Math.min(Math.max(0, g), 1);
+  b = Math.min(Math.max(0, b), 1);
+
+  return [r * 255, g * 255, b * 255];
+}
+
+function xyz2lab(xyz) {
+  var x = xyz[0],
+      y = xyz[1],
+      z = xyz[2],
+      l, a, b;
+
+  x /= 95.047;
+  y /= 100;
+  z /= 108.883;
+
+  x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+  y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+  z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+  l = (116 * y) - 16;
+  a = 500 * (x - y);
+  b = 200 * (y - z);
+
+  return [l, a, b];
+}
+
+function xyz2lch(args) {
+  return lab2lch(xyz2lab(args));
+}
+
+function lab2xyz(lab) {
+  var l = lab[0],
+      a = lab[1],
+      b = lab[2],
+      x, y, z, y2;
+
+  if (l <= 8) {
+    y = (l * 100) / 903.3;
+    y2 = (7.787 * (y / 100)) + (16 / 116);
+  } else {
+    y = 100 * Math.pow((l + 16) / 116, 3);
+    y2 = Math.pow(y / 100, 1/3);
+  }
+
+  x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
+
+  z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
+
+  return [x, y, z];
+}
+
+function lab2lch(lab) {
+  var l = lab[0],
+      a = lab[1],
+      b = lab[2],
+      hr, h, c;
+
+  hr = Math.atan2(b, a);
+  h = hr * 360 / 2 / Math.PI;
+  if (h < 0) {
+    h += 360;
+  }
+  c = Math.sqrt(a * a + b * b);
+  return [l, c, h];
+}
+
+function lab2rgb(args) {
+  return xyz2rgb(lab2xyz(args));
+}
+
+function lch2lab(lch) {
+  var l = lch[0],
+      c = lch[1],
+      h = lch[2],
+      a, b, hr;
+
+  hr = h / 360 * 2 * Math.PI;
+  a = c * Math.cos(hr);
+  b = c * Math.sin(hr);
+  return [l, a, b];
+}
+
+function lch2xyz(args) {
+  return lab2xyz(lch2lab(args));
+}
+
+function lch2rgb(args) {
+  return lab2rgb(lch2lab(args));
+}
+
+function keyword2rgb(keyword) {
+  return cssKeywords[keyword];
+}
+
+function keyword2hsl(args) {
+  return rgb2hsl(keyword2rgb(args));
+}
+
+function keyword2hsv(args) {
+  return rgb2hsv(keyword2rgb(args));
+}
+
+function keyword2hwb(args) {
+  return rgb2hwb(keyword2rgb(args));
+}
+
+function keyword2cmyk(args) {
+  return rgb2cmyk(keyword2rgb(args));
+}
+
+function keyword2lab(args) {
+  return rgb2lab(keyword2rgb(args));
+}
+
+function keyword2xyz(args) {
+  return rgb2xyz(keyword2rgb(args));
+}
+
+var cssKeywords = {
+  aliceblue:  [240,248,255],
+  antiquewhite: [250,235,215],
+  aqua: [0,255,255],
+  aquamarine: [127,255,212],
+  azure:  [240,255,255],
+  beige:  [245,245,220],
+  bisque: [255,228,196],
+  black:  [0,0,0],
+  blanchedalmond: [255,235,205],
+  blue: [0,0,255],
+  blueviolet: [138,43,226],
+  brown:  [165,42,42],
+  burlywood:  [222,184,135],
+  cadetblue:  [95,158,160],
+  chartreuse: [127,255,0],
+  chocolate:  [210,105,30],
+  coral:  [255,127,80],
+  cornflowerblue: [100,149,237],
+  cornsilk: [255,248,220],
+  crimson:  [220,20,60],
+  cyan: [0,255,255],
+  darkblue: [0,0,139],
+  darkcyan: [0,139,139],
+  darkgoldenrod:  [184,134,11],
+  darkgray: [169,169,169],
+  darkgreen:  [0,100,0],
+  darkgrey: [169,169,169],
+  darkkhaki:  [189,183,107],
+  darkmagenta:  [139,0,139],
+  darkolivegreen: [85,107,47],
+  darkorange: [255,140,0],
+  darkorchid: [153,50,204],
+  darkred:  [139,0,0],
+  darksalmon: [233,150,122],
+  darkseagreen: [143,188,143],
+  darkslateblue:  [72,61,139],
+  darkslategray:  [47,79,79],
+  darkslategrey:  [47,79,79],
+  darkturquoise:  [0,206,209],
+  darkviolet: [148,0,211],
+  deeppink: [255,20,147],
+  deepskyblue:  [0,191,255],
+  dimgray:  [105,105,105],
+  dimgrey:  [105,105,105],
+  dodgerblue: [30,144,255],
+  firebrick:  [178,34,34],
+  floralwhite:  [255,250,240],
+  forestgreen:  [34,139,34],
+  fuchsia:  [255,0,255],
+  gainsboro:  [220,220,220],
+  ghostwhite: [248,248,255],
+  gold: [255,215,0],
+  goldenrod:  [218,165,32],
+  gray: [128,128,128],
+  green:  [0,128,0],
+  greenyellow:  [173,255,47],
+  grey: [128,128,128],
+  honeydew: [240,255,240],
+  hotpink:  [255,105,180],
+  indianred:  [205,92,92],
+  indigo: [75,0,130],
+  ivory:  [255,255,240],
+  khaki:  [240,230,140],
+  lavender: [230,230,250],
+  lavenderblush:  [255,240,245],
+  lawngreen:  [124,252,0],
+  lemonchiffon: [255,250,205],
+  lightblue:  [173,216,230],
+  lightcoral: [240,128,128],
+  lightcyan:  [224,255,255],
+  lightgoldenrodyellow: [250,250,210],
+  lightgray:  [211,211,211],
+  lightgreen: [144,238,144],
+  lightgrey:  [211,211,211],
+  lightpink:  [255,182,193],
+  lightsalmon:  [255,160,122],
+  lightseagreen:  [32,178,170],
+  lightskyblue: [135,206,250],
+  lightslategray: [119,136,153],
+  lightslategrey: [119,136,153],
+  lightsteelblue: [176,196,222],
+  lightyellow:  [255,255,224],
+  lime: [0,255,0],
+  limegreen:  [50,205,50],
+  linen:  [250,240,230],
+  magenta:  [255,0,255],
+  maroon: [128,0,0],
+  mediumaquamarine: [102,205,170],
+  mediumblue: [0,0,205],
+  mediumorchid: [186,85,211],
+  mediumpurple: [147,112,219],
+  mediumseagreen: [60,179,113],
+  mediumslateblue:  [123,104,238],
+  mediumspringgreen:  [0,250,154],
+  mediumturquoise:  [72,209,204],
+  mediumvioletred:  [199,21,133],
+  midnightblue: [25,25,112],
+  mintcream:  [245,255,250],
+  mistyrose:  [255,228,225],
+  moccasin: [255,228,181],
+  navajowhite:  [255,222,173],
+  navy: [0,0,128],
+  oldlace:  [253,245,230],
+  olive:  [128,128,0],
+  olivedrab:  [107,142,35],
+  orange: [255,165,0],
+  orangered:  [255,69,0],
+  orchid: [218,112,214],
+  palegoldenrod:  [238,232,170],
+  palegreen:  [152,251,152],
+  paleturquoise:  [175,238,238],
+  palevioletred:  [219,112,147],
+  papayawhip: [255,239,213],
+  peachpuff:  [255,218,185],
+  peru: [205,133,63],
+  pink: [255,192,203],
+  plum: [221,160,221],
+  powderblue: [176,224,230],
+  purple: [128,0,128],
+  rebeccapurple: [102, 51, 153],
+  red:  [255,0,0],
+  rosybrown:  [188,143,143],
+  royalblue:  [65,105,225],
+  saddlebrown:  [139,69,19],
+  salmon: [250,128,114],
+  sandybrown: [244,164,96],
+  seagreen: [46,139,87],
+  seashell: [255,245,238],
+  sienna: [160,82,45],
+  silver: [192,192,192],
+  skyblue:  [135,206,235],
+  slateblue:  [106,90,205],
+  slategray:  [112,128,144],
+  slategrey:  [112,128,144],
+  snow: [255,250,250],
+  springgreen:  [0,255,127],
+  steelblue:  [70,130,180],
+  tan:  [210,180,140],
+  teal: [0,128,128],
+  thistle:  [216,191,216],
+  tomato: [255,99,71],
+  turquoise:  [64,224,208],
+  violet: [238,130,238],
+  wheat:  [245,222,179],
+  white:  [255,255,255],
+  whitesmoke: [245,245,245],
+  yellow: [255,255,0],
+  yellowgreen:  [154,205,50]
+};
+
+var reverseKeywords = {};
+for (var key in cssKeywords) {
+  reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
+}
+
+},{}],3:[function(require,module,exports){
+var conversions = require("./conversions");
+
+var convert = function() {
+   return new Converter();
+}
+
+for (var func in conversions) {
+  // export Raw versions
+  convert[func + "Raw"] =  (function(func) {
+    // accept array or plain args
+    return function(arg) {
+      if (typeof arg == "number")
+        arg = Array.prototype.slice.call(arguments);
+      return conversions[func](arg);
+    }
+  })(func);
+
+  var pair = /(\w+)2(\w+)/.exec(func),
+      from = pair[1],
+      to = pair[2];
+
+  // export rgb2hsl and ["rgb"]["hsl"]
+  convert[from] = convert[from] || {};
+
+  convert[from][to] = convert[func] = (function(func) { 
+    return function(arg) {
+      if (typeof arg == "number")
+        arg = Array.prototype.slice.call(arguments);
+      
+      var val = conversions[func](arg);
+      if (typeof val == "string" || val === undefined)
+        return val; // keyword
+
+      for (var i = 0; i < val.length; i++)
+        val[i] = Math.round(val[i]);
+      return val;
+    }
+  })(func);
+}
+
+
+/* Converter does lazy conversion and caching */
+var Converter = function() {
+   this.convs = {};
+};
+
+/* Either get the values for a space or
+  set the values for a space, depending on args */
+Converter.prototype.routeSpace = function(space, args) {
+   var values = args[0];
+   if (values === undefined) {
+      // color.rgb()
+      return this.getValues(space);
+   }
+   // color.rgb(10, 10, 10)
+   if (typeof values == "number") {
+      values = Array.prototype.slice.call(args);        
+   }
+
+   return this.setValues(space, values);
+};
+  
+/* Set the values for a space, invalidating cache */
+Converter.prototype.setValues = function(space, values) {
+   this.space = space;
+   this.convs = {};
+   this.convs[space] = values;
+   return this;
+};
+
+/* Get the values for a space. If there's already
+  a conversion for the space, fetch it, otherwise
+  compute it */
+Converter.prototype.getValues = function(space) {
+   var vals = this.convs[space];
+   if (!vals) {
+      var fspace = this.space,
+          from = this.convs[fspace];
+      vals = convert[fspace][space](from);
+
+      this.convs[space] = vals;
+   }
+  return vals;
+};
+
+["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
+   Converter.prototype[space] = function(vals) {
+      return this.routeSpace(space, arguments);
+   }
+});
+
+module.exports = convert;
+},{"./conversions":2}],4:[function(require,module,exports){
+/* MIT license */
+var colorNames = require('color-name');
+
+module.exports = {
+   getRgba: getRgba,
+   getHsla: getHsla,
+   getRgb: getRgb,
+   getHsl: getHsl,
+   getHwb: getHwb,
+   getAlpha: getAlpha,
+
+   hexString: hexString,
+   rgbString: rgbString,
+   rgbaString: rgbaString,
+   percentString: percentString,
+   percentaString: percentaString,
+   hslString: hslString,
+   hslaString: hslaString,
+   hwbString: hwbString,
+   keyword: keyword
+}
+
+function getRgba(string) {
+   if (!string) {
+      return;
+   }
+   var abbr =  /^#([a-fA-F0-9]{3})$/,
+       hex =  /^#([a-fA-F0-9]{6})$/,
+       rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
+       per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
+       keyword = /(\D+)/;
+
+   var rgb = [0, 0, 0],
+       a = 1,
+       match = string.match(abbr);
+   if (match) {
+      match = match[1];
+      for (var i = 0; i < rgb.length; i++) {
+         rgb[i] = parseInt(match[i] + match[i], 16);
+      }
+   }
+   else if (match = string.match(hex)) {
+      match = match[1];
+      for (var i = 0; i < rgb.length; i++) {
+         rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
+      }
+   }
+   else if (match = string.match(rgba)) {
+      for (var i = 0; i < rgb.length; i++) {
+         rgb[i] = parseInt(match[i + 1]);
+      }
+      a = parseFloat(match[4]);
+   }
+   else if (match = string.match(per)) {
+      for (var i = 0; i < rgb.length; i++) {
+         rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
+      }
+      a = parseFloat(match[4]);
+   }
+   else if (match = string.match(keyword)) {
+      if (match[1] == "transparent") {
+         return [0, 0, 0, 0];
+      }
+      rgb = colorNames[match[1]];
+      if (!rgb) {
+         return;
+      }
+   }
+
+   for (var i = 0; i < rgb.length; i++) {
+      rgb[i] = scale(rgb[i], 0, 255);
+   }
+   if (!a && a != 0) {
+      a = 1;
+   }
+   else {
+      a = scale(a, 0, 1);
+   }
+   rgb[3] = a;
+   return rgb;
+}
+
+function getHsla(string) {
+   if (!string) {
+      return;
+   }
+   var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
+   var match = string.match(hsl);
+   if (match) {
+      var alpha = parseFloat(match[4]);
+      var h = scale(parseInt(match[1]), 0, 360),
+          s = scale(parseFloat(match[2]), 0, 100),
+          l = scale(parseFloat(match[3]), 0, 100),
+          a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
+      return [h, s, l, a];
+   }
+}
+
+function getHwb(string) {
+   if (!string) {
+      return;
+   }
+   var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
+   var match = string.match(hwb);
+   if (match) {
+    var alpha = parseFloat(match[4]);
+      var h = scale(parseInt(match[1]), 0, 360),
+          w = scale(parseFloat(match[2]), 0, 100),
+          b = scale(parseFloat(match[3]), 0, 100),
+          a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
+      return [h, w, b, a];
+   }
+}
+
+function getRgb(string) {
+   var rgba = getRgba(string);
+   return rgba && rgba.slice(0, 3);
+}
+
+function getHsl(string) {
+  var hsla = getHsla(string);
+  return hsla && hsla.slice(0, 3);
+}
+
+function getAlpha(string) {
+   var vals = getRgba(string);
+   if (vals) {
+      return vals[3];
+   }
+   else if (vals = getHsla(string)) {
+      return vals[3];
+   }
+   else if (vals = getHwb(string)) {
+      return vals[3];
+   }
+}
+
+// generators
+function hexString(rgb) {
+   return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
+              + hexDouble(rgb[2]);
+}
+
+function rgbString(rgba, alpha) {
+   if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
+      return rgbaString(rgba, alpha);
+   }
+   return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
+}
+
+function rgbaString(rgba, alpha) {
+   if (alpha === undefined) {
+      alpha = (rgba[3] !== undefined ? rgba[3] : 1);
+   }
+   return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
+           + ", " + alpha + ")";
+}
+
+function percentString(rgba, alpha) {
+   if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
+      return percentaString(rgba, alpha);
+   }
+   var r = Math.round(rgba[0]/255 * 100),
+       g = Math.round(rgba[1]/255 * 100),
+       b = Math.round(rgba[2]/255 * 100);
+
+   return "rgb(" + r + "%, " + g + "%, " + b + "%)";
+}
+
+function percentaString(rgba, alpha) {
+   var r = Math.round(rgba[0]/255 * 100),
+       g = Math.round(rgba[1]/255 * 100),
+       b = Math.round(rgba[2]/255 * 100);
+   return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
+}
+
+function hslString(hsla, alpha) {
+   if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
+      return hslaString(hsla, alpha);
+   }
+   return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
+}
+
+function hslaString(hsla, alpha) {
+   if (alpha === undefined) {
+      alpha = (hsla[3] !== undefined ? hsla[3] : 1);
+   }
+   return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
+           + alpha + ")";
+}
+
+// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
+// (hwb have alpha optional & 1 is default value)
+function hwbString(hwb, alpha) {
+   if (alpha === undefined) {
+      alpha = (hwb[3] !== undefined ? hwb[3] : 1);
+   }
+   return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
+           + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
+}
+
+function keyword(rgb) {
+  return reverseNames[rgb.slice(0, 3)];
+}
+
+// helpers
+function scale(num, min, max) {
+   return Math.min(Math.max(min, num), max);
+}
+
+function hexDouble(num) {
+  var str = num.toString(16).toUpperCase();
+  return (str.length < 2) ? "0" + str : str;
+}
+
+
+//create a list of reverse color names
+var reverseNames = {};
+for (var name in colorNames) {
+   reverseNames[colorNames[name]] = name;
+}
+
+},{"color-name":5}],5:[function(require,module,exports){
+module.exports={
+	"aliceblue": [240, 248, 255],
+	"antiquewhite": [250, 235, 215],
+	"aqua": [0, 255, 255],
+	"aquamarine": [127, 255, 212],
+	"azure": [240, 255, 255],
+	"beige": [245, 245, 220],
+	"bisque": [255, 228, 196],
+	"black": [0, 0, 0],
+	"blanchedalmond": [255, 235, 205],
+	"blue": [0, 0, 255],
+	"blueviolet": [138, 43, 226],
+	"brown": [165, 42, 42],
+	"burlywood": [222, 184, 135],
+	"cadetblue": [95, 158, 160],
+	"chartreuse": [127, 255, 0],
+	"chocolate": [210, 105, 30],
+	"coral": [255, 127, 80],
+	"cornflowerblue": [100, 149, 237],
+	"cornsilk": [255, 248, 220],
+	"crimson": [220, 20, 60],
+	"cyan": [0, 255, 255],
+	"darkblue": [0, 0, 139],
+	"darkcyan": [0, 139, 139],
+	"darkgoldenrod": [184, 134, 11],
+	"darkgray": [169, 169, 169],
+	"darkgreen": [0, 100, 0],
+	"darkgrey": [169, 169, 169],
+	"darkkhaki": [189, 183, 107],
+	"darkmagenta": [139, 0, 139],
+	"darkolivegreen": [85, 107, 47],
+	"darkorange": [255, 140, 0],
+	"darkorchid": [153, 50, 204],
+	"darkred": [139, 0, 0],
+	"darksalmon": [233, 150, 122],
+	"darkseagreen": [143, 188, 143],
+	"darkslateblue": [72, 61, 139],
+	"darkslategray": [47, 79, 79],
+	"darkslategrey": [47, 79, 79],
+	"darkturquoise": [0, 206, 209],
+	"darkviolet": [148, 0, 211],
+	"deeppink": [255, 20, 147],
+	"deepskyblue": [0, 191, 255],
+	"dimgray": [105, 105, 105],
+	"dimgrey": [105, 105, 105],
+	"dodgerblue": [30, 144, 255],
+	"firebrick": [178, 34, 34],
+	"floralwhite": [255, 250, 240],
+	"forestgreen": [34, 139, 34],
+	"fuchsia": [255, 0, 255],
+	"gainsboro": [220, 220, 220],
+	"ghostwhite": [248, 248, 255],
+	"gold": [255, 215, 0],
+	"goldenrod": [218, 165, 32],
+	"gray": [128, 128, 128],
+	"green": [0, 128, 0],
+	"greenyellow": [173, 255, 47],
+	"grey": [128, 128, 128],
+	"honeydew": [240, 255, 240],
+	"hotpink": [255, 105, 180],
+	"indianred": [205, 92, 92],
+	"indigo": [75, 0, 130],
+	"ivory": [255, 255, 240],
+	"khaki": [240, 230, 140],
+	"lavender": [230, 230, 250],
+	"lavenderblush": [255, 240, 245],
+	"lawngreen": [124, 252, 0],
+	"lemonchiffon": [255, 250, 205],
+	"lightblue": [173, 216, 230],
+	"lightcoral": [240, 128, 128],
+	"lightcyan": [224, 255, 255],
+	"lightgoldenrodyellow": [250, 250, 210],
+	"lightgray": [211, 211, 211],
+	"lightgreen": [144, 238, 144],
+	"lightgrey": [211, 211, 211],
+	"lightpink": [255, 182, 193],
+	"lightsalmon": [255, 160, 122],
+	"lightseagreen": [32, 178, 170],
+	"lightskyblue": [135, 206, 250],
+	"lightslategray": [119, 136, 153],
+	"lightslategrey": [119, 136, 153],
+	"lightsteelblue": [176, 196, 222],
+	"lightyellow": [255, 255, 224],
+	"lime": [0, 255, 0],
+	"limegreen": [50, 205, 50],
+	"linen": [250, 240, 230],
+	"magenta": [255, 0, 255],
+	"maroon": [128, 0, 0],
+	"mediumaquamarine": [102, 205, 170],
+	"mediumblue": [0, 0, 205],
+	"mediumorchid": [186, 85, 211],
+	"mediumpurple": [147, 112, 219],
+	"mediumseagreen": [60, 179, 113],
+	"mediumslateblue": [123, 104, 238],
+	"mediumspringgreen": [0, 250, 154],
+	"mediumturquoise": [72, 209, 204],
+	"mediumvioletred": [199, 21, 133],
+	"midnightblue": [25, 25, 112],
+	"mintcream": [245, 255, 250],
+	"mistyrose": [255, 228, 225],
+	"moccasin": [255, 228, 181],
+	"navajowhite": [255, 222, 173],
+	"navy": [0, 0, 128],
+	"oldlace": [253, 245, 230],
+	"olive": [128, 128, 0],
+	"olivedrab": [107, 142, 35],
+	"orange": [255, 165, 0],
+	"orangered": [255, 69, 0],
+	"orchid": [218, 112, 214],
+	"palegoldenrod": [238, 232, 170],
+	"palegreen": [152, 251, 152],
+	"paleturquoise": [175, 238, 238],
+	"palevioletred": [219, 112, 147],
+	"papayawhip": [255, 239, 213],
+	"peachpuff": [255, 218, 185],
+	"peru": [205, 133, 63],
+	"pink": [255, 192, 203],
+	"plum": [221, 160, 221],
+	"powderblue": [176, 224, 230],
+	"purple": [128, 0, 128],
+	"rebeccapurple": [102, 51, 153],
+	"red": [255, 0, 0],
+	"rosybrown": [188, 143, 143],
+	"royalblue": [65, 105, 225],
+	"saddlebrown": [139, 69, 19],
+	"salmon": [250, 128, 114],
+	"sandybrown": [244, 164, 96],
+	"seagreen": [46, 139, 87],
+	"seashell": [255, 245, 238],
+	"sienna": [160, 82, 45],
+	"silver": [192, 192, 192],
+	"skyblue": [135, 206, 235],
+	"slateblue": [106, 90, 205],
+	"slategray": [112, 128, 144],
+	"slategrey": [112, 128, 144],
+	"snow": [255, 250, 250],
+	"springgreen": [0, 255, 127],
+	"steelblue": [70, 130, 180],
+	"tan": [210, 180, 140],
+	"teal": [0, 128, 128],
+	"thistle": [216, 191, 216],
+	"tomato": [255, 99, 71],
+	"turquoise": [64, 224, 208],
+	"violet": [238, 130, 238],
+	"wheat": [245, 222, 179],
+	"white": [255, 255, 255],
+	"whitesmoke": [245, 245, 245],
+	"yellow": [255, 255, 0],
+	"yellowgreen": [154, 205, 50]
+}
+},{}],6:[function(require,module,exports){
+module.exports = require('./lib/geocrunch');
+},{"./lib/geocrunch":11}],7:[function(require,module,exports){
+// distance.js - Distance mixins for Paths
+
+var _ = require('underscore');
+
+var R = require('./constants').EARTHRADIUS;
+var units = require('./units');
+var flipCoords = require('./flipcoords');
+
+// Area conversions (from sqmeters)
+var convertFuncs = {
+  sqmeters: function (a) {
+    return a;
+  },
+  sqmiles: function (a) {
+    return units.sqMeters.toSqMiles(a);
+  },
+  acres: function (a) {
+    return units.sqMeters.toAcres(a);
+  }
+};
+
+// Calculates area in square meters
+// Method taken from OpenLayers API, https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270
+var calcArea = function (coordArray) {
+  var area = 0, i, l, c1, c2;
+  for (i = 0, l = coordArray.length; i < l; i += 1) {
+    c1 = coordArray[i];
+    c2 = coordArray[(i + 1) % coordArray.length]; // Access next item in array until last item is i, then accesses first (0)
+    area = area + units.degrees.toRadians(c2[0] - c1[0]) * (2 + Math.sin(units.degrees.toRadians(c1[1])) + Math.sin(units.degrees.toRadians(c2[1])));
+  }
+  return Math.abs(area * R * R / 2);
+};
+
+var calcCenter = function (coordArray) {
+  var offset = coordArray[0], twiceArea = 0, x = 0, y = 0, i, l, c1, c2, f;
+  if (coordArray.length === 1) {
+    return coordArray[0];
+  }
+  for (i = 0, l = coordArray.length; i < l; i += 1) {
+    c1 = coordArray[i];
+    c2 = coordArray[(i + 1) % coordArray.length]; // Access next item in array until last item is i, then accesses first (0)
+    f = (c1[1] - offset[1]) * (c2[0] - offset[0]) - (c2[1] - offset[1]) * (c1[0] - offset[0]);
+    twiceArea = twiceArea + f;
+    x = x + ((c1[0] + c2[0] - 2 * offset[0]) * f);
+    y = y + ((c1[1] + c2[1] - 2 * offset[1]) * f);
+  }
+  f = twiceArea * 3;
+  return [x / f + offset[0], y / f + offset[1]];
+};
+
+module.exports = {
+  _internalAreaCalc: function () {
+    // If not set, set this._calcedArea to total area in UNITS
+    // Checks for cache to prevent additional unnecessary calcs
+    if (!this._calcedArea) {
+      if (this._coords.length < 3) {
+        this._calcedArea = 0;
+      } else {
+        this._calcedArea = calcArea(this._coords);
+      }
+    }
+  },
+  _internalCenterCalc: function () {
+    if (!this._calcedCenter && this._coords.length) {
+      this._calcedCenter = calcCenter(this._coords);
+    }
+  },
+  area: function (options) {
+    var opts = _.extend({
+      units: 'sqmeters'
+    }, options);
+    this._internalAreaCalc();
+    if (_.isFunction(convertFuncs[opts.units])) {
+      return convertFuncs[opts.units](this._calcedArea);
+    }
+    // TODO. Handle non-matching units
+  },
+  center: function () {
+    this._internalCenterCalc();
+    return this._options.imBackwards === true ? flipCoords(this._calcedCenter) : this._calcedCenter;
+  }
+};
+},{"./constants":8,"./flipcoords":10,"./units":13,"underscore":14}],8:[function(require,module,exports){
+// utils/constants.js
+
+module.exports = {
+  EARTHRADIUS: 6371000 // R in meters
+};
+},{}],9:[function(require,module,exports){
+// distance.js - Distance mixins for Paths
+
+var _ = require('underscore');
+
+var R = require('./constants').EARTHRADIUS;
+var units = require('./units');
+
+// Distance conversions (from meters)
+var convertFuncs = {
+  meters: function (d) {
+    return d;
+  },
+  kilometers: function (d) {
+    return units.meters.toKilometers(d);
+  },
+  feet: function (d) {
+    return units.meters.toFeet(d);
+  },
+  miles: function (d) {
+    return units.meters.toMiles(d);
+  }
+};
+
+// Distance in meters
+// Always positive regardless of direction
+// Calculation based on Haversine Formula http://en.wikipedia.org/wiki/Haversine_formula
+// Another method is @ http://www.movable-type.co.uk/scripts/latlong-vincenty.html but seems way overcomplicated
+var calcDistance = function (coord1, coord2) {
+  var deltaLng = units.degrees.toRadians(coord1[0] - coord2[0]),
+      deltaLat = units.degrees.toRadians(coord1[1] - coord2[1]),
+      lat1 = units.degrees.toRadians(coord1[1]),
+      lat2 = units.degrees.toRadians(coord2[1]),
+      hvsLng = Math.sin(deltaLng / 2),
+      hvsLat = Math.sin(deltaLat / 2);
+
+  var a = hvsLat * hvsLat + hvsLng * hvsLng * Math.cos(lat1) * Math.cos(lat2);
+  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+};
+
+module.exports = {
+  _internalDistanceCalc: function () {
+    // If not set, set this._calcedDistance to total distance in meters
+    // Checks for cache to prevent additional unnecessary calcs
+    var distance = 0, i, l;
+    if (!this._calcedDistance) {
+      for (i = 0, l = this._coords.length; i < l; i += 1) {
+        if (i > 0) {
+          distance = distance + calcDistance(this._coords[i - 1], this._coords[i]);
+        }
+      }
+      this._calcedDistance = distance;
+    }
+  },
+  distance: function (options) {
+    var opts = _.extend({
+      units: 'meters'
+    }, options);
+    this._internalDistanceCalc();
+    if (_.isFunction(convertFuncs[opts.units])) {
+      return convertFuncs[opts.units](this._calcedDistance);
+    }
+    // TODO. Handle non-matching units
+  }
+};
+},{"./constants":8,"./units":13,"underscore":14}],10:[function(require,module,exports){
+// utils/flipcoords.js - Util functions for working with backwards coordinates [lat, lng]
+
+var _ = require('underscore');
+
+module.exports = function (backwardsCoordArray) {
+  return _.map(backwardsCoordArray, function (backwardsCoord) {
+    return [backwardsCoord[1], backwardsCoord[0]];
+  });
+};
+},{"underscore":14}],11:[function(require,module,exports){
+// geocrunch.js
+
+var _ = require('underscore');
+
+var Path = require('./path');
+var distanceMixins = require('./distance'),
+    areaMixins = require('./area');
+
+_.extend(Path.prototype, distanceMixins, areaMixins);
+
+exports.path = function (coords, options) {
+  return new Path(coords, options);
+};
+},{"./area":7,"./distance":9,"./path":12,"underscore":14}],12:[function(require,module,exports){
+// path.js - Object for working with a linear path of coordinates
+
+var flipCoords = require('./flipcoords');
+
+var Path = function (coords, options) {
+  this._options = options || {};
+
+  // Set this._coords... Think about flipping at time of calcs for less iterations/better perf. May risk code clarity and mixin ease.
+  coords = coords || [];
+  this._coords = this._options.imBackwards === true ? flipCoords(coords) : coords;
+};
+
+module.exports = Path;
+
+},{"./flipcoords":10}],13:[function(require,module,exports){
+// units.js - Standard unit conversions
+
+exports.meters = {
+  toFeet: function (m) {
+    return m * 3.28084;
+  },
+  toKilometers: function (m) {
+    return m * 0.001;
+  },
+  toMiles: function (m) {
+    return m * 0.000621371;
+  }
+};
+
+exports.sqMeters = {
+  toSqMiles: function (m) {
+    return m * 0.000000386102;
+  },
+  toAcres: function (m) {
+    return m * 0.000247105;
+  }
+};
+
+exports.degrees = {
+  toRadians: function (d) {
+    return d * Math.PI / 180;
+  }
+};
+},{}],14:[function(require,module,exports){
+//     Underscore.js 1.5.2
+//     http://underscorejs.org
+//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `exports` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Establish the object that gets returned to break out of a loop iteration.
+  var breaker = {};
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var
+    push             = ArrayProto.push,
+    slice            = ArrayProto.slice,
+    concat           = ArrayProto.concat,
+    toString         = ObjProto.toString,
+    hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+    nativeForEach      = ArrayProto.forEach,
+    nativeMap          = ArrayProto.map,
+    nativeReduce       = ArrayProto.reduce,
+    nativeReduceRight  = ArrayProto.reduceRight,
+    nativeFilter       = ArrayProto.filter,
+    nativeEvery        = ArrayProto.every,
+    nativeSome         = ArrayProto.some,
+    nativeIndexOf      = ArrayProto.indexOf,
+    nativeLastIndexOf  = ArrayProto.lastIndexOf,
+    nativeIsArray      = Array.isArray,
+    nativeKeys         = Object.keys,
+    nativeBind         = FuncProto.bind;
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object via a string identifier,
+  // for Closure Compiler "advanced" mode.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.5.2';
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles objects with the built-in `forEach`, arrays, and raw objects.
+  // Delegates to **ECMAScript 5**'s native `forEach` if available.
+  var each = _.each = _.forEach = function(obj, iterator, context) {
+    if (obj == null) return;
+    if (nativeForEach && obj.forEach === nativeForEach) {
+      obj.forEach(iterator, context);
+    } else if (obj.length === +obj.length) {
+      for (var i = 0, length = obj.length; i < length; i++) {
+        if (iterator.call(context, obj[i], i, obj) === breaker) return;
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (var i = 0, length = keys.length; i < length; i++) {
+        if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
+      }
+    }
+  };
+
+  // Return the results of applying the iterator to each element.
+  // Delegates to **ECMAScript 5**'s native `map` if available.
+  _.map = _.collect = function(obj, iterator, context) {
+    var results = [];
+    if (obj == null) return results;
+    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+    each(obj, function(value, index, list) {
+      results.push(iterator.call(context, value, index, list));
+    });
+    return results;
+  };
+
+  var reduceError = 'Reduce of empty array with no initial value';
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+    var initial = arguments.length > 2;
+    if (obj == null) obj = [];
+    if (nativeReduce && obj.reduce === nativeReduce) {
+      if (context) iterator = _.bind(iterator, context);
+      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+    }
+    each(obj, function(value, index, list) {
+      if (!initial) {
+        memo = value;
+        initial = true;
+      } else {
+        memo = iterator.call(context, memo, value, index, list);
+      }
+    });
+    if (!initial) throw new TypeError(reduceError);
+    return memo;
+  };
+
+  // The right-associative version of reduce, also known as `foldr`.
+  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+    var initial = arguments.length > 2;
+    if (obj == null) obj = [];
+    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+      if (context) iterator = _.bind(iterator, context);
+      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+    }
+    var length = obj.length;
+    if (length !== +length) {
+      var keys = _.keys(obj);
+      length = keys.length;
+    }
+    each(obj, function(value, index, list) {
+      index = keys ? keys[--length] : --length;
+      if (!initial) {
+        memo = obj[index];
+        initial = true;
+      } else {
+        memo = iterator.call(context, memo, obj[index], index, list);
+      }
+    });
+    if (!initial) throw new TypeError(reduceError);
+    return memo;
+  };
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, iterator, context) {
+    var result;
+    any(obj, function(value, index, list) {
+      if (iterator.call(context, value, index, list)) {
+        result = value;
+        return true;
+      }
+    });
+    return result;
+  };
+
+  // Return all the elements that pass a truth test.
+  // Delegates to **ECMAScript 5**'s native `filter` if available.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, iterator, context) {
+    var results = [];
+    if (obj == null) return results;
+    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+    each(obj, function(value, index, list) {
+      if (iterator.call(context, value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, iterator, context) {
+    return _.filter(obj, function(value, index, list) {
+      return !iterator.call(context, value, index, list);
+    }, context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Delegates to **ECMAScript 5**'s native `every` if available.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, iterator, context) {
+    iterator || (iterator = _.identity);
+    var result = true;
+    if (obj == null) return result;
+    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+    each(obj, function(value, index, list) {
+      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+    });
+    return !!result;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Delegates to **ECMAScript 5**'s native `some` if available.
+  // Aliased as `any`.
+  var any = _.some = _.any = function(obj, iterator, context) {
+    iterator || (iterator = _.identity);
+    var result = false;
+    if (obj == null) return result;
+    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+    each(obj, function(value, index, list) {
+      if (result || (result = iterator.call(context, value, index, list))) return breaker;
+    });
+    return !!result;
+  };
+
+  // Determine if the array or object contains a given value (using `===`).
+  // Aliased as `include`.
+  _.contains = _.include = function(obj, target) {
+    if (obj == null) return false;
+    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+    return any(obj, function(value) {
+      return value === target;
+    });
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      return (isFunc ? method : value[method]).apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, function(value){ return value[key]; });
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs, first) {
+    if (_.isEmpty(attrs)) return first ? void 0 : [];
+    return _[first ? 'find' : 'filter'](obj, function(value) {
+      for (var key in attrs) {
+        if (attrs[key] !== value[key]) return false;
+      }
+      return true;
+    });
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.where(obj, attrs, true);
+  };
+
+  // Return the maximum element or (element-based computation).
+  // Can't optimize arrays of integers longer than 65,535 elements.
+  // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797)
+  _.max = function(obj, iterator, context) {
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+      return Math.max.apply(Math, obj);
+    }
+    if (!iterator && _.isEmpty(obj)) return -Infinity;
+    var result = {computed : -Infinity, value: -Infinity};
+    each(obj, function(value, index, list) {
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
+      computed > result.computed && (result = {value : value, computed : computed});
+    });
+    return result.value;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iterator, context) {
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+      return Math.min.apply(Math, obj);
+    }
+    if (!iterator && _.isEmpty(obj)) return Infinity;
+    var result = {computed : Infinity, value: Infinity};
+    each(obj, function(value, index, list) {
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
+      computed < result.computed && (result = {value : value, computed : computed});
+    });
+    return result.value;
+  };
+
+  // Shuffle an array, using the modern version of the 
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  _.shuffle = function(obj) {
+    var rand;
+    var index = 0;
+    var shuffled = [];
+    each(obj, function(value) {
+      rand = _.random(index++);
+      shuffled[index - 1] = shuffled[rand];
+      shuffled[rand] = value;
+    });
+    return shuffled;
+  };
+
+  // Sample **n** random values from an array.
+  // If **n** is not specified, returns a single random element from the array.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (arguments.length < 2 || guard) {
+      return obj[_.random(obj.length - 1)];
+    }
+    return _.shuffle(obj).slice(0, Math.max(0, n));
+  };
+
+  // An internal function to generate lookup iterators.
+  var lookupIterator = function(value) {
+    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+  };
+
+  // Sort the object's values by a criterion produced by an iterator.
+  _.sortBy = function(obj, value, context) {
+    var iterator = lookupIterator(value);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value: value,
+        index: index,
+        criteria: iterator.call(context, value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior) {
+    return function(obj, value, context) {
+      var result = {};
+      var iterator = value == null ? _.identity : lookupIterator(value);
+      each(obj, function(value, index) {
+        var key = iterator.call(context, value, index, obj);
+        behavior(result, key, value);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, key, value) {
+    (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, key, value) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, key) {
+    _.has(result, key) ? result[key]++ : result[key] = 1;
+  });
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iterator, context) {
+    iterator = iterator == null ? _.identity : lookupIterator(iterator);
+    var value = iterator.call(context, obj);
+    var low = 0, high = array.length;
+    while (low < high) {
+      var mid = (low + high) >>> 1;
+      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+    }
+    return low;
+  };
+
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    return (n == null) || guard ? array[0] : slice.call(array, 0, n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N. The **guard** check allows it to work with
+  // `_.map`.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if ((n == null) || guard) {
+      return array[array.length - 1];
+    } else {
+      return slice.call(array, Math.max(array.length - n, 0));
+    }
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array. The **guard**
+  // check allows it to work with `_.map`.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, (n == null) || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, output) {
+    if (shallow && _.every(input, _.isArray)) {
+      return concat.apply(output, input);
+    }
+    each(input, function(value) {
+      if (_.isArray(value) || _.isArguments(value)) {
+        shallow ? push.apply(output, value) : flatten(value, shallow, output);
+      } else {
+        output.push(value);
+      }
+    });
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, []);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iterator, context) {
+    if (_.isFunction(isSorted)) {
+      context = iterator;
+      iterator = isSorted;
+      isSorted = false;
+    }
+    var initial = iterator ? _.map(array, iterator, context) : array;
+    var results = [];
+    var seen = [];
+    each(initial, function(value, index) {
+      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+        seen.push(value);
+        results.push(array[index]);
+      }
+    });
+    return results;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(_.flatten(arguments, true));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    var rest = slice.call(arguments, 1);
+    return _.filter(_.uniq(array), function(item) {
+      return _.every(rest, function(other) {
+        return _.indexOf(other, item) >= 0;
+      });
+    });
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+    return _.filter(array, function(value){ return !_.contains(rest, value); });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function() {
+    var length = _.max(_.pluck(arguments, "length").concat(0));
+    var results = new Array(length);
+    for (var i = 0; i < length; i++) {
+      results[i] = _.pluck(arguments, '' + i);
+    }
+    return results;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    if (list == null) return {};
+    var result = {};
+    for (var i = 0, length = list.length; i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+  // we need this function. Return the position of the first occurrence of an
+  // item in an array, or -1 if the item is not included in the array.
+  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = function(array, item, isSorted) {
+    if (array == null) return -1;
+    var i = 0, length = array.length;
+    if (isSorted) {
+      if (typeof isSorted == 'number') {
+        i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted);
+      } else {
+        i = _.sortedIndex(array, item);
+        return array[i] === item ? i : -1;
+      }
+    }
+    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+    for (; i < length; i++) if (array[i] === item) return i;
+    return -1;
+  };
+
+  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+  _.lastIndexOf = function(array, item, from) {
+    if (array == null) return -1;
+    var hasIndex = from != null;
+    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
+      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+    }
+    var i = (hasIndex ? from : array.length);
+    while (i--) if (array[i] === item) return i;
+    return -1;
+  };
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (arguments.length <= 1) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = arguments[2] || 1;
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var idx = 0;
+    var range = new Array(length);
+
+    while(idx < length) {
+      range[idx++] = start;
+      start += step;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Reusable constructor function for prototype setting.
+  var ctor = function(){};
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    var args, bound;
+    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    if (!_.isFunction(func)) throw new TypeError;
+    args = slice.call(arguments, 2);
+    return bound = function() {
+      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
+      ctor.prototype = func.prototype;
+      var self = new ctor;
+      ctor.prototype = null;
+      var result = func.apply(self, args.concat(slice.call(arguments)));
+      if (Object(result) === result) return result;
+      return self;
+    };
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context.
+  _.partial = function(func) {
+    var args = slice.call(arguments, 1);
+    return function() {
+      return func.apply(this, args.concat(slice.call(arguments)));
+    };
+  };
+
+  // Bind all of an object's methods to that object. Useful for ensuring that
+  // all callbacks defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var funcs = slice.call(arguments, 1);
+    if (funcs.length === 0) throw new Error("bindAll must be passed function names");
+    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memo = {};
+    hasher || (hasher = _.identity);
+    return function() {
+      var key = hasher.apply(this, arguments);
+      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+    };
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){ return func.apply(null, args); }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = function(func) {
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+  };
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var context, args, result;
+    var timeout = null;
+    var previous = 0;
+    options || (options = {});
+    var later = function() {
+      previous = options.leading === false ? 0 : new Date;
+      timeout = null;
+      result = func.apply(context, args);
+    };
+    return function() {
+      var now = new Date;
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0) {
+        clearTimeout(timeout);
+        timeout = null;
+        previous = now;
+        result = func.apply(context, args);
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, args, context, timestamp, result;
+    return function() {
+      context = this;
+      args = arguments;
+      timestamp = new Date();
+      var later = function() {
+        var last = (new Date()) - timestamp;
+        if (last < wait) {
+          timeout = setTimeout(later, wait - last);
+        } else {
+          timeout = null;
+          if (!immediate) result = func.apply(context, args);
+        }
+      };
+      var callNow = immediate && !timeout;
+      if (!timeout) {
+        timeout = setTimeout(later, wait);
+      }
+      if (callNow) result = func.apply(context, args);
+      return result;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = function(func) {
+    var ran = false, memo;
+    return function() {
+      if (ran) return memo;
+      ran = true;
+      memo = func.apply(this, arguments);
+      func = null;
+      return memo;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return function() {
+      var args = [func];
+      push.apply(args, arguments);
+      return wrapper.apply(this, args);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var funcs = arguments;
+    return function() {
+      var args = arguments;
+      for (var i = funcs.length - 1; i >= 0; i--) {
+        args = [funcs[i].apply(this, args)];
+      }
+      return args[0];
+    };
+  };
+
+  // Returns a function that will only be executed after being called N times.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Object Functions
+  // ----------------
+
+  // Retrieve the names of an object's properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = nativeKeys || function(obj) {
+    if (obj !== Object(obj)) throw new TypeError('Invalid object');
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = new Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = new Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = function(obj) {
+    each(slice.call(arguments, 1), function(source) {
+      if (source) {
+        for (var prop in source) {
+          obj[prop] = source[prop];
+        }
+      }
+    });
+    return obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(obj) {
+    var copy = {};
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+    each(keys, function(key) {
+      if (key in obj) copy[key] = obj[key];
+    });
+    return copy;
+  };
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj) {
+    var copy = {};
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+    for (var key in obj) {
+      if (!_.contains(keys, key)) copy[key] = obj[key];
+    }
+    return copy;
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = function(obj) {
+    each(slice.call(arguments, 1), function(source) {
+      if (source) {
+        for (var prop in source) {
+          if (obj[prop] === void 0) obj[prop] = source[prop];
+        }
+      }
+    });
+    return obj;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a == 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className != toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return a == String(b);
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a == +b;
+      // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+               a.global == b.global &&
+               a.multiline == b.multiline &&
+               a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) return bStack[length] == b;
+    }
+    // Objects with different constructors are not equivalent, but `Object`s
+    // from different frames are.
+    var aCtor = a.constructor, bCtor = b.constructor;
+    if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+                             _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+      return false;
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0, result = true;
+    // Recursively compare objects and arrays.
+    if (className == '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size == b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+        }
+      }
+    } else {
+      // Deep compare objects.
+      for (var key in a) {
+        if (_.has(a, key)) {
+          // Count the expected number of properties.
+          size++;
+          // Deep compare each member.
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+        }
+      }
+      // Ensure that both objects contain the same number of properties.
+      if (result) {
+        for (key in b) {
+          if (_.has(b, key) && !(size--)) break;
+        }
+        result = !size;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return result;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b, [], []);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+    for (var key in obj) if (_.has(obj, key)) return false;
+    return true;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) == '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    return obj === Object(obj);
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) == '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return !!(obj && _.has(obj, 'callee'));
+    };
+  }
+
+  // Optimize `isFunction` if appropriate.
+  if (typeof (/./) !== 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj === 'function';
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj != +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iterators.
+  _.identity = function(value) {
+    return value;
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iterator, context) {
+    var accum = Array(Math.max(0, n));
+    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // List of HTML entities for escaping.
+  var entityMap = {
+    escape: {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#x27;'
+    }
+  };
+  entityMap.unescape = _.invert(entityMap.escape);
+
+  // Regexes containing the keys and values listed immediately above.
+  var entityRegexes = {
+    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+  };
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  _.each(['escape', 'unescape'], function(method) {
+    _[method] = function(string) {
+      if (string == null) return '';
+      return ('' + string).replace(entityRegexes[method], function(match) {
+        return entityMap[method][match];
+      });
+    };
+  });
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, property) {
+    if (object == null) return void 0;
+    var value = object[property];
+    return _.isFunction(value) ? value.call(object) : value;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result.call(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\t':     't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  _.template = function(text, data, settings) {
+    var render;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = new RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset)
+        .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      }
+      if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      }
+      if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+      index = offset + match.length;
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + "return __p;\n";
+
+    try {
+      render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    if (data) return render(data, _);
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled function source as a convenience for precompilation.
+    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function, which will delegate to the wrapper.
+  _.chain = function(obj) {
+    return _(obj).chain();
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(obj) {
+    return this._chain ? _(obj).chain() : obj;
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+      return result.call(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result.call(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  _.extend(_.prototype, {
+
+    // Start chaining a wrapped Underscore object.
+    chain: function() {
+      this._chain = true;
+      return this;
+    },
+
+    // Extracts the result from a wrapped and chained object.
+    value: function() {
+      return this._wrapped;
+    }
+
+  });
+
+}).call(this);
+
+},{}],15:[function(require,module,exports){
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `global` on the server.
+  var root = this;
+
+  // Save the previous value of the `humanize` variable.
+  var previousHumanize = root.humanize;
+
+  var humanize = {};
+
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = humanize;
+    }
+    exports.humanize = humanize;
+  } else {
+    if (typeof define === 'function' && define.amd) {
+      define('humanize', function() {
+        return humanize;
+      });
+    }
+    root.humanize = humanize;
+  }
+
+  humanize.noConflict = function() {
+    root.humanize = previousHumanize;
+    return this;
+  };
+
+  humanize.pad = function(str, count, padChar, type) {
+    str += '';
+    if (!padChar) {
+      padChar = ' ';
+    } else if (padChar.length > 1) {
+      padChar = padChar.charAt(0);
+    }
+    type = (type === undefined) ? 'left' : 'right';
+
+    if (type === 'right') {
+      while (str.length < count) {
+        str = str + padChar;
+      }
+    } else {
+      // default to left
+      while (str.length < count) {
+        str = padChar + str;
+      }
+    }
+
+    return str;
+  };
+
+  // gets current unix time
+  humanize.time = function() {
+    return new Date().getTime() / 1000;
+  };
+
+  /**
+   * PHP-inspired date
+   */
+
+                        /*  jan  feb  mar  apr  may  jun  jul  aug  sep  oct  nov  dec */
+  var dayTableCommon = [ 0,   0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334 ];
+  var dayTableLeap   = [ 0,   0,  31,  60,  91, 121, 152, 182, 213, 244, 274, 305, 335 ];
+  // var mtable_common[13] = {  0,  31,  28,  31,  30,  31,  30,  31,  31,  30,  31,  30,  31 };
+  // static int ml_table_leap[13]   = {  0,  31,  29,  31,  30,  31,  30,  31,  31,  30,  31,  30,  31 };
+
+
+  humanize.date = function(format, timestamp) {
+    var jsdate = ((timestamp === undefined) ? new Date() : // Not provided
+                  (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
+                  new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
+                 );
+
+    var formatChr = /\\?([a-z])/gi;
+    var formatChrCb = function (t, s) {
+      return f[t] ? f[t]() : s;
+    };
+
+    var shortDayTxt = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+    var monthTxt = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+
+    var f = {
+      /* Day */
+      // Day of month w/leading 0; 01..31
+      d: function () { return humanize.pad(f.j(), 2, '0'); },
+
+      // Shorthand day name; Mon..Sun
+      D: function () { return f.l().slice(0, 3); },
+
+      // Day of month; 1..31
+      j: function () { return jsdate.getDate(); },
+
+      // Full day name; Monday..Sunday
+      l: function () { return shortDayTxt[f.w()]; },
+
+      // ISO-8601 day of week; 1[Mon]..7[Sun]
+      N: function () { return f.w() || 7; },
+
+      // Ordinal suffix for day of month; st, nd, rd, th
+      S: function () {
+        var j = f.j();
+        return j > 4 && j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th';
+      },
+
+      // Day of week; 0[Sun]..6[Sat]
+      w: function () { return jsdate.getDay(); },
+
+      // Day of year; 0..365
+      z: function () {
+        return (f.L() ? dayTableLeap[f.n()] : dayTableCommon[f.n()]) + f.j() - 1;
+      },
+
+      /* Week */
+      // ISO-8601 week number
+      W: function () {
+        // days between midweek of this week and jan 4
+        // (f.z() - f.N() + 1 + 3.5) - 3
+        var midWeekDaysFromJan4 = f.z() - f.N() + 1.5;
+        // 1 + number of weeks + rounded week
+        return humanize.pad(1 + Math.floor(Math.abs(midWeekDaysFromJan4) / 7) + (midWeekDaysFromJan4 % 7 > 3.5 ? 1 : 0), 2, '0');
+      },
+
+      /* Month */
+      // Full month name; January..December
+      F: function () { return monthTxt[jsdate.getMonth()]; },
+
+      // Month w/leading 0; 01..12
+      m: function () { return humanize.pad(f.n(), 2, '0'); },
+
+      // Shorthand month name; Jan..Dec
+      M: function () { return f.F().slice(0, 3); },
+
+      // Month; 1..12
+      n: function () { return jsdate.getMonth() + 1; },
+
+      // Days in month; 28..31
+      t: function () { return (new Date(f.Y(), f.n(), 0)).getDate(); },
+
+      /* Year */
+      // Is leap year?; 0 or 1
+      L: function () { return new Date(f.Y(), 1, 29).getMonth() === 1 ? 1 : 0; },
+
+      // ISO-8601 year
+      o: function () {
+        var n = f.n();
+        var W = f.W();
+        return f.Y() + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
+      },
+
+      // Full year; e.g. 1980..2010
+      Y: function () { return jsdate.getFullYear(); },
+
+      // Last two digits of year; 00..99
+      y: function () { return (String(f.Y())).slice(-2); },
+
+      /* Time */
+      // am or pm
+      a: function () { return jsdate.getHours() > 11 ? 'pm' : 'am'; },
+
+      // AM or PM
+      A: function () { return f.a().toUpperCase(); },
+
+      // Swatch Internet time; 000..999
+      B: function () {
+        var unixTime = jsdate.getTime() / 1000;
+        var secondsPassedToday = unixTime % 86400 + 3600; // since it's based off of UTC+1
+        if (secondsPassedToday < 0) { secondsPassedToday += 86400; }
+        var beats = ((secondsPassedToday) / 86.4) % 1000;
+        if (unixTime < 0) {
+          return Math.ceil(beats);
+        }
+        return Math.floor(beats);
+      },
+
+      // 12-Hours; 1..12
+      g: function () { return f.G() % 12 || 12; },
+
+      // 24-Hours; 0..23
+      G: function () { return jsdate.getHours(); },
+
+      // 12-Hours w/leading 0; 01..12
+      h: function () { return humanize.pad(f.g(), 2, '0'); },
+
+      // 24-Hours w/leading 0; 00..23
+      H: function () { return humanize.pad(f.G(), 2, '0'); },
+
+      // Minutes w/leading 0; 00..59
+      i: function () { return humanize.pad(jsdate.getMinutes(), 2, '0'); },
+
+      // Seconds w/leading 0; 00..59
+      s: function () { return humanize.pad(jsdate.getSeconds(), 2, '0'); },
+
+      // Microseconds; 000000-999000
+      u: function () { return humanize.pad(jsdate.getMilliseconds() * 1000, 6, '0'); },
+
+      // Whether or not the date is in daylight savings time
+      /*
+      I: function () {
+        // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
+        // If they are not equal, then DST is observed.
+        var Y = f.Y();
+        return 0 + ((new Date(Y, 0) - Date.UTC(Y, 0)) !== (new Date(Y, 6) - Date.UTC(Y, 6)));
+      },
+      */
+
+      // Difference to GMT in hour format; e.g. +0200
+      O: function () {
+        var tzo = jsdate.getTimezoneOffset();
+        var tzoNum = Math.abs(tzo);
+        return (tzo > 0 ? '-' : '+') + humanize.pad(Math.floor(tzoNum / 60) * 100 + tzoNum % 60, 4, '0');
+      },
+
+      // Difference to GMT w/colon; e.g. +02:00
+      P: function () {
+        var O = f.O();
+        return (O.substr(0, 3) + ':' + O.substr(3, 2));
+      },
+
+      // Timezone offset in seconds (-43200..50400)
+      Z: function () { return -jsdate.getTimezoneOffset() * 60; },
+
+      // Full Date/Time, ISO-8601 date
+      c: function () { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); },
+
+      // RFC 2822
+      r: function () { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); },
+
+      // Seconds since UNIX epoch
+      U: function () { return jsdate.getTime() / 1000 || 0; }
+    };    
+
+    return format.replace(formatChr, formatChrCb);
+  };
+
+
+  /**
+   * format number by adding thousands separaters and significant digits while rounding
+   */
+  humanize.numberFormat = function(number, decimals, decPoint, thousandsSep) {
+    decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
+    decPoint = (decPoint === undefined) ? '.' : decPoint;
+    thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep;
+
+    var sign = number < 0 ? '-' : '';
+    number = Math.abs(+number || 0);
+
+    var intPart = parseInt(number.toFixed(decimals), 10) + '';
+    var j = intPart.length > 3 ? intPart.length % 3 : 0;
+
+    return sign + (j ? intPart.substr(0, j) + thousandsSep : '') + intPart.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep) + (decimals ? decPoint + Math.abs(number - intPart).toFixed(decimals).slice(2) : '');
+  };
+
+
+  /**
+   * For dates that are the current day or within one day, return 'today', 'tomorrow' or 'yesterday', as appropriate.
+   * Otherwise, format the date using the passed in format string.
+   *
+   * Examples (when 'today' is 17 Feb 2007):
+   * 16 Feb 2007 becomes yesterday.
+   * 17 Feb 2007 becomes today.
+   * 18 Feb 2007 becomes tomorrow.
+   * Any other day is formatted according to given argument or the DATE_FORMAT setting if no argument is given.
+   */
+  humanize.naturalDay = function(timestamp, format) {
+    timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
+    format = (format === undefined) ? 'Y-m-d' : format;
+
+    var oneDay = 86400;
+    var d = new Date();
+    var today = (new Date(d.getFullYear(), d.getMonth(), d.getDate())).getTime() / 1000;
+
+    if (timestamp < today && timestamp >= today - oneDay) {
+      return 'yesterday';
+    } else if (timestamp >= today && timestamp < today + oneDay) {
+      return 'today';
+    } else if (timestamp >= today + oneDay && timestamp < today + 2 * oneDay) {
+      return 'tomorrow';
+    }
+
+    return humanize.date(format, timestamp);
+  };
+
+  /**
+   * returns a string representing how many seconds, minutes or hours ago it was or will be in the future
+   * Will always return a relative time, most granular of seconds to least granular of years. See unit tests for more details
+   */
+  humanize.relativeTime = function(timestamp) {
+    timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
+
+    var currTime = humanize.time();
+    var timeDiff = currTime - timestamp;
+
+    // within 2 seconds
+    if (timeDiff < 2 && timeDiff > -2) {
+      return (timeDiff >= 0 ? 'just ' : '') + 'now';
+    }
+
+    // within a minute
+    if (timeDiff < 60 && timeDiff > -60) {
+      return (timeDiff >= 0 ? Math.floor(timeDiff) + ' seconds ago' : 'in ' + Math.floor(-timeDiff) + ' seconds');
+    }
+
+    // within 2 minutes
+    if (timeDiff < 120 && timeDiff > -120) {
+      return (timeDiff >= 0 ? 'about a minute ago' : 'in about a minute');
+    }
+
+    // within an hour
+    if (timeDiff < 3600 && timeDiff > -3600) {
+      return (timeDiff >= 0 ? Math.floor(timeDiff / 60) + ' minutes ago' : 'in ' + Math.floor(-timeDiff / 60) + ' minutes');
+    }
+
+    // within 2 hours
+    if (timeDiff < 7200 && timeDiff > -7200) {
+      return (timeDiff >= 0 ? 'about an hour ago' : 'in about an hour');
+    }
+
+    // within 24 hours
+    if (timeDiff < 86400 && timeDiff > -86400) {
+      return (timeDiff >= 0 ? Math.floor(timeDiff / 3600) + ' hours ago' : 'in ' + Math.floor(-timeDiff / 3600) + ' hours');
+    }
+
+    // within 2 days
+    var days2 = 2 * 86400;
+    if (timeDiff < days2 && timeDiff > -days2) {
+      return (timeDiff >= 0 ? '1 day ago' : 'in 1 day');
+    }
+
+    // within 29 days
+    var days29 = 29 * 86400;
+    if (timeDiff < days29 && timeDiff > -days29) {
+      return (timeDiff >= 0 ? Math.floor(timeDiff / 86400) + ' days ago' : 'in ' + Math.floor(-timeDiff / 86400) + ' days');
+    }
+
+    // within 60 days
+    var days60 = 60 * 86400;
+    if (timeDiff < days60 && timeDiff > -days60) {
+      return (timeDiff >= 0 ? 'about a month ago' : 'in about a month');
+    }
+
+    var currTimeYears = parseInt(humanize.date('Y', currTime), 10);
+    var timestampYears = parseInt(humanize.date('Y', timestamp), 10);
+    var currTimeMonths = currTimeYears * 12 + parseInt(humanize.date('n', currTime), 10);
+    var timestampMonths = timestampYears * 12 + parseInt(humanize.date('n', timestamp), 10);
+
+    // within a year
+    var monthDiff = currTimeMonths - timestampMonths;
+    if (monthDiff < 12 && monthDiff > -12) {
+      return (monthDiff >= 0 ? monthDiff + ' months ago' : 'in ' + (-monthDiff) + ' months');
+    }
+
+    var yearDiff = currTimeYears - timestampYears;
+    if (yearDiff < 2 && yearDiff > -2) {
+      return (yearDiff >= 0 ? 'a year ago' : 'in a year');
+    }
+
+    return (yearDiff >= 0 ? yearDiff + ' years ago' : 'in ' + (-yearDiff) + ' years');
+  };
+
+  /**
+   * Converts an integer to its ordinal as a string.
+   *
+   * 1 becomes 1st
+   * 2 becomes 2nd
+   * 3 becomes 3rd etc
+   */
+  humanize.ordinal = function(number) {
+    number = parseInt(number, 10);
+    number = isNaN(number) ? 0 : number;
+    var sign = number < 0 ? '-' : '';
+    number = Math.abs(number);
+    var tens = number % 100;
+
+    return sign + number + (tens > 4 && tens < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[number % 10] || 'th');
+  };
+
+  /**
+   * Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc).
+   *
+   * For example:
+   * If value is 123456789, the output would be 117.7 MB.
+   */
+  humanize.filesize = function(filesize, kilo, decimals, decPoint, thousandsSep, suffixSep) {
+    kilo = (kilo === undefined) ? 1024 : kilo;
+    if (filesize <= 0) { return '0 bytes'; }
+    if (filesize < kilo && decimals === undefined) { decimals = 0; }
+    if (suffixSep === undefined) { suffixSep = ' '; }
+    return humanize.intword(filesize, ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'], kilo, decimals, decPoint, thousandsSep, suffixSep);
+  };
+
+  /**
+   * Formats the value like a 'human-readable' number (i.e. '13 K', '4.1 M', '102', etc).
+   *
+   * For example:
+   * If value is 123456789, the output would be 117.7 M.
+   */
+  humanize.intword = function(number, units, kilo, decimals, decPoint, thousandsSep, suffixSep) {
+    var humanized, unit;
+
+    units = units || ['', 'K', 'M', 'B', 'T'],
+    unit = units.length - 1,
+    kilo = kilo || 1000,
+    decimals = isNaN(decimals) ? 2 : Math.abs(decimals),
+    decPoint = decPoint || '.',
+    thousandsSep = thousandsSep || ',',
+    suffixSep = suffixSep || '';
+
+    for (var i=0; i < units.length; i++) {
+      if (number < Math.pow(kilo, i+1)) {
+        unit = i;
+        break;
+      }
+    }
+    humanized = number / Math.pow(kilo, unit);
+
+    var suffix = units[unit] ? suffixSep + units[unit] : '';
+    return humanize.numberFormat(humanized, decimals, decPoint, thousandsSep) + suffix;
+  };
+
+  /**
+   * Replaces line breaks in plain text with appropriate HTML
+   * A single newline becomes an HTML line break (<br />) and a new line followed by a blank line becomes a paragraph break (</p>).
+   * 
+   * For example:
+   * If value is Joel\nis a\n\nslug, the output will be <p>Joel<br />is a</p><p>slug</p>
+   */
+  humanize.linebreaks = function(str) {
+    // remove beginning and ending newlines
+    str = str.replace(/^([\n|\r]*)/, '');
+    str = str.replace(/([\n|\r]*)$/, '');
+
+    // normalize all to \n
+    str = str.replace(/(\r\n|\n|\r)/g, "\n");
+
+    // any consecutive new lines more than 2 gets turned into p tags
+    str = str.replace(/(\n{2,})/g, '</p><p>');
+
+    // any that are singletons get turned into br
+    str = str.replace(/\n/g, '<br />');
+    return '<p>' + str + '</p>';
+  };
+
+  /**
+   * Converts all newlines in a piece of plain text to HTML line breaks (<br />).
+   */
+  humanize.nl2br = function(str) {
+    return str.replace(/(\r\n|\n|\r)/g, '<br />');
+  };
+
+  /**
+   * Truncates a string if it is longer than the specified number of characters.
+   * Truncated strings will end with a translatable ellipsis sequence ('…').
+   */
+  humanize.truncatechars = function(string, length) {
+    if (string.length <= length) { return string; }
+    return string.substr(0, length) + '…';
+  };
+
+  /**
+   * Truncates a string after a certain number of words.
+   * Newlines within the string will be removed.
+   */
+  humanize.truncatewords = function(string, numWords) {
+    var words = string.split(' ');
+    if (words.length < numWords) { return string; }
+    return words.slice(0, numWords).join(' ') + '…';
+  };
+
+}).call(this);
+
+},{}],16:[function(require,module,exports){
+//     Underscore.js 1.7.0
+//     http://underscorejs.org
+//     (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `exports` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var
+    push             = ArrayProto.push,
+    slice            = ArrayProto.slice,
+    concat           = ArrayProto.concat,
+    toString         = ObjProto.toString,
+    hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+    nativeIsArray      = Array.isArray,
+    nativeKeys         = Object.keys,
+    nativeBind         = FuncProto.bind;
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.7.0';
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  var createCallback = function(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount == null ? 3 : argCount) {
+      case 1: return function(value) {
+        return func.call(context, value);
+      };
+      case 2: return function(value, other) {
+        return func.call(context, value, other);
+      };
+      case 3: return function(value, index, collection) {
+        return func.call(context, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(context, accumulator, value, index, collection);
+      };
+    }
+    return function() {
+      return func.apply(context, arguments);
+    };
+  };
+
+  // A mostly-internal function to generate callbacks that can be applied
+  // to each element in a collection, returning the desired result — either
+  // identity, an arbitrary callback, a property matcher, or a property accessor.
+  _.iteratee = function(value, context, argCount) {
+    if (value == null) return _.identity;
+    if (_.isFunction(value)) return createCallback(value, context, argCount);
+    if (_.isObject(value)) return _.matches(value);
+    return _.property(value);
+  };
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles raw objects in addition to array-likes. Treats all
+  // sparse array-likes as if they were dense.
+  _.each = _.forEach = function(obj, iteratee, context) {
+    if (obj == null) return obj;
+    iteratee = createCallback(iteratee, context);
+    var i, length = obj.length;
+    if (length === +length) {
+      for (i = 0; i < length; i++) {
+        iteratee(obj[i], i, obj);
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (i = 0, length = keys.length; i < length; i++) {
+        iteratee(obj[keys[i]], keys[i], obj);
+      }
+    }
+    return obj;
+  };
+
+  // Return the results of applying the iteratee to each element.
+  _.map = _.collect = function(obj, iteratee, context) {
+    if (obj == null) return [];
+    iteratee = _.iteratee(iteratee, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        results = Array(length),
+        currentKey;
+    for (var index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      results[index] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  var reduceError = 'Reduce of empty array with no initial value';
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`.
+  _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
+    if (obj == null) obj = [];
+    iteratee = createCallback(iteratee, context, 4);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index = 0, currentKey;
+    if (arguments.length < 3) {
+      if (!length) throw new TypeError(reduceError);
+      memo = obj[keys ? keys[index++] : index++];
+    }
+    for (; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      memo = iteratee(memo, obj[currentKey], currentKey, obj);
+    }
+    return memo;
+  };
+
+  // The right-associative version of reduce, also known as `foldr`.
+  _.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
+    if (obj == null) obj = [];
+    iteratee = createCallback(iteratee, context, 4);
+    var keys = obj.length !== + obj.length && _.keys(obj),
+        index = (keys || obj).length,
+        currentKey;
+    if (arguments.length < 3) {
+      if (!index) throw new TypeError(reduceError);
+      memo = obj[keys ? keys[--index] : --index];
+    }
+    while (index--) {
+      currentKey = keys ? keys[index] : index;
+      memo = iteratee(memo, obj[currentKey], currentKey, obj);
+    }
+    return memo;
+  };
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, predicate, context) {
+    var result;
+    predicate = _.iteratee(predicate, context);
+    _.some(obj, function(value, index, list) {
+      if (predicate(value, index, list)) {
+        result = value;
+        return true;
+      }
+    });
+    return result;
+  };
+
+  // Return all the elements that pass a truth test.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, predicate, context) {
+    var results = [];
+    if (obj == null) return results;
+    predicate = _.iteratee(predicate, context);
+    _.each(obj, function(value, index, list) {
+      if (predicate(value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, predicate, context) {
+    return _.filter(obj, _.negate(_.iteratee(predicate)), context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, predicate, context) {
+    if (obj == null) return true;
+    predicate = _.iteratee(predicate, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index, currentKey;
+    for (index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Aliased as `any`.
+  _.some = _.any = function(obj, predicate, context) {
+    if (obj == null) return false;
+    predicate = _.iteratee(predicate, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index, currentKey;
+    for (index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      if (predicate(obj[currentKey], currentKey, obj)) return true;
+    }
+    return false;
+  };
+
+  // Determine if the array or object contains a given value (using `===`).
+  // Aliased as `include`.
+  _.contains = _.include = function(obj, target) {
+    if (obj == null) return false;
+    if (obj.length !== +obj.length) obj = _.values(obj);
+    return _.indexOf(obj, target) >= 0;
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      return (isFunc ? method : value[method]).apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, _.property(key));
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs) {
+    return _.filter(obj, _.matches(attrs));
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.find(obj, _.matches(attrs));
+  };
+
+  // Return the maximum element (or element-based computation).
+  _.max = function(obj, iteratee, context) {
+    var result = -Infinity, lastComputed = -Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = obj.length === +obj.length ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value > result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iteratee, context) {
+    var result = Infinity, lastComputed = Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = obj.length === +obj.length ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value < result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed < lastComputed || computed === Infinity && result === Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Shuffle a collection, using the modern version of the
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  _.shuffle = function(obj) {
+    var set = obj && obj.length === +obj.length ? obj : _.values(obj);
+    var length = set.length;
+    var shuffled = Array(length);
+    for (var index = 0, rand; index < length; index++) {
+      rand = _.random(0, index);
+      if (rand !== index) shuffled[index] = shuffled[rand];
+      shuffled[rand] = set[index];
+    }
+    return shuffled;
+  };
+
+  // Sample **n** random values from a collection.
+  // If **n** is not specified, returns a single random element.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (n == null || guard) {
+      if (obj.length !== +obj.length) obj = _.values(obj);
+      return obj[_.random(obj.length - 1)];
+    }
+    return _.shuffle(obj).slice(0, Math.max(0, n));
+  };
+
+  // Sort the object's values by a criterion produced by an iteratee.
+  _.sortBy = function(obj, iteratee, context) {
+    iteratee = _.iteratee(iteratee, context);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value: value,
+        index: index,
+        criteria: iteratee(value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior) {
+    return function(obj, iteratee, context) {
+      var result = {};
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index) {
+        var key = iteratee(value, index, obj);
+        behavior(result, value, key);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, value, key) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key]++; else result[key] = 1;
+  });
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iteratee, context) {
+    iteratee = _.iteratee(iteratee, context, 1);
+    var value = iteratee(obj);
+    var low = 0, high = array.length;
+    while (low < high) {
+      var mid = low + high >>> 1;
+      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+    }
+    return low;
+  };
+
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return obj.length === +obj.length ? obj.length : _.keys(obj).length;
+  };
+
+  // Split a collection into two arrays: one whose elements all satisfy the given
+  // predicate, and one whose elements all do not satisfy the predicate.
+  _.partition = function(obj, predicate, context) {
+    predicate = _.iteratee(predicate, context);
+    var pass = [], fail = [];
+    _.each(obj, function(value, key, obj) {
+      (predicate(value, key, obj) ? pass : fail).push(value);
+    });
+    return [pass, fail];
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[0];
+    if (n < 0) return [];
+    return slice.call(array, 0, n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N. The **guard** check allows it to work with
+  // `_.map`.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[array.length - 1];
+    return slice.call(array, Math.max(array.length - n, 0));
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array. The **guard**
+  // check allows it to work with `_.map`.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, n == null || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, strict, output) {
+    if (shallow && _.every(input, _.isArray)) {
+      return concat.apply(output, input);
+    }
+    for (var i = 0, length = input.length; i < length; i++) {
+      var value = input[i];
+      if (!_.isArray(value) && !_.isArguments(value)) {
+        if (!strict) output.push(value);
+      } else if (shallow) {
+        push.apply(output, value);
+      } else {
+        flatten(value, shallow, strict, output);
+      }
+    }
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, false, []);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+    if (array == null) return [];
+    if (!_.isBoolean(isSorted)) {
+      context = iteratee;
+      iteratee = isSorted;
+      isSorted = false;
+    }
+    if (iteratee != null) iteratee = _.iteratee(iteratee, context);
+    var result = [];
+    var seen = [];
+    for (var i = 0, length = array.length; i < length; i++) {
+      var value = array[i];
+      if (isSorted) {
+        if (!i || seen !== value) result.push(value);
+        seen = value;
+      } else if (iteratee) {
+        var computed = iteratee(value, i, array);
+        if (_.indexOf(seen, computed) < 0) {
+          seen.push(computed);
+          result.push(value);
+        }
+      } else if (_.indexOf(result, value) < 0) {
+        result.push(value);
+      }
+    }
+    return result;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(flatten(arguments, true, true, []));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    if (array == null) return [];
+    var result = [];
+    var argsLength = arguments.length;
+    for (var i = 0, length = array.length; i < length; i++) {
+      var item = array[i];
+      if (_.contains(result, item)) continue;
+      for (var j = 1; j < argsLength; j++) {
+        if (!_.contains(arguments[j], item)) break;
+      }
+      if (j === argsLength) result.push(item);
+    }
+    return result;
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = flatten(slice.call(arguments, 1), true, true, []);
+    return _.filter(array, function(value){
+      return !_.contains(rest, value);
+    });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function(array) {
+    if (array == null) return [];
+    var length = _.max(arguments, 'length').length;
+    var results = Array(length);
+    for (var i = 0; i < length; i++) {
+      results[i] = _.pluck(arguments, i);
+    }
+    return results;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    if (list == null) return {};
+    var result = {};
+    for (var i = 0, length = list.length; i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // Return the position of the first occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = function(array, item, isSorted) {
+    if (array == null) return -1;
+    var i = 0, length = array.length;
+    if (isSorted) {
+      if (typeof isSorted == 'number') {
+        i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
+      } else {
+        i = _.sortedIndex(array, item);
+        return array[i] === item ? i : -1;
+      }
+    }
+    for (; i < length; i++) if (array[i] === item) return i;
+    return -1;
+  };
+
+  _.lastIndexOf = function(array, item, from) {
+    if (array == null) return -1;
+    var idx = array.length;
+    if (typeof from == 'number') {
+      idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
+    }
+    while (--idx >= 0) if (array[idx] === item) return idx;
+    return -1;
+  };
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (arguments.length <= 1) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = step || 1;
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var range = Array(length);
+
+    for (var idx = 0; idx < length; idx++, start += step) {
+      range[idx] = start;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Reusable constructor function for prototype setting.
+  var Ctor = function(){};
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    var args, bound;
+    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+    args = slice.call(arguments, 2);
+    bound = function() {
+      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
+      Ctor.prototype = func.prototype;
+      var self = new Ctor;
+      Ctor.prototype = null;
+      var result = func.apply(self, args.concat(slice.call(arguments)));
+      if (_.isObject(result)) return result;
+      return self;
+    };
+    return bound;
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context. _ acts
+  // as a placeholder, allowing any combination of arguments to be pre-filled.
+  _.partial = function(func) {
+    var boundArgs = slice.call(arguments, 1);
+    return function() {
+      var position = 0;
+      var args = boundArgs.slice();
+      for (var i = 0, length = args.length; i < length; i++) {
+        if (args[i] === _) args[i] = arguments[position++];
+      }
+      while (position < arguments.length) args.push(arguments[position++]);
+      return func.apply(this, args);
+    };
+  };
+
+  // Bind a number of an object's methods to that object. Remaining arguments
+  // are the method names to be bound. Useful for ensuring that all callbacks
+  // defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var i, length = arguments.length, key;
+    if (length <= 1) throw new Error('bindAll must be passed function names');
+    for (i = 1; i < length; i++) {
+      key = arguments[i];
+      obj[key] = _.bind(obj[key], obj);
+    }
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memoize = function(key) {
+      var cache = memoize.cache;
+      var address = hasher ? hasher.apply(this, arguments) : key;
+      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+      return cache[address];
+    };
+    memoize.cache = {};
+    return memoize;
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){
+      return func.apply(null, args);
+    }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = function(func) {
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+  };
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var context, args, result;
+    var timeout = null;
+    var previous = 0;
+    if (!options) options = {};
+    var later = function() {
+      previous = options.leading === false ? 0 : _.now();
+      timeout = null;
+      result = func.apply(context, args);
+      if (!timeout) context = args = null;
+    };
+    return function() {
+      var now = _.now();
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0 || remaining > wait) {
+        clearTimeout(timeout);
+        timeout = null;
+        previous = now;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, args, context, timestamp, result;
+
+    var later = function() {
+      var last = _.now() - timestamp;
+
+      if (last < wait && last > 0) {
+        timeout = setTimeout(later, wait - last);
+      } else {
+        timeout = null;
+        if (!immediate) {
+          result = func.apply(context, args);
+          if (!timeout) context = args = null;
+        }
+      }
+    };
+
+    return function() {
+      context = this;
+      args = arguments;
+      timestamp = _.now();
+      var callNow = immediate && !timeout;
+      if (!timeout) timeout = setTimeout(later, wait);
+      if (callNow) {
+        result = func.apply(context, args);
+        context = args = null;
+      }
+
+      return result;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return _.partial(wrapper, func);
+  };
+
+  // Returns a negated version of the passed-in predicate.
+  _.negate = function(predicate) {
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var args = arguments;
+    var start = args.length - 1;
+    return function() {
+      var i = start;
+      var result = args[start].apply(this, arguments);
+      while (i--) result = args[i].call(this, result);
+      return result;
+    };
+  };
+
+  // Returns a function that will only be executed after being called N times.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Returns a function that will only be executed before being called N times.
+  _.before = function(times, func) {
+    var memo;
+    return function() {
+      if (--times > 0) {
+        memo = func.apply(this, arguments);
+      } else {
+        func = null;
+      }
+      return memo;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = _.partial(_.before, 2);
+
+  // Object Functions
+  // ----------------
+
+  // Retrieve the names of an object's properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    if (nativeKeys) return nativeKeys(obj);
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    var source, prop;
+    for (var i = 1, length = arguments.length; i < length; i++) {
+      source = arguments[i];
+      for (prop in source) {
+        if (hasOwnProperty.call(source, prop)) {
+            obj[prop] = source[prop];
+        }
+      }
+    }
+    return obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(obj, iteratee, context) {
+    var result = {}, key;
+    if (obj == null) return result;
+    if (_.isFunction(iteratee)) {
+      iteratee = createCallback(iteratee, context);
+      for (key in obj) {
+        var value = obj[key];
+        if (iteratee(value, key, obj)) result[key] = value;
+      }
+    } else {
+      var keys = concat.apply([], slice.call(arguments, 1));
+      obj = new Object(obj);
+      for (var i = 0, length = keys.length; i < length; i++) {
+        key = keys[i];
+        if (key in obj) result[key] = obj[key];
+      }
+    }
+    return result;
+  };
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj, iteratee, context) {
+    if (_.isFunction(iteratee)) {
+      iteratee = _.negate(iteratee);
+    } else {
+      var keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
+      iteratee = function(value, key) {
+        return !_.contains(keys, key);
+      };
+    }
+    return _.pick(obj, iteratee, context);
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    for (var i = 1, length = arguments.length; i < length; i++) {
+      var source = arguments[i];
+      for (var prop in source) {
+        if (obj[prop] === void 0) obj[prop] = source[prop];
+      }
+    }
+    return obj;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a === 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className !== toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+      case '[object RegExp]':
+      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return '' + a === '' + b;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive.
+        // Object(NaN) is equivalent to NaN
+        if (+a !== +a) return +b !== +b;
+        // An `egal` comparison is performed for other numeric values.
+        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a === +b;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] === a) return bStack[length] === b;
+    }
+    // Objects with different constructors are not equivalent, but `Object`s
+    // from different frames are.
+    var aCtor = a.constructor, bCtor = b.constructor;
+    if (
+      aCtor !== bCtor &&
+      // Handle Object.create(x) cases
+      'constructor' in a && 'constructor' in b &&
+      !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+        _.isFunction(bCtor) && bCtor instanceof bCtor)
+    ) {
+      return false;
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size, result;
+    // Recursively compare objects and arrays.
+    if (className === '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size === b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+        }
+      }
+    } else {
+      // Deep compare objects.
+      var keys = _.keys(a), key;
+      size = keys.length;
+      // Ensure that both objects contain the same number of properties before comparing deep equality.
+      result = _.keys(b).length === size;
+      if (result) {
+        while (size--) {
+          // Deep compare each member
+          key = keys[size];
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+        }
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return result;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b, [], []);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
+    for (var key in obj) if (_.has(obj, key)) return false;
+    return true;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) === '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return _.has(obj, 'callee');
+    };
+  }
+
+  // Optimize `isFunction` if appropriate. Work around an IE 11 bug.
+  if (typeof /./ !== 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj == 'function' || false;
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj !== +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return obj != null && hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iteratees.
+  _.identity = function(value) {
+    return value;
+  };
+
+  _.constant = function(value) {
+    return function() {
+      return value;
+    };
+  };
+
+  _.noop = function(){};
+
+  _.property = function(key) {
+    return function(obj) {
+      return obj[key];
+    };
+  };
+
+  // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
+  _.matches = function(attrs) {
+    var pairs = _.pairs(attrs), length = pairs.length;
+    return function(obj) {
+      if (obj == null) return !length;
+      obj = new Object(obj);
+      for (var i = 0; i < length; i++) {
+        var pair = pairs[i], key = pair[0];
+        if (pair[1] !== obj[key] || !(key in obj)) return false;
+      }
+      return true;
+    };
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iteratee, context) {
+    var accum = Array(Math.max(0, n));
+    iteratee = createCallback(iteratee, context, 1);
+    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // A (possibly faster) way to get the current timestamp as an integer.
+  _.now = Date.now || function() {
+    return new Date().getTime();
+  };
+
+   // List of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+  var unescapeMap = _.invert(escapeMap);
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  var createEscaper = function(map) {
+    var escaper = function(match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped
+    var source = '(?:' + _.keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function(string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  };
+  _.escape = createEscaper(escapeMap);
+  _.unescape = createEscaper(unescapeMap);
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, property) {
+    if (object == null) return void 0;
+    var value = object[property];
+    return _.isFunction(value) ? object[property]() : value;
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+  var escapeChar = function(match) {
+    return '\\' + escapes[match];
+  };
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  // NB: `oldSettings` only exists for backwards compatibility.
+  _.template = function(text, settings, oldSettings) {
+    if (!settings && oldSettings) settings = oldSettings;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset).replace(escaper, escapeChar);
+      index = offset + match.length;
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      } else if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      } else if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+
+      // Adobe VMs need the match returned to produce the correct offest.
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + 'return __p;\n';
+
+    try {
+      var render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled source as a convenience for precompilation.
+    var argument = settings.variable || 'obj';
+    template.source = 'function(' + argument + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function. Start chaining a wrapped Underscore object.
+  _.chain = function(obj) {
+    var instance = _(obj);
+    instance._chain = true;
+    return instance;
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(obj) {
+    return this._chain ? _(obj).chain() : obj;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    _.each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result.call(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+      return result.call(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  _.each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result.call(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  // Extracts the result from a wrapped and chained object.
+  _.prototype.value = function() {
+    return this._wrapped;
+  };
+
+  // AMD registration happens at the end for compatibility with AMD loaders
+  // that may not enforce next-turn semantics on modules. Even though general
+  // practice for AMD registration is to be anonymous, underscore registers
+  // as a named module because, like jQuery, it is a base library that is
+  // popular enough to be bundled in a third party lib, but not be part of
+  // an AMD load request. Those cases could generate an error when an
+  // anonymous define() is called outside of a loader request.
+  if (typeof define === 'function' && define.amd) {
+    define('underscore', [], function() {
+      return _;
+    });
+  }
+}.call(this));
+
+},{}],17:[function(require,module,exports){
+// calc.js
+// measure calculations
+
+var _ = require('underscore');
+var geocrunch = require('geocrunch');
+
+var pad = function (num) {
+  return num < 10 ? '0' + num.toString() : num.toString();
+};
+
+var ddToDms = function (coordinate, posSymbol, negSymbol) {
+  var dd = Math.abs(coordinate),
+      d = Math.floor(dd),
+      m = Math.floor((dd - d) * 60),
+      s = Math.round((dd - d - (m/60)) * 3600 * 100)/100,
+      directionSymbol = dd === coordinate ? posSymbol : negSymbol;
+  return pad(d) + '&deg; ' + pad(m) + '\' ' + pad(s) + '" ' + directionSymbol;
+};
+
+var measure = function (latlngs) {
+  var last = _.last(latlngs), feet, meters, miles, kilometers, sqMeters, acres, hectares, sqMiles;
+  var path = geocrunch.path(_.map(latlngs, function (latlng) {
+    return [latlng.lng, latlng.lat];
+  }));
+
+  feet = path.distance({
+    units: 'feet'
+  });
+  meters = feet / 3.2808;
+  miles = feet / 5280;
+  kilometers = meters / 1000;
+  sqMeters = path.area({
+    units: 'sqmeters'
+  });
+  acres = sqMeters * 0.00024711;
+  hectares = sqMeters / 10000;
+  sqMiles = acres * 0.0015625;
+
+  return {
+    lastCoord: {
+      dd: {
+        x: last.lng,
+        y: last.lat
+      },
+      dms: {
+        x: ddToDms(last.lng, 'E', 'W'),
+        y: ddToDms(last.lat, 'N', 'S')
+      }
+    },
+    length: {
+      feet: feet,
+      meters: meters,
+      miles: miles,
+      kilometers: kilometers
+    },
+    area: {
+      acres: acres,
+      hectares: hectares,
+      sqmeters: sqMeters,
+      sqmiles: sqMiles
+    }
+  };
+};
+
+module.exports = {
+  measure: measure // `measure(latLngArray)` - returns object with calced measurements for passed points
+};
+},{"geocrunch":6,"underscore":16}],18:[function(require,module,exports){
+// dom.js
+// utility functions for managing DOM elements
+
+var selectOne = function (selector, el) {
+  if (!el) {
+    el = document;
+  }
+  return el.querySelector(selector);
+};
+
+var selectAll = function (selector, el) {
+  if (!el) {
+    el = document;
+  }
+  return Array.prototype.slice.call(el.querySelectorAll(selector));
+};
+
+var hide = function (el) {
+  if (el) {
+    el.setAttribute('style', 'display:none;');
+    return el;
+  }
+};
+
+var show = function (el) {
+  if (el) {
+    el.removeAttribute('style');
+    return el;
+  }
+};
+
+module.exports = {
+  $: selectOne,  // `$('.myclass', baseElement)` - returns selected element or undefined
+  $$: selectAll, // `$$('.myclass', baseElement)` - returns array of selected elements
+  hide: hide,    // `hide(someElement)` - hide passed dom element
+  show: show     // `show(someElement)` - show passed dom element
+};
+},{}],19:[function(require,module,exports){
+(function (global){
+// leaflet-measure.js
+
+var _ = require('underscore');
+var L = (typeof window !== "undefined" ? window.L : typeof global !== "undefined" ? global.L : null);
+var humanize = require('humanize');
+
+var units = require('./units');
+var calc = require('./calc');
+var dom = require('./dom');
+var $ = dom.$;
+
+var Symbology = require('./mapsymbology');
+
+
+var controlTemplate = _.template("<a class=\"<%= model.className %>-toggle js-toggle\" href=\"#\" title=\"Measure distances and areas\">Measure</a>\n<div class=\"<%= model.className %>-interaction js-interaction\">\n  <div class=\"js-startprompt startprompt\">\n    <h3>Measure Distances and Areas</h3>\n    <ul class=\"tasks\">\n      <a href=\"#\" class=\"js-start start\">Create a new measurement</a>\n    </ul>\n  </div>\n  <div class=\"js-measuringprompt\">\n    <h3>Measure Distances and Areas</h3>\n    <p class=\"js-starthelp\">Start creating a measurement by adding points to the map</h3>\n    <div class=\"js-results results\"></div>\n    <ul class=\"js-measuretasks tasks\">\n      <li><a href=\"#\" class=\"js-cancel cancel\">Cancel</a></li>\n      <li><a href=\"#\" class=\"js-finish finish\">Finish Measurement</a></li>\n    </ul>\n  </div>\n</div>");
+var resultsTemplate = _.template("<div class=\"group\">\n<p class=\"lastpoint heading\">Last Point</p>\n<p><%= model.lastCoord.dms.y %> <span class=\"coorddivider\">/</span> <%= model.lastCoord.dms.x %></p>\n<p><%= humanize.numberFormat(model.lastCoord.dd.y, 6) %> <span class=\"coorddivider\">/</span> <%= humanize.numberFormat(model.lastCoord.dd.x, 6) %></p>\n</div>\n<% if (model.pointCount > 1) { %>\n<div class=\"group\">\n<p><span class=\"heading\">Path Distance</span> <%= model.lengthDisplay %></p>\n</div>\n<% } %>\n<% if (model.pointCount > 2) { %>\n<div class=\"group\">\n<p><span class=\"heading\">Area</span> <%= model.areaDisplay %></p>\n</div>\n<% } %>");
+var pointPopupTemplate = _.template("<h3>Point Location</h3>\n<p><%= model.lastCoord.dms.y %> <span class=\"coorddivider\">/</span> <%= model.lastCoord.dms.x %></p>\n<p><%= humanize.numberFormat(model.lastCoord.dd.y, 6) %> <span class=\"coorddivider\">/</span> <%= humanize.numberFormat(model.lastCoord.dd.x, 6) %></p>\n<ul class=\"tasks\">\n  <li><a href=\"#\" class=\"js-zoomto zoomto\">Center on this Location</a></li>\n  <li><a href=\"#\" class=\"js-deletemarkup deletemarkup\">Delete</a></li>\n</ul>");
+var linePopupTemplate = _.template("<h3>Linear Measurement</h3>\n<p><%= model.lengthDisplay %></p>\n<ul class=\"tasks\">\n  <li><a href=\"#\" class=\"js-zoomto zoomto\">Center on this Line</a></li>\n  <li><a href=\"#\" class=\"js-deletemarkup deletemarkup\">Delete</a></li>\n</ul>");
+var areaPopupTemplate = _.template("<h3>Area Measurement</h3>\n<p><%= model.areaDisplay %></p>\n<p><%= model.lengthDisplay %> Perimeter</p>\n<ul class=\"tasks\">\n  <li><a href=\"#\" class=\"js-zoomto zoomto\">Center on this Area</a></li>\n  <li><a href=\"#\" class=\"js-deletemarkup deletemarkup\">Delete</a></li>\n</ul>");
+
+L.Control.Measure = L.Control.extend({
+  _className: 'leaflet-control-measure',
+  options: {
+    position: 'topright',
+    primaryLengthUnit: 'feet',
+    secondaryLengthUnit: 'miles',
+    primaryAreaUnit: 'acres',
+    activeColor: '#ABE67E',     // base color for map features while actively measuring
+    completedColor: '#C8F2BE',  // base color for permenant features generated from completed measure
+    popupOptions: {             // standard leaflet popup options http://leafletjs.com/reference.html#popup-options
+      className: 'leaflet-measure-resultpopup',
+      autoPanPadding: [10, 10]
+    }
+  },
+  initialize: function (options) {
+    L.setOptions(this, options);
+    this._symbols = new Symbology(_.pick(this.options, 'activeColor', 'completedColor'));
+  },
+  onAdd: function (map) {
+    this._map = map;
+    this._latlngs = [];
+    this._initLayout();
+    map.on('click', this._collapse, this);
+    this._layer = L.layerGroup().addTo(map);
+    return this._container;
+  },
+  onRemove: function (map) {
+    map.off('click', this._collapse, this);
+    map.removeLayer(this._layer);
+  },
+  _initLayout: function () {
+    var className = this._className, container = this._container = L.DomUtil.create('div', className);
+    var $toggle, $start, $cancel, $finish;
+
+    container.innerHTML = controlTemplate({
+      model: {
+        className: className
+      }
+    });
+
+    // copied from leaflet
+    // https://bitbucket.org/ljagis/js-mapbootstrap/src/4ab1e9e896c08bdbc8164d4053b2f945143f4f3a/app/components/measure/leaflet-measure-control.js?at=master#cl-30
+    container.setAttribute('aria-haspopup', true);
+    if (!L.Browser.touch) {
+      L.DomEvent.disableClickPropagation(container);
+      L.DomEvent.disableScrollPropagation(container);
+    } else {
+      L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
+    }
+
+    $toggle = this.$toggle = $('.js-toggle', container);         // collapsed content
+    this.$interaction = $('.js-interaction', container);         // expanded content
+    $start = $('.js-start', container);                          // start button
+    $cancel = $('.js-cancel', container);                        // cancel button
+    $finish = $('.js-finish', container);                        // finish button
+    this.$startPrompt = $('.js-startprompt', container);         // full area with button to start measurment
+    this.$measuringPrompt = $('.js-measuringprompt', container); // full area with all stuff for active measurement
+    this.$startHelp = $('.js-starthelp', container);             // "Start creating a measurement by adding points"
+    this.$results = $('.js-results', container);                 // div with coordinate, linear, area results
+    this.$measureTasks = $('.js-measuretasks', container);       // active measure buttons container
+
+    this._collapse();
+    this._updateMeasureNotStarted();
+
+    if (!L.Browser.android) {
+      L.DomEvent.on(container, 'mouseenter', this._expand, this);
+      L.DomEvent.on(container, 'mouseleave', this._collapse, this);
+    }
+    L.DomEvent.on($toggle, 'click', L.DomEvent.stop);
+    if (L.Browser.touch) {
+      L.DomEvent.on($toggle, 'click', this._expand, this);
+    } else {
+      L.DomEvent.on($toggle, 'focus', this._expand, this);
+    }
+    L.DomEvent.on($start, 'click', L.DomEvent.stop);
+    L.DomEvent.on($start, 'click', this._startMeasure, this);
+    L.DomEvent.on($cancel, 'click', L.DomEvent.stop);
+    L.DomEvent.on($cancel, 'click', this._finishMeasure, this);
+    L.DomEvent.on($finish, 'click', L.DomEvent.stop);
+    L.DomEvent.on($finish, 'click', this._handleMeasureDoubleClick, this);
+  },
+  _expand: function () {
+    dom.hide(this.$toggle);
+    dom.show(this.$interaction);
+  },
+  _collapse: function () {
+    if (!this._locked) {
+      dom.hide(this.$interaction);
+      dom.show(this.$toggle);
+    }
+  },
+  // move between basic states:
+  // measure not started, started/in progress but no points added, in progress and with points
+  _updateMeasureNotStarted: function () {
+    dom.hide(this.$startHelp);
+    dom.hide(this.$results);
+    dom.hide(this.$measureTasks);
+    dom.hide(this.$measuringPrompt);
+    dom.show(this.$startPrompt);
+  },
+  _updateMeasureStartedNoPoints: function () {
+    dom.hide(this.$results);
+    dom.show(this.$startHelp);
+    dom.show(this.$measureTasks);
+    dom.hide(this.$startPrompt);
+    dom.show(this.$measuringPrompt);
+  },
+  _updateMeasureStartedWithPoints: function () {
+    dom.hide(this.$startHelp);
+    dom.show(this.$results);
+    dom.show(this.$measureTasks);
+    dom.hide(this.$startPrompt);
+    dom.show(this.$measuringPrompt);
+  },
+  // get state vars and interface ready for measure
+  _startMeasure: function () {
+    this._locked = true;
+
+    this._map.doubleClickZoom.disable(); // double click now finishes measure
+    this._map.on('mouseout', this._handleMapMouseOut, this);
+
+    L.DomEvent.on(this._container, 'mouseenter', this._handleMapMouseOut, this);
+
+    if (!this._measureCollector) {
+      // polygon to cover all other layers and collection measure move and click events
+      this._measureCollector = L.polygon([[90, -180], [90, 180], [-90, 180], [-90, -180]], this._symbols.getSymbol('measureCollector')).addTo(this._layer);
+      this._measureCollector.on('mousemove', this._handleMeasureMove, this);
+      this._measureCollector.on('dblclick', this._handleMeasureDoubleClick, this);
+      this._measureCollector.on('click', this._handleMeasureClick, this);
+    }
+    this._measureCollector.bringToFront();
+
+    this._measureVertexes = L.featureGroup().addTo(this._layer);
+
+    this._updateMeasureStartedNoPoints();
+  },
+  // return to state with no measure in progress, undo `this._startMeasure`
+  _finishMeasure: function () {
+    this._locked = false;
+
+    this._map.doubleClickZoom.enable();
+    this._map.off('mouseout', this._handleMapMouseOut, this);
+
+    L.DomEvent.off(this._container, 'mouseover', this._handleMapMouseOut, this);
+
+    this._clearMeasure();
+
+    this._measureCollector.off();
+    this._layer.removeLayer(this._measureCollector);
+    this._measureCollector = null;
+
+    this._layer.removeLayer(this._measureVertexes);
+    this._measureVertexes = null;
+
+    this._updateMeasureNotStarted();
+    this._collapse();
+  },
+  // clear all running measure data
+  _clearMeasure: function () {
+    this._latlngs = [];
+    this._measureVertexes.clearLayers();
+    if (this._measureDrag) {
+      this._layer.removeLayer(this._measureDrag);
+    }
+    if (this._measureArea) {
+      this._layer.removeLayer(this._measureArea);
+    }
+    if (this._measureBoundary) {
+      this._layer.removeLayer(this._measureBoundary);
+    }
+    this._measureDrag = null;
+    this._measureArea = null;
+    this._measureBoundary = null;
+  },
+  // format measurements to nice display string based on units in options. `{ lengthDisplay: '100 Feet (0.02 Miles)', areaDisplay: ... }`
+  _getMeasurementDisplayStrings: function (measurement) {
+    var result = {};
+    if (this.options.primaryLengthUnit && units[this.options.primaryLengthUnit]) {
+      result.lengthDisplay = humanize.numberFormat(measurement.length[this.options.primaryLengthUnit], units[this.options.primaryLengthUnit].decimals) + ' ' + units[this.options.primaryLengthUnit].display;
+      if (this.options.secondaryLengthUnit && units[this.options.secondaryLengthUnit]) {
+        result.lengthDisplay = result.lengthDisplay + ' (' + humanize.numberFormat(measurement.length[this.options.secondaryLengthUnit], units[this.options.secondaryLengthUnit].decimals) + ' ' + units[this.options.secondaryLengthUnit].display + ')';
+      }
+    }
+    if (this.options.primaryAreaUnit && units[this.options.primaryAreaUnit]) {
+      result.areaDisplay = humanize.numberFormat(measurement.area[this.options.primaryAreaUnit], units[this.options.primaryAreaUnit].decimals) + ' ' + units[this.options.primaryAreaUnit].display;
+      if (this.options.secondaryAreaUnit && units[this.options.secondaryAreaUnit]) {
+        result.areaDisplay = result.areaDisplay + ' (' + humanize.numberFormat(measurement.area[this.options.secondaryAreaUnit], units[this.options.secondaryAreaUnit].decimals) + ' ' + units[this.options.secondaryAreaUnit].display + ')';
+      }
+    }
+    return result;
+  },
+  // update results area of dom with calced measure from `this._latlngs`
+  _updateResults: function () {
+    var calced = calc.measure(this._latlngs);
+    this.$results.innerHTML = resultsTemplate({
+      model: _.extend({}, calced, this._getMeasurementDisplayStrings(calced), {
+        pointCount: this._latlngs.length
+      }),
+      humanize: humanize
+    });
+  },
+  // mouse move handler while measure in progress
+  // adds floating measure marker under cursor
+  _handleMeasureMove: function (evt) {
+    if (!this._measureDrag) {
+      this._measureDrag = L.circleMarker(evt.latlng, this._symbols.getSymbol('measureDrag')).addTo(this._layer);
+    } else {
+      this._measureDrag.setLatLng(evt.latlng);
+    }
+    this._measureDrag.bringToFront();
+  },
+  // handler for both double click and clicking finish button
+  // do final calc and finish out current measure, clear dom and internal state, add permanent map features
+  _handleMeasureDoubleClick: function () {
+    var latlngs = this._latlngs, calced, resultFeature, popupContainer, popupContent, zoomLink, deleteLink;
+
+    this._finishMeasure();
+
+    if (!latlngs.length) {
+      return;
+    }
+
+    if (latlngs.length > 2) {
+      latlngs.push(_.first(latlngs)); // close path to get full perimeter measurement for areas
+    }
+
+    calced = calc.measure(latlngs);
+
+    if (latlngs.length === 1) {
+      resultFeature = L.circleMarker(latlngs[0], this._symbols.getSymbol('resultPoint'));
+      popupContent = pointPopupTemplate({
+        model: calced,
+        humanize: humanize
+      });
+    } else if (latlngs.length === 2) {
+      resultFeature = L.polyline(latlngs, this._symbols.getSymbol('resultLine')).addTo(this._map);
+      popupContent = linePopupTemplate({
+        model: _.extend({}, calced, this._getMeasurementDisplayStrings(calced)),
+        humanize: humanize
+      });
+    } else {
+      resultFeature = L.polygon(latlngs, this._symbols.getSymbol('resultArea'));
+      popupContent = areaPopupTemplate({
+        model: _.extend({}, calced, this._getMeasurementDisplayStrings(calced)),
+        humanize: humanize,
+        units: this._units
+      });
+    }
+
+    popupContainer = L.DomUtil.create('div', '');
+    popupContainer.innerHTML = popupContent;
+
+    zoomLink = $('.js-zoomto', popupContainer);
+    if (zoomLink) {
+      L.DomEvent.on(zoomLink, 'click', L.DomEvent.stop);
+      L.DomEvent.on(zoomLink, 'click', function () {
+        this._map.fitBounds(resultFeature.getBounds(), {
+          padding: [20, 20],
+          maxZoom: 17
+        });
+      }, this);
+    }
+
+    deleteLink = $('.js-deletemarkup', popupContainer);
+    if (deleteLink) {
+      L.DomEvent.on(deleteLink, 'click', L.DomEvent.stop);
+      L.DomEvent.on(deleteLink, 'click', function () {
+        // TODO. maybe remove any event handlers on zoom and delete buttons?
+        this._map.removeLayer(resultFeature);
+      }, this);
+    }
+
+    resultFeature.addTo(this._map);
+    resultFeature.bindPopup(popupContainer, this.options.popupOptions);
+    resultFeature.openPopup(resultFeature.getBounds().getCenter());
+  },
+  // handle map click during ongoing measurement
+  // add new clicked point, update measure layers and results ui
+  _handleMeasureClick: function (evt) {
+    var latlng = evt.latlng, lastClick = _.last(this._latlngs), vertexSymbol = this._symbols.getSymbol('measureVertex');
+
+    this._map.closePopup(); // open popups aren't closed on click. may be bug. close popup manually just in case.
+
+    if (!lastClick || !latlng.equals(lastClick)) { // skip if same point as last click, happens on `dblclick`
+      this._latlngs.push(latlng);
+      this._addMeasureArea(this._latlngs);
+      this._addMeasureBoundary(this._latlngs);
+
+      this._measureVertexes.eachLayer(function (layer) {
+        layer.setStyle(vertexSymbol);
+        // reset all vertexes to non-active class - only last vertex is active
+        // `layer.setStyle({ className: 'layer-measurevertex'})` doesn't work. https://github.com/leaflet/leaflet/issues/2662
+        // set attribute on path directly
+        layer._path.setAttribute('class', vertexSymbol.className);
+      });
+
+      this._addNewVertex(latlng);
+
+      if (this._measureBoundary) {
+        this._measureBoundary.bringToFront();
+      }
+      this._measureVertexes.bringToFront();
+    }
+
+    this._updateResults();
+    this._updateMeasureStartedWithPoints();
+  },
+  // handle map mouse out during ongoing measure
+  // remove floating cursor vertex from map
+  _handleMapMouseOut: function () {
+    if (this._measureDrag) {
+      this._layer.removeLayer(this._measureDrag);
+      this._measureDrag = null;
+    }
+  },
+  // add various measure graphics to map - vertex, area, boundary
+  _addNewVertex: function (latlng) {
+    L.circleMarker(latlng, this._symbols.getSymbol('measureVertexActive')).addTo(this._measureVertexes);
+  },
+  _addMeasureArea: function (latlngs) {
+    if (latlngs.length < 3) {
+      if (this._measureArea) {
+        this._layer.removeLayer(this._measureArea);
+        this._measureArea = null;
+      }
+      return;
+    }
+    if (!this._measureArea) {
+      this._measureArea = L.polygon(latlngs, this._symbols.getSymbol('measureArea')).addTo(this._layer);
+    } else {
+      this._measureArea.setLatLngs(latlngs);
+    }
+  },
+  _addMeasureBoundary: function (latlngs) {
+    if (latlngs.length < 2) {
+      if (this._measureBoundary) {
+        this._layer.removeLayer(this._measureBoundary);
+        this._measureBoundary = null;
+      }
+      return;
+    }
+    if (!this._measureBoundary) {
+      this._measureBoundary = L.polyline(latlngs, this._symbols.getSymbol('measureBoundary')).addTo(this._layer);
+    } else {
+      this._measureBoundary.setLatLngs(latlngs);
+    }
+  }
+});
+
+L.Map.mergeOptions({
+  measureControl: false
+});
+
+L.Map.addInitHook(function () {
+  if (this.options.measureControl) {
+    this.measureControl = (new L.Control.Measure()).addTo(this);
+  }
+});
+
+L.control.measure = function (options) {
+  return new L.Control.Measure(options);
+};
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./calc":17,"./dom":18,"./mapsymbology":20,"./units":21,"humanize":15,"underscore":16}],20:[function(require,module,exports){
+// mapsymbology.js
+
+var _ = require('underscore');
+
+var color = require('color');
+
+var Symbology = function (options) {
+  this.setOptions(options);
+};
+
+Symbology.DEFAULTS = {
+  activeColor: '#ABE67E',    // base color for map features while actively measuring
+  completedColor: '#C8F2BE'  // base color for permenant features generated from completed measure
+};
+
+_.extend(Symbology.prototype, {
+  setOptions: function (options) {
+    this._options = _.extend({}, Symbology.DEFAULTS, this._options, options);
+    return this;
+  },
+  getSymbol: function (name) {
+    var symbols = {
+      measureCollector: {
+        clickable: true,
+        stroke: false,
+        fillOpacity: 0.0,
+        className: 'layer-measurecollector'
+      },
+      measureDrag: {
+        clickable: false,
+        radius: 4,
+        color: this._options.activeColor,
+        weight: 2,
+        opacity: 0.7,
+        fillColor: this._options.activeColor,
+        fillOpacity: 0.5,
+        className: 'layer-measuredrag'
+      },
+      measureArea: {
+        clickable: false,
+        stroke: false,
+        fillColor: this._options.activeColor,
+        fillOpacity: 0.2,
+        className: 'layer-measurearea'
+      },
+      measureBoundary: {
+        clickable: false,
+        color: this._options.activeColor,
+        weight: 2,
+        opacity: 0.9,
+        fill: false,
+        className: 'layer-measureboundary'
+      },
+      measureVertex: {
+        clickable: false,
+        radius: 4,
+        color: this._options.activeColor,
+        weight: 2,
+        opacity: 1,
+        fillColor: this._options.activeColor,
+        fillOpacity: 0.7,
+        className: 'layer-measurevertex'
+      },
+      measureVertexActive: {
+        clickable: false,
+        radius: 4,
+        color: this._options.activeColor,
+        weight: 2,
+        opacity: 1,
+        fillColor: color(this._options.activeColor).darken(0.15),
+        fillOpacity: 0.7,
+        className: 'layer-measurevertex active'
+      },
+      resultArea: {
+        clickable: true,
+        color: this._options.completedColor,
+        weight: 2,
+        opacity: 0.9,
+        fillColor: this._options.completedColor,
+        fillOpacity: 0.2,
+        className: 'layer-measure-resultarea'
+      },
+      resultLine: {
+        clickable: true,
+        color: this._options.completedColor,
+        weight: 3,
+        opacity: 0.9,
+        fill: false,
+        className: 'layer-measure-resultline'
+      },
+      resultPoint: {
+        clickable: true,
+        radius: 4,
+        color: this._options.completedColor,
+        weight: 2,
+        opacity: 1,
+        fillColor: this._options.completedColor,
+        fillOpacity: 0.7,
+        className: 'layer-measure-resultpoint'
+      }
+    };
+    return symbols[name];
+  }
+});
+
+module.exports = Symbology;
+},{"color":1,"underscore":16}],21:[function(require,module,exports){
+// units.js
+// Unit configurations
+
+module.exports = {
+  acres: {
+    display: 'Acres',
+    decimals: 2
+  },
+  feet: {
+    display: 'Feet',
+    decimals: 0
+  },
+  kilometers: {
+    display: 'Kilometers',
+    decimals: 2
+  },
+  hectares: {
+    display: 'Hectares',
+    decimals: 2
+  },
+  meters: {
+    display: 'Meters',
+    decimals: 0
+  },
+  miles: {
+    display: 'Miles',
+    decimals: 2
+  },
+  sqmeters: {
+    display: 'Sq Meters',
+    decimals: 0
+  },
+  sqmiles: {
+    display: 'Sq Miles',
+    decimals: 2
+  }
+};
+},{}]},{},[19]);
Index: /binary-improvements/webserver/leaflet/measure/leaflet-measure.min.js
===================================================================
--- /binary-improvements/webserver/leaflet/measure/leaflet-measure.min.js	(revision 249)
+++ /binary-improvements/webserver/leaflet/measure/leaflet-measure.min.js	(revision 249)
@@ -0,0 +1,3 @@
+!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){var c=a("color-convert"),d=a("color-string"),e=function(a){if(a instanceof e)return a;if(!(this instanceof e))return new e(a);if(this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},"string"==typeof a){var b=d.getRgba(a);if(b)this.setValues("rgb",b);else if(b=d.getHsla(a))this.setValues("hsl",b);else{if(!(b=d.getHwb(a)))throw new Error('Unable to parse color from string "'+a+'"');this.setValues("hwb",b)}}else if("object"==typeof a){var b=a;if(void 0!==b.r||void 0!==b.red)this.setValues("rgb",b);else if(void 0!==b.l||void 0!==b.lightness)this.setValues("hsl",b);else if(void 0!==b.v||void 0!==b.value)this.setValues("hsv",b);else if(void 0!==b.w||void 0!==b.whiteness)this.setValues("hwb",b);else{if(void 0===b.c&&void 0===b.cyan)throw new Error("Unable to parse color from object "+JSON.stringify(a));this.setValues("cmyk",b)}}};e.prototype={rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){return 1!==this.values.alpha?this.values.hwb.concat([this.values.alpha]):this.values.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var a=this.values.rgb;return a.concat([this.values.alpha])},hslaArray:function(){var a=this.values.hsl;return a.concat([this.values.alpha])},alpha:function(a){return void 0===a?this.values.alpha:(this.setValues("alpha",a),this)},red:function(a){return this.setChannel("rgb",0,a)},green:function(a){return this.setChannel("rgb",1,a)},blue:function(a){return this.setChannel("rgb",2,a)},hue:function(a){return this.setChannel("hsl",0,a)},saturation:function(a){return this.setChannel("hsl",1,a)},lightness:function(a){return this.setChannel("hsl",2,a)},saturationv:function(a){return this.setChannel("hsv",1,a)},whiteness:function(a){return this.setChannel("hwb",1,a)},blackness:function(a){return this.setChannel("hwb",2,a)},value:function(a){return this.setChannel("hsv",2,a)},cyan:function(a){return this.setChannel("cmyk",0,a)},magenta:function(a){return this.setChannel("cmyk",1,a)},yellow:function(a){return this.setChannel("cmyk",2,a)},black:function(a){return this.setChannel("cmyk",3,a)},hexString:function(){return d.hexString(this.values.rgb)},rgbString:function(){return d.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return d.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return d.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return d.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return d.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return d.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return d.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){return this.values.rgb[0]<<16|this.values.rgb[1]<<8|this.values.rgb[2]},luminosity:function(){for(var a=this.values.rgb,b=[],c=0;c<a.length;c++){var d=a[c]/255;b[c]=.03928>=d?d/12.92:Math.pow((d+.055)/1.055,2.4)}return.2126*b[0]+.7152*b[1]+.0722*b[2]},contrast:function(a){var b=this.luminosity(),c=a.luminosity();return b>c?(b+.05)/(c+.05):(c+.05)/(b+.05)},level:function(a){var b=this.contrast(a);return b>=7.1?"AAA":b>=4.5?"AA":""},dark:function(){var a=this.values.rgb,b=(299*a[0]+587*a[1]+114*a[2])/1e3;return 128>b},light:function(){return!this.dark()},negate:function(){for(var a=[],b=0;3>b;b++)a[b]=255-this.values.rgb[b];return this.setValues("rgb",a),this},lighten:function(a){return this.values.hsl[2]+=this.values.hsl[2]*a,this.setValues("hsl",this.values.hsl),this},darken:function(a){return this.values.hsl[2]-=this.values.hsl[2]*a,this.setValues("hsl",this.values.hsl),this},saturate:function(a){return this.values.hsl[1]+=this.values.hsl[1]*a,this.setValues("hsl",this.values.hsl),this},desaturate:function(a){return this.values.hsl[1]-=this.values.hsl[1]*a,this.setValues("hsl",this.values.hsl),this},whiten:function(a){return this.values.hwb[1]+=this.values.hwb[1]*a,this.setValues("hwb",this.values.hwb),this},blacken:function(a){return this.values.hwb[2]+=this.values.hwb[2]*a,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var a=this.values.rgb,b=.3*a[0]+.59*a[1]+.11*a[2];return this.setValues("rgb",[b,b,b]),this},clearer:function(a){return this.setValues("alpha",this.values.alpha-this.values.alpha*a),this},opaquer:function(a){return this.setValues("alpha",this.values.alpha+this.values.alpha*a),this},rotate:function(a){var b=this.values.hsl[0];return b=(b+a)%360,b=0>b?360+b:b,this.values.hsl[0]=b,this.setValues("hsl",this.values.hsl),this},mix:function(a,b){b=1-(null==b?.5:b);for(var c=2*b-1,d=this.alpha()-a.alpha(),e=((c*d==-1?c:(c+d)/(1+c*d))+1)/2,f=1-e,g=this.rgbArray(),h=a.rgbArray(),i=0;i<g.length;i++)g[i]=g[i]*e+h[i]*f;this.setValues("rgb",g);var j=this.alpha()*b+a.alpha()*(1-b);return this.setValues("alpha",j),this},toJSON:function(){return this.rgb()},clone:function(){return new e(this.rgb())}},e.prototype.getValues=function(a){for(var b={},c=0;c<a.length;c++)b[a.charAt(c)]=this.values[a][c];return 1!=this.values.alpha&&(b.a=this.values.alpha),b},e.prototype.setValues=function(a,b){var d={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},e={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},f=1;if("alpha"==a)f=b;else if(b.length)this.values[a]=b.slice(0,a.length),f=b[a.length];else if(void 0!==b[a.charAt(0)]){for(var g=0;g<a.length;g++)this.values[a][g]=b[a.charAt(g)];f=b.a}else if(void 0!==b[d[a][0]]){for(var h=d[a],g=0;g<a.length;g++)this.values[a][g]=b[h[g]];f=b.alpha}if(this.values.alpha=Math.max(0,Math.min(1,void 0!==f?f:this.values.alpha)),"alpha"!=a){for(var g=0;g<a.length;g++){var i=Math.max(0,Math.min(e[a][g],this.values[a][g]));this.values[a][g]=Math.round(i)}for(var j in d){j!=a&&(this.values[j]=c[a][j](this.values[a]));for(var g=0;g<j.length;g++){var i=Math.max(0,Math.min(e[j][g],this.values[j][g]));this.values[j][g]=Math.round(i)}}return!0}},e.prototype.setSpace=function(a,b){var c=b[0];return void 0===c?this.getValues(a):("number"==typeof c&&(c=Array.prototype.slice.call(b)),this.setValues(a,c),this)},e.prototype.setChannel=function(a,b,c){return void 0===c?this.values[a][b]:(this.values[a][b]=c,this.setValues(a,this.values[a]),this)},b.exports=e},{"color-convert":3,"color-string":4}],2:[function(a,c){function d(a){var b,c,d,e=a[0]/255,f=a[1]/255,g=a[2]/255,h=Math.min(e,f,g),i=Math.max(e,f,g),j=i-h;return i==h?b=0:e==i?b=(f-g)/j:f==i?b=2+(g-e)/j:g==i&&(b=4+(e-f)/j),b=Math.min(60*b,360),0>b&&(b+=360),d=(h+i)/2,c=i==h?0:.5>=d?j/(i+h):j/(2-i-h),[b,100*c,100*d]}function e(a){var b,c,d,e=a[0],f=a[1],g=a[2],h=Math.min(e,f,g),i=Math.max(e,f,g),j=i-h;return c=0==i?0:j/i*1e3/10,i==h?b=0:e==i?b=(f-g)/j:f==i?b=2+(g-e)/j:g==i&&(b=4+(e-f)/j),b=Math.min(60*b,360),0>b&&(b+=360),d=i/255*1e3/10,[b,c,d]}function f(a){var b=a[0],c=a[1],e=a[2],f=d(a)[0],g=1/255*Math.min(b,Math.min(c,e)),e=1-1/255*Math.max(b,Math.max(c,e));return[f,100*g,100*e]}function h(a){var b,c,d,e,f=a[0]/255,g=a[1]/255,h=a[2]/255;return e=Math.min(1-f,1-g,1-h),b=(1-f-e)/(1-e)||0,c=(1-g-e)/(1-e)||0,d=(1-h-e)/(1-e)||0,[100*b,100*c,100*d,100*e]}function i(a){return Y[JSON.stringify(a)]}function j(a){var b=a[0]/255,c=a[1]/255,d=a[2]/255;b=b>.04045?Math.pow((b+.055)/1.055,2.4):b/12.92,c=c>.04045?Math.pow((c+.055)/1.055,2.4):c/12.92,d=d>.04045?Math.pow((d+.055)/1.055,2.4):d/12.92;var e=.4124*b+.3576*c+.1805*d,f=.2126*b+.7152*c+.0722*d,g=.0193*b+.1192*c+.9505*d;return[100*e,100*f,100*g]}function k(a){var b,c,d,e=j(a),f=e[0],g=e[1],h=e[2];return f/=95.047,g/=100,h/=108.883,f=f>.008856?Math.pow(f,1/3):7.787*f+16/116,g=g>.008856?Math.pow(g,1/3):7.787*g+16/116,h=h>.008856?Math.pow(h,1/3):7.787*h+16/116,b=116*g-16,c=500*(f-g),d=200*(g-h),[b,c,d]}function l(a){return L(k(a))}function m(a){var b,c,d,e,f,g=a[0]/360,h=a[1]/100,i=a[2]/100;if(0==h)return f=255*i,[f,f,f];c=.5>i?i*(1+h):i+h-i*h,b=2*i-c,e=[0,0,0];for(var j=0;3>j;j++)d=g+1/3*-(j-1),0>d&&d++,d>1&&d--,f=1>6*d?b+6*(c-b)*d:1>2*d?c:2>3*d?b+(c-b)*(2/3-d)*6:b,e[j]=255*f;return e}function n(a){var b,c,d=a[0],e=a[1]/100,f=a[2]/100;return f*=2,e*=1>=f?f:2-f,c=(f+e)/2,b=2*e/(f+e),[d,100*b,100*c]}function o(a){return f(m(a))}function p(a){return h(m(a))}function q(a){return i(m(a))}function s(a){var b=a[0]/60,c=a[1]/100,d=a[2]/100,e=Math.floor(b)%6,f=b-Math.floor(b),g=255*d*(1-c),h=255*d*(1-c*f),i=255*d*(1-c*(1-f)),d=255*d;switch(e){case 0:return[d,i,g];case 1:return[h,d,g];case 2:return[g,d,i];case 3:return[g,h,d];case 4:return[i,g,d];case 5:return[d,g,h]}}function t(a){var b,c,d=a[0],e=a[1]/100,f=a[2]/100;return c=(2-e)*f,b=e*f,b/=1>=c?c:2-c,b=b||0,c/=2,[d,100*b,100*c]}function u(a){return f(s(a))}function v(a){return h(s(a))}function w(a){return i(s(a))}function x(a){var c,d,e,f,h=a[0]/360,i=a[1]/100,j=a[2]/100,k=i+j;switch(k>1&&(i/=k,j/=k),c=Math.floor(6*h),d=1-j,e=6*h-c,0!=(1&c)&&(e=1-e),f=i+e*(d-i),c){default:case 6:case 0:r=d,g=f,b=i;break;case 1:r=f,g=d,b=i;break;case 2:r=i,g=d,b=f;break;case 3:r=i,g=f,b=d;break;case 4:r=f,g=i,b=d;break;case 5:r=d,g=i,b=f}return[255*r,255*g,255*b]}function y(a){return d(x(a))}function z(a){return e(x(a))}function A(a){return h(x(a))}function B(a){return i(x(a))}function C(a){var b,c,d,e=a[0]/100,f=a[1]/100,g=a[2]/100,h=a[3]/100;return b=1-Math.min(1,e*(1-h)+h),c=1-Math.min(1,f*(1-h)+h),d=1-Math.min(1,g*(1-h)+h),[255*b,255*c,255*d]}function D(a){return d(C(a))}function E(a){return e(C(a))}function F(a){return f(C(a))}function G(a){return i(C(a))}function H(a){var b,c,d,e=a[0]/100,f=a[1]/100,g=a[2]/100;return b=3.2406*e+-1.5372*f+g*-.4986,c=e*-.9689+1.8758*f+.0415*g,d=.0557*e+f*-.204+1.057*g,b=b>.0031308?1.055*Math.pow(b,1/2.4)-.055:b=12.92*b,c=c>.0031308?1.055*Math.pow(c,1/2.4)-.055:c=12.92*c,d=d>.0031308?1.055*Math.pow(d,1/2.4)-.055:d=12.92*d,b=Math.min(Math.max(0,b),1),c=Math.min(Math.max(0,c),1),d=Math.min(Math.max(0,d),1),[255*b,255*c,255*d]}function I(a){var b,c,d,e=a[0],f=a[1],g=a[2];return e/=95.047,f/=100,g/=108.883,e=e>.008856?Math.pow(e,1/3):7.787*e+16/116,f=f>.008856?Math.pow(f,1/3):7.787*f+16/116,g=g>.008856?Math.pow(g,1/3):7.787*g+16/116,b=116*f-16,c=500*(e-f),d=200*(f-g),[b,c,d]}function J(a){return L(I(a))}function K(a){var b,c,d,e,f=a[0],g=a[1],h=a[2];return 8>=f?(c=100*f/903.3,e=7.787*(c/100)+16/116):(c=100*Math.pow((f+16)/116,3),e=Math.pow(c/100,1/3)),b=.008856>=b/95.047?b=95.047*(g/500+e-16/116)/7.787:95.047*Math.pow(g/500+e,3),d=.008859>=d/108.883?d=108.883*(e-h/200-16/116)/7.787:108.883*Math.pow(e-h/200,3),[b,c,d]}function L(a){var b,c,d,e=a[0],f=a[1],g=a[2];return b=Math.atan2(g,f),c=360*b/2/Math.PI,0>c&&(c+=360),d=Math.sqrt(f*f+g*g),[e,d,c]}function M(a){return H(K(a))}function N(a){var b,c,d,e=a[0],f=a[1],g=a[2];return d=g/360*2*Math.PI,b=f*Math.cos(d),c=f*Math.sin(d),[e,b,c]}function O(a){return K(N(a))}function P(a){return M(N(a))}function Q(a){return X[a]}function R(a){return d(Q(a))}function S(a){return e(Q(a))}function T(a){return f(Q(a))}function U(a){return h(Q(a))}function V(a){return k(Q(a))}function W(a){return j(Q(a))}c.exports={rgb2hsl:d,rgb2hsv:e,rgb2hwb:f,rgb2cmyk:h,rgb2keyword:i,rgb2xyz:j,rgb2lab:k,rgb2lch:l,hsl2rgb:m,hsl2hsv:n,hsl2hwb:o,hsl2cmyk:p,hsl2keyword:q,hsv2rgb:s,hsv2hsl:t,hsv2hwb:u,hsv2cmyk:v,hsv2keyword:w,hwb2rgb:x,hwb2hsl:y,hwb2hsv:z,hwb2cmyk:A,hwb2keyword:B,cmyk2rgb:C,cmyk2hsl:D,cmyk2hsv:E,cmyk2hwb:F,cmyk2keyword:G,keyword2rgb:Q,keyword2hsl:R,keyword2hsv:S,keyword2hwb:T,keyword2cmyk:U,keyword2lab:V,keyword2xyz:W,xyz2rgb:H,xyz2lab:I,xyz2lch:J,lab2xyz:K,lab2rgb:M,lab2lch:L,lch2lab:N,lch2xyz:O,lch2rgb:P};var X={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},Y={};for(var Z in X)Y[JSON.stringify(X[Z])]=Z},{}],3:[function(a,b){var c=a("./conversions"),d=function(){return new i};for(var e in c){d[e+"Raw"]=function(a){return function(b){return"number"==typeof b&&(b=Array.prototype.slice.call(arguments)),c[a](b)}}(e);var f=/(\w+)2(\w+)/.exec(e),g=f[1],h=f[2];d[g]=d[g]||{},d[g][h]=d[e]=function(a){return function(b){"number"==typeof b&&(b=Array.prototype.slice.call(arguments));var d=c[a](b);if("string"==typeof d||void 0===d)return d;for(var e=0;e<d.length;e++)d[e]=Math.round(d[e]);return d}}(e)}var i=function(){this.convs={}};i.prototype.routeSpace=function(a,b){var c=b[0];return void 0===c?this.getValues(a):("number"==typeof c&&(c=Array.prototype.slice.call(b)),this.setValues(a,c))},i.prototype.setValues=function(a,b){return this.space=a,this.convs={},this.convs[a]=b,this},i.prototype.getValues=function(a){var b=this.convs[a];if(!b){var c=this.space,e=this.convs[c];b=d[c][a](e),this.convs[a]=b}return b},["rgb","hsl","hsv","cmyk","keyword"].forEach(function(a){i.prototype[a]=function(){return this.routeSpace(a,arguments)}}),b.exports=d},{"./conversions":2}],4:[function(a,b){function c(a){if(a){var b=/^#([a-fA-F0-9]{3})$/,c=/^#([a-fA-F0-9]{6})$/,d=/^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,e=/^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,f=/(\D+)/,g=[0,0,0],h=1,i=a.match(b);if(i){i=i[1];for(var j=0;j<g.length;j++)g[j]=parseInt(i[j]+i[j],16)}else if(i=a.match(c)){i=i[1];for(var j=0;j<g.length;j++)g[j]=parseInt(i.slice(2*j,2*j+2),16)}else if(i=a.match(d)){for(var j=0;j<g.length;j++)g[j]=parseInt(i[j+1]);h=parseFloat(i[4])}else if(i=a.match(e)){for(var j=0;j<g.length;j++)g[j]=Math.round(2.55*parseFloat(i[j+1]));h=parseFloat(i[4])}else if(i=a.match(f)){if("transparent"==i[1])return[0,0,0,0];if(g=t[i[1]],!g)return}for(var j=0;j<g.length;j++)g[j]=r(g[j],0,255);return h=h||0==h?r(h,0,1):1,g[3]=h,g}}function d(a){if(a){var b=/^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/,c=a.match(b);if(c){var d=parseFloat(c[4]),e=r(parseInt(c[1]),0,360),f=r(parseFloat(c[2]),0,100),g=r(parseFloat(c[3]),0,100),h=r(isNaN(d)?1:d,0,1);return[e,f,g,h]}}}function e(a){if(a){var b=/^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/,c=a.match(b);if(c){var d=parseFloat(c[4]),e=r(parseInt(c[1]),0,360),f=r(parseFloat(c[2]),0,100),g=r(parseFloat(c[3]),0,100),h=r(isNaN(d)?1:d,0,1);return[e,f,g,h]}}}function f(a){var b=c(a);return b&&b.slice(0,3)}function g(a){var b=d(a);return b&&b.slice(0,3)}function h(a){var b=c(a);return b?b[3]:(b=d(a))?b[3]:(b=e(a))?b[3]:void 0}function i(a){return"#"+s(a[0])+s(a[1])+s(a[2])}function j(a,b){return 1>b||a[3]&&a[3]<1?k(a,b):"rgb("+a[0]+", "+a[1]+", "+a[2]+")"}function k(a,b){return void 0===b&&(b=void 0!==a[3]?a[3]:1),"rgba("+a[0]+", "+a[1]+", "+a[2]+", "+b+")"}function l(a,b){if(1>b||a[3]&&a[3]<1)return m(a,b);var c=Math.round(a[0]/255*100),d=Math.round(a[1]/255*100),e=Math.round(a[2]/255*100);return"rgb("+c+"%, "+d+"%, "+e+"%)"}function m(a,b){var c=Math.round(a[0]/255*100),d=Math.round(a[1]/255*100),e=Math.round(a[2]/255*100);return"rgba("+c+"%, "+d+"%, "+e+"%, "+(b||a[3]||1)+")"}function n(a,b){return 1>b||a[3]&&a[3]<1?o(a,b):"hsl("+a[0]+", "+a[1]+"%, "+a[2]+"%)"}function o(a,b){return void 0===b&&(b=void 0!==a[3]?a[3]:1),"hsla("+a[0]+", "+a[1]+"%, "+a[2]+"%, "+b+")"}function p(a,b){return void 0===b&&(b=void 0!==a[3]?a[3]:1),"hwb("+a[0]+", "+a[1]+"%, "+a[2]+"%"+(void 0!==b&&1!==b?", "+b:"")+")"}function q(a){return u[a.slice(0,3)]}function r(a,b,c){return Math.min(Math.max(b,a),c)}function s(a){var b=a.toString(16).toUpperCase();return b.length<2?"0"+b:b}var t=a("color-name");b.exports={getRgba:c,getHsla:d,getRgb:f,getHsl:g,getHwb:e,getAlpha:h,hexString:i,rgbString:j,rgbaString:k,percentString:l,percentaString:m,hslString:n,hslaString:o,hwbString:p,keyword:q};var u={};for(var v in t)u[t[v]]=v},{"color-name":5}],5:[function(a,b){b.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}],6:[function(a,b){b.exports=a("./lib/geocrunch")},{"./lib/geocrunch":11}],7:[function(a,b){var c=a("underscore"),d=a("./constants").EARTHRADIUS,e=a("./units"),f=a("./flipcoords"),g={sqmeters:function(a){return a},sqmiles:function(a){return e.sqMeters.toSqMiles(a)},acres:function(a){return e.sqMeters.toAcres(a)}},h=function(a){var b,c,f,g,h=0;for(b=0,c=a.length;c>b;b+=1)f=a[b],g=a[(b+1)%a.length],h+=e.degrees.toRadians(g[0]-f[0])*(2+Math.sin(e.degrees.toRadians(f[1]))+Math.sin(e.degrees.toRadians(g[1])));return Math.abs(h*d*d/2)},i=function(a){var b,c,d,e,f,g=a[0],h=0,i=0,j=0;if(1===a.length)return a[0];for(b=0,c=a.length;c>b;b+=1)d=a[b],e=a[(b+1)%a.length],f=(d[1]-g[1])*(e[0]-g[0])-(e[1]-g[1])*(d[0]-g[0]),h+=f,i+=(d[0]+e[0]-2*g[0])*f,j+=(d[1]+e[1]-2*g[1])*f;return f=3*h,[i/f+g[0],j/f+g[1]]};b.exports={_internalAreaCalc:function(){this._calcedArea||(this._calcedArea=this._coords.length<3?0:h(this._coords))},_internalCenterCalc:function(){!this._calcedCenter&&this._coords.length&&(this._calcedCenter=i(this._coords))},area:function(a){var b=c.extend({units:"sqmeters"},a);return this._internalAreaCalc(),c.isFunction(g[b.units])?g[b.units](this._calcedArea):void 0},center:function(){return this._internalCenterCalc(),this._options.imBackwards===!0?f(this._calcedCenter):this._calcedCenter}}},{"./constants":8,"./flipcoords":10,"./units":13,underscore:14}],8:[function(a,b){b.exports={EARTHRADIUS:6371e3}},{}],9:[function(a,b){var c=a("underscore"),d=a("./constants").EARTHRADIUS,e=a("./units"),f={meters:function(a){return a},kilometers:function(a){return e.meters.toKilometers(a)},feet:function(a){return e.meters.toFeet(a)},miles:function(a){return e.meters.toMiles(a)}},g=function(a,b){var c=e.degrees.toRadians(a[0]-b[0]),f=e.degrees.toRadians(a[1]-b[1]),g=e.degrees.toRadians(a[1]),h=e.degrees.toRadians(b[1]),i=Math.sin(c/2),j=Math.sin(f/2),k=j*j+i*i*Math.cos(g)*Math.cos(h);return 2*d*Math.atan2(Math.sqrt(k),Math.sqrt(1-k))};b.exports={_internalDistanceCalc:function(){var a,b,c=0;if(!this._calcedDistance){for(a=0,b=this._coords.length;b>a;a+=1)a>0&&(c+=g(this._coords[a-1],this._coords[a]));this._calcedDistance=c}},distance:function(a){var b=c.extend({units:"meters"},a);return this._internalDistanceCalc(),c.isFunction(f[b.units])?f[b.units](this._calcedDistance):void 0}}},{"./constants":8,"./units":13,underscore:14}],10:[function(a,b){var c=a("underscore");b.exports=function(a){return c.map(a,function(a){return[a[1],a[0]]})}},{underscore:14}],11:[function(a,b,c){var d=a("underscore"),e=a("./path"),f=a("./distance"),g=a("./area");d.extend(e.prototype,f,g),c.path=function(a,b){return new e(a,b)}},{"./area":7,"./distance":9,"./path":12,underscore:14}],12:[function(a,b){var c=a("./flipcoords"),d=function(a,b){this._options=b||{},a=a||[],this._coords=this._options.imBackwards===!0?c(a):a};b.exports=d},{"./flipcoords":10}],13:[function(a,b,c){c.meters={toFeet:function(a){return 3.28084*a},toKilometers:function(a){return.001*a},toMiles:function(a){return 621371e-9*a}},c.sqMeters={toSqMiles:function(a){return 3.86102e-7*a},toAcres:function(a){return 247105e-9*a}},c.degrees={toRadians:function(a){return a*Math.PI/180}}},{}],14:[function(a,b,c){(function(){var a=this,d=a._,e={},f=Array.prototype,g=Object.prototype,h=Function.prototype,i=f.push,j=f.slice,k=f.concat,l=g.toString,m=g.hasOwnProperty,n=f.forEach,o=f.map,p=f.reduce,q=f.reduceRight,r=f.filter,s=f.every,t=f.some,u=f.indexOf,v=f.lastIndexOf,w=Array.isArray,x=Object.keys,y=h.bind,z=function(a){return a instanceof z?a:this instanceof z?void(this._wrapped=a):new z(a)};"undefined"!=typeof c?("undefined"!=typeof b&&b.exports&&(c=b.exports=z),c._=z):a._=z,z.VERSION="1.5.2";var A=z.each=z.forEach=function(a,b,c){if(null!=a)if(n&&a.forEach===n)a.forEach(b,c);else if(a.length===+a.length){for(var d=0,f=a.length;f>d;d++)if(b.call(c,a[d],d,a)===e)return}else for(var g=z.keys(a),d=0,f=g.length;f>d;d++)if(b.call(c,a[g[d]],g[d],a)===e)return};z.map=z.collect=function(a,b,c){var d=[];return null==a?d:o&&a.map===o?a.map(b,c):(A(a,function(a,e,f){d.push(b.call(c,a,e,f))}),d)};var B="Reduce of empty array with no initial value";z.reduce=z.foldl=z.inject=function(a,b,c,d){var e=arguments.length>2;if(null==a&&(a=[]),p&&a.reduce===p)return d&&(b=z.bind(b,d)),e?a.reduce(b,c):a.reduce(b);if(A(a,function(a,f,g){e?c=b.call(d,c,a,f,g):(c=a,e=!0)}),!e)throw new TypeError(B);return c},z.reduceRight=z.foldr=function(a,b,c,d){var e=arguments.length>2;if(null==a&&(a=[]),q&&a.reduceRight===q)return d&&(b=z.bind(b,d)),e?a.reduceRight(b,c):a.reduceRight(b);var f=a.length;if(f!==+f){var g=z.keys(a);f=g.length}if(A(a,function(h,i,j){i=g?g[--f]:--f,e?c=b.call(d,c,a[i],i,j):(c=a[i],e=!0)}),!e)throw new TypeError(B);return c},z.find=z.detect=function(a,b,c){var d;return C(a,function(a,e,f){return b.call(c,a,e,f)?(d=a,!0):void 0}),d},z.filter=z.select=function(a,b,c){var d=[];return null==a?d:r&&a.filter===r?a.filter(b,c):(A(a,function(a,e,f){b.call(c,a,e,f)&&d.push(a)}),d)},z.reject=function(a,b,c){return z.filter(a,function(a,d,e){return!b.call(c,a,d,e)},c)},z.every=z.all=function(a,b,c){b||(b=z.identity);var d=!0;return null==a?d:s&&a.every===s?a.every(b,c):(A(a,function(a,f,g){return(d=d&&b.call(c,a,f,g))?void 0:e}),!!d)};var C=z.some=z.any=function(a,b,c){b||(b=z.identity);var d=!1;return null==a?d:t&&a.some===t?a.some(b,c):(A(a,function(a,f,g){return d||(d=b.call(c,a,f,g))?e:void 0}),!!d)};z.contains=z.include=function(a,b){return null==a?!1:u&&a.indexOf===u?-1!=a.indexOf(b):C(a,function(a){return a===b})},z.invoke=function(a,b){var c=j.call(arguments,2),d=z.isFunction(b);return z.map(a,function(a){return(d?b:a[b]).apply(a,c)})},z.pluck=function(a,b){return z.map(a,function(a){return a[b]})},z.where=function(a,b,c){return z.isEmpty(b)?c?void 0:[]:z[c?"find":"filter"](a,function(a){for(var c in b)if(b[c]!==a[c])return!1;return!0})},z.findWhere=function(a,b){return z.where(a,b,!0)},z.max=function(a,b,c){if(!b&&z.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.max.apply(Math,a);if(!b&&z.isEmpty(a))return-1/0;var d={computed:-1/0,value:-1/0};return A(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g>d.computed&&(d={value:a,computed:g})}),d.value},z.min=function(a,b,c){if(!b&&z.isArray(a)&&a[0]===+a[0]&&a.length<65535)return Math.min.apply(Math,a);if(!b&&z.isEmpty(a))return 1/0;var d={computed:1/0,value:1/0};return A(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g<d.computed&&(d={value:a,computed:g})}),d.value},z.shuffle=function(a){var b,c=0,d=[];return A(a,function(a){b=z.random(c++),d[c-1]=d[b],d[b]=a}),d},z.sample=function(a,b,c){return arguments.length<2||c?a[z.random(a.length-1)]:z.shuffle(a).slice(0,Math.max(0,b))};var D=function(a){return z.isFunction(a)?a:function(b){return b[a]}};z.sortBy=function(a,b,c){var d=D(b);return z.pluck(z.map(a,function(a,b,e){return{value:a,index:b,criteria:d.call(c,a,b,e)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;if(c!==d){if(c>d||void 0===c)return 1;if(d>c||void 0===d)return-1}return a.index-b.index}),"value")};var E=function(a){return function(b,c,d){var e={},f=null==c?z.identity:D(c);return A(b,function(c,g){var h=f.call(d,c,g,b);a(e,h,c)}),e}};z.groupBy=E(function(a,b,c){(z.has(a,b)?a[b]:a[b]=[]).push(c)}),z.indexBy=E(function(a,b,c){a[b]=c}),z.countBy=E(function(a,b){z.has(a,b)?a[b]++:a[b]=1}),z.sortedIndex=function(a,b,c,d){c=null==c?z.identity:D(c);for(var e=c.call(d,b),f=0,g=a.length;g>f;){var h=f+g>>>1;c.call(d,a[h])<e?f=h+1:g=h}return f},z.toArray=function(a){return a?z.isArray(a)?j.call(a):a.length===+a.length?z.map(a,z.identity):z.values(a):[]},z.size=function(a){return null==a?0:a.length===+a.length?a.length:z.keys(a).length},z.first=z.head=z.take=function(a,b,c){return null==a?void 0:null==b||c?a[0]:j.call(a,0,b)},z.initial=function(a,b,c){return j.call(a,0,a.length-(null==b||c?1:b))},z.last=function(a,b,c){return null==a?void 0:null==b||c?a[a.length-1]:j.call(a,Math.max(a.length-b,0))},z.rest=z.tail=z.drop=function(a,b,c){return j.call(a,null==b||c?1:b)},z.compact=function(a){return z.filter(a,z.identity)};var F=function(a,b,c){return b&&z.every(a,z.isArray)?k.apply(c,a):(A(a,function(a){z.isArray(a)||z.isArguments(a)?b?i.apply(c,a):F(a,b,c):c.push(a)}),c)};z.flatten=function(a,b){return F(a,b,[])},z.without=function(a){return z.difference(a,j.call(arguments,1))},z.uniq=z.unique=function(a,b,c,d){z.isFunction(b)&&(d=c,c=b,b=!1);var e=c?z.map(a,c,d):a,f=[],g=[];return A(e,function(c,d){(b?d&&g[g.length-1]===c:z.contains(g,c))||(g.push(c),f.push(a[d]))}),f},z.union=function(){return z.uniq(z.flatten(arguments,!0))},z.intersection=function(a){var b=j.call(arguments,1);return z.filter(z.uniq(a),function(a){return z.every(b,function(b){return z.indexOf(b,a)>=0})})},z.difference=function(a){var b=k.apply(f,j.call(arguments,1));
+return z.filter(a,function(a){return!z.contains(b,a)})},z.zip=function(){for(var a=z.max(z.pluck(arguments,"length").concat(0)),b=new Array(a),c=0;a>c;c++)b[c]=z.pluck(arguments,""+c);return b},z.object=function(a,b){if(null==a)return{};for(var c={},d=0,e=a.length;e>d;d++)b?c[a[d]]=b[d]:c[a[d][0]]=a[d][1];return c},z.indexOf=function(a,b,c){if(null==a)return-1;var d=0,e=a.length;if(c){if("number"!=typeof c)return d=z.sortedIndex(a,b),a[d]===b?d:-1;d=0>c?Math.max(0,e+c):c}if(u&&a.indexOf===u)return a.indexOf(b,c);for(;e>d;d++)if(a[d]===b)return d;return-1},z.lastIndexOf=function(a,b,c){if(null==a)return-1;var d=null!=c;if(v&&a.lastIndexOf===v)return d?a.lastIndexOf(b,c):a.lastIndexOf(b);for(var e=d?c:a.length;e--;)if(a[e]===b)return e;return-1},z.range=function(a,b,c){arguments.length<=1&&(b=a||0,a=0),c=arguments[2]||1;for(var d=Math.max(Math.ceil((b-a)/c),0),e=0,f=new Array(d);d>e;)f[e++]=a,a+=c;return f};var G=function(){};z.bind=function(a,b){var c,d;if(y&&a.bind===y)return y.apply(a,j.call(arguments,1));if(!z.isFunction(a))throw new TypeError;return c=j.call(arguments,2),d=function(){if(!(this instanceof d))return a.apply(b,c.concat(j.call(arguments)));G.prototype=a.prototype;var e=new G;G.prototype=null;var f=a.apply(e,c.concat(j.call(arguments)));return Object(f)===f?f:e}},z.partial=function(a){var b=j.call(arguments,1);return function(){return a.apply(this,b.concat(j.call(arguments)))}},z.bindAll=function(a){var b=j.call(arguments,1);if(0===b.length)throw new Error("bindAll must be passed function names");return A(b,function(b){a[b]=z.bind(a[b],a)}),a},z.memoize=function(a,b){var c={};return b||(b=z.identity),function(){var d=b.apply(this,arguments);return z.has(c,d)?c[d]:c[d]=a.apply(this,arguments)}},z.delay=function(a,b){var c=j.call(arguments,2);return setTimeout(function(){return a.apply(null,c)},b)},z.defer=function(a){return z.delay.apply(z,[a,1].concat(j.call(arguments,1)))},z.throttle=function(a,b,c){var d,e,f,g=null,h=0;c||(c={});var i=function(){h=c.leading===!1?0:new Date,g=null,f=a.apply(d,e)};return function(){var j=new Date;h||c.leading!==!1||(h=j);var k=b-(j-h);return d=this,e=arguments,0>=k?(clearTimeout(g),g=null,h=j,f=a.apply(d,e)):g||c.trailing===!1||(g=setTimeout(i,k)),f}},z.debounce=function(a,b,c){var d,e,f,g,h;return function(){f=this,e=arguments,g=new Date;var i=function(){var j=new Date-g;b>j?d=setTimeout(i,b-j):(d=null,c||(h=a.apply(f,e)))},j=c&&!d;return d||(d=setTimeout(i,b)),j&&(h=a.apply(f,e)),h}},z.once=function(a){var b,c=!1;return function(){return c?b:(c=!0,b=a.apply(this,arguments),a=null,b)}},z.wrap=function(a,b){return function(){var c=[a];return i.apply(c,arguments),b.apply(this,c)}},z.compose=function(){var a=arguments;return function(){for(var b=arguments,c=a.length-1;c>=0;c--)b=[a[c].apply(this,b)];return b[0]}},z.after=function(a,b){return function(){return--a<1?b.apply(this,arguments):void 0}},z.keys=x||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)z.has(a,c)&&b.push(c);return b},z.values=function(a){for(var b=z.keys(a),c=b.length,d=new Array(c),e=0;c>e;e++)d[e]=a[b[e]];return d},z.pairs=function(a){for(var b=z.keys(a),c=b.length,d=new Array(c),e=0;c>e;e++)d[e]=[b[e],a[b[e]]];return d},z.invert=function(a){for(var b={},c=z.keys(a),d=0,e=c.length;e>d;d++)b[a[c[d]]]=c[d];return b},z.functions=z.methods=function(a){var b=[];for(var c in a)z.isFunction(a[c])&&b.push(c);return b.sort()},z.extend=function(a){return A(j.call(arguments,1),function(b){if(b)for(var c in b)a[c]=b[c]}),a},z.pick=function(a){var b={},c=k.apply(f,j.call(arguments,1));return A(c,function(c){c in a&&(b[c]=a[c])}),b},z.omit=function(a){var b={},c=k.apply(f,j.call(arguments,1));for(var d in a)z.contains(c,d)||(b[d]=a[d]);return b},z.defaults=function(a){return A(j.call(arguments,1),function(b){if(b)for(var c in b)void 0===a[c]&&(a[c]=b[c])}),a},z.clone=function(a){return z.isObject(a)?z.isArray(a)?a.slice():z.extend({},a):a},z.tap=function(a,b){return b(a),a};var H=function(a,b,c,d){if(a===b)return 0!==a||1/a==1/b;if(null==a||null==b)return a===b;a instanceof z&&(a=a._wrapped),b instanceof z&&(b=b._wrapped);var e=l.call(a);if(e!=l.call(b))return!1;switch(e){case"[object String]":return a==String(b);case"[object Number]":return a!=+a?b!=+b:0==a?1/a==1/b:a==+b;case"[object Date]":case"[object Boolean]":return+a==+b;case"[object RegExp]":return a.source==b.source&&a.global==b.global&&a.multiline==b.multiline&&a.ignoreCase==b.ignoreCase}if("object"!=typeof a||"object"!=typeof b)return!1;for(var f=c.length;f--;)if(c[f]==a)return d[f]==b;var g=a.constructor,h=b.constructor;if(g!==h&&!(z.isFunction(g)&&g instanceof g&&z.isFunction(h)&&h instanceof h))return!1;c.push(a),d.push(b);var i=0,j=!0;if("[object Array]"==e){if(i=a.length,j=i==b.length)for(;i--&&(j=H(a[i],b[i],c,d)););}else{for(var k in a)if(z.has(a,k)&&(i++,!(j=z.has(b,k)&&H(a[k],b[k],c,d))))break;if(j){for(k in b)if(z.has(b,k)&&!i--)break;j=!i}}return c.pop(),d.pop(),j};z.isEqual=function(a,b){return H(a,b,[],[])},z.isEmpty=function(a){if(null==a)return!0;if(z.isArray(a)||z.isString(a))return 0===a.length;for(var b in a)if(z.has(a,b))return!1;return!0},z.isElement=function(a){return!(!a||1!==a.nodeType)},z.isArray=w||function(a){return"[object Array]"==l.call(a)},z.isObject=function(a){return a===Object(a)},A(["Arguments","Function","String","Number","Date","RegExp"],function(a){z["is"+a]=function(b){return l.call(b)=="[object "+a+"]"}}),z.isArguments(arguments)||(z.isArguments=function(a){return!(!a||!z.has(a,"callee"))}),"function"!=typeof/./&&(z.isFunction=function(a){return"function"==typeof a}),z.isFinite=function(a){return isFinite(a)&&!isNaN(parseFloat(a))},z.isNaN=function(a){return z.isNumber(a)&&a!=+a},z.isBoolean=function(a){return a===!0||a===!1||"[object Boolean]"==l.call(a)},z.isNull=function(a){return null===a},z.isUndefined=function(a){return void 0===a},z.has=function(a,b){return m.call(a,b)},z.noConflict=function(){return a._=d,this},z.identity=function(a){return a},z.times=function(a,b,c){for(var d=Array(Math.max(0,a)),e=0;a>e;e++)d[e]=b.call(c,e);return d},z.random=function(a,b){return null==b&&(b=a,a=0),a+Math.floor(Math.random()*(b-a+1))};var I={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"}};I.unescape=z.invert(I.escape);var J={escape:new RegExp("["+z.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+z.keys(I.unescape).join("|")+")","g")};z.each(["escape","unescape"],function(a){z[a]=function(b){return null==b?"":(""+b).replace(J[a],function(b){return I[a][b]})}}),z.result=function(a,b){if(null==a)return void 0;var c=a[b];return z.isFunction(c)?c.call(a):c},z.mixin=function(a){A(z.functions(a),function(b){var c=z[b]=a[b];z.prototype[b]=function(){var a=[this._wrapped];return i.apply(a,arguments),O.call(this,c.apply(z,a))}})};var K=0;z.uniqueId=function(a){var b=++K+"";return a?a+b:b},z.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var L=/(.)^/,M={"'":"'","\\":"\\","\r":"r","\n":"n","	":"t","\u2028":"u2028","\u2029":"u2029"},N=/\\|'|\r|\n|\t|\u2028|\u2029/g;z.template=function(a,b,c){var d;c=z.defaults({},c,z.templateSettings);var e=new RegExp([(c.escape||L).source,(c.interpolate||L).source,(c.evaluate||L).source].join("|")+"|$","g"),f=0,g="__p+='";a.replace(e,function(b,c,d,e,h){return g+=a.slice(f,h).replace(N,function(a){return"\\"+M[a]}),c&&(g+="'+\n((__t=("+c+"))==null?'':_.escape(__t))+\n'"),d&&(g+="'+\n((__t=("+d+"))==null?'':__t)+\n'"),e&&(g+="';\n"+e+"\n__p+='"),f=h+b.length,b}),g+="';\n",c.variable||(g="with(obj||{}){\n"+g+"}\n"),g="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+g+"return __p;\n";try{d=new Function(c.variable||"obj","_",g)}catch(h){throw h.source=g,h}if(b)return d(b,z);var i=function(a){return d.call(this,a,z)};return i.source="function("+(c.variable||"obj")+"){\n"+g+"}",i},z.chain=function(a){return z(a).chain()};var O=function(a){return this._chain?z(a).chain():a};z.mixin(z),A(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=f[a];z.prototype[a]=function(){var c=this._wrapped;return b.apply(c,arguments),"shift"!=a&&"splice"!=a||0!==c.length||delete c[0],O.call(this,c)}}),A(["concat","join","slice"],function(a){var b=f[a];z.prototype[a]=function(){return O.call(this,b.apply(this._wrapped,arguments))}}),z.extend(z.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this)},{}],15:[function(a,b,c){(function(){var a=this,d=a.humanize,e={};"undefined"!=typeof c?("undefined"!=typeof b&&b.exports&&(c=b.exports=e),c.humanize=e):("function"==typeof define&&define.amd&&define("humanize",function(){return e}),a.humanize=e),e.noConflict=function(){return a.humanize=d,this},e.pad=function(a,b,c,d){if(a+="",c?c.length>1&&(c=c.charAt(0)):c=" ",d=void 0===d?"left":"right","right"===d)for(;a.length<b;)a+=c;else for(;a.length<b;)a=c+a;return a},e.time=function(){return(new Date).getTime()/1e3};var f=[0,0,31,59,90,120,151,181,212,243,273,304,334],g=[0,0,31,60,91,121,152,182,213,244,274,305,335];e.date=function(a,b){var c=void 0===b?new Date:new Date(b instanceof Date?b:1e3*b),d=/\\?([a-z])/gi,h=function(a,b){return k[a]?k[a]():b},i=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],j=["January","February","March","April","May","June","July","August","September","October","November","December"],k={d:function(){return e.pad(k.j(),2,"0")},D:function(){return k.l().slice(0,3)},j:function(){return c.getDate()},l:function(){return i[k.w()]},N:function(){return k.w()||7},S:function(){var a=k.j();return a>4&&21>a?"th":{1:"st",2:"nd",3:"rd"}[a%10]||"th"},w:function(){return c.getDay()},z:function(){return(k.L()?g[k.n()]:f[k.n()])+k.j()-1},W:function(){var a=k.z()-k.N()+1.5;return e.pad(1+Math.floor(Math.abs(a)/7)+(a%7>3.5?1:0),2,"0")},F:function(){return j[c.getMonth()]},m:function(){return e.pad(k.n(),2,"0")},M:function(){return k.F().slice(0,3)},n:function(){return c.getMonth()+1},t:function(){return new Date(k.Y(),k.n(),0).getDate()},L:function(){return 1===new Date(k.Y(),1,29).getMonth()?1:0},o:function(){var a=k.n(),b=k.W();return k.Y()+(12===a&&9>b?-1:1===a&&b>9)},Y:function(){return c.getFullYear()},y:function(){return String(k.Y()).slice(-2)},a:function(){return c.getHours()>11?"pm":"am"},A:function(){return k.a().toUpperCase()},B:function(){var a=c.getTime()/1e3,b=a%86400+3600;0>b&&(b+=86400);var d=b/86.4%1e3;return 0>a?Math.ceil(d):Math.floor(d)},g:function(){return k.G()%12||12},G:function(){return c.getHours()},h:function(){return e.pad(k.g(),2,"0")},H:function(){return e.pad(k.G(),2,"0")},i:function(){return e.pad(c.getMinutes(),2,"0")},s:function(){return e.pad(c.getSeconds(),2,"0")},u:function(){return e.pad(1e3*c.getMilliseconds(),6,"0")},O:function(){var a=c.getTimezoneOffset(),b=Math.abs(a);return(a>0?"-":"+")+e.pad(100*Math.floor(b/60)+b%60,4,"0")},P:function(){var a=k.O();return a.substr(0,3)+":"+a.substr(3,2)},Z:function(){return 60*-c.getTimezoneOffset()},c:function(){return"Y-m-d\\TH:i:sP".replace(d,h)},r:function(){return"D, d M Y H:i:s O".replace(d,h)},U:function(){return c.getTime()/1e3||0}};return a.replace(d,h)},e.numberFormat=function(a,b,c,d){b=isNaN(b)?2:Math.abs(b),c=void 0===c?".":c,d=void 0===d?",":d;var e=0>a?"-":"";a=Math.abs(+a||0);var f=parseInt(a.toFixed(b),10)+"",g=f.length>3?f.length%3:0;return e+(g?f.substr(0,g)+d:"")+f.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(b?c+Math.abs(a-f).toFixed(b).slice(2):"")},e.naturalDay=function(a,b){a=void 0===a?e.time():a,b=void 0===b?"Y-m-d":b;var c=86400,d=new Date,f=new Date(d.getFullYear(),d.getMonth(),d.getDate()).getTime()/1e3;return f>a&&a>=f-c?"yesterday":a>=f&&f+c>a?"today":a>=f+c&&f+2*c>a?"tomorrow":e.date(b,a)},e.relativeTime=function(a){a=void 0===a?e.time():a;var b=e.time(),c=b-a;if(2>c&&c>-2)return(c>=0?"just ":"")+"now";if(60>c&&c>-60)return c>=0?Math.floor(c)+" seconds ago":"in "+Math.floor(-c)+" seconds";if(120>c&&c>-120)return c>=0?"about a minute ago":"in about a minute";if(3600>c&&c>-3600)return c>=0?Math.floor(c/60)+" minutes ago":"in "+Math.floor(-c/60)+" minutes";if(7200>c&&c>-7200)return c>=0?"about an hour ago":"in about an hour";if(86400>c&&c>-86400)return c>=0?Math.floor(c/3600)+" hours ago":"in "+Math.floor(-c/3600)+" hours";var d=172800;if(d>c&&c>-d)return c>=0?"1 day ago":"in 1 day";var f=2505600;if(f>c&&c>-f)return c>=0?Math.floor(c/86400)+" days ago":"in "+Math.floor(-c/86400)+" days";var g=5184e3;if(g>c&&c>-g)return c>=0?"about a month ago":"in about a month";var h=parseInt(e.date("Y",b),10),i=parseInt(e.date("Y",a),10),j=12*h+parseInt(e.date("n",b),10),k=12*i+parseInt(e.date("n",a),10),l=j-k;if(12>l&&l>-12)return l>=0?l+" months ago":"in "+-l+" months";var m=h-i;return 2>m&&m>-2?m>=0?"a year ago":"in a year":m>=0?m+" years ago":"in "+-m+" years"},e.ordinal=function(a){a=parseInt(a,10),a=isNaN(a)?0:a;var b=0>a?"-":"";a=Math.abs(a);var c=a%100;return b+a+(c>4&&21>c?"th":{1:"st",2:"nd",3:"rd"}[a%10]||"th")},e.filesize=function(a,b,c,d,f,g){return b=void 0===b?1024:b,0>=a?"0 bytes":(b>a&&void 0===c&&(c=0),void 0===g&&(g=" "),e.intword(a,["bytes","KB","MB","GB","TB","PB"],b,c,d,f,g))},e.intword=function(a,b,c,d,f,g,h){var i,j;b=b||["","K","M","B","T"],j=b.length-1,c=c||1e3,d=isNaN(d)?2:Math.abs(d),f=f||".",g=g||",",h=h||"";for(var k=0;k<b.length;k++)if(a<Math.pow(c,k+1)){j=k;break}i=a/Math.pow(c,j);var l=b[j]?h+b[j]:"";return e.numberFormat(i,d,f,g)+l},e.linebreaks=function(a){return a=a.replace(/^([\n|\r]*)/,""),a=a.replace(/([\n|\r]*)$/,""),a=a.replace(/(\r\n|\n|\r)/g,"\n"),a=a.replace(/(\n{2,})/g,"</p><p>"),a=a.replace(/\n/g,"<br />"),"<p>"+a+"</p>"},e.nl2br=function(a){return a.replace(/(\r\n|\n|\r)/g,"<br />")},e.truncatechars=function(a,b){return a.length<=b?a:a.substr(0,b)+"…"},e.truncatewords=function(a,b){var c=a.split(" ");return c.length<b?a:c.slice(0,b).join(" ")+"…"}}).call(this)},{}],16:[function(a,b,c){(function(){var a=this,d=a._,e=Array.prototype,f=Object.prototype,g=Function.prototype,h=e.push,i=e.slice,j=e.concat,k=f.toString,l=f.hasOwnProperty,m=Array.isArray,n=Object.keys,o=g.bind,p=function(a){return a instanceof p?a:this instanceof p?void(this._wrapped=a):new p(a)};"undefined"!=typeof c?("undefined"!=typeof b&&b.exports&&(c=b.exports=p),c._=p):a._=p,p.VERSION="1.7.0";var q=function(a,b,c){if(void 0===b)return a;switch(null==c?3:c){case 1:return function(c){return a.call(b,c)};case 2:return function(c,d){return a.call(b,c,d)};case 3:return function(c,d,e){return a.call(b,c,d,e)};case 4:return function(c,d,e,f){return a.call(b,c,d,e,f)}}return function(){return a.apply(b,arguments)}};p.iteratee=function(a,b,c){return null==a?p.identity:p.isFunction(a)?q(a,b,c):p.isObject(a)?p.matches(a):p.property(a)},p.each=p.forEach=function(a,b,c){if(null==a)return a;b=q(b,c);var d,e=a.length;if(e===+e)for(d=0;e>d;d++)b(a[d],d,a);else{var f=p.keys(a);for(d=0,e=f.length;e>d;d++)b(a[f[d]],f[d],a)}return a},p.map=p.collect=function(a,b,c){if(null==a)return[];b=p.iteratee(b,c);for(var d,e=a.length!==+a.length&&p.keys(a),f=(e||a).length,g=Array(f),h=0;f>h;h++)d=e?e[h]:h,g[h]=b(a[d],d,a);return g};var r="Reduce of empty array with no initial value";p.reduce=p.foldl=p.inject=function(a,b,c,d){null==a&&(a=[]),b=q(b,d,4);var e,f=a.length!==+a.length&&p.keys(a),g=(f||a).length,h=0;if(arguments.length<3){if(!g)throw new TypeError(r);c=a[f?f[h++]:h++]}for(;g>h;h++)e=f?f[h]:h,c=b(c,a[e],e,a);return c},p.reduceRight=p.foldr=function(a,b,c,d){null==a&&(a=[]),b=q(b,d,4);var e,f=a.length!==+a.length&&p.keys(a),g=(f||a).length;if(arguments.length<3){if(!g)throw new TypeError(r);c=a[f?f[--g]:--g]}for(;g--;)e=f?f[g]:g,c=b(c,a[e],e,a);return c},p.find=p.detect=function(a,b,c){var d;return b=p.iteratee(b,c),p.some(a,function(a,c,e){return b(a,c,e)?(d=a,!0):void 0}),d},p.filter=p.select=function(a,b,c){var d=[];return null==a?d:(b=p.iteratee(b,c),p.each(a,function(a,c,e){b(a,c,e)&&d.push(a)}),d)},p.reject=function(a,b,c){return p.filter(a,p.negate(p.iteratee(b)),c)},p.every=p.all=function(a,b,c){if(null==a)return!0;b=p.iteratee(b,c);var d,e,f=a.length!==+a.length&&p.keys(a),g=(f||a).length;for(d=0;g>d;d++)if(e=f?f[d]:d,!b(a[e],e,a))return!1;return!0},p.some=p.any=function(a,b,c){if(null==a)return!1;b=p.iteratee(b,c);var d,e,f=a.length!==+a.length&&p.keys(a),g=(f||a).length;for(d=0;g>d;d++)if(e=f?f[d]:d,b(a[e],e,a))return!0;return!1},p.contains=p.include=function(a,b){return null==a?!1:(a.length!==+a.length&&(a=p.values(a)),p.indexOf(a,b)>=0)},p.invoke=function(a,b){var c=i.call(arguments,2),d=p.isFunction(b);return p.map(a,function(a){return(d?b:a[b]).apply(a,c)})},p.pluck=function(a,b){return p.map(a,p.property(b))},p.where=function(a,b){return p.filter(a,p.matches(b))},p.findWhere=function(a,b){return p.find(a,p.matches(b))},p.max=function(a,b,c){var d,e,f=-1/0,g=-1/0;if(null==b&&null!=a){a=a.length===+a.length?a:p.values(a);for(var h=0,i=a.length;i>h;h++)d=a[h],d>f&&(f=d)}else b=p.iteratee(b,c),p.each(a,function(a,c,d){e=b(a,c,d),(e>g||e===-1/0&&f===-1/0)&&(f=a,g=e)});return f},p.min=function(a,b,c){var d,e,f=1/0,g=1/0;if(null==b&&null!=a){a=a.length===+a.length?a:p.values(a);for(var h=0,i=a.length;i>h;h++)d=a[h],f>d&&(f=d)}else b=p.iteratee(b,c),p.each(a,function(a,c,d){e=b(a,c,d),(g>e||1/0===e&&1/0===f)&&(f=a,g=e)});return f},p.shuffle=function(a){for(var b,c=a&&a.length===+a.length?a:p.values(a),d=c.length,e=Array(d),f=0;d>f;f++)b=p.random(0,f),b!==f&&(e[f]=e[b]),e[b]=c[f];return e},p.sample=function(a,b,c){return null==b||c?(a.length!==+a.length&&(a=p.values(a)),a[p.random(a.length-1)]):p.shuffle(a).slice(0,Math.max(0,b))},p.sortBy=function(a,b,c){return b=p.iteratee(b,c),p.pluck(p.map(a,function(a,c,d){return{value:a,index:c,criteria:b(a,c,d)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;if(c!==d){if(c>d||void 0===c)return 1;if(d>c||void 0===d)return-1}return a.index-b.index}),"value")};var s=function(a){return function(b,c,d){var e={};return c=p.iteratee(c,d),p.each(b,function(d,f){var g=c(d,f,b);a(e,d,g)}),e}};p.groupBy=s(function(a,b,c){p.has(a,c)?a[c].push(b):a[c]=[b]}),p.indexBy=s(function(a,b,c){a[c]=b}),p.countBy=s(function(a,b,c){p.has(a,c)?a[c]++:a[c]=1}),p.sortedIndex=function(a,b,c,d){c=p.iteratee(c,d,1);for(var e=c(b),f=0,g=a.length;g>f;){var h=f+g>>>1;c(a[h])<e?f=h+1:g=h}return f},p.toArray=function(a){return a?p.isArray(a)?i.call(a):a.length===+a.length?p.map(a,p.identity):p.values(a):[]},p.size=function(a){return null==a?0:a.length===+a.length?a.length:p.keys(a).length},p.partition=function(a,b,c){b=p.iteratee(b,c);var d=[],e=[];return p.each(a,function(a,c,f){(b(a,c,f)?d:e).push(a)}),[d,e]},p.first=p.head=p.take=function(a,b,c){return null==a?void 0:null==b||c?a[0]:0>b?[]:i.call(a,0,b)},p.initial=function(a,b,c){return i.call(a,0,Math.max(0,a.length-(null==b||c?1:b)))},p.last=function(a,b,c){return null==a?void 0:null==b||c?a[a.length-1]:i.call(a,Math.max(a.length-b,0))},p.rest=p.tail=p.drop=function(a,b,c){return i.call(a,null==b||c?1:b)},p.compact=function(a){return p.filter(a,p.identity)};var t=function(a,b,c,d){if(b&&p.every(a,p.isArray))return j.apply(d,a);for(var e=0,f=a.length;f>e;e++){var g=a[e];p.isArray(g)||p.isArguments(g)?b?h.apply(d,g):t(g,b,c,d):c||d.push(g)}return d};p.flatten=function(a,b){return t(a,b,!1,[])},p.without=function(a){return p.difference(a,i.call(arguments,1))},p.uniq=p.unique=function(a,b,c,d){if(null==a)return[];p.isBoolean(b)||(d=c,c=b,b=!1),null!=c&&(c=p.iteratee(c,d));for(var e=[],f=[],g=0,h=a.length;h>g;g++){var i=a[g];if(b)g&&f===i||e.push(i),f=i;else if(c){var j=c(i,g,a);p.indexOf(f,j)<0&&(f.push(j),e.push(i))}else p.indexOf(e,i)<0&&e.push(i)}return e},p.union=function(){return p.uniq(t(arguments,!0,!0,[]))},p.intersection=function(a){if(null==a)return[];for(var b=[],c=arguments.length,d=0,e=a.length;e>d;d++){var f=a[d];if(!p.contains(b,f)){for(var g=1;c>g&&p.contains(arguments[g],f);g++);g===c&&b.push(f)}}return b},p.difference=function(a){var b=t(i.call(arguments,1),!0,!0,[]);return p.filter(a,function(a){return!p.contains(b,a)})},p.zip=function(a){if(null==a)return[];for(var b=p.max(arguments,"length").length,c=Array(b),d=0;b>d;d++)c[d]=p.pluck(arguments,d);return c},p.object=function(a,b){if(null==a)return{};for(var c={},d=0,e=a.length;e>d;d++)b?c[a[d]]=b[d]:c[a[d][0]]=a[d][1];return c},p.indexOf=function(a,b,c){if(null==a)return-1;var d=0,e=a.length;if(c){if("number"!=typeof c)return d=p.sortedIndex(a,b),a[d]===b?d:-1;d=0>c?Math.max(0,e+c):c}for(;e>d;d++)if(a[d]===b)return d;return-1},p.lastIndexOf=function(a,b,c){if(null==a)return-1;var d=a.length;for("number"==typeof c&&(d=0>c?d+c+1:Math.min(d,c+1));--d>=0;)if(a[d]===b)return d;return-1},p.range=function(a,b,c){arguments.length<=1&&(b=a||0,a=0),c=c||1;for(var d=Math.max(Math.ceil((b-a)/c),0),e=Array(d),f=0;d>f;f++,a+=c)e[f]=a;return e};var u=function(){};p.bind=function(a,b){var c,d;if(o&&a.bind===o)return o.apply(a,i.call(arguments,1));if(!p.isFunction(a))throw new TypeError("Bind must be called on a function");return c=i.call(arguments,2),d=function(){if(!(this instanceof d))return a.apply(b,c.concat(i.call(arguments)));u.prototype=a.prototype;var e=new u;u.prototype=null;var f=a.apply(e,c.concat(i.call(arguments)));return p.isObject(f)?f:e}},p.partial=function(a){var b=i.call(arguments,1);return function(){for(var c=0,d=b.slice(),e=0,f=d.length;f>e;e++)d[e]===p&&(d[e]=arguments[c++]);for(;c<arguments.length;)d.push(arguments[c++]);return a.apply(this,d)}},p.bindAll=function(a){var b,c,d=arguments.length;if(1>=d)throw new Error("bindAll must be passed function names");for(b=1;d>b;b++)c=arguments[b],a[c]=p.bind(a[c],a);return a},p.memoize=function(a,b){var c=function(d){var e=c.cache,f=b?b.apply(this,arguments):d;return p.has(e,f)||(e[f]=a.apply(this,arguments)),e[f]};return c.cache={},c},p.delay=function(a,b){var c=i.call(arguments,2);return setTimeout(function(){return a.apply(null,c)},b)},p.defer=function(a){return p.delay.apply(p,[a,1].concat(i.call(arguments,1)))},p.throttle=function(a,b,c){var d,e,f,g=null,h=0;c||(c={});var i=function(){h=c.leading===!1?0:p.now(),g=null,f=a.apply(d,e),g||(d=e=null)};return function(){var j=p.now();h||c.leading!==!1||(h=j);var k=b-(j-h);return d=this,e=arguments,0>=k||k>b?(clearTimeout(g),g=null,h=j,f=a.apply(d,e),g||(d=e=null)):g||c.trailing===!1||(g=setTimeout(i,k)),f}},p.debounce=function(a,b,c){var d,e,f,g,h,i=function(){var j=p.now()-g;b>j&&j>0?d=setTimeout(i,b-j):(d=null,c||(h=a.apply(f,e),d||(f=e=null)))};return function(){f=this,e=arguments,g=p.now();var j=c&&!d;return d||(d=setTimeout(i,b)),j&&(h=a.apply(f,e),f=e=null),h}},p.wrap=function(a,b){return p.partial(b,a)},p.negate=function(a){return function(){return!a.apply(this,arguments)}},p.compose=function(){var a=arguments,b=a.length-1;return function(){for(var c=b,d=a[b].apply(this,arguments);c--;)d=a[c].call(this,d);return d}},p.after=function(a,b){return function(){return--a<1?b.apply(this,arguments):void 0}},p.before=function(a,b){var c;return function(){return--a>0?c=b.apply(this,arguments):b=null,c}},p.once=p.partial(p.before,2),p.keys=function(a){if(!p.isObject(a))return[];if(n)return n(a);var b=[];for(var c in a)p.has(a,c)&&b.push(c);return b},p.values=function(a){for(var b=p.keys(a),c=b.length,d=Array(c),e=0;c>e;e++)d[e]=a[b[e]];return d},p.pairs=function(a){for(var b=p.keys(a),c=b.length,d=Array(c),e=0;c>e;e++)d[e]=[b[e],a[b[e]]];return d},p.invert=function(a){for(var b={},c=p.keys(a),d=0,e=c.length;e>d;d++)b[a[c[d]]]=c[d];return b},p.functions=p.methods=function(a){var b=[];for(var c in a)p.isFunction(a[c])&&b.push(c);return b.sort()},p.extend=function(a){if(!p.isObject(a))return a;for(var b,c,d=1,e=arguments.length;e>d;d++){b=arguments[d];for(c in b)l.call(b,c)&&(a[c]=b[c])}return a},p.pick=function(a,b,c){var d,e={};if(null==a)return e;if(p.isFunction(b)){b=q(b,c);for(d in a){var f=a[d];b(f,d,a)&&(e[d]=f)}}else{var g=j.apply([],i.call(arguments,1));a=new Object(a);for(var h=0,k=g.length;k>h;h++)d=g[h],d in a&&(e[d]=a[d])}return e},p.omit=function(a,b,c){if(p.isFunction(b))b=p.negate(b);else{var d=p.map(j.apply([],i.call(arguments,1)),String);b=function(a,b){return!p.contains(d,b)}}return p.pick(a,b,c)},p.defaults=function(a){if(!p.isObject(a))return a;for(var b=1,c=arguments.length;c>b;b++){var d=arguments[b];for(var e in d)void 0===a[e]&&(a[e]=d[e])}return a},p.clone=function(a){return p.isObject(a)?p.isArray(a)?a.slice():p.extend({},a):a},p.tap=function(a,b){return b(a),a};var v=function(a,b,c,d){if(a===b)return 0!==a||1/a===1/b;if(null==a||null==b)return a===b;a instanceof p&&(a=a._wrapped),b instanceof p&&(b=b._wrapped);var e=k.call(a);if(e!==k.call(b))return!1;switch(e){case"[object RegExp]":case"[object String]":return""+a==""+b;case"[object Number]":return+a!==+a?+b!==+b:0===+a?1/+a===1/b:+a===+b;case"[object Date]":case"[object Boolean]":return+a===+b}if("object"!=typeof a||"object"!=typeof b)return!1;for(var f=c.length;f--;)if(c[f]===a)return d[f]===b;var g=a.constructor,h=b.constructor;if(g!==h&&"constructor"in a&&"constructor"in b&&!(p.isFunction(g)&&g instanceof g&&p.isFunction(h)&&h instanceof h))return!1;c.push(a),d.push(b);var i,j;if("[object Array]"===e){if(i=a.length,j=i===b.length)for(;i--&&(j=v(a[i],b[i],c,d)););}else{var l,m=p.keys(a);if(i=m.length,j=p.keys(b).length===i)for(;i--&&(l=m[i],j=p.has(b,l)&&v(a[l],b[l],c,d)););}return c.pop(),d.pop(),j};p.isEqual=function(a,b){return v(a,b,[],[])},p.isEmpty=function(a){if(null==a)return!0;if(p.isArray(a)||p.isString(a)||p.isArguments(a))return 0===a.length;for(var b in a)if(p.has(a,b))return!1;return!0},p.isElement=function(a){return!(!a||1!==a.nodeType)},p.isArray=m||function(a){return"[object Array]"===k.call(a)},p.isObject=function(a){var b=typeof a;return"function"===b||"object"===b&&!!a},p.each(["Arguments","Function","String","Number","Date","RegExp"],function(a){p["is"+a]=function(b){return k.call(b)==="[object "+a+"]"}}),p.isArguments(arguments)||(p.isArguments=function(a){return p.has(a,"callee")}),"function"!=typeof/./&&(p.isFunction=function(a){return"function"==typeof a||!1}),p.isFinite=function(a){return isFinite(a)&&!isNaN(parseFloat(a))},p.isNaN=function(a){return p.isNumber(a)&&a!==+a},p.isBoolean=function(a){return a===!0||a===!1||"[object Boolean]"===k.call(a)},p.isNull=function(a){return null===a},p.isUndefined=function(a){return void 0===a},p.has=function(a,b){return null!=a&&l.call(a,b)},p.noConflict=function(){return a._=d,this},p.identity=function(a){return a},p.constant=function(a){return function(){return a}},p.noop=function(){},p.property=function(a){return function(b){return b[a]}},p.matches=function(a){var b=p.pairs(a),c=b.length;return function(a){if(null==a)return!c;a=new Object(a);for(var d=0;c>d;d++){var e=b[d],f=e[0];if(e[1]!==a[f]||!(f in a))return!1}return!0}},p.times=function(a,b,c){var d=Array(Math.max(0,a));b=q(b,c,1);for(var e=0;a>e;e++)d[e]=b(e);return d},p.random=function(a,b){return null==b&&(b=a,a=0),a+Math.floor(Math.random()*(b-a+1))},p.now=Date.now||function(){return(new Date).getTime()};var w={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},x=p.invert(w),y=function(a){var b=function(b){return a[b]},c="(?:"+p.keys(a).join("|")+")",d=RegExp(c),e=RegExp(c,"g");return function(a){return a=null==a?"":""+a,d.test(a)?a.replace(e,b):a}};p.escape=y(w),p.unescape=y(x),p.result=function(a,b){if(null==a)return void 0;var c=a[b];return p.isFunction(c)?a[b]():c};var z=0;p.uniqueId=function(a){var b=++z+"";return a?a+b:b},p.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},C=/\\|'|\r|\n|\u2028|\u2029/g,D=function(a){return"\\"+B[a]};p.template=function(a,b,c){!b&&c&&(b=c),b=p.defaults({},b,p.templateSettings);var d=RegExp([(b.escape||A).source,(b.interpolate||A).source,(b.evaluate||A).source].join("|")+"|$","g"),e=0,f="__p+='";a.replace(d,function(b,c,d,g,h){return f+=a.slice(e,h).replace(C,D),e=h+b.length,c?f+="'+\n((__t=("+c+"))==null?'':_.escape(__t))+\n'":d?f+="'+\n((__t=("+d+"))==null?'':__t)+\n'":g&&(f+="';\n"+g+"\n__p+='"),b}),f+="';\n",b.variable||(f="with(obj||{}){\n"+f+"}\n"),f="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+f+"return __p;\n";try{var g=new Function(b.variable||"obj","_",f)}catch(h){throw h.source=f,h}var i=function(a){return g.call(this,a,p)},j=b.variable||"obj";return i.source="function("+j+"){\n"+f+"}",i},p.chain=function(a){var b=p(a);return b._chain=!0,b};var E=function(a){return this._chain?p(a).chain():a};p.mixin=function(a){p.each(p.functions(a),function(b){var c=p[b]=a[b];p.prototype[b]=function(){var a=[this._wrapped];return h.apply(a,arguments),E.call(this,c.apply(p,a))}})},p.mixin(p),p.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=e[a];p.prototype[a]=function(){var c=this._wrapped;return b.apply(c,arguments),"shift"!==a&&"splice"!==a||0!==c.length||delete c[0],E.call(this,c)}}),p.each(["concat","join","slice"],function(a){var b=e[a];p.prototype[a]=function(){return E.call(this,b.apply(this._wrapped,arguments))}}),p.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return p})}).call(this)},{}],17:[function(a,b){var c=a("underscore"),d=a("geocrunch"),e=function(a){return 10>a?"0"+a.toString():a.toString()},f=function(a,b,c){var d=Math.abs(a),f=Math.floor(d),g=Math.floor(60*(d-f)),h=Math.round(3600*(d-f-g/60)*100)/100,i=d===a?b:c;return e(f)+"&deg; "+e(g)+"' "+e(h)+'" '+i},g=function(a){var b,e,g,h,i,j,k,l,m=c.last(a),n=d.path(c.map(a,function(a){return[a.lng,a.lat]}));return b=n.distance({units:"feet"}),e=b/3.2808,g=b/5280,h=e/1e3,i=n.area({units:"sqmeters"}),j=24711e-8*i,k=i/1e4,l=.0015625*j,{lastCoord:{dd:{x:m.lng,y:m.lat},dms:{x:f(m.lng,"E","W"),y:f(m.lat,"N","S")}},length:{feet:b,meters:e,miles:g,kilometers:h},area:{acres:j,hectares:k,sqmeters:i,sqmiles:l}}};b.exports={measure:g}},{geocrunch:6,underscore:16}],18:[function(a,b){var c=function(a,b){return b||(b=document),b.querySelector(a)},d=function(a,b){return b||(b=document),Array.prototype.slice.call(b.querySelectorAll(a))},e=function(a){return a?(a.setAttribute("style","display:none;"),a):void 0},f=function(a){return a?(a.removeAttribute("style"),a):void 0};b.exports={$:c,$$:d,hide:e,show:f}},{}],19:[function(a){(function(b){var c=a("underscore"),d="undefined"!=typeof window?window.L:"undefined"!=typeof b?b.L:null,e=a("humanize"),f=a("./units"),g=a("./calc"),h=a("./dom"),i=h.$,j=a("./mapsymbology"),k=c.template('<a class="<%= model.className %>-toggle js-toggle" href="#" title="Measure distances and areas">Measure</a>\n<div class="<%= model.className %>-interaction js-interaction">\n  <div class="js-startprompt startprompt">\n    <h3>Measure Distances and Areas</h3>\n    <ul class="tasks">\n      <a href="#" class="js-start start">Create a new measurement</a>\n    </ul>\n  </div>\n  <div class="js-measuringprompt">\n    <h3>Measure Distances and Areas</h3>\n    <p class="js-starthelp">Start creating a measurement by adding points to the map</h3>\n    <div class="js-results results"></div>\n    <ul class="js-measuretasks tasks">\n      <li><a href="#" class="js-cancel cancel">Cancel</a></li>\n      <li><a href="#" class="js-finish finish">Finish Measurement</a></li>\n    </ul>\n  </div>\n</div>'),l=c.template('<div class="group">\n<p class="lastpoint heading">Last Point</p>\n<p><%= model.lastCoord.dms.y %> <span class="coorddivider">/</span> <%= model.lastCoord.dms.x %></p>\n<p><%= humanize.numberFormat(model.lastCoord.dd.y, 6) %> <span class="coorddivider">/</span> <%= humanize.numberFormat(model.lastCoord.dd.x, 6) %></p>\n</div>\n<% if (model.pointCount > 1) { %>\n<div class="group">\n<p><span class="heading">Path Distance</span> <%= model.lengthDisplay %></p>\n</div>\n<% } %>\n<% if (model.pointCount > 2) { %>\n<div class="group">\n<p><span class="heading">Area</span> <%= model.areaDisplay %></p>\n</div>\n<% } %>'),m=c.template('<h3>Point Location</h3>\n<p><%= model.lastCoord.dms.y %> <span class="coorddivider">/</span> <%= model.lastCoord.dms.x %></p>\n<p><%= humanize.numberFormat(model.lastCoord.dd.y, 6) %> <span class="coorddivider">/</span> <%= humanize.numberFormat(model.lastCoord.dd.x, 6) %></p>\n<ul class="tasks">\n  <li><a href="#" class="js-zoomto zoomto">Center on this Location</a></li>\n  <li><a href="#" class="js-deletemarkup deletemarkup">Delete</a></li>\n</ul>'),n=c.template('<h3>Linear Measurement</h3>\n<p><%= model.lengthDisplay %></p>\n<ul class="tasks">\n  <li><a href="#" class="js-zoomto zoomto">Center on this Line</a></li>\n  <li><a href="#" class="js-deletemarkup deletemarkup">Delete</a></li>\n</ul>'),o=c.template('<h3>Area Measurement</h3>\n<p><%= model.areaDisplay %></p>\n<p><%= model.lengthDisplay %> Perimeter</p>\n<ul class="tasks">\n  <li><a href="#" class="js-zoomto zoomto">Center on this Area</a></li>\n  <li><a href="#" class="js-deletemarkup deletemarkup">Delete</a></li>\n</ul>');
+d.Control.Measure=d.Control.extend({_className:"leaflet-control-measure",options:{position:"topright",primaryLengthUnit:"feet",secondaryLengthUnit:"miles",primaryAreaUnit:"acres",activeColor:"#ABE67E",completedColor:"#C8F2BE",popupOptions:{className:"leaflet-measure-resultpopup",autoPanPadding:[10,10]}},initialize:function(a){d.setOptions(this,a),this._symbols=new j(c.pick(this.options,"activeColor","completedColor"))},onAdd:function(a){return this._map=a,this._latlngs=[],this._initLayout(),a.on("click",this._collapse,this),this._layer=d.layerGroup().addTo(a),this._container},onRemove:function(a){a.off("click",this._collapse,this),a.removeLayer(this._layer)},_initLayout:function(){var a,b,c,e,f=this._className,g=this._container=d.DomUtil.create("div",f);g.innerHTML=k({model:{className:f}}),g.setAttribute("aria-haspopup",!0),d.Browser.touch?d.DomEvent.on(g,"click",d.DomEvent.stopPropagation):(d.DomEvent.disableClickPropagation(g),d.DomEvent.disableScrollPropagation(g)),a=this.$toggle=i(".js-toggle",g),this.$interaction=i(".js-interaction",g),b=i(".js-start",g),c=i(".js-cancel",g),e=i(".js-finish",g),this.$startPrompt=i(".js-startprompt",g),this.$measuringPrompt=i(".js-measuringprompt",g),this.$startHelp=i(".js-starthelp",g),this.$results=i(".js-results",g),this.$measureTasks=i(".js-measuretasks",g),this._collapse(),this._updateMeasureNotStarted(),d.Browser.android||(d.DomEvent.on(g,"mouseenter",this._expand,this),d.DomEvent.on(g,"mouseleave",this._collapse,this)),d.DomEvent.on(a,"click",d.DomEvent.stop),d.Browser.touch?d.DomEvent.on(a,"click",this._expand,this):d.DomEvent.on(a,"focus",this._expand,this),d.DomEvent.on(b,"click",d.DomEvent.stop),d.DomEvent.on(b,"click",this._startMeasure,this),d.DomEvent.on(c,"click",d.DomEvent.stop),d.DomEvent.on(c,"click",this._finishMeasure,this),d.DomEvent.on(e,"click",d.DomEvent.stop),d.DomEvent.on(e,"click",this._handleMeasureDoubleClick,this)},_expand:function(){h.hide(this.$toggle),h.show(this.$interaction)},_collapse:function(){this._locked||(h.hide(this.$interaction),h.show(this.$toggle))},_updateMeasureNotStarted:function(){h.hide(this.$startHelp),h.hide(this.$results),h.hide(this.$measureTasks),h.hide(this.$measuringPrompt),h.show(this.$startPrompt)},_updateMeasureStartedNoPoints:function(){h.hide(this.$results),h.show(this.$startHelp),h.show(this.$measureTasks),h.hide(this.$startPrompt),h.show(this.$measuringPrompt)},_updateMeasureStartedWithPoints:function(){h.hide(this.$startHelp),h.show(this.$results),h.show(this.$measureTasks),h.hide(this.$startPrompt),h.show(this.$measuringPrompt)},_startMeasure:function(){this._locked=!0,this._map.doubleClickZoom.disable(),this._map.on("mouseout",this._handleMapMouseOut,this),d.DomEvent.on(this._container,"mouseenter",this._handleMapMouseOut,this),this._measureCollector||(this._measureCollector=d.polygon([[90,-180],[90,180],[-90,180],[-90,-180]],this._symbols.getSymbol("measureCollector")).addTo(this._layer),this._measureCollector.on("mousemove",this._handleMeasureMove,this),this._measureCollector.on("dblclick",this._handleMeasureDoubleClick,this),this._measureCollector.on("click",this._handleMeasureClick,this)),this._measureCollector.bringToFront(),this._measureVertexes=d.featureGroup().addTo(this._layer),this._updateMeasureStartedNoPoints()},_finishMeasure:function(){this._locked=!1,this._map.doubleClickZoom.enable(),this._map.off("mouseout",this._handleMapMouseOut,this),d.DomEvent.off(this._container,"mouseover",this._handleMapMouseOut,this),this._clearMeasure(),this._measureCollector.off(),this._layer.removeLayer(this._measureCollector),this._measureCollector=null,this._layer.removeLayer(this._measureVertexes),this._measureVertexes=null,this._updateMeasureNotStarted(),this._collapse()},_clearMeasure:function(){this._latlngs=[],this._measureVertexes.clearLayers(),this._measureDrag&&this._layer.removeLayer(this._measureDrag),this._measureArea&&this._layer.removeLayer(this._measureArea),this._measureBoundary&&this._layer.removeLayer(this._measureBoundary),this._measureDrag=null,this._measureArea=null,this._measureBoundary=null},_getMeasurementDisplayStrings:function(a){var b={};return this.options.primaryLengthUnit&&f[this.options.primaryLengthUnit]&&(b.lengthDisplay=e.numberFormat(a.length[this.options.primaryLengthUnit],f[this.options.primaryLengthUnit].decimals)+" "+f[this.options.primaryLengthUnit].display,this.options.secondaryLengthUnit&&f[this.options.secondaryLengthUnit]&&(b.lengthDisplay=b.lengthDisplay+" ("+e.numberFormat(a.length[this.options.secondaryLengthUnit],f[this.options.secondaryLengthUnit].decimals)+" "+f[this.options.secondaryLengthUnit].display+")")),this.options.primaryAreaUnit&&f[this.options.primaryAreaUnit]&&(b.areaDisplay=e.numberFormat(a.area[this.options.primaryAreaUnit],f[this.options.primaryAreaUnit].decimals)+" "+f[this.options.primaryAreaUnit].display,this.options.secondaryAreaUnit&&f[this.options.secondaryAreaUnit]&&(b.areaDisplay=b.areaDisplay+" ("+e.numberFormat(a.area[this.options.secondaryAreaUnit],f[this.options.secondaryAreaUnit].decimals)+" "+f[this.options.secondaryAreaUnit].display+")")),b},_updateResults:function(){var a=g.measure(this._latlngs);this.$results.innerHTML=l({model:c.extend({},a,this._getMeasurementDisplayStrings(a),{pointCount:this._latlngs.length}),humanize:e})},_handleMeasureMove:function(a){this._measureDrag?this._measureDrag.setLatLng(a.latlng):this._measureDrag=d.circleMarker(a.latlng,this._symbols.getSymbol("measureDrag")).addTo(this._layer),this._measureDrag.bringToFront()},_handleMeasureDoubleClick:function(){var a,b,f,h,j,k,l=this._latlngs;this._finishMeasure(),l.length&&(l.length>2&&l.push(c.first(l)),a=g.measure(l),1===l.length?(b=d.circleMarker(l[0],this._symbols.getSymbol("resultPoint")),h=m({model:a,humanize:e})):2===l.length?(b=d.polyline(l,this._symbols.getSymbol("resultLine")).addTo(this._map),h=n({model:c.extend({},a,this._getMeasurementDisplayStrings(a)),humanize:e})):(b=d.polygon(l,this._symbols.getSymbol("resultArea")),h=o({model:c.extend({},a,this._getMeasurementDisplayStrings(a)),humanize:e,units:this._units})),f=d.DomUtil.create("div",""),f.innerHTML=h,j=i(".js-zoomto",f),j&&(d.DomEvent.on(j,"click",d.DomEvent.stop),d.DomEvent.on(j,"click",function(){this._map.fitBounds(b.getBounds(),{padding:[20,20],maxZoom:17})},this)),k=i(".js-deletemarkup",f),k&&(d.DomEvent.on(k,"click",d.DomEvent.stop),d.DomEvent.on(k,"click",function(){this._map.removeLayer(b)},this)),b.addTo(this._map),b.bindPopup(f,this.options.popupOptions),b.openPopup(b.getBounds().getCenter()))},_handleMeasureClick:function(a){var b=a.latlng,d=c.last(this._latlngs),e=this._symbols.getSymbol("measureVertex");this._map.closePopup(),d&&b.equals(d)||(this._latlngs.push(b),this._addMeasureArea(this._latlngs),this._addMeasureBoundary(this._latlngs),this._measureVertexes.eachLayer(function(a){a.setStyle(e),a._path.setAttribute("class",e.className)}),this._addNewVertex(b),this._measureBoundary&&this._measureBoundary.bringToFront(),this._measureVertexes.bringToFront()),this._updateResults(),this._updateMeasureStartedWithPoints()},_handleMapMouseOut:function(){this._measureDrag&&(this._layer.removeLayer(this._measureDrag),this._measureDrag=null)},_addNewVertex:function(a){d.circleMarker(a,this._symbols.getSymbol("measureVertexActive")).addTo(this._measureVertexes)},_addMeasureArea:function(a){return a.length<3?void(this._measureArea&&(this._layer.removeLayer(this._measureArea),this._measureArea=null)):void(this._measureArea?this._measureArea.setLatLngs(a):this._measureArea=d.polygon(a,this._symbols.getSymbol("measureArea")).addTo(this._layer))},_addMeasureBoundary:function(a){return a.length<2?void(this._measureBoundary&&(this._layer.removeLayer(this._measureBoundary),this._measureBoundary=null)):void(this._measureBoundary?this._measureBoundary.setLatLngs(a):this._measureBoundary=d.polyline(a,this._symbols.getSymbol("measureBoundary")).addTo(this._layer))}}),d.Map.mergeOptions({measureControl:!1}),d.Map.addInitHook(function(){this.options.measureControl&&(this.measureControl=(new d.Control.Measure).addTo(this))}),d.control.measure=function(a){return new d.Control.Measure(a)}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./calc":17,"./dom":18,"./mapsymbology":20,"./units":21,humanize:15,underscore:16}],20:[function(a,b){var c=a("underscore"),d=a("color"),e=function(a){this.setOptions(a)};e.DEFAULTS={activeColor:"#ABE67E",completedColor:"#C8F2BE"},c.extend(e.prototype,{setOptions:function(a){return this._options=c.extend({},e.DEFAULTS,this._options,a),this},getSymbol:function(a){var b={measureCollector:{clickable:!0,stroke:!1,fillOpacity:0,className:"layer-measurecollector"},measureDrag:{clickable:!1,radius:4,color:this._options.activeColor,weight:2,opacity:.7,fillColor:this._options.activeColor,fillOpacity:.5,className:"layer-measuredrag"},measureArea:{clickable:!1,stroke:!1,fillColor:this._options.activeColor,fillOpacity:.2,className:"layer-measurearea"},measureBoundary:{clickable:!1,color:this._options.activeColor,weight:2,opacity:.9,fill:!1,className:"layer-measureboundary"},measureVertex:{clickable:!1,radius:4,color:this._options.activeColor,weight:2,opacity:1,fillColor:this._options.activeColor,fillOpacity:.7,className:"layer-measurevertex"},measureVertexActive:{clickable:!1,radius:4,color:this._options.activeColor,weight:2,opacity:1,fillColor:d(this._options.activeColor).darken(.15),fillOpacity:.7,className:"layer-measurevertex active"},resultArea:{clickable:!0,color:this._options.completedColor,weight:2,opacity:.9,fillColor:this._options.completedColor,fillOpacity:.2,className:"layer-measure-resultarea"},resultLine:{clickable:!0,color:this._options.completedColor,weight:3,opacity:.9,fill:!1,className:"layer-measure-resultline"},resultPoint:{clickable:!0,radius:4,color:this._options.completedColor,weight:2,opacity:1,fillColor:this._options.completedColor,fillOpacity:.7,className:"layer-measure-resultpoint"}};return b[a]}}),b.exports=e},{color:1,underscore:16}],21:[function(a,b){b.exports={acres:{display:"Acres",decimals:2},feet:{display:"Feet",decimals:0},kilometers:{display:"Kilometers",decimals:2},hectares:{display:"Hectares",decimals:2},meters:{display:"Meters",decimals:0},miles:{display:"Miles",decimals:2},sqmeters:{display:"Sq Meters",decimals:0},sqmiles:{display:"Sq Miles",decimals:2}}},{}]},{},[19]);
