Guild A/B test and Achievement (#8740)

* WIP(guilds): AB test pester modal

* WIP(AB-test): guild pester cont'd

* fix(style): linting error

* fix(AB-test): markModified and notif enum

* fix(tests): update AB expectations

* fix(modal): remove extra includes

* feat(achievements): add Joined Guild cheevo
Also removes unused achievement sprites, and properly saves counter used in A/B testing

* fix(style): linting error from conflict
This commit is contained in:
Sabe Jones
2017-05-19 14:45:11 -05:00
committed by GitHub
parent 8a9ed04f5e
commit 547c87dee7
64 changed files with 167 additions and 28 deletions

View File

@@ -74,6 +74,18 @@ describe('POST /group', () => {
expect(updatedUser.guilds).to.include(guild._id); expect(updatedUser.guilds).to.include(guild._id);
}); });
it('awards the Joined Guild achievement', async () => {
await user.post('/groups', {
name: 'some guild',
type: 'guild',
privacy: 'public',
});
let updatedUser = await user.get('/user');
expect(updatedUser.achievements.joinedGuild).to.eql(true);
});
context('public guild', () => { context('public guild', () => {
it('creates a group', async () => { it('creates a group', async () => {
let groupName = 'Test Public Guild'; let groupName = 'Test Public Guild';

View File

@@ -68,6 +68,12 @@ describe('POST /group/:groupId/join', () => {
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
}); });
it('awards Joined Guild achievement', async () => {
await joiningUser.post(`/groups/${publicGuild._id}/join`);
await expect(joiningUser.get('/user')).to.eventually.have.deep.property('achievements.joinedGuild', true);
});
}); });
context('Joining a private guild', () => { context('Joining a private guild', () => {
@@ -147,8 +153,14 @@ describe('POST /group/:groupId/join', () => {
}), }),
}; };
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED'); expect(inviter.notifications[1].type).to.eql('GROUP_INVITE_ACCEPTED');
expect(inviter.notifications[0].data).to.eql(expectedData); expect(inviter.notifications[1].data).to.eql(expectedData);
});
it('awards Joined Guild achievement', async () => {
await invitedUser.post(`/groups/${guild._id}/join`);
await expect(invitedUser.get('/user')).to.eventually.have.deep.property('achievements.joinedGuild', true);
}); });
}); });
}); });

View File

@@ -112,8 +112,8 @@ describe('POST /tasks/:id/approve/:userId', () => {
await user.sync(); await user.sync();
await member2.sync(); await member2.sync();
expect(user.notifications.length).to.equal(1); expect(user.notifications.length).to.equal(2);
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL'); expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
expect(member2.notifications.length).to.equal(1); expect(member2.notifications.length).to.equal(1);
expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL'); expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
@@ -122,7 +122,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
await user.sync(); await user.sync();
await member2.sync(); await member2.sync();
expect(user.notifications.length).to.equal(0); expect(user.notifications.length).to.equal(1);
expect(member2.notifications.length).to.equal(0); expect(member2.notifications.length).to.equal(0);
}); });

View File

@@ -52,14 +52,14 @@ describe('POST /tasks/:id/score/:direction', () => {
await user.sync(); await user.sync();
expect(user.notifications.length).to.equal(1); expect(user.notifications.length).to.equal(2);
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL'); expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', { expect(user.notifications[1].data.message).to.equal(t('userHasRequestedTaskApproval', {
user: member.auth.local.username, user: member.auth.local.username,
taskName: updatedTask.text, taskName: updatedTask.text,
taskId: updatedTask._id, taskId: updatedTask._id,
}, 'cs')); // This test only works if we have the notification translated }, 'cs')); // This test only works if we have the notification translated
expect(user.notifications[0].data.groupId).to.equal(guild._id); expect(user.notifications[1].data.groupId).to.equal(guild._id);
expect(updatedTask.group.approval.requested).to.equal(true); expect(updatedTask.group.approval.requested).to.equal(true);
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
@@ -82,14 +82,14 @@ describe('POST /tasks/:id/score/:direction', () => {
await user.sync(); await user.sync();
await member2.sync(); await member2.sync();
expect(user.notifications.length).to.equal(1); expect(user.notifications.length).to.equal(2);
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL'); expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', { expect(user.notifications[1].data.message).to.equal(t('userHasRequestedTaskApproval', {
user: member.auth.local.username, user: member.auth.local.username,
taskName: updatedTask.text, taskName: updatedTask.text,
taskId: updatedTask._id, taskId: updatedTask._id,
})); }));
expect(user.notifications[0].data.groupId).to.equal(guild._id); expect(user.notifications[1].data.groupId).to.equal(guild._id);
expect(member2.notifications.length).to.equal(1); expect(member2.notifications.length).to.equal(1);
expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL'); expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');

View File

@@ -251,7 +251,6 @@ describe('POST /user/auth/local/register', () => {
confirmPassword: password, confirmPassword: password,
}); });
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object'); await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
}); });

View File

@@ -83,7 +83,7 @@ describe('POST /user/auth/social', () => {
network, network,
}); });
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string'); await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
}); });
}); });
@@ -139,7 +139,7 @@ describe('POST /user/auth/social', () => {
network, network,
}); });
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string'); await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
}); });
}); });
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -78,7 +78,7 @@ habitrpg.controller('NotificationCtrl',
// Avoid showing the same notiication more than once // Avoid showing the same notiication more than once
var lastShownNotifications = []; var lastShownNotifications = [];
function trasnferGroupNotification(notification) { function transferGroupNotification(notification) {
if (!User.user.groupNotifications) User.user.groupNotifications = []; if (!User.user.groupNotifications) User.user.groupNotifications = [];
User.user.groupNotifications.push(notification); User.user.groupNotifications.push(notification);
} }
@@ -110,6 +110,13 @@ habitrpg.controller('NotificationCtrl',
var markAsRead = true; var markAsRead = true;
switch (notification.type) { switch (notification.type) {
case 'GUILD_PROMPT':
if (notification.data.textVariant === -1) {
$rootScope.openModal('testing');
} else {
$rootScope.openModal('testingVariant');
}
break;
case 'DROPS_ENABLED': case 'DROPS_ENABLED':
$rootScope.openModal('dropsEnabled'); $rootScope.openModal('dropsEnabled');
break; break;
@@ -129,12 +136,19 @@ habitrpg.controller('NotificationCtrl',
} }
break; break;
case 'ULTIMATE_GEAR_ACHIEVEMENT': case 'ULTIMATE_GEAR_ACHIEVEMENT':
$rootScope.playSound('Achievement_Unlocked');
Achievement.displayAchievement('ultimateGear', {size: 'md'}); Achievement.displayAchievement('ultimateGear', {size: 'md'});
break; break;
case 'REBIRTH_ACHIEVEMENT': case 'REBIRTH_ACHIEVEMENT':
$rootScope.playSound('Achievement_Unlocked');
Achievement.displayAchievement('rebirth'); Achievement.displayAchievement('rebirth');
break; break;
case 'GUILD_JOINED_ACHIEVEMENT':
$rootScope.playSound('Achievement_Unlocked');
Achievement.displayAchievement('joinedGuild', {size: 'md'});
break;
case 'NEW_CONTRIBUTOR_LEVEL': case 'NEW_CONTRIBUTOR_LEVEL':
$rootScope.playSound('Achievement_Unlocked');
Achievement.displayAchievement('contributor', {size: 'md'}); Achievement.displayAchievement('contributor', {size: 'md'});
break; break;
case 'CRON': case 'CRON':
@@ -144,11 +158,11 @@ habitrpg.controller('NotificationCtrl',
} }
break; break;
case 'GROUP_TASK_APPROVAL': case 'GROUP_TASK_APPROVAL':
trasnferGroupNotification(notification); transferGroupNotification(notification);
markAsRead = false; markAsRead = false;
break; break;
case 'GROUP_TASK_APPROVED': case 'GROUP_TASK_APPROVED':
trasnferGroupNotification(notification); transferGroupNotification(notification);
markAsRead = false; markAsRead = false;
break; break;
case 'SCORED_TASK': case 'SCORED_TASK':

View File

@@ -282,5 +282,7 @@
"userIsNotManager": "User is not manager", "userIsNotManager": "User is not manager",
"canOnlyApproveTaskOnce": "This task has already been approved.", "canOnlyApproveTaskOnce": "This task has already been approved.",
"leaderMarker": " - Leader", "leaderMarker": " - Leader",
"managerMarker": " - Manager" "managerMarker": " - Manager",
"joinedGuild": "Joined a Guild",
"joinedGuildText": "Ventured into the social side of Habitica by joining a Guild!"
} }

View File

@@ -0,0 +1,7 @@
{
"guildReminderTitle": "Check out Guilds!",
"guildReminderText1": "Now that you've mastered Habitica's basics, come check out Guilds, where you can chat with other people who are committed to improving their lives!",
"guildReminderText2": "Ready for more? Join a Guild to meet other people who are committed to improving their lives!",
"guildReminderCTA": "Take me there!",
"guildReminderDismiss": "Not right now."
}

View File

@@ -102,6 +102,11 @@ let basicAchievs = {
titleKey: 'royallyLoyal', titleKey: 'royallyLoyal',
textKey: 'royallyLoyalText', textKey: 'royallyLoyalText',
}, },
joinedGuild: {
icon: 'achievement-guild',
titleKey: 'joinedGuild',
textKey: 'joinedGuildText',
},
}; };
Object.assign(achievementsData, basicAchievs); Object.assign(achievementsData, basicAchievs);

View File

@@ -179,6 +179,7 @@ function _getBasicAchievements (user, language) {
_addSimple(result, user, {path: 'partyUp', language}); _addSimple(result, user, {path: 'partyUp', language});
_addSimple(result, user, {path: 'partyOn', language}); _addSimple(result, user, {path: 'partyOn', language});
_addSimple(result, user, {path: 'joinedGuild', language});
_addSimple(result, user, {path: 'royallyLoyal', language}); _addSimple(result, user, {path: 'royallyLoyal', language});
_addSimpleWithMasterCount(result, user, {path: 'beastMaster', language}); _addSimpleWithMasterCount(result, user, {path: 'beastMaster', language});

View File

@@ -122,6 +122,14 @@ api.createGroup = {
user.balance--; user.balance--;
user.guilds.push(group._id); user.guilds.push(group._id);
if (!user.achievements.joinedGuild) {
user.achievements.joinedGuild = true;
user.addNotification('GUILD_JOINED_ACHIEVEMENT');
}
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
user._ABtests.counter = -1;
user.markModified('_ABtests');
}
} else { } else {
if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate')); if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate'));
if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty')); if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty'));
@@ -510,6 +518,14 @@ api.joinGroup = {
throw new NotAuthorized(res.t('userAlreadyInGroup')); throw new NotAuthorized(res.t('userAlreadyInGroup'));
} }
user.guilds.push(group._id); // Add group to user's guilds user.guilds.push(group._id); // Add group to user's guilds
if (!user.achievements.joinedGuild) {
user.achievements.joinedGuild = true;
user.addNotification('GUILD_JOINED_ACHIEVEMENT');
}
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
user._ABtests.counter = -1;
user.markModified('_ABtests');
}
} }
if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite'));

View File

@@ -589,6 +589,18 @@ api.scoreTask = {
} }
} }
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
user._ABtests.counter++;
if (user._ABtests.counter > 1) {
if (user._ABtests.guildReminder.indexOf('timing1') !== -1 || user._ABtests.counter > 4) {
user._ABtests.counter = -1;
let textVariant = user._ABtests.guildReminder.indexOf('text2');
user.addNotification('GUILD_PROMPT', {textVariant});
}
}
user.markModified('_ABtests');
}
if (task.type === 'daily') { if (task.type === 'daily') {
task.isDue = common.shouldDo(Date.now(), task, user.preferences); task.isDue = common.shouldDo(Date.now(), task, user.preferences);
} }

View File

@@ -121,6 +121,8 @@ api.getBuyList = {
}; };
let updatablePaths = [ let updatablePaths = [
'_ABtests.counter',
'flags.customizationsNotification', 'flags.customizationsNotification',
'flags.showTour', 'flags.showTour',
'flags.tour', 'flags.tour',

View File

@@ -10,7 +10,7 @@ import schema from './schema';
schema.plugin(baseModel, { schema.plugin(baseModel, {
// noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...) // noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...)
noSet: [], noSet: [],
private: ['auth.local.hashed_password', 'auth.local.passwordHashMethod', 'auth.local.salt', '_cronSignature', '_ABtest', '_ABtests'], private: ['auth.local.hashed_password', 'auth.local.passwordHashMethod', 'auth.local.salt', '_cronSignature', '_ABtests'],
toJSONTransform: function userToJSON (plainObj, originalDoc) { toJSONTransform: function userToJSON (plainObj, originalDoc) {
plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs
delete plainObj.filters; delete plainObj.filters;
@@ -94,13 +94,25 @@ function _setUpNewUser (user) {
let taskTypes; let taskTypes;
let iterableFlags = user.flags.toObject(); let iterableFlags = user.flags.toObject();
user._ABtest = ''; // A/B test 2017-05-11: Can we encourage people to join Guilds with a pester modal?
// A/B test 2016-12-21: Should we deliver notifications for upcoming incentives on days when users don't receive rewards? let testGroup = Math.random();
if (Math.random() < 0.5) { if (testGroup < 0.1) {
user._ABtests.checkInModals = '20161221_noCheckInPreviews'; // no 'preview' check-in modals user._ABtests.guildReminder = '20170511_noGuildReminder'; // control group, don't pester about Guilds
user._ABtests.counter = -1;
} else if (testGroup < 0.235) {
user._ABtests.guildReminder = '20170511_text1timing1'; // first sample text, show after two clicks
user._ABtests.counter = 0;
} else if (testGroup < 0.46) {
user._ABtests.guildReminder = '20170511_text2timing1'; // second sample text, show after two clicks
user._ABtests.counter = 0;
} else if (testGroup < 0.685) {
user._ABtests.guildReminder = '20170511_text1timing2'; // first sample text, show after five clicks
user._ABtests.counter = 0;
} else { } else {
user._ABtests.checkInModals = '20161221_showCheckInPreviews'; // show 'preview' check-in modals user._ABtests.guildReminder = '20170511_text2timing2'; // second sample text, show after five clicks
user._ABtests.counter = 0;
} }
user.items.quests.dustbunnies = 1; user.items.quests.dustbunnies = 1;
user.purchased.background.violet = true; user.purchased.background.violet = true;
user.preferences.background = 'violet'; user.preferences.background = 'violet';

View File

@@ -112,6 +112,7 @@ let schema = new Schema({
partyUp: Boolean, partyUp: Boolean,
partyOn: Boolean, partyOn: Boolean,
royallyLoyal: Boolean, royallyLoyal: Boolean,
joinedGuild: Boolean,
}, },
backer: { backer: {
@@ -545,7 +546,6 @@ let schema = new Schema({
return {}; return {};
}}, }},
pushDevices: [PushDeviceSchema], pushDevices: [PushDeviceSchema],
_ABtest: {type: String}, // deprecated. Superseded by _ABtests
_ABtests: {type: Schema.Types.Mixed, default: () => { _ABtests: {type: Schema.Types.Mixed, default: () => {
return {}; return {};
}}, }},

View File

@@ -18,6 +18,8 @@ const NOTIFICATION_TYPES = [
'GROUP_INVITE_ACCEPTED', 'GROUP_INVITE_ACCEPTED',
'SCORED_TASK', 'SCORED_TASK',
'BOSS_DAMAGE', // Not used currently but kept to avoid validation errors 'BOSS_DAMAGE', // Not used currently but kept to avoid validation errors
'GUILD_PROMPT',
'GUILD_JOINED_ACHIEVEMENT',
]; ];
const Schema = mongoose.Schema; const Schema = mongoose.Schema;

View File

@@ -117,7 +117,7 @@ script(id='modals/achievements/contributor.html', type='text/ng-template')
button.btn.btn-primary(style='margin-top:1em' ng-click='$close()')=env.t('huzzah') button.btn.btn-primary(style='margin-top:1em' ng-click='$close()')=env.t('huzzah')
+achievementFooter +achievementFooter
//Rebirth // Rebirth
script(id='modals/achievements/rebirth.html', type='text/ng-template') script(id='modals/achievements/rebirth.html', type='text/ng-template')
.modal-content(style='min-width:28em') .modal-content(style='min-width:28em')
.modal-body.text-center .modal-body.text-center
@@ -152,3 +152,14 @@ script(id='modals/achievements/partyOn.html', type='text/ng-template')
br br
button.btn.btn-primary(ng-click='$close()')=env.t('huzzah') button.btn.btn-primary(ng-click='$close()')=env.t('huzzah')
+achievementFooter +achievementFooter
// Joined Guild
script(id='modals/achievements/joinedGuild.html', type='text/ng-template')
.modal-content(style='min-width:28em')
.modal-body.text-center
h3(style='margin-bottom:0')=env.t('modalAchievement')
+achievementAvatar('guild',0)
p=env.t('joinedGuildText')
br
button.btn.btn-primary(ng-click='$close()')=env.t('huzzah')
+achievementFooter

View File

@@ -27,6 +27,7 @@ include ./generic.jade
include ./tasks-edit.jade include ./tasks-edit.jade
include ./task-notes.jade include ./task-notes.jade
include ./task-extra-notes.jade include ./task-extra-notes.jade
include ./testing.jade
//- Settings //- Settings
script(type='text/ng-template', id='modals/change-day-start.html') script(type='text/ng-template', id='modals/change-day-start.html')

View File

@@ -0,0 +1,31 @@
script(type='text/ng-template', id='modals/testing.html')
.modal-content(style='min-width:28em')
.modal-body.text-center(style='padding-bottom:0')
h3=env.t('guildReminderTitle')
br
.scene_guilds.center-block
br
h4=env.t('guildReminderText1')
.modal-footer(style='padding-top:0')
.container-fluid
.row
.col-xs-6.text-center
button.btn-lg.btn-default(ng-click='$close()')=env.t('guildReminderDismiss')
.col-xs-6.text-center
button.btn-lg.btn-primary(ui-sref='options.social.guilds.public', href='/#/options/groups/guilds/public', ng-click='$close()')=env.t('guildReminderCTA')
script(type='text/ng-template', id='modals/testingVariant.html')
.modal-content(style='min-width:28em')
.modal-body.text-center(style='padding-bottom:0')
h3=env.t('guildReminderTitle')
br
.scene_guilds.center-block
br
h4=env.t('guildReminderText2')
.modal-footer(style='padding-top:0')
.container-fluid
.row
.col-xs-6.text-center
button.btn-lg.btn-default(ng-click='$close()')=env.t('guildReminderDismiss')
.col-xs-6.text-center
button.btn-lg.btn-primary(ui-sref='options.social.guilds.public', href='/#/options/groups/guilds/public', ng-click='$close()')=env.t('guildReminderCTA')