diff --git a/app/lib/God.coffee b/app/lib/God.coffee
index 8feafbfa1..cf1631689 100644
--- a/app/lib/God.coffee
+++ b/app/lib/God.coffee
@@ -131,9 +131,7 @@ module.exports = class God extends CocoClass
         Backbone.Mediator.publish 'god:debug-world-load-progress-changed', progress: event.data.progress
 
   onNewWorldCreated: (e) ->
-    console.log 'filtering', _.cloneDeep e.world.userCodeMap
     @currentUserCodeMap = @filterUserCodeMapWhenFromWorld e.world.userCodeMap
-    console.log '   ... filtered into', _.cloneDeep e.world.userCodeMap
 
   filterUserCodeMapWhenFromWorld: (worldUserCodeMap) ->
     newUserCodeMap = {}
diff --git a/app/lib/surface/PlaybackOverScreen.coffee b/app/lib/surface/PlaybackOverScreen.coffee
index a50dadc78..a129bd45f 100644
--- a/app/lib/surface/PlaybackOverScreen.coffee
+++ b/app/lib/surface/PlaybackOverScreen.coffee
@@ -1,6 +1,9 @@
 CocoClass = require 'lib/CocoClass'
 
 module.exports = class PlaybackOverScreen extends CocoClass
+  subscriptions:
+    'goal-manager:new-goal-states': 'onNewGoalStates'
+
   constructor: (options) ->
     super()
     options ?= {}
@@ -16,15 +19,13 @@ module.exports = class PlaybackOverScreen extends CocoClass
     @dimLayer = new createjs.Container()
     @dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
     @dimLayer.addChild @dimScreen = new createjs.Shape()
-    @dimScreen.graphics.beginFill('rgba(0,0,0,0.4)').rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
-    @dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight
     @dimLayer.alpha = 0
     @layer.addChild @dimLayer
 
   show: ->
     return if @showing
     @showing = true
-
+    @updateColor 'rgba(212, 212, 212, 0.4' unless @color  # If we haven't caught the goal state for the first run, just do something neutral.
     @dimLayer.alpha = 0
     createjs.Tween.removeTweens @dimLayer
     createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
@@ -32,6 +33,22 @@ module.exports = class PlaybackOverScreen extends CocoClass
   hide: ->
     return unless @showing
     @showing = false
-
     createjs.Tween.removeTweens @dimLayer
     createjs.Tween.get(@dimLayer).to({alpha: 0}, 500)
+
+  onNewGoalStates: (e) ->
+    success = e.overallStatus is 'success'
+    failure = e.overallStatus is 'failure'
+    timedOut = e.timedOut
+    incomplete = not success and not failure and not timedOut
+    color = if failure then 'rgba(255, 128, 128, 0.4)' else 'rgba(255, 255, 255, 0.4)'
+    @updateColor color
+
+  updateColor: (color) ->
+    return if color is @color
+    @dimScreen.graphics.clear().beginFill(color).rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
+    if @color
+      @dimLayer.updateCache()
+    else
+      @dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight  # I wonder if caching is even worth it for just a rect fill.
+    @color = color
diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 980085db8..b96203fd4 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -115,6 +115,7 @@ module.exports = Surface = class Surface extends CocoClass
     @lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
     @countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer
     @playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer
+    @normalStage.addChildAt @playbackOverScreen.dimLayer, 0  # Put this below the other layers, actually, so we can more easily read text on the screen.
     @waitingScreen = new WaitingScreen camera: @camera, layer: @screenLayer
     @initCoordinates()
     @webGLStage.enableMouseOver(10)
diff --git a/app/styles/play/level/goals.sass b/app/styles/play/level/goals.sass
index 39b5b53a8..9ce1381b2 100644
--- a/app/styles/play/level/goals.sass
+++ b/app/styles/play/level/goals.sass
@@ -5,19 +5,24 @@
   position: absolute
   left: 10px
   top: -100px
-  @include transition(top 0.5s ease-in-out)
+  @include transition(0.5s ease-in-out)
   background-color: rgba(200,200,200,1.0)
   
   border: black
   padding: 15px 7px 2px 5px
   box-sizing: border-box
-  border:  1px solid #333
+  border: 1px solid #333
   border-radius: 5px
-  
+  z-index: 3
+  font-size: 14px
+
+  &.brighter
+    font-size: 28px
+
   .goals-status
-    font-size: 14px
     margin: 0
     color: black
+
     .success
       color: darkgreen
     .timed-out
diff --git a/app/views/play/level/LevelGoalsView.coffee b/app/views/play/level/LevelGoalsView.coffee
index 27849d659..04000c569 100644
--- a/app/views/play/level/LevelGoalsView.coffee
+++ b/app/views/play/level/LevelGoalsView.coffee
@@ -4,9 +4,9 @@ template = require 'templates/play/level/goals'
 utils = require 'lib/utils'
 
 stateIconMap =
-  incomplete: 'icon-minus'
-  success: 'icon-ok'
-  failure: 'icon-remove'
+  incomplete: 'glyphicon-minus'
+  success: 'glyphicon-ok'
+  failure: 'glyphicon-remove'
 
 module.exports = class LevelGoalsView extends CocoView
   id: 'goals-view'
@@ -61,32 +61,47 @@ module.exports = class LevelGoalsView extends CocoView
       # This should really get refactored, along with GoalManager, so that goals have a standard
       # representation of how many are done, how many are needed, what that means, etc.
       li = $('<li></li>').addClass("status-#{state.status}").text(text)
-      li.prepend($('<i></i>').addClass(stateIconMap[state.status]))
+      li.prepend($('<i></i>').addClass('glyphicon').addClass(stateIconMap[state.status]))
       list.append(li)
       goals.push goal
       if not firstRun and state.status is 'success' and @previousGoalStatus[goal.id] isnt 'success'
-        Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'goal-success', volume: 1
+        @soundToPlayWhenPlaybackEnded = 'goal-success'
       else if not firstRun and state.status isnt 'success' and @previousGoalStatus[goal.id] is 'success'
-        Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'goal-incomplete-again', volume: 1
+        @soundToPlayWhenPlaybackEnded = 'goal-incomplete-again'
+      else
+        @soundToPlayWhenPlaybackEnded = null
       @previousGoalStatus[goal.id] = state.status
-    @$el.removeClass('secret') if goals.length > 0
+    if goals.length > 0 and @$el.hasClass 'secret'
+      @$el.removeClass('secret')
+      @lastSizeTweenTime = new Date()
     @updatePlacement()
 
   onSurfacePlaybackRestarted: ->
     @playbackEnded = false
     @$el.removeClass 'brighter'
+    @lastSizeTweenTime = new Date()
     @updatePlacement()
 
   onSurfacePlaybackEnded: ->
     @playbackEnded = true
+    @updateHeight()
     @$el.addClass 'brighter'
+    @lastSizeTweenTime = new Date()
     @updatePlacement()
+    if @soundToPlayWhenPlaybackEnded
+      Backbone.Mediator.publish 'audio-player:play-sound', trigger: @soundToPlayWhenPlaybackEnded, volume: 1
+
+  updateHeight: ->
+    return if @$el.hasClass('brighter') or @$el.hasClass('secret')
+    return if (new Date() - @lastSizeTweenTime) < 500  # Don't measure this while still animating, might get the wrong value. Should match sass transition time.
+    @normalHeight = @$el.outerHeight()
 
   updatePlacement: ->
     expand = @playbackEnded or @mouseEntered
     return if expand is @expanded
+    @updateHeight()
     sound = if expand then 'goals-expand' else 'goals-collapse'
-    top = if expand then -10 else 26 - @$el.outerHeight()
+    top = if expand then -10 else 26 - (@normalHeight ? @$el.outerHeight())
     @$el.css 'top', top
     if @soundTimeout
       # Don't play the sound we were going to play after all; the transition has reversed.