refactor(bs3): sync with develop & misc

This commit is contained in:
Matteo Pagliazzi 2014-01-31 20:04:40 +01:00
commit cc348b2f11
37 changed files with 508 additions and 355 deletions

View file

@ -1,3 +1,8 @@
{
"directory": "public/bower_components"
"directory": "public/bower_components",
"storage": {
"packages": ".bower-cache",
"registry": ".bower-registry"
},
"tmp": ".bower-tmp"
}

1
.gitignore vendored
View file

@ -8,6 +8,7 @@ npm-debug.log
lib
public/bower_components
build
newrelic_agent.log
src/*/*.map
src/*/*/*.map

View file

@ -6,3 +6,4 @@ Gruntfile.js
CHANGELOG.md
.idea*
.git*
newrelic_agent.log

View file

@ -13,5 +13,6 @@
"SMTP_SERVICE":"Gmail",
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com"
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com",
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY"
}

View file

@ -0,0 +1 @@
db.users.update({},{$set:{'achievements.habitBirthday':true}},{multi:1})

View file

@ -0,0 +1,12 @@
db.users.update({},{$set:{
'items.food.Cake_Skeleton':1,
'items.food.Cake_Base':1,
'items.food.Cake_CottonCandyBlue':1,
'items.food.Cake_CottonCandyPink':1,
'items.food.Cake_Shade':1,
'items.food.Cake_White':1,
'items.food.Cake_Golden':1,
'items.food.Cake_Zombie':1,
'items.food.Cake_Desert':1,
'items.food.Cake_Red':1
}},{multi:1})

25
newrelic.js Normal file
View file

@ -0,0 +1,25 @@
/**
* New Relic agent configuration.
*
* See lib/config.defaults.js in the agent distribution for a more complete
* description of configuration variables and their potential values.
*/
var nconf = require('nconf')
exports.config = {
/**
* Array of application names.
*/
app_name : ['HabitRPG'],
/**
* Your New Relic license key.
*/
license_key : nconf.get('NEW_RELIC_LICENSE_KEY'),
logging : {
/**
* Level at which to log. 'trace' is most useful to New Relic when diagnosing
* issues with the agent, 'info' and higher will impose the least overhead on
* production applications.
*/
level : 'warning'
}
};

View file

@ -43,7 +43,9 @@
"domain-middleware": "~0.1.0",
"swagger-node-express": "git://github.com/lefnire/swagger-node-express#habitrpg",
"passport": "~0.1.18",
"passport-facebook": "~1.0.2"
"passport-facebook": "~1.0.2",
"newrelic": "~1.3.0",
"connect-ratelimit": "0.0.6"
},
"private": true,
"subdomain": "habitrpg",

View file

@ -24,7 +24,7 @@ api.auth = function(req, res, next) {
var token = req.headers['x-api-key'];
if (!(uid && token)) return res.json(401, NO_TOKEN_OR_UID);
User.findOne({_id: uid,apiToken: token}, function(err, user) {
if (err) return res.json(500, {err: err});
if (err) return next(err);
if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND);
res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true;

View file

@ -147,16 +147,15 @@ api.create = function(req, res, next) {
group.balance = 1;
user.balance--;
user.save(function(err){
if(err) return res.json(500,{err:err});
group.save(function(err, saved){
if (err) return res.json(500,{err:err});
saved.populate('members', nameFields, function(err, populated){
if (err) return res.json(500,{err:err});
return res.json(populated);
});
});
});
async.waterfall([
function(cb){user.save(cb)},
function(saved,ct,cb){group.save(cb)},
function(saved,ct,cb){saved.populate('members',nameFields,cb)}
],function(err,saved){
if (err) return next(err);
res.json(saved);
});
}else{
async.waterfall([
function(cb){
@ -250,7 +249,7 @@ api.deleteChatMessage = function(req, res){
});
}
api.likeChatMessage = function(req, res) {
api.likeChatMessage = function(req, res, next) {
var user = res.locals.user;
var group = res.locals.group;
var message = _.find(group.chat, {id: req.params.mid});
@ -264,6 +263,7 @@ api.likeChatMessage = function(req, res) {
}
group.markModified('chat');
group.save(function(err,_saved){
if (err) return next(err);
return res.send(_saved.chat);
})
}

View file

@ -25,7 +25,7 @@ api.getHeroes = function(req,res,next) {
api.getPatrons = function(req,res,next){
var page = req.query.page || 0,
perPage = 50;
User.find({'backer.tier':{$ne:null}})
User.find({'backer.tier':{$gt:0}})
.select('contributor backer profile.name')
.sort('-backer.tier')
.skip(page*perPage)

View file

@ -166,6 +166,9 @@ acceptablePUTPaths = _.reduce(require('./../models/user').schema.paths, function
if (found) m[leaf]=true;
return m;
}, {})
_.each('stats.gp'.split(' '), function(removePath){
delete acceptablePUTPaths[removePath];
})
/**
* Update user
@ -186,8 +189,8 @@ api.update = function(req, res, next) {
return true;
});
user.save(function(err) {
if (!_.isEmpty(errors)) return res.json(500, {err: errors});
if (err) {return res.json(500, {err: err})}
if (!_.isEmpty(errors)) return res.json(401, {err: errors});
if (err) return res.json(500, {err: err});
res.json(200, user);
});
};
@ -257,6 +260,8 @@ api.addTenGems = function(req, res) {
})
}
// TODO delete plan
/*
Setup Stripe response when posting payment
*/
@ -264,20 +269,39 @@ api.buyGems = function(req, res) {
var api_key = nconf.get('STRIPE_API_KEY');
var stripe = require("stripe")(api_key);
var token = req.body.id;
// console.dir {token:token, req:req}, 'stripe'
var user = res.locals.user;
async.waterfall([
function(cb){
stripe.charges.create({
amount: "500", // $5
currency: "usd",
card: token
}, cb);
if (req.query.plan) {
stripe.customers.create({
email: req.body.email,
metadata: {uuid: res.locals.user._id},
card: token,
plan: req.query.plan,
}, cb);
} else {
stripe.charges.create({
amount: "500", // $5
currency: "usd",
card: token
}, cb);
}
},
function(response, cb) {
res.locals.user.balance += 5;
res.locals.user.purchased.ads = true;
res.locals.user.save(cb);
//user.purchased.ads = true;
if (req.query.plan) {
user.purchased.plan = {
planId:'basic_earned',
customerId: response.id,
dateCreated: new Date,
dateUpdated: new Date,
gemsBought: 0
};
} else {
user.balance += 5;
}
user.save(cb);
}
], function(err, saved){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
@ -285,6 +309,29 @@ api.buyGems = function(req, res) {
});
};
api.cancelSubscription = function(req, res) {
var api_key = nconf.get('STRIPE_API_KEY');
var stripe = require("stripe")(api_key);
var user = res.locals.user;
if (!user.purchased.plan.customerId)
return res.json(401, {err: "User does not have a plan subscription"});
async.waterfall([
function(cb) {
stripe.customers.del(user.purchased.plan.customerId, cb);
},
function(response, cb) {
user.purchased.plan = {};
user.markModified('purchased.plan');
user.save(cb);
}
], function(err, saved){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
res.send(200, saved);
});
}
api.buyGemsPaypalIPN = function(req, res, next) {
res.send(200);
ipn.verify(req.body, function callback(err, msg) {
@ -298,7 +345,7 @@ api.buyGemsPaypalIPN = function(req, res, next) {
if (_.isEmpty(user)) err = "user not found with uuid " + uuid + " when completing paypal transaction";
if (err) return nex(err);
user.balance += 5;
user.purchased.ads = true;
//user.purchased.ads = true;
user.save();
console.log('PayPal transaction completed and user updated');
});
@ -326,6 +373,7 @@ api.cast = function(req, res) {
var targetId = req.query.targetId;
var klass = shared.content.spells.special[req.params.spell] ? 'special' : user.stats.class
var spell = shared.content.spells[klass][req.params.spell];
if (!spell) return res.json(404, {err: 'Spell "' + req.params.spell + '" not found.'});
var done = function(){
var err = arguments[0];
@ -336,6 +384,7 @@ api.cast = function(req, res) {
switch (targetType) {
case 'task':
if (!user.tasks[targetId]) return res.json(404, {err: 'Task "' + targetId + '" not found.'});
spell.cast(user, user.tasks[targetId]);
user.save(done);
break;
@ -415,7 +464,7 @@ _.each(shared.wrap({}).ops, function(op,k){
api.batchUpdate = function(req, res, next) {
if (_.isEmpty(req.body)) req.body = []; // cases of {} or null
if (req.body[0] && req.body[0].data)
return res.json(400, {err: "API has been updated, please refresh your browser or upgrade your mobile app."})
return res.json(501, {err: "API has been updated, please refresh your browser or upgrade your mobile app."})
var user = res.locals.user;
var oldSend = res.send;
@ -423,9 +472,8 @@ api.batchUpdate = function(req, res, next) {
var callOp = function(_req, cb) {
res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 400)
console.error({code: code, data: data});
//FIXME send error messages down
if (_.isNumber(code) && code >= 500)
return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data)));
return cb();
};
api[_req.op](_req, res);
@ -446,7 +494,7 @@ api.batchUpdate = function(req, res, next) {
async.series(ops, function(err) {
res.json = oldJson;
res.send = oldSend;
if (err) return res.json(500, {err: err});
if (err) return next(err);
var response = user.toJSON();
response.wasModified = res.locals.wasModified;

View file

@ -3,6 +3,24 @@ var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var User = require('./models/user').model
var limiter = require('connect-ratelimit');
module.exports.apiThrottle = function(app) {
app.use(limiter({
end:false,
catagories:{
normal: {
// 2 req/s, but split as minutes
totalRequests: 120,
every: 60000
}
}
})).use(function(req,res,next){
//console.log(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
}
module.exports.forceSSL = function(req, res, next){
var baseUrl = nconf.get("BASE_URL");

View file

@ -41,7 +41,8 @@ var UserSchema = new Schema({
quests: Schema.Types.Mixed,
rebirths: Number,
rebirthLevel: Number,
perfect: Number
perfect: Number,
habitBirthday: Boolean
},
auth: {
facebook: Schema.Types.Mixed,
@ -78,6 +79,13 @@ var UserSchema = new Schema({
skin: {type: Schema.Types.Mixed, 'default': {}}, // eg, {skeleton: true, pumpkin: true, eb052b: true}
hair: {type: Schema.Types.Mixed, 'default': {}},
shirt: {type: Schema.Types.Mixed, 'default': {}},
plan: {
planId: String,
customerId: String,
dateCreated: Date,
dateUpdated: Date,
gemsBought: {type: Number, 'default': 0}
}
},
flags: {

View file

@ -304,6 +304,11 @@ module.exports = (swagger, v2) ->
middleware: auth.auth
action:user.buyGems
"/user/cancel-subscription":
spec: method: 'POST', description: "Do not use this route"
middleware: auth.auth
action:user.cancelSubscription
"/user/buy-gems/paypal-ipn":
spec:
method: 'POST'

View file

@ -1,141 +1,148 @@
// Only do the minimal amount of work before forking just in case of a dyno restart
var cluster = require("cluster");
var _ = require('lodash');
var nconf = require('nconf');
var utils = require('./utils');
utils.setupConfig();
if (false && cluster.isMaster && (nconf.get('NODE_ENV') == 'development' || nconf.get('NODE_ENV') == 'production')) {
var numCPUs = require('os').cpus().length;
var isProd = nconf.get('NODE_ENV') === 'production';
var isDev = nconf.get('NODE_ENV') === 'development';
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
if (cluster.isMaster && (isDev || isProd)) {
// Fork workers.
_.times(require('os').cpus().length, function(){
cluster.fork();
})
cluster.on('exit', function(worker, code, signal) {
cluster.fork(); // replace the dead worker
});
cluster.on('exit', function(worker, code, signal) {
var w = cluster.fork(); // replace the dead worker
console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid);
});
} else {
require('coffee-script') // remove this once we've fully converted over
var express = require("express");
var http = require("http");
var path = require("path");
var domainMiddleware = require('domain-middleware');
var swagger = require("swagger-node-express");
require('coffee-script'); // remove this once we've fully converted over
var express = require("express");
var http = require("http");
var path = require("path");
var domainMiddleware = require('domain-middleware');
var swagger = require("swagger-node-express");
var utils = require('./utils');
var middleware = require('./middleware');
var middleware = require('./middleware');
var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
var app = express();
var server;
var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
var app = express();
var server = http.createServer();
// ------------ Setup configurations ------------
utils.setupConfig();
// ------------ MongoDB Configuration ------------
mongoose = require('mongoose');
require('./models/user'); //load up the user schema - TODO is this necessary?
require('./models/group');
require('./models/challenge');
mongoose.connect(nconf.get('NODE_DB_URI'), {auto_reconnect:true}, function(err) {
if (err) throw err;
console.info('Connected with Mongoose');
});
// ------------ MongoDB Configuration ------------
mongoose = require('mongoose');
require('./models/user'); //load up the user schema - TODO is this necessary?
require('./models/group');
require('./models/challenge');
mongoose.connect(nconf.get('NODE_DB_URI'), {auto_reconnect:true}, function(err) {
if (err) throw err;
console.info('Connected with Mongoose');
});
// ------------ Passport Configuration ------------
var passport = require('passport')
var util = require('util')
var FacebookStrategy = require('passport-facebook').Strategy;
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing. However, since this example does not
// have a database of user records, the complete Facebook profile is serialized
// and deserialized.
passport.serializeUser(function(user, done) {
done(null, user);
});
// ------------ Passport Configuration ------------
var passport = require('passport')
var util = require('util')
var FacebookStrategy = require('passport-facebook').Strategy;
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing. However, since this example does not
// have a database of user records, the complete Facebook profile is serialized
// and deserialized.
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
// Use the FacebookStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Facebook
// profile), and invoke a callback with a user object.
passport.use(new FacebookStrategy({
clientID: nconf.get("FACEBOOK_KEY"),
clientSecret: nconf.get("FACEBOOK_SECRET"),
callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
//process.nextTick(function () {
// Use the FacebookStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Facebook
// profile), and invoke a callback with a user object.
passport.use(new FacebookStrategy({
clientID: nconf.get("FACEBOOK_KEY"),
clientSecret: nconf.get("FACEBOOK_SECRET"),
callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
//process.nextTick(function () {
// To keep the example simple, the user's Facebook profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Facebook account with a user record in your database,
// and return that user instead.
return done(null, profile);
//});
}
));
// To keep the example simple, the user's Facebook profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Facebook account with a user record in your database,
// and return that user instead.
return done(null, profile);
//});
}
));
// ------------ Server Configuration ------------
app.set("port", nconf.get('PORT'));
// ------------ Server Configuration ------------
if (!process.env.SUPPRESS) app.use(express.logger("dev"));
app.use(express.compress());
app.set("views", __dirname + "/../views");
app.set("view engine", "jade");
app.use(express.favicon());
app.use(middleware.cors);
app.use(middleware.forceSSL);
app.use(express.urlencoded());
app.use(express.json());
app.use(express.methodOverride());
//app.use(express.cookieParser(nconf.get('SESSION_SECRET')));
app.use(express.cookieParser());
app.use(express.cookieSession({ secret: nconf.get('SESSION_SECRET'), httpOnly: false, cookie: { maxAge: TWO_WEEKS }}));
//app.use(express.session());
domainMiddleware({
server: server,
killTimeout: 3000
}),
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.set("port", nconf.get('PORT'));
app.use(app.router);
middleware.apiThrottle(app);
if (!isProd) app.use(express.logger("dev"));
app.use(express.compress());
app.set("views", __dirname + "/../views");
app.set("view engine", "jade");
app.use(express.favicon());
app.use(middleware.cors);
app.use(middleware.forceSSL);
app.use(express.urlencoded());
app.use(express.json());
app.use(express.methodOverride());
//app.use(express.cookieParser(nconf.get('SESSION_SECRET')));
app.use(express.cookieParser());
app.use(express.cookieSession({ secret: nconf.get('SESSION_SECRET'), httpOnly: false, cookie: { maxAge: TWO_WEEKS }}));
//app.use(express.session());
var maxAge = (nconf.get('NODE_ENV') === 'production') ? 31536000000 : 0;
app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge }));
app.use(express['static'](path.join(__dirname, "/../public")));
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
// development only
//if ("development" === app.get("env")) {
// app.use(express.errorHandler());
//}
app.use(app.router);
// Custom Directives
app.use(require('./routes/pages').middleware);
app.use(require('./routes/auth').middleware);
var v2 = express();
app.use('/api/v2', v2);
app.use('/api/v1', require('./routes/apiv1').middleware);
app.use('/export', require('./routes/dataexport').middleware);
var maxAge = isProd ? 31536000000 : 0;
app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge }));
app.use(express['static'](path.join(__dirname, "/../public")));
app.use(utils.errorHandler);
// development only
//if ("development" === app.get("env")) {
// app.use(express.errorHandler());
//}
require('./routes/apiv2.coffee')(swagger, v2);
// Custom Directives
app.use(require('./routes/pages').middleware);
app.use(require('./routes/auth').middleware);
var v2 = express();
app.use('/api/v2', v2);
app.use('/api/v1', require('./routes/apiv1').middleware);
app.use('/export', require('./routes/dataexport').middleware);
server = http.createServer(app).listen(app.get("port"), function() {
return console.log("Express server listening on port " + app.get("port"));
});
app.use(domainMiddleware({
server: server
//killTimeout: 30000,
}));
}
app.use(utils.errorHandler);
require('./routes/apiv2.coffee')(swagger, v2);
server.on('request', app);
server.listen(app.get("port"), function() {
return console.log("Express server listening on port " + app.get("port"));
});
module.exports = server;
}

View file

@ -56,6 +56,7 @@ module.exports.setupConfig = function(){
if (nconf.get('NODE_ENV') === "development") {
Error.stackTraceLimit = Infinity;
}
if (nconf.get('NODE_ENV') === 'production') require('newrelic');
};
@ -75,7 +76,8 @@ module.exports.errorHandler = function(err, req, res, next) {
text: stack
});
console.error(stack);
var shortMessage = (err.message.length < 200) ? err.message :
err.message.substring(0,100) + err.message.substring(err.message.length-100,err.message.length);
res.json(500,{err:shortMessage}); //res.end(err.message);
var message = err.message ? err.message : err;
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
res.json(500,{err:message}); //res.end(err.message);
process.exit(0);
}

View file

@ -78,24 +78,9 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
.popover-content
p=env.t('welcomeMarket')
p
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')
=env.t('sell')
| &nbsp;{{selectedEgg.text}}&nbsp;
=env.t('for')
| &nbsp;{{selectedEgg.value}}&nbsp;
=env.t('gold')
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')
=env.t('sell')
| &nbsp;{{selectedPotion.text}}&nbsp;
=env.t('for')
| &nbsp;{{selectedPotion.value}}&nbsp;
=env.t('gold')
button.btn.btn-primary(ng-show='selectedFood', ng-click='sellInventory()')
=env.t('sell')
| &nbsp;{{selectedFood.text}}&nbsp;
=env.t('for')
| &nbsp;{{selectedFood.value}}&nbsp;
=env.t('gold')
button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedEgg.text}}", gold: "{{selectedEgg.value}}"})
button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedPotion.text}}", gold: "{{selectedPotion.value}}"})
button.btn.btn-primary(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text}}", gold: "{{selectedFood.value}}"})
menu.inventory-list(type='list')
li.customize-menu
menu.pets-menu(label=env.t('eggs'))
@ -115,7 +100,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
li.customize-menu
menu.pets-menu(label=env.t('food'))
div(ng-repeat='food in Content.food', ng-show='food.key !== "Saddle"')
div(ng-repeat='food in Content.food', ng-if='food.key !== "Saddle" && food.canBuy')
button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", food)', class='Pet_Food_{{food.key}}')
p
| {{food.value}}

View file

@ -10,7 +10,7 @@ script(type='text/ng-template', id='partials/options.inventory.mounts.html')
h3.popover-title
a(target='_blank', href='http://www.kickstarter.com/profile/mattboch')=env.t('mattBoch')
.popover-content
p=env.t('mattShall1') + ' {{user.profile.name}} ' + env.t('mattShall2')
p=env.t('mattShall', {name: "{{user.profile.name}}"})
h4= '{{mountCount}} / {{totalPets}} ' + env.t('mountsTamed')
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Content.eggs')

View file

@ -1,15 +1,15 @@
script(id='partials/options.profile.avatar.html', type='text/ng-template')
.row-fluid
.span4
h3=env.t('bodybody')
h3=env.t('bodyBody')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
= ' ' + env.t('locked')
h5=env.t('bodysize')
h5=env.t('bodySize')
.btn-group
button.btn.btn-small(ng-class='{active: user.preferences.size=="slim"}', ng-click='set({"preferences.size":"slim"})')=env.t('bodyslim')
button.btn.btn-small(ng-class='{active: user.preferences.size=="broad"}', ng-click='set({"preferences.size":"broad"})')=env.t('bodybroad')
button.btn.btn-small(ng-class='{active: user.preferences.size=="slim"}', ng-click='set({"preferences.size":"slim"})')=env.t('bodySlim')
button.btn.btn-small(ng-class='{active: user.preferences.size=="broad"}', ng-click='set({"preferences.size":"broad"})')=env.t('bodyBroad')
menu(type='list')
li.customize-menu
@ -17,15 +17,15 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
each shirt in ['black', 'blue', 'green', 'pink', 'white', 'yellow']
button.customize-option(class='{{user.preferences.size}}_shirt_'+shirt, type='button', ng-click='set({"preferences.shirt":"'+shirt+'"})')
menu(label=env.t('specialshirts'))
menu(label=env.t('specialShirts'))
each shirt in ['convict', 'cross', 'fire', 'horizon', 'ocean', 'purple', 'rainbow', 'redblue', 'thunder', 'tropical', 'zombie']
button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")')
menu
button.btn.btn-small.btn-primary(ng-hide="user.purchased.shirt.convict && user.purchased.shirt.cross && user.purchased.shirt.fire && user.purchased.shirt.horizon && user.purchased.shirt.ocean && user.purchased.shirt.purple && user.purchased.shirt.rainbow && user.purchased.shirt.redblue && user.purchased.shirt.thunder && user.purchased.shirt.tropical && user.purchased.shirt.zombie", ng-click='unlock("shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie")')!= env.t('unlockset5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-small.btn-primary(ng-hide="user.purchased.shirt.convict && user.purchased.shirt.cross && user.purchased.shirt.fire && user.purchased.shirt.horizon && user.purchased.shirt.ocean && user.purchased.shirt.purple && user.purchased.shirt.rainbow && user.purchased.shirt.redblue && user.purchased.shirt.thunder && user.purchased.shirt.tropical && user.purchased.shirt.zombie", ng-click='unlock("shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
.span4
h3=env.t('bodyhead')
h3=env.t('bodyHead')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
= ' ' + env.t('locked')
@ -38,35 +38,35 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
// Special Events
li.customize-menu.well.limited-edition
.label.label-info.pull-right(popover=env.t('limited31Jan'), popover-title=env.t('limitededition'), popover-placement='right', popover-trigger='mouseenter')
=env.t('limitededition')
.label.label-info.pull-right(popover=env.t('limited31Jan'), popover-title=env.t('limitedEdition'), popover-placement='right', popover-trigger='mouseenter')
=env.t('limitedEdition')
|
span.glyphicon.glyphicon.icon-question-sign
menu(label=env.t('wintercolors'))
span.glyphicon.glyphicon-question-sign
menu(label=env.t('winterColors'))
each color in ['candycane','frost','winternight','holly']
button(type='button', ng-class='{locked: !user.purchased.hair.color.#{color}}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.color.candycane && user.purchased.hair.color.frost && user.purchased.hair.color.winternight && user.purchased.hair.color.holly', ng-click='unlock("hair.color.candycane,hair.color.frost,hair.color.winternight,hair.color.holly")')!= env.t('unlockset5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.color.candycane && user.purchased.hair.color.frost && user.purchased.hair.color.winternight && user.purchased.hair.color.holly', ng-click='unlock("hair.color.candycane,hair.color.frost,hair.color.winternight,hair.color.holly")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
h5=env.t('bodyhair')
h5=env.t('bodyHair')
// Bangs
li.customize-menu
menu(label=env.t('hairbangs'))
menu(label=env.t('hairBangs'))
button(class='head_0 customize-option', type='button', ng-click='set({"preferences.hair.bangs":0})')
each num in [1,2,3]
button(class='hair_bangs_#{num}_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.bangs":#{num}})')
// Base
li.customize-menu
menu(label=env.t('hairbase'))
menu(label=env.t('hairBase'))
button(class='head_0 customize-option', type='button', ng-click='set({"preferences.hair.base":0})')
each v,k in {1:true,2:false,3:true,4:false,5:false,6:false,7:false,8:false}
case v
when true: button(class='hair_base_#{k}_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":#{k}})')
when false: button(class='hair_base_#{k}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.base.#{k}}', ng-click='unlock("hair.base.#{k}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.base.2 && user.purchased.hair.base.4 && user.purchased.hair.base.5 && user.purchased.hair.base.6 && user.purchased.hair.base.7 && user.purchased.hair.base.8', ng-click='unlock("hair.base.2,hair.base.4,hair.base.5,hair.base.6,hair.base.7,hair.base.8")')!= env.t('unlockset5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.base.2 && user.purchased.hair.base.4 && user.purchased.hair.base.5 && user.purchased.hair.base.6 && user.purchased.hair.base.7 && user.purchased.hair.base.8', ng-click='unlock("hair.base.2,hair.base.4,hair.base.5,hair.base.6,hair.base.7,hair.base.8")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
h5=env.t('bodyfacialhair')
h5=env.t('bodyFacialHair')
// Beard
li.customize-menu
@ -82,31 +82,31 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
each num in [1,2]
button(class='hair_mustache_#{num}_{{user.preferences.hair.color}} customize-option', type='button', ng-class='{locked: !user.purchased.hair.mustache.#{num}}', ng-click='unlock("hair.mustache.#{num}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.mustache.1 && user.purchased.hair.mustache.2 && user.purchased.hair.beard.1 && user.purchased.hair.beard.2 && user.purchased.hair.beard.3', ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockset5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.mustache.1 && user.purchased.hair.mustache.2 && user.purchased.hair.beard.1 && user.purchased.hair.beard.2 && user.purchased.hair.beard.3', ng-click='unlock("hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
.span4
h3=env.t('bodyskin')
h3=env.t('bodySkin')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
= ' ' + env.t('locked')
// skin
li.customize-menu
menu(label=env.t('basicskins'))
menu(label=env.t('basicSkins'))
each color in ['ddc994','f5a76e','ea8349','c06534','98461a','915533','c3e1dc','6bd049']
button.customize-option(type='button', class='skin_#{color}', ng-click='set({"preferences.skin":"#{color}"})')
// Rainbow Skin
h5=env.t('rainbowskins')
h5=env.t('rainbowSkins')
//menu(label='Rainbow Skins (2G / skin)')
menu
each color in ['eb052b','f69922','f5d70f','0ff591','2b43f6','d7a9f7','800ed0','rainbow']
button.customize-option(type='button', class='skin_#{color}', ng-class='{locked: !user.purchased.skin.#{color}}', ng-click='unlock("skin.#{color}")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock("skin.eb052b,skin.f69922,skin.f5d70f,skin.0ff591,skin.2b43f6,skin.d7a9f7,skin.800ed0,skin.rainbow")')!= env.t('unlockset5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock("skin.eb052b,skin.f69922,skin.f5d70f,skin.0ff591,skin.2b43f6,skin.d7a9f7,skin.800ed0,skin.rainbow")')!= env.t('unlockSet5') + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
// Special Events
// restore to d4df481 to see purchasing + "limited edition" code
div(ng-if='user.purchased.skin.monster || user.purchased.skin.pumpkin || user.purchased.skin.skeleton || user.purchased.skin.zombie || user.purchased.skin.ghost || user.purchased.skin.shadow')
h5=env.t('spookyskins')
h5=env.t('spookySkins')
menu
each color in ['monster','pumpkin','skeleton','zombie','ghost','shadow']
button.customize-option(type='button', class='skin_#{color}', ng-if='user.purchased.skin.#{color}', ng-click='unlock("skin.#{color}")')
@ -116,11 +116,11 @@ script(id='partials/options.profile.stats.html', type='text/ng-template')
.border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"')
include ../shared/profiles/stats
.span4.border-right.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses')
h3=env.t('characterbuild')
h3=env.t('characterBuild')
h4
=env.t('class') + ': '
span {{ {warrior:'Warrior',wizard:'Mage',healer:'Healer',rogue:'Rogue'}[user.stats.class] }}&nbsp;
a.btn.btn-danger.btn-mini(ng-click='changeClass(null)')=env.t('changeclass')
a.btn.btn-danger.btn-mini(ng-click='changeClass(null)')=env.t('changeClass')
small 3 <span class="Pet_Currency_Gem1x inline-gems"/>
table.table.table-striped
tr
@ -129,48 +129,53 @@ script(id='partials/options.profile.stats.html', type='text/ng-template')
{{user.stats.points}}&nbsp;
=env.t('unallocated')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelpopover'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))
td
tr
td(colspan=2)
fieldset.auto-allocate
label.checkbox
input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation?true: false})', ng-click='set({"preferences.allocationMode":"taskbased"})')
=env.t('automaticallocation')
=env.t('autoAllocation')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('automaticallocationpopover'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))
form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em')
label.radio
input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})')
=env.t('evenallocation')
=env.t('evenAllocation')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenallocationpopover'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))
label.radio
input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})')
=env.t('classallocation')
=env.t('classAllocation')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classallocationpopover'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))
label.radio
input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})')
=env.t('taskallocation')
=env.t('taskAllocation')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskallocationpopover'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))
div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)')
a.btn.btn-primary.btn-mini(ng-click='user.ops.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop'))
span.glyphicon.glyphicon-download
&nbsp;
=env.t('distributePoints')
tr
td= env.t('allocatestr') + ' {{user.stats.str}}'
td= env.t('allocateStr') + ' {{user.stats.str}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatestrpop')) +
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) +
tr
td= env.t('allocatecon') + ' {{user.stats.con}}'
td= env.t('allocateCon') + ' {{user.stats.con}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateconpop')) +
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateConPop')) +
tr
td= env.t('allocateper') + ' {{user.stats.per}}'
td= env.t('allocatePer') + ' {{user.stats.per}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateperpop')) +
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatePerPop')) +
tr
td= env.t('allocateint') + ' {{user.stats.int}}'
td= env.t('allocateInt') + ' {{user.stats.int}}'
td
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateintpop')) +
a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) +
div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"')
include ../shared/profiles/achievements
@ -178,19 +183,19 @@ script(id='partials/options.profile.profile.html', type='text/ng-template')
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile')= env.t('edit')
button.btn.btn-primary(ng-click='save()', ng-show='_editing.profile')= env.t('save')
div(ng-show='!_editing.profile')
h4=env.t('displayname')
h4=env.t('displayName')
span(ng-show='profile.profile.name') {{profile.profile.name}}
span.muted(ng-hide='profile.profile.name') -&nbsp;
=env.t('none')
| &nbsp;-
h4=env.t('displayphoto')
h4=env.t('displayPhoto')
img(ng-show='profile.profile.imageUrl', ng-src='{{profile.profile.imageUrl}}')
span.muted(ng-hide='profile.profile.imageUrl') -&nbsp;
=env.t('none')
| &nbsp;-
h4=env.t('displayblurb')
h4=env.t('displayBlurb')
markdown(ng-show='profile.profile.blurb', ng-model='profile.profile.blurb')
span.muted(ng-hide='profile.profile.blurb') -&nbsp;
=env.t('none')
@ -200,14 +205,14 @@ script(id='partials/options.profile.profile.html', type='text/ng-template')
div.whatever-options(ng-show='_editing.profile')
// TODO use photo-upload instead: https://groups.google.com/forum/?fromgroups=#!topic/derbyjs/xMmADvxBOak
.control-group.option-large
label.control-label=env.t('displayname')
label.control-label=env.t('displayName')
input.option-content(type='text', placeholder=env.t('fullName'), ng-model='editingProfile.name')
.control-group.option-large
label.control-label=env.t('photourl')
input.option-content(type='url', ng-model='editingProfile.imageUrl', placeholder=env.t('imageurl'))
label.control-label=env.t('photoUrl')
input.option-content(type='url', ng-model='editingProfile.imageUrl', placeholder=env.t('imageUrl'))
.control-group.option-large
label.control-label=env.t('displayblurb')
textarea.option-content(style='height:15em;', placeholder=env.t("displayblurb"), ng-model='editingProfile.blurb')
label.control-label=env.t('displayBlurb')
textarea.option-content(style='height:15em;', placeholder=env.t('displayBlurb'), ng-model='editingProfile.blurb')
include ../shared/formatting-help
script(id='partials/options.profile.html', type="text/ng-template")
@ -217,7 +222,7 @@ script(id='partials/options.profile.html', type="text/ng-template")
=env.t('avatar')
li(ng-class="{ active: $state.includes('options.profile.stats') }")
a(ui-sref='options.profile.stats')
=env.t('statsach')
=env.t('statsAch')
li(ng-class="{ active: $state.includes('options.profile.profile') }")
a(ui-sref='options.profile.profile')
=env.t('profile')

View file

@ -8,10 +8,9 @@ script(id='partials/options.settings.html', type="text/ng-template")
=env.t('API')
li(ng-class="{ active: $state.includes('options.settings.export') }")
a(ui-sref='options.settings.export')
=env.t('dataexport')
=env.t('dataExport')
li(ng-class="{ active: $state.includes('options.settings.subscription') }")
a(ui-sref='options.settings.subscription')
| Subscription
a(ui-sref='options.settings.subscription')=env.t('subscription')
.tab-content
.tab-pane.active
@ -28,7 +27,10 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
div
small
div=env.t('clockInfo')
.alert.alert-danger Warning: this is a highly-experimental feature, and many <a href='https://github.com/HabitRPG/habitrpg/issues/1057' target='_blank'>experience issues</a> with it.
.alert.alert-danger
=env.t('subWarning1')
a(href='https://github.com/HabitRPG/habitrpg/issues/1057' target='_blank')=env.t('subWarning2')
=env.t('subWarning3')
hr
h4=env.t('language')
select(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()')
@ -38,36 +40,36 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
input(type='checkbox', ng-click='set({"preferences.hideHeader":user.preferences.hideHeader?false:true})', ng-checked='user.preferences.hideHeader!==true')
=env.t('showHeader')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('showHeaderpop'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('showHeaderPop'))
label.checkbox
input(type='checkbox', ng-click='toggleStickyHeader()', ng-checked='user.preferences.stickyHeader!==false')
=env.t('stickyHeader')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('stickyHeaderpop'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('stickyHeaderPop'))
label.checkbox
input(type='checkbox', ng-model='user.preferences.newTaskEdit', ng-change='set({"preferences.newTaskEdit": user.preferences.newTaskEdit?true: false})')
=env.t('newtaskedit')
=env.t('newTaskEdit')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('newtaskeditpop'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('newTaskEditPop'))
label.checkbox
input(type='checkbox', ng-model='user.preferences.tagsCollapsed', ng-change='set({"preferences.tagsCollapsed": user.preferences.tagsCollapsed?true: false})')
=env.t('startcollapsed')
=env.t('startCollapsed')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startcollapsedpop'))
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startCollapsedPop'))
label.checkbox
input(type='checkbox', ng-model='user.preferences.advancedCollapsed', ng-change='set({"preferences.advancedCollapsed": user.preferences.advancedCollapsed?true: false})')
=env.t('startacollapsed')
=env.t('startAdvCollapsed')
&nbsp;
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startacollapsedpop'))
button.btn(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('showtourpop1'))= env.t('showtour')
span.glyphicon.glyphicon-question-sign(popover-trigger='mouseenter', popover-placement='right', popover=env.t('startAdvCollapsedPop'))
button.btn(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
br
button.btn(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showtourpop'))= env.t('showtour2')
button.btn(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey')
br
button.btn(ng-click='modals.restore = true', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixvalpop'))= env.t('fixval')
button.btn(ng-click='modals.restore = true', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal')
div(ng-if='user.preferences.disableClasses==true')
button.btn(ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableclasspop'))= env.t('enableclass')
button.btn(ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
div(ng-if='!user.preferences.disableClasses && user.flags.classSelected')
button.btn(ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classtourpop'))= env.t('showclass')
button.btn(ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classTourPop'))= env.t('showClass')
//- Why is ng-if='user.auth.local' validating for users *without* user.auth.local (facebook users)? adding .username here for extra
div(ng-if='user.auth.local.username')
@ -84,8 +86,8 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
hr
h4=env.t('dangerZone')
a.btn.btn-danger(ng-click='modals.reset = true', popover-trigger='mouseenter', popover-placement='right', popover=env.t('resetaccpop'))= env.t('resetaccount')
a.btn.btn-danger(ng-click='modals.delete = true', popover-trigger='mouseenter', popover=env.t('deleteaccpop'))= env.t('deleteaccount')
a.btn.btn-danger(ng-click='modals.reset = true', popover-trigger='mouseenter', popover-placement='right', popover=env.t('resetAccPop'))= env.t('resetAccount')
a.btn.btn-danger(ng-click='modals.delete = true', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')
script(type='text/ng-template', id='partials/options.settings.api.html')
.row.fluid
@ -96,19 +98,19 @@ script(type='text/ng-template', id='partials/options.settings.api.html')
pre.prettyprint {{user.id}}
h6=env.t('APIToken')
pre.prettyprint {{user.apiToken}}
h6=env.t('qrcode')
h6=env.t('qrCode')
img(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{user.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode')
script(id='partials/options.settings.export.html', type="text/ng-template")
.row.fluid
.span6
h2=env.t('dataexport')
small=env.t('dataexports')
h4=env.t('habithistory')
=env.t('exporthistory')
h2=env.t('dataExport')
small=env.t('saveData')
h4=env.t('habitHistory')
=env.t('exportHistory')
a(href="/export/history.csv")= ' ' + env.t('csv')
h4=env.t('userdata')
=env.t('exportuserdata')
h4=env.t('userData')
=env.t('exportUserData')
a(href="/export/userdata.xml")= ' ' + env.t('xml') + ' '
a(href="/export/userdata.json")= env.t('json')
@ -116,28 +118,24 @@ script(id='partials/options.settings.subscription.perks.html',type='text/ng-temp
table.table.table-striped
tr
td
span.dashed-underline(popover="Ads will stay disabled while you have an active account (original users with disabled ads are grandfathered).",popover-trigger='mouseenter',popover-placement='right')
| Disable ads
span.dashed-underline(popover=env.t('disableAdsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('disableAds')
tr
td
span.dashed-underline(popover="(1 Gem costs {{Shared.planGemLimits.convRate}} Gold) Addresses the \"pay to win\" concern, as everything is now achievable through hard work. There's a {{Shared.planGemLimits.convCap}}G monthly conversion cap to prevent farming.",popover-trigger='mouseenter',popover-placement='right')
| Buy Gems with Gold
span.dashed-underline(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right')=env.t('buyGemsGold')
tr
td
span.dashed-underline(popover="Makes your full history available in graphs and export. Non-subscriber histories get consolidated for database optimization.",popover-trigger='mouseenter',popover-placement='right')
| Retain full history entries
span.dashed-underline(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory')
tr
td
span.dashed-underline(popover="Complete your stable faster!",popover-trigger='mouseenter',popover-placement='right')
| Daily drop-caps doubled
span.dashed-underline(popover=env.t('doubleDropsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('doubleDrops')
//-tr
//- td +20 gems to your account
tr
td
span.dashed-underline(popover="This open source project can use all the help it can get. Help us keep Habit alive!",popover-trigger='mouseenter',popover-placement='right')
| Supports the developers
span.dashed-underline(popover=env.t('supportDevsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('supportDevs')
tr
td.alert.alert-info $5 USD / Month
td.alert.alert-info $5&nbsp;
=env.t('monthUSD')
script(id='partials/feature-matrix-check.html',type='text/ng-template')
span.task-checker.action-yesno
@ -147,16 +145,15 @@ script(id='partials/feature-matrix-check.html',type='text/ng-template')
script(id='partials/options.settings.subscription.html',type='text/ng-template')
.well
h2 Individual Subscription
h2=env.t('individualSub')
div(ng-if='!user.purchased.plan.customerId')
div(ng-include="'partials/options.settings.subscription.perks.html'")
.btn.btn-primary(ng-click='showStripe(true)') Subscribe
.btn.btn-primary(ng-click='showStripe(true)')=env.t('subscribe')
//-small.muted PayPal coming soon.
div(ng-if='user.purchased.plan.customerId')
.well
div(style='font-size:22px')
span.glyphicon.glyphicon-ok
| Subscribed
span.glyphicon.glyphicon-ok=env.t('subscribed')
div(ng-include="'partials/options.settings.subscription.perks.html'")
.btn.btn-small.btn-danger(ng-click='cancelSubscription()') Cancel Subscription
.btn.btn-small.btn-danger(ng-click='cancelSubscription()')=env.t('cancelSub')

View file

@ -1,6 +1,6 @@
script(type='text/ng-template', id='partials/options.social.challenges.detail.close.html')
a.btn.btn-small.btn-danger(ng-click="delete(closingChal)")=env.t('delete')
h5=env.t('minusOr')
h5= '- ' + env.t('or') + ' -'
select(ui-select2, ng-required=true, ng-model='closingChal.winner', data-placeholder=env.t('selectWinner'), ng-change='selectWinner(closingChal)', )
option(value='')
option(ng-repeat='u in closingChal.members', value='{{u._id}}') {{u.profile.name}}
@ -111,7 +111,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html')
span.input-suffix.Pet_Currency_Gem1x.inline-gems
span.glyphicon.glyphicon-question-sign(popover=env.t('prizePop'), popover-trigger='mouseenter', popover-placement='right')
span(ng-show='newChallenge.group=="habitrpg"')
!=env.t('min1Gem') + ' <strong>' + env.t('publicChallenges') + '</strong> ' + env.t('helpsPrevent')
!=env.t('publicChallenges')
.option-medium(ng-if='user.contributor.admin')
label.checkbox

View file

@ -11,7 +11,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild
// ------ Bosses -------
.modal.inline-modal(bindonce='group', ng-if='group.type==="party" && group.quest.key')
.modal-header
h3(ng-if='group.quest.active==false')=env.t('questInv') + '{{Content.quests[group.quest.key].text}}'
h3(ng-if='group.quest.active==false')=env.t('questInvitation') + '{{Content.quests[group.quest.key].text}}'
h3(ng-if='group.quest.active==true') {{Content.quests[group.quest.key].text}}
.modal-body
div(ng-if='group.quest.active==false')
@ -61,9 +61,9 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild
div(ng-if='Content.quests[group.quest.key].boss')
.npc_ian.pull-left
p!=env.t('bossDmg1') + ' <strong>' + env.t('bossDmg2') + '</strong>'
p!=env.t('bossDmg1')
br
p=env.t('bossDmg3')
p=env.t('bossDmg2')
div(ng-if='Content.quests[group.quest.key].collect')
.npc_ian.pull-left
@ -88,7 +88,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild
input.option-content(type='text', ng-model='group.name', placeholder=env.t('groupName'))
.control-group.option-large
label.control-label=env.t('description')
textarea.option-content(style='height:15em;', placeholder=env.t('groupdescr'), ng-model='group.description')
textarea.option-content(style='height:15em;', placeholder=env.t('groupDescr'), ng-model='group.description')
.control-group.option-large
label.control-label=env.t('logoUrl')
input.option-content(type='url', placeholder=env.t('logoUrl'), ng-model='group.logo')
@ -131,7 +131,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild
tr(ng-if='group.memberCount > group.members.length')
td
|<span class='badge'>{{group.memberCount - group.members.length}}
= ' ' + env.t('moremembers')
= ' ' + env.t('moreMembers')
|</span>
h4(ng-show='group.invites.length > 0')=env.t('invited')

View file

@ -42,9 +42,9 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html')
.control-group.option-medium
input.option-content(type='number', step="any", ng-model='hero.balance')
span.input-suffix Balance
span.input-suffix=env.t('balance')
p
small!= '`user.balance`' + env.t('USD1') + ' <em>' + env.t('not') + '</em> ' + env.t('USD2')
small!= '`user.balance`' + env.t('notGems')
.control-group.option-medium
label.checkbox
@ -70,7 +70,7 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html')
span(ng-if='hero.contributor.admin',popover=env.t('gamemaster'),popover-trigger='mouseenter',popover-placement='right')
a.label(class='label-contributor-{{hero.contributor.level}}', ng-class='{"label-npc": hero.backer.npc}', ng-click='clickMember(hero._id, true)')
| {{hero.profile.name}}&nbsp;
span.glyphicon.glyphicon-star.icon-white
span.glyphicon.glyphicon-star
span(ng-if='!hero.contributor.admin')
a.label(class='label-contributor-{{hero.contributor.level}}', ng-class='{"label-npc": hero.backer.npc}', ng-click='clickMember(hero._id, true)') {{hero.profile.name}}
td(ng-if='user.contributor.admin') {{hero._id}}

View file

@ -68,35 +68,40 @@
a.label.label-contributor-1(ng-click='toggleUserTier($event)')=env.t('friendBadge')
div(style='display:none;')
p
!= '<span class="achievement achievement-firefox"></span> ' + env.t('friendText') + '<strong> ' + env.t('first') + '</strong> ' + env.t('friend1Text') + '<strong> 2' + env.t('gems') + '</strong>.'
span.achievement.achievement-firefox
!=env.t('friendFirst')
hr
p
!= '<span class="shop_armor_special_1 shop-sprite item-img"></span> ' + env.t('friendText') + '<strong> ' + env.t('second') + '</strong> ' + env.t('friendText2') + '<strong> ' + env.t('crystalArmor') + '</strong> ' + env.t('friendText3') + '<strong> 2' + env.t('gems') + '</strong>.'
span.shop-sprite.item-img(class='shop_armor_special_1')
!=env.t('friendSecond')
tr
td
a.label.label-contributor-3(ng-click='toggleUserTier($event)')=env.t('eliteBadge')
div(style='display:none;')
p
!= '<span class="shop_head_special_1 shop-sprite item-img"></span> ' + env.t('friendText') + ' <strong>' + env.t('third') + '</strong> ' + env.t('friendText2') + ' <strong>' + env.t('crystalHelmet') + '</strong> ' + env.t('friendText3') + '<strong> 2' + env.t('gems') + '</strong>.'
span.shop-sprite.item-img(class='shop_head_special_1')
!=env.t('eliteThird')
hr
p
!= '<span class="shop_weapon_special_1 shop-sprite item-img"></span> ' + env.t('friendText') + ' <strong>' + env.t('fourth') + '</strong> ' + env.t('friendText2') + ' <strong>' + env.t('crystalSword') + '</strong> ' + env.t('friendText3') + '<strong> 2' + env.t('gems') + '</strong>.'
span.shop-sprite.item-img(class='shop_weapon_special_1')
!=env.t('eliteFourth')
tr
td
a.label.label-contributor-5(ng-click='toggleUserTier($event)')=env.t('championBadge')
div(style='display:none;')
p
!= '<span class="shop_shield_special_1 shop-sprite item-img"></span> ' + env.t('friendText') + ' <em>' + env.t('fifth') + '</em> ' + env.t('friendText2') + ' <strong>' + env.t('crystalShield') + '</strong> ' + env.t('friendText3') + '<strong> 2' + env.t('gems') + '</strong>.'
span.shop-sprite.item-img(class='shop_shield_special_1')
!=env.t('championFifth')
hr
p
!= '<div class="Pet-Dragon-Hydra pull-left"></div>' + env.t('friendText') + ' <em>' + env.t('sixth') + '</em> ' + env.t('friendText2b') + ' <strong>' + env.t('hydraPet') + '</strong> ' + env.t('friendText3b') + '<strong> 2' + env.t('gems') + '</strong>.'
div(class='Pet-Dragon-Hydra pull-left')
!=env.t('championSixth')
tr
td
a.label.label-contributor-7(ng-click='toggleUserTier($event)')=env.t('legendaryBadge')
div(style='display:none;')
p
!= env.t('friendText') + ' <em>' + env.t('seventh') + '</em> ' + env.t('friendText2c') + '<strong> 2' + env.t('gems') + ' </strong>' + env.t('friendText3c')
!=env.t('legSeventh')
tr
td
a.label.label-contributor-8(ng-click='toggleUserTier($event)')=env.t('heroicBadge')
@ -111,12 +116,12 @@
include ./challenge-box
.span8(ng-controller='ChatCtrl')
h3=env.t('taverntalk')
h3=env.t('tavernTalk')
include ./chat-box
small(style='position: relative; top: -20px; left: 25px;')
include ../../shared/formatting-help
small(style='position: relative; top: 18px; left: 0px')
alert.alert-info
!=env.t('tavernalert1') + ' <a href="https://github.com/HabitRPG/habitrpg/issues" target="_blank">' + env.t('tavernalert2') + '</a>.'
!=env.t('tavernAlert1') + ' <a href="https://github.com/HabitRPG/habitrpg/issues" target="_blank">' + env.t('tavernAlert2') + '</a>.'
ul.unstyled.tavern-chat
include ./chat-message

View file

@ -44,7 +44,7 @@ figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(
// FIXME handle @minimal, this might have to be a directive
span.current-pet(class='Pet-{{profile.items.currentPet}}', ng-show='profile.items.currentPet && !minimal')
.avatar-level(ng-class='userLevelStyle(profile,"label")')
span.glyphicon.glyphicon-circle-arrow-up.icon-white(ng-show='profile.stats.buffs.str || profile.stats.buffs.per || profile.stats.buffs.con || profile.stats.buffs.int || profile.stats.buffs.stealth', tooltip=env.t('buffed'), style='margin-right:5px;')
span.glyphicon.glyphicon-circle-arrow-up(ng-show='profile.stats.buffs.str || profile.stats.buffs.per || profile.stats.buffs.con || profile.stats.buffs.int || profile.stats.buffs.stealth', tooltip=env.t('buffed'), style='margin-right:5px;')
=env.t('lvl')
| {{profile.stats.lvl}}
span.glyphicon.glyphicon-plus-sign.icon-white(ng-show='profile.achievements.rebirths', tooltip=env.t('reborn') + '{{profile.achievements.rebirthLevel}}', style='margin-left:5px')
span.glyphicon.glyphicon-plus-sign(ng-show='profile.achievements.rebirths', tooltip=env.t('reborn', {reLevel: "{{profile.achievements.rebirthLevel}}"}), style='margin-left:5px')

View file

@ -10,19 +10,19 @@
span.glyphicon.glyphicon-ok
=env.t('tasks')
span(ng-show='$state.includes("tasks")', ui-sref='options')
span.glyphicon.glyphicon.icon-wrench
span.glyphicon.glyphicon-wrench
=env.t('options')
li
a.task-action-btn.tile.solid(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')
span.glyphicon.glyphicon.icon-book
span.glyphicon.glyphicon.-book
=env.t('FAQ')
li
a.task-action-btn.tile.solid(href="https://vimeo.com/57654086", target='_blank')
span.glyphicon.glyphicon.icon-film
span.glyphicon.glyphicon-film
=env.t('tutorials')
li
a.task-action-btn.tile.solid(ng-click='User.sync()')
span.glyphicon.glyphicon.icon-refresh
span.glyphicon.glyphicon-refresh
=env.t('sync')
li
a.task-action-btn.tile.solid(ng-click='logout()')

View file

@ -37,11 +37,8 @@ script(id='modals/achievements/contributor.html', type='text/ng-template')
.modal-body
.npc_justin.float-left
p
| {{user.profile.name}},
=env.t('contribText1')
| {{user.contributor.level}}
=env.t('contribText2')
a(href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank')=env.t('contribText3')
!=env.t('contribModal', {name: "{{user.profile.name}}", level: "{{user.contributor.level}}"})
a(href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank')=env.t('contribLink')
.modal-footer
button.btn.btn-default.cancel(ng-click='set({"flags.contributor":false})')=env.t('ok')
@ -51,10 +48,6 @@ script(id='modals/achievements/rebirth.html', type='text/ng-template')
h3=env.t('modalAchievement')
.modal-body
.achievement.achievement-sun
=env.t('rebirthAchievement1')
| {{user.achievements.rebirths}}
=env.t('rebirthAchievement2')
| {{user.achievements.rebirthLevel}}.
=env.t('rebirthAchievement3')
=env.t('rebirthAchievement', {number: "{{user.achievements.rebirths}}", level: "{{user.achievements.rebirthLevel}}"})
.modal-footer
button.btn.btn-default.cancel(ng-click='modals.achievements.rebirth = false')=env.t('ok')

View file

@ -4,7 +4,7 @@ script(id='modals/buyGems.html', type='text/ng-template')
.buy-gems
include ../gems
.well
h3 Buy Gems
h3=env.t('buyGems')
table.table
tr
td
@ -15,7 +15,8 @@ script(id='modals/buyGems.html', type='text/ng-template')
span.dashed-underline(popover=env.t('donateText3'),popover-trigger='mouseenter',popover-placement='right')
=env.t('donateText2')
tr
td.alert.alert-info=env.t('fiveUSD1')
td.alert.alert-info $5&nbsp;
=env.t('USD')
tr
td
.btn.btn-primary(ng-click='showStripe()',style='margin-left:10px;')=env.t('payWithCard')

View file

@ -30,24 +30,50 @@ script(type='text/ng-template', id='modals/newStuff.html')
table.table.table-striped
tr
td
h5 Group Plans
p We've begun adding <a href='/static/plans' targte='_blank'>plans for groups</a> (parents, teachers, health & wellness administrators, etc). These plans will provide group leaders with more control, privacy, security, and support. Currently only the Organization Plan (top tier) is available (due to tech limitations believe it or not), and we'll be releasing the Family & Group plans later. <a href='/static/plans' targte='_blank'>Click the "Contact Us" buttons</a> if you're interested, and we'll keep you updated!
h5 Happy Birthday, HabitRPG!
p The fair land of Habitica is two years old on January 31st! The NPCs are celebrating in style, and it looks like some of the staff is, too! Won't you join in?
tr
td
h5 Individual Plan
p We've introduced a $5/mo basic subscription plan. It comes with a number of perks, which <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>you can see here</a>. We'll likely add more benefits over time, follow <a href='https://trello.com/c/euDUHPpn/371-basic-plan-subscription' target='_blank'>the conversation here</a>.
h5 Absurd Party Robtes
p As part of the festivities, Absurd Party Robes are available free of charge in the Item Store! Swath yourself in those silly garbs and don your matching hats to celebrate this momentous day.
tr
td
h5 Perfect Day Achievement
p Now when you complete all your dailies, you stack this badge, plus and additional perk: you get a +(level/2) buff to all stats!
h5 Delicious Cake
p What would a birthday be without birthday cake in a myriad of flavors? Of course, pets are very picky, but luckily Lemoness and her team of bakers have plenty of slices to go around. Mmm, delicious!
tr
td
h5 <a href='/#/options/groups/challenges/95533e05-1ff9-4e46-970b-d77219f199e9' target='_blank'>Spread The Word Challenge</a> Update
p We have 2k+ submissions, holy cow! Great job everyone! Now, we need to go through these manually, so it will take a few days to a couple weeks to process. The challenge will stay open until we're done choosing our winners, but be sure to edit the To-Do with your submission URL before 1/31, as that's the cut-off date for processing. We'll send a Tweet out when the winner has been selected, so follow <a href='https://twitter.com/habitrpg' target='_blank'>@habitrpg</a> and stay tuned.
h5 Last Day of Winter Wonderland Event
p Also, just a reminder - January 31st is the final day of the Winter Wonderland event, so it's your last day to get the Limited Edition Winter Hair Colors, the Winter Outfits, the snowballs, and the Trapper Santa and Find the Cub quest scrolls. Remember that mid-progress Trapper Santa and Find the Cub quests will not abort, nor will you lose your scrolls - they will simply be removed from Alexander's marketplace. We hope that you've had a wonderful winter!
tr
td
h5 Birthday Bash Badge
p Finally, to commemorate the fun, all party participants receive a birthday badge! Polish it frequently and wear it fondly.
p Thanks so much for being a part of the HabitRPG community. We love you guys, and we can't wait to have you at our sides in the upcoming year! Stay productive, Habiteers, and have an awesome day.
p.muted By @lemoness
small.muted 01/28/2014
small.muted 01/30/2014
hr
h5 01/28/2014
table.table.table-striped
tr
td
h5 Group Plans
p We've begun adding <a href='/static/plans' targte='_blank'>plans for groups</a> (parents, teachers, health & wellness administrators, etc). These plans will provide group leaders with more control, privacy, security, and support. Currently only the Organization Plan (top tier) is available (due to tech limitations believe it or not), and we'll be releasing the Family & Group plans later. <a href='/static/plans' targte='_blank'>Click the "Contact Us" buttons</a> if you're interested, and we'll keep you updated!
tr
td
h5 Individual Plan
p We've introduced a $5/mo basic subscription plan. It comes with a number of perks, which <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>you can see here</a>. We'll likely add more benefits over time, follow <a href='https://trello.com/c/euDUHPpn/371-basic-plan-subscription' target='_blank'>the conversation here</a>.
tr
td
h5 Perfect Day Achievement
p Now when you complete all your dailies, you stack this badge, plus and additional perk: you get a +(level/2) buff to all stats!
tr
td
h5 <a href='/#/options/groups/challenges/95533e05-1ff9-4e46-970b-d77219f199e9' target='_blank'>Spread The Word Challenge</a> Update
p We have 1k+ submissions, holy cow! Great job everyone! Now, we need to go through these manually, so it will take a few days to a couple weeks to process. The challenge will stay open until we're done choosing our winners, but be sure to edit the To-Do with your submission URL before 1/31, as that's the cut-off date for processing. We'll send a Tweet out when the winner has been selected, so follow <a href='https://twitter.com/habitrpg' target='_blank'>@habitrpg</a> and stay tuned.
hr
h5 01/25/2014

View file

@ -2,7 +2,7 @@
div(modal='modals.reset')
script(type='text/ng-template', id='modals/reset.html')
.modal-header
h3=env.t('resetaccount')
h3=env.t('resetAccount')
.modal-body
p=env.t('resetText1')
p=env.t('resetText2')
@ -67,11 +67,9 @@ script(type='text/ng-template', id='modals/restore.html')
//div(modal='modals.delete')
script(type='text/ng-template', id='modals/delete.html')
.modal-header
h3=env.t('deleteaccount')
h3=env.t('deleteAccount')
.modal-body
p=env.t('deleteText1')
strong=env.t('deleteText2')
=env.t('deleteText3')
p!=env.t('deleteText')
p
input(type='text', ng-model='_deleteAccount')
.modal-footer

View file

@ -123,3 +123,11 @@
small
=env.t('annoyingFriendsText', {snowballs: "{{profile.achievements.snowball}}"})
hr
div(ng-if='profile.achievements.habitBirthday')
.achievement.achievement-habitBirthday
h5=env.t('habitBirthday')
small
=env.t('habitBirthdayText')
hr

View file

@ -93,9 +93,7 @@ li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s
a(ng-click='unlink(task, "remove-all")')=env.t('removeThem')
div(ng-if='task.challenge.broken=="CHALLENGE_CLOSED"')
p
=env.t('challengeCompleted1')
span.badge {{task.challenge.winner}}
=env.t('challengeCompleted2')
!=env.t('challengeCompleted', {user: "{{task.challenge.winner}}"})
p
a(ng-click='unlink(task, "keep-all")')=env.t('keepThem')
| &nbsp;|&nbsp;
@ -117,7 +115,7 @@ li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s
legend.option-title
=env.t('checklist')
| &nbsp;
span.glyphicon.glyphicon.icon-question-sign(popover=env.t('checklistText'),popover-trigger='mouseenter',popover-placement='bottom')
span.glyphicon.glyphicon-question-sign(popover=env.t('checklistText'),popover-trigger='mouseenter',popover-placement='bottom')
ul.unstyled
li(ng-repeat='item in task.checklist')
a.pull-right.checklist-icons(ng-click='removeChecklistItem(task,$event,$index,true)')

View file

@ -5,7 +5,7 @@ block vars
- var menuItem = 'about'
block title
title About
title=env.t('companyAbout')
block content
.row

View file

@ -3,7 +3,7 @@ block vars
- var layoutEnv = env
- var menuItem = 'contact'
block title
title Contact
title=env.t('contact')
block content
// Probably just add linkks to the respective contact locations?
// Bugs? Github

View file

@ -28,13 +28,13 @@ html
.collapse.navbar-collapse(collapse="isNavbarCollapsed")
ul.nav.navbar-nav
li(class='#{menuItem=="about" ? "active" : ""}')
a(href='/static/about') Learn More
a(href='/static/about')=env.t('learnMore')
li
a(href='http://blog.habitrpg.com/') Blog
a(href='http://blog.habitrpg.com/')=env.t('companyBlog')
li(class='#{menuItem=="plans" ? "active" : ""}')
a(href='/static/plans') Plans
a(href='/static/plans')=env.t('groupPlans')
//li(class='#{menuItem=="contact" ? "active" : ""}')
a(href='/static/contact') Contact
a(href='/static/contact')=env.t('contact')
button#header-play-button.btn.btn-primary.navbar-btn.navbar-right(ng-click='playButtonClick()')=env.t('playButton')

View file

@ -5,29 +5,37 @@ block vars
- var menuItem = 'plans'
block title
title Group Plans
title=env.t('groupPlans')
block content
.row
.col-md-12
h2 Group Plans
h2=env.t('groupPlans')
p For individuals, HabitRPG is free to play. Even for small interest groups, free (or cheap) <a href='http://habitrpg.wikia.com/wiki/Guilds' target='_blank'>guilds</a> and <a href='http://habitrpg.wikia.com/wiki/Challenges' target='_blank'>challenges</a> can be used to motivate participants in behavioral modification. Think writing groups, art challenges, and more.
p But some group leaders will want more control, privacy, security, and support. Examples of such groups are families, health and wellness groups, employee groups, and more. These plans provide private instances of HabitRPG for your group or organization, secure and independent of <a href='http://habitrpg.wikia.com/wiki/Habitica' target='_blank'>Habitica</a>. See below for additional plan perks, and contact us for more information!
p
=env.t('indivPlan1')
a(href='http://habitrpg.wikia.com/wiki/Guilds' target='_blank')=env.t('guilds')
= ' ' + env.t('and') + ' '
a(href='http://habitrpg.wikia.com/wiki/Challenges' target='_blank')=env.t('challenges')
=env.t('indivPlan2')
p
=env.t('groupText1')
a(href='http://habitrpg.wikia.com/wiki/Habitica' target='_blank')=env.t('habitica')
|.&nbsp;
=env.t('groupText2')
.subscription-features
table.table.table-striped
thead
tr
th.feature-name
th.feature-name.muted Family (Coming Soon)
th.feature-name.muted Group (Coming Soon)
th.feature-name Organization
th.feature-name.muted=env.t('planFamily')
th.feature-name.muted=env.t('planGroup')
th.feature-name=env.t('organization')
tbody
tr
th
span.dashed-underline(popover="Members of the organization participate outside of HabitRPG proper, providing focus for your participants.",popover-trigger='mouseenter',popover-placement='right')
| Private Organization
span.dashed-underline(popover=env.t('organizationSubText'),popover-trigger='mouseenter',popover-placement='right')=env.t('organizationSub')
td.muted
span.glyphicon.glyphicon-ok
td.muted
@ -36,53 +44,46 @@ block content
span.glyphicon.glyphicon-ok
tr
th
span.dashed-underline(popover="Dedicated Hosting: you get your own database and server hosted by HabitRPG, or optionally we'll install it in your organization's network. If not checked, the plan uses \"Shared Hosting\": your organization uses the same database as HabitRPG proper while performing independently Habitica. Your members are shielded from Tavern & Guilds, but still on the same server/database.",popover-trigger='mouseenter',popover-placement='right')
| Dedicated Hosting
span.dashed-underline(popover=env.t('dedicatedHostText'),popover-trigger='mouseenter',popover-placement='right')=env.t('dedicatedHost')
td
td
td
span.glyphicon.glyphicon-ok
tr
th
span.dashed-underline(popover="We can optionally give you your own domain for the installation.",popover-trigger='mouseenter',popover-placement='right')
| Custom Domain
span.dashed-underline(popover=env.t('customDomainText'),popover-trigger='mouseenter',popover-placement='right')=env.t('customDomain')
td
td
td
span.glyphicon.glyphicon-ok
tr
th
span.dashed-underline(popover="The maximum number of players in your private organization.",popover-trigger='mouseenter',popover-placement='right')
| Max Participants
span.dashed-underline(popover=env.t('maxPlayersText'),popover-trigger='mouseenter',popover-placement='right')=env.t('maxPlayers')
td.muted 10
td.muted 75
td Unlimited
td=env.t('unlimited')
tr
th
span.dashed-underline(popover="First to be provided for with support.",popover-trigger='mouseenter',popover-placement='right')
| Priority Support On Tickets & Hosting
span.dashed-underline(popover=env.t('priSupportText'),popover-trigger='mouseenter',popover-placement='right')=env.t('priSupport')
td
td
td
span.glyphicon.glyphicon-ok
tr
th
span.dashed-underline(popover="We will provide support for training, bugs, installation, and feature requests. Additional hours available at an hourly rate.",popover-trigger='mouseenter',popover-placement='right')
| Support Hours / Month
span.dashed-underline(popover=env.t('timeSupportText'),popover-trigger='mouseenter',popover-placement='right')=env.t('timeSupport')
td.muted -
td.muted 5
td 10
tr
th
h5 Game features:
h5=env.t('gameFeatures') + ':'
ul
li Ads disabled for members
li=env.t('gameNoAds')
li
span.dashed-underline(popover="Members will be able to purchase gems with gold, meaning none of your participants need to buy anything with real money.",popover-trigger='mouseenter',popover-placement='right')
| Gems purchasable with gold
span.dashed-underline(popover=env.t('gold2GemText'),popover-trigger='mouseenter',popover-placement='right')=env.t('gold2Gem')
li
span.dashed-underline(popover="We will provide the organization leaders with as many gems as they need, for things like challenge prizes, guild-creation, etc.",popover-trigger='mouseenter',popover-placement='right')
| Infinite leader gems
span.dashed-underline(popover=env.t('infiniteGemText'),popover-trigger='mouseenter',popover-placement='right')=env.t('infiniteGem')
td.muted
span.glyphicon.glyphicon-ok
td.muted
@ -93,8 +94,8 @@ block content
th
//| Price
td
a.btn(href='https://docs.google.com/forms/d/17torT7OlxgtbHAPdBDRQNG2lTdIQyrGXk4O2YXvUMig/viewform',popover="Plan not yet available, but click to contact us and we'll keep you updated.",popover-placement='right',popover-trigger='mouseenter') Contact Us
a.btn.muted(href='https://docs.google.com/forms/d/17torT7OlxgtbHAPdBDRQNG2lTdIQyrGXk4O2YXvUMig/viewform',popover=env.t('notYetPlan'),popover-placement='right',popover-trigger='mouseenter')=env.t('contactUs')
td
a.btn.muted(href='https://docs.google.com/forms/d/17torT7OlxgtbHAPdBDRQNG2lTdIQyrGXk4O2YXvUMig/viewform',popover="Plan not yet available, but click to contact us and we'll keep you updated.",popover-placement='right',popover-trigger='mouseenter') Contact Us
a.btn.muted(href='https://docs.google.com/forms/d/17torT7OlxgtbHAPdBDRQNG2lTdIQyrGXk4O2YXvUMig/viewform',popover=env.t('notYetPlan'),popover-placement='right',popover-trigger='mouseenter')=env.t('contactUs')
td
a.btn.btn-primary(href='https://docs.google.com/forms/d/12Jqj_8f3oQS0B3ZUHewHbK61uLjBdzBeB0zyEqB9lxM/viewform') Contact Us
a.btn.btn-primary(href='https://docs.google.com/forms/d/12Jqj_8f3oQS0B3ZUHewHbK61uLjBdzBeB0zyEqB9lxM/viewform')=env.t('contactUs')