diff --git a/app/lib/sprites/SpriteBuilder.coffee b/app/lib/sprites/SpriteBuilder.coffee
index 154e90cc0..95d3a67a8 100644
--- a/app/lib/sprites/SpriteBuilder.coffee
+++ b/app/lib/sprites/SpriteBuilder.coffee
@@ -1,9 +1,13 @@
+{hexToHSL, hslToHex} = require 'lib/utils'
+
 module.exports = class SpriteBuilder
   constructor: (@thangType, @options) ->
+    @options ?= {}
     raw = _.cloneDeep(@thangType.get('raw'))
     @shapeStore = raw.shapes
     @containerStore = raw.containers
     @animationStore = raw.animations
+    @buildColorMaps()
 
   setOptions: (@options) ->
 
@@ -93,7 +97,7 @@ module.exports = class SpriteBuilder
     if shapeData.lf?
       shape.graphics.lf shapeData.lf...
     else if shapeData.fc?
-      shape.graphics.f shapeData.fc
+      shape.graphics.f @colorMap[shapeKey] or shapeData.fc
     if shapeData.ls?
       shape.graphics.ls shapeData.ls...
     else if shapeData.sc?
@@ -118,3 +122,64 @@ module.exports = class SpriteBuilder
       cont.addChild(child)
     cont.bounds = new createjs.Rectangle(contData.b...)
     cont
+    
+  buildColorMaps: ->
+    @colorMap = {}
+    colorGroups = @thangType.get('colorGroups')
+    return if _.isEmpty colorGroups
+    colorConfig = @options.colorConfig
+#    colorConfig ?= {team: {hue:0.4, saturation: -0.5, lightness: -0.5}} # test config
+    return if not colorConfig
+
+    for group, config of colorConfig
+      continue unless colorGroups[group] # color group not found...
+      @buildColorMapForGroup(colorGroups[group], config)
+      
+  buildColorMapForGroup: (shapes, config) ->
+    return unless shapes.length
+    colors = @initColorMap(shapes)
+    @adjustHuesForColorMap(colors, config.hue)
+    @adjustColorMap(colors, 1, config.lightness)
+    @adjustColorMap(colors, 2, config.saturation)
+    @applyColorMap(shapes, colors)
+    
+  initColorMap: (shapes) ->
+    colors = {}
+    for shapeKey in shapes
+      shape = @shapeStore[shapeKey]
+      continue if (not shape.fc?) or colors[shape.fc]
+      hsl = hexToHSL(shape.fc)
+      colors[shape.fc] = hsl
+    colors
+
+  adjustHuesForColorMap: (colors, targetHue) ->
+    hues = (hsl[0] for hex, hsl of colors)
+    
+    # 'rotate' the hue spectrum so averaging works
+    if Math.max(hues) - Math.min(hues) > 0.5
+      hues = (if h < 0.5 then h + 1.0 else h for h in hues)
+    averageHue = sum(hues) / hues.length
+    averageHue %= 1
+    # end result should be something like a hue array of [0.9, 0.3] gets an average of 0.1
+    
+    targetHue ?= 0
+    diff = targetHue - averageHue
+    hsl[0] = (hsl[0] + diff + 1) % 1 for hex, hsl of colors
+
+  adjustColorMap: (colorMap, index, adjustment) ->
+    return unless adjustment
+    for hex, hsl of colorMap
+      value = hsl[index]
+      if adjustment > 0
+        hsl[index] = value + (1 - value) * adjustment
+      else
+        hsl[index] = value + value * adjustment
+
+  applyColorMap: (shapes, colors) ->
+    for shapeKey in shapes
+      shape = @shapeStore[shapeKey]
+      continue if (not shape.fc?) or not(colors[shape.fc])
+      @colorMap[shapeKey] = hslToHex(colors[shape.fc])
+
+
+sum = (nums) -> _.reduce(nums, (s, num) -> s + num)
\ No newline at end of file
diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee
index 499fdcc3b..8fe47dbee 100644
--- a/app/lib/utils.coffee
+++ b/app/lib/utils.coffee
@@ -28,4 +28,20 @@ module.exports.normalizeFunc = (func_thing, object) ->
       console.error("Could not find method", func_thing, 'in object', @)
       return => null # always return a func, or Mediator will go boom
     func_thing = func
-  return func_thing 
\ No newline at end of file
+  return func_thing 
+  
+module.exports.hexToHSL = (hex) ->
+  rgbToHsl(hexToR(hex), hexToG(hex), hexToB(hex))
+
+hexToR = (h) -> parseInt (cutHex(h)).substring(0, 2), 16
+hexToG = (h) -> parseInt (cutHex(h)).substring(2, 4), 16
+hexToB = (h) -> parseInt (cutHex(h)).substring(4, 6), 16
+cutHex = (h) -> (if (h.charAt(0) is "#") then h.substring(1, 7) else h)
+  
+module.exports.hslToHex = (hsl) ->
+  '#' + (toHex(n) for n in hslToRgb(hsl...)).join('')
+  
+toHex = (n) ->
+  h = Math.floor(n).toString(16)
+  h = '0'+h if h.length is 1
+  h
\ No newline at end of file
diff --git a/app/styles/editor/thang/colors_tab.sass b/app/styles/editor/thang/colors_tab.sass
new file mode 100644
index 000000000..d91d81fd0
--- /dev/null
+++ b/app/styles/editor/thang/colors_tab.sass
@@ -0,0 +1,55 @@
+$height: 550px
+
+#editor-thang-colors-tab-view
+  #color-group-settings
+    width: 75%
+    box-sizing: border-box
+    float: right
+
+  #color-groups-treema
+    float: left
+    width: 25%
+    height: $height
+    box-sizing: border-box
+    margin-bottom: 20px
+      
+  #shape-buttons
+    border: 1px solid saddlebrown
+    margin: 0 20px 0 10px
+    width: 30%
+    float: left
+    height: $height
+    overflow: scroll
+    background: sandybrown
+    padding: 5px
+    box-sizing: border-box
+    
+    button
+      width: 20px
+      height: 25px
+      border: 2px solid white
+      margin: 1px
+      
+      &.selected
+        border: 2px solid black
+        
+  #controls
+    //clear: both
+    float: left
+    width: 60%
+    text-align: center
+    background-color: aliceblue
+    border: 1px solid blue
+    
+    .slider-cell
+      margin: 20px 10px
+      border-color: black
+      .ui-slider
+        border: 1px solid black
+      .ui-slider-handle
+        border: 1px solid black
+    
+  canvas
+    width: 60%
+    background: steelblue
+    border: 1px solid darkblue
\ No newline at end of file
diff --git a/app/templates/editor/thang/colors_tab.jade b/app/templates/editor/thang/colors_tab.jade
new file mode 100644
index 000000000..3c517093e
--- /dev/null
+++ b/app/templates/editor/thang/colors_tab.jade
@@ -0,0 +1,22 @@
+div#color-groups-treema
+
+div#color-group-settings.hide
+  div#shape-buttons
+    
+  canvas#tinting-display(width=400, height=400)
+    
+  div#controls
+    div.slider-cell
+      | Hue:
+      span.hue-label
+      .selector#hue-slider
+  
+    div.slider-cell
+      | Saturation:
+      span.saturation-label
+      .selector#saturation-slider
+  
+    div.slider-cell
+      | Lightness:
+      span.lightness-label
+      .selector#lightness-slider
diff --git a/app/templates/editor/thang/edit.jade b/app/templates/editor/thang/edit.jade
index ae85337c4..9878c4e92 100644
--- a/app/templates/editor/thang/edit.jade
+++ b/app/templates/editor/thang/edit.jade
@@ -16,8 +16,12 @@ block content
       a(href="#editor-thang-components-tab-view", data-toggle="tab") Components
     li
       a(href="#editor-thang-canvases-view", data-toggle="tab") Canvases
+    li
+      a(href="#editor-thang-colors-tab-view", data-toggle="tab")#color-tab Colors
 
   div.tab-content
+    div.tab-pane#editor-thang-colors-tab-view
+    
     div.tab-pane.active#editor-thang-main-tab-view
 
       div.main-area.well
diff --git a/app/views/editor/thang/colors_tab_view.coffee b/app/views/editor/thang/colors_tab_view.coffee
new file mode 100644
index 000000000..a116ff4c5
--- /dev/null
+++ b/app/views/editor/thang/colors_tab_view.coffee
@@ -0,0 +1,158 @@
+CocoView = require 'views/kinds/CocoView'
+template = require 'templates/editor/thang/colors_tab'
+SpriteBuilder = require 'lib/sprites/SpriteBuilder'
+{hexToHSL} = require 'lib/utils' 
+
+module.exports = class ColorsTabView extends CocoView
+  id: 'editor-thang-colors-tab-view'
+  template: template
+  className: 'tab-pane'
+  
+  offset: 0
+  
+  constructor: (@thangType, options) ->
+    @thangType.once 'sync', @tryToBuild, @
+    @thangType.schema().once 'sync', @tryToBuild, @
+    @colorConfig = { hue: 0, saturation: 0, lightness: 0 }
+    @spriteBuilder = new SpriteBuilder(@thangType)
+    f = => 
+      @offset++
+      @updateMovieClip()
+    @interval = setInterval f, 1000
+    super options
+
+  afterRender: ->
+    super()
+    @createShapeButtons()
+    @initStage()
+    @initSliders()
+    @tryToBuild()
+    
+  # sliders
+
+  initSliders: ->
+    @hueSlider = @initSlider $("#hue-slider", @$el), 0, @updateHue
+    @saturationSlider = @initSlider $("#saturation-slider", @$el), 50, @updateSaturation
+    @lightnessSlider = @initSlider $("#lightness-slider", @$el), 50, @updateLightness
+    
+  updateHue: =>
+    @colorConfig.hue = @hueSlider.slider('value') / 100
+    @updateMovieClip()
+    
+  updateSaturation: =>
+    @colorConfig.saturation = (@saturationSlider.slider('value') / 50) - 1
+    @updateMovieClip()
+
+  updateLightness: =>
+    @colorConfig.lightness = (@lightnessSlider.slider('value') / 50) - 1
+    @updateMovieClip()
+    
+  # movie clip
+  
+  initStage: ->
+    canvas = @$el.find('#tinting-display')
+    @stage = new createjs.Stage(canvas[0])
+    createjs.Ticker.setFPS 20
+    createjs.Ticker.addEventListener("tick", @stage)
+    @updateMovieClip()
+
+  updateMovieClip: ->
+    return unless @currentColorGroupTreema
+    actionDict = @thangType.getActions()
+    animations = (a.animation for key, a of actionDict when a.animation)
+    index = @offset % animations.length
+    animation = animations[index]
+    return unless animation
+    @stage.removeChild(@movieClip) if @movieClip
+    options = {colorConfig: {}}
+    options.colorConfig[@currentColorGroupTreema.keyForParent] = @colorConfig
+    @spriteBuilder.setOptions options
+    @spriteBuilder.buildColorMaps()
+    @movieClip = @spriteBuilder.buildMovieClip animation
+    larger = Math.min(400 / @movieClip.nominalBounds.width, 400 / @movieClip.nominalBounds.height)
+    @movieClip.scaleX = larger
+    @movieClip.scaleY = larger
+    @movieClip.regX = @movieClip.nominalBounds.x
+    @movieClip.regY = @movieClip.nominalBounds.y
+    @stage.addChild @movieClip
+
+  createShapeButtons: ->
+    buttons = $('<div></div>').prop('id', 'shape-buttons')
+    shapes = (shape for key, shape of @thangType.get('raw')?.shapes or {})
+    colors = (s.fc for s in shapes when s.fc?)
+    colors = _.uniq(colors)
+    colors.sort (a, b) ->
+      aHSL = hexToHSL(a)
+      bHSL = hexToHSL(b)
+      if aHSL[0] > bHSL[0] then -1 else 1
+      
+    for color in colors
+      button = $('<button></button>').addClass('btn')
+      button.css('background', color)
+      button.val color
+      buttons.append(button)
+    buttons.click (e) =>
+      $(e.target).toggleClass('selected')
+      @updateColorGroup()
+    @$el.find('#shape-buttons').replaceWith(buttons)
+    @buttons = buttons
+
+  tryToBuild: ->
+    return unless @thangType.loaded and @thangType.schema().loaded
+    data = @thangType.get('colorGroups')
+    data ?= {}
+    schema = @thangType.schema().attributes.properties?.colorGroups
+    treemaOptions =
+      data: data
+      schema: schema
+      callbacks:
+        change: @onColorGroupsChanged
+        select: @onColorGroupSelected
+      nodeClasses:
+        'thang-color-group': ColorGroupNode
+    @colorGroups = @$el.find('#color-groups-treema').treema treemaOptions
+    @colorGroups.build()
+    @colorGroups.open()
+    keys = Object.keys @colorGroups.childrenTreemas
+    @colorGroups.childrenTreemas[keys[0]]?.$el.click() if keys[0]
+
+  onColorGroupsChanged: =>
+    @thangType.set('colorGroups', @colorGroups.data)
+
+  onColorGroupSelected: (e, selected) =>
+    @$el.find('#color-group-settings').toggleClass('hide', not selected.length)
+    treema = @colorGroups.getLastSelectedTreema()
+    return unless treema
+    @currentColorGroupTreema = treema
+    
+    shapes = {}
+    shapes[shape] = true for shape in treema.data
+    
+    colors = {}
+    for key, shape of @thangType.get('raw')?.shapes or {}
+      continue unless shape.fc?
+      colors[shape.fc] = true if shapes[key]
+    
+    @buttons.find('button').removeClass('selected')
+    @buttons.find('button').each (i, button) ->
+      $(button).addClass('selected') if colors[$(button).val()]
+      
+    @updateMovieClip()
+
+  updateColorGroup: ->
+    colors = {}
+    @buttons.find('button').each (i, button) ->
+      return unless $(button).hasClass('selected')
+      window.button = button
+      colors[$(button).val()] = true
+    
+    shapes = []
+    for key, shape of @thangType.get('raw')?.shapes or {}
+      continue unless shape.fc?
+      shapes.push(key) if colors[shape.fc]
+
+    @currentColorGroupTreema.set('/', shapes)
+
+class ColorGroupNode extends TreemaNode.nodeMap.array
+  collection: false
+  canAddChild: -> false
diff --git a/app/views/editor/thang/edit.coffee b/app/views/editor/thang/edit.coffee
index 06091470d..a218ca4af 100644
--- a/app/views/editor/thang/edit.coffee
+++ b/app/views/editor/thang/edit.coffee
@@ -8,6 +8,8 @@ Camera = require 'lib/surface/Camera'
 ThangComponentEditView = require 'views/editor/components/main'
 DocumentFiles = require 'collections/DocumentFiles'
 
+ColorsTabView = require './colors_tab_view'
+
 CENTER = {x:200, y:300}
 
 module.exports = class ThangTypeEditView extends View
@@ -66,6 +68,7 @@ module.exports = class ThangTypeEditView extends View
     @buildTreema()
     @initSliders()
     @initComponents()
+    @insertSubView(new ColorsTabView(@thangType))
 
   initComponents: =>
     options =
@@ -243,13 +246,6 @@ module.exports = class ThangTypeEditView extends View
 
   # sliders
 
-  initSlider: ($el, startValue, changeCallback) ->
-    slider = $el.slider({ animate: "fast" })
-    slider.slider('value', startValue)
-    slider.on('slide',changeCallback)
-    slider.on('slidechange',changeCallback)
-    slider
-
   initSliders: ->
     @rotationSlider = @initSlider $("#rotation-slider", @$el), 50, @updateRotation
     @scaleSlider = @initSlider $('#scale-slider', @$el), 29, @updateScale
diff --git a/app/views/kinds/CocoView.coffee b/app/views/kinds/CocoView.coffee
index debb13f56..8ee5cacd7 100644
--- a/app/views/kinds/CocoView.coffee
+++ b/app/views/kinds/CocoView.coffee
@@ -217,5 +217,14 @@ module.exports = class CocoView extends Backbone.View
     ua = navigator.userAgent or navigator.vendor or window.opera
     return ua.search("MSIE") != -1
 
+  initSlider: ($el, startValue, changeCallback) ->
+    slider = $el.slider({ animate: "fast" })
+    slider.slider('value', startValue)
+    slider.on('slide',changeCallback)
+    slider.on('slidechange',changeCallback)
+    slider
+    
+    
+
 mobileRELong = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
 mobileREShort = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
diff --git a/server/handlers/thang_type.coffee b/server/handlers/thang_type.coffee
index d80b70e48..d7a9b59dd 100644
--- a/server/handlers/thang_type.coffee
+++ b/server/handlers/thang_type.coffee
@@ -19,6 +19,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
     'positions',
     'snap',
     'components'
+    'colorGroups'
   ]
 
   hasAccess: (req) ->
diff --git a/server/schemas/thang_type.coffee b/server/schemas/thang_type.coffee
index 11a6a1db0..8583955b2 100644
--- a/server/schemas/thang_type.coffee
+++ b/server/schemas/thang_type.coffee
@@ -122,6 +122,12 @@ _.extend ThangTypeSchema.properties,
     title: 'Scale'
     type: 'number'
   positions: PositionsSchema
+  colorGroups: c.object 
+    title: 'Color Groups'
+    additionalProperties:
+      type:'array'
+      format: 'thang-color-group'
+      items: {type:'string'} 
   snap: c.object { title: "Snap", description: "In the level editor, snap positioning to these intervals.", required: ['x', 'y'] },
     x:
       title: "Snap X"