(function($){
  
  var rupper = /([A-Z])/g; 
  
  // ----------
  // Function: stopCssAnimation
  // Stops an animation in its tracks!
  $.fn.stopCssAnimation = function(){   
    this.each(function(){
      // When you remove the CSS Transition properties
      // it doesn't just end the animation where it was,
      // instead it jumps the animation to the end state.
      // Thus, to stop an animation, you first have to get
      // its current computed values, remove the transition
      // properties, and then finally set the elements
      // values appropriatly.
      
      // Get the computed values of the animated properties
      var $el = $(this);
      var props = $el.data("cssAnimatedProps");
      var cStyle = window.getComputedStyle(this, null);
      var cssValues = {};     
      for(var prop in props){
        prop = prop.replace( rupper, "-$1" ).toLowerCase();
        cssValues[prop] = cStyle.getPropertyValue(prop);
      }
      
      // Remove the CSS Transition CSS
      $el.css({
        '-moz-transition-property': 'none',  
        '-moz-transition-duration': '',  
        '-moz-transition-timing-function': '',
        '-webkit-transition-property': 'none',  
        '-webkit-transition-duration': '',  
        '-webkit-transition-timing-function': ''    
      });
      
      // Set the saved computed properties
      for( var prop in cssValues ){
        $el.css(prop, cssValues[prop]);
      }
      
      // Cancel any onComplete function
      $el.data("cssAnimatedProps", null);
      var timeoutId = $el.data("cssTimeoutId");
      if( timeoutId != null ) {
        clearTimeout(timeoutId);
        $el.data("cssTimeoutId", null);
      }
    });
  };
  
  // ----------
  // Function: animateWithCSS
  // Uses CSS transitions to animate the element. 
  // 
  // Parameters: 
  //   Takes the same properties as jQuery's animate function.
  //
  //   The only difference is that the easing paramater can now be:
  //   bounce, swing, linear, ease-in, ease-out, cubic-bezier, or
  //   manually defined as cubic-bezier(x1,y1,x2,y2);
  $.fn.animateWithCss = function(props, speed, easing, callback) {
    var optall = jQuery.speed(speed, easing, callback);  
    
    if ( jQuery.isEmptyObject( props ) ) {
      return this.each( optall.complete );
    }
    
    var easings = {
      bounce: 'cubic-bezier(0.0, 0.35, .5, 1.3)', 
      linear: 'linear',
      swing: 'ease-in-out'
    };
    
    optall.easing = optall.easing || "swing";
    optall.easing = easings[optall.easing] ? easings[optall.easing] : optall.easing;
    
    // The latest versions of Firefox do not animate from a non-explicitly set
    // css properties. So for each element to be animated, go through and
    // explicitly define 'em.
    this.each(function(){
      $(this).data("cssAnimatedProps", props);
      var cStyle = window.getComputedStyle(this, null);      
      for(var prop in props){
        prop = prop.replace( rupper, "-$1" ).toLowerCase();
        $(this).css(prop, cStyle.getPropertyValue(prop));
      }    
    });
    
    this.css({
      '-moz-transition-property': 'all', // TODO: just animate the properties we're changing  
      '-moz-transition-duration': optall.duration + 'ms',  
      '-moz-transition-timing-function': optall.easing,
      '-webkit-transition-property': 'all', // TODO: just animate the properties we're changing  
      '-webkit-transition-duration': optall.duration + 'ms',  
      '-webkit-transition-timing-function': optall.easing
    });
    
    this.css(props);
  
    var self = this;
    var timeoutId = setTimeout(function() {    
      self.css({
        '-moz-transition-property': 'none',  
        '-moz-transition-duration': '',  
        '-moz-transition-timing-function': '',
        '-webkit-transition-property': 'none',  
        '-webkit-transition-duration': '',  
        '-webkit-transition-timing-function': ''
      });
      
      self.data("cssAnimatedProps", null); 
      self.data("cssTimeoutId", null);        
  
      if(jQuery.isFunction(optall.complete))
        optall.complete.apply(self);
    }, optall.duration);
    this.data( "cssTimeoutId", timeoutId );
    
    
    return this;
  }

})(jQuery);