diff --git a/.gitignore b/.gitignore
index 79ec252..533683f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,8 @@
images/*/*.png
-*.log
node_modules/
-.DS_Store
-*.rdb
coverage/
-config.js
+.DS_Store
+*.log
+*.rdb
*.sublime-*
+config.js
diff --git a/README.md b/README.md
index 69ef0c1..63ff7e4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Crafatar [](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar)
-[](https://webchat.esper.net/?channels=crafatar) [](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
+[](https://webchat.esper.net/?channels=crafatar "#crafatar") [](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
@@ -33,41 +33,11 @@ Please [visit the website](https://crafatar.com) for details.
* Open an [issue](https://github.com/crafatar/crafatar/issues/) on GitHub
* You can [join IRC](https://webchat.esper.net/?channels=crafatar) in #crafatar on irc.esper.net.
-## Installation on Heroku
-[](https://heroku.com/deploy)
+# Installation
-## Installation on Dokku
-##### [dokku server]
-Install the [dokku-redis](https://github.com/ohardy/dokku-redis#redis-plugin-for-dokku) plugin.
-```shell
-dokku redis:start
-dokku apps:create crafatar
-dokku config:set crafatar BIND=0.0.0.0 PORT=5000
-```
-For persistent images and logs:
-```shell
-dokku docker-options:add crafatar deploy "-v /var/lib/crafatar/images:/app/images"
-dokku docker-options:add crafatar deploy "-v /var/log/crafatar:/app/logs"
-```
-If you want to listen on extra domains:
-```shell
-dokku domains crafatar:add example.com
-```
-##### [your machine]
-Add dokku remote and deploy!
-```shell
-git remote add dokku dokku@example.com:crafatar
-git push dokku master
-```
-
-## Installation on your machine
-* Use io.js
-* [Install](https://github.com/Automattic/node-canvas/wiki) Cairo.
-* `npm install`
-* Start `redis-server`
-* `npm start`
-* Access [http://localhost:3000](http://localhost:3000)
+Have a look at [crafatar/setup](https://github.com/crafatar/setup) to see how we set things up at Crafatar.
+For more info about local setup, Heroku, or Dokku please see [Installation](https://github.com/crafatar/crafatar/wiki/Installation) on the wiki.
## Tests
```shell
@@ -83,4 +53,4 @@ env VERBOSE_TEST=true npm test
It can be helpful to monitor redis commands to debug caching errors:
```shell
redis-cli monitor
-```
+```
\ No newline at end of file
diff --git a/app.json b/app.json
index 40bd9ac..939ca44 100644
--- a/app.json
+++ b/app.json
@@ -1,6 +1,6 @@
{
"name": "Crafatar",
- "description": "A Minecraft Avatar API written in NodeJS",
+ "description": "A blazing fast API for Minecraft faces!",
"repository": "https://github.com/crafatar/crafatar",
"keywords": [
"node",
@@ -10,10 +10,21 @@
],
"website": "https://crafatar.com/",
"env": {
- "HEROKU": "true",
- "BUILDPACK_URL": "https://github.com/mojodna/heroku-buildpack-multi.git#build-env"
+ "EPHEMERAL_STORAGE": {
+ "description": "Set to true if your storage is gone after deploying",
+ "required": false,
+ "value": true
+ }
},
"addons": [
"rediscloud"
+ ],
+ "buildpacks": [
+ {
+ "url": "https://github.com/mojodna/heroku-buildpack-cairo.git"
+ },
+ {
+ "url": "https://github.com/heroku/heroku-buildpack-nodejs.git"
+ }
]
-}
+}
\ No newline at end of file
diff --git a/config.example.js b/config.example.js
index be165d5..25b273e 100644
--- a/config.example.js
+++ b/config.example.js
@@ -1,36 +1,35 @@
var config = {
avatars: {
- min_size: 1, // for avatars
- max_size: 512, // for avatars; too big values might lead to slow response time or DoS
- default_size: 160 // for avatars; size to be used when no size given
+ min_size: 1, // for avatars
+ max_size: 512, // for avatars; too big values might lead to slow response time or DoS
+ default_size: 160 // for avatars; size to be used when no size given
},
renders: {
- min_scale: 1, // for 3D rendered skins
- max_scale: 10, // for 3D rendered skins; too big values might lead to slow response time or DoS
- default_scale: 6 // for 3D rendered skins; scale to be used when no scale given
+ min_scale: 1, // for 3D rendered skins
+ max_scale: 10, // for 3D rendered skins; too big values might lead to slow response time or DoS
+ default_scale: 6 // for 3D rendered skins; scale to be used when no scale given
},
cleaner: {
- interval: 1800, // interval seconds to check limits
- disk_limit: 10240, // min allowed free KB on disk to trigger image deletion
- redis_limit: 24576, // max allowed used KB on redis to trigger redis flush
- amount: 50000 // amount of skins for which all iamge types are deleted
+ interval: 600, // interval seconds to check limits
+ disk_limit: 524288, // min allowed free KB on disk to trigger image deletion
+ redis_limit: 24576, // max allowed used KB on redis to trigger redis flush
+ amount: 50000 // amount of skins for which all image types are deleted
},
directories: {
- faces: "images/faces/", // directory where faces are kept. should have trailing "/"
- helms: "images/helms/", // directory where helms are kept. should have trailing "/"
- skins: "images/skins/", // directory where skins are kept. should have trailing "/"
- renders: "images/renders/", // directory where rendered skins are kept. should have trailing "/"
- capes: "images/capes/" // directory where capes are kept. should have trailing "/"
+ faces: "./images/faces/", // directory where faces are kept. must have trailing "/"
+ helms: "./images/helms/", // directory where helms are kept. must have trailing "/"
+ skins: "./images/skins/", // directory where skins are kept. must have trailing "/"
+ renders: "./images/renders/", // directory where rendered skins are kept. must have trailing "/"
+ capes: "./images/capes/" // directory where capes are kept. must have trailing "/"
},
caching: {
- local: 1200, // seconds until we will check if user's skin changed. should be > 60 to comply with Mojang's rate limit
- browser: 3600 // seconds until browser will request image again
+ local: 1200, // seconds until we will check if user's skin changed. should be > 60 to comply with Mojang's rate limit
+ browser: 3600 // seconds until browser will request image again
},
server: {
- http_timeout: 1000, // ms until connection to Mojang is dropped
- debug_enabled: false, // enables logging.debug
- clusters: 1, // we recommend not using multiple clusters YET, see issue #80
- log_time: true // set to false if you use an external logger that provides timestamps
+ http_timeout: 2000, // ms until connection to Mojang is dropped
+ debug_enabled: false, // enables logging.debug & editing index page
+ log_time: true // set to false if you use an external logger that provides timestamps
}
};
diff --git a/lib/cache.js b/lib/cache.js
index b996d05..219e6af 100644
--- a/lib/cache.js
+++ b/lib/cache.js
@@ -1,18 +1,16 @@
var logging = require("./logging");
var node_redis = require("redis");
var config = require("../config");
-var path = require("path");
var url = require("url");
-var fs = require("fs");
var redis = null;
// sets up redis connection
-// flushes redis when running on heroku (files aren't kept between pushes)
+// flushes redis when using ephemeral storage (e.g. Heroku)
function connect_redis() {
logging.log("connecting to redis...");
// parse redis env
- var redis_env = (process.env.REDISCLOUD_URL || process.env.REDIS_URL);
+ var redis_env = process.env.REDISCLOUD_URL || process.env.REDIS_URL;
var redis_url = redis_env ? url.parse(redis_env) : {};
redis_url.port = redis_url.port || 6379;
redis_url.hostname = redis_url.hostname || "localhost";
@@ -23,39 +21,19 @@ function connect_redis() {
}
redis.on("ready", function() {
logging.log("Redis connection established.");
- if (process.env.HEROKU) {
- logging.log("Running on heroku, flushing redis");
+ if (process.env.EPHEMERAL_STORAGE) {
+ logging.log("Storage is ephemeral, flushing redis");
redis.flushall();
}
});
- redis.on("error", function (err) {
+ redis.on("error", function(err) {
logging.error(err);
});
- redis.on("end", function () {
+ redis.on("end", function() {
logging.warn("Redis connection lost!");
});
}
-// sets the date of the face file belonging to +skin_hash+ to now
-// the helms file is ignored because we only need 1 file to read/write from
-function update_file_date(rid, skin_hash) {
- if (skin_hash) {
- var face_path = path.join(__dirname, "..", config.directories.faces, skin_hash + ".png");
- fs.exists(face_path, function(exists) {
- if (exists) {
- var date = new Date();
- fs.utimes(face_path, date, date, function(err) {
- if (err) {
- logging.error(rid, "Error:", err.stack);
- }
- });
- } else {
- logging.error(rid, "tried to update", face_path + " date, but it does not exist");
- }
- });
- }
-}
-
var exp = {};
// returns the redis instance
@@ -92,20 +70,19 @@ exp.info = function(callback) {
});
};
-// sets the timestamp for +userId+ and its face file's (+hash+) date to the current time
+// sets the timestamp for +userId+
// if +temp+ is true, the timestamp is set so that the record will be outdated after 60 seconds
// these 60 seconds match the duration of Mojang's rate limit ban
// callback: error
-exp.update_timestamp = function(rid, userId, hash, temp, callback) {
- logging.debug(rid, "updating cache timestamp");
- var sub = temp ? (config.caching.local - 60) : 0;
+exp.update_timestamp = function(rid, userId, temp, callback) {
+ logging.debug(rid, "updating cache timestamp (" + temp + ")");
+ var sub = temp ? config.caching.local - 60 : 0;
var time = Date.now() - sub;
// store userId in lower case if not null
userId = userId && userId.toLowerCase();
redis.hmset(userId, "t", time, function(err) {
callback(err);
});
- update_file_date(rid, hash);
};
// create the key +userId+, store +skin_hash+, +cape_hash+, +slim+ and current time
@@ -114,12 +91,10 @@ exp.update_timestamp = function(rid, userId, hash, temp, callback) {
// +slim+ can be true (alex) or false (steve)
// +callback+ contans error
exp.save_hash = function(rid, userId, skin_hash, cape_hash, slim, callback) {
- logging.debug(rid, "caching skin:" + skin_hash + " cape:" + cape_hash);
-
+ logging.debug(rid, "caching skin:" + skin_hash + " cape:" + cape_hash + " slim:" + slim);
// store shorter null value instead of "null" string
skin_hash = skin_hash === null ? "" : skin_hash;
cape_hash = cape_hash === null ? "" : cape_hash;
-
// store userId in lower case if not null
userId = userId && userId.toLowerCase();
diff --git a/lib/cleaner.js b/lib/cleaner.js
index 53924c5..21c032d 100644
--- a/lib/cleaner.js
+++ b/lib/cleaner.js
@@ -20,10 +20,11 @@ function should_clean_redis(callback) {
} else {
try {
// logging.debug(info.toString());
- logging.debug("used mem:" + info.used_memory);
var used = parseInt(info.used_memory) / 1024;
- logging.log("RedisCleaner:", used + "KB used");
- callback(err, used >= config.cleaner.redis_limit);
+ var result = used >= config.cleaner.redis_limit;
+ var msg = "RedisCleaner: " + used + "KB used";
+ (result ? logging.log : logging.debug)(msg);
+ callback(err, result);
} catch(e) {
callback(e, false);
}
@@ -35,17 +36,19 @@ function should_clean_redis(callback) {
// callback: error, true|false
function should_clean_disk(callback) {
df({
- file: path.join(__dirname, "..", config.directories.faces),
+ file: config.directories.faces,
prefixMultiplier: "KiB",
isDisplayPrefixMultiplier: false,
precision: 2
- }, function (err, response) {
+ }, function(err, response) {
if (err) {
callback(err, false);
} else {
var available = response[0].available;
- logging.log("DiskCleaner:", available + "KB available");
- callback(err, available < config.cleaner.disk_limit);
+ var result = available < config.cleaner.disk_limit;
+ var msg = "DiskCleaner: " + available + "KB available";
+ (result ? logging.log : logging.debug)(msg);
+ callback(err, result);
}
});
}
@@ -71,28 +74,45 @@ exp.run = function() {
logging.error(err);
} else if (clean) {
logging.warn("DiskCleaner: Disk limit reached! Cleaning images now");
- var facesdir = path.join(__dirname, "..", config.directories.faces);
- var helmdir = path.join(__dirname, "..", config.directories.helms);
- var renderdir = path.join(__dirname, "..", config.directories.renders);
- var skindir = path.join(__dirname, "..", config.directories.skins);
- fs.readdir(facesdir, function (readerr, files) {
+
+ // hotfix for #139 | FIXME
+ logging.warn("DiskCleaner: Flushing Redis to prevent ENOENT");
+ redis.flushall();
+ // end hotfix
+
+ var skinsdir = config.directories.skins;
+ var capesdir = config.directories.capes;
+ var facesdir = config.directories.faces;
+ var helmsdir = config.directories.helms;
+ var rendersdir = config.directories.renders;
+ fs.readdir(skinsdir, function(readerr, files) {
if (!readerr) {
for (var i = 0, l = Math.min(files.length, config.cleaner.amount); i < l; i++) {
var filename = files[i];
if (filename[0] !== ".") {
fs.unlink(path.join(facesdir, filename), nil);
- fs.unlink(path.join(helmdir, filename), nil);
- fs.unlink(path.join(skindir, filename), nil);
+ fs.unlink(path.join(helmsdir, filename), nil);
+ fs.unlink(path.join(skinsdir, filename), nil);
}
}
}
});
- fs.readdir(renderdir, function (readerr, files) {
+ fs.readdir(rendersdir, function(readerr, files) {
if (!readerr) {
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
var filename = files[j];
if (filename[0] !== ".") {
- fs.unlink(renderdir + filename, nil);
+ fs.unlink(rendersdir + filename, nil);
+ }
+ }
+ }
+ });
+ fs.readdir(capesdir, function(readerr, files) {
+ if (!readerr) {
+ for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
+ var filename = files[j];
+ if (filename[0] !== ".") {
+ fs.unlink(capesdir + filename, nil);
}
}
}
diff --git a/lib/helpers.js b/lib/helpers.js
index 4fcd367..5859b7a 100644
--- a/lib/helpers.js
+++ b/lib/helpers.js
@@ -25,14 +25,14 @@ function store_skin(rid, userId, profile, cache_details, callback) {
if (!err && url) {
var skin_hash = get_hash(url);
if (cache_details && cache_details.skin === skin_hash) {
- cache.update_timestamp(rid, userId, skin_hash, false, function(cache_err) {
+ cache.update_timestamp(rid, userId, false, function(cache_err) {
callback(cache_err, skin_hash, slim);
});
} else {
logging.debug(rid, "new skin hash:", skin_hash);
- var facepath = path.join(__dirname, "..", config.directories.faces, skin_hash + ".png");
- var helmpath = path.join(__dirname, "..", config.directories.helms, skin_hash + ".png");
- var skinpath = path.join(__dirname, "..", config.directories.skins, skin_hash + ".png");
+ var facepath = path.join(config.directories.faces, skin_hash + ".png");
+ var helmpath = path.join(config.directories.helms, skin_hash + ".png");
+ var skinpath = path.join(config.directories.skins, skin_hash + ".png");
fs.exists(facepath, function(exists) {
if (exists) {
logging.debug(rid, "skin already exists, not downloading");
@@ -42,14 +42,12 @@ function store_skin(rid, userId, profile, cache_details, callback) {
if (err1 || !img) {
callback(err1, null, slim);
} else {
- skins.save_image(img, skinpath, function(skin_err) {
+ skins.save_image(img, skinpath, function(skin_err, skin_img) {
if (skin_err) {
- logging.error(rid, skin_err);
callback(skin_err, null, slim);
} else {
skins.extract_face(img, facepath, function(err2) {
if (err2) {
- logging.error(rid, err2.stack);
callback(err2, null, slim);
} else {
logging.debug(rid, "face extracted");
@@ -82,12 +80,12 @@ function store_cape(rid, userId, profile, cache_details, callback) {
if (!err && url) {
var cape_hash = get_hash(url);
if (cache_details && cache_details.cape === cape_hash) {
- cache.update_timestamp(rid, userId, cape_hash, false, function(cache_err) {
+ cache.update_timestamp(rid, userId, false, function(cache_err) {
callback(cache_err, cape_hash);
});
} else {
logging.debug(rid, "new cape hash:", cape_hash);
- var capepath = path.join(__dirname, "..", config.directories.capes, cape_hash + ".png");
+ var capepath = path.join(config.directories.capes, cape_hash + ".png");
fs.exists(capepath, function(exists) {
if (exists) {
logging.debug(rid, "cape already exists, not downloading");
@@ -95,10 +93,9 @@ function store_cape(rid, userId, profile, cache_details, callback) {
} else {
networking.get_from(rid, url, function(img, response, net_err) {
if (net_err || !img) {
- logging.error(rid, net_err.stack);
callback(net_err, null);
} else {
- skins.save_image(img, capepath, function(skin_err) {
+ skins.save_image(img, capepath, function(skin_err, skin_img) {
logging.debug(rid, "cape saved");
callback(skin_err, cape_hash);
});
@@ -122,15 +119,18 @@ var requests = {
};
function push_request(userId, type, fun) {
- if (!requests[type][userId]) {
- requests[type][userId] = [];
+ // avoid special properties (e.g. 'constructor')
+ var userId_safe = "!" + userId;
+ if (!requests[type][userId_safe]) {
+ requests[type][userId_safe] = [];
}
- requests[type][userId].push(fun);
+ requests[type][userId_safe].push(fun);
}
// calls back all queued requests that match userId and type
function resume(userId, type, err, hash, slim) {
- var callbacks = requests[type][userId];
+ var userId_safe = "!" + userId;
+ var callbacks = requests[type][userId_safe];
if (callbacks) {
if (callbacks.length > 1) {
logging.debug(callbacks.length, "simultaneous requests for", userId);
@@ -145,17 +145,17 @@ function resume(userId, type, err, hash, slim) {
}
// it's still an empty array
- delete requests[type][userId];
+ delete requests[type][userId_safe];
}
}
// downloads the images for +userId+ while checking the cache
// status based on +cache_details+. +type+ specifies which
// image type should be called back on
-// callback: error, image hash
+// callback: error, image hash, slim
function store_images(rid, userId, cache_details, type, callback) {
var is_uuid = userId.length > 16;
- if (requests[type][userId]) {
+ if (requests[type]["!" + userId]) {
logging.debug(rid, "adding to request queue");
push_request(userId, type, callback);
} else {
@@ -229,8 +229,9 @@ exp.get_image_hash = function(rid, userId, type, callback) {
callback(null, (cached_hash ? 1 : 0), cached_hash, cache_details.slim);
} else {
// download image
- if (cache_details) {
+ if (cache_details && cache_details[type] !== undefined) {
logging.debug(rid, "userId cached, but too old");
+ logging.debug(rid, JSON.stringify(cache_details));
} else {
logging.debug(rid, "userId not cached");
}
@@ -238,7 +239,7 @@ exp.get_image_hash = function(rid, userId, type, callback) {
if (store_err) {
// we might have a cached hash although an error occured
// (e.g. Mojang servers not reachable, using outdated hash)
- cache.update_timestamp(rid, userId, cached_hash, true, function(err2) {
+ cache.update_timestamp(rid, userId, true, function(err2) {
callback(err2 || store_err, -1, cache_details && cached_hash, slim);
});
} else {
@@ -256,16 +257,16 @@ exp.get_image_hash = function(rid, userId, type, callback) {
// handles requests for +userId+ avatars with +size+
// callback: error, status, image buffer, skin hash
-// image is the user's face+helm when helm is true, or the face otherwise
+// image is the user's face+overlay when overlay is true, or the face otherwise
// for status, see get_image_hash
-exp.get_avatar = function(rid, userId, helm, size, callback) {
+exp.get_avatar = function(rid, userId, overlay, size, callback) {
exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) {
if (skin_hash) {
- var facepath = path.join(__dirname, "..", config.directories.faces, skin_hash + ".png");
- var helmpath = path.join(__dirname, "..", config.directories.helms, skin_hash + ".png");
+ var facepath = path.join(config.directories.faces, skin_hash + ".png");
+ var helmpath = path.join(config.directories.helms, skin_hash + ".png");
var filepath = facepath;
fs.exists(helmpath, function(exists) {
- if (helm && exists) {
+ if (overlay && exists) {
filepath = helmpath;
}
skins.resize_img(filepath, size, function(img_err, image) {
@@ -284,11 +285,11 @@ exp.get_avatar = function(rid, userId, helm, size, callback) {
};
// handles requests for +userId+ skins
-// callback: error, skin hash, status, image buffer
+// callback: error, skin hash, status, image buffer, slim
exp.get_skin = function(rid, userId, callback) {
exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) {
if (skin_hash) {
- var skinpath = path.join(__dirname, "..", config.directories.skins, skin_hash + ".png");
+ var skinpath = path.join(config.directories.skins, skin_hash + ".png");
fs.exists(skinpath, function(exists) {
if (exists) {
logging.debug(rid, "skin already exists, not downloading");
@@ -308,22 +309,22 @@ exp.get_skin = function(rid, userId, callback) {
};
// helper method used for file names
-// possible returned names based on +helm+ and +body+ are:
+// possible returned names based on +overlay+ and +body+ are:
// body, bodyhelm, head, headhelm
-function get_type(helm, body) {
+function get_type(overlay, body) {
var text = body ? "body" : "head";
- return helm ? text + "helm" : text;
+ return overlay ? text + "helm" : text;
}
// handles creations of 3D renders
// callback: error, skin hash, image buffer
-exp.get_render = function(rid, userId, scale, helm, body, callback) {
+exp.get_render = function(rid, userId, scale, overlay, body, callback) {
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
if (!skin_hash) {
callback(err, status, skin_hash, null);
return;
}
- var renderpath = path.join(__dirname, "..", config.directories.renders, [skin_hash, scale, get_type(helm, body), slim ? "s" : "t"].join("-") + ".png");
+ var renderpath = path.join(config.directories.renders, [skin_hash, scale, get_type(overlay, body), slim ? "s" : "t"].join("-") + ".png");
fs.exists(renderpath, function(exists) {
if (exists) {
renders.open_render(rid, renderpath, function(render_err, rendered_img) {
@@ -335,17 +336,14 @@ exp.get_render = function(rid, userId, scale, helm, body, callback) {
callback(err, 0, skin_hash, null);
return;
}
- renders.draw_model(rid, img, scale, helm, body, slim, function(draw_err, drawn_img) {
+ renders.draw_model(rid, img, scale, overlay, body, slim, function(draw_err, drawn_img) {
if (draw_err) {
callback(draw_err, -1, skin_hash, null);
} else if (!drawn_img) {
callback(null, 0, skin_hash, null);
} else {
fs.writeFile(renderpath, drawn_img, "binary", function(fs_err) {
- if (fs_err) {
- logging.error(rid, fs_err.stack);
- }
- callback(null, 2, skin_hash, drawn_img);
+ callback(fs_err, 2, skin_hash, drawn_img);
});
}
});
@@ -362,7 +360,7 @@ exp.get_cape = function(rid, userId, callback) {
callback(err, null, status, null);
return;
}
- var capepath = path.join(__dirname, "..", config.directories.capes, cape_hash + ".png");
+ var capepath = path.join(config.directories.capes, cape_hash + ".png");
fs.exists(capepath, function(exists) {
if (exists) {
logging.debug(rid, "cape already exists, not downloading");
diff --git a/lib/logging.js b/lib/logging.js
index b39844f..668bd89 100644
--- a/lib/logging.js
+++ b/lib/logging.js
@@ -1,4 +1,3 @@
-var cluster = require("cluster");
var config = require("../config");
var exp = {};
@@ -18,10 +17,9 @@ function join_args(args) {
function log(level, args, logger) {
logger = logger || console.log;
var time = config.server.log_time ? new Date().toISOString() + " " : "";
- var clid = (cluster.worker && cluster.worker.id || "M");
var lines = join_args(args).split("\n");
for (var i = 0, l = lines.length; i < l; i++) {
- logger(time + clid, level + ":", lines[i]);
+ logger(time, level + ":", lines[i]);
}
}
diff --git a/lib/networking.js b/lib/networking.js
index 0aca57a..cbeba48 100644
--- a/lib/networking.js
+++ b/lib/networking.js
@@ -76,35 +76,56 @@ exp.get_from_options = function(rid, url, options, callback) {
},
timeout: config.server.http_timeout,
followRedirect: false,
- encoding: (options.encoding || null),
+ encoding: options.encoding || null,
}, function(error, response, body) {
// log url + code + description
var code = response && response.statusCode;
- if (error) {
- logging.error(rid, url, error);
- } else {
- var logfunc = code && code < 405 ? logging.debug : logging.warn;
- logfunc(rid, url, code, http_code[code]);
+
+ var logfunc = code && code < 405 ? logging.debug : logging.warn;
+ logfunc(rid, url, code || error && error.code, http_code[code]);
+
+ // not necessarily used
+ var e = new Error(code);
+ e.name = "HTTP";
+ e.code = "HTTPERROR";
+
+ switch (code) {
+ case 200:
+ case 301:
+ case 302: // never seen, but mojang might use it in future
+ case 307: // never seen, but mojang might use it in future
+ case 308: // never seen, but mojang might use it in future
+ // these are okay
+ break;
+ case 204: // no content, used like 404 by mojang. making sure it really has no content
+ case 404:
+ // can be cached as null
+ body = null;
+ break;
+ case 429: // this shouldn't usually happen, but occasionally does
+ case 500:
+ case 503:
+ case 504:
+ // we don't want to cache this
+ error = error || e;
+ body = null;
+ break;
+ default:
+ if (!error) {
+ // Probably 500 or the likes
+ logging.error(rid, "Unexpected response:", code, body);
+ }
+ error = error || e;
+ body = null;
+ break;
}
- // 200 or 301 depending on content type
- if (!error && (code === 200 || code === 301)) {
- // response received successfully
- callback(body, response, null);
- } else if (error) {
- callback(body || null, response, error);
- } else if (code === 404 || code === 204) {
- // page does not exist
- callback(null, response, null);
- } else if (code === 429) {
- // Too Many Requests exception - code 429
- // cause error so the image will not be cached
- callback(body || null, response, (error || "TooManyRequests"));
- } else {
- logging.error(rid, " Unknown reply:");
- logging.error(rid, JSON.stringify(response));
- callback(body || null, response, error);
+ if (body && !body.length) {
+ // empty response
+ body = null;
}
+
+ callback(body, response, error);
});
};
@@ -161,7 +182,18 @@ exp.get_profile = function(rid, uuid, callback) {
callback(null, null);
} else {
exp.get_from_options(rid, session_url + uuid, { encoding: "utf8" }, function(body, response, err) {
- callback(err || null, (body !== null ? JSON.parse(body) : null));
+ try {
+ body = body ? JSON.parse(body) : null;
+ callback(err || null, body);
+ } catch(e) {
+ if (e instanceof SyntaxError) {
+ logging.warn(rid, "Failed to parse JSON", e);
+ logging.debug(rid, body);
+ callback(err || null, null);
+ } else {
+ throw e;
+ }
+ }
});
}
};
@@ -187,11 +219,10 @@ exp.save_texture = function(rid, tex_hash, outpath, callback) {
var textureurl = textures_url + tex_hash;
exp.get_from(rid, textureurl, function(img, response, err) {
if (err) {
- logging.error(rid, "error while downloading texture");
callback(err, response, null);
} else {
- skins.save_image(img, outpath, function(img_err) {
- callback(img_err, response, img);
+ skins.save_image(img, outpath, function(img_err, saved_img) {
+ callback(img_err, response, saved_img);
});
}
});
diff --git a/lib/public/images/akliz.png b/lib/public/images/akliz.png
index 3783439..ba4d2a6 100644
Binary files a/lib/public/images/akliz.png and b/lib/public/images/akliz.png differ
diff --git a/lib/public/images/alex.png b/lib/public/images/mhf_alex.png
similarity index 100%
rename from lib/public/images/alex.png
rename to lib/public/images/mhf_alex.png
diff --git a/lib/public/images/alex_skin.png b/lib/public/images/mhf_alex_skin.png
similarity index 100%
rename from lib/public/images/alex_skin.png
rename to lib/public/images/mhf_alex_skin.png
diff --git a/lib/public/images/steve.png b/lib/public/images/mhf_steve.png
similarity index 100%
rename from lib/public/images/steve.png
rename to lib/public/images/mhf_steve.png
diff --git a/lib/public/images/steve_skin.png b/lib/public/images/mhf_steve_skin.png
similarity index 100%
rename from lib/public/images/steve_skin.png
rename to lib/public/images/mhf_steve_skin.png
diff --git a/lib/public/images/twitter.png b/lib/public/images/twitter.png
deleted file mode 100644
index 39c805a..0000000
Binary files a/lib/public/images/twitter.png and /dev/null differ
diff --git a/lib/public/javascript/crafatar.js b/lib/public/javascript/crafatar.js
new file mode 100644
index 0000000..ee828b8
--- /dev/null
+++ b/lib/public/javascript/crafatar.js
@@ -0,0 +1,60 @@
+var valid_user_id = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
+var xhr = new XMLHttpRequest();
+
+xhr.onload = function() {
+ var response = JSON.parse(xhr.responseText);
+ var status = {};
+ response.map(function(elem) {
+ var key = Object.keys(elem)[0];
+ status[key] = elem[key];
+ });
+
+ var textures = status["textures.minecraft.net"] !== "green";
+ var session = status["sessionserver.mojang.com"] !== "green";
+ var skins = status["skins.minecraft.net"] !== "green";
+ var error = null;
+
+ if (textures || session && skins) {
+ error = "all";
+ } else if (skins) {
+ error = "username";
+ } else if (session) {
+ error = "UUID";
+ }
+
+ if (error) {
+ var warn = document.createElement("div");
+ warn.setAttribute("class", "alert alert-warning");
+ warn.setAttribute("role", "alert");
+ warn.innerHTML = "
Mojang issues Mojang's servers are having trouble right now , this may affect " + error + " requests at Crafatar. check status ";
+ document.querySelector("#alerts").appendChild(warn);
+ }
+};
+
+document.addEventListener("DOMContentLoaded", function(event) {
+ var avatars = document.querySelector("#avatar-wrapper");
+ for (var i = 0; i < avatars.children.length; i++) {
+ // shake 'em on down!
+ // https://stackoverflow.com/a/11972692/2517068
+ avatars.appendChild(avatars.children[Math.random() * i | 0]);
+ }
+
+ var tryit = document.querySelector("#tryit");
+ var tryname = document.querySelector("#tryname");
+ var images = document.querySelectorAll(".tryit");
+ tryit.onsubmit = function(e) {
+ e.preventDefault();
+ tryname.value = tryname.value.trim();
+ var value = tryname.value || "853c80ef3c3749fdaa49938b674adae6";
+ if (!valid_user_id.test(value)) {
+ tryname.value = "";
+ return;
+ }
+ for (var j = 0; j < images.length; j++) {
+ images[j].src = images[j].dataset.src.replace("$", value);
+ }
+ };
+
+ xhr.open("GET", "https://status.mojang.com/check", true);
+ xhr.send();
+});
\ No newline at end of file
diff --git a/lib/public/stylesheets/bootstrap.min.css b/lib/public/stylesheets/bootstrap.min.css
new file mode 100644
index 0000000..e22f474
--- /dev/null
+++ b/lib/public/stylesheets/bootstrap.min.css
@@ -0,0 +1,5 @@
+@charset "UTF-8";/*!
+ * Bootstrap v4.0.0-alpha (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */img,legend{border:0}dl,ol,p,pre,ul{margin-top:0}address,dl,ol,p,ul{margin-bottom:1rem}b,dt,optgroup,strong{font-weight:700}caption,th{text-align:left}fieldset,legend,td,th{padding:0}pre,textarea{overflow:auto}.btn-group>.btn-group,.btn-toolbar .btn-group,.btn-toolbar .input-group,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.dropdown-menu,.table-reflow thead,.table-reflow tr{float:left}.btn,.c-indicator{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal:after,.dropdown-item,.modal-footer:after,.modal-header:after,.nav-tabs:after,.navbar:after,.pager:after,.row:after{clear:both}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{vertical-align:middle}svg:not(:root){overflow:hidden}hr{height:0;-webkit-box-sizing:content-box;box-sizing:content-box}code,kbd,pre,samp{font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}address,legend{line-height:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:none}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}textarea{resize:vertical}table{border-spacing:0;border-collapse:collapse}@media print{blockquote,img,pre,tr{page-break-inside:avoid}*,:after,:before{text-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}blockquote,pre{border:1px solid #999}thead{display:table-header-group}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;box-sizing:border-box;font-size:16px;-webkit-tap-highlight-color:transparent}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}@viewport{width:device-width}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:1rem;line-height:1.5;color:#373a3c;background-color:#fff}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #818a91}address{font-style:normal}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dd,label{margin-bottom:.5rem}dd{margin-left:0}blockquote,figure{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}[role=button]{cursor:pointer}table{background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#818a91;caption-side:bottom}label{display:inline-block}button,input,select,textarea{margin:0;line-height:inherit}fieldset{min-width:0;margin:0;border:0}legend{display:block;width:100%;margin-bottom:.5rem;font-size:1.5rem}.list-inline>li,output{display:inline-block}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;margin-bottom:.5rem}.display-1,.display-2,.display-3,.display-4,.lead{font-weight:300}.blockquote,hr{margin-bottom:1rem}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem}.display-1{font-size:3.5rem}.display-2{font-size:4.5rem}.display-3{font-size:5.5rem}.display-4{font-size:6rem}hr{margin-top:1rem;border:0;border-top:.0625rem solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline{margin-left:-5px}.list-inline>li{padding-right:5px;padding-left:5px}.dl-horizontal{margin-right:-1.875rem;margin-left:-1.875rem}.container,.container-fluid{margin-right:auto;margin-left:auto}.dl-horizontal:after,.dl-horizontal:before{display:table;content:" "}.initialism{font-size:90%;text-transform:uppercase}.blockquote{padding:.5rem 1rem;font-size:1.25rem;border-left:.25rem solid #eceeef}.blockquote ol:last-child,.blockquote p:last-child,.blockquote ul:last-child{margin-bottom:0}.blockquote footer{display:block;font-size:80%;line-height:1.5;color:#818a91}.blockquote footer:before{content:"\2014 \00A0"}.blockquote-reverse{padding-right:1rem;padding-left:0;text-align:right;border-right:.25rem solid #eceeef;border-left:0}.blockquote-reverse footer:before{content:""}.blockquote-reverse footer:after{content:"\00A0 \2014"}.figure{display:inline-block}.figure>img{margin-bottom:.5rem;line-height:1}.table,pre{margin-bottom:1rem}.figure-caption{font-size:90%;color:#818a91}.carousel-inner>.carousel-item>a>img,.carousel-inner>.carousel-item>img,.figure>img,.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:.3rem}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:.25rem;line-height:1.5;background-color:#fff;border:1px solid #ddd;border-radius:.25rem;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}code,kbd{padding:.2rem .4rem;font-size:90%}.img-circle{border-radius:50%}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{color:#bd4147;background-color:#f7f7f9;border-radius:.25rem}kbd{color:#fff;background-color:#333;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:90%;line-height:1.5;color:#373a3c}.container-fluid:after,.container-fluid:before,.container:after,.container:before,.row:after,.row:before{display:table;content:" "}pre code{padding:0;font-size:inherit;color:inherit;background-color:transparent;border-radius:0}.container,.container-fluid{padding-right:.9375rem;padding-left:.9375rem}.pre-scrollable{max-height:340px;overflow-y:scroll}.row{margin-right:-.9375rem;margin-left:-.9375rem}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:.9375rem;padding-left:.9375rem}.col-xs-1{width:8.333333%}.col-xs-2{width:16.666667%}.col-xs-3{width:25%}.col-xs-4{width:33.333333%}.col-xs-5{width:41.666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333%}.col-xs-8{width:66.666667%}.col-xs-9{width:75%}.col-xs-10{width:83.333333%}.col-xs-11{width:91.666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.333333%}.col-xs-pull-2{right:16.666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.333333%}.col-xs-pull-5{right:41.666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.333333%}.col-xs-pull-8{right:66.666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.333333%}.col-xs-pull-11{right:91.666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.333333%}.col-xs-push-2{left:16.666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.333333%}.col-xs-push-5{left:41.666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.333333%}.col-xs-push-8{left:66.666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.333333%}.col-xs-push-11{left:91.666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.333333%}.col-xs-offset-2{margin-left:16.666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.333333%}.col-xs-offset-5{margin-left:41.666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.333333%}.col-xs-offset-8{margin-left:66.666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.333333%}.col-xs-offset-11{margin-left:91.666667%}.col-xs-offset-12{margin-left:100%}@media (min-width:34em){.container{max-width:34rem}.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-1{width:8.333333%}.col-sm-2{width:16.666667%}.col-sm-3{width:25%}.col-sm-4{width:33.333333%}.col-sm-5{width:41.666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333%}.col-sm-8{width:66.666667%}.col-sm-9{width:75%}.col-sm-10{width:83.333333%}.col-sm-11{width:91.666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.333333%}.col-sm-pull-2{right:16.666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.333333%}.col-sm-pull-5{right:41.666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333%}.col-sm-pull-8{right:66.666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.333333%}.col-sm-pull-11{right:91.666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.333333%}.col-sm-push-2{left:16.666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.333333%}.col-sm-push-5{left:41.666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333%}.col-sm-push-8{left:66.666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.333333%}.col-sm-push-11{left:91.666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.333333%}.col-sm-offset-2{margin-left:16.666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.333333%}.col-sm-offset-5{margin-left:41.666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333%}.col-sm-offset-8{margin-left:66.666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.333333%}.col-sm-offset-11{margin-left:91.666667%}.col-sm-offset-12{margin-left:100%}}@media (min-width:48em){.container{max-width:45rem}.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-1{width:8.333333%}.col-md-2{width:16.666667%}.col-md-3{width:25%}.col-md-4{width:33.333333%}.col-md-5{width:41.666667%}.col-md-6{width:50%}.col-md-7{width:58.333333%}.col-md-8{width:66.666667%}.col-md-9{width:75%}.col-md-10{width:83.333333%}.col-md-11{width:91.666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333%}.col-md-pull-2{right:16.666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.333333%}.col-md-pull-5{right:41.666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333%}.col-md-pull-8{right:66.666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.333333%}.col-md-pull-11{right:91.666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333%}.col-md-push-2{left:16.666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.333333%}.col-md-push-5{left:41.666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333%}.col-md-push-8{left:66.666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.333333%}.col-md-push-11{left:91.666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333%}.col-md-offset-2{margin-left:16.666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.333333%}.col-md-offset-5{margin-left:41.666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333%}.col-md-offset-8{margin-left:66.666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.333333%}.col-md-offset-11{margin-left:91.666667%}.col-md-offset-12{margin-left:100%}}@media (min-width:62em){.container{max-width:60rem}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-1{width:8.333333%}.col-lg-2{width:16.666667%}.col-lg-3{width:25%}.col-lg-4{width:33.333333%}.col-lg-5{width:41.666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333%}.col-lg-8{width:66.666667%}.col-lg-9{width:75%}.col-lg-10{width:83.333333%}.col-lg-11{width:91.666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333%}.col-lg-pull-2{right:16.666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.333333%}.col-lg-pull-5{right:41.666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333%}.col-lg-pull-8{right:66.666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.333333%}.col-lg-pull-11{right:91.666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333%}.col-lg-push-2{left:16.666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.333333%}.col-lg-push-5{left:41.666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333%}.col-lg-push-8{left:66.666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.333333%}.col-lg-push-11{left:91.666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333%}.col-lg-offset-2{margin-left:16.666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.333333%}.col-lg-offset-5{margin-left:41.666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333%}.col-lg-offset-8{margin-left:66.666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.333333%}.col-lg-offset-11{margin-left:91.666667%}.col-lg-offset-12{margin-left:100%}}@media (min-width:75em){.container{max-width:72.25rem}.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{float:left}.col-xl-1{width:8.333333%}.col-xl-2{width:16.666667%}.col-xl-3{width:25%}.col-xl-4{width:33.333333%}.col-xl-5{width:41.666667%}.col-xl-6{width:50%}.col-xl-7{width:58.333333%}.col-xl-8{width:66.666667%}.col-xl-9{width:75%}.col-xl-10{width:83.333333%}.col-xl-11{width:91.666667%}.col-xl-12{width:100%}.col-xl-pull-0{right:auto}.col-xl-pull-1{right:8.333333%}.col-xl-pull-2{right:16.666667%}.col-xl-pull-3{right:25%}.col-xl-pull-4{right:33.333333%}.col-xl-pull-5{right:41.666667%}.col-xl-pull-6{right:50%}.col-xl-pull-7{right:58.333333%}.col-xl-pull-8{right:66.666667%}.col-xl-pull-9{right:75%}.col-xl-pull-10{right:83.333333%}.col-xl-pull-11{right:91.666667%}.col-xl-pull-12{right:100%}.col-xl-push-0{left:auto}.col-xl-push-1{left:8.333333%}.col-xl-push-2{left:16.666667%}.col-xl-push-3{left:25%}.col-xl-push-4{left:33.333333%}.col-xl-push-5{left:41.666667%}.col-xl-push-6{left:50%}.col-xl-push-7{left:58.333333%}.col-xl-push-8{left:66.666667%}.col-xl-push-9{left:75%}.col-xl-push-10{left:83.333333%}.col-xl-push-11{left:91.666667%}.col-xl-push-12{left:100%}.col-xl-offset-0{margin-left:0}.col-xl-offset-1{margin-left:8.333333%}.col-xl-offset-2{margin-left:16.666667%}.col-xl-offset-3{margin-left:25%}.col-xl-offset-4{margin-left:33.333333%}.col-xl-offset-5{margin-left:41.666667%}.col-xl-offset-6{margin-left:50%}.col-xl-offset-7{margin-left:58.333333%}.col-xl-offset-8{margin-left:66.666667%}.col-xl-offset-9{margin-left:75%}.col-xl-offset-10{margin-left:83.333333%}.col-xl-offset-11{margin-left:91.666667%}.col-xl-offset-12{margin-left:100%}}.table{width:100%;max-width:100%}.table td,.table th{padding:.75rem;line-height:1.5;vertical-align:top;border-top:1px solid #eceeef}.table thead th{vertical-align:bottom;border-bottom:2px solid #eceeef}.table tbody+tbody{border-top:2px solid #eceeef}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #eceeef}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:#f9f9f9}.table-active,.table-active>td,.table-active>th,.table-hover tbody tr:hover{background-color:#f5f5f5}.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:#e8e8e8}.table-success,.table-success>td,.table-success>th{background-color:#dff0d8}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#d0e9c6}.table-info,.table-info>td,.table-info>th{background-color:#d9edf7}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#c4e3f3}.table-warning,.table-warning>td,.table-warning>th{background-color:#fcf8e3}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#faf2cc}.table-danger,.table-danger>td,.table-danger>th{background-color:#f2dede}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#ebcccc}.table-responsive{display:block;width:100%;overflow-x:auto}.collapsing,.dropdown-divider,.embed-responsive,.modal,.modal-open,.navbar-divider{overflow:hidden}.thead-inverse th{color:#fff;background-color:#373a3c}.thead-default th{color:#55595c;background-color:#eceeef}.table-inverse{color:#eceeef;background-color:#373a3c}.table-inverse.table-bordered{border:0}.table-inverse td,.table-inverse th,.table-inverse thead th{border-color:#55595c}.table-reflow tbody{display:block;white-space:nowrap}.table-reflow td,.table-reflow th{border-top:1px solid #eceeef;border-left:1px solid #eceeef}.table-reflow td:last-child,.table-reflow th:last-child{border-right:1px solid #eceeef}.table-reflow tbody:last-child tr:last-child td,.table-reflow tbody:last-child tr:last-child th,.table-reflow tfoot:last-child tr:last-child td,.table-reflow tfoot:last-child tr:last-child th,.table-reflow thead:last-child tr:last-child td,.table-reflow thead:last-child tr:last-child th{border-bottom:1px solid #eceeef}.table-reflow tr td,.table-reflow tr th{display:block!important;border:1px solid #eceeef}.form-control,.form-control-file,.form-control-range{display:block}.form-control{width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#55595c;background-color:#fff;background-image:none;border:.0625rem solid #ccc;border-radius:.25rem}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{border-color:#66afe9;outline:0}.form-control::-webkit-input-placeholder{color:#999;opacity:1}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999;opacity:1}.form-control::placeholder{color:#999;opacity:1}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#5cb85c}.form-control:disabled,.form-control[readonly],fieldset[disabled] .form-control{background-color:#eceeef;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}.form-control-label{padding:.4375rem .75rem;margin-bottom:0}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:2.375rem}.input-group-sm input[type=date].form-control,.input-group-sm input[type=time].form-control,.input-group-sm input[type=datetime-local].form-control,.input-group-sm input[type=month].form-control,input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:1.95rem}.input-group-lg input[type=date].form-control,.input-group-lg input[type=time].form-control,.input-group-lg input[type=datetime-local].form-control,.input-group-lg input[type=month].form-control,input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:3.291667rem}}.form-control-static{min-height:2.375rem;padding-top:.4375rem;padding-bottom:.4375rem;margin-bottom:0}.form-control-static.form-control-lg,.form-control-static.form-control-sm,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{padding:.275rem .75rem;font-size:.85rem;line-height:1.5;border-radius:.2rem}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{padding:.75rem 1.25rem;font-size:1.25rem;line-height:1.333333;border-radius:.3rem}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-bottom:.75rem}.checkbox label,.checkbox-inline,.radio label,.radio-inline{padding-left:1.25rem;margin-bottom:0;cursor:pointer;font-weight:400}.checkbox label input:only-child,.radio label input:only-child{position:static}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:.25rem;margin-left:-1.25rem}.collapsing,.dropdown,.dropup{position:relative}.checkbox+.checkbox,.radio+.radio{margin-top:-.25rem}.checkbox-inline,.radio-inline{position:relative;display:inline-block;vertical-align:middle}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:.75rem}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox]:disabled,input[type=radio].disabled,input[type=radio]:disabled{cursor:not-allowed}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline{cursor:not-allowed}.form-control-error,.form-control-success,.form-control-warning{padding-right:2.25rem;background-repeat:no-repeat;background-position:center right .59375rem;-webkit-background-size:1.54375rem 1.54375rem;background-size:1.54375rem 1.54375rem}.has-success .form-control{border-color:#5cb85c}.has-success .input-group-addon{color:#5cb85c;background-color:#eaf6ea;border-color:#5cb85c}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .form-control-feedback,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#f0ad4e}.has-success .form-control-success{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkNoZWNrIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDYxMiA3OTIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYxMiA3OTIiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGZpbGw9IiM1Q0I4NUMiIGQ9Ik0yMzMuOCw2MTAuMWMtMTMuMywwLTI1LjktNi4yLTM0LTE2LjlMOTAuNSw0NDguOEM3Ni4zLDQzMCw4MCw0MDMuMyw5OC44LDM4OS4xYzE4LjgtMTQuMyw0NS41LTEwLjUsNTkuOCw4LjNsNzEuOSw5NWwyMjAuOS0yNTAuNWMxMi41LTIwLDM4LjgtMjYuMSw1OC44LTEzLjZjMjAsMTIuNCwyNi4xLDM4LjcsMTMuNiw1OC44TDI3MCw1OTBjLTcuNCwxMi0yMC4yLDE5LjQtMzQuMywyMC4xQzIzNS4xLDYxMC4xLDIzNC41LDYxMC4xLDIzMy44LDYxMC4xeiIvPjwvc3ZnPg==)}.has-warning .form-control{border-color:#f0ad4e}.has-warning .input-group-addon{color:#f0ad4e;background-color:#fff;border-color:#f0ad4e}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .form-control-feedback,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#d9534f}.has-warning .form-control-warning{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9Ildhcm5pbmciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNjEyIDc5MiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNjEyIDc5MiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZmlsbD0iI0YwQUQ0RSIgZD0iTTYwMyw2NDAuMmwtMjc4LjUtNTA5Yy0zLjgtNi42LTEwLjgtMTAuNi0xOC41LTEwLjZzLTE0LjcsNC4xLTE4LjUsMTAuNkw5LDY0MC4yYy0zLjcsNi41LTMuNiwxNC40LDAuMiwyMC44YzMuOCw2LjUsMTAuOCwxMC40LDE4LjMsMTAuNGg1NTcuMWM3LjUsMCwxNC41LTMuOSwxOC4zLTEwLjRDNjA2LjYsNjU0LjYsNjA2LjcsNjQ2LjYsNjAzLDY0MC4yeiBNMzM2LjYsNjEwLjJoLTYxLjJWNTQ5aDYxLjJWNjEwLjJ6IE0zMzYuNiw1MDMuMWgtNjEuMlYzMDQuMmg2MS4yVjUwMy4xeiIvPjwvc3ZnPg==)}.has-error .form-control{border-color:#d9534f}.has-error .input-group-addon{color:#d9534f;background-color:#fdf7f7;border-color:#d9534f}.has-error .form-control-error{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkNyb3NzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDYxMiA3OTIiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYxMiA3OTIiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGZpbGw9IiNEOTUzNEYiIGQ9Ik00NDcsNTQ0LjRjLTE0LjQsMTQuNC0zNy42LDE0LjQtNTEuOSwwTDMwNiw0NTEuN2wtODkuMSw5Mi43Yy0xNC40LDE0LjQtMzcuNiwxNC40LTUxLjksMGMtMTQuNC0xNC40LTE0LjQtMzcuNiwwLTUxLjlsOTIuNC05Ni40TDE2NSwyOTkuNmMtMTQuNC0xNC40LTE0LjQtMzcuNiwwLTUxLjlzMzcuNi0xNC40LDUxLjksMGw4OS4yLDkyLjdsODkuMS05Mi43YzE0LjQtMTQuNCwzNy42LTE0LjQsNTEuOSwwYzE0LjQsMTQuNCwxNC40LDM3LjYsMCw1MS45TDM1NC43LDM5Nmw5Mi40LDk2LjRDNDYxLjQsNTA2LjgsNDYxLjQsNTMwLDQ0Nyw1NDQuNHoiLz48L3N2Zz4=)}.btn-danger-outline,.btn-info-outline,.btn-info.active,.btn-info:active,.btn-primary-outline,.btn-primary.active,.btn-primary:active,.btn-secondary-outline,.btn-secondary.active,.btn-secondary:active,.btn-success-outline,.btn-success.active,.btn-success:active,.btn-warning-outline,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.open>.btn-info.dropdown-toggle,.open>.btn-primary.dropdown-toggle,.open>.btn-secondary.dropdown-toggle,.open>.btn-success.dropdown-toggle,.open>.btn-warning.dropdown-toggle{background-image:none}@media (min-width:34em){.form-inline .form-control-static,.form-inline .form-group{display:inline-block}.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.btn-block,input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.btn{display:inline-block;padding:.375rem 1rem;font-size:1rem;font-weight:400;line-height:1.5;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;user-select:none;border:.0625rem solid transparent;border-radius:.25rem}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{text-decoration:none}.btn.active,.btn:active{outline:0}.btn.disabled,.btn:disabled,fieldset[disabled] .btn{cursor:not-allowed;opacity:.65}a.btn.disaabled,fieldset[disabled] a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#025aa5;border-color:#01549b}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary:disabled.focus,.btn-primary:disabled:focus,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus{background-color:#0275d8;border-color:#0275d8}.btn-primary.disabled:hover,.btn-primary:disabled:hover,fieldset[disabled] .btn-primary:hover{background-color:#0275d8;border-color:#0275d8}.btn-secondary{color:#373a3c;background-color:#fff;border-color:#ccc}.btn-secondary.active,.btn-secondary.focus,.btn-secondary:active,.btn-secondary:focus,.btn-secondary:hover,.open>.btn-secondary.dropdown-toggle{color:#373a3c;background-color:#e6e6e6;border-color:#adadad}.btn-secondary.disabled.focus,.btn-secondary.disabled:focus,.btn-secondary:disabled.focus,.btn-secondary:disabled:focus,fieldset[disabled] .btn-secondary.focus,fieldset[disabled] .btn-secondary:focus{background-color:#fff;border-color:#ccc}.btn-secondary.disabled:hover,.btn-secondary:disabled:hover,fieldset[disabled] .btn-secondary:hover{background-color:#fff;border-color:#ccc}.btn-info{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#2aabd2}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info:disabled.focus,.btn-info:disabled:focus,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus{background-color:#5bc0de;border-color:#5bc0de}.btn-info.disabled:hover,.btn-info:disabled:hover,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#5bc0de}.btn-success{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#419641}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success:disabled.focus,.btn-success:disabled:focus,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus{background-color:#5cb85c;border-color:#5cb85c}.btn-success.disabled:hover,.btn-success:disabled:hover,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#5cb85c}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#eb9316}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning:disabled.focus,.btn-warning:disabled:focus,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus{background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning.disabled:hover,.btn-warning:disabled:hover,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#f0ad4e}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#c12e2a}.btn-danger.active,.btn-danger:active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger:disabled.focus,.btn-danger:disabled:focus,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus{background-color:#d9534f;border-color:#d9534f}.btn-danger.disabled:hover,.btn-danger:disabled:hover,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d9534f}.btn-primary-outline{color:#0275d8;background-color:transparent;border-color:#0275d8}.btn-primary-outline.active,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline:focus,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-primary-outline.disabled.focus,.btn-primary-outline.disabled:focus,.btn-primary-outline:disabled.focus,.btn-primary-outline:disabled:focus,fieldset[disabled] .btn-primary-outline.focus,fieldset[disabled] .btn-primary-outline:focus{border-color:#43a7fd}.btn-primary-outline.disabled:hover,.btn-primary-outline:disabled:hover,fieldset[disabled] .btn-primary-outline:hover{border-color:#43a7fd}.btn-secondary-outline{color:#ccc;background-color:transparent;border-color:#ccc}.btn-secondary-outline.active,.btn-secondary-outline.focus,.btn-secondary-outline:active,.btn-secondary-outline:focus,.btn-secondary-outline:hover,.open>.btn-secondary-outline.dropdown-toggle{color:#fff;background-color:#ccc;border-color:#ccc}.btn-secondary-outline.disabled.focus,.btn-secondary-outline.disabled:focus,.btn-secondary-outline:disabled.focus,.btn-secondary-outline:disabled:focus,fieldset[disabled] .btn-secondary-outline.focus,fieldset[disabled] .btn-secondary-outline:focus{border-color:#fff}.btn-secondary-outline.disabled:hover,.btn-secondary-outline:disabled:hover,fieldset[disabled] .btn-secondary-outline:hover{border-color:#fff}.btn-info-outline{color:#5bc0de;background-color:transparent;border-color:#5bc0de}.btn-info-outline.active,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline:focus,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info-outline.disabled.focus,.btn-info-outline.disabled:focus,.btn-info-outline:disabled.focus,.btn-info-outline:disabled:focus,fieldset[disabled] .btn-info-outline.focus,fieldset[disabled] .btn-info-outline:focus{border-color:#b0e1ef}.btn-info-outline.disabled:hover,.btn-info-outline:disabled:hover,fieldset[disabled] .btn-info-outline:hover{border-color:#b0e1ef}.btn-success-outline{color:#5cb85c;background-color:transparent;border-color:#5cb85c}.btn-success-outline.active,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline:focus,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success-outline.disabled.focus,.btn-success-outline.disabled:focus,.btn-success-outline:disabled.focus,.btn-success-outline:disabled:focus,fieldset[disabled] .btn-success-outline.focus,fieldset[disabled] .btn-success-outline:focus{border-color:#a3d7a3}.btn-success-outline.disabled:hover,.btn-success-outline:disabled:hover,fieldset[disabled] .btn-success-outline:hover{border-color:#a3d7a3}.btn-warning-outline{color:#f0ad4e;background-color:transparent;border-color:#f0ad4e}.btn-warning-outline.active,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline:focus,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning-outline.disabled.focus,.btn-warning-outline.disabled:focus,.btn-warning-outline:disabled.focus,.btn-warning-outline:disabled:focus,fieldset[disabled] .btn-warning-outline.focus,fieldset[disabled] .btn-warning-outline:focus{border-color:#f8d9ac}.btn-warning-outline.disabled:hover,.btn-warning-outline:disabled:hover,fieldset[disabled] .btn-warning-outline:hover{border-color:#f8d9ac}.btn-danger-outline{color:#d9534f;background-color:transparent;border-color:#d9534f}.btn-danger-outline.active,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline:focus,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger-outline.disabled.focus,.btn-danger-outline.disabled:focus,.btn-danger-outline:disabled.focus,.btn-danger-outline:disabled:focus,fieldset[disabled] .btn-danger-outline.focus,fieldset[disabled] .btn-danger-outline:focus{border-color:#eba5a3}.btn-danger-outline.disabled:hover,.btn-danger-outline:disabled:hover,fieldset[disabled] .btn-danger-outline:hover{border-color:#eba5a3}.btn-link{font-weight:400;color:#0275d8;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link:disabled,fieldset[disabled] .btn-link{background-color:transparent}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#014c8c;text-decoration:underline;background-color:transparent}.btn-link:disabled:focus,.btn-link:disabled:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#818a91;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:.75rem 1.25rem;font-size:1.25rem;line-height:1.333333;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .75rem;font-size:.85rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block}.btn-block+.btn-block{margin-top:5px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{height:0;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height;-o-transition-property:height;transition-property:height}.dropdown-toggle:after{display:inline-block;width:0;height:0;margin-left:.25rem;vertical-align:middle;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-left:.3em solid transparent}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:1rem;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-header,.dropdown-item{display:block;padding:3px 20px;line-height:1.5;white-space:nowrap}.dropdown-divider{height:1px;margin:.5rem 0;background-color:#e5e5e5}.dropdown-item{width:100%;font-weight:400;color:#373a3c;text-align:inherit;background:0 0;border:0}.c-indicator,.label,.pager{text-align:center}.dropdown-item:focus,.dropdown-item:hover{color:#2b2d2f;text-decoration:none;background-color:#f5f5f5}.dropdown-item.active,.dropdown-item.active:focus,.dropdown-item.active:hover{color:#fff;text-decoration:none;background-color:#0275d8;outline:0}.dropdown-item.disabled,.dropdown-item.disabled:focus,.dropdown-item.disabled:hover{color:#818a91}.dropdown-item.disabled:focus,.dropdown-item.disabled:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:"progid:DXImageTransform.Microsoft.gradient(enabled = false)"}.c-input,.file{cursor:pointer}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{font-size:.85rem;color:#818a91}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover,.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:.3em solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar:after,.btn-toolbar:before{display:table;content:" "}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn .caret,.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group-lg.btn-group>.btn+.dropdown-toggle,.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group-lg>.btn .caret,.btn-lg .caret{border-width:.3em .3em 0}.dropup .btn-group-lg>.btn .caret,.dropup .btn-lg .caret{border-width:0 .3em .3em}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:.25rem;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:.25rem}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.c-input,.input-group,.input-group-btn,.input-group-btn>.btn{position:relative}.input-group{display:table;border-collapse:separate}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1;color:#55595c;text-align:center;background-color:#eceeef;border:1px solid #ccc;border-radius:.25rem}.alert-link,.close,.label{font-weight:700}.input-group-addon.form-control-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:.275rem .75rem;font-size:.85rem;border-radius:.2rem}.input-group-addon.form-control-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:1.25rem;font-size:1.25rem;border-radius:.3rem}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{font-size:0;white-space:nowrap}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.c-input{display:inline;padding-left:1.5rem;color:#555}.c-input>input{position:absolute;z-index:-1;opacity:0}.c-input>input:checked~.c-indicator{color:#fff;background-color:#0074d9}.c-input>input:active~.c-indicator{color:#fff;background-color:#84c6ff}.c-input+.c-input{margin-left:1rem}.c-indicator{position:absolute;top:0;left:0;display:block;width:1rem;height:1rem;font-size:65%;line-height:1rem;color:#eee;user-select:none;background-color:#eee;background-repeat:no-repeat;background-position:center center;-webkit-background-size:50% 50%;background-size:50% 50%}.c-checkbox .c-indicator{border-radius:.25rem}.c-checkbox input:checked~.c-indicator{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgOCA4IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA4IDgiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTYuNCwxTDUuNywxLjdMMi45LDQuNUwyLjEsMy43TDEuNCwzTDAsNC40bDAuNywwLjdsMS41LDEuNWwwLjcsMC43bDAuNy0wLjdsMy41LTMuNWwwLjctMC43TDYuNCwxTDYuNCwxeiINCgkvPg0KPC9zdmc+DQo=)}.c-checkbox input:indeterminate~.c-indicator{background-color:#0074d9;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iOHB4IiBoZWlnaHQ9IjhweCIgdmlld0JveD0iMCAwIDggOCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOCA4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0wLDN2Mmg4VjNIMHoiLz4NCjwvc3ZnPg0K)}.c-radio .c-indicator{border-radius:50%}.c-radio input:checked~.c-indicator{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgOCA4IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA4IDgiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTQsMUMyLjMsMSwxLDIuMywxLDRzMS4zLDMsMywzczMtMS4zLDMtM1M1LjcsMSw0LDF6Ii8+DQo8L3N2Zz4NCg==)}.c-inputs-stacked .c-input{display:inline}.c-inputs-stacked .c-input:after{display:block;margin-bottom:.25rem;content:""}.c-select,.file{display:inline-block}.c-inputs-stacked .c-input+.c-input{margin-left:0}.c-select{max-width:100%;-webkit-appearance:none;padding:.375rem 1.75rem .375rem .75rem;padding-right:.75rem\9;vertical-align:middle;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC) right .75rem center no-repeat #fff;background-image:none\9;-webkit-background-size:8px 10px;background-size:8px 10px;border:1px solid #ccc;-moz-appearance:none;appearance:none}.c-select:focus{border-color:#51a7e8;outline:0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(81,167,232,.5);box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(81,167,232,.5)}.c-select::-ms-expand{opacity:0}.c-select-sm{padding-top:3px;padding-bottom:3px;font-size:12px}.c-select-sm:not([multiple]){height:26px;min-height:26px}.file{position:relative;height:2.5rem}.file-custom,.file-custom:before{position:absolute;height:2.5rem;padding:.5rem 1rem;line-height:1.5;color:#555}.file input{min-width:14rem;margin:0;filter:alpha(opacity=0);opacity:0}.file-custom{top:0;right:0;left:0;z-index:5;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#fff;border:.075rem solid #ddd;border-radius:.25rem;-webkit-box-shadow:inset 0 .2rem .4rem rgba(0,0,0,.05);box-shadow:inset 0 .2rem .4rem rgba(0,0,0,.05)}.file-custom:after{content:"Choose file..."}.file-custom:before{top:-.075rem;right:-.075rem;bottom:-.075rem;z-index:6;display:block;content:"Browse";background-color:#eee;border:.075rem solid #ddd;border-radius:0 .25rem .25rem 0}.file input:focus~.file-custom{-webkit-box-shadow:0 0 0 .075rem #fff,0 0 0 .2rem #0074d9;box-shadow:0 0 0 .075rem #fff,0 0 0 .2rem #0074d9}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:inline-block}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#818a91}.nav-link.disabled,.nav-link.disabled:focus,.nav-link.disabled:hover{color:#818a91;cursor:not-allowed;background-color:transparent}.nav-inline .nav-link+.nav-link{margin-left:1rem}.nav-pills .nav-item+.nav-item,.nav-tabs .nav-item+.nav-item{margin-left:.2rem}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs:after,.nav-tabs:before{display:table;content:" "}.nav-tabs .nav-item{float:left;margin-bottom:-1px}.nav-tabs .nav-link{display:block;padding:.5em 1em;border:1px solid transparent;border-radius:.25rem .25rem 0 0}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#eceeef #eceeef #ddd}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link.disabled:focus,.nav-tabs .nav-link.disabled:hover{color:#818a91;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover{color:#55595c;background-color:#fff;border-color:#ddd #ddd transparent}.nav-pills .nav-item{float:left}.nav-pills .nav-link{display:block;padding:.5em 1em;border-radius:.25rem}.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover{color:#fff;cursor:default;background-color:#0275d8}.nav-stacked .nav-item{display:block;float:none}.nav-stacked .nav-item+.nav-item{margin-top:.2rem;margin-left:0}.navbar-divider,.navbar-nav .nav-item+.nav-item,.navbar-nav .nav-link+.nav-link{margin-left:1rem}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;padding:.5rem 1rem}.navbar:after,.navbar:before{display:table;content:" "}.navbar-static-top{z-index:1000}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.card,.card-title{margin-bottom:.75rem}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar-sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1030;width:100%}@media (min-width:34em){.navbar{border-radius:.25rem}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top,.navbar-sticky-top{border-radius:0}}.navbar-brand{float:left;padding-top:.25rem;padding-bottom:.25rem;margin-right:1rem;font-size:1.25rem}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}.navbar-divider{float:left;width:1px;padding-top:.425rem;padding-bottom:.425rem;margin-right:1rem}.navbar-divider:before{content:'\00a0'}.navbar-toggler{padding:.5rem .75rem;font-size:1.25rem;line-height:1;background:0 0;border:.0625rem solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}@media (min-width:34em){.navbar-toggleable-xs{display:block!important}}@media (min-width:48em){.navbar-toggleable-sm{display:block!important}}.navbar-nav .nav-item{float:left}.navbar-nav .nav-link{display:block;padding-top:.425rem;padding-bottom:.425rem}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.8)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.6)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .active>.nav-link:focus,.navbar-light .navbar-nav .active>.nav-link:hover,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.active:focus,.navbar-light .navbar-nav .nav-link.active:hover,.navbar-light .navbar-nav .nav-link.open,.navbar-light .navbar-nav .nav-link.open:focus,.navbar-light .navbar-nav .nav-link.open:hover,.navbar-light .navbar-nav .open>.nav-link,.navbar-light .navbar-nav .open>.nav-link:focus,.navbar-light .navbar-nav .open>.nav-link:hover{color:rgba(0,0,0,.8)}.navbar-light .navbar-divider{background-color:rgba(0,0,0,.075)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.card-inverse .card-blockquote,.card-inverse .card-footer,.card-inverse .card-header,.card-inverse .card-title,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .active>.nav-link:focus,.navbar-dark .navbar-nav .active>.nav-link:hover,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.active:focus,.navbar-dark .navbar-nav .nav-link.active:hover,.navbar-dark .navbar-nav .nav-link.open,.navbar-dark .navbar-nav .nav-link.open:focus,.navbar-dark .navbar-nav .nav-link.open:hover,.navbar-dark .navbar-nav .open>.nav-link,.navbar-dark .navbar-nav .open>.nav-link:focus,.navbar-dark .navbar-nav .open>.nav-link:hover{color:#fff}.navbar-dark .navbar-divider{background-color:rgba(255,255,255,.075)}.card{position:relative;border:.0625rem solid #e5e5e5;border-radius:.25rem}.card-block{padding:1.25rem}.card-footer,.card-header{padding:.75rem 1.25rem;background-color:#f5f5f5}.card-title{margin-top:0}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card>.list-group:first-child .list-group-item:first-child{border-radius:.25rem .25rem 0 0}.card>.list-group:last-child .list-group-item:last-child{border-radius:0 0 .25rem .25rem}.card-header{border-bottom:.0625rem solid #e5e5e5}.card-header:first-child{border-radius:.1875rem .1875rem 0 0}.card-footer{border-top:.0625rem solid #e5e5e5}.card-footer:last-child{border-radius:0 0 .1875rem .1875rem}.card-primary{background-color:#0275d8;border-color:#0275d8}.card-success{background-color:#5cb85c;border-color:#5cb85c}.card-info{background-color:#5bc0de;border-color:#5bc0de}.card-warning{background-color:#f0ad4e;border-color:#f0ad4e}.card-danger{background-color:#d9534f;border-color:#d9534f}.card-inverse .card-footer,.card-inverse .card-header{border-bottom:.075rem solid rgba(255,255,255,.2)}.card-inverse .card-blockquote>footer,.card-inverse .card-link,.card-inverse .card-text{color:rgba(255,255,255,.65)}.card-inverse .card-link:focus,.card-inverse .card-link:hover{color:#fff}.card-blockquote{padding:0;margin-bottom:0;border-left:0}.card-img{border-radius:.25rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img-top{border-radius:.25rem .25rem 0 0}.card-img-bottom{border-radius:0 0 .25rem .25rem}.card-deck{display:table;table-layout:fixed;border-spacing:1.25rem 0}.card-deck .card{display:table-cell;width:1%;vertical-align:top}.card-columns .card,.progress{width:100%}.card-deck-wrapper{margin-right:-1.25rem;margin-left:-1.25rem}.card-group{display:table;width:100%;table-layout:fixed}.card-group .card{display:table-cell;vertical-align:top}.breadcrumb>li,.card-columns .card,.pagination{display:inline-block}.card-group .card+.card{margin-left:0;border-left:0}.card-group .card:first-child .card-img-top{border-top-right-radius:0}.card-group .card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group .card:last-child .card-img-top{border-top-left-radius:0}.card-group .card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group .card:not(:first-child):not(:last-child){border-radius:0}.card-group .card:not(:first-child):not(:last-child) .card-img-bottom,.card-group .card:not(:first-child):not(:last-child) .card-img-top{border-radius:0}.breadcrumb,.pagination{border-radius:.25rem;margin-bottom:1rem}.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.breadcrumb{padding:.75rem 1rem;list-style:none;background-color:#eceeef}.breadcrumb>li+li:before{padding-right:.5rem;padding-left:.5rem;color:#818a91;content:"/ "}.breadcrumb>.active{color:#818a91}.pagination{padding-left:0;margin-top:1rem}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:.5rem .75rem;margin-left:-1px;line-height:1.5;color:#0275d8;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#014c8c;background-color:#eceeef;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#0275d8;border-color:#0275d8}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#818a91;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm>li>a,.pagination-sm>li>span{padding:.275rem .75rem;font-size:.85rem;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.pager{padding-left:0;margin-top:1rem;margin-bottom:1rem;list-style:none}.pager:after,.pager:before{display:table;content:" "}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eceeef}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#818a91;cursor:not-allowed;background-color:#fff}.pager-next>a,.pager-next>span{float:right}.pager-prev>a,.pager-prev>span{float:left}.label{display:inline-block;padding:.25em .4em;font-size:75%;line-height:1;color:#fff;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label-pill{padding-right:.6em;padding-left:.6em;border-radius:1rem}.label-default{background-color:#818a91}.label-default[href]:focus,.label-default[href]:hover{background-color:#687077}.label-primary{background-color:#0275d8}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#025aa5}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#eceeef;border-radius:.3rem}.jumbotron-hr{border-top-color:#d0d5d8}@media (min-width:34em){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{padding:15px;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-heading{margin-top:0;color:inherit}.alert-dismissible{padding-right:35px}.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d0e9c6}.alert-success hr{border-top-color:#c1e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.alert-info hr{border-top-color:#a6d5ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faf2cc}.alert-warning hr{border-top-color:#f7ecb5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebcccc}.alert-danger hr{border-top-color:#e4b9b9}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:block;height:1rem;margin-bottom:1rem}.progress[value]{-webkit-appearance:none;color:#0074d9;border:0;-moz-appearance:none;appearance:none}.progress[value]::-webkit-progress-bar{background-color:#eee;border-radius:.25rem}.progress[value]::-webkit-progress-value::before{content:attr(value)}.progress[value]::-webkit-progress-value{background-color:#0074d9;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.progress[value="100"]::-webkit-progress-value{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}@media screen and (min-width:0 \0){.progress{background-color:#eee;border-radius:.25rem}.progress-bar{display:inline-block;height:1rem;text-indent:-999rem;background-color:#0074d9;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.progress[width^="0"]{min-width:2rem;color:#818a91;background-color:transparent;background-image:none}.progress[width="100%"]{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}}.progress-striped[value]::-webkit-progress-value{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:1rem 1rem;background-size:1rem 1rem}.progress-striped[value]::-moz-progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-animated[value]::-webkit-progress-value{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-animated[value]::-moz-progress-bar{animation:progress-bar-stripes 2s linear infinite}.progress-success[value]::-webkit-progress-value{background-color:#5cb85c}.progress-success[value]::-moz-progress-bar{background-color:#5cb85c}@media screen and (min-width:0 \0){.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:1rem 1rem;background-size:1rem 1rem}.progress-animated .progress-bar-striped{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-success .progress-bar{background-color:#5cb85c}}.progress-info[value]::-webkit-progress-value{background-color:#5bc0de}.progress-info[value]::-moz-progress-bar{background-color:#5bc0de}@media screen and (min-width:0 \0){.progress-info .progress-bar{background-color:#5bc0de}}.progress-warning[value]::-webkit-progress-value{background-color:#f0ad4e}.progress-warning[value]::-moz-progress-bar{background-color:#f0ad4e}@media screen and (min-width:0 \0){.progress-warning .progress-bar{background-color:#f0ad4e}}.progress-danger[value]::-webkit-progress-value{background-color:#d9534f}.progress-danger[value]::-moz-progress-bar{background-color:#d9534f}@media screen and (min-width:0 \0){.progress-danger .progress-bar{background-color:#d9534f}}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right{padding-left:10px}.media-left{padding-right:10px}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:0}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-.0625rem;background-color:#fff;border:.0625rem solid #ddd}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-flush .list-group-item{border-width:.0625rem 0;border-radius:0}a.list-group-item,button.list-group-item{width:100%;color:#555;text-align:inherit}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#818a91;cursor:not-allowed;background-color:#eceeef}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#818a91}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#0275d8;border-color:#0275d8}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#a8d6fe}.list-group-item-state{color:#a94442;background-color:#f2dede}a.list-group-item-state,button.list-group-item-state{color:#a94442}a.list-group-item-state .list-group-item-heading,button.list-group-item-state .list-group-item-heading{color:inherit}a.list-group-item-state:focus,a.list-group-item-state:hover,button.list-group-item-state:focus,button.list-group-item-state:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-state.active,a.list-group-item-state.active:focus,a.list-group-item-state.active:hover,button.list-group-item-state.active,button.list-group-item-state.active:focus,button.list-group-item-state.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.embed-responsive{position:relative;display:block;height:0;padding:0}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9{padding-bottom:42.857143%}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.close{float:right;font-size:1.5rem;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-content,.popover{-webkit-background-clip:padding-box}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;-webkit-overflow-scrolling:touch;outline:0}.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before{display:table;content:" "}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.in{opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.5}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.popover,.tooltip{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:.85rem;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;line-break:auto;text-decoration:none}.popover,.tooltip{position:absolute;display:block}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:34em){.modal-dialog{width:600px;margin:30px auto}.modal-sm{width:300px}}@media (min-width:48em){.modal-lg{width:900px}}.tooltip{z-index:1070;text-align:start;opacity:0}.tooltip.in{opacity:.9}.tooltip.bs-tether-element-attached-bottom,.tooltip.tooltip-top{padding:5px 0;margin-top:-3px}.tooltip.bs-tether-element-attached-bottom .tooltip-arrow,.tooltip.tooltip-top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.bs-tether-element-attached-left,.tooltip.tooltip-right{padding:0 5px;margin-left:3px}.tooltip.bs-tether-element-attached-left .tooltip-arrow,.tooltip.tooltip-right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.bs-tether-element-attached-top,.tooltip.tooltip-bottom{padding:5px 0;margin-top:3px}.tooltip.bs-tether-element-attached-top .tooltip-arrow,.tooltip.tooltip-bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bs-tether-element-attached-right,.tooltip.tooltip-left{padding:0 5px;margin-left:-3px}.tooltip.bs-tether-element-attached-right .tooltip-arrow,.tooltip.tooltip-left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{top:0;left:0;z-index:1060;max-width:276px;padding:1px;text-align:start;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.carousel-caption,.carousel-control{color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.text-nowrap,.text-truncate{white-space:nowrap}.popover.bs-tether-element-attached-bottom,.popover.popover-top{margin-top:-10px}.popover.bs-tether-element-attached-bottom .popover-arrow,.popover.popover-top .popover-arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.bs-tether-element-attached-bottom .popover-arrow:after,.popover.popover-top .popover-arrow:after{bottom:1px;margin-left:-10px;content:"";border-top-color:#fff;border-bottom-width:0}.popover.bs-tether-element-attached-left,.popover.popover-right{margin-left:10px}.popover.bs-tether-element-attached-left .popover-arrow,.popover.popover-right .popover-arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.bs-tether-element-attached-left .popover-arrow:after,.popover.popover-right .popover-arrow:after{bottom:-10px;left:1px;content:"";border-right-color:#fff;border-left-width:0}.popover.bs-tether-element-attached-top,.popover.popover-bottom{margin-top:10px}.popover.bs-tether-element-attached-top .popover-arrow,.popover.popover-bottom .popover-arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-top .popover-arrow:after,.popover.popover-bottom .popover-arrow:after{top:1px;margin-left:-10px;content:"";border-top-width:0;border-bottom-color:#fff}.popover.bs-tether-element-attached-right,.popover.popover-left{margin-left:-10px}.popover.bs-tether-element-attached-right .popover-arrow,.popover.popover-left .popover-arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-right .popover-arrow:after,.popover.popover-left .popover-arrow:after{right:1px;bottom:-10px;content:"";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:-.7rem -.7rem 0 0}.popover-content{padding:9px 14px}.popover-arrow,.popover-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.carousel,.carousel-inner{position:relative}.popover-arrow{border-width:11px}.popover-arrow:after{content:"";border-width:10px}.carousel-inner{width:100%;overflow:hidden}.carousel-inner>.carousel-item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.carousel-item>a>img,.carousel-inner>.carousel-item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.carousel-item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.carousel-item.active.right,.carousel-inner>.carousel-item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.carousel-item.active.left,.carousel-inner>.carousel-item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.carousel-item.active,.carousel-inner>.carousel-item.next.left,.carousel-inner>.carousel-item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;opacity:.5}.carousel-control.left{background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:transparent;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px}.carousel-caption .btn,.text-hide{text-shadow:none}@media (min-width:34em){.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .icon-prev{margin-left:-15px}.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:after,.clearfix:before{display:table;content:" "}.center-block{display:block;margin-right:auto;margin-left:auto}.hidden-xl-down,.hidden-xs-up,.visible-print-block,[hidden]{display:none!important}.pull-right{float:right!important}.pull-left{float:left!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.p-r-0,.p-x-0{padding-right:0!important}.p-l-0,.p-x-0{padding-left:0!important}.p-t-0,.p-y-0{padding-top:0!important}.p-b-0,.p-y-0{padding-bottom:0!important}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.m-r-0,.m-x-0{margin-right:0!important}.m-l-0,.m-x-0{margin-left:0!important}.m-t-0,.m-y-0{margin-top:0!important}.m-b-0,.m-y-0{margin-bottom:0!important}.invisible{visibility:hidden}.text-hide{font:"0/0" a;color:transparent;background-color:transparent;border:0}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-truncate{overflow:hidden;text-overflow:ellipsis}.text-xs-left{text-align:left}.text-xs-right{text-align:right}.text-xs-center{text-align:center}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#818a91}.text-primary{color:#0275d8}a.text-primary:focus,a.text-primary:hover{color:#025aa5}.text-success{color:#5cb85c}a.text-success:focus,a.text-success:hover{color:#449d44}.text-info{color:#5bc0de}a.text-info:focus,a.text-info:hover{color:#31b0d5}.text-warning{color:#f0ad4e}a.text-warning:focus,a.text-warning:hover{color:#ec971f}.text-danger{color:#d9534f}a.text-danger:focus,a.text-danger:hover{color:#c9302c}.bg-inverse{color:#eceeef;background-color:#373a3c}.bg-faded{background-color:#f7f7f9}.bg-primary{color:#fff;background-color:#0275d8}a.bg-primary:focus,a.bg-primary:hover{background-color:#025aa5}.bg-success{color:#fff;background-color:#5cb85c}a.bg-success:focus,a.bg-success:hover{background-color:#449d44}.bg-info{color:#fff;background-color:#5bc0de}a.bg-info:focus,a.bg-info:hover{background-color:#31b0d5}.bg-warning{color:#fff;background-color:#f0ad4e}a.bg-warning:focus,a.bg-warning:hover{background-color:#ec971f}.bg-danger{color:#fff;background-color:#d9534f}a.bg-danger:focus,a.bg-danger:hover{background-color:#c9302c}.m-a-0{margin:0!important}.m-r,.m-x{margin-right:1rem!important}.m-l,.m-x{margin-left:1rem!important}.m-t,.m-y{margin-top:1rem!important}.m-b,.m-y{margin-bottom:1rem!important}.m-a{margin:1rem!important}.m-t-md,.m-y-md{margin-top:1.5rem!important}.m-b-md,.m-y-md{margin-bottom:1.5rem!important}.m-x-auto{margin-right:auto!important;margin-left:auto!important}.m-r-md,.m-x-md{margin-right:1.5rem!important}.m-l-md,.m-x-md{margin-left:1.5rem!important}.m-a-md{margin:1.5rem!important}.m-r-lg,.m-x-lg{margin-right:3rem!important}.m-l-lg,.m-x-lg{margin-left:3rem!important}.m-t-lg,.m-y-lg{margin-top:3rem!important}.m-b-lg,.m-y-lg{margin-bottom:3rem!important}.m-a-lg{margin:3rem!important}.p-a-0{padding:0!important}.p-r,.p-x{padding-right:1rem!important}.p-l,.p-x{padding-left:1rem!important}.p-t,.p-y{padding-top:1rem!important}.p-b,.p-y{padding-bottom:1rem!important}.p-a{padding:1rem!important}.p-r-md,.p-x-md{padding-right:1.5rem!important}.p-l-md,.p-x-md{padding-left:1.5rem!important}.p-t-md,.p-y-md{padding-top:1.5rem!important}.p-b-md,.p-y-md{padding-bottom:1.5rem!important}.p-a-md{padding:1.5rem!important}.p-r-lg,.p-x-lg{padding-right:3rem!important}.p-l-lg,.p-x-lg{padding-left:3rem!important}.p-t-lg,.p-y-lg{padding-top:3rem!important}.p-b-lg,.p-y-lg{padding-bottom:3rem!important}.p-a-lg{padding:3rem!important}.pos-f-t{position:fixed;top:0;right:0;left:0;z-index:1030}@media (max-width:33.9em){.hidden-xs-down{display:none!important}}@media (min-width:34em){.text-sm-left{text-align:left}.text-sm-right{text-align:right}.text-sm-center{text-align:center}.hidden-sm-up{display:none!important}}@media (max-width:47.9em){.hidden-sm-down{display:none!important}}@media (min-width:48em){.text-md-left{text-align:left}.text-md-right{text-align:right}.text-md-center{text-align:center}.hidden-md-up{display:none!important}}@media (max-width:61.9em){.hidden-md-down{display:none!important}}@media (min-width:62em){.text-lg-left{text-align:left}.text-lg-right{text-align:right}.text-lg-center{text-align:center}.hidden-lg-up{display:none!important}}@media (max-width:74.9em){.hidden-lg-down{display:none!important}}@media (min-width:75em){.text-xl-left{text-align:left}.text-xl-right{text-align:right}.text-xl-center{text-align:center}.hidden-xl-up{display:none!important}}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}.hidden-print .hidden-print{display:none!important}}
\ No newline at end of file
diff --git a/lib/public/stylesheets/style.css b/lib/public/stylesheets/style.css
index 7d4de14..5dbd90f 100644
--- a/lib/public/stylesheets/style.css
+++ b/lib/public/stylesheets/style.css
@@ -13,11 +13,6 @@ a {
color: #00B7FF;
}
-a.anchor {
- position: relative;
- top: -50px;
-}
-
a.forkme {
top: 0;
right: 0;
@@ -25,14 +20,14 @@ a.forkme {
position: fixed;
display: inline-block;
background: #008000;
- box-shadow: 0 0 5px #000;
color: #fff;
font-weight: bold;
- padding: 3px 40px;
+ padding: 3px 100px;
border: 2px solid #006400;
- -webkit-transform: rotate(45deg) translate(65px);
- transform: rotate(45deg) translate(65px);
+ -webkit-transform: rotate(45deg) translate(108px, -46px);
+ transform: rotate(45deg) translate(108px, -46px);
}
+
a.forkme:hover {
color: #ddd;
text-decoration: none;
@@ -40,60 +35,89 @@ a.forkme:hover {
a.sponsor {
position: fixed;
+ z-index: 1041;
+ width: 48px;
+ height: 48px;
right: 0px;
top: 0px;
- height: 40px;
- width: 40px;
- z-index: 1041;
- margin: 5px 10px;
+ margin: 5px;
}
-.container > .navbar-header {
- display: inline-block;
- margin: inherit;
+.alert {
+ font-size: 1rem;
}
-a.navbar-brand.twitter {
- color: #55acee;
- font-size: 16px;
+#documentation .row {
+ background: #eee;
+ border-radius: 0.25rem;
}
-a.navbar-brand.twitter:before {
- content: "";
- background: url("/images/twitter.png");
- display: inline-block;
- height: 16px;
- width: 16px;
- vertical-align: middle;
+#documentation .row .col-md-2 {
+ text-align: center;
}
-mark.green {
+#documentation .row > div {
+ padding: 15px;
+}
+
+#try input {
+ width: 100%;
+ background: #fff;
+ border: 1px solid #ddd;
+ padding: 0.3em;
+ line-height: 1.5em;
+ margin: 0px;
+}
+
+img.tryit {
+ -webkit-filter: drop-shadow(0px 0px 6px);
+ filter: drop-shadow(0px 0px 6px);
+}
+
+mark {
background: inherit;
- color: #008000;
font-weight: bold;
padding: 0;
}
-thead {
- font-weight: bold;
+mark.green {
+ color: #080;
}
+mark.blue {
+ color: #08f;
+}
+
+span[title] {
+ cursor: help;
+ text-decoration: underline dotted;
+}
+
+
.row {
margin-right: auto;
margin-left: auto;
}
-h1, h2, h3, h4, h5, h6 {
- color: #333;
- font-weight: normal;
+h1, h2, h3, h4, h6 {
+ font-weight: 200;
}
-h3 {
+h1 {
+ font-size: 4rem;
+}
+
+h2 {
margin-top: 2em;
}
-h4 {
- margin-top: 1em;
+h3 {
+ font-size: 1.3rem;
+ margin-top: 2em;
+}
+
+code {
+ word-wrap: break-word;
}
.code {
@@ -110,186 +134,88 @@ h4 {
position: relative;
}
-.code .example {
- cursor: text;
-}
-
-.code .example:hover {
- color: #000;
- text-decoration: underline;
-}
-
-.preview-background {
- background: #eee;
- height: 220px;
-}
-
-.code .example-wrapper .preview, .code .preview-placeholder {
- display: none;
- left: 0;
- right: 0;
- position: absolute;
- bottom: -260px;
- padding-left: 10px;
- height: 220px;
- background-position: 10px center;
- background-repeat: no-repeat;
- font-size: 14px;
- font-family: "Helvetica Neue", Arial, sans-serif;
- font-weight: 300;
- color: #666;
-}
-
-.code .preview-placeholder {
- display: block;
- font-weight: bold;
- line-height: 200px;
-}
-
-.code .preview-placeholder:hover {
- /* fixes glitchy blinking */
- display: block !important;
-}
-
-.code:hover .preview-placeholder {
- display: none;
-}
-
-.code .example-wrapper .preview i {
- color: #aaa;
-}
-
-.code .example-wrapper:hover .preview {
- display: block;
-}
-
-#avatar-example-1:hover .preview {
- background-image: url("/avatars/jeb_");
-}
-#avatar-example-2:hover .preview {
- background-image: url("/avatars/jeb_?helm");
-}
-#avatar-example-3:hover .preview {
- background-image: url("/avatars/jeb_?size=128");
-}
-#avatar-example-4:hover .preview {
- background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6");
-}
-#avatar-example-5:hover .preview {
- background-image: url("/avatars/0?default=alex");
-}
-#avatar-example-6:hover .preview {
- background-image: url("/avatars/0?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png");
-}
-
-#render-example-1:hover .preview {
- background-image: url("/renders/body/jeb_?helm&scale=4");
-}
-#render-example-2:hover .preview {
- background-image: url("/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8");
-}
-
-#skin-example-1:hover .preview {
- background-image: url("/skins/jeb_");
-}
-#skin-example-2:hover .preview {
- background-image: url("/skins/0?default=alex");
-}
-
-#cape-example-1:hover .preview {
- background-image: url("/capes/Dinnerbone");
-}
-#cape-example-2:hover .preview {
- background-image: url("/capes/md_5");
-}
-
-img.preload {
- /*
- preload hover images
- browsers don't load 0x0 images
- */
- position: fixed;
- top: -9999px;
- left: -9999px;
+.jumbotron {
+ padding: 1em 0 3em;
}
.jumbotron img {
margin: 5px;
}
-.avatar-wrapper {
+#avatar-wrapper {
height: 64px;
overflow: hidden;
+ font-size: 0;
}
.avatar {
width: 64px;
height: 64px;
display: inline-block;
- margin-right: 0.5em;
+ margin-right: 6px;
}
.avatar.jomo {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64")}
-.avatar.jomo:hover {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&helm")}
+.avatar.jomo:hover {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&overlay")}
.avatar.jake_0 {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64")}
-.avatar.jake_0:hover {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&helm")}
+.avatar.jake_0:hover {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&overlay")}
.avatar.sk89q {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64")}
-.avatar.sk89q:hover {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&helm")}
+.avatar.sk89q:hover {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&overlay")}
.avatar.md_5 {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64")}
-.avatar.md_5:hover {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64&helm")}
+.avatar.md_5:hover {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64&overlay")}
.avatar.jeb {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64")}
-.avatar.jeb:hover {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm")}
+.avatar.jeb:hover {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&overlay")}
.avatar.notch {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64")}
-.avatar.notch:hover {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64&helm")}
+.avatar.notch:hover {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64&overlay")}
.avatar.dinnerbone {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64")}
-.avatar.dinnerbone:hover {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&helm")}
+.avatar.dinnerbone:hover {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&overlay")}
.avatar.ez {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64")}
-.avatar.ez:hover {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64&helm")}
+.avatar.ez:hover {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64&overlay")}
.avatar.grumm {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64")}
-.avatar.grumm:hover {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&helm")}
+.avatar.grumm:hover {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&overlay")}
.avatar.themogmimer {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64")}
-.avatar.themogmimer:hover {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&helm")}
+.avatar.themogmimer:hover {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&overlay")}
.avatar.marc {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64")}
-.avatar.marc:hover {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64&helm")}
+.avatar.marc:hover {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64&overlay")}
.avatar.searge {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64")}
-.avatar.searge:hover {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&helm")}
+.avatar.searge:hover {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&overlay")}
.avatar.xlson {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64")}
-.avatar.xlson:hover {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&helm")}
+.avatar.xlson:hover {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&overlay")}
.avatar.minecraftchick {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64")}
-.avatar.minecraftchick:hover {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64&helm")}
+.avatar.minecraftchick:hover {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64&overlay")}
.avatar.kappe {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64")}
-.avatar.kappe:hover {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&helm")}
+.avatar.kappe:hover {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&overlay")}
.avatar.krisjelbring {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64")}
-.avatar.krisjelbring:hover {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&helm")}
+.avatar.krisjelbring:hover {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&overlay")}
.avatar.thinkofdeath {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64")}
-.avatar.thinkofdeath:hover {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&helm")}
+.avatar.thinkofdeath:hover {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&overlay")}
.avatar.evilseph {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64")}
-.avatar.evilseph:hover {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64&helm")}
+.avatar.evilseph:hover {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64&overlay")}
.avatar.mollstam {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64")}
-.avatar.mollstam:hover {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&helm")}
+.avatar.mollstam:hover {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&overlay")}
.avatar.mollstam {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64")}
-.avatar.mollstam:hover {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&helm")}
+.avatar.mollstam:hover {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&overlay")}
.avatar.flipped {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
-}
+}
\ No newline at end of file
diff --git a/lib/renders.js b/lib/renders.js
index f30f321..9fe1b9c 100644
--- a/lib/renders.js
+++ b/lib/renders.js
@@ -247,12 +247,7 @@ exp.draw_model = function(rid, img, scale, overlay, is_body, slim, callback) {
// helper method to open a render from +renderpath+
// callback: error, image buffer
exp.open_render = function(rid, renderpath, callback) {
- fs.readFile(renderpath, function(err, buf) {
- if (err) {
- logging.error(rid, "error while opening skin file:", err);
- }
- callback(err, buf);
- });
+ fs.readFile(renderpath, callback);
};
module.exports = exp;
\ No newline at end of file
diff --git a/lib/response.js b/lib/response.js
index 09a703a..561d58f 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -12,6 +12,9 @@ var human_status = {
};
+// print these, but without stacktrace
+var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR"];
+
// handles HTTP responses
// +request+ a http.IncomingMessage
// +response+ a http.ServerResponse
@@ -23,31 +26,36 @@ var human_status = {
// * hash: image hash, required when body is an image
// * err: a possible Error
module.exports = function(request, response, result) {
-
- response.on("close", function() {
- logging.warn(request.id, "Connection closed");
- });
-
- response.on("finish", function() {
- logging.log(request.method, request.url.href, request.id, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
- });
-
- response.on("error", function(err) {
- logging.error(request.id, err);
- });
-
// These headers are the same for every response
var headers = {
- "Content-Type": (result.body && result.type) || "text/plain",
+ "Content-Type": result.body && result.type || "text/plain",
"Cache-Control": "max-age=" + config.caching.browser + ", public",
"Response-Time": Date.now() - request.start,
"X-Request-ID": request.id,
"Access-Control-Allow-Origin": "*"
};
+ response.on("close", function() {
+ logging.warn(request.id, "Connection closed");
+ });
+
+ response.on("finish", function() {
+ logging.log(request.id, request.method, request.url.href, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
+ });
+
+ response.on("error", function(err) {
+ logging.error(request.id, err);
+ });
+
if (result.err) {
- logging.error(request.id, result.err);
- logging.error(request.id, result.err.stack);
+ var silent = silent_errors.indexOf(result.err.code) !== -1;
+ if (result.err.stack && !silent) {
+ logging.error(request.id, result.err.stack);
+ } else if (silent) {
+ logging.warn(request.id, result.err);
+ } else {
+ logging.error(request.id, result.err);
+ }
result.status = -1;
}
diff --git a/lib/routes/avatars.js b/lib/routes/avatars.js
index 0d03a22..5146638 100644
--- a/lib/routes/avatars.js
+++ b/lib/routes/avatars.js
@@ -1,4 +1,3 @@
-var logging = require("../logging");
var helpers = require("../helpers");
var config = require("../../config");
var skins = require("../skins");
@@ -8,7 +7,7 @@ var url = require("url");
function handle_default(img_status, userId, size, def, req, err, callback) {
def = def || skins.default_skin(userId);
- if (def !== "steve" && def !== "alex") {
+ if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") {
if (helpers.id_valid(def)) {
// clean up the old URL to match new image
var parsed = req.url;
@@ -30,6 +29,10 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
}
} else {
// handle steve and alex
+ def = def.toLowerCase();
+ if (def.substr(0, 4) !== "mhf_") {
+ def = "mhf_" + def;
+ }
skins.resize_img(path.join(__dirname, "..", "public", "images", def + ".png"), size, function(resize_err, image) {
callback({
status: img_status,
@@ -47,7 +50,7 @@ module.exports = function(req, callback) {
var userId = (req.url.path_list[1] || "").split(".")[0];
var size = parseInt(req.url.query.size) || config.avatars.default_size;
var def = req.url.query.default;
- var helm = req.url.query.hasOwnProperty("helm");
+ var overlay = req.url.query.hasOwnProperty("overlay") || req.url.query.hasOwnProperty("helm");
// check for extra paths
if (req.url.path_list.length > 2) {
@@ -80,9 +83,8 @@ module.exports = function(req, callback) {
userId = userId.replace(/-/g, "");
try {
- helpers.get_avatar(req.id, userId, helm, size, function(err, status, image, hash) {
+ helpers.get_avatar(req.id, userId, overlay, size, function(err, status, image, hash) {
if (err) {
- logging.error(req.id, err);
if (err.code === "ENOENT") {
// no such file
cache.remove_hash(req.id, userId);
@@ -101,7 +103,6 @@ module.exports = function(req, callback) {
}
});
} catch (e) {
- logging.error(req.id, "error:", e.stack);
handle_default(-1, userId, size, def, req, e, callback);
}
};
\ No newline at end of file
diff --git a/lib/routes/capes.js b/lib/routes/capes.js
index c3f303d..77f1953 100644
--- a/lib/routes/capes.js
+++ b/lib/routes/capes.js
@@ -1,4 +1,3 @@
-var logging = require("../logging");
var helpers = require("../helpers");
var cache = require("../cache");
@@ -32,7 +31,6 @@ module.exports = function(req, callback) {
try {
helpers.get_cape(rid, userId, function(err, hash, status, image) {
if (err) {
- logging.error(rid, err);
if (err.code === "ENOENT") {
// no such file
cache.remove_hash(rid, userId);
diff --git a/lib/routes/index.js b/lib/routes/index.js
index c3d76cf..fc13275 100644
--- a/lib/routes/index.js
+++ b/lib/routes/index.js
@@ -1,11 +1,25 @@
+var logging = require("../logging");
var config = require("../../config");
var path = require("path");
-var jade = require("jade");
+var read = require("fs").readFileSync;
+var ejs = require("ejs");
-// compile jade
-var index = jade.compileFile(path.join(__dirname, "..", "views", "index.jade"));
+var str;
+var index;
+
+function compile() {
+ logging.log("Compiling index page");
+ str = read(path.join(__dirname, "..", "views", "index.html.ejs"), "utf-8");
+ index = ejs.compile(str);
+}
+
+compile();
module.exports = function(req, callback) {
+ if (config.server.debug_enabled) {
+ // allow changes without reloading
+ compile();
+ }
var html = index({
title: "Crafatar",
domain: "https://" + req.headers.host,
diff --git a/lib/routes/renders.js b/lib/routes/renders.js
index 0df771f..6950a11 100644
--- a/lib/routes/renders.js
+++ b/lib/routes/renders.js
@@ -9,10 +9,10 @@ var url = require("url");
var fs = require("fs");
// valid types: head, body
-// helmet is query param
-function handle_default(rid, scale, helm, body, img_status, userId, size, def, req, err, callback) {
+// overlay is query param
+function handle_default(rid, scale, overlay, body, img_status, userId, size, def, req, err, callback) {
def = def || skins.default_skin(userId);
- if (def !== "steve" && def !== "alex") {
+ if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") {
if (helpers.id_valid(def)) {
// clean up the old URL to match new image
var parsed = req.url;
@@ -34,9 +34,13 @@ function handle_default(rid, scale, helm, body, img_status, userId, size, def, r
}
} else {
// handle steve and alex
+ def = def.toLowerCase();
+ if (def.substr(0, 4) !== "mhf_") {
+ def = "mhf_" + def;
+ }
fs.readFile(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(fs_err, buf) {
// we render the default skins, but not custom images
- renders.draw_model(rid, buf, scale, helm, body, def === "alex", function(render_err, def_img) {
+ renders.draw_model(rid, buf, scale, overlay, body, def === "mhf_alex", function(render_err, def_img) {
callback({
status: img_status,
body: def_img,
@@ -57,7 +61,7 @@ module.exports = function(req, callback) {
var userId = (req.url.path_list[2] || "").split(".")[0];
var def = req.url.query.default;
var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
- var helm = req.url.query.hasOwnProperty("helm");
+ var overlay = req.url.query.hasOwnProperty("overlay") || req.url.query.hasOwnProperty("helm");
// check for extra paths
if (req.url.path_list.length > 3) {
@@ -96,9 +100,8 @@ module.exports = function(req, callback) {
userId = userId.replace(/-/g, "");
try {
- helpers.get_render(rid, userId, scale, helm, body, function(err, status, hash, image) {
+ helpers.get_render(rid, userId, scale, overlay, body, function(err, status, hash, image) {
if (err) {
- logging.error(rid, err);
if (err.code === "ENOENT") {
// no such file
cache.remove_hash(rid, userId);
@@ -114,11 +117,10 @@ module.exports = function(req, callback) {
});
} else {
logging.debug(rid, "image not found, using default.");
- handle_default(rid, scale, helm, body, status, userId, scale, def, req, err, callback);
+ handle_default(rid, scale, overlay, body, status, userId, scale, def, req, err, callback);
}
});
} catch(e) {
- logging.error(rid, "error:", e.stack);
- handle_default(rid, scale, helm, body, -1, userId, scale, def, req, e, callback);
+ handle_default(rid, scale, overlay, body, -1, userId, scale, def, req, e, callback);
}
};
\ No newline at end of file
diff --git a/lib/routes/skins.js b/lib/routes/skins.js
index 3a245fa..19280e4 100644
--- a/lib/routes/skins.js
+++ b/lib/routes/skins.js
@@ -1,13 +1,14 @@
var logging = require("../logging");
var helpers = require("../helpers");
var skins = require("../skins");
+var cache = require("../cache");
var path = require("path");
var lwip = require("lwip");
var url = require("url");
function handle_default(img_status, userId, def, req, err, callback) {
def = def || skins.default_skin(userId);
- if (def !== "steve" && def !== "alex") {
+ if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") {
if (helpers.id_valid(def)) {
// clean up the old URL to match new image
var parsed = req.url;
@@ -29,6 +30,10 @@ function handle_default(img_status, userId, def, req, err, callback) {
}
} else {
// handle steve and alex
+ def = def.toLowerCase();
+ if (def.substr(0, 4) !== "mhf_") {
+ def = "mhf_" + def;
+ }
lwip.open(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(lwip_err, image) {
if (image) {
image.toBuffer("png", function(buf_err, buffer) {
@@ -78,9 +83,8 @@ module.exports = function(req, callback) {
userId = userId.replace(/-/g, "");
try {
- helpers.get_skin(rid, userId, function(err, hash, status, image) {
+ helpers.get_skin(rid, userId, function(err, hash, status, image, slim) {
if (err) {
- logging.error(req.id, err);
if (err.code === "ENOENT") {
// no such file
cache.remove_hash(req.id, userId);
@@ -99,7 +103,6 @@ module.exports = function(req, callback) {
}
});
} catch(e) {
- logging.error(rid, "error:", e.stack);
handle_default(-1, userId, def, req, e, callback);
}
};
\ No newline at end of file
diff --git a/lib/server.js b/lib/server.js
index b480a3e..3d2d6be 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -131,18 +131,33 @@ var exp = {};
exp.boot = function(callback) {
var port = process.env.PORT || 3000;
var bind_ip = process.env.BIND || "0.0.0.0";
- logging.log("Server running on http://" + bind_ip + ":" + port + "/");
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
+ logging.log("Server running on http://" + bind_ip + ":" + port + "/");
if (callback) {
callback();
}
});
+
+ // stop accepting new connections,
+ // wait for established connections to finish (30s max),
+ // then exit
+ process.on("SIGTERM", function() {
+ logging.warn("Got SIGTERM, no longer accepting connections!");
+
+ setTimeout(function() {
+ logging.error("Dropping connections after 30s. Force quit.");
+ process.exit(1);
+ }, 30000);
+
+ server.close(function() {
+ logging.log("All connections closed, shutting down.");
+ process.exit();
+ });
+ });
};
exp.close = function(callback) {
- server.close(function() {
- callback();
- });
+ server.close(callback);
};
module.exports = exp;
diff --git a/lib/skins.js b/lib/skins.js
index a302443..a6e1637 100644
--- a/lib/skins.js
+++ b/lib/skins.js
@@ -56,7 +56,7 @@ exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
} else {
face_helm_img.toBuffer("png", {compression: "none"}, function(buf_err2, face_helm_buffer) {
if (buf_err2) {
- callback(buf_err2)
+ callback(buf_err2);
} else {
if (face_helm_buffer.toString() !== face_buffer.toString()) {
face_helm_img.writeFile(outname, function(write_err) {
@@ -101,11 +101,11 @@ exp.resize_img = function(inname, size, callback) {
});
};
-// returns "alex" or "steve" calculated by the +uuid+
+// returns "mhf_alex" or "mhf_steve" calculated by the +uuid+
exp.default_skin = function(uuid) {
if (uuid.length <= 16) {
// we can't get the skin type by username
- return "steve";
+ return "mhf_steve";
} else {
// great thanks to Minecrell for research into Minecraft and Java's UUID hashing!
// https://git.io/xJpV
@@ -117,7 +117,7 @@ exp.default_skin = function(uuid) {
parseInt(uuid[15], 16) ^
parseInt(uuid[23], 16) ^
parseInt(uuid[31], 16);
- return lsbs_even ? "alex" : "steve";
+ return lsbs_even ? "mhf_alex" : "mhf_steve";
}
};
@@ -126,7 +126,6 @@ exp.default_skin = function(uuid) {
exp.open_skin = function(rid, skinpath, callback) {
fs.readFile(skinpath, function(err, buf) {
if (err) {
- logging.error(rid, "error while opening skin file:", err);
callback(err, null);
} else {
callback(null, buf);
@@ -135,18 +134,18 @@ exp.open_skin = function(rid, skinpath, callback) {
};
// write the image +buffer+ to the +outpath+ file
-// callback: error
+// the image is stripped down by lwip.
+// callback: error, image
exp.save_image = function(buffer, outpath, callback) {
lwip.open(buffer, "png", function(err, image) {
if (err) {
- callback(err);
+ callback(err, image);
} else {
- image.batch()
- .writeFile(outpath, function(write_err) {
+ image.writeFile(outpath, function(write_err) {
if (write_err) {
- callback(write_err);
+ callback(write_err, image);
} else {
- callback(null);
+ callback(null, image);
}
});
}
diff --git a/lib/views/index.html.ejs b/lib/views/index.html.ejs
new file mode 100644
index 0000000..bd4630f
--- /dev/null
+++ b/lib/views/index.html.ejs
@@ -0,0 +1,306 @@
+
+
+
+ Crafatar – A blazing fast API for Minecraft faces!
+
+
+ <%# FIXME: Use CDN %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fork me on GitHub
+
+
+
+
Crafatar
+
A blazing fast API for Minecraft faces!
+
+
+ <%# These are shuffled by JS %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Usernames are deprecated!
+ You should only use usernames for
testing .
+ Updates are slower, some features are not available, and it may
break anytime !
+
We strongly advise you to use UUIDs instead of usernames. more info
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= domain %>/avatars/uuid
+
+
Accepted modifiers : size , overlay , default .
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= domain %>/renders/head/uuid
+
+
+ Accepted modifiers : scale , overlay , default .
+ Please note that renders are still beta and have some issues. New renders are in progress !
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= domain %>/renders/body/uuid
+
+
+ Accepted modifiers : scale , overlay , default .
+ Please note that renders are still beta and have some issues. New renders are in progress !
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= domain %>/skins/uuid
+
+
Accepted modifiers : default .
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= domain %>/capes/uuid
+
+
Accepted modifiers : default .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Popular Crafatar users
+
+
See also: what users say about Crafatar
+
+
Crafatar Tools & Plugins
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/views/index.jade b/lib/views/index.jade
deleted file mode 100644
index 251a5c0..0000000
--- a/lib/views/index.jade
+++ /dev/null
@@ -1,397 +0,0 @@
-extends layout
-
-block content
- .jumbotron
- .container
- h1 Crafatar
- p A blazing fast API for Minecraft faces!
- .avatar-wrapper
- .avatar.jomo(title="jomo's avatar")
- .avatar.jake_0(title="jake_0's avatar")
- .avatar.sk89q(title="sk89q's avatar")
- .avatar.md_5(title="md_5's avatar")
- .avatar.notch(title="notch's avatar")
- .avatar.jeb(title="jeb's avatar")
- .avatar.dinnerbone.flipped(title="dinnerbone's avatar")
- .avatar.ez(title="ez' avatar")
- .avatar.grumm.flipped(title="grumm's avatar")
- .avatar.themogmimer(title="themogmimer's avatar")
- .avatar.searge(title="searge's avatar")
- .avatar.xlson(title="xlson's avatar")
- .avatar.krisjelbring(title="krisjelbring's avatar")
- .avatar.minecraftchick(title="minecraftchick's avatar")
- .avatar.kappe(title="kappe's avatar")
- .avatar.marc(title="marc's avatar")
- .avatar.mollstam(title="mollstam's avatar")
- .avatar.evilseph(title="evilseph's avatar")
- .avatar.thinkofdeath(title="thinkofdeath's avatar")
-
- .container
- section(id="documentation")
- h2 Documentation
- .row
- section
- a(id="avatars", class="anchor")
- a(href="#avatars")
- h3 Avatars
- | Replace
- mark.green userid
- | with a Mojang UUID or username to get the related head. All images are PNGs.
- .code
- | #{domain}/avatars/
- mark.green userid
-
- section
- a(id="avatar-parameters" class="anchor")
- a(href="#avatar-parameters")
- h4 Avatar Parameters
- table(class="table table-striped")
- thead
- tr
- td parameter
- td type
- td default
- td description
- tbody
- tr
- td size
- td integer
- td #{config.avatars.default_size}
- td The size of the image in pixels, #{config.avatars.min_size} - #{config.avatars.max_size}.
- tr
- td default
- td string
- td
- | The standard value is calculated based on the UUID (even = alex, odd = steve).
- | Usernames always default to steve.
- td
- | The image to be served when the userid has no skin.
- | Valid options are
- a(href="/avatars/0?default=steve") steve
- | ,
- a(href="/avatars/0?default=alex") alex
- | , or a custom URL.
- tr
- td helm
- td null
- td
- td Apply the "second" layer (hat) to the avatar.
-
- section
- a(id="avatar-examples", class="anchor")
- a(href="#avatar-examples")
- h4 Avatar Examples
- .code
- #avatar-example-1.example-wrapper
- .example #{domain}/avatars/jeb_
- p.preview Jeb's avatar
- #avatar-example-2.example-wrapper
- .example #{domain}/avatars/jeb_?helm
- p.preview Jeb's avatar with helm
- #avatar-example-3.example-wrapper
- .example #{domain}/avatars/jeb_?size=128
- p.preview Jeb's avatar, 128 × 128
- #avatar-example-4.example-wrapper
- .example #{domain}/avatars/853c80ef3c3749fdaa49938b674adae6
- p.preview Jeb's avatar by UUID
- #avatar-example-5.example-wrapper
- .example #{domain}/avatars/jeb_?default=alex
- p.preview Jeb's avatar, or fall back to alex (this example assumes jeb_ does not exist)
- #avatar-example-6.example-wrapper
- .example #{domain}/avatars/jeb_?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png
- p.preview
- | Jeb's avatar, or fall back to a custom image (this example assumes jeb_ does not exist)
- p.preview-placeholder
- | Hover over the example URLs above for a preview!
- .preview-background
-
-
- section
- a(id="renders" class="anchor")
- a(href="#renders")
- h3 3D Renders
- p
- | Crafatar also provides support for 3D renders of Minecraft skins.
- | Please note that this feature is currently beta !
- | Replace
- mark.green userid
- | with a Mojang UUID or username to get a render of the skin.
- | The head render type returns a render of the skin's head.
- span.code
- | #{domain}/renders/head/
- mark.green userid
- | The body render returns a render of the entire skin.
- span.code
- | #{domain}/renders/body/
- mark.green userid
-
- section
- a(id="render-parameters" class="anchor")
- a(href="#render-parameters")
- h4 Render Parameters
- table(class="table table-striped")
- thead
- tr
- td parameter
- td type
- td default
- td description
- tbody
- tr
- td scale
- td integer
- td #{config.renders.default_scale}. The actual size differs between the type of render.
- td The scale factor of the image #{config.renders.min_scale} - #{config.renders.max_scale}.
- tr
- td helm
- td null
- td
- td Apply the "second" layer (hat) to the avatar.
-
- section
- a(id="render-examples", class="anchor")
- a(href="#render-examples")
- h4 Render Examples
- .code
- #render-example-1.example-wrapper
- .example #{domain}/renders/body/jeb_?helm&scale=4
- p.preview Jeb's body, with helmet, scale 4
- #render-example-2.example-wrapper
- .example #{domain}/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8
- p.preview Jeb's head, by UUID, scale 8
- p.preview-placeholder
- | Hover over the example URLs above for a preview!
- .preview-background
-
-
- section
- a(id="skins" class="anchor")
- a(href="#skins")
- h3 Skins
- p
- | You can also get the full skin file of a player.
- | Replace
- mark.green userid
- | with a Mojang UUID or username to get the related skin.
- | The user's skin is returned, or the default image is served.
- | You can use the default parameter here as well.
- span.code
- | #{domain}/skins/
- mark.green userid
-
- section
- a(id="skin-parameters" class="anchor")
- a(href="#skin-parameters")
- h4 Skin Parameters
- table(class="table table-striped")
- thead
- tr
- td parameter
- td type
- td default
- td description
- tbody
- tr
- td default
- td string
- td
- | The standard value is calculated based on the UUID (even = alex, odd = steve).
- | Usernames always default to steve.
- td
- | The image to be served when the userid has no skin.
- | Valid options are
- a(href="/skins/0?default=steve") steve
- | ,
- a(href="/skins/0?default=alex") alex
- | , or a custom URL.
-
- section
- a(id="skin-examples", class="anchor")
- a(href="#skin-examples")
- h4 Skin Examples
- .code
- #skin-example-1.example-wrapper
- .example #{domain}/skins/jeb_
- p.preview Jeb's skin
- #skin-example-2.example-wrapper
- .example #{domain}/skins/jeb_?default=alex
- p.preview Jeb's skin, or fall back to alex (this example assumes jeb_ does not exist)
- p.preview-placeholder
- | Hover over the example URLs above for a preview!
- .preview-background
-
- section
- a(id="capes" class="anchor")
- a(href="#capes")
- h3 Capes
- p
- | A cape endpoint is also available to get the active cape of a user.
- | Replace
- mark.green userid
- | with a Mojang UUID or username to get the related cape.
- | The user's cape is returned, otherwise a 404 is returned.
- .code
- | #{domain}/capes/
- mark.green userid
-
- section
- a(id="cape-examples", class="anchor")
- a(href="#cape-examples")
- h4 Cape Examples
- .code
- #cape-example-1.example-wrapper
- .example #{domain}/capes/Dinnerbone
- p.preview Dinnerbone's Cape Mojang capes are not transparent...
- #cape-example-2.example-wrapper
- .example #{domain}/capes/md_5
- p.preview md_5's Cape
- p.preview-placeholder
- | Hover over the example URLs above for a preview!
- .preview-background
-
- section
- a(id="meta" class="anchor")
- a(href="#meta")
- h2 Meta
-
- section
- a(id="meta-cors" class="anchor")
- a(href="#meta-cors")
- h3 CORS
- p
- | Crafatar supports CORS so you can make AJAX request from within the browser!
-
- section
- a(id="meta-http-headers" class="anchor")
- a(href="#meta-http-headers")
- h3 HTTP Headers
- p
- | Responses come with these HTTP headers, useful for debugging.
- | Please note that these headers are cached by CloudFlare (CF-Cache-Status: HIT) .
-
- section
- a(id="meta-response-time" class="anchor")
- a(href="#meta-response-time")
- h4 Response-Time
- p The time, in milliseconds, it took Crafatar to process the request.
-
- section
- a(id="meta-x-storage-type" class="anchor")
- a(href="#meta-x-storage-type")
- h4 X-Storage-Type
- p Details about how the requested image was stored on the server
- ul
- li none : No external requests. Cached: User has no skin.
- li cached : No external requests. Skin cached and stored locally.
- li
- | checked : 1 external request. Skin cached, checked for updates, no skin downloaded.
- | This happens either when the user removed their skin or when it didn't change.
- li downloaded : 2 external requests. First request or skin changed, skin downloaded.
- li
- | server error : This can happen, for example, when Mojang's servers are down.
- | If possible, a cached image is served instead.
- li
- | user error : You have done something wrong, such as requesting a malformed userid.
- | Check the response body for details.
- section
- a(id="meta-x-request-id" class="anchor")
- a(href="#meta-x-request-id")
- h4 X-Request-ID
- p
- | The internal ID assigned to this request.
- | If you think something is wrong with your request, please contact us and provide this ID.
-
- section
- a(id="meta-about-usernames" class="anchor")
- a(href="#meta-about-usernames")
- h3 About Usernames
- p
- | We strongly advise you to use UUIDs instead of usernames in production.
- | Usernames are deprecated by Mojang and you should only use usernames for testing.
- | You don't have to change anything when using UUIDs and someone changes their Username.
- | Malformed usernames are rejected.
-
- section
- a(id="meta-about-uuids" class="anchor")
- a(href="#meta-about-uuids")
- h3 About UUIDs
- p
- | UUIDs may use the blank or dashed format.
- | Malformed UUIDs are rejected.
-
- section
- a(id="meta-about-caching" class="anchor")
- a(href="#meta-about-caching")
- h3 About Caching
- p
- | Crafatar caches skins for #{config.caching.local/60} minutes before checking for skin changes.
- | Images are cached in your browser for #{config.caching.browser/60} minutes until a new request to Crafatar is made.
- | When you changed your skin you can try clearing your browser cache to see the change faster.
-
-
- section
- a(id="contact" class="anchor")
- a(href="#contact")
- h2 Contact
- ul
- li Follow us on twitter @crafatar
- li Open an issue on GitHub
- li Join us in #crafatar on irc.esper.net
-
- footer
- hr
- p(class="pull-right") Copyright Crafatar #{new Date().getFullYear()}
-
-
- // preload hover images
- img.preload(src="/avatars/020242a17b9441799eff511eea1221da?size=64", alt="preloaded image")
- img.preload(src="/avatars/020242a17b9441799eff511eea1221da?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/069a79f444e94726a5befca90e38aaf5?size=64", alt="preloaded image")
- img.preload(src="/avatars/0?default=alex", alt="preloaded image")
- img.preload(src="/avatars/0?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png", alt="preloaded image")
- img.preload(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64", alt="preloaded image")
- img.preload(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64", alt="preloaded image")
- img.preload(src="/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64", alt="preloaded image")
- img.preload(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64", alt="preloaded image")
- img.preload(src="/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64", alt="preloaded image")
- img.preload(src="/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64", alt="preloaded image")
- img.preload(src="/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64", alt="preloaded image")
- img.preload(src="/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/7d043c7389524696bfba571c05b6aec0?size=64", alt="preloaded image")
- img.preload(src="/avatars/7d043c7389524696bfba571c05b6aec0?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6", alt="preloaded image")
- img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=64", alt="preloaded image")
- img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64", alt="preloaded image")
- img.preload(src="/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64", alt="preloaded image")
- img.preload(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/af74a02d19cb445bb07f6866a861f783?size=64", alt="preloaded image")
- img.preload(src="/avatars/af74a02d19cb445bb07f6866a861f783?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/b05881186e75410db2db4d3066b223f7?size=64", alt="preloaded image")
- img.preload(src="/avatars/b05881186e75410db2db4d3066b223f7?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64", alt="preloaded image")
- img.preload(src="/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/c9b54008fd8047428b238787b5f2401c?size=64", alt="preloaded image")
- img.preload(src="/avatars/c9b54008fd8047428b238787b5f2401c?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64", alt="preloaded image")
- img.preload(src="/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/e6b5c088068044df9e1b9bf11792291b?size=64", alt="preloaded image")
- img.preload(src="/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64", alt="preloaded image")
- img.preload(src="/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&helm", alt="preloaded image")
- img.preload(src="/avatars/jeb_", alt="preloaded image")
- img.preload(src="/avatars/jeb_?helm", alt="preloaded image")
- img.preload(src="/avatars/jeb_?size=128", alt="preloaded image")
- img.preload(src="/capes/Dinnerbone", alt="preloaded image")
- img.preload(src="/capes/md_5", alt="preloaded image")
- img.preload(src="/renders/body/jeb_?helm&scale=4", alt="preloaded image")
- img.preload(src="/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8", alt="preloaded image")
- img.preload(src="/skins/0?default=alex", alt="preloaded image")
- img.preload(src="/skins/jeb_", alt="preloaded image")
\ No newline at end of file
diff --git a/lib/views/layout.jade b/lib/views/layout.jade
deleted file mode 100644
index 0eef6f8..0000000
--- a/lib/views/layout.jade
+++ /dev/null
@@ -1,31 +0,0 @@
-doctype html
-html(lang="en")
- head
- title= title
- link(rel="icon", sizes="16x16", type="image/png", href="/favicon.png")
- link(href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css", rel="stylesheet")
- link(rel="stylesheet", href="/stylesheets/style.css")
-
- meta(name="description", content="Crafatar is a blazing fast Minecraft avatar API with support for avatars, skins, and even 3D renders!")
- meta(name="keywords", content="minecraft, avatar, renders, skins, uuid, username")
- meta(name="viewport", content="initial-scale=1,maximum-scale=1")
-
- meta(charset='utf-8')
- meta(property='og:title', content='Crafatar')
- meta(property='og:type', content='website')
- meta(property='og:url', content='https://crafatar.com')
- meta(property='og:image', content='https://crafatar.com/logo.png')
- meta(property='og:description', content='A blazing fast Minecraft avatar API with support for avatars, skins, and 3D renders.')
-
- meta(name='twitter:card', content='summary')
- meta(name='twitter:creator', content='@Crafatar')
- body
- a.forkme(href="https://github.com/crafatar/crafatar", target="_blank") Fork me on GitHub
- a.sponsor(href="https://akliz.net/crafatar", target="_blank", title="Crafatar is sponsored by Akliz")
- img(src="/images/akliz.png", alt="Akliz")
- .navbar.navbar-default.navbar-fixed-top
- .container
- .navbar-header
- a.navbar-brand(href="/") Crafatar
- a.navbar-brand.twitter(href="https://twitter.com/Crafatar", target="_blank") crafatar
- block content
\ No newline at end of file
diff --git a/logs/.gitkeep b/logs/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/package.json b/package.json
index 6ab201c..fad407f 100644
--- a/package.json
+++ b/package.json
@@ -2,11 +2,15 @@
"name": "crafatar",
"version": "1.0.0",
"private": true,
- "author": "Jake0oo0",
- "description": "A Minecraft avatar service with support for avatars, 1.8 skins, and even 3D renders!",
+ "description": "A blazing fast API for Minecraft faces!",
"contributors": [
{
- "name": "jomo"
+ "name": "jomo",
+ "url": "https://github.com/jomo"
+ },
+ {
+ "name": "Jake",
+ "url": "https://github.com/Jake0oo0"
}
],
"repository": {
@@ -22,7 +26,7 @@
],
"scripts": {
"postinstall": "cp 'config.example.js' 'config.js'",
- "start": "forever -l logs/log.log -o logs/out.log -e logs/error.log -p ./ -a --minUptime 8000 --spinSleepTime 1500 www.js",
+ "start": "node www.js",
"test": "mocha",
"test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
@@ -32,19 +36,18 @@
"dependencies": {
"canvas": "^1.3.4",
"crc": "~3.3.0",
- "forever": "~0.14.2",
- "jade": "~1.11.0",
+ "ejs": "^2.3.4",
"lwip": "~0.0.7",
"mime": "~1.3.4",
- "node-df": "~0.1.1",
- "redis": "~0.12.1",
- "request": "~2.58.0",
+ "node-df": "crafatar/node-df",
+ "redis": "~2.0.0",
+ "request": "~2.64.0",
"toobusy-js": "~0.4.2"
},
"devDependencies": {
"coveralls": "~2.11.2",
- "istanbul": "~0.3.17",
- "mocha": "~2.2.5",
- "mocha-lcov-reporter": "~0.0.2"
+ "istanbul": "~0.3.20",
+ "mocha": "~2.3.3",
+ "mocha-lcov-reporter": "~1.0.0"
}
}
diff --git a/test/bulk.sh b/test/bulk.sh
index 239b32c..a975c83 100755
--- a/test/bulk.sh
+++ b/test/bulk.sh
@@ -25,9 +25,9 @@ bulk() {
trap return INT
echo "$ids" | while read id; do
if [ -z "$async" ]; then
- curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?helm"
+ curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
else
- curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?helm" &
+ curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
sleep "$interval"
fi
done
diff --git a/test/test.js b/test/test.js
index 424ba46..40d0772 100644
--- a/test/test.js
+++ b/test/test.js
@@ -52,7 +52,10 @@ var alex_ids = [
"fffffff1" + "fffffff1" + "fffffff1" + "fffffff0",
];
-var rid = "TestReqID: ";
+// generates a 12 character random string
+function rid() {
+ return Math.random().toString(36).substring(2, 14);
+}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
@@ -120,14 +123,14 @@ describe("Crafatar", function() {
});
it("should not exist (uuid)", function(done) {
var number = getRandomInt(0, 9).toString();
- networking.get_profile(rid, Array(33).join(number), function(err, profile) {
+ networking.get_profile(rid(), Array(33).join(number), function(err, profile) {
assert.ifError(err);
assert.strictEqual(profile, null);
done();
});
});
it("should not exist (username)", function(done) {
- networking.get_username_url(rid, "Steve", 0, function(err, profile) {
+ networking.get_username_url(rid(), "Steve", 0, function(err, profile) {
assert.ifError(err);
done();
});
@@ -136,7 +139,7 @@ describe("Crafatar", function() {
describe("Avatar", function() {
it("uuid's account should exist, but skin should not", function(done) {
// profile "Alex" - hoping it'll never have a skin
- networking.get_profile(rid, "ec561538f3fd461daff5086b22154bce", function(err, profile) {
+ networking.get_profile(rid(), "ec561538f3fd461daff5086b22154bce", function(err, profile) {
assert.ifError(err);
assert.notStrictEqual(profile, null);
networking.get_uuid_info(profile, "CAPE", function(url) {
@@ -145,15 +148,15 @@ describe("Crafatar", function() {
});
});
});
- it("Username should default to Steve", function(done) {
- assert.strictEqual(skins.default_skin("TestUser"), "steve");
+ it("Username should default to MHF_Steve", function(done) {
+ assert.strictEqual(skins.default_skin("TestUser"), "mhf_steve");
done();
});
for (var a in alex_ids) {
var alexid = alex_ids[a];
(function(alex_id) {
- it("UUID " + alex_id + " should default to Alex", function(done) {
- assert.strictEqual(skins.default_skin(alex_id), "alex");
+ it("UUID " + alex_id + " should default to MHF_Alex", function(done) {
+ assert.strictEqual(skins.default_skin(alex_id), "mhf_alex");
done();
});
}(alexid));
@@ -161,8 +164,8 @@ describe("Crafatar", function() {
for (var s in steve_ids) {
var steveid = steve_ids[s];
(function(steve_id) {
- it("UUID " + steve_id + " should default to Steve", function(done) {
- assert.strictEqual(skins.default_skin(steve_id), "steve");
+ it("UUID " + steve_id + " should default to MHF_Steve", function(done) {
+ assert.strictEqual(skins.default_skin(steve_id), "mhf_steve");
done();
});
}(steveid));
@@ -172,7 +175,7 @@ describe("Crafatar", function() {
it("should time out on uuid info download", function(done) {
var original_timeout = config.server.http_timeout;
config.server.http_timeout = 1;
- networking.get_profile(rid, "069a79f444e94726a5befca90e38aaf5", function(err, profile) {
+ networking.get_profile(rid(), "069a79f444e94726a5befca90e38aaf5", function(err, profile) {
assert.strictEqual(err.code, "ETIMEDOUT");
config.server.http_timeout = original_timeout;
done();
@@ -181,7 +184,7 @@ describe("Crafatar", function() {
it("should time out on username info download", function(done) {
var original_timeout = config.server.http_timeout;
config.server.http_timeout = 1;
- networking.get_username_url(rid, "jomo", 0, function(err, url) {
+ networking.get_username_url(rid(), "jomo", 0, function(err, url) {
assert.strictEqual(err.code, "ETIMEDOUT");
config.server.http_timeout = original_timeout;
done();
@@ -190,7 +193,7 @@ describe("Crafatar", function() {
it("should time out on skin download", function(done) {
var original_timeout = config.http_timeout;
config.server.http_timeout = 1;
- networking.get_from(rid, "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
+ networking.get_from(rid(), "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
assert.strictEqual(error.code, "ETIMEDOUT");
config.server.http_timeout = original_timeout;
done();
@@ -198,22 +201,14 @@ describe("Crafatar", function() {
});
it("should not find the skin", function(done) {
assert.doesNotThrow(function() {
- networking.get_from(rid, "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
+ networking.get_from(rid(), "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
done();
});
});
});
- it("should ignore file updates on invalid files", function(done) {
- assert.doesNotThrow(function() {
- cache.update_timestamp(rid, "0123456789abcdef0123456789abcdef", "invalid-file.png", false, function(err) {
- assert.ifError(err);
- done();
- });
- });
- });
it("should not find the file", function(done) {
- skins.open_skin(rid, "non/existent/path", function(err, img) {
+ skins.open_skin(rid(), "non/existent/path", function(err, img) {
assert(err);
done();
});
@@ -309,30 +304,37 @@ describe("Crafatar", function() {
});
it("should not fail on simultaneous requests", function(done) {
- var url = "http://localhost:3000/avatars/696a82ce41f44b51aa31b8709b8686f0";
- // 10 requests at once
- var requests = 10;
- var finished = 0;
- function partDone() {
- finished++;
- if (requests === finished) {
- done();
+ // do not change "constructor" !
+ // it's a reserved property name, we're testing for that
+ var sids = ["696a82ce41f44b51aa31b8709b8686f0", "constructor"];
+
+ for (var j in sids) {
+ var id = sids[j];
+ var url = "http://localhost:3000/avatars/" + id;
+ // 10 requests at once
+ var requests = 10;
+ var finished = 0;
+ function partDone() {
+ finished++;
+ if (requests === finished) {
+ done();
+ }
+ }
+ function req() {
+ request.get(url, function(error, res, body) {
+ assert.ifError(error);
+ assert.strictEqual(res.statusCode, 200);
+ assert_headers(res);
+ assert(res.headers.etag);
+ assert.strictEqual(res.headers["content-type"], "image/png");
+ assert(body);
+ partDone();
+ });
+ }
+ // make simultanous requests
+ for (var k = 0; k < requests; k++) {
+ req(k);
}
- }
- function req() {
- request.get(url, function(error, res, body) {
- assert.ifError(error);
- assert.strictEqual(res.statusCode, 200);
- assert_headers(res);
- assert(res.headers.etag);
- assert.strictEqual(res.headers["content-type"], "image/png");
- assert(body);
- partDone();
- });
- }
- // make simultanous requests
- for (var j = 0; j < requests; j++) {
- req(j);
}
});
@@ -344,12 +346,12 @@ describe("Crafatar", function() {
},
"avatar with non-existent username": {
url: "http://localhost:3000/avatars/0?size=16",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: [2416827277, 1243826040]
},
- "avatar with non-existent username defaulting to alex": {
- url: "http://localhost:3000/avatars/0?size=16&default=alex",
- etag: '"alex"',
+ "avatar with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/avatars/0?size=16&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [862751081, 809395677]
},
"avatar with non-existent username defaulting to username": {
@@ -363,39 +365,39 @@ describe("Crafatar", function() {
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
},
"avatar with non-existent username defaulting to url": {
- url: "http://localhost:3000/avatars/0?size=16&default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/avatars/0?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
- "helm avatar with existing username": {
- url: "http://localhost:3000/avatars/jeb_?size=16&helm",
+ "overlay avatar with existing username": {
+ url: "http://localhost:3000/avatars/jeb_?size=16&overlay",
etag: '"a846b82963"',
crc32: 646871998
},
- "helm avatar with non-existent username": {
- url: "http://localhost:3000/avatars/0?size=16&helm",
- etag: '"steve"',
+ "overlay avatar with non-existent username": {
+ url: "http://localhost:3000/avatars/0?size=16&overlay",
+ etag: '"mhf_steve"',
crc32: [2416827277, 1243826040]
},
- "helm avatar with non-existent username defaulting to alex": {
- url: "http://localhost:3000/avatars/0?size=16&helm&default=alex",
- etag: '"alex"',
+ "overlay avatar with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/avatars/0?size=16&overlay&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [862751081, 809395677]
},
- "helm avatar with non-existent username defaulting to username": {
- url: "http://localhost:3000/avatars/0?size=16&helm&default=jeb_",
+ "overlay avatar with non-existent username defaulting to username": {
+ url: "http://localhost:3000/avatars/0?size=16&overlay&default=jeb_",
crc32: 0,
- redirect: "/avatars/jeb_?size=16&helm="
+ redirect: "/avatars/jeb_?size=16&overlay="
},
- "helm avatar with non-existent username defaulting to uuid": {
- url: "http://localhost:3000/avatars/0?size=16&helm&default=853c80ef3c3749fdaa49938b674adae6",
+ "overlay avatar with non-existent username defaulting to uuid": {
+ url: "http://localhost:3000/avatars/0?size=16&overlay&default=853c80ef3c3749fdaa49938b674adae6",
crc32: 0,
- redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&helm="
+ redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay="
},
- "helm avatar with non-existent username defaulting to url": {
- url: "http://localhost:3000/avatars/0?size=16&helm&default=http%3A%2F%2Fexample.com",
+ "overlay avatar with non-existent username defaulting to url": {
+ url: "http://localhost:3000/avatars/0?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"avatar with existing uuid": {
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
@@ -404,12 +406,12 @@ describe("Crafatar", function() {
},
"avatar with non-existent uuid": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: [2416827277, 1243826040]
},
- "avatar with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=alex",
- etag: '"alex"',
+ "avatar with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [862751081, 809395677]
},
"avatar with non-existent uuid defaulting to username": {
@@ -423,39 +425,39 @@ describe("Crafatar", function() {
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
},
"avatar with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
- "helm avatar with existing uuid": {
- url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&helm",
+ "overlay avatar with existing uuid": {
+ url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
etag: '"a846b82963"',
crc32: 646871998
},
- "helm avatar with non-existent uuid": {
- url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm",
- etag: '"steve"',
+ "overlay avatar with non-existent uuid": {
+ url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
+ etag: '"mhf_steve"',
crc32: [2416827277, 1243826040]
},
- "helm avatar with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm&default=alex",
- etag: '"alex"',
+ "overlay avatar with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [862751081, 809395677]
},
- "helm avatar with non-existent uuid defaulting to username": {
+ "overlay avatar with non-existent uuid defaulting to username": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
crc32: 0,
redirect: "/avatars/jeb_?size=16"
},
- "helm avatar with non-existent uuid defaulting to uuid": {
+ "overlay avatar with non-existent uuid defaulting to uuid": {
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
crc32: 0,
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
},
- "helm avatar with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm&default=http%3A%2F%2Fexample.com",
+ "overlay avatar with non-existent uuid defaulting to url": {
+ url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"cape with existing username": {
url: "http://localhost:3000/capes/jeb_",
@@ -467,9 +469,9 @@ describe("Crafatar", function() {
crc32: 0
},
"cape with non-existent username defaulting to url": {
- url: "http://localhost:3000/capes/0?default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/capes/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"cape with existing uuid": {
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
@@ -481,9 +483,9 @@ describe("Crafatar", function() {
crc32: 0
},
"cape with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/capes/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/capes/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"skin with existing username": {
url: "http://localhost:3000/skins/jeb_",
@@ -492,12 +494,12 @@ describe("Crafatar", function() {
},
"skin with non-existent username": {
url: "http://localhost:3000/skins/0",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: 981937087
},
- "skin with non-existent username defaulting to alex": {
- url: "http://localhost:3000/skins/0?default=alex",
- etag: '"alex"',
+ "skin with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/skins/0?default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: 2298915739
},
"skin with non-existent username defaulting to username": {
@@ -511,9 +513,9 @@ describe("Crafatar", function() {
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16"
},
"skin with non-existent username defaulting to url": {
- url: "http://localhost:3000/skins/0?default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/skins/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"skin with existing uuid": {
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
@@ -522,12 +524,12 @@ describe("Crafatar", function() {
},
"skin with non-existent uuid": {
url: "http://localhost:3000/skins/00000000000000000000000000000000",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: 981937087
},
- "skin with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/skins/00000000000000000000000000000000?default=alex",
- etag: '"alex"',
+ "skin with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: 2298915739
},
"skin with non-existent uuid defaulting to username": {
@@ -541,9 +543,9 @@ describe("Crafatar", function() {
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16"
},
"skin with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"head render with existing username": {
url: "http://localhost:3000/renders/head/jeb_?scale=2",
@@ -552,12 +554,12 @@ describe("Crafatar", function() {
},
"head render with non-existent username": {
url: "http://localhost:3000/renders/head/0?scale=2",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: [3257141069, 214248305]
},
- "head render with non-existent username defaulting to alex": {
- url: "http://localhost:3000/renders/head/0?scale=2&default=alex",
- etag: '"alex"',
+ "head render with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/head/0?scale=2&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [263450586, 3116770561]
},
"head render with non-existent username defaulting to username": {
@@ -571,39 +573,39 @@ describe("Crafatar", function() {
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?scale=2"
},
"head render with non-existent username defaulting to url": {
- url: "http://localhost:3000/renders/head/0?scale=2&default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/renders/head/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
- "helm head render with existing username": {
- url: "http://localhost:3000/renders/head/jeb_?scale=2&helm",
+ "overlay head render with existing username": {
+ url: "http://localhost:3000/renders/head/jeb_?scale=2&overlay",
etag: '"a846b82963"',
crc32: [762377383, 1726474987]
},
- "helm head render with non-existent username": {
- url: "http://localhost:3000/renders/head/0?scale=2&helm",
- etag: '"steve"',
+ "overlay head render with non-existent username": {
+ url: "http://localhost:3000/renders/head/0?scale=2&overlay",
+ etag: '"mhf_steve"',
crc32: [3257141069, 214248305]
},
- "helm head render with non-existent username defaulting to alex": {
- url: "http://localhost:3000/renders/head/0?scale=2&helm&default=alex",
- etag: '"alex"',
+ "overlay head render with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [263450586, 3116770561]
},
- "helm head render with non-existent username defaulting to username": {
- url: "http://localhost:3000/renders/head/0?scale=2&helm&default=jeb_",
+ "overlay head render with non-existent username defaulting to username": {
+ url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=jeb_",
crc32: 0,
- redirect: "/renders/head/jeb_?scale=2&helm="
+ redirect: "/renders/head/jeb_?scale=2&overlay="
},
- "helm head render with non-existent username defaulting to uuid": {
- url: "http://localhost:3000/renders/head/0?scale=2&helm&default=853c80ef3c3749fdaa49938b674adae6",
+ "overlay head render with non-existent username defaulting to uuid": {
+ url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
crc32: 0,
- redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&helm="
+ redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay="
},
- "helm head render with non-existent username defaulting to url": {
- url: "http://localhost:3000/renders/head/0?scale=2&helm&default=http%3A%2F%2Fexample.com",
+ "overlay head render with non-existent username defaulting to url": {
+ url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"head render with existing uuid": {
url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
@@ -612,12 +614,12 @@ describe("Crafatar", function() {
},
"head render with non-existent uuid": {
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: [3257141069, 214248305]
},
- "head render with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=alex",
- etag: '"alex"',
+ "head render with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [263450586, 3116770561]
},
"head render with non-existent uuid defaulting to username": {
@@ -631,39 +633,39 @@ describe("Crafatar", function() {
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2"
},
"head render with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
- "helm head render with existing uuid": {
- url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&helm",
+ "overlay head render with existing uuid": {
+ url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
etag: '"a846b82963"',
crc32: [762377383]
},
- "helm head render with non-existent uuid": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm",
- etag: '"steve"',
+ "overlay head render with non-existent uuid": {
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay",
+ etag: '"mhf_steve"',
crc32: [3257141069, 214248305]
},
- "helm head render with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=alex",
- etag: '"alex"',
+ "overlay head render with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [263450586, 3116770561]
},
- "helm head with non-existent uuid defaulting to username": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=jeb_",
+ "overlay head with non-existent uuid defaulting to username": {
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=jeb_",
crc32: 0,
- redirect: "/renders/head/jeb_?scale=2&helm="
+ redirect: "/renders/head/jeb_?scale=2&overlay="
},
- "helm head with non-existent uuid defaulting to uuid": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=853c80ef3c3749fdaa49938b674adae6",
+ "overlay head with non-existent uuid defaulting to uuid": {
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
crc32: 0,
- redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&helm="
+ redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay="
},
- "helm head render with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=http%3A%2F%2Fexample.com",
+ "overlay head render with non-existent uuid defaulting to url": {
+ url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"body render with existing username": {
url: "http://localhost:3000/renders/body/jeb_?scale=2",
@@ -672,12 +674,12 @@ describe("Crafatar", function() {
},
"body render with non-existent username": {
url: "http://localhost:3000/renders/body/0?scale=2",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: [1046655221, 1620063267]
},
- "body render with non-existent username defaulting to alex": {
- url: "http://localhost:3000/renders/body/0?scale=2&default=alex",
- etag: '"alex"',
+ "body render with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/body/0?scale=2&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [549240598, 3952648540]
},
"body render with non-existent username defaulting to username": {
@@ -691,39 +693,39 @@ describe("Crafatar", function() {
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2"
},
"body render with non-existent username defaulting to url": {
- url: "http://localhost:3000/renders/body/0?scale=2&default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/renders/body/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
- "helm body render with existing username": {
- url: "http://localhost:3000/renders/body/jeb_?scale=2&helm",
+ "overlay body render with existing username": {
+ url: "http://localhost:3000/renders/body/jeb_?scale=2&overlay",
etag: '"a846b82963"',
crc32: [699892097, 2732138694]
},
- "helm body render with non-existent username": {
- url: "http://localhost:3000/renders/body/0?scale=2&helm",
- etag: '"steve"',
+ "overlay body render with non-existent username": {
+ url: "http://localhost:3000/renders/body/0?scale=2&overlay",
+ etag: '"mhf_steve"',
crc32: [1046655221, 1620063267]
},
- "helm body render with non-existent username defaulting to alex": {
- url: "http://localhost:3000/renders/body/0?scale=2&helm&default=alex",
- etag: '"alex"',
+ "overlay body render with non-existent username defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [549240598, 3952648540]
},
- "helm body render with non-existent username defaulting to username": {
- url: "http://localhost:3000/renders/body/0?scale=2&helm&default=jeb_",
+ "overlay body render with non-existent username defaulting to username": {
+ url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=jeb_",
crc32: 0,
- redirect: "/renders/body/jeb_?scale=2&helm="
+ redirect: "/renders/body/jeb_?scale=2&overlay="
},
- "helm body render with non-existent username defaulting to uuid": {
- url: "http://localhost:3000/renders/body/0?scale=2&helm&default=853c80ef3c3749fdaa49938b674adae6",
+ "overlay body render with non-existent username defaulting to uuid": {
+ url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
crc32: 0,
- redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&helm="
+ redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay="
},
- "helm body render with non-existent username defaulting to url": {
- url: "http://localhost:3000/renders/body/0?scale=2&helm&default=http%3A%2F%2Fexample.com",
+ "overlay body render with non-existent username defaulting to url": {
+ url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
"body render with existing uuid": {
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
@@ -732,12 +734,12 @@ describe("Crafatar", function() {
},
"body render with non-existent uuid": {
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
- etag: '"steve"',
+ etag: '"mhf_steve"',
crc32: [1046655221, 1620063267]
},
- "body render with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=alex",
- etag: '"alex"',
+ "body render with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [549240598, 3952648540]
},
"body render with non-existent uuid defaulting to username": {
@@ -751,29 +753,29 @@ describe("Crafatar", function() {
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2"
},
"body render with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com",
+ url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
- "helm body render with existing uuid": {
- url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&helm",
+ "overlay body render with existing uuid": {
+ url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
etag: '"a846b82963"',
crc32: [699892097]
},
- "helm body render with non-existent uuid": {
- url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm",
- etag: '"steve"',
+ "overlay body render with non-existent uuid": {
+ url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
+ etag: '"mhf_steve"',
crc32: [1046655221, 1620063267]
},
- "helm body render with non-existent uuid defaulting to alex": {
- url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm&default=alex",
- etag: '"alex"',
+ "overlay body render with non-existent uuid defaulting to mhf_alex": {
+ url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
+ etag: '"mhf_alex"',
crc32: [549240598, 3952648540]
},
- "helm body render with non-existent uuid defaulting to url": {
- url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm&default=http%3A%2F%2Fexample.com",
+ "overlay body render with non-existent uuid defaulting to url": {
+ url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
crc32: 0,
- redirect: "http://example.com"
+ redirect: "http://example.com/CaseSensitive"
},
};
@@ -800,7 +802,7 @@ describe("Crafatar", function() {
try {
assert.ok(matches);
} catch(e) {
- throw new Error(crc(body) + " != " + location.crc32);
+ throw new Error(crc(body) + " != " + location.crc32 + " | " + body.toString("base64"));
}
assert.strictEqual(res.headers.location, location.redirect);
if (location.etag === undefined) {
@@ -879,13 +881,13 @@ describe("Crafatar", function() {
// we have to make sure that we test both a 32x64 and 64x64 skin
describe("Networking: Render", function() {
it("should not fail (username, 32x64 skin)", function(done) {
- helpers.get_render(rid, "md_5", 6, true, true, function(err, hash, img) {
+ helpers.get_render(rid(), "md_5", 6, true, true, function(err, hash, img) {
assert.strictEqual(err, null);
done();
});
});
it("should not fail (username, 64x64 skin)", function(done) {
- helpers.get_render(rid, "Jake_0", 6, true, true, function(err, hash, img) {
+ helpers.get_render(rid(), "Jake_0", 6, true, true, function(err, hash, img) {
assert.strictEqual(err, null);
done();
});
@@ -894,7 +896,7 @@ describe("Crafatar", function() {
describe("Networking: Cape", function() {
it("should not fail (guaranteed cape)", function(done) {
- helpers.get_cape(rid, "Dinnerbone", function(err, hash, status, img) {
+ helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@@ -903,13 +905,13 @@ describe("Crafatar", function() {
before(function() {
cache.get_redis().flushall();
});
- helpers.get_cape(rid, "Dinnerbone", function(err, hash, status, img) {
+ helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
});
it("should not be found", function(done) {
- helpers.get_cape(rid, "Jake_0", function(err, hash, status, img) {
+ helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
assert.ifError(err);
assert.strictEqual(img, null);
done();
@@ -919,7 +921,7 @@ describe("Crafatar", function() {
describe("Networking: Skin", function() {
it("should not fail", function(done) {
- helpers.get_cape(rid, "Jake_0", function(err, hash, status, img) {
+ helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@@ -928,7 +930,7 @@ describe("Crafatar", function() {
before(function() {
cache.get_redis().flushall();
});
- helpers.get_cape(rid, "Jake_0", function(err, hash, status, img) {
+ helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@@ -949,14 +951,14 @@ describe("Crafatar", function() {
});
it("should be downloaded", function(done) {
- helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
+ helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
assert.ifError(err);
assert.strictEqual(status, 2);
done();
});
});
it("should be cached", function(done) {
- helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
+ helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
assert.ifError(err);
assert.strictEqual(status === 0 || status === 1, true);
done();
@@ -968,7 +970,7 @@ describe("Crafatar", function() {
it("should be checked", function(done) {
var original_cache_time = config.caching.local;
config.caching.local = 0;
- helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
+ helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
assert.ifError(err);
assert.strictEqual(status, 3);
config.caching.local = original_cache_time;
@@ -980,7 +982,7 @@ describe("Crafatar", function() {
describe("Networking: Skin", function() {
it("should not fail (uuid)", function(done) {
- helpers.get_skin(rid, id, function(err, hash, status, img) {
+ helpers.get_skin(rid(), id, function(err, hash, status, img) {
assert.strictEqual(err, null);
done();
});
@@ -989,13 +991,13 @@ describe("Crafatar", function() {
describe("Networking: Render", function() {
it("should not fail (full body)", function(done) {
- helpers.get_render(rid, id, 6, true, true, function(err, hash, img) {
+ helpers.get_render(rid(), id, 6, true, true, function(err, hash, img) {
assert.ifError(err);
done();
});
});
it("should not fail (only head)", function(done) {
- helpers.get_render(rid, id, 6, true, false, function(err, hash, img) {
+ helpers.get_render(rid(), id, 6, true, false, function(err, hash, img) {
assert.ifError(err);
done();
});
@@ -1004,7 +1006,7 @@ describe("Crafatar", function() {
describe("Networking: Cape", function() {
it("should not fail (possible cape)", function(done) {
- helpers.get_cape(rid, id, function(err, hash, status, img) {
+ helpers.get_cape(rid(), id, function(err, hash, status, img) {
assert.ifError(err);
done();
});
@@ -1019,18 +1021,18 @@ describe("Crafatar", function() {
if (id_type === "uuid") {
it("uuid should be rate limited", function(done) {
- networking.get_profile(rid, id, function() {
- networking.get_profile(rid, id, function(err, profile) {
- assert.strictEqual(err, "TooManyRequests");
- assert.strictEqual(profile.error, "TooManyRequestsException");
+ networking.get_profile(rid(), id, function() {
+ networking.get_profile(rid(), id, function(err, profile) {
+ assert.strictEqual(err.toString(), "HTTP: 429");
+ assert.strictEqual(profile, null);
done();
});
});
});
} else {
it("username should NOT be rate limited (username)", function(done) {
- helpers.get_avatar(rid, id, false, 160, function() {
- helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
+ helpers.get_avatar(rid(), id, false, 160, function() {
+ helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
assert.strictEqual(err, null);
done();
});
diff --git a/www.js b/www.js
index 981c14b..2a52daf 100644
--- a/www.js
+++ b/www.js
@@ -1,25 +1,12 @@
var logging = require("./lib/logging");
var cleaner = require("./lib/cleaner");
var config = require("./config");
-var cluster = require("cluster");
-process.on("uncaughtException", function (err) {
+process.on("uncaughtException", function(err) {
logging.error("uncaughtException", err.stack || err.toString());
+ process.exit(1);
});
-if (cluster.isMaster) {
- var cores = config.server.clusters || require("os").cpus().length;
- logging.log("Starting", cores + " worker" + (cores > 1 ? "s" : ""));
- for (var i = 0; i < cores; i++) {
- cluster.fork();
- }
+setInterval(cleaner.run, config.cleaner.interval * 1000);
- cluster.on("exit", function (worker) {
- logging.error("Worker #" + worker.id + " died. Rebooting a new one.");
- setTimeout(cluster.fork, 100);
- });
-
- setInterval(cleaner.run, config.cleaner.interval * 1000);
-} else {
- require("./lib/server.js").boot();
-}
\ No newline at end of file
+require("./lib/server.js").boot();
\ No newline at end of file