(function($){

  var fadeElement = function($obj, fps, duration, from, to) {
    if (!fps) fps = 30;
    if (!duration) duration = 3000;
    if (!from || from=='#') from = '#FFFF33';
    if (!to) {
      to = $obj.css('background-color');
      if ( to == undefined || to == '' || to == 'transparent') to = '#FFFFFF';
      var rgb = to.match(/rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/);
      if(rgb) to = makeHex(parseInt(rgb[1]), parseInt(rgb[2]), parseInt(rgb[3]));
    }

    var frames = Math.round(fps * (duration / 1000));
    var interval = duration / frames;
    var delay = interval;
    var frame = 0;

    if (from.length < 7) from += from.substr(1,3);
    if (to.length < 7) to += to.substr(1,3);

    var rf = parseInt(from.substr(1,2),16);
    var gf = parseInt(from.substr(3,2),16);
    var bf = parseInt(from.substr(5,2),16);
    var rt = parseInt(to.substr(1,2),16);
    var gt = parseInt(to.substr(3,2),16);
    var bt = parseInt(to.substr(5,2),16);

    var r,g,b;
    var o = $obj.get(0);
    while (frame < frames)
    {
      r = Math.floor(rf * ((frames-frame)/frames) + rt * (frame/frames));
      g = Math.floor(gf * ((frames-frame)/frames) + gt * (frame/frames));
      b = Math.floor(bf * ((frames-frame)/frames) + bt * (frame/frames));

      setTimeout(
        (function() { 
          var h = makeHex(r,g,b); 
          return function() { o.style.backgroundColor = h; } 
        })(), delay);

      frame++;
      delay = interval * frame;
    }
    setTimeout(function() { o.style.backgroundColor = to; }, delay);
  }

  var makeHex = function(r,g,b) {
    var r = r.toString(16); if (r.length == 1) r = '0' + r;
    var g = g.toString(16); if (g.length == 1) g = '0' + g;
    var b = b.toString(16); if (b.length == 1) b = '0' + b;
    return '#' + r + g + b;
  }

  $.fn.fade = function(options) {
    if (!options) options = {};

    $.extend({
      duration: 3000,
      fps: 30,
      to: null
    }, options);

    this.each(function() {
      var r = /fade-?(\w{3,6})?/.exec(this.className);
      if(r)
      {
        if (!r[1]) r[1] = "";
        fadeElement($(this), options.fps, options.duration, '#'+r[1]);
      }
    });

    return this;
  }
})(jQuery);
