diff --git a/app/assets/images/pages/play/modal/subscribe-heroes.png b/app/assets/images/pages/play/modal/subscribe-heroes.png
new file mode 100644
index 000000000..bd55cf02b
Binary files /dev/null and b/app/assets/images/pages/play/modal/subscribe-heroes.png differ
diff --git a/app/assets/images/pages/play/portal-background.png b/app/assets/images/pages/play/portal-background.png
new file mode 100644
index 000000000..4a9d33933
Binary files /dev/null and b/app/assets/images/pages/play/portal-background.png differ
diff --git a/app/assets/images/pages/play/portal-campaigns.png b/app/assets/images/pages/play/portal-campaigns.png
new file mode 100644
index 000000000..3d9806dd1
Binary files /dev/null and b/app/assets/images/pages/play/portal-campaigns.png differ
diff --git a/app/assets/javascripts/run-tests.js b/app/assets/javascripts/run-tests.js
index 913f71fe3..a32c38e6e 100644
--- a/app/assets/javascripts/run-tests.js
+++ b/app/assets/javascripts/run-tests.js
@@ -1,8 +1,10 @@
 // Helper for running tests through Karma.
 // Hooks into the test view logic for running tests.
 
+
+window.userObject = {_id:'1'}
 initialize = require('core/initialize');
 initialize.init();
 console.debug = function() {}; // Karma conf doesn't seem to work? Debug messages are still emitted when they shouldn't be.
 TestView = require('views/TestView');
-TestView.runTests();
\ No newline at end of file
+TestView.runTests();
diff --git a/app/core/initialize.coffee b/app/core/initialize.coffee
index 3d42b664a..0ad7b49da 100644
--- a/app/core/initialize.coffee
+++ b/app/core/initialize.coffee
@@ -43,6 +43,8 @@ init = ->
   handleNormalUrls()
   setUpMoment() # Set up i18n for moment
 
+module.exports.init = init
+
 handleNormalUrls = ->
   # http://artsy.github.com/blog/2012/06/25/replacing-hashbang-routes-with-pushstate/
   $(document).on 'click', "a[href^='/']", (event) ->
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index e57efd220..048487ec1 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -32,6 +32,7 @@ module.exports = class LevelLoader extends CocoClass
     @team = options.team
     @headless = options.headless
     @spectateMode = options.spectateMode ? false
+    @observing = options.observing
 
     @worldNecessities = []
     @listenTo @supermodel, 'resource-loaded', @onWorldNecessityLoaded
@@ -389,6 +390,8 @@ module.exports = class LevelLoader extends CocoClass
     @world.submissionCount = @session?.get('state')?.submissionCount ? 0
     @world.flagHistory = @session?.get('state')?.flagHistory ? []
     @world.difficulty = @session?.get('state')?.difficulty ? 0
+    if @observing
+      @world.difficulty = Math.max 0, @world.difficulty - 1  # Show the difficulty they won, not the next one.
     serializedLevel = @level.serialize(@supermodel, @session, @opponentSession)
     @world.loadFromLevel serializedLevel, false
     console.log 'World has been initialized from level loader.'
diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee
index 8b83cad13..4dd1628d2 100644
--- a/app/lib/surface/Lank.coffee
+++ b/app/lib/surface/Lank.coffee
@@ -204,39 +204,45 @@ module.exports = Lank = class Lank extends CocoClass
       @handledDisplayEvents[event] = true
       args = JSON.parse(event[4...])
       key = 'aoe-' + JSON.stringify(args[2..])
+      layerName = args[6] ? 'ground'  # Can also specify 'floating'.
+      unless layer = @options[layerName + 'Layer']
+        console.error "#{@thang.id} couldn't find layer #{layerName}Layer for AOE effect #{key}; using ground layer."
+        layer = @options.groundLayer
 
-      unless key in @options.groundLayer.spriteSheet.getAnimations()
-        args = JSON.parse(event[4...])
+      unless key in layer.spriteSheet.getAnimations()
         circle = new createjs.Shape()
         radius = args[2] * Camera.PPM
         if args.length is 4
           circle.graphics.beginFill(args[3]).drawCircle(0, 0, radius)
         else
-          startAngle = args[4]
-          endAngle = args[5]
+          startAngle = args[4] or 0
+          endAngle = args[5] or 2 * Math.PI
+          if startAngle is endAngle
+            startAngle = 0
+            endAngle = 2 * Math.PI
           circle.graphics.beginFill(args[3])
             .lineTo(0, 0)
             .lineTo(radius * Math.cos(startAngle), radius * Math.sin(startAngle))
             .arc(0, 0, radius, startAngle, endAngle)
             .lineTo(0, 0)
-        @options.groundLayer.addCustomGraphic(key, circle, [-radius, -radius, radius*2, radius*2])
+        layer.addCustomGraphic(key, circle, [-radius, -radius, radius*2, radius*2])
 
-      circle = new createjs.Sprite(@options.groundLayer.spriteSheet)
+      circle = new createjs.Sprite(layer.spriteSheet)
       circle.gotoAndStop(key)
       pos = @options.camera.worldToSurface {x: args[0], y: args[1]}
       circle.x = pos.x
       circle.y = pos.y
-      resFactor = @options.groundLayer.resolutionFactor
+      resFactor = layer.resolutionFactor
       circle.scaleY = @options.camera.y2x * 0.7 / resFactor
       circle.scaleX = 0.7 / resFactor
       circle.alpha = 0.2
-      @options.groundLayer.addChild circle
+      layer.addChild circle
       createjs.Tween.get(circle)
         .to({alpha: 0.6, scaleY: @options.camera.y2x / resFactor, scaleX: 1 / resFactor}, 100, createjs.Ease.circOut)
         .to({alpha: 0, scaleY: 0, scaleX: 0}, 700, createjs.Ease.circIn)
         .call =>
           return if @destroyed
-          @options.groundLayer.removeChild circle
+          layer.removeChild circle
           delete @handledDisplayEvents[event]
 
   showTextEvents: ->
diff --git a/app/locale/ar.coffee b/app/locale/ar.coffee
index b8bfd4387..6d630d35f 100644
--- a/app/locale/ar.coffee
+++ b/app/locale/ar.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/bg.coffee b/app/locale/bg.coffee
index e10d128ec..79d9a36f6 100644
--- a/app/locale/bg.coffee
+++ b/app/locale/bg.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "български език", englishDescri
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "български език", englishDescri
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "български език", englishDescri
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/ca.coffee b/app/locale/ca.coffee
index b637d9df8..88beea5c4 100644
--- a/app/locale/ca.coffee
+++ b/app/locale/ca.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Subscriu-te"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/cs.coffee b/app/locale/cs.coffee
index 9d41840a5..4103f1fa2 100644
--- a/app/locale/cs.coffee
+++ b/app/locale/cs.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
     recovered: "Obnovení již zakoupených drahokamů proběhlo úspěšně. Aktualizujte stránku prosím."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Předplacení"
     unsubscribe: "Zrušit předplacení"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
     heroes: "Více silnějších hrdinů!"
     gems: "3500 bonusových drahokamů každý měsíc!"
     items: "Více než 250 bonusových předmětů!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Pro rodiče"
     parents_title: "Vaše dítě se naučí programovat."
     parents_blurb1: "Pomocí CodeCombat se vaše dítě učí psaním opravdového kódu. Začínají učením se základním příkazů a postupně se přidávají pokročilejší témata."
diff --git a/app/locale/da.coffee b/app/locale/da.coffee
index e7cd538f9..b508065a3 100644
--- a/app/locale/da.coffee
+++ b/app/locale/da.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/de-AT.coffee b/app/locale/de-AT.coffee
index 2be3750dc..d1ecf92a5 100644
--- a/app/locale/de-AT.coffee
+++ b/app/locale/de-AT.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
     recovered: "Voriger Juwelenkauf wiederhergestellt. Bitte die Seite neu laden."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/de-CH.coffee b/app/locale/de-CH.coffee
index 54339edc4..d79d5d0c4 100644
--- a/app/locale/de-CH.coffee
+++ b/app/locale/de-CH.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee
index 3eb8857f5..96b53f03e 100644
--- a/app/locale/de-DE.coffee
+++ b/app/locale/de-DE.coffee
@@ -341,18 +341,19 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     multiplayer_caption: "Spiele mit Freunden!"
     auth_caption: "Fortschritt speichern."
 
-#  leaderboard:
+  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
-#    day: "Today"
-#    week: "This Week"
+    day: "Heute"
+    week: "Diese Woche"
 #    all: "All-Time"
-#    time: "Time"
+    time: "Zeit"
 #    damage_taken: "Damage Taken"
 #    damage_dealt: "Damage Dealt"
-#    difficulty: "Difficulty"
-#    gold_collected: "Gold Collected"
+    difficulty: "Schwierigkeit"
+    gold_collected: "Gold gesammelt"
 
   inventory:
     choose_inventory: "Gegenstände ausrüsten"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     recovered: "Vorhergegangener Edelsteinkauf rückgängig gemacht. Aktualisiere bitte die Seite."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+    free: "Kostenlos"
+    month: "Monate"
     subscribe_title: "Abonnieren"
     unsubscribe: "Abmelden"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     heroes: "Stärkere Helden!"
     gems: "3500 bonus Edelsteine jeden Monat!"
     items: "Über 250 bonus Gegenstände!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Für Eltern"
     parents_title: "Dein Kind lernt zu programmieren."
     parents_blurb1: "Mit CodeCombat, lernt dein Kind richtige Programme zu schreiben. Es fängt mit einfachen Befehlen an, und schreitet ganz unmerklich zu schwierigeren Themen fort."
diff --git a/app/locale/el.coffee b/app/locale/el.coffee
index df890ec39..0188e06ec 100644
--- a/app/locale/el.coffee
+++ b/app/locale/el.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/en-AU.coffee b/app/locale/en-AU.coffee
index cff21061b..fdf1f8e71 100644
--- a/app/locale/en-AU.coffee
+++ b/app/locale/en-AU.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/en-GB.coffee b/app/locale/en-GB.coffee
index 484ce1efa..54fea1862 100644
--- a/app/locale/en-GB.coffee
+++ b/app/locale/en-GB.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/en-US.coffee b/app/locale/en-US.coffee
index 6aac6f809..c551131f6 100644
--- a/app/locale/en-US.coffee
+++ b/app/locale/en-US.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index dabab010d..ad19489b6 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -381,6 +381,15 @@
     recovered: "Previous gems purchase recovered. Please refresh the page."
 
   subscribe:
+    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+    feature1: "60+ basic levels across 4 worlds"
+    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+    feature3: "30+ bonus levels"
+    feature4: "<strong>3500 bonus gems</strong> every month!"
+    feature5: "Video tutorials"
+    feature6: "Premium email support"
+    free: "Free"
+    month: "month"
     subscribe_title: "Subscribe"
     unsubscribe: "Unsubscribe"
     confirm_unsubscribe: "Confirm Unsubscribe"
@@ -390,16 +399,19 @@
     thank_you: "Thank you for supporting CodeCombat."
     sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
     unsubscribe_feedback_placeholder: "O, what have we done?"
-    levels: "Get more practice with bonus levels!"
-    heroes: "More powerful heroes!"
-    gems: "3500 bonus gems every month!"
-    items: "Over 250 bonus items!"
+    parent_button: "Ask your parent"
+    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+    parent_email_input_invalid: "Email address invalid."
+    parent_email_input_label: "Parent email address"
+    parent_email_input_placeholder: "Enter parent email"
+    parent_email_send: "Send Email"
+    parent_email_sent: "Email sent!"
+    parent_email_title: "What's your parent's email?"
     parents: "For Parents"
     parents_title: "Your child will learn to code."
     parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
     parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
     parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-    subscribe_button: "Subscribe Now"
     stripe_description: "Monthly Subscription"
     subscription_required_to_play: "You'll need a subscription to play this level."
     unlock_help_videos: "Subscribe to unlock all video tutorials."
@@ -986,6 +998,7 @@
     play_counts: "Play Counts"
     feedback: "Feedback"
     payment_info: "Payment Info"
+    campaigns: "Campaigns"
 
   delta:
     added: "Added"
diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee
index 759a96802..dcf6c9a6c 100644
--- a/app/locale/es-419.coffee
+++ b/app/locale/es-419.coffee
@@ -322,7 +322,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
     tip_lines_of_code: "Medir el progreso en la programación en líneas de código es como medir el progreso de construcción de una aeronave por su peso. — Bill Gates"
     tip_source_code: "Quisiera cambiar el mundo, pero no me dan el código fuente."
     tip_javascript_java: "Java es a Javascript lo mismo que Comer es a Comercial. - Chris Heilmann"
-#    tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
+    tip_move_forward: "Hagas lo que hagas, siempre sigue hacia delante. - Martin Luther King Jr."
 
   game_menu:
     inventory_tab: "Inventario"
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
     recovered: "Se recuperaron las anteriores compras de gemas. Por favor recarga la página"
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Suscribirse"
     unsubscribe: "Des-suscribirse"
     confirm_unsubscribe: "Confirmar cancelacion de suscripción"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
     heroes: "Héroes más poderosos!"
     gems: "Bonus de 3500 todos los meses!"
     items: "Más de 250 ítems de bonus!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Para padres"
     parents_title: "Su hijo aprenderá a programar."
     parents_blurb1: "Con CodeCombat, su hijo aprenderá a escribiendo código real. Empezaran aprendiendo comandos simples avanzando a temas más complejos."
@@ -504,45 +522,45 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
     matt_title: "Programador"
     matt_blurb: "Bicicletero"
 
-#  teachers:
-#    title: "CodeCombat for Teachers"
-#    preparation_title: "Preparation"
-#    preparation_1: "CodeCombat is free to play for the core level progression and does not require students to sign up. We encourage teachers to"
-#    preparation_play_campaign: "play through the campaign"
-#    preparation_2: "to try it out, but the only thing you absolutely need to do to be ready is ensure students have access to a computer."
-#    preparation_3: "It is not necessary for teachers to be comfortable with computer science concepts for students to have fun learning with CodeCombat."
-#    violent_title: "Is it violent?"
-#    violent_1: "We get this from teachers a lot due to our name. Although CodeCombat does contain cartoon violence, there is nothing graphic in either the visuals or language."
-#    violent_2: "If you are comfortable having your students play Angry Birds, you will be comfortable with CodeCombat."
-#    for_girls_title: "Is it for girls?"
-#    for_girls_1: "There are three game modes in CodeCombat: building, puzzles, and combat. We have intentionally designed each to appeal to both boys and girls and think that the building and puzzle levels especially differentiate the game from violent triple A titles that repel female players."
-#    what_cover_title: "What do we cover?"
-#    what_cover_1: "There are 20 levels in the Hour of Code tutorial that teach and reinforce 6 specific computer science concepts:"
-#    what_cover_notation_1: "Formal notation"
-#    what_cover_notation_2: "- builds an understanding of the importance of syntax in programming."
-#    what_cover_methods_1: "Calling methods"
-#    what_cover_methods_2: "- familiarizes students with the syntax of object-oriented method calls."
-#    what_cover_parameters_1: "Parameters"
-#    what_cover_parameters_2: "- trains how to pass parameters to functions."
-#    what_cover_strings_1: "Strings"
-#    what_cover_strings_2: "- teaches students about string notation and passing strings as parameters."
-#    what_cover_loops_1: "Loops"
-#    what_cover_loops_2: "- develops the abstraction of designing short programs with loops."
-#    what_cover_variables_1: "Variables"
-#    what_cover_variables_2: "- adds the skill of referencing values that change over time."
-#    what_cover_2: "Students may continue past level 20, depending on their speed and interest, to learn two additional concepts in later levels:"
-#    what_cover_logic_1: "Conditional logic"
-#    what_cover_logic_2: "- when and how to use if/else to control in-game outcomes."
-#    what_cover_input_1: "Handling player input"
-#    what_cover_input_2: "- responding to input events to create a user interface."
-#    sys_requirements_title: "System Requirements"
-#    sys_requirements_1: "Because CodeCombat is a game, it is more intensive for computers to run smoothly than video or written tutorials. We have optimized it to run quickly on all modern browsers and on older machines so that everyone can play. That said, here are our suggestions for getting the most out of your Hour of Code experience:"
-#    sys_requirements_2: "Use newer versions of Chrome or Firefox."
-#    sys_requirements_3: "Although CodeCombat will work on browsers as old as IE9, the performance is not as good. Chrome is best."
-#    sys_requirements_4: "Use newer computers."
-#    sys_requirements_5: "Older computers, Chromebooks, and netbooks tend to have very few system resources, which makes for a less enjoyable experience. At least 2GB of RAM is required."
-#    sys_requirements_6: "Allow players to wear headphones/earbuds to hear the audio."
-#    sys_requirements_7: "We help players learn through voiceover and sound effects, which will make classrooms noisy and distracting."
+  teachers:
+    title: "CodeCombat para Profesores"
+    preparation_title: "Preparación"
+    preparation_1: "CodeCombat es gratuito para jugar en la progresión de nivel básico y no requiere el registro de los usuarios. Alentamos a los profesores a"
+    preparation_play_campaign: "jugar a través de la campaña"
+    preparation_2: "para probarlo, la única cosa que necesitas para estar listo es asegurarte que los estudiantes tengan acceso a una computadora con internet."
+    preparation_3: "No es necesario que los maestros se sienten cómodos con los conceptos informáticos para que los estudiantes se divierten aprendiendo con CodeCombat."
+    violent_title: "¿Es violento?"
+    violent_1: "Nos preguntan esto debido a nuestro nombre. Sin embargo CodeCombat solo contiene violencia de dibujos animados, no hay nada gráfico en las imágenes o el lenguaje."
+    violent_2: "Si te sientes cómodo cuanto tus estudiantes juegan Angry Bird, entonces estaras cómodo con CodeCombat."
+    for_girls_title: "¿Es para chicas?"
+    for_girls_1: "Existen tres modos de juego en CodeCombat: Edificación, rompecabezas, y combate. Hemos diseñado intencionalmente cada uno para atraer tanto a los niños como a las niñas, y creemos en que los niveles de construcción y rompecabezas diferencia el juego de los títulos triple A ultra violentos que repelen a las jugadoras."
+    what_cover_title: "¿Qué es lo que cubrimos?"
+    what_cover_1: "Hay 20 niveles en nuestro tutorial Hora del Código que enseña y refuerza 6 conceptos especifico de las ciencias computacionales:"
+    what_cover_notation_1: "Notación Formal"
+    what_cover_notation_2: "- construye una comprensión de la importancia de la sintaxis en la programación."
+    what_cover_methods_1: "Metodos de llamada"
+    what_cover_methods_2: "- familiariza a los estudiantes con la sintaxis de las llamadas a métodos orientados a objetos."
+    what_cover_parameters_1: "Parametros"
+    what_cover_parameters_2: "- entrena en cómo pasar parámetros a funciones."
+    what_cover_strings_1: "Cadenas"
+    what_cover_strings_2: "- enseña a los estudiantes acerca de la notación en cadena y como pasar estas cadenas como parámetros."
+    what_cover_loops_1: "Bucles"
+    what_cover_loops_2: "- desarrolla la abstracción del diseño de programas cortos con bucles."
+    what_cover_variables_1: "Variables"
+    what_cover_variables_2: "- añade la habilidad de referenciar valores que cambian con el tiempo."
+    what_cover_2: "Los estudiantes pueden continuar más allá del nivel 20, dependiendo de su velocidad e interés, para aprender dos conceptos adicionales en los niveles tardíos:"
+    what_cover_logic_1: "Condicionales lógicos"
+    what_cover_logic_2: "- cuándo y cómo utilizar if / else para controlar los resultados del juego."
+    what_cover_input_1: "Manipulación de eventos de entrada"
+    what_cover_input_2: "- responder a eventos de entrada para crear una interfaz de usuario."
+    sys_requirements_title: "Requerimientos del sistema"
+    sys_requirements_1: "Debido que CodeCombat es un juego, es más difícil para las computadoras correrlo en relación a un tutorial escrito o un video. Para que todos puedan jugar, hemos optimizado la web para correr rápidamente en todos los navegadores modernos y en maquinas antiguas. Dicho esto, aquí están nuestras sugerencias para sacar el máximo provecho de su experiencia en la Hora del Código:"
+    sys_requirements_2: "Usar una versión actualizada del navegador Chrome o Firefox."
+    sys_requirements_3: "Aunque CodeCombat funcionará en navegadores tan antiguas como IE9, el rendimiento no es tan bueno. Chrome es la mejor opción."
+    sys_requirements_4: "Usar computadoras nuevas."
+    sys_requirements_5: "Cumputadoras viejas, Chromebooks y netbooks tienden a tener menos recursos del sistema, lo que los convierte en una experiencia menos agradable. Se requiere al menos 2 GB de RAM."
+    sys_requirements_6: "Permitir a los estudiantes usar auriculares / audífonos para escuchar el audio."
+    sys_requirements_7: "Ayudamos a los jugadores mediante efectos de sonidos y voces en off, lo que podría hacer a las aulas espacios ruidosos y molestos."
 
   versions:
     save_version_title: "Guardar nueva versión"
@@ -610,7 +628,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
     view_profile: "Ver tu perfil"
 
   keyboard_shortcuts:
-    keyboard_shortcuts: "Keyboard Shortcuts"
+    keyboard_shortcuts: "Atajos de teclado"
     space: "Barra espaciadora"
     enter: "Enter"
     escape: "Escape"
diff --git a/app/locale/es-ES.coffee b/app/locale/es-ES.coffee
index 79c57e475..879a7820a 100644
--- a/app/locale/es-ES.coffee
+++ b/app/locale/es-ES.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     recovered: "Las gemas compradas con anterioridad han sido recuperadas. Por favor, refresca la página."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Suscríbete"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     heroes: "¡Más heroes poderosos!"
     gems: "¡3500 joyas adicionales cada mes!"
     items: "¡Más de 250 artículos adicionales!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Para Padres"
     parents_title: "Tus hijos aprenderan a programar."
     parents_blurb1: "Con CodeCombat, tus hijos aprendes a desarrollar código real. Al inicio aprenden comandos simples, y avanzan a temas más avanzados."
diff --git a/app/locale/fa.coffee b/app/locale/fa.coffee
index e56e6547b..7d8b38107 100644
--- a/app/locale/fa.coffee
+++ b/app/locale/fa.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/fi.coffee b/app/locale/fi.coffee
index 0e81aa759..74907e6c4 100644
--- a/app/locale/fi.coffee
+++ b/app/locale/fi.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/fr.coffee b/app/locale/fr.coffee
index 4d25053b4..f060f294f 100644
--- a/app/locale/fr.coffee
+++ b/app/locale/fr.coffee
@@ -315,7 +315,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     tip_extrapolation: "Il y a seulement deux types de personnes : celles qui peuvent extrapoler à partir de données incomplètes..."
 #    tip_superpower: "Coding is the closest thing we have to a superpower."
 #    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
-#    tip_no_code: "No code is faster than no code."
+    tip_no_code: "Aucun code n'est plus rapide qu'aucun code."
 #    tip_code_never_lies: "Code never lies, comments sometimes do. — Ron Jeffries"
 #    tip_reusable_software: "Before software can be reusable it first has to be usable."
 #    tip_optimization_operator: "Every language has an optimization operator. In most languages that operator is ‘//’"
@@ -341,18 +341,19 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     multiplayer_caption: "Jouer avec des amis!"
     auth_caption: "Sauvegarder votre progression."
 
-#  leaderboard:
-#    leaderboard: "Leaderboard"
-#    view_other_solutions: "View Other Solutions"
-#    top_solutions: "Top Solutions"
-#    day: "Today"
-#    week: "This Week"
-#    all: "All-Time"
-#    time: "Time"
-#    damage_taken: "Damage Taken"
-#    damage_dealt: "Damage Dealt"
-#    difficulty: "Difficulty"
-#    gold_collected: "Gold Collected"
+  leaderboard:
+    leaderboard: "classement"
+    view_other_solutions: "Voir les autres solutions"
+    scores: "Scores"
+    top_solutions: "Meilleures solutions"
+    day: "Aujourd'hui"
+    week: "Cette semaine"
+    all: "Tous les temps"
+    time: "Temps"
+    damage_taken: "Dégât subis"
+    damage_dealt: "Dégât infligés"
+    difficulty: "Difficulté"
+    gold_collected: "Or collecté"
 
   inventory:
     choose_inventory: "Équiper des Objets"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     recovered: "Gemmes précédemment achetées récupérées. Merci de rafraîchir la page."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Inscription"
     unsubscribe: "Désinscription"
     confirm_unsubscribe: "Confirmer la désinscription"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     heroes: "Héros plus puissants!"
     gems: "3500 gemmes en bonus chaque mois !"
     items: "Plus de 250 objets en bonus !"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Pour les parents"
     parents_title: "Votre enfant va apprendre à programmer."
     parents_blurb1: "Avec CodeCombat, votre enfant apprend en écrisant de vrais programmes. Ils commencent en apprenant des instructions simples, puis progressent sur des thèmes plus complexes."
diff --git a/app/locale/gl.coffee b/app/locale/gl.coffee
index c6e1db7eb..e636c9a9d 100644
--- a/app/locale/gl.coffee
+++ b/app/locale/gl.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Galego", englishDescription: "Galician", tr
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Galego", englishDescription: "Galician", tr
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Galego", englishDescription: "Galician", tr
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/he.coffee b/app/locale/he.coffee
index 35d9bab6c..e449c4ba3 100644
--- a/app/locale/he.coffee
+++ b/app/locale/he.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/hi.coffee b/app/locale/hi.coffee
index 9b8d5a15c..d0ab8e107 100644
--- a/app/locale/hi.coffee
+++ b/app/locale/hi.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/hu.coffee b/app/locale/hu.coffee
index e78767bcf..0f2578e0b 100644
--- a/app/locale/hu.coffee
+++ b/app/locale/hu.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Feliratkozás"
     unsubscribe: "Leiratkozás"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     heroes: "Még erősebb hősök!"
     gems: "3500 búnusz drágakő havonta!"
     items: "Több mint 250 bónusz tárgy!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Szülőknek"
     parents_title: "A gyereke programozni tanul majd."
     parents_blurb1: "A CodeCombattal a gyereke valódi programozási feladatokon keresztül tanul. Egyszerű utasításokkal kezdenek, aztán további témákba is betekintést kapnak."
diff --git a/app/locale/id.coffee b/app/locale/id.coffee
index ff2139b2a..6def8abb0 100644
--- a/app/locale/id.coffee
+++ b/app/locale/id.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/it.coffee b/app/locale/it.coffee
index c4228803a..8e735028e 100644
--- a/app/locale/it.coffee
+++ b/app/locale/it.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
     recovered: "Acquisto precedente recuperato. Ricaricare la pagina."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/ja.coffee b/app/locale/ja.coffee
index db2757914..f900ca79b 100644
--- a/app/locale/ja.coffee
+++ b/app/locale/ja.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
     recovered: "前のジェム購入をリカバリーしました。ページを更新してください。"
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/ko.coffee b/app/locale/ko.coffee
index 4ec5d1542..f2dd7b2f4 100644
--- a/app/locale/ko.coffee
+++ b/app/locale/ko.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/lt.coffee b/app/locale/lt.coffee
index d0b368832..9e62fa347 100644
--- a/app/locale/lt.coffee
+++ b/app/locale/lt.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/mk-MK.coffee b/app/locale/mk-MK.coffee
index 33c90740f..f9486aec2 100644
--- a/app/locale/mk-MK.coffee
+++ b/app/locale/mk-MK.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     recovered: "Претходното купување на скапоцени камења е вратено од загуба. Те молам 'освежи' ја страната."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Зачлени се"
     unsubscribe: "Откажи членство"
     confirm_unsubscribe: "Потврди откажување на членство"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     heroes: "Помоќни херои!"
     gems: "3500 скапоцени камења секој месец!"
     items: "Над 250 дополнителни предмети и опрема!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "За родители"
     parents_title: "Вашето дете ќе научи да програмира."
     parents_blurb1: "Со CodeCombat, вашите деца учат преку пишување на вистински програмски код. Почнуваат со учење на едноставни команди, по што се продолжува на понапредни теми."
diff --git a/app/locale/ms.coffee b/app/locale/ms.coffee
index 1a36fb167..90ec80d45 100644
--- a/app/locale/ms.coffee
+++ b/app/locale/ms.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/nb.coffee b/app/locale/nb.coffee
index a50f34632..529c5aea7 100644
--- a/app/locale/nb.coffee
+++ b/app/locale/nb.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/nl-BE.coffee b/app/locale/nl-BE.coffee
index d57c21b5b..1275e949b 100644
--- a/app/locale/nl-BE.coffee
+++ b/app/locale/nl-BE.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
 #    heroes: "More powerful heroes!"
     gems: "Elke maand 3500 bonus juwelen!"
     items: "Meer dan 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Voor ouders"
     parents_title: "Uw kind zal de code leren."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/nl-NL.coffee b/app/locale/nl-NL.coffee
index 4ff90f2f2..bfa4f1e4b 100644
--- a/app/locale/nl-NL.coffee
+++ b/app/locale/nl-NL.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Abonneren"
     unsubscribe: "Abonnement pzeggen"
     confirm_unsubscribe: "Opzegging Bevestigen"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     heroes: "Sterkere helden!"
     gems: "3500 extra edelstenen elke maand!"
     items: "Meer dan 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Voor ouders"
     parents_title: "Uw kind leert programmeren."
     parents_blurb1: "Met CodeCombat leert uw kind door echte code te schrijven. Ze beginnen met simpele instructies en naarmate ze verder komen, komen er moeilijkere onderwerpen aan bod."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/nn.coffee b/app/locale/nn.coffee
index 67e4166b7..79db67511 100644
--- a/app/locale/nn.coffee
+++ b/app/locale/nn.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/no.coffee b/app/locale/no.coffee
index 6ec5a142d..c27697e6d 100644
--- a/app/locale/no.coffee
+++ b/app/locale/no.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/pl.coffee b/app/locale/pl.coffee
index 1507ee6fe..6cdea4df1 100644
--- a/app/locale/pl.coffee
+++ b/app/locale/pl.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/pt-BR.coffee b/app/locale/pt-BR.coffee
index 11ad83b1a..0cb6aa88b 100644
--- a/app/locale/pt-BR.coffee
+++ b/app/locale/pt-BR.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
     recovered: "Gems de compras anteriores Recuperadas. Por favor atualize a pagina."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Inscrever-se"
     unsubscribe: "Desinscrever-se"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
     heroes: "Mais poderosos heróis!"
     gems: "3500 gemas bônus todo mês!"
     items: "Mais de 250 itens bônus!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Para os pais"
     parents_title: "Seus filhos aprenderam código."
     parents_blurb1: "Com o CodeCombat, seus filhos aprendem a codificar de verdade. Eles começam a aprender comandos simples, e progridem para tópicos avançados."
diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee
index 4823fd50e..00c0b58b5 100644
--- a/app/locale/pt-PT.coffee
+++ b/app/locale/pt-PT.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
   leaderboard:
     leaderboard: "Tabela de Classificação"
     view_other_solutions: "Ver Outras Soluções"
+    scores: "Pontuações"
     top_solutions: "Melhores Soluções"
     day: "Hoje"
     week: "Esta Semana"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
     recovered: "Recuperada a compra de gemas anterior. Por favor atualiza a página."
 
   subscribe:
+    comparison_blurb: "Aperfeiçoa as tuas habilidades com uma subscrição do CodeCombat!"
+    feature1: "60+ níveis básicos dispersos por 4 mundos"
+    feature2: "7 <strong>heróis novos</strong> e poderosos com habilidades únicas!"
+    feature3: "30+ níveis de bónus"
+    feature4: "<strong>3500 gemas de bónus</strong> por mês!"
+    feature5: "Tutoriais em vídeo"
+    feature6: "Apoio por e-mail superior"
+    free: "Grátis"
+    month: "mês"
     subscribe_title: "Subscrever"
     unsubscribe: "Cancelar Subscrição"
     confirm_unsubscribe: "Confirmar Cancelamento da Subscrição"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
     heroes: "Heróis mais poderosos!"
     gems: "3500 gemas de bónus todos os meses!"
     items: "Mais de 250 itens de bónus!"
+    parent_button: "Pergunta ao teu educador"
+    parent_email_description: "Vamos mandar-lhe um e-mail para que ele possa comprar-te uma subscrição do CodeCombat."
+    parent_email_input_invalid: "Endereço de e-mail inválido."
+    parent_email_input_label: "Endereço de e-mail do educador"
+    parent_email_input_placeholder: "Introduz o e-mail do educador"
+    parent_email_send: "Enviar E-mail"
+    parent_email_sent: "E-mail enviado!"
+    parent_email_title: "Qual é o e-mail do teu educador?"
     parents: "Para Educadores"
     parents_title: "O teu educando vai aprender a programar."
     parents_blurb1: "Com o CodeCombat, o teu educando aprende ao escrever código real. Começa por aprender comandos simples e progride para tópicos mais avançados."
     parents_blurb2: "Por $9.99 USD/mês, recebe novos desafios todas as semanas e suporte pessoal, via e-mail, de programadores profissionais."
     parents_blurb3: "Sem Risco: 100% de garantia de devolução do dinheiro, com anulação fácil de 1 clique."
-    subscribe_button: "Subscrever Agora"
+    subscribe_button: "Subscrever"
     stripe_description: "Subscrição Mensal"
     subscription_required_to_play: "Precisas de uma subscrição para jogares este nível."
     unlock_help_videos: "Subscreve-te para desbloqueares todos os tutoriais em vídeo."
diff --git a/app/locale/ro.coffee b/app/locale/ro.coffee
index 0a919b1d8..214cd380c 100644
--- a/app/locale/ro.coffee
+++ b/app/locale/ro.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee
index 119cf8cef..abc4a84cd 100644
--- a/app/locale/ru.coffee
+++ b/app/locale/ru.coffee
@@ -322,7 +322,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
     tip_lines_of_code: "Измерение прогресса программирования в строках кода - это как измерять прогресс построения самолета по его весу. — Bill Gates"
     tip_source_code: "Я хочу изменить мир, но они вряд ли дадут мне исходники."
     tip_javascript_java: "Java к JavaScript относится так же, как кол относится к колготкам. - Chris Heilmann (перефраз.)"
-#    tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
+    tip_move_forward: "Что бы вы ни делали, вы должны двигаться вперед. - Martin Luther King Jr"
 
   game_menu:
     inventory_tab: "Инвентарь"
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
   leaderboard:
     leaderboard: "Таблица лидеров"
     view_other_solutions: "Посмотреть другие решения"
+    scores: "Рейтинг"
     top_solutions: "Лучшие решения"
     day: "Сегодня"
     week: "На этой неделе"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
     recovered: "Предыдущие покупки самоцветов восстановлены. Пожалуйста, обновите страницу."
 
   subscribe:
+    comparison_blurb: "Отточите свое мастерство багодаря подписке на CodeCombat!"
+    feature1: "60+ основных уровней на просторах 4-х миров"
+    feature2: "7 могущественных <strong>новых героев</strong> с уникальными способностями!"
+    feature3: "30+ дополнительных уровней"
+    feature4: "<strong>3500 бонусных самоцветов</strong> каждый месяц!"
+    feature5: "Обучающие видеоролики"
+    feature6: "Эксклюзивная поддержка по электронной почте"
+    free: "Бесплатно"
+    month: "месяц"
     subscribe_title: "Подпишись"
     unsubscribe: "Отписаться"
     confirm_unsubscribe: "Подтвердить отмену подписки"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
     heroes: "Более сильные герои!"
     gems: "3500 бонусных самоцветов каждый месяц!"
     items: "Более 250 бонусных предметов!"
+    parent_button: "Спросить у родителей"
+    parent_email_description: "Мы отправим им электронное письмо, чтобы они смогли приобрести тебе подписку на CodeCombat."
+    parent_email_input_invalid: "Адрес электронной почты введен неправильно."
+    parent_email_input_label: "Адрес электронной почты родителей"
+    parent_email_input_placeholder: "Введи адрес электронной почты родителей"
+    parent_email_send: "Отправить письмо"
+    parent_email_sent: "Письмо отправлено!"
+    parent_email_title: "Какой у твоих родителей адрес электронной почты?"
     parents: "Для Родителей"
     parents_title: "Ваш ребенок научится программировать."
     parents_blurb1: "С CodeCombat ваш ребенок учится через написание реального кода. Начиная с изучения простых команд, продолжая более продвинутыми темами."
diff --git a/app/locale/sk.coffee b/app/locale/sk.coffee
index 042fd45b4..b07e5a867 100644
--- a/app/locale/sk.coffee
+++ b/app/locale/sk.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/sl.coffee b/app/locale/sl.coffee
index 1e5c6c560..0ea3bf0f1 100644
--- a/app/locale/sl.coffee
+++ b/app/locale/sl.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee
index 08543f7d6..e40f80ddd 100644
--- a/app/locale/sr.coffee
+++ b/app/locale/sr.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/sv.coffee b/app/locale/sv.coffee
index 2d8efdc7f..7ac00fa06 100644
--- a/app/locale/sv.coffee
+++ b/app/locale/sv.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/th.coffee b/app/locale/th.coffee
index 881773bf8..f6a92482f 100644
--- a/app/locale/th.coffee
+++ b/app/locale/th.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/tr.coffee b/app/locale/tr.coffee
index 66b60f809..79d81b67f 100644
--- a/app/locale/tr.coffee
+++ b/app/locale/tr.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/uk.coffee b/app/locale/uk.coffee
index 4910b9288..d2744183c 100644
--- a/app/locale/uk.coffee
+++ b/app/locale/uk.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Українська", englishDescription:
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Українська", englishDescription:
     recovered: "Попередні покупки самоцвітів відновлені. Будь ласка, поновіть сторінку."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "Взяти абонемент"
     unsubscribe: "Скасувати абонемент"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "Українська", englishDescription:
     heroes: "Більше могутніх героїв!"
     gems: "Щомісячний бонус 3500 самоцвітів!"
     items: "Більше 250-ти бонусних предметів!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "Батькам"
     parents_title: "Ваша дитина вчитиметься програмувати."
     parents_blurb1: "Разом з CodeCombat Ваша дитина писатиме реальний код. Почне з простих команд та поступово буде розвиватись до складніших тем."
diff --git a/app/locale/ur.coffee b/app/locale/ur.coffee
index 4adbef941..7b52a14ef 100644
--- a/app/locale/ur.coffee
+++ b/app/locale/ur.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/vi.coffee b/app/locale/vi.coffee
index c0263785b..d5086d851 100644
--- a/app/locale/vi.coffee
+++ b/app/locale/vi.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/zh-HANS.coffee b/app/locale/zh-HANS.coffee
index 944fb3332..79f147e2e 100644
--- a/app/locale/zh-HANS.coffee
+++ b/app/locale/zh-HANS.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
     recovered: "之前购买的宝石已恢复。请刷新页面。"
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "订阅"
     unsubscribe: "取消订阅"
     confirm_unsubscribe: "确认取消订阅"
@@ -393,6 +403,14 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
     heroes: "更多强大的英雄!"
     gems: "每月多3500宝石奖励!"
     items: "超过250个物品奖励!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
     parents: "致家长"
     parents_title: "您的孩子将要学习编写程序。"
     parents_blurb1: "通过使用CodeCombat,您的孩子将学习编写真正的程序代码。他们将学到简单指令,进而处理更复杂的问题。"
diff --git a/app/locale/zh-HANT.coffee b/app/locale/zh-HANT.coffee
index 9f26cef31..1a4cba6d7 100644
--- a/app/locale/zh-HANT.coffee
+++ b/app/locale/zh-HANT.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
     recovered: "先前購買的寶石已回復. 請重新載入頁面."
 
   subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
     subscribe_title: "訂閱"
     unsubscribe: "取消訂閱"
     confirm_unsubscribe: "確認訂閱"
@@ -387,6 +397,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
     thank_you_months_prefix: "感謝您這幾個"
     thank_you_months_suffix: "月來的支持"
     thank_you: "感謝您支持CodeCombat."
+<<<<<<< HEAD
     sorry_to_see_you_go: "捨不得您離開! 請讓我們知道我們如何做得更好."
     unsubscribe_feedback_placeholder: "O, 我們做錯事了嗎?"
     levels: "獲得更多新關卡來磨練!"
@@ -398,6 +409,27 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
     parents_blurb1: "使用CodeCombat, 您的孩子學習真正的編寫程式. 他們學習從簡單的指令,漸進到更加進階的課題."
     parents_blurb2: "每月支付$9.99美金, 他們每週獲得新挑戰以及使用信件取得專業程式員的幫助."
     parents_blurb3: "沒有風險: 保證100%退費, 一步取消訂閱."
+=======
+#    sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
+#    unsubscribe_feedback_placeholder: "O, what have we done?"
+#    levels: "Get more practice with bonus levels!"
+#    heroes: "More powerful heroes!"
+#    gems: "3500 bonus gems every month!"
+#    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
+#    parents: "For Parents"
+#    parents_title: "Your child will learn to code."
+#    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
+#    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
+#    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
+>>>>>>> upstream/master
     subscribe_button: "現在訂閱"
     stripe_description: "每月訂閱"
     subscription_required_to_play: "你將需要訂閱來開啟這關."
@@ -1234,7 +1266,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
 #    other_developers: "Other Developers"
 #    inactive_developers: "Inactive Developers"
 
-#  admin:
+  admin:
 #    av_espionage: "Espionage" # Really not important to translate /admin controls.
     av_espionage_placeholder: "信箱或用戶名"
     av_usersearch: "用戶搜尋"
diff --git a/app/locale/zh-WUU-HANS.coffee b/app/locale/zh-WUU-HANS.coffee
index 10e264da3..32f17a6a3 100644
--- a/app/locale/zh-WUU-HANS.coffee
+++ b/app/locale/zh-WUU-HANS.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/locale/zh-WUU-HANT.coffee b/app/locale/zh-WUU-HANT.coffee
index 5d5fec3c5..88e83b6f3 100644
--- a/app/locale/zh-WUU-HANT.coffee
+++ b/app/locale/zh-WUU-HANT.coffee
@@ -344,6 +344,7 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
 #  leaderboard:
 #    leaderboard: "Leaderboard"
 #    view_other_solutions: "View Other Solutions"
+#    scores: "Scores"
 #    top_solutions: "Top Solutions"
 #    day: "Today"
 #    week: "This Week"
@@ -380,6 +381,15 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
 #  subscribe:
+#    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
+#    feature1: "60+ basic levels across 4 worlds"
+#    feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
+#    feature3: "30+ bonus levels"
+#    feature4: "<strong>3500 bonus gems</strong> every month!"
+#    feature5: "Video tutorials"
+#    feature6: "Premium email support"
+#    free: "Free"
+#    month: "month"
 #    subscribe_title: "Subscribe"
 #    unsubscribe: "Unsubscribe"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -393,12 +403,20 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
 #    heroes: "More powerful heroes!"
 #    gems: "3500 bonus gems every month!"
 #    items: "Over 250 bonus items!"
+#    parent_button: "Ask your parent"
+#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+#    parent_email_input_invalid: "Email address invalid."
+#    parent_email_input_label: "Parent email address"
+#    parent_email_input_placeholder: "Enter parent email"
+#    parent_email_send: "Send Email"
+#    parent_email_sent: "Email sent!"
+#    parent_email_title: "What's your parent's email?"
 #    parents: "For Parents"
 #    parents_title: "Your child will learn to code."
 #    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
 #    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
 #    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
+#    subscribe_button: "Subscribe"
 #    stripe_description: "Monthly Subscription"
 #    subscription_required_to_play: "You'll need a subscription to play this level."
 #    unlock_help_videos: "Subscribe to unlock all video tutorials."
diff --git a/app/models/User.coffee b/app/models/User.coffee
index 2dcc1dcc6..4239d3d90 100644
--- a/app/models/User.coffee
+++ b/app/models/User.coffee
@@ -148,6 +148,14 @@ module.exports = class User extends CocoModel
     application.tracker.identify leaderboardsGroup: @leaderboardsGroup unless me.isAdmin()
     @leaderboardsGroup
 
+  getShowsPortal: ->
+    return @showsPortal if @showsPortal?
+    group = me.get('testGroupNumber')
+    @showsPortal = if group < 128 then true else false
+    @showsPortal = true if me.isAdmin()
+    application.tracker.identify showsPortal: @showsPortal unless me.isAdmin()
+    @showsPortal
+
   getVideoTutorialStylesIndex: (numVideos=0)->
     # A/B Testing video tutorial styles
     # Not a constant number of videos available (e.g. could be 0, 1, 3, or 4 currently)
diff --git a/app/schemas/models/achievement.coffee b/app/schemas/models/achievement.coffee
index 289b5e2bd..98d4767ad 100644
--- a/app/schemas/models/achievement.coffee
+++ b/app/schemas/models/achievement.coffee
@@ -91,5 +91,6 @@ AchievementSchema.definitions = {}
 AchievementSchema.definitions['mongoQueryOperator'] = MongoQueryOperatorSchema
 AchievementSchema.definitions['mongoFindQuery'] = MongoFindQuerySchema
 c.extendTranslationCoverageProperties AchievementSchema
+c.extendPatchableProperties AchievementSchema
 
 module.exports = AchievementSchema
diff --git a/app/schemas/models/campaign.schema.coffee b/app/schemas/models/campaign.schema.coffee
index 34225115a..2c97dccc1 100644
--- a/app/schemas/models/campaign.schema.coffee
+++ b/app/schemas/models/campaign.schema.coffee
@@ -124,5 +124,6 @@ _.extend CampaignSchema.properties, {
 
 c.extendBasicProperties CampaignSchema, 'campaign'
 c.extendTranslationCoverageProperties CampaignSchema
+c.extendPatchableProperties CampaignSchema
 
 module.exports = CampaignSchema
diff --git a/app/schemas/models/patch.coffee b/app/schemas/models/patch.coffee
index 918644c8b..e2e056af9 100644
--- a/app/schemas/models/patch.coffee
+++ b/app/schemas/models/patch.coffee
@@ -1,6 +1,6 @@
 c = require './../schemas'
 
-patchables = ['level', 'thang_type', 'level_system', 'level_component', 'article']
+patchables = ['level', 'thang_type', 'level_system', 'level_component', 'article', 'achievement', 'campaign']
 
 PatchSchema = c.object({title: 'Patch', required: ['target', 'delta', 'commitMessage']}, {
   delta: {title: 'Delta', type: ['array', 'object']}
diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee
index 1d6396ac0..a499decdd 100644
--- a/app/schemas/models/user.coffee
+++ b/app/schemas/models/user.coffee
@@ -85,6 +85,12 @@ _.extend UserSchema.properties,
     recruitNotes: {$ref: '#/definitions/emailSubscription'}
     employerNotes: {$ref: '#/definitions/emailSubscription'}
 
+    oneTimes: c.array {title: 'One-time emails'},
+      c.object {title: 'One-time email', required: ['type', 'targetEmail']},
+        type: c.shortString() # E.g 'subscribe modal parent'
+        targetEmail: c.shortString()
+        sent: c.date() # Set when sent
+
   # server controlled
   permissions: c.array {}, c.shortString()
   dateCreated: c.date({title: 'Date Joined'})
@@ -272,7 +278,7 @@ _.extend UserSchema.properties,
   purchased: c.RewardSchema 'purchased with gems or money'
   spent: {type: 'number'}
   stripeCustomerID: { type: 'string' } # TODO: Migrate away from this property
-  
+
   stripe: c.object {}, {
     customerID: { type: 'string' }
     planID: { enum: ['basic'] }
diff --git a/app/styles/modal/subscribe-modal.sass b/app/styles/modal/subscribe-modal.sass
index 96a89f967..8a56a0895 100644
--- a/app/styles/modal/subscribe-modal.sass
+++ b/app/styles/modal/subscribe-modal.sass
@@ -51,36 +51,11 @@
     &:hover
       color: yellow
 
-
-  //- Selling points
-
-  #selling-points
-    position: absolute
-    left: 65px
-    top: 335px
-    width: 650px
-    font-weight: bold
-    line-height: 18px
-    color: black
-    font-family: $headings-font-family
-    font-size: 18px
-
-    .point
-      width: 150px
-      overflow: none
-      float: left
-      text-align: center
-      margin-right: 10px
-
-    #parents-info
-      position: absolute
-      right: 7px
-      top: 56px
-      text-decoration: underline
-      cursor: pointer
+  //- Popovers
 
   .popover
     z-index: 1050
+    min-width: 400px
 
     h3
       background: transparent
@@ -88,13 +63,67 @@
       font-size: 30px
       color: black
 
+  //- Sales image
+
+  .subscribe-image
+    position: absolute
+    top: 114px
+    right: 65px
+
+  //- Feature comparison table
+
+  .comparison-blurb
+    position: absolute
+    left: 10%
+    top: 132px
+    width: 450px
+    background: rgba(0, 0, 0, 0.0)
+    font-weight: normal
+    line-height: 18px
+    color: black
+    font-family: $headings-font-family
+    font-size: 18px
+
+  .comparison-table
+    position: absolute
+    left: 10%
+    top: 160px
+    width: 450px
+    background: rgba(0, 0, 0, 0.0)
+    thead
+      tr
+        th
+          font-size: 24px
+          font-variant: small-caps
+          font-family: "Open Sans Condensed", "Helvetica Neue", Helvetica, Arial, sans-serif
+          font-weight: 700
+          line-height: 1.1
+          color: #317EAC
+    tbody
+      font-size: 14px
+      .center-ok
+        text-align: center
+
+  //- Parent info popover link
+
+  #parents-info
+    position: absolute
+    left: 38px
+    top: 389px
+    text-decoration: underline
+    cursor: pointer
+    font-weight: bold
+    line-height: 18px
+    color: black
+    font-family: $headings-font-family
+    font-size: 18px
 
   //- Purchase button
 
   .purchase-button
     position: absolute
-    left: 73px
-    width: 600px
+    right: 24px
+    width: 400px
     height: 70px
     top: 430px
     font-size: 32px
@@ -116,6 +145,28 @@
       padding: 2px 0 0 2px
       color: white
 
+  //- Parent button
+  //- TODO: Add hover and active effects
+
+  .parent-button
+    position: absolute
+    left: 24px
+    width: 250px
+    height: 70px
+    top: 430px
+    font-size: 28px
+    line-height: 38px
+    border-style: solid
+    border-image: url(/images/common/button-background-warning-disabled.png) 14 20 20 20 fill round
+    border-width: 14px 20px 20px 20px
+    color: darken(white, 5%)
+
+  #email-parent-form
+    .email_invalid
+      color: red
+      display: none
+  #email-parent-complete
+    display: none
 
   //- Errors
 
diff --git a/app/styles/play/campaign-view.sass b/app/styles/play/campaign-view.sass
index e14d5ba5f..66e9ad6ef 100644
--- a/app/styles/play/campaign-view.sass
+++ b/app/styles/play/campaign-view.sass
@@ -243,6 +243,21 @@ $gameControlMargin: 30px
           min-width: 200px
           display: block
           margin: 10px auto 0 auto
+          position: relative
+
+          .badge
+            position: absolute
+            top: initial
+            left: initial
+            right: -25px
+            bottom: -25px
+            font-size: 20px
+            color: black
+            border: 1px solid black
+            background-color: rgb(232, 217, 87)
+            border-radius: 50%
+            opacity: 1
+            padding: 3px 9px
   
         &.complete
           .start-level, .view-solutions
@@ -433,6 +448,21 @@ $gameControlMargin: 30px
     &.vol-down .glyphicon.glyphicon-volume-down
       display: inline-block
 
+  #back-button
+    position: absolute
+    left: 70px
+    left: -webkit-calc(1% + 55px)
+    left: calc(1% + 55px)
+    top: 1%
+    padding: 3px 8px
+    @include opacity(0.75)
+
+    &:hover
+      @include opacity(1.0)
+
+    .glyphicon
+      font-size: 32px
+
   #campaign-status
     position: absolute
     left: 0
@@ -463,6 +493,78 @@ $gameControlMargin: 30px
   .particle-man
     z-index: 2
 
+  .portal
+    position: relative
+    width: 100%
+    height: 100%
+    background: transparent url(/images/pages/play/portal-background.png)
+    display: flex
+    align-items: center
+    justify-content: center
+
+    .portals
+      $campaignWidth: 317px
+      $campaignHeight: 634px
+      $campaignHoverScale: 1.2
+      width: 6 * $campaignWidth
+      height: $campaignHeight * $campaignHoverScale
+      flex-wrap: nowrap
+      display: flex
+      overflow: hidden
+
+      .campaign
+        width: $campaignWidth
+        height: $campaignHeight
+        margin-top: $campaignHeight * ($campaignHoverScale - 1) / 2
+        background: transparent url(/images/pages/play/portal-campaigns.png) no-repeat 0 0
+        display: inline-block
+        flex-shrink: 0
+        position: relative
+        cursor: pointer
+        // http://easings.net/#easeOutBack plus tweaked a bit: http://cubic-bezier.com/#.11,.67,.08,1.42
+        @include transition(0.25s cubic-bezier(0.11, 0.67, 0.8, 1.42))
+
+        &:hover
+          @include scale($campaignHoverScale)
+
+        &.silhouette
+          @include filter(contrast(50%) brightness(65%))
+          pointer-events: none
+
+        &.locked
+          @include filter(contrast(80%) brightness(80%))
+          pointer-events: none
+
+        &.forest
+          background-position: (-1 * $campaignWidth) 0
+        &.desert
+          background-position: (-2 * $campaignWidth) 0
+        &.mountain
+          background-position: (-3 * $campaignWidth) 0
+        &.ice
+          background-position: (-4 * $campaignWidth) 0
+        &.volcano
+          background-position: (-5 * $campaignWidth) 0
+
+        .campaign-label
+          position: absolute
+          top: 55%
+          width: 100%
+          text-align: center
+
+          .campaign-name, .levels-completed, .campaign-locked
+            margin: 0
+            color: white
+            text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
+
+          .levels-completed
+            font-size: 22px
+
+          .play-button
+            margin-top: 30px
+            min-width: 100px
+
+
 
 body.ipad #campaign-view
   // iPad only supports up to Kithgard Gates for now.
diff --git a/app/templates/core/subscribe-modal.jade b/app/templates/core/subscribe-modal.jade
index ecf13ba2d..e1621dee6 100644
--- a/app/templates/core/subscribe-modal.jade
+++ b/app/templates/core/subscribe-modal.jade
@@ -7,27 +7,67 @@
       #retrying-alert.alert.alert-danger(data-i18n="buy_gems.retrying")
 
     else
-      img(src="/images/pages/play/modal/subscribe-background.png")#subscribe-background
+      img#subscribe-background(src="/images/pages/play/modal/subscribe-background-blank.png")
+      img.subscribe-image(src="/images/pages/play/modal/subscribe-heroes.png")
 
       h1(data-i18n="subscribe.subscribe_title") Subscribe
 
       div#close-modal
         span.glyphicon.glyphicon-remove
 
-      #selling-points
-        #point-levels.point
-          .blurb(data-i18n="subscribe.levels")
-        #point-heroes.point
-          .blurb(data-i18n="subscribe.heroes")
-        #point-gems.point
-          .blurb(data-i18n="subscribe.gems")
-        #point-items.point
-          .blurb(data-i18n="subscribe.items")
+      div.comparison-blurb(data-i18n="subscribe.comparison_blurb")
+      table.table.table-condensed.table-bordered.comparison-table
+        thead
+          tr
+            th 
+            th(data-i18n="subscribe.free")
+            th
+              //- TODO: find a better way to localize '$9.99/month'
+              span $#{price}/
+              span(data-i18n="subscribe.month")
+        tbody
+          tr
+            td.feature-description
+              span(data-i18n="subscribe.feature1")
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+          tr
+            td.feature-description
+              span(data-i18n="[html]subscribe.feature2")
+            td 
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+          tr
+            td.feature-description
+              span(data-i18n="subscribe.feature3")
+            td 
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+          tr
+            td.feature-description
+              span(data-i18n="[html]subscribe.feature4")
+            td 
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+          tr
+            td.feature-description
+              span(data-i18n="subscribe.feature5")
+            td 
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+          tr
+            td.feature-description
+              span(data-i18n="subscribe.feature6")
+            td 
+            td.center-ok
+              span.glyphicon.glyphicon-ok
+      #parents-info(data-i18n="subscribe.parents")
 
-        #parents-info(data-i18n="subscribe.parents")
+      button.btn.btn-lg.btn-illustrated.purchase-button(data-i18n="subscribe.subscribe_title")
+      button.btn.btn-lg.btn-illustrated.parent-button(data-i18n="subscribe.parent_button")
 
-      button.btn.btn-lg.btn-illustrated.purchase-button(data-i18n="subscribe.subscribe_button")
-                
       if state === 'declined'
         #declined-alert.alert.alert-danger.alert-dismissible
           span(data-i18n="buy_gems.declined")
diff --git a/app/templates/editor/campaign/campaign-level-view.jade b/app/templates/editor/campaign/campaign-level-view.jade
index 21195bc60..a638db24e 100644
--- a/app/templates/editor/campaign/campaign-level-view.jade
+++ b/app/templates/editor/campaign/campaign-level-view.jade
@@ -59,6 +59,7 @@
           td Playtime
           td Complete
           td Changed
+          td Replay
       tbody
         - for (var i = 0; i < analytics.recentSessions.data.length; i++)
           tr.recent-session(data-player-id=analytics.recentSessions.data[i].creator, data-session-id=analytics.recentSessions.data[i]._id)
@@ -71,6 +72,9 @@
             else
               td false
             td= analytics.recentSessions.data[i].changed
+            td
+              button.btn.replay-button.btn-xs
+                .glyphicon.glyphicon-eye-open
 
   h4 Completion Rates
   if analytics.levelCompletions.loading
diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade
index 227a24da3..ee679fabc 100644
--- a/app/templates/play/campaign-view.jade
+++ b/app/templates/play/campaign-view.jade
@@ -1,54 +1,77 @@
-.map
-  .gradient.horizontal-gradient.top-gradient
-  .gradient.vertical-gradient.right-gradient
-  .gradient.horizontal-gradient.bottom-gradient
-  .gradient.vertical-gradient.left-gradient
-  .map-background(alt="", draggable="false")
+if campaign
+  .map
+    .gradient.horizontal-gradient.top-gradient
+    .gradient.vertical-gradient.right-gradient
+    .gradient.horizontal-gradient.bottom-gradient
+    .gradient.vertical-gradient.left-gradient
+    .map-background(alt="", draggable="false")
 
-  each level in levels
-    if !level.hidden
-      div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + (levelStatusMap[level.slug] || ""), data-level-slug=level.slug, data-level-original=level.original, title=i18n(level, 'name') + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
-        if level.unlocksHero && (!level.purchasedHero || editorMode)
-          img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png")
-        a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
-        if level.requiresSubscription
-          img.star(src="/images/pages/play/star.png")
-        if levelStatusMap[level.slug] === 'complete'
-          img.banner(src="/images/pages/play/level-banner-complete.png")
-        if levelStatusMap[level.slug] === 'started'
-          img.banner(src="/images/pages/play/level-banner-started.png")
-      div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || ""))
-      .level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
-        - var playCount = levelPlayCountMap[level.slug]
-        div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : ""))
-          .level-status
-          h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
-          - var description = i18n(level, 'description') || level.description || ""
-          .level-description!= marked(description)
-          if level.disabled
-            p
-              span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week.
-              a.spr(href="/contribute/adventurer")
-                strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
-              span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
-              
-          if !level.disabled && !level.locked
-            if playCount && playCount.sessions
-              .play-counts.hidden
-                span.spl.spr= playCount.sessions
-                span(data-i18n="play.players") players
-                span.spr , #{Math.round(playCount.playtime / 3600)}
-                span(data-i18n="play.hours_played") hours played
-            if levelStatusMap[level.slug] === 'complete'
-              button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug)
-                span(data-i18n="leaderboard.scores")
-            button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play
-    else if level.unlocksHero && !level.purchasedHero
-      img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png", style="left: #{level.position.x}%; bottom: #{level.position.y}%;")
-  
-  for adjacentCampaign in adjacentCampaigns
-    a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug)
-      span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id)
+    each level in levels
+      if !level.hidden
+        div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + (levelStatusMap[level.slug] || ""), data-level-slug=level.slug, data-level-original=level.original, title=i18n(level, 'name') + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
+          if level.unlocksHero && (!level.purchasedHero || editorMode)
+            img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png")
+          a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
+          if level.requiresSubscription
+            img.star(src="/images/pages/play/star.png")
+          if levelStatusMap[level.slug] === 'complete'
+            img.banner(src="/images/pages/play/level-banner-complete.png")
+          if levelStatusMap[level.slug] === 'started'
+            img.banner(src="/images/pages/play/level-banner-started.png")
+        div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + (levelStatusMap[level.slug] || ""))
+        .level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
+          - var playCount = levelPlayCountMap[level.slug]
+          div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : ""))
+            .level-status
+            h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
+            - var description = i18n(level, 'description') || level.description || ""
+            .level-description!= marked(description)
+            if level.disabled
+              p
+                span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week.
+                a.spr(href="/contribute/adventurer")
+                  strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
+                span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
+
+            if !level.disabled && !level.locked
+              if playCount && playCount.sessions
+                .play-counts.hidden
+                  span.spl.spr= playCount.sessions
+                  span(data-i18n="play.players") players
+                  span.spr , #{Math.round(playCount.playtime / 3600)}
+                  span(data-i18n="play.hours_played") hours played
+              if levelStatusMap[level.slug] === 'complete'
+                button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug)
+                  span(data-i18n="leaderboard.scores")
+              button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play
+      else if level.unlocksHero && !level.purchasedHero
+        img.hero-portrait(src="/file/db/thang.type/#{level.unlocksHero}/portrait.png", style="left: #{level.position.x}%; bottom: #{level.position.y}%;")
+
+    for adjacentCampaign in adjacentCampaigns
+      a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug)
+        span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id)
+
+else
+  .portal
+    .portals
+      for campaignSlug in ['dungeon', 'forest', 'desert', 'mountain', 'ice', 'volcano']
+        - var campaign = campaigns[campaignSlug];
+        div(class="campaign #{campaignSlug}" + (campaign ? "" : " silhouette") + (campaign && campaign.locked ? " locked" : ""), data-campaign-slug=campaignSlug)
+          .campaign-label
+            h2.campaign-name
+              if campaign
+                span= i18n(campaign.attributes, 'fullName')
+              else
+                span ???
+            if campaign && campaign.levelsTotal
+              h3.levels-completed
+                span= campaign.levelsCompleted
+                | /
+                span= campaign.levelsTotal
+            if campaign && campaign.locked
+              h3.campaign-locked(data-i18n="play.locked") Locked
+            else if campaign
+              btn(data-i18n="common.play").btn.btn-illustrated.btn-lg.btn-success.play-button
 
 .game-controls.header-font
   button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
@@ -85,7 +108,11 @@ button.btn.btn-lg.btn-inverse#volume-button(data-i18n="[title]play.adjust_volume
   .glyphicon.glyphicon-volume-down
   .glyphicon.glyphicon-volume-up
 
-if campaign.loaded
+if campaign
+  .btn.btn-lg.btn-inverse#back-button(data-i18n="[title]resources.campaigns", title="Campaigns")
+    .glyphicon.glyphicon-globe
+
+if campaign && campaign.loaded
   h1#campaign-status
     .campaign-status-background
       .campaign-name
diff --git a/app/views/core/CocoView.coffee b/app/views/core/CocoView.coffee
index 73835d28e..b94359362 100644
--- a/app/views/core/CocoView.coffee
+++ b/app/views/core/CocoView.coffee
@@ -241,7 +241,7 @@ module.exports = class CocoView extends Backbone.View
 
   showLoading: ($el=@$el) ->
     $el.find('>').addClass('hidden')
-    $el.append loadingScreenTemplate()
+    $el.append(loadingScreenTemplate()).i18n()
     @_lastLoading = $el
 
   hideLoading: ->
diff --git a/app/views/core/SubscribeModal.coffee b/app/views/core/SubscribeModal.coffee
index 01dfe153c..bf729b381 100644
--- a/app/views/core/SubscribeModal.coffee
+++ b/app/views/core/SubscribeModal.coffee
@@ -17,8 +17,9 @@ module.exports = class SubscribeModal extends ModalView
     'stripe:received-token': 'onStripeReceivedToken'
 
   events:
-    'click .purchase-button': 'onClickPurchaseButton'
     'click #close-modal': 'hide'
+    'click #parent-send': 'onClickParentSendButton'
+    'click .purchase-button': 'onClickPurchaseButton'
 
   constructor: (options) ->
     super(options)
@@ -34,6 +35,40 @@ module.exports = class SubscribeModal extends ModalView
 
   afterRender: ->
     super()
+    @setupParentButtonPopover()
+    @setupParentInfoPopover()
+
+  setupParentButtonPopover: ->
+    popoverTitle = $.i18n.t 'subscribe.parent_email_title'
+    popoverTitle += '<button type="button" class="close" onclick="$(&#39;.parent-button&#39;).popover(&#39;hide&#39;);">&times;</button>'
+    popoverContent = "<div id='email-parent-form'>"
+    popoverContent += "<p>#{$.i18n.t('subscribe.parent_email_description')}</p>"
+    popoverContent += "<form>"
+    popoverContent += "  <div class='form-group'>"
+    popoverContent += "    <label>#{$.i18n.t('subscribe.parent_email_input_label')}</label>"
+    popoverContent += "    <input id='parent-input' type='email' class='form-control' placeholder='#{$.i18n.t('subscribe.parent_email_input_placeholder')}'/>"
+    popoverContent += "  <div id='parent-email-validator' class='email_invalid'>#{$.i18n.t('subscribe.parent_email_input_invalid')}</div>"
+    popoverContent += "  </div>"
+    popoverContent += "  <button id='parent-send' type='submit' class='btn btn-default'>#{$.i18n.t('subscribe.parent_email_send')}</button>"
+    popoverContent += "</form>"
+    popoverContent += "</div>"
+    popoverContent += "<div id='email-parent-complete'>"
+    popoverContent += " <p>#{$.i18n.t('subscribe.parent_email_sent')}</p>"
+    popoverContent += " <button type='button' onclick='$(&#39;.parent-button&#39;).popover(&#39;hide&#39;);'>#{$.i18n.t('modal.close')}</button>"
+    popoverContent += "</div>"
+
+    @$el.find('.parent-button').popover(
+      animation: true
+      html: true
+      placement: 'top'
+      trigger: 'click'
+      title: popoverTitle
+      content: popoverContent
+      container: @$el
+    ).on 'shown.bs.popover', =>
+      application.tracker?.trackEvent 'Subscription ask parent button click', {}
+
+  setupParentInfoPopover: ->
     popoverTitle = $.i18n.t 'subscribe.parents_title'
     popoverContent = "<p>" + $.i18n.t('subscribe.parents_blurb1') + "</p>"
     popoverContent += "<p>" + $.i18n.t('subscribe.parents_blurb2') + "</p>"
@@ -50,6 +85,26 @@ module.exports = class SubscribeModal extends ModalView
     ).on 'shown.bs.popover', =>
       application.tracker?.trackEvent 'Subscription parent hover', {}
 
+  onClickParentSendButton: (e) ->
+    # TODO: Popover sometimes dismisses immediately after send
+
+    email = $('#parent-input').val()
+    unless /[\w\.]+@\w+\.\w+/.test email
+      $('#parent-input').parent().addClass('has-error')
+      $('#parent-email-validator').show()
+      return false
+
+    request = @supermodel.addRequestResource 'send_one_time_email', {
+      url: '/db/user/-/send_one_time_email'
+      data: {email: email, type: 'subscribe modal parent'}
+      method: 'POST'
+    }, 0
+    request.load()
+
+    $('#email-parent-form').hide()
+    $('#email-parent-complete').show()
+    false
+
   onClickPurchaseButton: (e) ->
     @playSound 'menu-button-click'
     return @openModalView new AuthModal() if me.get('anonymous')
diff --git a/app/views/editor/campaign/CampaignLevelView.coffee b/app/views/editor/campaign/CampaignLevelView.coffee
index 96358c032..3864a7686 100644
--- a/app/views/editor/campaign/CampaignLevelView.coffee
+++ b/app/views/editor/campaign/CampaignLevelView.coffee
@@ -16,6 +16,7 @@ module.exports = class CampaignLevelView extends CocoView
     'dblclick .recent-session': 'onDblClickRecentSession'
     'mouseenter .graph-point': 'onMouseEnterPoint'
     'mouseleave .graph-point': 'onMouseLeavePoint'
+    'click .replay-button': 'onClickReplay'
 
   constructor: (options, @level) ->
     super(options)
@@ -77,6 +78,11 @@ module.exports = class CampaignLevelView extends CocoView
     pointID = $(e.target).data('pointid')
     @$el.find(".graph-point-info-container[data-pointid=#{pointID}]").hide()
 
+  onClickReplay: (e) ->
+    sessionID = $(e.target).closest('tr').data 'session-id'
+    url = "/play/level/#{@level.get('slug')}?session=#{sessionID}&observing=true"
+    window.open url, '_blank'
+
   updateAnalyticsGraphData: ->
     # console.log 'updateAnalyticsGraphData'
     # Build graphs based on available @analytics data
diff --git a/app/views/i18n/I18NEditModelView.coffee b/app/views/i18n/I18NEditModelView.coffee
index 112f74350..89971cad5 100644
--- a/app/views/i18n/I18NEditModelView.coffee
+++ b/app/views/i18n/I18NEditModelView.coffee
@@ -140,9 +140,14 @@ module.exports = class I18NEditModelView extends RootView
       return _.isArray(delta.o) and delta.o.length is 1 and 'i18n' in delta.dataPath
     )
 
+    commitMessage = "Diplomat submission for lang #{@selectedLanguage}: #{flattened.length} change(s)."
+    save = false if @savedBefore
+
     if save
       modelToSave = @model.cloneNewMinorVersion()
       modelToSave.updateI18NCoverage() if modelToSave.get('i18nCoverage')
+      if @modelClass.schema.properties.commitMessage
+        modelToSave.set 'commitMessage', commitMessage
 
     else
       modelToSave = new Patch()
@@ -151,17 +156,21 @@ module.exports = class I18NEditModelView extends RootView
         'collection': _.string.underscored @model.constructor.className
         'id': @model.id
       }
-
-    if @modelClass.schema.properties.commitMessage
-      commitMessage = "Diplomat submission for lang #{@selectedLanguage}: #{flattened.length} change(s)."
       modelToSave.set 'commitMessage', commitMessage
 
     errors = modelToSave.validate()
     button = $(e.target)
     button.attr('disabled', 'disabled')
     return button.text('Failed to Submit Changes') if errors
-    res = modelToSave.save(null, {type: 'POST'})  # Override PUT so we can trigger postNewVersion logic
+    type = 'PUT'
+    if @modelClass.schema.properties.version or (not save)
+      # Override PUT so we can trigger postNewVersion logic
+      # or you're POSTing a Patch
+      type = 'POST'
+    res = modelToSave.save(null, {type: type}) 
     return button.text('Failed to Submit Changes') unless res
     button.text('Submitting...')
     res.error => button.text('Error Submitting Changes')
-    res.success => button.text('Submit Changes')
+    res.success =>
+      @savedBefore = true
+      button.text('Submit Changes')
diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee
index 8eb306f91..88a3d92fa 100644
--- a/app/views/play/CampaignView.coffee
+++ b/app/views/play/CampaignView.coffee
@@ -27,6 +27,11 @@ class LevelSessionsCollection extends CocoCollection
     super()
     @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID"
 
+class CampaignsCollection extends CocoCollection
+  url: '/db/campaign'
+  model: Campaign
+  project: ['name', 'fullName', 'i18n']
+
 module.exports = class CampaignView extends RootView
   id: 'campaign-view'
   template: template
@@ -41,18 +46,30 @@ module.exports = class CampaignView extends RootView
     'click .level-info-container .start-level': 'onClickStartLevel'
     'click .level-info-container .view-solutions': 'onClickViewSolutions'
     'click #volume-button': 'onToggleVolume'
+    'click #back-button': 'onClickBack'
+    'click .portal .campaign': 'onClickPortalCampaign'
+    'mouseenter .portals': 'onMouseEnterPortals'
+    'mouseleave .portals': 'onMouseLeavePortals'
+    'mousemove .portals': 'onMouseMovePortals'
 
-  constructor: (options, @terrain='dungeon') ->
+  constructor: (options, @terrain) ->
     super options
-    options ?= {}
-
-    @campaign = new Campaign({_id:@terrain})
-    @campaign = @supermodel.loadModel(@campaign, 'campaign').model
-
-    @editorMode = options.editorMode
+    @editorMode = options?.editorMode
+    if @editorMode
+      @terrain ?= 'dungeon'
+    else unless me.getShowsPortal()
+      @terrain ?= 'dungeon'
     @levelStatusMap = {}
     @levelPlayCountMap = {}
     @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model
+    @listenToOnce @sessions, 'sync', @onSessionsLoaded
+    unless @terrain
+      @campaigns = @supermodel.loadCollection(new CampaignsCollection(), 'campaigns', null, 0).model
+      @listenToOnce @campaigns, 'sync', @onCampaignsLoaded
+      return
+
+    @campaign = new Campaign({_id:@terrain})
+    @campaign = @supermodel.loadModel(@campaign, 'campaign').model
 
     # Temporary attempt to make sure all earned rewards are accounted for. Figure out a better solution...
     @earnedAchievements = new CocoCollection([], {url: '/db/earned_achievement', model:EarnedAchievement, project: ['earnedRewards']})
@@ -69,7 +86,6 @@ module.exports = class CampaignView extends RootView
 
     @supermodel.loadCollection(@earnedAchievements, 'achievements')
 
-    @listenToOnce @sessions, 'sync', @onSessionsLoaded
     @listenToOnce @campaign, 'sync', @getLevelPlayCounts
     $(window).on 'resize', @onWindowResize
     @probablyCachedMusic = storage.load("loaded-menu-music")
@@ -91,7 +107,7 @@ module.exports = class CampaignView extends RootView
 
   destroy: ->
     @setupManager?.destroy()
-    @$el.find('.ui-draggable').draggable 'destroy'
+    @$el.find('.ui-draggable').off().draggable 'destroy'
     $(window).off 'resize', @onWindowResize
     if ambientSound = @ambientSound
       # Doesn't seem to work; stops immediately.
@@ -99,6 +115,7 @@ module.exports = class CampaignView extends RootView
     @musicPlayer?.destroy()
     clearTimeout @playMusicTimeout
     @particleMan?.destroy()
+    clearInterval @portalScrollInterval
     super()
 
   getLevelPlayCounts: ->
@@ -124,6 +141,7 @@ module.exports = class CampaignView extends RootView
     @fullyRendered = true
     @render()
     @preloadTopHeroes() unless me.get('heroConfig')?.thangType
+    @$el.find('#campaign-status').delay(4000).animate({top: "-=58"}, 1000) unless @terrain is 'dungeon'
 
   setCampaign: (@campaign) ->
     @render()
@@ -135,31 +153,16 @@ module.exports = class CampaignView extends RootView
   getRenderData: (context={}) ->
     context = super(context)
     context.campaign = @campaign
-    context.levels = _.values($.extend true, {}, @campaign.get('levels'))
-    context.levelsCompleted = context.levelsTotal = 0
-    for level in context.levels
-      level.position ?= { x: 10, y: 10 }
-      level.locked = not me.ownsLevel level.original
-      level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
-      level.locked = false if @editorMode
-      level.locked = false if @campaign.get('name') is 'Auditions'
-      level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
-      level.color = 'rgb(255, 80, 60)'
-      if level.requiresSubscription
-        level.color = 'rgb(80, 130, 200)'
-      if unlocksHero = _.find(level.rewards, 'hero')?.hero
-        level.unlocksHero = unlocksHero
-      if level.unlocksHero
-        level.purchasedHero = level.unlocksHero in (me.get('purchased')?.heroes or [])
-      level.hidden = level.locked
-      unless level.disabled
-        ++context.levelsTotal
-        ++context.levelsCompleted if @levelStatusMap[level.slug] is 'complete'
+    context.levels = _.values($.extend true, {}, @campaign?.get('levels') ? {})
+    @annotateLevel level for level in context.levels
+    count = @countLevels context.levels
+    context.levelsCompleted = count.completed
+    context.levelsTotal = count.total
 
-    @determineNextLevel context.levels if @sessions.loaded
+    @determineNextLevel context.levels if @sessions?.loaded
     # put lower levels in last, so in the world map they layer over one another properly.
     context.levels = (_.sortBy context.levels, (l) -> l.position.y).reverse()
-    @campaign.renderedLevels = context.levels
+    @campaign.renderedLevels = context.levels if @campaign
 
     context.levelStatusMap = @levelStatusMap
     context.levelPlayCountMap = @levelPlayCountMap
@@ -167,7 +170,7 @@ module.exports = class CampaignView extends RootView
     context.mapType = _.string.slugify @terrain
     context.requiresSubscription = @requiresSubscription
     context.editorMode = @editorMode
-    context.adjacentCampaigns = _.filter _.values(_.cloneDeep(@campaign.get('adjacentCampaigns') or {})), (ac) =>
+    context.adjacentCampaigns = _.filter _.values(_.cloneDeep(@campaign?.get('adjacentCampaigns') or {})), (ac) =>
       return false if ac.showIfUnlocked and (ac.showIfUnlocked not in me.levels()) and not @editorMode
       ac.name = utils.i18n ac, 'name'
       styles = []
@@ -180,6 +183,26 @@ module.exports = class CampaignView extends RootView
       return true
     context.marked = marked
     context.i18n = utils.i18n
+
+    if @campaigns
+      context.campaigns = {}
+      for campaign in @campaigns.models
+        context.campaigns[campaign.get('slug')] = campaign
+        if @sessions.loaded
+          levels = _.values($.extend true, {}, campaign.get('levels') ? {})
+          count = @countLevels levels
+          campaign.levelsTotal = count.total
+          campaign.levelsCompleted = count.completed
+          if campaign.get('slug') is 'dungeon'
+            campaign.locked = false
+          else unless campaign.levelsTotal
+            campaign.locked = true
+          else
+            campaign.locked = true
+      for campaign in @campaigns.models
+        for acID, ac of campaign.get('adjacentCampaigns') ? {}
+          _.find(@campaigns.models, id: acID)?.locked = false if ac.showIfUnlocked in me.levels()
+
     context
 
   afterRender: ->
@@ -189,7 +212,7 @@ module.exports = class CampaignView extends RootView
       _.defer => @$el?.find('.game-controls .btn').addClass('has-tooltip').tooltip()  # Have to defer or i18n doesn't take effect.
       view = @
       @$el.find('.level, .campaign-switch').addClass('has-tooltip').tooltip().each ->
-        return unless me.isAdmin()
+        return unless me.isAdmin() and view.editorMode
         $(@).draggable().on 'dragstop', ->
           bg = $('.map-background')
           x = ($(@).offset().left - bg.offset().left + $(@).outerWidth() / 2) / bg.width()
@@ -202,7 +225,7 @@ module.exports = class CampaignView extends RootView
     unless window.currentModal or not @fullyRendered
       @highlightElement '.level.next', delay: 500, duration: 60000, rotation: 0, sides: ['top']
       if @editorMode
-        for level in @campaign.renderedLevels
+        for level in @campaign?.renderedLevels ? []
           for nextLevelOriginal in level.nextLevels ? []
             if nextLevel = _.find(@campaign.renderedLevels, original: nextLevelOriginal)
               @createLine level.position, nextLevel.position
@@ -219,6 +242,32 @@ module.exports = class CampaignView extends RootView
     authModal.mode = 'signup'
     @openModalView authModal
 
+  annotateLevel: (level) ->
+    level.position ?= { x: 10, y: 10 }
+    level.locked = not me.ownsLevel level.original
+    level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
+    level.locked = false if @editorMode
+    level.locked = false if @campaign?.get('name') is 'Auditions'
+    level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
+    level.color = 'rgb(255, 80, 60)'
+    if level.requiresSubscription
+      level.color = 'rgb(80, 130, 200)'
+    if unlocksHero = _.find(level.rewards, 'hero')?.hero
+      level.unlocksHero = unlocksHero
+    if level.unlocksHero
+      level.purchasedHero = level.unlocksHero in (me.get('purchased')?.heroes or [])
+    level.hidden = level.locked
+    level
+
+  countLevels: (levels) ->
+    count = total: 0, completed: 0
+    for level in levels
+      @annotateLevel level unless level.locked?  # Annotate if we haven't already.
+      unless level.disabled
+        ++count.total
+        ++count.completed if @levelStatusMap[level.slug] is 'complete'
+    count
+
   showLeaderboard: (levelSlug) ->
     #levelSlug ?= 'siege-of-stonehold'  # Testing
     leaderboardModal = new LeaderboardModal supermodel: @supermodel, levelSlug: levelSlug
@@ -248,7 +297,7 @@ module.exports = class CampaignView extends RootView
     line.append($('<div class="line">')).append($('<div class="point">'))
 
   applyCampaignStyles: ->
-    return unless @campaign.loaded
+    return unless @campaign?.loaded
     if (backgrounds = @campaign.get 'backgroundImage') and backgrounds.length
       backgrounds = _.sortBy backgrounds, 'width'
       backgrounds.reverse()
@@ -266,7 +315,7 @@ module.exports = class CampaignView extends RootView
     @playAmbientSound()
 
   testParticles: ->
-    return unless @campaign.loaded and me.getForeshadowsLevels()
+    return unless @campaign?.loaded and me.getForeshadowsLevels()
     @particleMan ?= new ParticleMan()
     @particleMan.removeEmitters()
     @particleMan.attach @$el.find('.map')
@@ -279,18 +328,62 @@ module.exports = class CampaignView extends RootView
       continue if particleKey.length is 2  # Don't show basic levels
       @particleMan.addEmitter level.position.x / 100, level.position.y / 100, particleKey.join('-')
 
+  onMouseEnterPortals: (e) ->
+    return unless @campaigns?.loaded and @sessions?.loaded
+    @portalScrollInterval = setInterval @onMouseMovePortals, 100
+    @onMouseMovePortals e
+
+  onMouseLeavePortals: (e) ->
+    return unless @portalScrollInterval
+    clearInterval @portalScrollInterval
+    @portalScrollInterval = null
+
+  onMouseMovePortals: (e) =>
+    return unless @portalScrollInterval
+    $portal = @$el.find('.portal')
+    $portals = @$el.find('.portals')
+    if e
+      @portalOffsetX = Math.round Math.max 0, e.clientX - $portal.offset().left
+    bodyWidth = $('body').innerWidth()
+    fraction = @portalOffsetX / bodyWidth
+    return if 0.2 < fraction < 0.8
+    direction = if fraction < 0.5 then 1 else -1
+    magnitude = 0.2 * bodyWidth * (if direction is -1 then fraction - 0.8 else 0.2 - fraction) / 0.2
+    portalsWidth = 1902  # TODO: if we add campaigns or change margins, this will get out of date...
+    scrollTo = $portals.offset().left + direction * magnitude
+    scrollTo = Math.max bodyWidth - portalsWidth, scrollTo
+    scrollTo = Math.min 0, scrollTo
+    $portals.stop().animate {marginLeft: scrollTo}, 100, 'linear'
+
   onSessionsLoaded: (e) ->
     return if @editorMode
     for session in @sessions.models
       @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
     @render()
 
+  onCampaignsLoaded: (e) ->
+    @render()
+
+  preloadLevel: (levelSlug) ->
+    levelURL = "/db/level/#{levelSlug}"
+    level = new Level().setURL levelURL
+    level = @supermodel.loadModel(level, 'level', null, 0).model
+    sessionURL = "/db/level/#{levelSlug}/session"
+    #@preloadedSession = new LevelSession().setURL sessionURL
+    #@preloadedSession.levelSlug = levelSlug
+    #@preloadedSession.fetch()
+    #@listenToOnce @preloadedSession, 'sync', @onSessionPreloaded
+
+  onSessionPreloaded: (session) ->
+    levelElement = @$el.find('.level-info-container:visible')
+    return unless session.levelSlug is levelElement.data 'level-slug'
+    return unless difficulty = session.get('state')?.difficulty
+    badge = $("<span class='badge'>#{difficulty}</span>")
+    levelElement.find('.start-level .badge').remove()
+    levelElement.find('.start-level').append badge
+
   onClickMap: (e) ->
     @$levelInfo?.hide()
-    # Easy-ish way of figuring out coordinates for placing level dots.
-    x = e.offsetX / @$el.find('.map-background').width()
-    y = (1 - e.offsetY / @$el.find('.map-background').height())
-    console.log "    x: #{(100 * x).toFixed(2)}\n    y: #{(100 * y).toFixed(2)}\n"
 
   onClickLevel: (e) ->
     e.preventDefault()
@@ -304,6 +397,7 @@ module.exports = class CampaignView extends RootView
     @$levelInfo = @$el.find(".level-info-container[data-level-slug=#{levelSlug}]").show()
     @adjustLevelInfoPosition e
     @endHighlight()
+    @preloadLevel levelSlug
 
   onDoubleClickLevel: (e) ->
     return unless @editorMode
@@ -326,7 +420,9 @@ module.exports = class CampaignView extends RootView
 
   startLevel: (levelElement) ->
     @setupManager?.destroy()
-    @setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelElement.data('level-slug'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @
+    levelSlug = levelElement.data 'level-slug'
+    session = @preloadedSession if @preloadedSession?.loaded and @preloadedSession.levelSlug is levelSlug
+    @setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelSlug, levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @, session: session
     @setupManager.open()
     @$levelInfo?.hide()
 
@@ -421,9 +517,24 @@ module.exports = class CampaignView extends RootView
         newI = 2
     @updateVolume volumes[newI]
 
+  onClickBack: (e) ->
+    Backbone.Mediator.publish 'router:navigate',
+      route: "/play"
+      viewClass: CampaignView
+      viewArgs: [{supermodel: @supermodel}]
+
   updateHero: ->
     return unless hero = me.get('heroConfig')?.thangType
     for slug, original of ThangType.heroes when original is hero
       @$el.find('.player-hero-icon').removeClass().addClass('player-hero-icon ' + slug)
       return
     console.error "CampaignView hero update couldn't find hero slug for original:", hero
+
+  onClickPortalCampaign: (e) ->
+    campaign = $(e.target).closest('.campaign')
+    return if campaign.is('.locked') or campaign.is('.silhouette')
+    campaignSlug = campaign.data('campaign-slug')
+    Backbone.Mediator.publish 'router:navigate',
+      route: "/play/#{campaignSlug}"
+      viewClass: CampaignView
+      viewArgs: [{supermodel: @supermodel}, campaignSlug]
diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee
index b493fa064..1a5135970 100644
--- a/app/views/play/level/ControlBarView.coffee
+++ b/app/views/play/level/ControlBarView.coffee
@@ -64,6 +64,8 @@ module.exports = class ControlBarView extends CocoView
       c.multiplayerStatus = @multiplayerStatusManager?.status
     if @level.get 'replayable'
       c.levelDifficulty = @session.get('state')?.difficulty ? 0
+      if @observing
+        c.levelDifficulty = Math.max 0, c.levelDifficulty - 1  # Show the difficulty they won, not the next one.
       c.difficultyTitle = "#{$.i18n.t 'play.level_difficulty'}#{c.levelDifficulty}"
       @lastDifficulty = c.levelDifficulty
     c.spectateGame = @spectateGame
@@ -78,9 +80,8 @@ module.exports = class ControlBarView extends CocoView
       @homeLink = c.homeLink = '/play'
       @homeViewClass = 'views/play/CampaignView'
       campaign = @level.get 'campaign'
-      if campaign isnt 'dungeon'
-        @homeLink += '/' + campaign
-        @homeViewArgs.push campaign
+      @homeLink += '/' + campaign
+      @homeViewArgs.push campaign
     else
       @homeLink = c.homeLink = '/'
       @homeViewClass = 'views/HomeView'
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index d2afbc082..48b302763 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -127,7 +127,7 @@ module.exports = class PlayLevelView extends RootView
   load: ->
     @loadStartTime = new Date()
     @god = new God debugWorker: true
-    @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team')
+    @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team'), observing: @observing
     @listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded
 
   trackLevelLoadEnd: ->
diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee
index 18d6ecbac..c2512e1b0 100644
--- a/app/views/play/level/modal/HeroVictoryModal.coffee
+++ b/app/views/play/level/modal/HeroVictoryModal.coffee
@@ -326,7 +326,7 @@ module.exports = class HeroVictoryModal extends ModalView
   getNextLevelLink: ->
     link = '/play'
     nextCampaign = @getNextLevelCampaign()
-    link += '/' + nextCampaign unless nextCampaign is 'dungeon'
+    link += '/' + nextCampaign
     link
 
   onClickContinue: (e, extraOptions=null) ->
diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee
index e70be5443..861ee31d3 100644
--- a/app/views/play/level/tome/SpellView.coffee
+++ b/app/views/play/level/tome/SpellView.coffee
@@ -785,17 +785,14 @@ module.exports = class SpellView extends CocoView
     if @_singleLineCommentRegex
       @_singleLineCommentRegex.lastIndex = 0
       return @_singleLineCommentRegex
-    commentStarts =
-      javascript: '//'
-      python: '#'
-      coffeescript: '#'
-      clojure: ';'
-      lua: '--'
-      io: '//'
     commentStart = commentStarts[@spell.language] or '//'
     @_singleLineCommentRegex = new RegExp "[ \t]*#{commentStart}[^\"'\n]*", 'g'
     @_singleLineCommentRegex
-
+  
+  commentOutMyCode: ->
+    prefix = if @spell.language is 'javascript' then 'return;  ' else 'return  '
+    comment = prefix + commentStarts[@spell.language]
+    
   preload: ->
     # Send this code over to the God for preloading, but don't change the cast state.
     oldSource = @spell.source
@@ -1096,3 +1093,11 @@ module.exports = class SpellView extends CocoView
     @toolbarView?.destroy()
     $(window).off 'resize', @onWindowResize
     super()
+    
+commentStarts =
+  javascript: '//'
+  python: '#'
+  coffeescript: '#'
+  clojure: ';'
+  lua: '--'
+  io: '//'
diff --git a/app/views/play/level/tome/TomeView.coffee b/app/views/play/level/tome/TomeView.coffee
index 7886c0132..41510424f 100644
--- a/app/views/play/level/tome/TomeView.coffee
+++ b/app/views/play/level/tome/TomeView.coffee
@@ -80,7 +80,7 @@ module.exports = class TomeView extends CocoView
   onCommentMyCode: (e) ->
     for spellKey, spell of @spells when spell.canWrite()
       console.log 'Commenting out', spellKey
-      commentedSource = 'return;  // Commented out to stop infinite loop.\n' + spell.getSource()
+      commentedSource = spell.view.commentOutMyCode() + 'Commented out to stop infinite loop.\n' + spell.getSource()
       spell.view.updateACEText commentedSource
       spell.view.recompile false
     @cast()
@@ -166,7 +166,10 @@ module.exports = class TomeView extends CocoView
       sessionState.flagHistory = _.filter sessionState.flagHistory ? [], (event) => event.team isnt (@options.session.get('team') ? 'humans')
       sessionState.lastUnsuccessfulSubmissionTime = new Date() if @options.level.get 'replayable'
       @options.session.set 'state', sessionState
-    Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: sessionState.difficulty ? 0
+    difficulty = sessionState.difficulty ? 0
+    if @options.observing
+      difficulty = Math.max 0, difficulty - 1  # Show the difficulty they won, not the next one.
+    Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: difficulty
 
   onToggleSpellList: (e) ->
     @spellList.rerenderEntries()
diff --git a/karma.conf.js b/karma.conf.js
index 00ba3af7f..c7ed0175d 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -9,6 +9,7 @@ module.exports = function(config) {
 
     // list of files / patterns to load in the browser
     files : [
+      'public/javascripts/vendor.js', // need for jade definition...
       'public/javascripts/whole-vendor.js',
       'public/lib/ace/ace.js',
       'public/javascripts/aether.js',
diff --git a/scripts/analytics/mongodb/queries/helpUsage.js b/scripts/analytics/mongodb/queries/helpUsage.js
new file mode 100644
index 000000000..094ba9aff
--- /dev/null
+++ b/scripts/analytics/mongodb/queries/helpUsage.js
@@ -0,0 +1,156 @@
+// Help button and video usage
+
+// Usage:
+// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
+
+// What do we want to know?
+// For each level, how many clicks, starts, finishes
+// Individual users only counted once for a level/event combo
+
+try {
+  var scriptStartTime = new Date();
+  var analyticsStringCache = {};
+
+  // Look at last 30 days, same as Mixpanel
+  var numDays = 30;
+
+  var startDay = new Date();
+  today = startDay.toISOString().substr(0, 10);
+  startDay.setUTCDate(startDay.getUTCDate() - numDays);
+  startDay = startDay.toISOString().substr(0, 10);
+
+  log("Today is " + today);
+  log("Start day is " + startDay);
+
+  var events = ['Problem alert help clicked', 'Spell palette help clicked', 'Start help video', 'Finish help video'];
+
+  var helpData = getHelpData(startDay, events);
+  helpData.sort(function (a,b) {
+    var clickedA = a['Problem alert help clicked'] || 0;
+    clickedA += a['Spell palette help clicked'] || 0;
+    var clickedB = b['Problem alert help clicked'] || 0;
+    clickedB += b['Spell palette help clicked'] || 0;
+    return clickedA < clickedB ? 1 : -1;
+  });
+
+  log('Help Clicks\tVideo Starts\tStart Rate\tVideo Finishes\tFinish Rate\tLevel')
+  for(var i = 0; i < helpData.length; i++) {
+    var level = helpData[i].level;
+    var clicked = helpData[i]['Problem alert help clicked'] || 0;
+    clicked += helpData[i]['Spell palette help clicked'] || 0;
+    var started = helpData[i]['Start help video'] || 0;
+    var startRate = clicked > 0 ? started / clicked * 100 : 0.0;
+    var finished = helpData[i]['Finish help video'] || 0;
+    var finishRate = clicked > 0 ? finished / clicked * 100 : 0.0;
+    if (started > 1) {
+      log(clicked + '\t' + started + '\t' + startRate.toFixed(2) + '%\t' + finished + '\t' + finishRate.toFixed(2) + '%\t' + level);
+    }
+  }
+
+  log("Script runtime: " + (new Date() - scriptStartTime));
+}
+catch(err) {
+  log("ERROR: " + err);
+  printjson(err);
+}
+
+// *** Helper functions ***
+
+function log(str) {
+  print(new Date().toISOString() + " " + str);
+}
+
+function objectIdWithTimestamp(timestamp) {
+  // Convert string date to Date object (otherwise assume timestamp is a date)
+  if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
+  // Convert date object to hex seconds since Unix epoch
+  var hexSeconds = Math.floor(timestamp/1000).toString(16);
+  // Create an ObjectId with that hex timestamp
+  var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
+  return constructedObjectId
+}
+
+function getAnalyticsString(str) {
+  if (analyticsStringCache[str]) return analyticsStringCache[str];
+
+  // Find existing string
+  var doc = db['analytics.strings'].findOne({v: str});
+  if (doc) {
+    analyticsStringCache[str] = doc._id;
+    return analyticsStringCache[str];
+  }
+
+  // TODO: Not sure we want to always insert strings here.
+  // // Insert string
+  // // http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/#auto-increment-optimistic-loop
+  // doc = {v: str};
+  // while (true) {
+  //   var cursor = db['analytics.strings'].find({}, {_id: 1}).sort({_id: -1}).limit(1);
+  //   var seq = cursor.hasNext() ? cursor.next()._id + 1 : 1;
+  //   doc._id = seq;
+  //   var results = db['analytics.strings'].insert(doc);
+  //   if (results.hasWriteError()) {
+  //     if ( results.writeError.code == 11000 /* dup key */ ) continue;
+  //     else throw new Error("ERROR: Unexpected error inserting data: " + tojson(results));
+  //   }
+  //   break;
+  // }
+  //
+  // // Find new string entry
+  // doc = db['analytics.strings'].findOne({v: str});
+  // if (doc) {
+  //   analyticsStringCache[str] = doc._id;
+  //   return analyticsStringCache[str];
+  // }
+  throw new Error("ERROR: Did not find analytics.strings insert for: " + str);
+}
+
+function getHelpData(startDay, events) {
+  if (!startDay || !events || events.length === 0) return {};
+
+  var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
+  var queryParams = {$and: [{_id: {$gte: startObj}},{"event": {$in: events}}]};
+  var cursor = db['analytics.log.events'].find(queryParams);
+
+  // Map ordering: level, user, event
+  var levelUserEventMap = {};
+  while (cursor.hasNext()) {
+    var doc = cursor.next();
+    var created = doc._id.getTimestamp().toISOString();
+    var event = doc.event;
+    var user = doc.user.valueOf();
+    var properties = doc.properties;
+    var level = properties.level || properties.levelID;
+
+    if (!levelUserEventMap[level]) levelUserEventMap[level] = {};
+    if (!levelUserEventMap[level][user]) levelUserEventMap[level][user] = {};
+    if (!levelUserEventMap[level][user][event]) levelUserEventMap[level][user][event] = 1;
+  }
+  // printjson(levelUserEventMap);
+
+  // Data: level, event, count
+  var levelEventMap = {};
+  for (level in levelUserEventMap) {
+    for (user in levelUserEventMap[level]) {
+      for (event in levelUserEventMap[level][user]) {
+        if (!levelEventMap[level]) levelEventMap[level] = {};
+        if (!levelEventMap[level][event]) levelEventMap[level][event] = 0;
+        levelEventMap[level][event] += levelUserEventMap[level][user][event];
+      }
+    }
+  }
+  // printjson(levelEventMap);
+
+  helpData = [];
+  for (level in levelEventMap) {
+    var data = {level: level};
+    for (event in levelEventMap[level]) {
+      data[event] = levelEventMap[level][event];
+    }
+    for (var i = 0; i < events.length; i++) {
+      if (!data[events[i]]) data[events[i]] = 0
+    }
+    helpData.push(data);
+  }
+  return helpData;
+}
diff --git a/scripts/analytics/subscriptionStats.js b/scripts/analytics/subscriptionStats.js
new file mode 100644
index 000000000..ddc3c3697
--- /dev/null
+++ b/scripts/analytics/subscriptionStats.js
@@ -0,0 +1,69 @@
+// To use: set the range you want below, make sure your environment has the stripe key, then run:
+// node scripts/analytics/subscriptions.js 
+
+require('coffee-script');
+require('coffee-script/register');
+config = require('../../server_config');
+if(config.stripe.secretKey.indexOf('sk_test_')==0) {
+  throw new Error('You should not run this on the test data... Get your environment in gear.');
+}
+
+stripe = require('stripe')(config.stripe.secretKey);
+
+var range = {
+  gt: ''+(new Date('2015-01-01').getTime()/1000),
+  lt: ''+(new Date('2015-02-01').getTime()/1000)
+}; 
+
+begin = function(starting_after) {
+  var query = {date: range, limit: 100};
+  if(starting_after) {
+    query.starting_after = starting_after;
+  }
+  stripe.invoices.list(query, onInvoicesReceived);
+}
+
+customersPaid = []
+
+onInvoicesReceived = function(err, invoices) {
+  for(var i in invoices.data) {
+    var invoice = invoices.data[i];
+    if(!invoice.paid) { continue; }
+    customersPaid.push(invoice.customer);
+  }
+  if(invoices.has_more) {
+    console.log('Loaded', customersPaid.length, 'invoices.')
+    begin(invoices.data[i].id);
+  }
+  else {
+    console.log('How many customers paid for a subscription:', customersPaid.length);
+    loadNewCustomers();
+  }
+}
+
+loadNewCustomers = function(starting_after) {
+  query = {created: range, limit: 100};
+  if(starting_after) {
+    query.starting_after = starting_after;
+  }
+  stripe.customers.list(query, onCustomersReceived);
+}
+
+newCustomersPaid = [];
+
+onCustomersReceived = function(err, customers) {
+  for(var i in customers.data) {
+    var customer = customers.data[i];
+    if(customersPaid.indexOf(customer.id) == -1) { continue; }
+    newCustomersPaid.push(customer.id);
+  }
+  if(customers.has_more) {
+    console.log('Loaded', newCustomersPaid.length, 'new customers.');
+    loadNewCustomers(customers.data[i].id);
+  }
+  else {
+    console.log('How many new customers paid for a subscription:', newCustomersPaid.length);
+  }
+}
+
+begin();
\ No newline at end of file
diff --git a/server/achievements/Achievement.coffee b/server/achievements/Achievement.coffee
index d3046546e..be576e532 100644
--- a/server/achievements/Achievement.coffee
+++ b/server/achievements/Achievement.coffee
@@ -81,6 +81,7 @@ AchievementSchema.post 'save', -> @constructor.loadAchievements()
 AchievementSchema.plugin(plugins.NamedPlugin)
 AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
 AchievementSchema.plugin plugins.TranslationCoveragePlugin
+AchievementSchema.plugin plugins.PatchablePlugin
 
 module.exports = Achievement = mongoose.model('Achievement', AchievementSchema, 'achievements')
 
diff --git a/server/analytics/AnalyticsLogEvent.coffee b/server/analytics/AnalyticsLogEvent.coffee
index be9a5b461..f5a5b3791 100644
--- a/server/analytics/AnalyticsLogEvent.coffee
+++ b/server/analytics/AnalyticsLogEvent.coffee
@@ -1,5 +1,7 @@
+log = require 'winston'
 mongoose = require 'mongoose'
 plugins = require '../plugins/plugins'
+utils = require '../lib/utils'
 
 AnalyticsLogEventSchema = new mongoose.Schema({
   u: mongoose.Schema.Types.ObjectId
@@ -14,4 +16,102 @@ AnalyticsLogEventSchema = new mongoose.Schema({
 
 AnalyticsLogEventSchema.index({event: 1, _id: 1})
 
+AnalyticsLogEventSchema.statics.logEvent = (user, event, properties) ->
+  unless user?
+    log.warn 'No user given to analytics logEvent.'
+    return
+
+  saveDoc = (eventID, slimProperties) ->
+    doc = new AnalyticsLogEvent
+      u: user
+      e: eventID
+      p: slimProperties
+      # TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
+      user: user
+      event: event
+      properties: properties
+    doc.save()
+
+  utils.getAnalyticsStringID event, (eventID) ->
+    if eventID > 0
+      # TODO: properties slimming is pretty ugly
+      slimProperties = _.cloneDeep properties
+      if event in ['Clicked Level', 'Show problem alert', 'Started Level', 'Saw Victory', 'Problem alert help clicked', 'Spell palette help clicked']
+        delete slimProperties.level if event is 'Saw Victory'
+        properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
+        slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
+        if slimProperties.levelID?
+          # levelID: string => l: string ID
+          utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
+            if levelStringID > 0
+              delete slimProperties.levelID
+              slimProperties.l = levelStringID
+            saveDoc eventID, slimProperties
+          return
+      else if event in ['Script Started', 'Script Ended']
+        properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
+        slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
+        if slimProperties.levelID? and slimProperties.label?
+          # levelID: string => l: string ID
+          # label: string => lb: string ID
+          utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
+            if levelStringID > 0
+              delete slimProperties.levelID
+              slimProperties.l = levelStringID
+            utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
+              if labelStringID > 0
+                delete slimProperties.label
+                slimProperties.lb = labelStringID
+              saveDoc eventID, slimProperties
+          return
+      else if event is 'Heard Sprite'
+        properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
+        slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
+        if slimProperties.message?
+          # message: string => m: string ID
+          utils.getAnalyticsStringID slimProperties.message, (messageStringID) ->
+            if messageStringID > 0
+              delete slimProperties.message
+              slimProperties.m = messageStringID
+            saveDoc eventID, slimProperties
+          return
+      else if event in ['Start help video', 'Finish help video']
+        properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
+        slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
+        if slimProperties.level and slimProperties.style?
+          # level: string => l: string ID
+          # style: string => s: string ID
+          utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
+            if levelStringID > 0
+              delete slimProperties.level
+              slimProperties.l = levelStringID
+            utils.getAnalyticsStringID slimProperties.style, (styleStringID) ->
+              if styleStringID > 0
+                delete slimProperties.style
+                slimProperties.s = styleStringID
+              saveDoc eventID, slimProperties
+          return
+      else if event is 'Show subscription modal'
+        delete properties.category
+        delete slimProperties.category
+        if slimProperties.label?
+          # label: string => lb: string ID
+          utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
+            if labelStringID > 0
+              delete slimProperties.label
+              slimProperties.lb = labelStringID
+            if slimProperties.level?
+              # level: string => l: string ID
+              utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
+                if levelStringID > 0
+                  delete slimProperties.level
+                  slimProperties.l = levelStringID
+                saveDoc eventID, slimProperties
+              return
+            saveDoc eventID, slimProperties
+          return
+      saveDoc eventID, slimProperties
+    else
+      log.warn "Unable to get analytics string ID for " + event
+
 module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)
diff --git a/server/analytics/analytics_log_event_handler.coffee b/server/analytics/analytics_log_event_handler.coffee
index 91039b731..f31a17234 100644
--- a/server/analytics/analytics_log_event_handler.coffee
+++ b/server/analytics/analytics_log_event_handler.coffee
@@ -39,102 +39,7 @@ class AnalyticsLogEventHandler extends Handler
     event = req.query.event or req.body.event
     properties = req.query.properties or req.body.properties
     @sendSuccess res # Return request immediately
-    unless user?
-      log.warn 'No user given to analytics logEvent.'
-      return
-
-    saveDoc = (eventID, slimProperties) ->
-      doc = new AnalyticsLogEvent 
-        u: user
-        e: eventID
-        p: slimProperties
-        # TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
-        user: user
-        event: event
-        properties: properties
-      doc.save()
-
-    utils.getAnalyticsStringID event, (eventID) ->
-      if eventID > 0
-        # TODO: properties slimming is pretty ugly
-        slimProperties = _.cloneDeep properties
-        if event in ['Clicked Level', 'Show problem alert', 'Started Level', 'Saw Victory', 'Problem alert help clicked', 'Spell palette help clicked']
-          delete slimProperties.level if event is 'Saw Victory'
-          properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
-          slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
-          if slimProperties.levelID?
-            # levelID: string => l: string ID
-            utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
-              if levelStringID > 0
-                delete slimProperties.levelID
-                slimProperties.l = levelStringID
-              saveDoc eventID, slimProperties
-            return
-        else if event in ['Script Started', 'Script Ended']
-          properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
-          slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
-          if slimProperties.levelID? and slimProperties.label?
-            # levelID: string => l: string ID
-            # label: string => lb: string ID
-            utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
-              if levelStringID > 0
-                delete slimProperties.levelID
-                slimProperties.l = levelStringID
-              utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
-                if labelStringID > 0
-                  delete slimProperties.label
-                  slimProperties.lb = labelStringID
-                saveDoc eventID, slimProperties
-            return
-        else if event is 'Heard Sprite'
-          properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
-          slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
-          if slimProperties.message?
-            # message: string => m: string ID
-            utils.getAnalyticsStringID slimProperties.message, (messageStringID) ->
-              if messageStringID > 0
-                delete slimProperties.message
-                slimProperties.m = messageStringID
-              saveDoc eventID, slimProperties
-            return
-        else if event in ['Start help video', 'Finish help video']
-          properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
-          slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
-          if slimProperties.level and slimProperties.style?
-            # level: string => l: string ID
-            # style: string => s: string ID
-            utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
-              if levelStringID > 0
-                delete slimProperties.level
-                slimProperties.l = levelStringID
-              utils.getAnalyticsStringID slimProperties.style, (styleStringID) ->
-                if styleStringID > 0
-                  delete slimProperties.style
-                  slimProperties.s = styleStringID
-                saveDoc eventID, slimProperties
-            return
-        else if event is 'Show subscription modal'
-          delete properties.category
-          delete slimProperties.category
-          if slimProperties.label?
-            # label: string => lb: string ID
-            utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
-              if labelStringID > 0
-                delete slimProperties.label
-                slimProperties.lb = labelStringID
-              if slimProperties.level?
-                # level: string => l: string ID
-                utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
-                  if levelStringID > 0
-                    delete slimProperties.level
-                    slimProperties.l = levelStringID
-                  saveDoc eventID, slimProperties
-                return
-              saveDoc eventID, slimProperties
-            return
-        saveDoc eventID, slimProperties
-      else
-        log.warn "Unable to get analytics string ID for " + event
+    AnalyticsLogEvent.logEvent user, event, properties
 
   getLevelCompletionsBySlug: (req, res) ->
     # Returns an array of per-day level starts and finishes
@@ -204,7 +109,7 @@ class AnalyticsLogEventHandler extends Handler
       for level of levelDateMap
         completions[level] = []
         for created, item of levelDateMap[level]
-          completions[level].push 
+          completions[level].push
             level: level
             created: created
             started: Object.keys(item.started).length
@@ -382,7 +287,7 @@ class AnalyticsLogEventHandler extends Handler
         getUserEventData campaigns
 
     getCampaignData = () =>
-      # Get campaign data 
+      # Get campaign data
       # Output:
       # campaigns - per-campaign dictionary of ordered levelIDs
       # campaignLevelIDs - dictionary of all campaign levelIDs
diff --git a/server/analytics/analytics_perday_handler.coffee b/server/analytics/analytics_perday_handler.coffee
index 29429879c..50a34c6c4 100644
--- a/server/analytics/analytics_perday_handler.coffee
+++ b/server/analytics/analytics_perday_handler.coffee
@@ -97,7 +97,7 @@ class AnalyticsPerDayHandler extends Handler
           @sendSuccess res, completions
 
     getLevelData = (campaignLevels) =>
-      # 2. Get ordered level slugs and string ID to level slug mappping
+      # 2. Get ordered level slugs and string ID to level slug mapping
       # Input:
       # campaignLevels - array of Level IDs
 
diff --git a/server/campaigns/Campaign.coffee b/server/campaigns/Campaign.coffee
index b32d72cf1..4d536ca08 100644
--- a/server/campaigns/Campaign.coffee
+++ b/server/campaigns/Campaign.coffee
@@ -8,5 +8,6 @@ CampaignSchema.index({slug: 1}, {name: 'slug index', sparse: true, unique: true}
 
 CampaignSchema.plugin(plugins.NamedPlugin)
 CampaignSchema.plugin(plugins.TranslationCoveragePlugin)
+CampaignSchema.plugin plugins.PatchablePlugin
 
 module.exports = mongoose.model('campaign', CampaignSchema)
diff --git a/server/campaigns/campaign_handler.coffee b/server/campaigns/campaign_handler.coffee
index 891fef35f..3d07e8eba 100644
--- a/server/campaigns/campaign_handler.coffee
+++ b/server/campaigns/campaign_handler.coffee
@@ -24,6 +24,15 @@ CampaignHandler = class CampaignHandler extends Handler
   hasAccess: (req) ->
     req.method is 'GET' or req.user?.isAdmin()
 
+  get: (req, res) ->
+    return @sendForbiddenError(res) if not @hasAccess(req)
+    # We don't have normal text search or anything set up to make /db/campaign work, so we'll just give them all campaigns, no problem.
+    q = @modelClass.find {}
+    q.exec (err, documents) =>
+      return @sendDatabaseError(res, err) if err
+      documents = (@formatEntity(req, doc) for doc in documents)
+      @sendSuccess(res, documents)
+
   getByRelationship: (req, res, args...) ->
     relationship = args[1]
     if relationship in ['levels', 'achievements']
diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee
index ecb6ba893..a6362bab2 100644
--- a/server/commons/Handler.coffee
+++ b/server/commons/Handler.coffee
@@ -34,7 +34,7 @@ module.exports = class Handler
   hasAccessToDocument: (req, document, method=null) ->
     return true if req.user?.isAdmin()
 
-    if @modelClass.schema.uses_coco_translation_coverage and (method or req.method).toLowerCase() is 'post'
+    if @modelClass.schema.uses_coco_translation_coverage and (method or req.method).toLowerCase() in ['post', 'put']
       return true if @isJustFillingTranslations(req, document)
 
     if @modelClass.schema.uses_coco_permissions
@@ -461,8 +461,9 @@ module.exports = class Handler
     sendwithus.api.send context, (err, result) ->
 
   sendChangedHipChatMessage: (options) ->
-    message = "#{options.creator.get('name')} saved a change to <a href=\"#{options.docLink}\">#{options.target.get('name')}</a>: #{options.target.get('commitMessage')}"
-    hipchat.sendHipChatMessage message
+    message = "#{options.creator.get('name')} saved a change to <a href=\"#{options.docLink}\">#{options.target.get('name')}</a>: #{options.target.get('commitMessage') or '(no commit message)'}"
+    rooms = if /Diplomat submission/.test(message) then ['main'] else ['main', 'artisans']
+    hipchat.sendHipChatMessage message, rooms
 
   makeNewInstance: (req) ->
     model = new @modelClass({})
diff --git a/server/hipchat.coffee b/server/hipchat.coffee
index e8b72fca2..0d872c2d3 100644
--- a/server/hipchat.coffee
+++ b/server/hipchat.coffee
@@ -2,32 +2,34 @@ config = require '../server_config'
 request = require 'request'
 log = require 'winston'
 
-module.exports.sendHipChatMessage = sendHipChatMessage = (message) ->
-  return unless key = config.hipchatAPIKey
-  return unless config.isProduction
-  roomID = 254598
-  form =
-    color: 'yellow'
-    notify: false
-    message: message
-    messageFormat: 'html'
-  url = "https://api.hipchat.com/v2/room/#{roomID}/notification?auth_token=#{key}"
-  request.post {uri: url, json: form}, (err, res, body) ->
-    return log.error 'Error sending HipChat patch request:', err or body if err or /error/i.test body
-    #log.info "Got HipChat patch response:", body
+roomIDMap =
+  main: 254598
+  artisans: 1146994
+  tower: 318356
 
-module.exports.sendTowerHipChatMessage = sendTowerHipChatMessage = (message) ->
-  secondsFromEpoch = Math.floor(new Date().getTime() / 1000)
-  link = "<a href=\"https://papertrailapp.com/groups/488214/events?time=#{secondsFromEpoch}\">PaperTrail</a>"
-  message = "#{message} #{link}"
-  return unless key = config.hipchatTowerAPIKey
+module.exports.sendHipChatMessage = sendHipChatMessage = (message, rooms, options) ->
   return unless config.isProduction
-  roomID = 318356
-  form =
-    color: 'red'
-    notify: true
-    message: message
-    messageFormat: 'html'
-  url = "https://api.hipchat.com/v2/room/#{roomID}/notification?auth_token=#{key}"
-  request.post {uri: url, json: form}, (err, res, body) ->
-    return log.error 'Error sending HipChat Tower message:', err or body if err or /error/i.test body
+  rooms ?= ['main']
+  options ?= {}
+  for room in rooms
+    unless roomID = roomIDMap[room]
+      log.error "Unknown HipChat room #{room}."
+      continue
+    unless key = config.hipchat[room]
+      log.info "No HipChat API key for room #{room}."
+      continue
+    form =
+      color: options.color or 'yellow'
+      notify: false
+      message: message
+      messageFormat: 'html'
+    if options.papertrail
+      secondsFromEpoch = Math.floor(new Date().getTime() / 1000)
+      link = "<a href=\"https://papertrailapp.com/groups/488214/events?time=#{secondsFromEpoch}\">PaperTrail</a>"
+      form.message = "#{message} #{link}"
+      form.color = options.color or 'red'
+      form.notify = true
+    url = "https://api.hipchat.com/v2/room/#{roomID}/notification?auth_token=#{key}"
+    request.post {uri: url, json: form}, (err, res, body) ->
+      return log.error 'Error sending HipChat message:', err or body if err or /error/i.test body
+      #log.info "Got HipChat message response:", body
diff --git a/server/patches/patch_handler.coffee b/server/patches/patch_handler.coffee
index 3a6cd4ff7..9593d8c83 100644
--- a/server/patches/patch_handler.coffee
+++ b/server/patches/patch_handler.coffee
@@ -101,6 +101,6 @@ PatchHandler = class PatchHandler extends Handler
 
   sendPatchCreatedHipChatMessage: (options) ->
     message = "#{options.creator.get('name')} submitted a patch to <a href=\"#{options.docLink}\">#{options.target.get('name')}</a>: #{options.patch.get('commitMessage')}"
-    hipchat.sendHipChatMessage message
+    hipchat.sendHipChatMessage message, ['main']
 
 module.exports = new PatchHandler()
diff --git a/server/payments/payment_handler.coffee b/server/payments/payment_handler.coffee
index fbd47af15..9e24be60e 100644
--- a/server/payments/payment_handler.coffee
+++ b/server/payments/payment_handler.coffee
@@ -35,7 +35,7 @@ PaymentHandler = class PaymentHandler extends Handler
   editableProperties: []
   postEditableProperties: ['purchased']
   jsonSchema: require '../../app/schemas/models/payment.schema'
-  
+
   get: (req, res) ->
     return res.send([]) unless req.user
     q = Payment.find({recipient:req.user._id})
@@ -43,7 +43,7 @@ PaymentHandler = class PaymentHandler extends Handler
       return @sendDatabaseError(res, err) if err
       res.send(payments)
     )
-    
+
   logPaymentError: (req, msg) ->
     console.warn "Payment Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'"
 
@@ -57,10 +57,10 @@ PaymentHandler = class PaymentHandler extends Handler
   post: (req, res, pathName) ->
     if pathName is 'check-stripe-charges'
       return @checkStripeCharges(req, res)
-    
+
     if (not req.user) or req.user.isAnonymous()
       return @sendForbiddenError(res)
-    
+
     appleReceipt = req.body.apple?.rawReceipt
     appleTransactionID = req.body.apple?.transactionID
     appleLocalPrice = req.body.apple?.localPrice
@@ -146,7 +146,7 @@ PaymentHandler = class PaymentHandler extends Handler
         if validation.valid is false
           @logPaymentError(req, 'Invalid apple payment object.')
           return @sendBadInputError(res, validation.errors)
-          
+
         payment.save((err) =>
           if err
             @logPaymentError(req, 'Apple payment save error.'+err)
@@ -170,24 +170,24 @@ PaymentHandler = class PaymentHandler extends Handler
     # First, make sure we save the payment info as a Customer object, if we haven't already.
     if token
       customerID = req.user.get('stripe')?.customerID
-      
+
       if customerID
         # old customer, new token. Save it.
         stripe.customers.update customerID, { card: token }, (err, customer) =>
           @beginStripePayment(req, res, timestamp, productID)
-          
+
       else
         newCustomer = {
           card: token
           email: req.user.get('email')
           metadata: { id: req.user._id + '', slug: req.user.get('slug') }
         }
-        
+
         stripe.customers.create newCustomer, (err, customer) =>
           if err
             @logPaymentError(req, 'Stripe customer creation error. '+err)
             return @sendDatabaseError(res, err)
-          
+
           stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
           stripeInfo.customerID = customer.id
           req.user.set('stripe', stripeInfo)
@@ -223,7 +223,7 @@ PaymentHandler = class PaymentHandler extends Handler
       ((err, results) =>
         if err
           @logPaymentError(req, 'Stripe async load db error. '+err)
-          return @sendDatabaseError(res, err) 
+          return @sendDatabaseError(res, err)
         [payment, charge] = results
 
         if not (payment or charge)
@@ -285,7 +285,7 @@ PaymentHandler = class PaymentHandler extends Handler
       timestamp: parseInt(charge.metadata.timestamp)
       chargeID: charge.id
     }
-    
+
     validation = @validateDocumentInput(payment.toObject())
     if validation.valid is false
       @logPaymentError(req, 'Invalid stripe payment object.')
@@ -302,9 +302,9 @@ PaymentHandler = class PaymentHandler extends Handler
       )
     )
 
-    
+
   #- Confirm all Stripe charges are recorded on our server
-  
+
   checkStripeCharges: (req, res) ->
     return @sendSuccess(res) unless customerID = req.user.get('stripe')?.customerID
     async.parallel([
@@ -366,7 +366,7 @@ PaymentHandler = class PaymentHandler extends Handler
   sendPaymentHipChatMessage: (options) ->
     try
       message = "#{options.user?.get('name')} bought #{options.payment?.get('amount')} via #{options.payment?.get('service')}."
-      hipchat.sendHipChatMessage message
+      hipchat.sendHipChatMessage message, ['tower']
     catch e
       log.error "Couldn't send HipChat message on payment because of error: #{e}"
 
diff --git a/server/purchases/purchase_handler.coffee b/server/purchases/purchase_handler.coffee
index cb00a4a67..dbcd5408c 100644
--- a/server/purchases/purchase_handler.coffee
+++ b/server/purchases/purchase_handler.coffee
@@ -4,8 +4,6 @@ Handler = require '../commons/Handler'
 {handlers} = require '../commons/mapping'
 mongoose = require 'mongoose'
 log = require 'winston'
-sendwithus = require '../sendwithus'
-hipchat = require '../hipchat'
 
 PurchaseHandler = class PurchaseHandler extends Handler
   modelClass: Purchase
@@ -19,22 +17,22 @@ PurchaseHandler = class PurchaseHandler extends Handler
     purchase.set 'recipient', req.user._id
     purchase.set 'created', new Date().toISOString()
     purchase
-    
+
   post: (req, res) ->
     purchased = req.body.purchased
     purchaser = req.user._id
     purchasedOriginal = purchased?.original
-    
+
     Handler = require '../commons/Handler'
     return @sendBadInputError(res) if not Handler.isID(purchasedOriginal)
-  
+
     collection = purchased?.collection
     return @sendBadInputError(res) if not collection in @jsonSchema.properties.purchased.properties.collection.enum
-    
+
     handler = require('../' + handlers[collection])
     criteria = { 'original': purchasedOriginal }
     sort = { 'version.major': -1, 'version.minor': -1 }
-    
+
     handler.modelClass.findOne(criteria).sort(sort).exec (err, purchasedItem) =>
       gemsOwned = req.user.get('earned')?.gems or 0
       return @sendDatabaseError(res, err) if err
@@ -51,7 +49,7 @@ PurchaseHandler = class PurchaseHandler extends Handler
         if purchase
           @addPurchaseToUser(req, res)
           return @sendSuccess(res, @formatEntity(req, purchase))
-          
+
         else
           super(req, res)
 
diff --git a/server/routes/db.coffee b/server/routes/db.coffee
index 232705e17..8bcc8e231 100644
--- a/server/routes/db.coffee
+++ b/server/routes/db.coffee
@@ -48,7 +48,7 @@ module.exports.setup = (app) ->
       log.error(error.stack)
       # TODO: Generally ignore this error: error: Error trying db method get route analytics.log.event from undefined: Error: Cannot find module '../undefined'
       unless "#{parts}" in ['analytics.users.active']
-        hipchat.sendTowerHipChatMessage errorMessage
+        hipchat.sendHipChatMessage errorMessage, ['tower'], papertrail: true
       errors.notFound(res, "Route #{req?.path} not found.")
 
 getSchema = (req, res, moduleName) ->
diff --git a/server/sendwithus.coffee b/server/sendwithus.coffee
index ff9cdf8b4..d8411b622 100644
--- a/server/sendwithus.coffee
+++ b/server/sendwithus.coffee
@@ -10,6 +10,7 @@ module.exports.api = new sendwithusAPI swuAPIKey, debug
 if config.unittest
   module.exports.api.send = ->
 module.exports.templates =
+  parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
   welcome_email: 'utnGaBHuSU4Hmsi7qrAypU'
   ladder_update_email: 'JzaZxf39A4cKMxpPZUfWy4'
   patch_created: 'tem_xhxuNosLALsizTNojBjNcL'
diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee
index 2dd8d2fd2..4cb163064 100644
--- a/server/users/user_handler.coffee
+++ b/server/users/user_handler.coffee
@@ -9,6 +9,7 @@ errors = require '../commons/errors'
 async = require 'async'
 log = require 'winston'
 moment = require 'moment'
+AnalyticsLogEvent = require '../analytics/AnalyticsLogEvent'
 LevelSession = require '../levels/sessions/LevelSession'
 LevelSessionHandler = require '../levels/sessions/level_session_handler'
 SubscriptionHandler = require '../payments/subscription_handler'
@@ -17,6 +18,7 @@ EarnedAchievement = require '../achievements/EarnedAchievement'
 UserRemark = require './remarks/UserRemark'
 {isID} = require '../lib/utils'
 hipchat = require '../hipchat'
+sendwithus = require '../sendwithus'
 
 serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset', 'lastIP']
 candidateProperties = [
@@ -232,6 +234,7 @@ UserHandler = class UserHandler extends Handler
     return @getRemark(req, res, args[0]) if args[1] is 'remark'
     return @searchForUser(req, res) if args[1] is 'admin_search'
     return @getStripeInfo(req, res, args[0]) if args[1] is 'stripe'
+    return @sendOneTimeEmail(req, res, args[0]) if args[1] is 'send_one_time_email'
     return @sendNotFoundError(res)
     super(arguments...)
 
@@ -244,6 +247,37 @@ UserHandler = class UserHandler extends Handler
         return @sendDatabaseError(res, err) if err
         @sendSuccess(res, JSON.stringify(customer, null, '\t'))
 
+  sendOneTimeEmail: (req, res) ->
+    # TODO: should this API be somewhere else?
+    return @sendForbiddenError(res) unless req.user
+    email = req.query.email or req.body.email
+    type = req.query.type or req.body.type
+    return @sendBadInputError res, 'No email given.' unless email?
+    return @sendBadInputError res, 'No type given.' unless type?
+
+    return @sendBadInputError res, "Unknown one-time email type #{type}" unless type is 'subscribe modal parent'
+
+    emailParams =
+      email_id: sendwithus.templates.parent_subscribe_email
+      recipient:
+        address: email
+      email_data:
+        name: req.user.get('name') or ''
+    if codeLanguage = req.user.get('aceConfig.language')
+      codeLanguage = codeLanguage[0].toUpperCase() + codeLanguage.slice(1)
+      emailParams['email_data']['codeLanguage'] = codeLanguage
+    sendwithus.api.send emailParams, (err, result) =>
+      if err
+        log.error "sendwithus one-time email error: #{err}, result: #{result}"
+        return @sendError res, 500, 'send mail failed.'
+      req.user.update {$push: {"emails.oneTimes": {type: type, email: email, sent: new Date()}}}, (err) =>
+        return @sendDatabaseError(res, err) if err
+        req.user.save (err) =>
+          return @sendDatabaseError(res, err) if err
+          @sendSuccess(res, {result: 'success'})
+          hipchat.sendHipChatMessage "#{req.user.get('name')} #{req.user.get('email')} submitted a subscribe modal parent email #{email}", ['tower']
+          AnalyticsLogEvent.logEvent req.user, 'Sent one time email', email: email, type: type
+
   agreeToCLA: (req, res) ->
     return @sendForbiddenError(res) unless req.user
     doc =
@@ -260,7 +294,7 @@ UserHandler = class UserHandler extends Handler
         req.user.save (err) =>
           return @sendDatabaseError(res, err) if err
           @sendSuccess(res, {result: 'success'})
-          hipchat.sendHipChatMessage "#{req.body.githubUsername or req.user.get('name')} just signed the CLA."
+          hipchat.sendHipChatMessage "#{req.body.githubUsername or req.user.get('name')} just signed the CLA.", ['main']
 
   avatar: (req, res, id) ->
     @modelClass.findById(id).exec (err, document) =>
diff --git a/server_config.coffee b/server_config.coffee
index 4b426be48..3d9fd2e43 100644
--- a/server_config.coffee
+++ b/server_config.coffee
@@ -48,8 +48,11 @@ config.mail =
   cronHandlerPublicIP: process.env.COCO_CRON_PUBLIC_IP or ''
   cronHandlerPrivateIP: process.env.COCO_CRON_PRIVATE_IP or ''
 
-config.hipchatAPIKey = process.env.COCO_HIPCHAT_API_KEY or ''
-config.hipchatTowerAPIKey = process.env.COCO_HIPCHAT_TOWER_API_KEY or ''
+config.hipchat =
+  main: process.env.COCO_HIPCHAT_API_KEY or ''
+  tower: process.env.COCO_HIPCHAT_TOWER_API_KEY or ''
+  artisans: process.env.COCO_HIPCHAT_ARTISANS_API_KEY or ''
+
 config.queue =
   accessKeyId: process.env.COCO_AWS_ACCESS_KEY_ID or ''
   secretAccessKey: process.env.COCO_AWS_SECRET_ACCESS_KEY or ''