Index: frontend/node_modules/relateurl/README.md
===================================================================
--- frontend/node_modules/relateurl/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,159 @@
+# relateurl [![NPM Version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][david-image]][david-url]
+
+> Minify URLs by converting them from absolute to relative.
+
+If you were to use this library on a website like `http://example.com/dir1/dir1-1/`, you would get results such as:
+
+| Before                                      | After                                |
+| :------------------------------------------ | :----------------------------------- |
+| `http://example.com/dir1/dir1-2/index.html` | `../dir1-2/`                         |
+| `http://example.com/dir2/dir2-1/`           | `/dir2/dir2-1/`                      |
+| `http://example.com/dir1/dir1-1/`           | ` `                                  |
+| `https://example.com/dir1/dir1-1/`          | `https://example.com/dir1/dir1-1/`   |
+| `http://google.com:80/dir/`                 | `//google.com/dir/`                  |
+| `../../../../../../../../#anchor`           | `/#anchor`                           |
+
+**All string parsing.** *No* directory browsing. It is thoroughly tested, very fast and lightweight with zero external dependencies.
+
+## Getting Started
+
+This utility requires [Node.js](http://nodejs.org/) `>= 0.10`. To install, type this at the command line:
+```
+npm install relateurl --save-dev
+```
+
+### Options
+
+#### options.defaultPorts
+Type: `Object`   
+Default value: `{ftp:21, http:80, https:443}`  
+
+Extend the list with any ports you need. Any URLs containing these default ports will have them removed. Example: `http://example.com:80/` will become `http://example.com/`.
+
+#### options.directoryIndexes
+Type: `Array`   
+Default value: `["index.html"]`  
+
+Extend the list with any resources you need. Works with [`options.removeDirectoryIndexes`](#options.removeDirectoryIndexes).
+
+#### options.ignore_www
+Type: `Boolean`  
+Default value: `false`  
+
+This will, for example, consider any domains containing `http://www.example.com/` to be related to any that contain `http://example.com/`.
+
+#### options.output
+Type: constant or `String`  
+Choices: `RelateUrl.ABSOLUTE`,`RelateUrl.PATH_RELATIVE`,`RelateUrl.ROOT_RELATIVE`,`RelateUrl.SHORTEST`  
+Choices: `"absolute"`,`"pathRelative"`,`"rootRelative"`,`"shortest"`  
+Default value: `RelateUrl.SHORTEST`  
+
+`RelateUrl.ABSOLUTE` will produce an absolute URL. Overrides [`options.schemeRelative`](#options.schemeRelative) with a value of `false`.  
+`RelateUrl.PATH_RELATIVE` will produce something like `../child-of-parent/etc/`.  
+`RelateUrl.ROOT_RELATIVE` will produce something like `/child-of-root/etc/`.  
+`RelateUrl.SHORTEST` will choose whichever is shortest between root- and path-relative.  
+
+#### options.rejectedSchemes
+Type: `Array`   
+Default value: `["data","javascript","mailto"]`  
+
+Extend the list with any additional schemes. Example: `javascript:something` will not be modified.
+
+#### options.removeAuth
+Type: `Boolean`   
+Default value: `false`  
+
+Remove user authentication information from the output URL.
+
+#### options.removeDirectoryIndexes
+Type: `Boolean`   
+Default value: `true`  
+
+Remove any resources that match any found in [`options.directoryIndexes`](#options.directoryIndexes).
+
+#### options.removeEmptyQueries
+Type: `Boolean`   
+Default value: `false`  
+
+Remove empty query variables. Example: `http://domain.com/?var1&var2=&var3=asdf` will become `http://domain.com/?var3=adsf`. This does not apply to unrelated URLs (with other protocols, auths, hosts and/or ports).
+
+#### options.removeRootTrailingSlash
+Type: `Boolean`   
+Default value: `true`  
+
+Remove trailing slashes from root paths. Example: `http://domain.com/?var` will become `http://domain.com?var` while `http://domain.com/dir/?var` will not be modified.
+
+#### options.schemeRelative
+Type: `Boolean`   
+Default value: `true`  
+
+Output URLs relative to the scheme. Example: `http://example.com/` will become `//example.com/`.
+
+#### options.site
+Type: `String`   
+Default value: `undefined`  
+
+An options-based version of the [`from`](#examples) argument. If both are specified, `from` takes priority.
+
+#### options.slashesDenoteHost
+Type: `Boolean`   
+Default value: `true`  
+
+Passed to Node's [`url.parse`](http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost).
+
+### Examples
+This library can be used as a [function for single-use](#single-instance) or as a [class for multiple conversions](#reusable-instances).
+
+Upon successful conversion, a `String` will be returned. If an issue is encountered while parsing `from`, an error will be thrown.
+
+#### Single Instance
+```js
+var RelateUrl = require("relateurl");
+
+var result = RelateUrl.relate(from, to, options);
+```
+
+#### Reusable Instances
+```js
+var RelateUrl = require("relateurl");
+
+var instance = new RelateUrl(from, options);
+
+var result1 = instance.relate(to1);
+var result2 = instance.relate(to2, customOptions);
+var result3 = instance.relate(to3);
+```
+
+## FAQ
+1. **Why bother writing/using this?**  
+To aid in further minifying HTML, mainly for the purpose of faster page loads and SEO. It's been integrated into [HTMLMinifier](https://github.com/kangax/html-minifier).
+
+2. **Why not just use Node's `url.parse`, `url.resolve` and `path.relative`?**  
+`url.parse` *is* used, but `url.resolve` and `path.relative` are both slower and less powerful than this library.
+
+
+## Release History
+* 0.2.7 Node v6 support
+* 0.2.6 minor enhancements
+* 0.2.5 added `options.removeRootTrailingSlash`
+* 0.2.4 added `options.site`
+* 0.2.3 added browserify npm-script
+* 0.2.2 removed task runner
+* 0.2.1 shorten resource- and query-relative URLs, test variations list with other site URLs
+* 0.2.0 code cleanup, `options.removeEmptyQueries=true` only applied to unrelated URLs
+* 0.1.0 initial release
+
+
+## Roadmap
+* 0.2.8 check if queries are the same, regardless of param order
+* 0.2.8 possible [scheme exclusions](http://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml) such as `tel:`
+* 0.2.8 decipher and return invalid input (special cases) to complete test suite
+* 0.3.0 test `options.slashesDenoteHost=false`, add something like `options.externalDirectoryIndexes=[]` for external sites
+
+
+[npm-image]: https://img.shields.io/npm/v/relateurl.svg
+[npm-url]: https://npmjs.org/package/relateurl
+[travis-image]: https://img.shields.io/travis/stevenvachon/relateurl.svg
+[travis-url]: https://travis-ci.org/stevenvachon/relateurl
+[david-image]: https://img.shields.io/david/stevenvachon/relateurl.svg
+[david-url]: https://david-dm.org/stevenvachon/relateurl
Index: frontend/node_modules/relateurl/lib/constants.js
===================================================================
--- frontend/node_modules/relateurl/lib/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/constants.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,10 @@
+"use strict";
+
+module.exports =
+{
+	// Output
+	ABSOLUTE:      "absolute",
+	PATH_RELATIVE: "pathRelative",
+	ROOT_RELATIVE: "rootRelative",
+	SHORTEST:      "shortest"
+};
Index: frontend/node_modules/relateurl/lib/format.js
===================================================================
--- frontend/node_modules/relateurl/lib/format.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/format.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,174 @@
+"use strict";
+
+var constants = require("./constants");
+
+
+
+function formatAuth(urlObj, options)
+{
+	if (urlObj.auth && !options.removeAuth && (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE))
+	{
+		return urlObj.auth + "@";
+	}
+	
+	return "";
+}
+
+
+
+function formatHash(urlObj, options)
+{
+	return urlObj.hash ? urlObj.hash : "";
+}
+
+
+
+function formatHost(urlObj, options)
+{
+	if (urlObj.host.full && (urlObj.extra.relation.maximumAuth || options.output===constants.ABSOLUTE))
+	{
+		return urlObj.host.full;
+	}
+	
+	return "";
+}
+
+
+
+function formatPath(urlObj, options)
+{
+	var str = "";
+	
+	var absolutePath = urlObj.path.absolute.string;
+	var relativePath = urlObj.path.relative.string;
+	var resource = showResource(urlObj, options);
+	
+	if (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE || options.output===constants.ROOT_RELATIVE)
+	{
+		str = absolutePath;
+	}
+	else if (relativePath.length<=absolutePath.length && options.output===constants.SHORTEST || options.output===constants.PATH_RELATIVE)
+	{
+		str = relativePath;
+		
+		if (str === "")
+		{
+			var query = showQuery(urlObj,options) && !!getQuery(urlObj,options);
+			
+			if (urlObj.extra.relation.maximumPath && !resource)
+			{
+				str = "./";
+			}
+			else if (urlObj.extra.relation.overridesQuery && !resource && !query)
+			{
+				str = "./";
+			}
+		}
+	}
+	else
+	{
+		str = absolutePath;
+	}
+	
+	if ( str==="/" && !resource && options.removeRootTrailingSlash && (!urlObj.extra.relation.minimumPort || options.output===constants.ABSOLUTE) )
+	{
+		str = "";
+	}
+	
+	return str;
+}
+
+
+
+function formatPort(urlObj, options)
+{
+	if (urlObj.port && !urlObj.extra.portIsDefault && urlObj.extra.relation.maximumHost)
+	{
+		return ":" + urlObj.port;
+	}
+	
+	return "";
+}
+
+
+
+function formatQuery(urlObj, options)
+{
+	return showQuery(urlObj,options) ? getQuery(urlObj, options) : "";
+}
+
+
+
+function formatResource(urlObj, options)
+{
+	return showResource(urlObj,options) ? urlObj.resource : "";
+}
+
+
+
+function formatScheme(urlObj, options)
+{
+	var str = "";
+	
+	if (urlObj.extra.relation.maximumHost || options.output===constants.ABSOLUTE)
+	{
+		if (!urlObj.extra.relation.minimumScheme || !options.schemeRelative || options.output===constants.ABSOLUTE)
+		{
+			str += urlObj.scheme + "://";
+		}
+		else
+		{
+			str += "//";
+		}
+	}
+	
+	return str;
+}
+
+
+
+function formatUrl(urlObj, options)
+{
+	var url = "";
+	
+	url += formatScheme(urlObj, options);
+	url += formatAuth(urlObj, options);
+	url += formatHost(urlObj, options);
+	url += formatPort(urlObj, options);
+	url += formatPath(urlObj, options);
+	url += formatResource(urlObj, options);
+	url += formatQuery(urlObj, options);
+	url += formatHash(urlObj, options);
+	
+	return url;
+}
+
+
+
+function getQuery(urlObj, options)
+{
+	var stripQuery = options.removeEmptyQueries && urlObj.extra.relation.minimumPort;
+	
+	return urlObj.query.string[ stripQuery ? "stripped" : "full" ];
+}
+
+
+
+function showQuery(urlObj, options)
+{
+	return !urlObj.extra.relation.minimumQuery || options.output===constants.ABSOLUTE || options.output===constants.ROOT_RELATIVE;
+}
+
+
+
+function showResource(urlObj, options)
+{
+	var removeIndex = options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex;
+	var removeMatchingResource = urlObj.extra.relation.minimumResource && options.output!==constants.ABSOLUTE && options.output!==constants.ROOT_RELATIVE;
+	
+	return !!urlObj.resource && !removeMatchingResource && !removeIndex;
+}
+
+
+
+module.exports = formatUrl;
Index: frontend/node_modules/relateurl/lib/index.js
===================================================================
--- frontend/node_modules/relateurl/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,94 @@
+"use strict";
+
+var constants  = require("./constants");
+var formatUrl  = require("./format");
+var getOptions = require("./options");
+var objUtils   = require("./util/object");
+var parseUrl   = require("./parse");
+var relateUrl  = require("./relate");
+
+
+
+function RelateUrl(from, options)
+{
+	this.options = getOptions(options,
+	{
+		defaultPorts: {ftp:21, http:80, https:443},
+		directoryIndexes: ["index.html"],
+		ignore_www: false,
+		output: RelateUrl.SHORTEST,
+		rejectedSchemes: ["data","javascript","mailto"],
+		removeAuth: false,
+		removeDirectoryIndexes: true,
+		removeEmptyQueries: false,
+		removeRootTrailingSlash: true,
+		schemeRelative: true,
+		site: undefined,
+		slashesDenoteHost: true
+	});
+	
+	this.from = parseUrl.from(from, this.options, null);
+}
+
+
+
+/*
+	Usage: instance=new RelateUrl(); instance.relate();
+*/
+RelateUrl.prototype.relate = function(from, to, options)
+{
+	// relate(to,options)
+	if ( objUtils.isPlainObject(to) )
+	{
+		options = to;
+		to = from;
+		from = null;
+	}
+	// relate(to)
+	else if (!to)
+	{
+		to = from;
+		from = null;
+	}
+	
+	options = getOptions(options, this.options);
+	from = from || options.site;
+	from = parseUrl.from(from, options, this.from);
+	
+	if (!from || !from.href)
+	{
+		throw new Error("from value not defined.");
+	}
+	else if (from.extra.hrefInfo.minimumPathOnly)
+	{
+		throw new Error("from value supplied is not absolute: "+from.href);
+	}
+	
+	to = parseUrl.to(to, options);
+	
+	if (to.valid===false) return to.href;
+	
+	to = relateUrl(from, to, options);
+	to = formatUrl(to, options);
+	
+	return to;
+}
+
+
+
+/*
+	Usage: RelateUrl.relate();
+*/
+RelateUrl.relate = function(from, to, options)
+{
+	return new RelateUrl().relate(from, to, options);
+}
+
+
+
+// Make constants accessible from API
+objUtils.shallowMerge(RelateUrl, constants);
+
+
+
+module.exports = RelateUrl;
Index: frontend/node_modules/relateurl/lib/options.js
===================================================================
--- frontend/node_modules/relateurl/lib/options.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/options.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+"use strict";
+
+var objUtils = require("./util/object");
+
+
+
+function getOptions(options, defaults)
+{
+	if ( objUtils.isPlainObject(options) )
+	{
+		var newOptions = {};
+		
+		for (var i in defaults)
+		{
+			if ( defaults.hasOwnProperty(i) )
+			{
+				if (options[i] !== undefined)
+				{
+					newOptions[i] = mergeOption(options[i], defaults[i]);
+				}
+				else
+				{
+					newOptions[i] = defaults[i];
+				}
+			}
+		}
+		
+		return newOptions;
+	}
+	else
+	{
+		return defaults;
+	}
+}
+
+
+
+function mergeOption(newValues, defaultValues)
+{
+	if (defaultValues instanceof Object && newValues instanceof Object)
+	{
+		if (defaultValues instanceof Array && newValues instanceof Array)
+		{
+			return defaultValues.concat(newValues);
+		}
+		else
+		{
+			return objUtils.shallowMerge(newValues, defaultValues);
+		}
+	}
+	
+	return newValues;
+}
+
+
+
+module.exports = getOptions;
Index: frontend/node_modules/relateurl/lib/parse/host.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/host.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/host.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,26 @@
+"use strict";
+
+function parseHost(urlObj, options)
+{
+	// TWEAK :: condition only for speed optimization
+	if (options.ignore_www)
+	{
+		var host = urlObj.host.full;
+		
+		if (host)
+		{
+			var stripped = host;
+			
+			if (host.indexOf("www.") === 0)
+			{
+				stripped = host.substr(4);
+			}
+			
+			urlObj.host.stripped = stripped;
+		}
+	}
+}
+
+
+
+module.exports = parseHost;
Index: frontend/node_modules/relateurl/lib/parse/hrefInfo.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/hrefInfo.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/hrefInfo.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,20 @@
+"use strict";
+
+function hrefInfo(urlObj)
+{
+	var minimumPathOnly     = (!urlObj.scheme && !urlObj.auth && !urlObj.host.full && !urlObj.port);
+	var minimumResourceOnly = (minimumPathOnly && !urlObj.path.absolute.string);
+	var minimumQueryOnly    = (minimumResourceOnly && !urlObj.resource);
+	var minimumHashOnly     = (minimumQueryOnly && !urlObj.query.string.full.length);
+	var empty               = (minimumHashOnly && !urlObj.hash);
+	
+	urlObj.extra.hrefInfo.minimumPathOnly     = minimumPathOnly;
+	urlObj.extra.hrefInfo.minimumResourceOnly = minimumResourceOnly;
+	urlObj.extra.hrefInfo.minimumQueryOnly    = minimumQueryOnly;
+	urlObj.extra.hrefInfo.minimumHashOnly     = minimumHashOnly;
+	urlObj.extra.hrefInfo.empty = empty;
+}
+
+
+
+module.exports = hrefInfo;
Index: frontend/node_modules/relateurl/lib/parse/index.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+"use strict";
+
+var hrefInfo   = require("./hrefInfo");
+var parseHost  = require("./host");
+var parsePath  = require("./path");
+var parsePort  = require("./port");
+var parseQuery = require("./query");
+var parseUrlString = require("./urlstring");
+var pathUtils      = require("../util/path");
+
+
+
+function parseFromUrl(url, options, fallback)
+{
+	if (url)
+	{
+		var urlObj = parseUrl(url, options);
+		
+		// Because the following occurs in the relate stage for "to" URLs,
+		// such had to be mostly duplicated here
+		
+		var pathArray = pathUtils.resolveDotSegments(urlObj.path.absolute.array);
+		
+		urlObj.path.absolute.array  = pathArray;
+		urlObj.path.absolute.string = "/" + pathUtils.join(pathArray);
+		
+		return urlObj;
+	}
+	else
+	{
+		return fallback;
+	}
+}
+
+
+
+function parseUrl(url, options)
+{
+	var urlObj = parseUrlString(url, options);
+	
+	if (urlObj.valid===false) return urlObj;
+	
+	parseHost(urlObj, options);
+	parsePort(urlObj, options);
+	parsePath(urlObj, options);
+	parseQuery(urlObj, options);
+	hrefInfo(urlObj);
+	
+	return urlObj;
+}
+
+
+
+module.exports =
+{
+	from: parseFromUrl,
+	to:   parseUrl
+};
Index: frontend/node_modules/relateurl/lib/parse/path.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,100 @@
+"use strict";
+
+function isDirectoryIndex(resource, options)
+{
+	var verdict = false;
+	
+	options.directoryIndexes.every( function(index)
+	{
+		if (index === resource)
+		{
+			verdict = true;
+			return false;
+		}
+		
+		return true;
+	});
+	
+	return verdict;
+}
+
+
+
+function parsePath(urlObj, options)
+{
+	var path = urlObj.path.absolute.string;
+	
+	if (path)
+	{
+		var lastSlash = path.lastIndexOf("/");
+		
+		if (lastSlash > -1)
+		{
+			if (++lastSlash < path.length)
+			{
+				var resource = path.substr(lastSlash);
+				
+				if (resource!=="." && resource!=="..")
+				{
+					urlObj.resource = resource;
+					path = path.substr(0, lastSlash);
+				}
+				else
+				{
+					path += "/";
+				}
+			}
+			
+			urlObj.path.absolute.string = path;
+			urlObj.path.absolute.array = splitPath(path);
+		}
+		else if (path==="." || path==="..")
+		{
+			// "..?var", "..#anchor", etc ... not "..index.html"
+			path += "/";
+			
+			urlObj.path.absolute.string = path;
+			urlObj.path.absolute.array = splitPath(path);
+		}
+		else
+		{
+			// Resource-only
+			urlObj.resource = path;
+			urlObj.path.absolute.string = null;
+		}
+		
+		urlObj.extra.resourceIsIndex = isDirectoryIndex(urlObj.resource, options);
+	}
+	// Else: query/hash-only or empty
+}
+
+
+
+function splitPath(path)
+{
+	// TWEAK :: condition only for speed optimization
+	if (path !== "/")
+	{
+		var cleaned = [];
+		
+		path.split("/").forEach( function(dir)
+		{
+			// Cleanup -- splitting "/dir/" becomes ["","dir",""]
+			if (dir !== "")
+			{
+				cleaned.push(dir);
+			}
+		});
+		
+		return cleaned;
+	}
+	else
+	{
+		// Faster to skip the above block and just create an array
+		return [];
+	}
+}
+
+
+
+module.exports = parsePath;
Index: frontend/node_modules/relateurl/lib/parse/port.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/port.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/port.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,32 @@
+"use strict";
+
+function parsePort(urlObj, options)
+{
+	var defaultPort = -1;
+	
+	for (var i in options.defaultPorts)
+	{
+		if ( i===urlObj.scheme && options.defaultPorts.hasOwnProperty(i) )
+		{
+			defaultPort = options.defaultPorts[i];
+			break;
+		}
+	}
+	
+	if (defaultPort > -1)
+	{
+		// Force same type as urlObj.port
+		defaultPort = defaultPort.toString();
+		
+		if (urlObj.port === null)
+		{
+			urlObj.port = defaultPort;
+		}
+		
+		urlObj.extra.portIsDefault = (urlObj.port === defaultPort);
+	}
+}
+
+
+
+module.exports = parsePort;
Index: frontend/node_modules/relateurl/lib/parse/query.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/query.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/query.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,53 @@
+"use strict";
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+
+
+function parseQuery(urlObj, options)
+{
+	urlObj.query.string.full = stringify(urlObj.query.object, false);
+	
+	// TWEAK :: condition only for speed optimization
+	if (options.removeEmptyQueries)
+	{
+		urlObj.query.string.stripped = stringify(urlObj.query.object, true);
+	}
+}
+
+
+
+function stringify(queryObj, removeEmptyQueries)
+{
+	var count = 0;
+	var str = "";
+	
+	for (var i in queryObj)
+	{
+		if ( i!=="" && hasOwnProperty.call(queryObj, i)===true )
+		{
+			var value = queryObj[i];
+			
+			if (value !== "" || !removeEmptyQueries)
+			{
+				str += (++count===1) ? "?" : "&";
+				
+				i = encodeURIComponent(i);
+				
+				if (value !== "")
+				{
+					str += i +"="+ encodeURIComponent(value).replace(/%20/g,"+");
+				}
+				else
+				{
+					str += i;
+				}
+			}
+		}
+	}
+	
+	return str;
+}
+
+
+
+module.exports = parseQuery;
Index: frontend/node_modules/relateurl/lib/parse/urlstring.js
===================================================================
--- frontend/node_modules/relateurl/lib/parse/urlstring.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/parse/urlstring.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,146 @@
+"use strict";
+
+var _parseUrl = require("url").parse;
+
+
+
+/*
+	Customize the URL object that Node generates
+	because:
+	
+	* necessary data for later
+	* urlObj.host is useless
+	* urlObj.hostname is too long
+	* urlObj.path is useless
+	* urlObj.pathname is too long
+	* urlObj.protocol is inaccurate; should be called "scheme"
+	* urlObj.search is mostly useless
+*/
+function clean(urlObj)
+{
+	var scheme = urlObj.protocol;
+	
+	if (scheme)
+	{
+		// Remove ":" suffix
+		if (scheme.indexOf(":") === scheme.length-1)
+		{
+			scheme = scheme.substr(0, scheme.length-1);
+		}
+	}
+	
+	urlObj.host =
+	{
+		// TODO :: unescape(encodeURIComponent(s)) ? ... http://ecmanaut.blogspot.ca/2006/07/encoding-decoding-utf8-in-javascript.html
+		full: urlObj.hostname,
+		stripped: null
+	};
+	
+	urlObj.path =
+	{
+		absolute:
+		{
+			array: null,
+			string: urlObj.pathname
+		},
+		relative:
+		{
+			array: null,
+			string: null
+		}
+	};
+	
+	urlObj.query =
+	{
+		object: urlObj.query,
+		string:
+		{
+			full: null,
+			stripped: null
+		}
+	};
+	
+	urlObj.extra =
+	{
+		hrefInfo:
+		{
+			minimumPathOnly: null,
+			minimumResourceOnly: null,
+			minimumQueryOnly: null,
+			minimumHashOnly: null,
+			empty: null,
+			
+			separatorOnlyQuery: urlObj.search==="?"
+		},
+		portIsDefault: null,
+		relation:
+		{
+			maximumScheme: null,
+			maximumAuth: null,
+			maximumHost: null,
+			maximumPort: null,
+			maximumPath: null,
+			maximumResource: null,
+			maximumQuery: null,
+			maximumHash: null,
+			
+			minimumScheme: null,
+			minimumAuth: null,
+			minimumHost: null,
+			minimumPort: null,
+			minimumPath: null,
+			minimumResource: null,
+			minimumQuery: null,
+			minimumHash: null,
+			
+			overridesQuery: null
+		},
+		resourceIsIndex: null,
+		slashes: urlObj.slashes
+	};
+	
+	urlObj.resource = null;
+	urlObj.scheme = scheme;
+	delete urlObj.hostname;
+	delete urlObj.pathname;
+	delete urlObj.protocol;
+	delete urlObj.search;
+	delete urlObj.slashes;
+	
+	return urlObj;
+}
+
+
+
+function validScheme(url, options)
+{
+	var valid = true;
+	
+	options.rejectedSchemes.every( function(rejectedScheme)
+	{
+		valid = !(url.indexOf(rejectedScheme+":") === 0);
+		
+		// Break loop
+		return valid;
+	});
+	
+	return valid;
+}
+
+
+
+function parseUrlString(url, options)
+{
+	if ( validScheme(url,options) )
+	{
+		return clean( _parseUrl(url, true, options.slashesDenoteHost) );
+	}
+	else
+	{
+		return {href:url, valid:false};
+	}
+}
+
+
+
+module.exports = parseUrlString;
Index: frontend/node_modules/relateurl/lib/relate/absolutize.js
===================================================================
--- frontend/node_modules/relateurl/lib/relate/absolutize.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/relate/absolutize.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,89 @@
+"use strict";
+
+var findRelation = require("./findRelation");
+var objUtils     = require("../util/object");
+var pathUtils    = require("../util/path");
+
+
+
+function absolutize(urlObj, siteUrlObj, options)
+{
+	findRelation.upToPath(urlObj, siteUrlObj, options);
+	
+	// Fill in relative URLs
+	if (urlObj.extra.relation.minimumScheme) urlObj.scheme = siteUrlObj.scheme;
+	if (urlObj.extra.relation.minimumAuth)   urlObj.auth   = siteUrlObj.auth;
+	if (urlObj.extra.relation.minimumHost)   urlObj.host   = objUtils.clone(siteUrlObj.host);
+	if (urlObj.extra.relation.minimumPort)   copyPort(urlObj, siteUrlObj);
+	if (urlObj.extra.relation.minimumScheme) copyPath(urlObj, siteUrlObj);
+	
+	// Check remaining relativeness now that path has been copied and/or resolved
+	findRelation.pathOn(urlObj, siteUrlObj, options);
+	
+	// Fill in relative URLs
+	if (urlObj.extra.relation.minimumResource) copyResource(urlObj, siteUrlObj);
+	if (urlObj.extra.relation.minimumQuery)    urlObj.query = objUtils.clone(siteUrlObj.query);
+	if (urlObj.extra.relation.minimumHash)     urlObj.hash  = siteUrlObj.hash;
+}
+
+
+
+/*
+	Get an absolute path that's relative to site url.
+*/
+function copyPath(urlObj, siteUrlObj)
+{
+	if (urlObj.extra.relation.maximumHost || !urlObj.extra.hrefInfo.minimumResourceOnly)
+	{
+		var pathArray = urlObj.path.absolute.array;
+		var pathString = "/";
+		
+		// If not erroneous URL
+		if (pathArray)
+		{
+			// If is relative path
+			if (urlObj.extra.hrefInfo.minimumPathOnly && urlObj.path.absolute.string.indexOf("/")!==0)
+			{
+				// Append path to site path
+				pathArray = siteUrlObj.path.absolute.array.concat(pathArray);
+			}
+			
+			pathArray   = pathUtils.resolveDotSegments(pathArray);
+			pathString += pathUtils.join(pathArray);
+		}
+		else
+		{
+			pathArray = [];
+		}
+		
+		urlObj.path.absolute.array  = pathArray;
+		urlObj.path.absolute.string = pathString;
+	}
+	else
+	{
+		// Resource-, query- or hash-only or empty
+		urlObj.path = objUtils.clone(siteUrlObj.path);
+	}
+}
+
+
+
+function copyPort(urlObj, siteUrlObj)
+{
+	urlObj.port = siteUrlObj.port;
+	
+	urlObj.extra.portIsDefault = siteUrlObj.extra.portIsDefault;
+}
+
+
+
+function copyResource(urlObj, siteUrlObj)
+{
+	urlObj.resource = siteUrlObj.resource;
+	
+	urlObj.extra.resourceIsIndex = siteUrlObj.extra.resourceIsIndex;
+}
+
+
+
+module.exports = absolutize;
Index: frontend/node_modules/relateurl/lib/relate/findRelation.js
===================================================================
--- frontend/node_modules/relateurl/lib/relate/findRelation.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/relate/findRelation.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,79 @@
+"use strict";
+
+function findRelation_upToPath(urlObj, siteUrlObj, options)
+{
+	// Path- or root-relative URL
+	var pathOnly = urlObj.extra.hrefInfo.minimumPathOnly;
+	
+	// Matching scheme, scheme-relative or path-only
+	var minimumScheme = (urlObj.scheme===siteUrlObj.scheme || !urlObj.scheme);
+	
+	// Matching auth, ignoring auth or path-only
+	var minimumAuth = minimumScheme && (urlObj.auth===siteUrlObj.auth || options.removeAuth || pathOnly);
+	
+	// Matching host or path-only
+	var www = options.ignore_www ? "stripped" : "full";
+	var minimumHost = minimumAuth && (urlObj.host[www]===siteUrlObj.host[www] || pathOnly);
+	
+	// Matching port or path-only
+	var minimumPort = minimumHost && (urlObj.port===siteUrlObj.port || pathOnly);
+	
+	urlObj.extra.relation.minimumScheme = minimumScheme;
+	urlObj.extra.relation.minimumAuth   = minimumAuth;
+	urlObj.extra.relation.minimumHost   = minimumHost;
+	urlObj.extra.relation.minimumPort   = minimumPort;
+	
+	urlObj.extra.relation.maximumScheme = !minimumScheme || minimumScheme && !minimumAuth;
+	urlObj.extra.relation.maximumAuth   = !minimumScheme || minimumScheme && !minimumHost;
+	urlObj.extra.relation.maximumHost   = !minimumScheme || minimumScheme && !minimumPort;
+}
+
+
+
+function findRelation_pathOn(urlObj, siteUrlObj, options)
+{
+	var queryOnly = urlObj.extra.hrefInfo.minimumQueryOnly;
+	var hashOnly  = urlObj.extra.hrefInfo.minimumHashOnly;
+	var empty     = urlObj.extra.hrefInfo.empty;	// not required, but self-documenting
+	
+	// From upToPath()
+	var minimumPort   = urlObj.extra.relation.minimumPort;
+	var minimumScheme = urlObj.extra.relation.minimumScheme;
+	
+	// Matching port and path
+	var minimumPath = minimumPort && urlObj.path.absolute.string===siteUrlObj.path.absolute.string;
+	
+	// Matching resource or query/hash-only or empty
+	var matchingResource = (urlObj.resource===siteUrlObj.resource || !urlObj.resource && siteUrlObj.extra.resourceIsIndex) || (options.removeDirectoryIndexes && urlObj.extra.resourceIsIndex && !siteUrlObj.resource);
+	var minimumResource = minimumPath && (matchingResource || queryOnly || hashOnly || empty);
+	
+	// Matching query or hash-only/empty
+	var query = options.removeEmptyQueries ? "stripped" : "full";
+	var urlQuery = urlObj.query.string[query];
+	var siteUrlQuery = siteUrlObj.query.string[query];
+	var minimumQuery = (minimumResource && !!urlQuery && urlQuery===siteUrlQuery) || ((hashOnly || empty) && !urlObj.extra.hrefInfo.separatorOnlyQuery);
+	
+	var minimumHash = minimumQuery && urlObj.hash===siteUrlObj.hash;
+	
+	urlObj.extra.relation.minimumPath     = minimumPath;
+	urlObj.extra.relation.minimumResource = minimumResource;
+	urlObj.extra.relation.minimumQuery    = minimumQuery;
+	urlObj.extra.relation.minimumHash     = minimumHash;
+	
+	urlObj.extra.relation.maximumPort     = !minimumScheme || minimumScheme && !minimumPath;
+	urlObj.extra.relation.maximumPath     = !minimumScheme || minimumScheme && !minimumResource;
+	urlObj.extra.relation.maximumResource = !minimumScheme || minimumScheme && !minimumQuery;
+	urlObj.extra.relation.maximumQuery    = !minimumScheme || minimumScheme && !minimumHash;
+	urlObj.extra.relation.maximumHash     = !minimumScheme || minimumScheme && !minimumHash;	// there's nothing after hash, so it's the same as maximumQuery
+	
+	// Matching path and/or resource with existing but non-matching site query
+	urlObj.extra.relation.overridesQuery  = minimumPath && urlObj.extra.relation.maximumResource && !minimumQuery && !!siteUrlQuery;
+}
+
+
+
+module.exports =
+{
+	pathOn:   findRelation_pathOn,
+	upToPath: findRelation_upToPath
+};
Index: frontend/node_modules/relateurl/lib/relate/index.js
===================================================================
--- frontend/node_modules/relateurl/lib/relate/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/relate/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,18 @@
+"use strict";
+
+var absolutize = require("./absolutize");
+var relativize = require("./relativize");
+
+
+
+function relateUrl(siteUrlObj, urlObj, options)
+{
+	absolutize(urlObj, siteUrlObj, options);
+	relativize(urlObj, siteUrlObj, options);
+	
+	return urlObj;
+}
+
+
+
+module.exports = relateUrl;
Index: frontend/node_modules/relateurl/lib/relate/relativize.js
===================================================================
--- frontend/node_modules/relateurl/lib/relate/relativize.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/relate/relativize.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,67 @@
+"use strict";
+
+var pathUtils = require("../util/path");
+
+
+
+/*
+	Get a path relative to the site path.
+*/
+function relatePath(absolutePath, siteAbsolutePath)
+{
+	var relativePath = [];
+	
+	// At this point, it's related to the host/port
+	var related = true;
+	var parentIndex = -1;
+	
+	// Find parents
+	siteAbsolutePath.forEach( function(siteAbsoluteDir, i)
+	{
+		if (related)
+		{
+			if (absolutePath[i] !== siteAbsoluteDir)
+			{
+				related = false;
+			}
+			else
+			{
+				parentIndex = i;
+			}
+		}
+		
+		if (!related)
+		{
+			// Up one level
+			relativePath.push("..");
+		}
+	});
+	
+	// Form path
+	absolutePath.forEach( function(dir, i)
+	{
+		if (i > parentIndex)
+		{
+			relativePath.push(dir);
+		}
+	});
+	
+	return relativePath;
+}
+
+
+
+function relativize(urlObj, siteUrlObj, options)
+{
+	if (urlObj.extra.relation.minimumScheme)
+	{
+		var pathArray = relatePath(urlObj.path.absolute.array, siteUrlObj.path.absolute.array);
+		
+		urlObj.path.relative.array  = pathArray;
+		urlObj.path.relative.string = pathUtils.join(pathArray);
+	}
+}
+
+
+
+module.exports = relativize;
Index: frontend/node_modules/relateurl/lib/util/devlog.js
===================================================================
--- frontend/node_modules/relateurl/lib/util/devlog.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/util/devlog.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,25 @@
+"use strict";
+
+var inspect = require("util").inspect;
+
+
+
+function log(data)
+{
+	console.log( inspect(data, {depth:null, colors:true}) );
+}
+
+
+
+function logAll(data)
+{
+	console.log( inspect(data, {depth:null, showHidden:true, colors:true}) );
+}
+
+
+
+module.exports =
+{
+	log:    log,
+	logAll: logAll
+};
Index: frontend/node_modules/relateurl/lib/util/object.js
===================================================================
--- frontend/node_modules/relateurl/lib/util/object.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/util/object.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+"use strict";
+
+/*
+	Deep-clone an object.
+*/
+function clone(obj)
+{
+	if (obj instanceof Object)
+	{
+		var clonedObj = (obj instanceof Array) ? [] : {};
+		
+		for (var i in obj)
+		{
+			if ( obj.hasOwnProperty(i) )
+			{
+				clonedObj[i] = clone( obj[i] );
+			}
+		}
+		
+		return clonedObj;
+	}
+	
+	return obj;
+}
+
+
+
+/*
+	https://github.com/jonschlinkert/is-plain-object
+*/
+function isPlainObject(obj)
+{
+	return !!obj && typeof obj==="object" && obj.constructor===Object;
+}
+
+
+
+/*
+	Shallow-merge two objects.
+*/
+function shallowMerge(target, source)
+{
+	if (target instanceof Object && source instanceof Object)
+	{
+		for (var i in source)
+		{
+			if ( source.hasOwnProperty(i) )
+			{
+				target[i] = source[i];
+			}
+		}
+	}
+	
+	return target;
+}
+
+
+
+module.exports =
+{
+	clone: clone,
+	isPlainObject: isPlainObject,
+	shallowMerge: shallowMerge
+};
Index: frontend/node_modules/relateurl/lib/util/path.js
===================================================================
--- frontend/node_modules/relateurl/lib/util/path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/lib/util/path.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,49 @@
+"use strict";
+
+function joinPath(pathArray)
+{
+	if (pathArray.length > 0)
+	{
+		return pathArray.join("/") + "/";
+	}
+	else
+	{
+		return "";
+	}
+}
+
+
+
+function resolveDotSegments(pathArray)
+{
+	var pathAbsolute = [];
+	
+	pathArray.forEach( function(dir)
+	{
+		if (dir !== "..")
+		{
+			if (dir !== ".")
+			{
+				pathAbsolute.push(dir);
+			}
+		}
+		else
+		{
+			// Remove parent
+			if (pathAbsolute.length > 0)
+			{
+				pathAbsolute.splice(pathAbsolute.length-1, 1);
+			}
+		}
+	});
+	
+	return pathAbsolute;
+}
+
+
+
+module.exports =
+{
+	join: joinPath,
+	resolveDotSegments: resolveDotSegments
+};
Index: frontend/node_modules/relateurl/license
===================================================================
--- frontend/node_modules/relateurl/license	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/license	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Steven Vachon <contact@svachon.com> (svachon.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: frontend/node_modules/relateurl/package.json
===================================================================
--- frontend/node_modules/relateurl/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/relateurl/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+{
+  "name": "relateurl",
+  "description": "Minify URLs by converting them from absolute to relative.",
+  "version": "0.2.7",
+  "license": "MIT",
+  "homepage": "https://github.com/stevenvachon/relateurl",
+  "author": {
+    "name": "Steven Vachon",
+    "email": "contact@svachon.com",
+    "url": "http://www.svachon.com/"
+  },
+  "main": "lib",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/stevenvachon/relateurl.git"
+  },
+  "bugs": {
+    "url": "https://github.com/stevenvachon/relateurl/issues"
+  },
+  "devDependencies": {
+    "browserify": "^13.0.1",
+    "chai": "^3.5.0",
+    "mocha": "^2.5.3",
+    "uglify-js": "^2.7.0"
+  },
+  "engines": {
+    "node": ">= 0.10"
+  },
+  "scripts": {
+    "browserify": "browserify lib/ --standalone RelateUrl | uglifyjs --compress --mangle -o relateurl-browser.js",
+    "test": "mocha test/ --bail --reporter spec --check-leaks"
+  },
+  "files": [
+    "lib",
+    "license"
+  ],
+  "keywords": [
+    "uri",
+    "url",
+    "minifier",
+    "minify",
+    "lint",
+    "relative",
+    "absolute"
+  ]
+}
