diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee
index 565b66dc9..c64591207 100644
--- a/app/lib/surface/Lank.coffee
+++ b/app/lib/surface/Lank.coffee
@@ -141,6 +141,7 @@ module.exports = Lank = class Lank extends CocoClass
   onSurfaceTicked: (e) -> @age += e.dt
 
   playNextAction: =>
+    return if @destroyed
     @playAction(@actionQueue.splice(0, 1)[0]) if @actionQueue.length
 
   playAction: (action) ->
diff --git a/app/locale/fr.coffee b/app/locale/fr.coffee
index a1aad2098..3f593a927 100644
--- a/app/locale/fr.coffee
+++ b/app/locale/fr.coffee
@@ -499,7 +499,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     writable: "éditable" # Hover over "attack" in Your Skills while playing a level to see most of this
     read_only: "lecture seulement"
     action_name: "nom"
-    action_cooldown: "Encaisse"
+    action_cooldown: "Durée"
     action_specific_cooldown: "Rechargement"
     action_damage: "Dégât"
     action_range: "Portée"
@@ -878,7 +878,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     diplomat_i18n_page_prefix: "Vous pouvez commencer à traduire nos niveaux en allant sur notre "
     diplomat_i18n_page: "page de traduction"
     diplomat_i18n_page_suffix: ", ou notre interface et le site Web sur GitHub."
-    diplomat_join_pref_github: "Trouvez le fichier de langue souhaité"
+    diplomat_join_pref_github: "Trouvez le fichier de langue souhaité "
     diplomat_github_url: "sur GitHub"
     diplomat_join_suf_github: ", modifiez en ligne, et soumettez des requêtes. Cochez aussi cette case ci-dessous pour vous tenir à jour sur les nouveaux développements d'internationalisation !"
     diplomat_subscribe_desc: "Recevoir un e-mail sur le développement i18n et les niveaux à traduire."
@@ -962,7 +962,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     no_singleplayer: "Aucune partie jouée pour le moment"
     no_multiplayer: "Aucune partie multijoueur pour le moment"
     no_achievements: "Aucun succès gagné pour le moment."
-    favorite_prefix: "Langage favori :"
+    favorite_prefix: "Langage favori : "
     favorite_postfix: "."
 
   achievements:
diff --git a/app/locale/sk.coffee b/app/locale/sk.coffee
index fc2697922..d7f98f4f3 100644
--- a/app/locale/sk.coffee
+++ b/app/locale/sk.coffee
@@ -67,9 +67,9 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #    poll: "Poll" # Tooltip on poll button from /play
     next: "Ďalší" # Go from choose hero to choose inventory before playing a level
     change_hero: "Zmeniť hrdinu" # Go back from choose inventory to choose hero
-    choose_inventory: "Vyzbrojiť sa s predmetmy"
+    choose_inventory: "Vyzbrojiť sa s predmetmi"
     buy_gems: "Zakúpiť drahokamy"
-    subscription_required: "Vyžaduje sa rredplatné"
+    subscription_required: "Vyžaduje sa predplatné"
     older_campaigns: "Staršie kampane"
     anonymous: "Anonymný hráč"
     level_difficulty: "Obtiažnosť."
@@ -457,16 +457,16 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #    subscribe_prepaid: "Click Subscribe to use prepaid code"
 #    using_prepaid: "Using prepaid code for monthly subscription"
 
-#  choose_hero:
-#    choose_hero: "Choose Your Hero"
-#    programming_language: "Programming Language"
-#    programming_language_description: "Which programming language do you want to use?"
-#    default: "Default"
-#    experimental: "Experimental"
-#    python_blurb: "Simple yet powerful, great for beginners and experts."
-#    javascript_blurb: "The language of the web. (Not the same as Java.)"
-#    coffeescript_blurb: "Nicer JavaScript syntax."
-#    clojure_blurb: "A modern Lisp."
+  choose_hero:
+    choose_hero: "Vyber svojho hrdinu"
+    programming_language: "Programovací jazyk"
+    programming_language_description: "Aký programovací jazyk chceš použiť ? "
+    default: "Predvolený"
+    experimental: "Experimentálny"
+    python_blurb: "Jednoduchý ale výkonný, skvelý pre začiatočníkov aj expertov"
+    javascript_blurb: "Jazyk webových stránok. (Nie je to Java)"
+    coffeescript_blurb: "Krajšia syntax pre JavaScript"
+    clojure_blurb: "Moderný Lisp"
 #    lua_blurb: "Game scripting language."
 #    io_blurb: "Simple but obscure."
 #    status: "Status"
diff --git a/app/models/User.coffee b/app/models/User.coffee
index 75d081685..88eaf912f 100644
--- a/app/models/User.coffee
+++ b/app/models/User.coffee
@@ -119,7 +119,9 @@ module.exports = class User extends CocoModel
 
   # Signs and Portents was receiving updates after test started, and also had a big bug on March 4, so just look at test from March 5 on.
   # ... and stopped working well until another update on March 10, so maybe March 11+...
+  # ... and another round, and then basically it just isn't completing well, so we pause the test until we can fix it.
   getFourthLevelGroup: ->
+    return 'signs-and-portents'
     return @fourthLevelGroup if @fourthLevelGroup
     group = me.get('testGroupNumber') % 8
     @fourthLevelGroup = switch group
diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass
index 0c5eb05ce..cd1ba311e 100644
--- a/app/styles/play/level.sass
+++ b/app/styles/play/level.sass
@@ -63,7 +63,6 @@ $level-resize-transition-time: 0.5s
 
   .level-content
     position: relative
-    overflow: hidden
 
   #canvas-wrapper
     top: 50px
@@ -128,6 +127,10 @@ $level-resize-transition-time: 0.5s
     top: 0px
     bottom: 0
     @include transition(width $level-resize-transition-time ease-in-out, right $level-resize-transition-time ease-in-out)
+    z-index: 2
+
+  #game-area
+    position: relative
     overflow: hidden
     
   // Level Docs
diff --git a/app/styles/play/level/tome/spell.sass b/app/styles/play/level/tome/spell.sass
index ca0bf50bb..1a4781c92 100644
--- a/app/styles/play/level/tome/spell.sass
+++ b/app/styles/play/level/tome/spell.sass
@@ -63,6 +63,7 @@
     overflow: visible
     // https://github.com/codecombat/codecombat/issues/1411#issuecomment-60492750 -- trying to make sure system defaults don't mess up our monospace font.
     font-family: Monaco, Menlo, Ubuntu Mono, Consolas, "source-code-pro", monospace !important
+    @include transition(height 0.25s ease-in-out)
 
     &.disabled
       @include opacity(0.8)
diff --git a/app/styles/play/level/tome/spell_palette.sass b/app/styles/play/level/tome/spell_palette.sass
index 263a793de..91a27a7ab 100644
--- a/app/styles/play/level/tome/spell_palette.sass
+++ b/app/styles/play/level/tome/spell_palette.sass
@@ -9,13 +9,19 @@
   background-color: transparent
   background-size: 100% 100%
   z-index: 2
-  //overflow-y: auto
+  @include transition(top 0.25s ease-in-out, height 0.25s ease-in-out)
+  box-shadow: 10px 4px 4px black
+  overflow-y: hidden
 
   .code-palette-background
     width: 100%
+    height: 592px
     position: absolute
     left: 0px
     z-index: -1
+    background: transparent url(/images/level/code_palette_wood_background.png)
+    background-size: 100% 592px
+    overflow: visible
 
   &.controls-disabled
     .code-palette-background
@@ -71,6 +77,9 @@
     @include flex-column()
     @include flex-align-content-start()
 
+    &.no-help
+      margin-top: 3%
+
     .property-entry-item-group
       display: inline-block
       min-height: 38px
@@ -97,6 +106,15 @@
         width: -webkit-calc(100% - 38px)
         width: calc(100% - 38px)
 
+  &.shortenize.hero .properties
+    .property-entry-item-group
+      width: 175px
+
+      .spell-palette-entry-view
+        width: 137px
+        width: -webkit-calc(100% - 38px)
+        width: calc(100% - 38px)
+
 @media only screen and (max-width: 1100px)
   #spell-palette-view
     // Make sure we have enough room for at least two columns
diff --git a/app/styles/play/level/tome/tome.sass b/app/styles/play/level/tome/tome.sass
index 924b72e31..d839b7a87 100644
--- a/app/styles/play/level/tome/tome.sass
+++ b/app/styles/play/level/tome/tome.sass
@@ -4,6 +4,7 @@
 #tome-view
   height: 100%
   margin-bottom: -20px
+  overflow: hidden
 
   > .popover
     // Only those popovers which are our direct children (spell documentation)
diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade
index c042d248c..9b8dca111 100644
--- a/app/templates/play/level.jade
+++ b/app/templates/play/level.jade
@@ -4,34 +4,36 @@
   #control-bar-view
 
   #fullscreen-editor-background-screen(title="Click to minimize the code editor")
-  
+
   #code-area
     #code-area-gradient.gradient
     #tome-view
-  
-  #canvas-wrapper
-    canvas(width=924, height=589)#webgl-surface
-    canvas(width=924, height=589)#normal-surface
-    #ascii-surface
-    #canvas-left-gradient.gradient
-    #canvas-top-gradient.gradient
-    #goals-view
-  
-  #level-flags-view
 
-  #gold-view
+  #game-area
 
-  #problem-alert-view
+    #canvas-wrapper
+      canvas(width=924, height=589)#webgl-surface
+      canvas(width=924, height=589)#normal-surface
+      #ascii-surface
+      #canvas-left-gradient.gradient
+      #canvas-top-gradient.gradient
+      #goals-view
 
-  #level-chat-view
-  
-  #multiplayer-status-view
-  
-  #playback-view
-  
-  #thang-hud
+    #level-flags-view
 
-  #level-dialogue-view
+    #gold-view
+
+    #problem-alert-view
+
+    #level-chat-view
+
+    #multiplayer-status-view
+
+    #playback-view
+
+    #thang-hud
+
+    #level-dialogue-view
 
   button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip
 
diff --git a/app/templates/play/level/tome/spell_palette.jade b/app/templates/play/level/tome/spell_palette.jade
index 904b8dc1a..4d9af9f42 100644
--- a/app/templates/play/level/tome/spell_palette.jade
+++ b/app/templates/play/level/tome/spell_palette.jade
@@ -1,4 +1,3 @@
-img(src="/images/level/code_palette_wood_background.png", draggable="false").code-palette-background
 span.code-palette-background
 if entryGroupSlugs
   // Non-hero; group by entry groups, or maybe nothing.
@@ -13,6 +12,9 @@ if entryGroupSlugs
         div(class="properties properties-" + slug + " nano-content")
 else
   // Hero; group by items, no tabs.
-  button.btn.btn-sm.btn-info.banner#spell-palette-help-button(data-i18n="play_level.tome_help")
-  .properties
+  if showsHelp
+    button.btn.btn-sm.btn-info.banner#spell-palette-help-button(data-i18n="play_level.tome_help")
+    .properties
+  else
+    .properties.no-help
 
diff --git a/app/views/play/level/tome/DocFormatter.coffee b/app/views/play/level/tome/DocFormatter.coffee
index 49ac48d4e..bc2772db7 100644
--- a/app/views/play/level/tome/DocFormatter.coffee
+++ b/app/views/play/level/tome/DocFormatter.coffee
@@ -82,6 +82,8 @@ module.exports = class DocFormatter
         @doc.shorterName = @doc.shortName.replace ';', ''
         if @doc.owner is 'this' or @options.tabbify
           @doc.shorterName = @doc.shorterName.replace /^this\./, ''
+      else if (@options.language in ['python', 'lua']) and (@doc.owner is 'this' or @options.tabbify)
+        @doc.shorterName = @doc.shortName.replace /^self[:.]/, ''
       @doc.title = if @options.shortenize then @doc.shorterName else @doc.shortName
 
     # Grab the language-specific documentation for some sub-properties, if we have it.
diff --git a/app/views/play/level/tome/SpellPaletteView.coffee b/app/views/play/level/tome/SpellPaletteView.coffee
index c782661af..f25fc84aa 100644
--- a/app/views/play/level/tome/SpellPaletteView.coffee
+++ b/app/views/play/level/tome/SpellPaletteView.coffee
@@ -29,6 +29,8 @@ module.exports = class SpellPaletteView extends CocoView
     @session = options.session
     @supermodel = options.supermodel
     @thang = options.thang
+    docs = @options.level.get('documentation') ? {}
+    @showsHelp = docs.specificArticles?.length or docs.generalArticles?.length
     @createPalette()
     $(window).on 'resize', @onResize
 
@@ -39,6 +41,7 @@ module.exports = class SpellPaletteView extends CocoView
     c.entryGroupNames = @entryGroupNames
     c.tabbed = _.size(@entryGroups) > 1
     c.defaultGroupSlug = @defaultGroupSlug
+    c.showsHelp = @showsHelp
     c
 
   afterRender: ->
@@ -72,6 +75,7 @@ module.exports = class SpellPaletteView extends CocoView
           if entryIndex is 0
             entry.$el.addClass 'first-entry'
       @$el.addClass 'hero'
+      @$el.toggleClass 'shortenize', Boolean @shortenize
       @updateMaxHeight() unless application.isIPadApp
 
   afterInsert: ->
@@ -83,18 +87,27 @@ module.exports = class SpellPaletteView extends CocoView
 
   updateMaxHeight: ->
     return unless @isHero
-    nColumns = Math.floor @$el.find('.properties').innerWidth() / 212   # ~212px is a good max entry width; will always have 2 columns
+    # We figure out how many columns we can fit, width-wise, and then guess how many rows will be needed.
+    # We can then assign a height based on the number of rows, and the flex layout will do the rest.
+    columnWidth = if @shortenize then 175 else 212
+    nColumns = Math.floor @$el.find('.properties').innerWidth() / columnWidth   # will always have 2 columns, since at 1024px screen we have 424px .properties
     columns = ({items: [], nEntries: 0} for i in [0 ... nColumns])
+    orderedColumns = []
     nRows = 0
-    for group, entries of @entryGroups
+    entryGroupsByLength = _.sortBy _.keys(@entryGroups), (group) => @entryGroups[group].length
+    entryGroupsByLength.reverse()
+    for group in entryGroupsByLength
+      entries = @entryGroups[group]
       continue unless shortestColumn = _.sortBy(columns, (column) -> column.nEntries)[0]
-      shortestColumn.nEntries += Math.max 2, entries.length
+      shortestColumn.nEntries += Math.max 2, entries.length  # Item portrait is two rows tall
       shortestColumn.items.push @entryGroupElements[group]
+      orderedColumns.push shortestColumn unless shortestColumn in orderedColumns
       nRows = Math.max nRows, shortestColumn.nEntries
-    for column in columns
+    for column in orderedColumns
       for item in column.items
         item.detach().appendTo @$el.find('.properties')
-    @$el.find('.properties').css('height', 19 * (nRows + 1))
+    desiredHeight = 19 * (nRows + 1)
+    @$el.find('.properties').css('height', desiredHeight)
 
   onResize: (e) =>
     @updateMaxHeight()
@@ -151,7 +164,7 @@ module.exports = class SpellPaletteView extends CocoView
         count += added.length
     Backbone.Mediator.publish 'tome:update-snippets', propGroups: propGroups, allDocs: allDocs, language: @options.language
 
-    shortenize = count > 6
+    @shortenize = count > 6
     tabbify = count >= 10
     @entries = []
     for owner, props of propGroups
@@ -163,7 +176,7 @@ module.exports = class SpellPaletteView extends CocoView
           console.log 'could not find doc for', prop, 'from', allDocs['__' + prop], 'for', owner, 'of', propGroups
           doc ?= prop
         if doc
-          @entries.push @addEntry(doc, shortenize, tabbify, owner is 'snippets')
+          @entries.push @addEntry(doc, @shortenize, tabbify, owner is 'snippets')
     groupForEntry = (entry) ->
       return 'more' if entry.doc.owner is 'this' and entry.doc.name in (propGroups.more ? [])
       entry.doc.owner
@@ -229,7 +242,7 @@ module.exports = class SpellPaletteView extends CocoView
 
     Backbone.Mediator.publish 'tome:update-snippets', propGroups: propsByItem, allDocs: allDocs, language: @options.language
 
-    shortenize = propCount > 6
+    @shortenize = propCount > 6
     @entries = []
     for itemName, props of propsByItem
       for prop, propIndex in props
@@ -243,7 +256,7 @@ module.exports = class SpellPaletteView extends CocoView
           console.log 'could not find doc for', prop, 'from', allDocs['__' + prop], 'for', owner, 'of', propsByItem, 'with item', item
           doc ?= prop
         if doc
-          @entries.push @addEntry(doc, shortenize, false, owner is 'snippets', item, propIndex > 0)
+          @entries.push @addEntry(doc, @shortenize, false, owner is 'snippets', item, propIndex > 0)
     @entryGroups = _.groupBy @entries, (entry) -> itemsByProp[entry.doc.name]?.get('name') ? 'Hero'
     iOSEntryGroups = {}
     for group, entries of @entryGroups
@@ -264,16 +277,6 @@ module.exports = class SpellPaletteView extends CocoView
     @controlsEnabled = enabled
     @$el.find('*').attr('disabled', not enabled)
     @$el.toggleClass 'controls-disabled', not enabled
-    @toggleBackground()
-
-  toggleBackground: =>
-    # TODO: make the palette background an actual background and do the CSS trick
-    # used in spell_list_entry.sass for disabling
-    background = @$el.find('img.code-palette-background')[0]
-    if background.naturalWidth is 0  # not loaded yet
-      return _.delay @toggleBackground, 100
-    filters.revertImage background, 'span.code-palette-background' if @controlsEnabled
-    filters.darkenImage background, 'span.code-palette-background', 0.8 unless @controlsEnabled
 
   onFrameChanged: (e) ->
     return unless e.selectedThang?.id is @thang.id
diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee
index 78dc7b008..797ac1154 100644
--- a/app/views/play/level/tome/SpellView.coffee
+++ b/app/views/play/level/tome/SpellView.coffee
@@ -568,15 +568,22 @@ module.exports = class SpellView extends CocoView
       @lastScreenLineCount = screenLineCount
       lineHeight = @ace.renderer.lineHeight or 20
       tomeHeight = $('#tome-view').innerHeight()
+      spellPaletteView = $('#spell-palette-view')
       spellListTabEntryHeight = $('#spell-list-tab-entry-view').outerHeight()
       spellToolbarHeight = $('.spell-toolbar-view').outerHeight()
-      spellPaletteHeight = $('#spell-palette-view').outerHeight()
-      maxHeight = tomeHeight - spellListTabEntryHeight - spellToolbarHeight - spellPaletteHeight
+      @spellPaletteHeight ?= spellPaletteView.outerHeight()  # Remember this until resize, since we change it afterward
+      spellPaletteAllowedHeight = Math.min @spellPaletteHeight, tomeHeight / 3
+      maxHeight = tomeHeight - spellListTabEntryHeight - spellToolbarHeight - spellPaletteAllowedHeight
       linesAtMaxHeight = Math.floor(maxHeight / lineHeight)
       lines = Math.max 8, Math.min(screenLineCount + 2, linesAtMaxHeight)
       # 2 lines buffer is nice
       @ace.setOptions minLines: lines, maxLines: lines
-      $('#spell-palette-view').css('top', 175 + lineHeight * lines)  # Move spell palette up, slightly overlapping us.
+      # Move spell palette up, slightly overlapping us.
+      newTop = 175 + lineHeight * lines
+      spellPaletteView.css('top', newTop)
+      # Expand it to bottom of tome if too short.
+      newHeight = Math.max @spellPaletteHeight, tomeHeight - newTop + 10
+      spellPaletteView.css('height', newHeight) if @spellPaletteHeight isnt newHeight
 
   hideProblemAlert: ->
     Backbone.Mediator.publish 'tome:hide-problem-alert', {}
@@ -1035,6 +1042,8 @@ module.exports = class SpellView extends CocoView
     _.delay (=> @resize()), 500 + 100  # Wait $level-resize-transition-time, plus a bit.
 
   onWindowResize: (e) =>
+    @spellPaletteHeight = null
+    $('#spell-palette-view').css 'height', 'auto'  # Let it go back to controlling its own height
     _.delay (=> @resize?()), 500 + 100  # Wait $level-resize-transition-time, plus a bit.
 
   resize: ->
diff --git a/scripts/mongodb/createBulkPrepaids.js b/scripts/mongodb/createBulkPrepaids.js
new file mode 100644
index 000000000..cd4d6920e
--- /dev/null
+++ b/scripts/mongodb/createBulkPrepaids.js
@@ -0,0 +1,60 @@
+// Bulk create prepaid codes + email message
+
+// Usage:
+// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
+
+var num = 10;
+var message = "Thanks for filling out the form.  You can follow this link to enable your free teacher subscription.  If you have any questions or comments, please let me know.";
+var urlPrefix = "https://codecombat.com/account/subscription?_ppc=";
+var creatorID = "52f94443fcb334581466a992";
+
+for (var i = 0; i < num; i++) {
+  createPrepaid();
+}
+
+function createPrepaid()
+{
+  generateNewCode(function(code) {
+    if (!code) {
+      print("ERROR: no code");
+      return;
+    }
+    criteria = {
+      creator: creatorID,
+      type: 'subscription',
+      status: 'active',
+      code: code,
+      properties: {
+        couponID: 'free'
+      },
+      __v: 0
+    };
+    db.prepaids.insert(criteria);
+
+    print(message + "  " + urlPrefix + code);
+  });
+}
+
+function generateNewCode(done)
+{
+  function tryCode() {
+    code = createCode(8);
+    criteria = {code: code};
+    if (db.prepaids.findOne(criteria)) {
+      return tryCode();
+    }
+    return done(code);
+  }
+  tryCode();
+}
+
+function createCode(length)
+{
+    var text = "";
+    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+    for( var i=0; i < length; i++ )
+        text += possible.charAt(Math.floor(Math.random() * possible.length));
+
+    return text;
+}
diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee
index 44a923160..2f698cdc4 100644
--- a/server/routes/mail.coffee
+++ b/server/routes/mail.coffee
@@ -706,9 +706,10 @@ sendNextStepsEmail = (user, now, daysAgo) ->
       return log.error "Couldn't find next level for #{user.get('email')}: #{err}" if err
       name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
       name = 'hero' if not name or name is 'Anoner'
-      secretLevel = switch user.get('testGroupNumber') % 8
-        when 0, 1, 2, 3 then name: 'Forgetful Gemsmith', slug: 'forgetful-gemsmith'
-        when 4, 5, 6, 7 then name: 'Signs and Portents', slug: 'signs-and-portents'
+      #secretLevel = switch user.get('testGroupNumber') % 8
+      #  when 0, 1, 2, 3 then name: 'Forgetful Gemsmith', slug: 'forgetful-gemsmith'
+      #  when 4, 5, 6, 7 then name: 'Signs and Portents', slug: 'signs-and-portents'
+      secretLevel = name: 'Signs and Portents', slug: 'signs-and-portents'  # We turned off this test for now and are sending everyone to forgetful-gemsmith
 
       # TODO: make this smarter, actually data-driven, looking at all available sessions
       shadowGuardSession = _.find sessions, levelID: 'shadow-guard'