diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 987e29bdb..8f0069d6f 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -96,10 +96,10 @@
     logging_in: "Logging In"
     log_out: "Log Out"
     recover: "recover account"
-    authenticate_gplus: 'Authenticate G+'
-    load_profile: 'Load G+ Profile'
-    load_email: 'Load G+ Email'
-    finishing: 'Finishing'
+    authenticate_gplus: "Authenticate G+"
+    load_profile: "Load G+ Profile"
+    load_email: "Load G+ Email"
+    finishing: "Finishing"
 
   signup:
     create_account_title: "Create Account to Save Progress"
@@ -319,12 +319,15 @@
     unequip: "Unequip"
 
   buy_gems:
-    few_gems: 'A few gems'
-    pile_gems: 'Pile of gems'
-    chest_gems: 'Chest of gems'
-    purchasing: 'Purchasing...'
-    declined: 'Your card was declined'
-    retrying: 'Server error, retrying.'
+    few_gems: "A few gems"
+    pile_gems: "Pile of gems"
+    chest_gems: "Chest of gems"
+    purchasing: "Purchasing..."
+    declined: "Your card was declined"
+    retrying: "Server error, retrying."
+    prompt_title: "Not Enough Gems"
+    prompt_body: "Do you want to get more?"
+    prompt_button: "Enter Shop"
 
   choose_hero:
     choose_hero: "Choose Your Hero"
diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee
index 31218bf26..1191eb16a 100644
--- a/app/models/ThangType.coffee
+++ b/app/models/ThangType.coffee
@@ -19,6 +19,10 @@ module.exports = class ThangType extends CocoModel
     librarian: '52fbf74b7e01835453bd8d8e'
     'potion-master': '52e9adf7427172ae56002172'
     sorcerer: '52fd1524c7e6cf99160e7bc9'
+  @heroClasses:
+    Warrior: ['captain', 'knight', 'samurai']
+    Ranger: ['ninja', 'forest-archer', 'trapper']
+    Wizard: ['librarian', 'potion-master', 'sorcerer']
   @items:
     'simple-boots': '53e237bf53457600003e3f05'
   urlRoot: '/db/thang.type'
@@ -447,7 +451,7 @@ module.exports = class ThangType extends CocoModel
   levelRequiredForItem: ->
     return console.error "Trying to determine what level is required for #{@get('name')}, but it has no tier." unless @get('tier')?
     itemTier = @get 'tier'
-    playerTier = itemTier / 2
+    playerTier = itemTier / 2.5
     playerLevel = me.constructor.levelForTier playerTier
     #console.log 'Level required for', @get('name'), 'is', playerLevel, 'player tier', playerTier, 'because it is itemTier', itemTier, 'which is normally level', me.constructor.levelForTier(itemTier)
     playerLevel
diff --git a/app/models/User.coffee b/app/models/User.coffee
index 07d1b83de..4896c98bb 100644
--- a/app/models/User.coffee
+++ b/app/models/User.coffee
@@ -95,6 +95,13 @@ module.exports = class User extends CocoModel
   ownsItem: (itemOriginal) -> itemOriginal in @items()
   ownsLevel: (levelOriginal) -> levelOriginal in @levels()
 
+  getHeroClasses: ->
+    idsToSlugs = _.invert ThangType.heroes
+    myHeroSlugs = (idsToSlugs[id] for id in @heroes())
+    myHeroClasses = []
+    myHeroClasses.push heroClass for heroClass, heroSlugs of ThangType.heroClasses when _.intersection(myHeroSlugs, heroSlugs).length
+    myHeroClasses
+
   getBranchingGroup: ->
     return @branchingGroup if @branchingGroup
     group = me.get('testGroupNumber') % 4
@@ -107,4 +114,14 @@ module.exports = class User extends CocoModel
     application.tracker.identify branchingGroup: @branchingGroup unless me.isAdmin()
     @branchingGroup
 
+  getGemPromptGroup: ->
+    return @gemPromptGroup if @gemPromptGroup
+    group = me.get('testGroupNumber') % 8
+    @gemPromptGroup = switch group
+      when 0, 1, 2, 3 then 'prompt'
+      when 4, 5, 6, 7 then 'no-prompt'
+    @gemPromptGroup = 'prompt' if me.isAdmin()
+    application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin()
+    @gemPromptGroup
+
 tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96]
diff --git a/app/styles/common/common.sass b/app/styles/common/common.sass
index 9c70cd67f..c97aea9a3 100644
--- a/app/styles/common/common.sass
+++ b/app/styles/common/common.sass
@@ -228,6 +228,9 @@ kbd
   border-width: 15px 20px
   .arrow
     display: none
+  .btn
+    font-size: 20px
+    width: 100%
 
 .btn.btn-illustrated
   background: 0
@@ -242,6 +245,9 @@ kbd
   font-weight: bold
   color: rgb(248, 197, 146)
 
+  &:hover
+    color: lighten(rgb(248, 197, 146), 5%)
+
   &:active
     border-image: url(/images/common/button-background-pressed-border.png) 14 16 16 20 fill round
     padding: 2px 0 0 2px
diff --git a/app/styles/play/modal/play-items-modal.sass b/app/styles/play/modal/play-items-modal.sass
index eddc2b276..ab7a3c5ca 100644
--- a/app/styles/play/modal/play-items-modal.sass
+++ b/app/styles/play/modal/play-items-modal.sass
@@ -307,6 +307,13 @@
       opacity: 1
       color: rgba(255,255,255, 0.4)
 
+// Make sure this shows up above our modals.
+.popover.buy-gems-prompt
+  z-index: 1050
+  text-align: center
+
+  button
+    margin-top: 20px
 
 //- Use the two-column layout and background image if we are on a narrow screen.
 
diff --git a/app/templates/play/modal/buy-gems-prompt.jade b/app/templates/play/modal/buy-gems-prompt.jade
new file mode 100644
index 000000000..366f986e1
--- /dev/null
+++ b/app/templates/play/modal/buy-gems-prompt.jade
@@ -0,0 +1,6 @@
+.popover.buy-gems-prompt(role="tooltip")
+  .arrow
+  h2(data-i18n="buy_gems.prompt_title") Not Enough Gems
+  p(data-i18n="buy_gems.prompt_body") Do you want to get more?
+  button.btn.btn-success.btn-illustrated.btn-lg.buy-gems-prompt-button(data-i18n="buy_gems.prompt_button") Enter Shop
+  
\ No newline at end of file
diff --git a/app/templates/play/modal/item-details-view.jade b/app/templates/play/modal/item-details-view.jade
index 3da543a79..ad2609aa1 100644
--- a/app/templates/play/modal/item-details-view.jade
+++ b/app/templates/play/modal/item-details-view.jade
@@ -37,7 +37,7 @@ if item && !item.owned
     // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff
     button.btn.big-font.disabled.unequippable #{item.get('heroClass')} Only
   else
-    button#selected-item-unlock-button.btn.big-font.unlock-button(disabled=!item.affordable, data-item-id=item.id)
+    button#selected-item-unlock-button.btn.big-font.unlock-button(data-item-id=item.id)
       span(data-i18n="play.unlock") Unlock
       span.spl= '('+item.get('gems')
       img(src="/images/common/gem.png", draggable="false")
diff --git a/app/templates/play/modal/play-items-modal.jade b/app/templates/play/modal/play-items-modal.jade
index 08d0d52ba..05656e783 100644
--- a/app/templates/play/modal/play-items-modal.jade
+++ b/app/templates/play/modal/play-items-modal.jade
@@ -53,7 +53,7 @@
                       // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff
                       span.big-font.unequippable= item.get('heroClass')
                     else
-                      button.btn.unlock-button.big-font(data-i18n="play.unlock", disabled=!item.affordable, data-item-id=item.id)
+                      button.btn.unlock-button.big-font(data-i18n="play.unlock", data-item-id=item.id)
               .clearfix
               
     #item-details-view
diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee
index ba4b1cbf6..22f4c5764 100644
--- a/app/views/game-menu/InventoryModal.coffee
+++ b/app/views/game-menu/InventoryModal.coffee
@@ -1,5 +1,6 @@
 ModalView = require 'views/kinds/ModalView'
 template = require 'templates/game-menu/inventory-modal'
+buyGemsPromptTemplate = require 'templates/play/modal/buy-gems-prompt'
 {me} = require 'lib/auth'
 ThangType = require 'models/ThangType'
 CocoCollection = require 'collections/CocoCollection'
@@ -8,6 +9,7 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder'
 ItemDetailsView = require 'views/play/modal/ItemDetailsView'
 Purchase = require 'models/Purchase'
 LevelOptions = require 'lib/LevelOptions'
+BuyGemsModal = require 'views/play/modal/BuyGemsModal'
 
 hasGoneFullScreenOnce = false
 
@@ -31,6 +33,8 @@ module.exports = class InventoryModal extends ModalView
     'click #equip-item-viewed': 'onClickEquipItemViewed'
     'click #unequip-item-viewed': 'onClickUnequipItemViewed'
     'click #close-modal': 'hide'
+    'click .buy-gems-prompt-button': 'onBuyGemsPromptButtonClicked'
+    'click': 'onClickedSomewhere'
 
   shortcuts:
     'esc': 'clearSelection'
@@ -224,40 +228,6 @@ module.exports = class InventoryModal extends ModalView
     @selectUnequippedItem(itemEl)
     @equipSelectedItem()
 
-  onUnlockButtonClicked: (e) ->
-    button = $(e.target).closest('button')
-    if button.hasClass('confirm')
-      item = @items.get($(e.target).data('item-id'))
-      purchase = Purchase.makeFor(item)
-      purchase.save()
-
-      #- set local changes to mimic what should happen on the server...
-      purchased = me.get('purchased') ? {}
-      purchased.items ?= []
-      purchased.items.push(item.get('original'))
-
-      me.set('purchased', purchased)
-      me.set('spent', (me.get('spent') ? 0) + item.get('gems'))
-
-      #- ...then rerender key bits
-      @itemGroups.lockedItems.remove(item)
-      # Redo all item sorting to make sure that we don't clobber state changes since last render.
-      equipped = _.values @getCurrentEquipmentConfig()
-      @sortItem(item, equipped) for item in @items.models
-      @renderSelectors('#unequipped', '#gems-count')
-
-      @requireLevelEquipment()
-      @delegateEvents()
-      @setUpDraggableEventsForAvailableEquipment()
-      @itemDetailsView.setItem(item)
-
-      Backbone.Mediator.publish 'store:item-purchased', item: item, itemSlug: item.get('slug')
-    else
-      button.addClass('confirm').text($.i18n.t('play.confirm'))
-      @$el.one 'click', (e) ->
-        button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]
-
-
   #- Select/equip higher-level, all encompassing methods the callbacks all use
 
   selectItemSlot: (slotEl) ->
@@ -470,7 +440,70 @@ module.exports = class InventoryModal extends ModalView
     else
       callback?()
 
+  #- TODO: DRY this between PlayItemsModal and InventoryModal
+
+  onUnlockButtonClicked: (e) ->
+    e.stopPropagation()
+    button = $(e.target).closest('button')
+    item = @items.get(button.data('item-id'))
+    affordable = item.affordable
+    if not affordable
+      @askToBuyGems button
+    else if button.hasClass('confirm')
+      purchase = Purchase.makeFor(item)
+      purchase.save()
+
+      #- set local changes to mimic what should happen on the server...
+      purchased = me.get('purchased') ? {}
+      purchased.items ?= []
+      purchased.items.push(item.get('original'))
+
+      me.set('purchased', purchased)
+      me.set('spent', (me.get('spent') ? 0) + item.get('gems'))
+
+      #- ...then rerender key bits
+      @itemGroups.lockedItems.remove(item)
+      # Redo all item sorting to make sure that we don't clobber state changes since last render.
+      equipped = _.values @getCurrentEquipmentConfig()
+      @sortItem(item, equipped) for item in @items.models
+      @renderSelectors('#unequipped', '#gems-count')
+
+      @requireLevelEquipment()
+      @delegateEvents()
+      @setUpDraggableEventsForAvailableEquipment()
+      @itemDetailsView.setItem(item)
+
+      Backbone.Mediator.publish 'store:item-purchased', item: item, itemSlug: item.get('slug')
+    else
+      button.addClass('confirm').text($.i18n.t('play.confirm'))
+      @$el.one 'click', (e) ->
+        button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]
+
+  askToBuyGems: (unlockButton) ->
+    if me.getGemPromptGroup() is 'no-prompt'
+      return @openModalView new BuyGemsModal()
+    @$el.find('.unlock-button').popover 'destroy'
+    popoverTemplate = buyGemsPromptTemplate {}
+    unlockButton.popover(
+      animation: true
+      trigger: 'manual'
+      placement: 'top'
+      content: ' '  # template has it
+      container: @$el
+      template: popoverTemplate
+    ).popover 'show'
+    popover = unlockButton.data('bs.popover')
+    popover?.$tip?.i18n()
+
+  onBuyGemsPromptButtonClicked: (e) ->
+    @openModalView new BuyGemsModal()
+
+  onClickedSomewhere: (e) ->
+    return if @destroyed
+    @$el.find('.unlock-button').popover 'destroy'
+
   destroy: ->
+    @$el.find('.unlock-button').popover 'destroy'
     @stage?.removeAllChildren()
     super()
 
diff --git a/app/views/play/modal/PlayItemsModal.coffee b/app/views/play/modal/PlayItemsModal.coffee
index fd4b9c6a5..74b17fcdf 100644
--- a/app/views/play/modal/PlayItemsModal.coffee
+++ b/app/views/play/modal/PlayItemsModal.coffee
@@ -1,6 +1,8 @@
 ModalView = require 'views/kinds/ModalView'
 template = require 'templates/play/modal/play-items-modal'
+buyGemsPromptTemplate = require 'templates/play/modal/buy-gems-prompt'
 ItemDetailsView = require './ItemDetailsView'
+BuyGemsModal = require 'views/play/modal/BuyGemsModal'
 
 CocoCollection = require 'collections/CocoCollection'
 ThangType = require 'models/ThangType'
@@ -46,7 +48,9 @@ module.exports = class PlayItemsModal extends ModalView
     'click .item': 'onItemClicked'
     'shown.bs.tab': 'onTabClicked'
     'click .unlock-button': 'onUnlockButtonClicked'
+    'click .buy-gems-prompt-button': 'onBuyGemsPromptButtonClicked'
     'click #close-modal': 'hide'
+    'click': 'onClickedSomewhere'
 
   constructor: (options) ->
     super options
@@ -89,7 +93,7 @@ module.exports = class PlayItemsModal extends ModalView
       model.affordable = cost <= gemsOwned
       model.silhouetted = not model.owned and model.isSilhouettedItem()
       model.level = model.levelRequiredForItem() if model.get('tier')?
-      model.unequippable = not ('Warrior' in model.getAllowedHeroClasses())  # Temp: while there are no wizards/rangers
+      model.unequippable = not _.intersection(me.getHeroClasses(), model.getAllowedHeroClasses()).length
       model.comingSoon = not model.getFrontFacingStats().props.length and not _.size(model.getFrontFacingStats().stats) and not model.owned  # Temp: while there are placeholder items
       @idToItem[model.id] = model
 
@@ -140,9 +144,14 @@ module.exports = class PlayItemsModal extends ModalView
     $($(e.target).attr('href')).find('.nano').nanoScroller({alwaysVisible: true})
 
   onUnlockButtonClicked: (e) ->
+    e.stopPropagation()
     button = $(e.target).closest('button')
-    if button.hasClass('confirm')
-      item = @idToItem[$(e.target).data('item-id')]
+    item = @idToItem[button.data('item-id')]
+    affordable = item.affordable
+    if not affordable
+      @askToBuyGems button
+    else if button.hasClass('confirm')
+
       purchase = Purchase.makeFor(item)
       purchase.save()
 
@@ -163,3 +172,29 @@ module.exports = class PlayItemsModal extends ModalView
       button.addClass('confirm').text($.i18n.t('play.confirm'))
       @$el.one 'click', (e) ->
         button.removeClass('confirm').text($.i18n.t('play.unlock')) if e.target isnt button[0]
+
+  askToBuyGems: (unlockButton) ->
+    if me.getGemPromptGroup() is 'no-prompt'
+      return @openModalView new BuyGemsModal()
+    @$el.find('.unlock-button').popover 'destroy'
+    popoverTemplate = buyGemsPromptTemplate {}
+    unlockButton.popover(
+      animation: true
+      trigger: 'manual'
+      content: ' '  # template has it
+      container: @$el
+      template: popoverTemplate
+    ).popover 'show'
+    popover = unlockButton.data('bs.popover')
+    popover?.$tip?.i18n()
+
+  onBuyGemsPromptButtonClicked: (e) ->
+    @openModalView new BuyGemsModal()
+
+  onClickedSomewhere: (e) ->
+    return if @destroyed
+    @$el.find('.unlock-button').popover 'destroy'
+
+  destroy: ->
+    @$el.find('.unlock-button').popover 'destroy'
+    super()