CodePen

HTML

            
              <div class="kudos" data-amount="0" data-url="codepen.io/TimPietrusky/pen/acBCf"></div>

<footer>
  2013 by 
  <a href="http://twitter.com/TimPietrusky" target="_blank">@TimPietrusky</a>
</footer>
            
          
!

CSS

            
              @import url(http://weloveiconfonts.com/api/?family=fontawesome);

/*
 * Use FontAwesome from weloveiconfonts.com
 */
[class*="fontawesome-"]:before {
  font-family: 'FontAwesome', sans-serif;
  font-weight:normal;
} 

/*
 * Default styles
 */
body {
  font: 1.5em sans-serif;
  margin:.5em;
  text-align:center;
}

footer {
  margin:3.75em 0 0 0;
  font-size:.65em;
  
  a {
    color:rgba(#000, .7);
    text-decoration:none;
    
    &:hover {
      color:#000;
    }
  }
}

/*
 * Kudos
 */
$kudos_duration: 1.5s;
$kudos_duration_finish: .45s;
$kudos_width: 6em;
$kudos_height: 6em;
$kudos_color_alpha: #fff; // default
$kudos_color_beta: #cc3d39; // active
$kudos_color_gamma: #000; // default border

.kudos {
  position:relative;
  width:$kudos_width;
  height:$kudos_height;
  margin:0 auto;
  background:$kudos_color_alpha;
  box-shadow:
    inset 0 0 0 .25em $kudos_color_gamma,
    inset 0 0 0 $kudos_width / 3 $kudos_color_alpha,
    inset 0 0 0 $kudos_width #000
  ; 
  line-height:$kudos_height;
  text-align:center;
  border-radius:50%;
  @include transition(box-shadow $kudos_duration_finish * 2 ease-in-out $kudos_duration_finish / 4);
  
  &:before {
    @include transition(font-size $kudos_duration_finish ease-in);
    font-size:1.75em;
    color:$kudos_color_alpha;
    line-height:$kudos_height / 1.725;
  }
  
  &.active {
    @include transition(box-shadow $kudos_duration linear);
    box-shadow:
      inset 0 0 0 .25em $kudos_color_gamma,
      inset 0 0 0 0 $kudos_color_alpha,
      inset 0 0 0 .75em rgba($kudos_color_beta, .75),
      inset 0 0 0 $kudos_width $kudos_color_beta
    ;
      
    &:before {
      @include transition(color $kudos_duration linear);
      color: $kudos-color-beta;
    }
      
    &:after {
       content: 'Don\'t move!';
    }
  }
  
  &.finish {
    @include transition(
      box-shadow $kudos_duration_finish linear,
      transform $kudos_duration_finish * 1.25 ease-in-out
    );
    box-shadow:
      inset 0 0 0 .25em rgba($kudos_color_beta, .5),
      inset 0 0 0 .5em $kudos_color_alpha,
      inset 0 0 0 .75em rgba($kudos_color_beta, .75),
      inset 0 0 0 1em $kudos_color_alpha,
      inset 0 0 0 0 $kudos_color_alpha,
      inset 0 0 0 $kudos_width $kudos_color_beta
    ;
    
    &:before {  
      font-size:2.25em;
      color:$kudos_color_alpha;
      line-height:$kudos_height / 2.125;
    }
  }
  
  &:after {
    position:absolute;
    content: attr(data-amount) ' Kudos';
    bottom:-1.25em;
    left:0;
    width:$kudos_width;
    text-align:center;
    line-height:1em;
    font-variant:small-caps;
  }
}
            
          
!
? ?
? ?
Must be a valid URL.
+ add another resource
via CSS Lint

JS

            
               /**
  Kudos Please

  A simple kudos widget without any external lib and it works
  with touch & mouse devices. 
  
  Inspired by dcurt.is
  
  The heart in this example is served by weloveiconfonts.com,
  but you can just add a value manually (.finish:before{content:''}).

  # 2013 by Tim Pietrusky
  # timpietrusky.com
**/

KudosPlease = (function() {
  
  var _$;
  
  // Constructor
  function KudosPlease(args) {
    _$ = this;

    // All widgets
    this.elements = document.querySelectorAll(args.el);
    // Set the status
    this.status = args.status;
    // Is localStorage enabled?
    this.persistent = (args.persistent != undefined && args.persistent && localStorage != undefined);
    // Duration of activation
    this.duration = args.duration;
    // setTimeout-ID's
    this.timer = {};
    // @TODO [TimPietrusky] - This should be an array
    this.currentStatus = '';
    
    for (var i = 0; i < this.elements.length; i++) {
      var el = this.elements[i];
      
      // Delete all elements from localStorage
      // localStorage.setItem('kudos:saved:'+el.getAttribute('data-url'), 0);
      
      // Identify element
      el.setAttribute('data-id', i);
      
      // Load kudos via ajax
      _$.request(el, 'GET');
      
      // Amount is 0
      if (this.loadAmount(i) == 0) {
        // Set kudos amount
        el.setAttribute('data-amount', 0);
      
        // Init timer id
        this.timer[i] = -1;
       
        // Events
        if (this.isTouch()) {
          this.on(el, 'touchstart', this.enter);
          this.on(el, 'touchend', this.out);
        } else {
          this.on(el, 'mouseover', this.enter);
          this.on(el, 'mouseout', this.out);
        }
        
      // Load the amount and display it, because user already voted
      } else {
        this.finish(el);
      }
    }
  };
  
  /*
   * Enter the element
   */
  KudosPlease.prototype.enter = function(e) {
     var that = this,
         id = -1;
    
    // Do the kudo twist
    if (!_$.hasClass(this, 'finish')) {
      // Activate the kudo twist
      _$.addClass(that, 'active');
    
      // Start timeout
      id = setTimeout(function() {
        _$.removeClass(that, 'active');
        _$.finish(that, true);
      }, _$.duration);
    
      // Add timeout id to global object
      _$.timer[that.getAttribute('data-id')] = id;
    }
  };
  
  // Leave the element
  KudosPlease.prototype.out = function(e) {
    if (!_$.hasClass(this, 'finish')) {
      _$.removeClass(this, 'active');
      clearTimeout(_$.timer[this.getAttribute('data-id')]);
    }
  };
  
  /*
   * State: finished (kudos given)
   */
  KudosPlease.prototype.finish = function(el, increase) {
    // Finished
    _$.addClass(el, 'finish');
    _$.changeStatus(el, 'gamma');
    
    increase = increase || false;
    amount = _$.loadAmount(parseInt(el.getAttribute('data-id'), 10));
    
    if (increase) {
      ++amount;
      
      // Update kudos via ajax
      _$.request(el, 'POST');
    }
  };
  
  /*
   * Change the status of the widget and
   * aply 3 different classes for the icon
   * in the middle. 
   */
  KudosPlease.prototype.changeStatus = function(el, state) {   
    if (_$.status != undefined) {
      _$.removeClass(el, _$.currentStatus);
      _$.addClass(el, _$.status[state]);
      _$.currentStatus = _$.status[state];
    }
  };
  
  /**
   * Helper functions 
   */
  
  /*
   * Bind event
   */
  KudosPlease.prototype.on = function(el, event, func) {
    try {
      el.addEventListener(event, func, false);
    } catch(e) {
      el.attachEvent('on' + event, func);
    }
  };
  
  /*
   * Add <CODE>class</CODE> to <CODE>el</CODE>
   */
  KudosPlease.prototype.addClass = function(el, classes) {
    classes = classes.split(',');
    
    for (var i=0; i < classes.length; i++) {
      if (el.className.indexOf(classes[i]) == -1) {
        el.className = el.className.trim() + ' ' + classes[i];
      }
    }
  };
  
  /*
   * Remove <CODE>class</CODE> to <CODE>el</CODE>
   */
  KudosPlease.prototype.removeClass = function(el, classes) {
    classes = classes.split(',');
    
    for (var i = 0; i < classes.length; i++) {
      el.className = el.className.replace(classes[i], '').trim();
    }
  };
  
  /* 
   * Returns <CODE>true</CODE> if <CODE>el</CODE> has 
   * the <CODE>class</CODE>, <CODE>false</CODE> otherwise
   */
  KudosPlease.prototype.hasClass = function(el, className) {
    var classes = el.className.split(' '),
        result = false;
    
    for (var i = 0; i < classes.length; i++) {
      if (classes[i] == className) {
        result = true;
      }
    }
    
    return result;
  };
  
  /* 
   * Returns <CODE>true</CODE> if the actual
   * device is a touch device, <CODE>false</CODE> otherwise
   * 
   * http://stackoverflow.com/a/4819886/1012875
   */
  KudosPlease.prototype.isTouch = function() {
    return !!('ontouchstart' in window)
        || !!('onmsgesturechange' in window); 
  };
  
  /*
   * Saves the amount of a specific widget into localStorage
   * when <CODE>persistent</CODE> is <CODE>true</CODE>. 
   */
  KudosPlease.prototype.save = function(el, amount) {
    if (_$.persistent) {
      localStorage.setItem('kudos:saved:' + el.getAttribute('data-url'), amount);
    }
  };
  
  /*
   * Loads the amount of a specific widget from the localStorage
   * when <CODE>persistent</CODE> is <CODE>true</CODE>. 
   */
  KudosPlease.prototype.loadAmount = function(id) {
    var result = _$.elements[id].getAttribute('data-amount') || 0;

    if (_$.persistent) {
      if ((amount = localStorage.getItem('kudos:saved:' + _$.elements[id].getAttribute('data-url'))) != null) {
        result = amount;
      }
    }
    
    return result;
  };
  
  /*
   * Create a ajax request to a backend
   * which just keeps track of the kudos counter
   * via php & mysql
   */
  KudosPlease.prototype.request = function(el, type) {
    var xhr;
    
    // Initialize
    try {
     xhr = new ActiveXObject("Microsoft.XMLHTTP");
    } catch(e) {
     xhr = new XMLHttpRequest(); 
    }
    
    // Change the amount
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4 && xhr.status == 200) {
        var amount = xhr.responseText;
        el.setAttribute('data-amount', amount);

        if (type == 'GET') {
          _$.changeStatus(el, amount == 0 ? 'alpha' : 'beta');
          
          if (_$.persistent 
           && localStorage.getItem('kudos:saved:' + el.getAttribute('data-url')) != null) {
            
            _$.changeStatus(el, 'gamma');
          }
        }

        if (type == 'POST') {
          _$.save(el, amount);
        }
      }
    }
    
    var url = "?url="+encodeURIComponent(el.getAttribute('data-url'));
    // Open request
    xhr.open(type, "http://api.kudosplease.com/" + url, true);
    xhr.send();
  };
  
  // trim polyfill
  ''.trim || (String.prototype.trim = function(){
    return this.replace(/^\s+|\s+$/g,'');
  });
  
  return KudosPlease;
})();

/*
 * DOM ready function 
 * http://dustindiaz.com/smallest-domready-ever
 */
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}

r(function() {
  /*
   * Create Kudos Please widget
   */
  var kudosPlease = new KudosPlease({ 
    el : '.kudos',
    duration : 1500,
    persistent : false,
    status : {
      alpha : 'fontawesome-star',
      beta : 'fontawesome-glass',
      gamma : 'fontawesome-bolt'
    }
  });
});
            
          
!
Must be a valid URL.
+ add another resource
via JS Hint
Loading ..................