Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
.container(style="margin-bottom:40px;")
  .row
    .col-sm-12
      h2 Stacked card shuffle

.card-shuffle
  .relative
    .card-shuffle__card(data-position="0")
      .container.height-inherit
        .row.height-inherit
          .col-sm-12.height-inherit
            .card-shuffle__card-inner.green.height-inherit.grab
    .card-shuffle__card(data-position="1")
      .container.height-inherit
        .row.height-inherit
          .col-sm-12.height-inherit
            .card-shuffle__card-inner.red.height-inherit.grab
    .card-shuffle__card(data-position="2")
      .container.height-inherit
        .row.height-inherit
          .col-sm-12.height-inherit
            .card-shuffle__card-inner.blue.height-inherit.grab
    .card-shuffle__card(data-position="3")
      .container.height-inherit
        .row.height-inherit
          .col-sm-12.height-inherit
            .card-shuffle__card-inner.orange.height-inherit.grab


              
            
!

CSS

              
                body {
  background-color: #ECF0F1;
}

// Easing
$easeOutQuart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
$easeOutExpo: cubic-bezier(0.190, 1.000, 0.220, 1.000);

.blue { background-color: #3498DB; }
.red { background-color: #E74C3C; }
.orange { background-color: #E67E22; }
.green { background-color: #2ECC71; }

.relative { position: relative; }
.height-inherit { height: inherit; }

.grab {
  cursor: -webkit-grab;
  cursor: -moz-grab;
  cursor: grab;
}

.grab.grabbing,
.grabbing,
.grabbing .grab,
.gxRangeHandle:active {
  cursor: -webkit-grabbing;
  cursor: -moz-grabbing;
  cursor: grabbing;
}

.container {
  max-width: 1200px;
}
@media (min-width: 768px) {
  .container {
    width: 93%;
  }
}

$stackedAdvanceDuration: .1s;
$stackedOffDeckDuration: .3s;
$stackedToBackDuration: .15s;

.card-shuffle {
  position: relative;
  padding-bottom: 20px;
  padding-top: 20px;
  height: 320px;
  width: 100%;
  overflow: hidden;
}

.card-shuffle__card {
  position: absolute;
  z-index: 0;
  top: 0;
  left: 0;
  width: 100%;
  height: 275px;
  transform: translateZ(0);
  transition: transform $stackedAdvanceDuration $easeOutQuart;
}

.card-shuffle__card-inner {
  border-radius: 2px;
  box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.2);
}

$skews: (-1.0deg, 1.5deg, -0.6deg, 0.8deg, -1.5deg);

@for $i from 1 through length($skews) {
  .card-shuffle__card[data-position="#{$i - 1}"] {
    z-index: length($skews) - $i;
  }
  .card-shuffle__card:nth-child(#{$i}) {
    transform: rotate( nth($skews, $i) );
  }
}

.card-shuffle__card[data-position="0"] {
  transition-duration: $stackedToBackDuration;
}

.card-shuffle__card.card-shuffle__card--front {
  transform: rotate(0);
}

// To bottom of the deck.
.card-shuffle__card.card-shuffle__card--to-bottom {
  z-index: 10;
  transition-duration: $stackedOffDeckDuration;
  transition-timing-function: $easeOutExpo;
}

.card-shuffle__card.card-shuffle--no-transition {
  transition-duration: 0ms;
}


@media (max-width: 568px) {
  $skews: (-2.4deg, 2.5deg, -3.0deg, 2.8deg, -2.5deg);

  @for $i from 1 through length($skews) {
    .card-shuffle__card:nth-child(#{$i}) {
      transform: rotate( nth($skews, $i) );
    }
  }

  .card-shuffle__card.card-shuffle__card--front {
    transform: rotate(0);
  }
}

@media (max-width: 767px) {

  .card-shuffle__card.card-shuffle__card--off-deck-end {
    transform: translate( calc(100% - 30px), 0 );
  }

  .card-shuffle__card.card-shuffle__card--off-deck-start {
    transform: translate( calc(-100% + 30px), 0 );
  }
}

@media (min-width: 768px) {

  .card-shuffle__card.card-shuffle__card--off-deck-end {
    transform: translate( calc(93% - 30px), 0 );
  }

  .card-shuffle__card.card-shuffle__card--off-deck-start {
    transform: translate( calc(-93% + 30px), 0 );
  }
}

@media (min-width: (1200px / 0.93)) {

  .card-shuffle__card.card-shuffle__card--off-deck-end {
    transform: translate( calc(1200px - 30px), 0 );
  }

  .card-shuffle__card.card-shuffle__card--off-deck-start {
    transform: translate( calc(-1200px + 30px), 0 );
  }
}


              
            
!

JS

              
                
define('app/device',['require','modernizr'],function(require) {

  var Modernizr = require('modernizr');
  var Device = {};

  /**
   * Hyphenates a javascript style string to a css one. For example:
   * MozBoxSizing -> -moz-box-sizing.
   *
   * @param {string|boolean} str The string to hyphenate.
   * @return {string} The hyphenated string.
   */
  Device.hyphenate = function(str) {

    // Catch booleans.
    if (!str) {
      return '';
    }

    // Turn MozBoxSizing into -moz-box-sizing.
    return str.replace(/([A-Z])/g, function(str, m1) {
      return '-' + m1.toLowerCase();
    }).replace(/^ms-/, '-ms-');
  };


  /**
   * Object Model prefixes.
   * @type {string}
   * @private
   */
  Device.omPrefixes_ = 'Webkit Moz O ms';


  /**
   * CSS Object Model prefixes.
   * @type {Array.<string>}
   * @private
   */
  Device.cssomPrefixes_ = Device.omPrefixes_.split(' ');


  /**
   * Document Object Model prefixes.
   * @type {Array.<string>}
   * @private
   */
  Device.domPrefixes_ = Device.omPrefixes_.toLowerCase().split(' ');


  /**
   * Prefix lookup cache.
   * @type {Object}
   * @private
   */
  Device.prefixCache_ = {};

  /**
   * @param {*} obj Anything.
   * @return {boolean}
   */
  var isString = function(obj) {
    return typeof obj === 'string';
  };


  /**
   * Capitalize a string.
   * @param {string} str String to capitalize.
   * @return {string} Capitalized string.
   */
  var capitalize = function(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  };


  /**
   * Returns the prefixed style property if it exists.
   * Mimics of Modernizr.prefixed. So Device.prefixed('transform') should
   * return 'WebkitTransform' for Chrome and 'transform' for FF and IE10.
   * {@link http://perfectionkills.com/feature-testing-css-properties/}
   *
   * @param  {string} propName The property name.
   * @param  {Element=} opt_element Element to test. Defaults to html element.
   * @return {string|boolean} The style property or false.
   */
  Device.prefixed = function(propName, opt_element) {
    opt_element = opt_element || document.documentElement;
    var style = opt_element.style,
        cache = Device.prefixCache_,
        prefixes = Device.cssomPrefixes_,
        prefixed,
        uPropName;

    // check cache only when no element is given.
    if (arguments.length === 1 && isString(cache[propName])) {
      return cache[propName];
    }

    // test standard property first.
    if (isString(style[propName])) {
      return (cache[propName] = propName);
    }

    // capitalize.
    uPropName = capitalize(propName);

    // test vendor specific properties.
    for (var i = 0, l = prefixes.length; i < l; i++) {
      prefixed = prefixes[i] + uPropName;
      if (isString(style[prefixed])) {
        return (cache[propName] = prefixed);
      }
    }

    return false;
  };


  /**
   * Prefixed style properties.
   * @enum {string|boolean}
   */
  Device.Js = {
    TRANSFORM: Device.prefixed('transform'),
    TRANSITION: Device.prefixed('transition'),
    TRANSITION_PROPERTY: Device.prefixed('transitionProperty'),
    TRANSITION_DURATION: Device.prefixed('transitionDuration'),
    TRANSITION_TIMING_FUNCTION: Device.prefixed('transitionTimingFunction'),
    TRANSITION_DELAY: Device.prefixed('transitionDelay')
  };


  /**
   * Prefixed css properties.
   * @enum {string}
   */
  Device.Css = {
    TRANSFORM: Device.hyphenate(Device.Js.TRANSFORM),
    TRANSITION: Device.hyphenate(Device.Js.TRANSITION),
    TRANSITION_PROPERTY: Device.hyphenate(Device.Js.TRANSITION_PROPERTY),
    TRANSITION_DURATION: Device.hyphenate(Device.Js.TRANSITION_DURATION),
    TRANSITION_TIMING_FUNCTION: Device.hyphenate(
        Device.Js.TRANSITION_TIMING_FUNCTION),
    TRANSITION_DELAY: Device.hyphenate(Device.Js.TRANSITION_DELAY)
  };


  /**
   * Whether the browser has css transitions.
   * @type {boolean}
   */
  Device.HAS_TRANSITIONS = Modernizr.csstransitions;


  /**
   * Whether the browser has css transitions.
   * @type {boolean}
   */
  Device.HAS_TRANSFORMS = Modernizr.csstransforms;


  /**
   * The browser can use css transitions and transforms.
   * @type {boolean}
   */
  Device.CAN_TRANSITION_TRANSFORMS = Device.HAS_TRANSITIONS &&
      Device.HAS_TRANSFORMS;


  /**
   * The browser can use 3d css transforms.
   * https://github.com/Modernizr/Modernizr/blob/master/feature-detects/css/transforms3d.js
   * @type {boolean}
   */
  Device.HAS_3D_TRANSFORMS = Modernizr.csstransforms3d;


  /**
   * Whether the browser supports touch events.
   * @type {boolean}
   */
  Device.HAS_TOUCH_EVENTS = ('ontouchstart' in window) ||
      !!window.DocumentTouch && document instanceof DocumentTouch;


  /**
   * Whether the browser supports pointer events.
   * http://blogs.windows.com/windows_phone/b/wpdev/archive/2012/11/15/adapting-your-webkit-optimized-site-for-internet-explorer-10.aspx
   * @type {boolean}
   */
  Device.HAS_POINTER_EVENTS = !!navigator.pointerEnabled ||
      !!navigator.msPointerEnabled;


  return Device;
});

define('app/helpers',['require','jquery','app/device', 'modernizr', 'app/event-type'],function(require) {
  var $ = require('jquery');
  var Device = require('app/device');
  var Modernizr = require('modernizr');
  var EventType = require('app/event-type');
  var Helpers = {};

  /** @enum {string} */
  Helpers.ClassName = {
    HIDDEN: 'hidden',
    FADE: 'fade',
    IN: 'in',
    INVISIBLE: 'invisible',
    ACTIVE: 'active',
    GRAB: 'grab',
    GRABBING: 'grabbing'
  };

  // Polyfill Object.create.
  if (!Object.create) {
    Object.create = (function() {
      function F(){}
      return function(o) {
        if (arguments.length !== 1) {
          throw new Error(
              'Object.create implementation only accepts one parameter.');
        }
        F.prototype = o;
        return new F();
      };
    })();
  }

  Helpers.inherits = function(child, parent) {
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;

    /**
     * Taken from Closure :)
     *
     * Calls superclass constructor/method.
     *
     * This function is only available if you use goog.inherits to
     * express inheritance relationships between classes.
     *
     * NOTE: This is a replacement for goog.base and for superClass_
     * property defined in child.
     *
     * @param {!Object} me Should always be "this".
     * @param {string} methodName The method name to call. Calling
     *     superclass constructor can be done with the special string
     *     'constructor'.
     * @param {...*} var_args The arguments to pass to superclass
     *     method/constructor.
     * @return {*} The return value of the superclass method/constructor.
     */
    child.base = function(me, methodName, var_args) {
      var args = Array.prototype.slice.call(arguments, 2);
      return parent.prototype[methodName].apply(me, args);
    };
  };


  /**
   * Capitalize a string.
   * @param {string} str String to capitalize.
   * @return {string} Capitalized string.
   */
  Helpers.capitalize = function(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  };



  // Math utilities.


  /**
   * Clone of jQuery.isNumeric {@link https://api.jquery.com/jQuery.isNumeric}
   * Because goog.isNumber only checks if the type is a number, so NaN passes.
   * @param {*} obj Object to test.
   * @return {!boolean} Whether the given object is a number.
   */
  Helpers.isNumeric = function(obj) {
    return !isNaN(parseFloat(obj)) && isFinite(obj);
  };


  /**
   * Takes a number and clamps it to within the provided bounds.
   * @param {number} value The input number.
   * @param {number} min The minimum value to return.
   * @param {number} max The maximum value to return.
   * @return {number} The input number if it is within bounds, or the nearest
   *     number within the bounds.
   */
  Helpers.clamp = function(value, min, max) {
    return Math.min(Math.max(value, min), max);
  };


  /**
   * Class for representing a box. A box is specified as a top, right, bottom,
   * and left. A box is useful for representing margins and padding.
   *
   * @param {number} top Top.
   * @param {number} right Right.
   * @param {number} bottom Bottom.
   * @param {number} left Left.
   * @constructor
   */
  Helpers.Box = function(top, right, bottom, left) {
    /**
     * Top
     * @type {number}
     */
    this.top = top;

    /**
     * Right
     * @type {number}
     */
    this.right = right;

    /**
     * Bottom
     * @type {number}
     */
    this.bottom = bottom;

    /**
     * Left
     * @type {number}
     */
    this.left = left;
  };


  /**
   * Class for representing rectangular regions.
   * @param {number} x Left.
   * @param {number} y Top.
   * @param {number} w Width.
   * @param {number} h Height.
   * @constructor
   */
  Helpers.Rect = function(x, y, w, h) {
    /**
     * Left
     * @type {number}
     */
    this.left = x;

    /**
     * Top
     * @type {number}
     */
    this.top = y;

    /**
     * Width
     * @type {number}
     */
    this.width = w;

    /**
     * Height
     * @type {number}
     */
    this.height = h;
  };



  // Style utilities

  /**
   * Gets the height and with of an element when the display is not none.
   * @param {Element} element Element to get size of.
   * @return {!{width: number, height: number}} Object with width/height.
   */
  Helpers.getSize = function(element) {
    var offsetWidth = element.offsetWidth;
    var offsetHeight = element.offsetHeight;
    if (offsetWidth === undefined && element.getBoundingClientRect) {
      // Fall back to calling getBoundingClientRect when offsetWidth or
      // offsetHeight are not defined.
      var clientRect = element.getBoundingClientRect();
      return {
        width: clientRect.right - clientRect.left,
        height: clientRect.bottom - clientRect.top
      };
    }
    return {
      width: offsetWidth,
      height: offsetHeight
    };
  };


  Helpers._getBox = function(element, property) {
    var props = $(element).css([
      property + 'Top',
      property + 'Right',
      property + 'Left',
      property + 'Bottom'
    ]);
    return new Helpers.Box(
        Helpers.getFloat(props[property + 'Top']),
        Helpers.getFloat(props[property + 'Right']),
        Helpers.getFloat(props[property + 'Left']),
        Helpers.getFloat(props[property + 'Bottom']));
  };

  Helpers.getFloat = function(value) {
    return parseFloat(value) || 0;
  };


  Helpers.getMarginBox = function(element) {
    return Helpers._getBox(element, 'margin');
  };


  /**
   * Returns a string to be used with transforms. Uses 3d translates
   * when available.
   * @param {string=} opt_x The x position value with units. Default is zero.
   * @param {string=} opt_y The y position value with units. Default is zero.
   * @return {string} The css value for transform.
   */
  Helpers.getTranslateString = function(opt_x, opt_y) {
    var x = opt_x !== undefined ? opt_x : 0;
    var y = opt_y !== undefined ? opt_y : 0;
    var prefix = 'translate';
    var suffix = ')';

    if (Device.HAS_3D_TRANSFORMS) {
      prefix += '3d(';
      suffix = ',0' + suffix;

    } else {
      prefix += '(';
    }

    return prefix + x + ',' + y + suffix;
  };


  Helpers.onTransitionEnd = function( elem, fn, context, opt_property ) {
    // transitioned and ignore others.
    if ( elem.jquery ) {
      elem = elem[0];
    }

    var callback = $.proxy(fn, context || window);
    var fakeEvent = {
      target: elem,
      currentTarget: elem,
      fake: true
    };

    /**
     * @param {$.Event|{target: Element, currentTarget: Element}} evt Event object.
     */
    function transitionEnded(evt) {
      var source = evt.currentTarget;
      // Some other element's transition event could have bubbled up to this.
      if (!source || source !== evt.target) {
        return;
      }

      // If the browser has transitions, there will be a listener bound to the
      // `transitionend` event which needs to be removed. `listenOnce` is not used
      // because transition events can bubble up to the parent.
      if (Modernizr.csstransitions) {
        // If the optional property exists and it's not the property which was
        // transitioned, exit out of the function and continue waiting for the
        // right transition property.
        if (opt_property && !evt.fake && evt.originalEvent.propertyName !== opt_property) {
          return;
        }

        $(source).off(EventType.TRANSITIONEND, transitionEnded);
      }

      // Done!
      callback(evt);
    }


    if (Modernizr.csstransitions) {
      $(elem).on(EventType.TRANSITIONEND, transitionEnded);
      // TODO(glen): Get length of transition and set a timeout as a backup.
      // The transition will not happen if the values don't change on the element,
      // the timeout would be a failsafe for that.
    } else {

      // Push to the end of the queue with a fake event which will pass the checks
      // inside the callback function.
      setTimeout($.proxy(transitionEnded, window, fakeEvent), 0);
    }
  };


  return Helpers;
});
/**
 * @fileoverview The base abstract component providing easy-to-use things for
 * working with dom and other services.
 */

define('app/base-component',['require','jquery','modernizr','app/helpers'],function(require) {


  var $ = require('jquery');
  var Modernizr = require('modernizr');
  var Helpers = require('app/helpers');



  /**
   * The base class for modules.
   * @param {Element} element Main element of the module.
   * @param {boolean} addListener Whether to listen for the 767|768 breakpoint.
   * @constructor
   */
  function BaseComponent( element, addListener ) {
    if ( element ) {
      this.$el = $( element );
      this.element = element;
    }


    if ( addListener ) {
      /**
       * Whether the screen is smaller than 768px or not.
       * @type {boolean}
       */
      this.isSmallScreen = false;

      if ( Modernizr.mediaqueries ) {
        this._mql = window.matchMedia('(max-width: 47.9375em)');
        this._mqlListener = this.handleMediaQueryChange.bind( this );
        this.isSmallScreen = this._mql.matches;
      }
    }
  }


  BaseComponent.prototype.decorateInternal = function() {};
  BaseComponent.prototype.enterDocument = function() {};


  BaseComponent.prototype.getElement = function() {
    return this.element;
  };


  /**
   * Listen for events.
   */
  BaseComponent.prototype.listen = function() {
    // Listen for changes across 767|768.
    if ( this._mql ) {
      this._mql.addListener( this._mqlListener );
    }
  };


  /**
   * Finds an element within this class' main element.
   * @param {string} selector Selector.
   * @param {jQuery|Element} [context] Optionally provide the context (scope)
   *     for the query. Default is the main element of the class.
   * @return {jQuery} A jQuery object which may or may not contain the element
   *     which was searched for.
   */
  BaseComponent.prototype.findBySelector = function( selector, context ) {
    return $( selector, context || this.$el );
  };


  /**
   * Finds an element within this class' main element.
   * @param {string} className Class name to search for.
   * @param {jQuery|Element} [context] Optionally provide the context (scope)
   *     for the query. Default is the main element of the class.
   * @return {jQuery} A jQuery object which may or may not contain the element
   *     which was searched for.
   */
  BaseComponent.prototype.findByClass = function( className, context ) {
    return this.findBySelector( '.' + className, context );
  };


  BaseComponent.prototype.getElementByClass = function(className, context) {
    return this.findByClass(className, context).get(0) || null;
  };


  BaseComponent.prototype.getElementsByClass = function(className, context) {
    return this.findByClass(className, context).get();
  };


  /**
   * Retieves elements from the children of a parent which match the given
   * class. This function is useful when an element has the same class name
   * nested deeper within the element that is needed.
   * @param {string} className The classname to match against.
   * @param {Element} parent The element whose children will be filtered.
   * @return {!Array.<Element>} Direct descendants of the parent by class.
   */
  BaseComponent.prototype.getDirectDescendantsByClass = function(
      className, parent) {
    return $(parent).children('.' + className).get();
  };


  BaseComponent.prototype.getParentByClass = function(className, child,
      opt_context) {
    var context = opt_context || this.element;
    return $(child).closest('.' + className, context).get(0) || null;
  };


  /**
   * Cleanup DOM references and event listeners.
   */
  BaseComponent.prototype.dispose = function() {
    if ( this._mql ) {
      this._mql.removeListener( this._mqlListener );
      this._mql = null;
    }

    if ( this.$el ) {
      this.$el = null;
      this.element = null;
    }
  };


  /**
   * Triggers an event on the class instance.
   * @param {string|jQuery.Event} eventName Name of the event to trigger or
   *     an event instance.
   * @param {Array.<*>} [args] Optional arguments to send with the event.
   * @return {?boolean} If the event name is an event instance, this function
   *     returns whether or not the event was prevented using preventDefault().
   */
  BaseComponent.prototype.dispatchEvent = function(eventName, args) {
    if ($.type(eventName) === 'string') {
      $(this).trigger( eventName, args && args.length ? args : [ this ] );
      return null;
    } else {
      $(this).trigger(eventName); // undefined not a function :(
      return eventName.isDefaultPrevented();
    }
  };


  /**
   * Return the media query list.
   * @return {MediaQueryList}
   * @protected
   */
  BaseComponent.prototype.getMediaQueryList = function() {
    return this._mql;
  };


  /**
   * Media query changed. Ideally this should be on some kind of base component which
   * all modules inherit from.
   * @param {MediaQueryList} mediaQueryList The media query list object.
   * @protected
   */
  BaseComponent.prototype.handleMediaQueryChange = function( mediaQueryList ) {
    this.isSmallScreen = mediaQueryList.matches;
  };


  return BaseComponent;
});


/**
 * @fileoverview A utility class for representing two-dimensional positions.
 */

define('app/coordinate',[],function() {
  /**
   * Class for representing coordinates and positions.
   * @param {number=} opt_x Left, defaults to 0.
   * @param {number=} opt_y Top, defaults to 0.
   * @constructor
   */
  var Coordinate = function(opt_x, opt_y) {
    /**
     * X-value
     * @type {number}
     */
    this.x = opt_x !== undefined ? opt_x : 0;

    /**
     * Y-value
     * @type {number}
     */
    this.y = opt_y !== undefined ? opt_y : 0;
  };


  /**
   * Returns the distance between two coordinates.
   * @param {!Coordinate} a A Coordinate.
   * @param {!Coordinate} b A Coordinate.
   * @return {number} The distance between {@code a} and {@code b}.
   */
  Coordinate.distance = function(a, b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return Math.sqrt(dx * dx + dy * dy);
  };

  return Coordinate;
});
define('app/event-type',['app/device'], function(Device) {

  // https://github.com/Modernizr/Modernizr/blob/master/src/prefixed.js
  var transEndEventNames = {
    'WebkitTransition': 'webkitTransitionEnd', // Saf 6, Android Browser
    'MozTransition': 'transitionend', // only for FF < 15
    'transition': 'transitionend' // IE10, Opera, Chrome, FF 15+, Saf 7+
  };

  var hasUnprefixedPointerEvents = !!navigator.pointerEnabled;

  function getPointerEvent(event) {
    if (Device.HAS_POINTER_EVENTS) {
      if (hasUnprefixedPointerEvents) {
        return event.toLowerCase();
      } else {
        return 'MS' + event;
      }
    } else {
      return null;
    }
  }


  return {
    // Mouse events
    CLICK: 'click',
    MOUSEDOWN: 'mousedown',
    MOUSEUP: 'mouseup',
    MOUSEOVER: 'mouseover',
    MOUSEOUT: 'mouseout',
    MOUSEMOVE: 'mousemove',

    // WebKit touch events.
    TOUCHSTART: 'touchstart',
    TOUCHMOVE: 'touchmove',
    TOUCHEND: 'touchend',
    TOUCHCANCEL: 'touchcancel',

    POINTERCANCEL: getPointerEvent('PointerCancel'),
    POINTERDOWN: getPointerEvent('PointerDown'),
    POINTERMOVE: getPointerEvent('PointerMove'),
    POINTEROVER: getPointerEvent('PointerOver'),
    POINTEROUT: getPointerEvent('PointerOut'),
    POINTERUP: getPointerEvent('PointerUp'),

    TRANSITIONEND: transEndEventNames[Device.Js.TRANSITION]
  };
});
/**
 * @fileoverview A draggable class which uses translate when
 * available and can use a containing element instead of specific boundaries.
 * It also emits events with more useful information for swipes, like velocity,
 * distance, and direction.
 */

define('app/draggable',['require','jquery','app/helpers','app/base-component','app/coordinate','app/device','app/event-type'],function(require) {

  var $ = require('jquery');
  var Helpers = require('app/helpers');
  var BaseComponent = require('app/base-component');
  var Coordinate = require('app/coordinate');
  var Device = require('app/device');
  var EventType = require('app/event-type');

  /**
   * A class that allows mouse or touch-based dragging (moving) of an element.
   *
   * @param {Element} dragElement The element that will be dragged.
   * @param {Element} containment The element which will contain the draggable
   *     element.
   * @param {string=} opt_axis The axis to drag on. Defaults to both. Should be
   *     "x" or "y" to constrain to an axis.
   * @extends {BaseComponent}
   * @constructor
   */
  var Draggable = function(dragElement, containment, opt_axis, opt_limits) {
    Draggable.base(this, 'constructor', dragElement);

    /**
     * The draggable element.
     * @type {Element}
     * @private
     */
    this.el_ = dragElement;

    /**
     * The element which contains the target.
     * @type {Element}
     * @private
     */
    this.containerEl_ = containment;

    /**
     * Object to keep track of the current position of the handle/target.
     * @type {Coordinate}
     * @private
     */
    this.currentPosition_ = new Coordinate();

    /**
     * Object to keep track of the where the starting location for dragging was.
     * Relative to the draggable element.
     * @type {Coordinate}
     * @private
     */
    this.startPosition_ = new Coordinate();

    /**
     * Current x position of mouse or touch relative to the document.
     * @type {number}
     */
    this.pageX = 0;


    /**
     * Current y position of mouse or touch relative to the document.
     * @type {number}
     */
    this.pageY = 0;


    /**
     * The x position where the first mousedown or touchstart occurred.
     * @type {number}
     */
    this.startX = 0;


    /**
     * The y position where the first mousedown or touchstart occurred.
     * @type {number}
     */
    this.startY = 0;


    /**
     * Current x position of drag relative to target's parent.
     * @type {number}
     */
    this.deltaX = 0;


    /**
     * Current y position of drag relative to target's parent.
     * @type {number}
     */
    this.deltaY = 0;

    /**
     * Limits of how far the draggable element can be dragged.
     * @type {Helpers.Rect}
     */
    this.limits = opt_limits || new Helpers.Rect(NaN, NaN, NaN, NaN);

    /**
     * Friction to apply to dragging. A value of zero would result in no dragging,
     * 0.5 would result in the draggable element moving half as far as the user
     * dragged, and 1 is a 1:1 ratio with user movement.
     * @type {number}
     */
    this.friction_ = 1;

    /**
     * Draggable axis.
     * @type {string}
     * @private
     */
    this.axis_ = opt_axis || Draggable.Axis.BOTH;

    /**
     * Flag indicating dragging has happened. It is set on dragmove and reset
     * after the draggableend event has been dispatched.
     * @type {boolean}
     */
    this.hasDragged = false;

    /**
     * Whether the user is locked in place within the draggable element. This
     * is set to true when `preventDefault` is called on the move event.
     * @type {boolean}
     * @private
     */
    this.isLocked_ = false;

    /**
     * Whether dragging is enabled internally. If the user attempts to scroll
     * in the opposite direction of the draggable element, this is set to true
     * and no more drag move events are counted until the user releases and
     * starts dragging again.
     * @type {boolean}
     * @private
     */
    this.isDeactivated_ = false;

    /**
     * Whether dragging is currently enabled.
     * @type {boolean}
     * @private
     */
    this.enabled_ = true;

    var $el = $(this.el_);
    // Namespace
    var ns = '.draggable';

    if (Device.HAS_POINTER_EVENTS) {
      $el.on(EventType.POINTERDOWN + ns, this.handleDragStart_.bind(this));

    } else {
      $el.on(EventType.MOUSEDOWN + ns, this.handleDragStart_.bind(this));

      if (Device.HAS_TOUCH_EVENTS) {
        $el.on(EventType.TOUCHSTART + ns, this.handleDragStart_.bind(this));
      }
    }
  };

  Helpers.inherits(Draggable, BaseComponent);


  /** @enum {string} */
  Draggable.EventType = {
    START: 'draggablestart',
    MOVE: 'draggablemove',
    END: 'draggableend'
  };


  /** @enum {string} */
  Draggable.Direction = {
    RIGHT: 'right',
    LEFT: 'left',
    UP: 'up',
    DOWN: 'down',
    NONE: 'no_movement'
  };


  /** @enum {string} */
  Draggable.Axis = {
    X: 'x',
    Y: 'y',
    BOTH: 'xy'
  };


  /**
   * The scroll/drag amount (pixels) required on the draggable axis before
   * stopping further page scrolling/movement.
   * @const {number}
   */
  Draggable.LOCK_THRESHOLD = 6;


  /**
   * The scroll/drag amount (pixels) required on the opposite draggable axis
   * before dragging is deactivated for the rest of the interaction.
   * @const {number}
   */
  Draggable.DRAG_THRESHOLD = 5;


  /**
   * Calculate the velocity between two points.
   *
   * @param {number} deltaTime Change in time.
   * @param {number} deltaX Change in x.
   * @param {number} deltaY Change in y.
   * @return {Object} Velocity of the drag.
   */
  Draggable.getVelocity = function(deltaTime, deltaX, deltaY) {
    var velocityX = Math.abs(deltaX / deltaTime);
    var velocityY = Math.abs(deltaY / deltaTime);
    return {
      x: isFinite(velocityX) ? velocityX : 0,
      y: isFinite(velocityY) ? velocityY : 0
    };
  };


  /**
   * angle to direction define.
   * @param {Coordinate} coord1 The starting coordinate.
   * @param {Coordinate} coord2 The ending coordinate.
   * @return {string} Direction constant.
   */
  Draggable.getDirection = function(coord1, coord2) {
    var x = Math.abs(coord1.x - coord2.x);
    var y = Math.abs(coord1.y - coord2.y);

    if (x >= y) {
      return coord1.x - coord2.x > 0 ?
          Draggable.Direction.LEFT :
          coord1.x - coord2.x < 0 ?
            Draggable.Direction.RIGHT :
            Draggable.Direction.NONE;
    } else {
      return coord1.y - coord2.y > 0 ?
          Draggable.Direction.UP :
          coord1.y - coord2.y < 0 ?
            Draggable.Direction.DOWN :
            Draggable.Direction.NONE;
    }
  };


  /**
   * Saves the containment element's width and height and scrubber position.
   * @private
   */
  Draggable.prototype.setDimensions_ = function() {
    var containmentRect = this.containerEl_.getBoundingClientRect();
    var elRect = this.el_.getBoundingClientRect();

    var relativeElement = Device.CAN_TRANSITION_TRANSFORMS ?
        this.el_ :
        this.containerEl_;

    // getBoundingClientRect does not include margins. They must be accounted for.
    var margins = Helpers.getMarginBox(this.el_);
    var offsetCorrectionX = margins.left;
    var offsetCorrectionY = margins.top;
    offsetCorrectionX += containmentRect.left;
    offsetCorrectionY += containmentRect.top;

    this.containmentWidth_ = relativeElement.offsetWidth;
    this.containmentHeight_ = relativeElement.offsetHeight;

    if (this.containmentWidth_ === 0) {
      throw new Error('containing element\'s width is zero');
    } else if (this.containmentHeight_ === 0) {
      throw new Error('containing element\'s height is zero');
    }

    this.startPosition_.x = elRect.left - offsetCorrectionX;
    this.startPosition_.y = elRect.top - offsetCorrectionY;
  };


  /**
   * Get whether dragger is enabled
   * @return {boolean} Whether dragger is enabled.
   */
  Draggable.prototype.getEnabled = function() {
    return this.enabled_;
  };


  /**
   * Set whether dragger is enabled
   * @param {boolean} enabled Whether dragger is enabled.
   */
  Draggable.prototype.setEnabled = function(enabled) {
    this.enabled_ = enabled;
  };


  /**
   * Sets (or reset) the Drag limits after a Dragger is created.
   * @param {Helpers.Rect} limits Object containing left, top, width,
   *     height for new Dragger limits.
   */
  Draggable.prototype.setLimits = function(limits) {
    this.limits = limits || new Helpers.Rect(NaN, NaN, NaN, NaN);
  };


  /**
   * Returns the 'real' x after limits are applied (allows for some
   * limits to be undefined).
   * @param {number} x X-coordinate to limit.
   * @return {number} The 'real' X-coordinate after limits are applied.
   */
  Draggable.prototype.limitX = function(x) {
    var rect = this.limits;
    var left = isNaN(rect.left) ? null : rect.left;
    var width = isNaN(rect.width) ? 0 : rect.width;
    var maxX = left !== null ? left + width : Infinity;
    var minX = left !== null ? left : -Infinity;
    return Helpers.clamp(x, minX, maxX);
  };


  /**
   * Returns the 'real' y after limits are applied (allows for some
   * limits to be undefined).
   * @param {number} y Y-coordinate to limit.
   * @return {number} The 'real' Y-coordinate after limits are applied.
   */
  Draggable.prototype.limitY = function(y) {
    var rect = this.limits;
    var top = isNaN(rect.top) ? null : rect.top;
    var height = isNaN(rect.height) ? 0 : rect.height;
    var maxY = top !== null ? top + height : Infinity;
    var minY = top !== null ? top : -Infinity;
    return Helpers.clamp(y, minY, maxY);
  };


  /**
   * Returns the x and y positions the draggable element should be set to.
   * @param {Coordinate=} opt_position Position to set the draggable
   *     element. This will optionally override calculating the position
   *     from a drag.
   * @return {!Coordinate} The x and y coordinates.
   * @private
   */
  Draggable.prototype.getElementPosition_ = function(opt_position) {
    var outputX = 0;
    var outputY = 0;

    if (opt_position) {
      var scrubberX = (opt_position.x / 100) * this.containerEl_.offsetWidth;
      var scrubberY = (opt_position.y / 100) * this.containerEl_.offsetHeight;
      this.currentPosition_.x = this.limitX(scrubberX);
      this.currentPosition_.y = this.limitY(scrubberY);
    }

    var newX = (this.currentPosition_.x / this.containmentWidth_) * 100;
    var newY = (this.currentPosition_.y / this.containmentHeight_) * 100;

    // Drag horizontal only.
    if (this.axis_ === Draggable.Axis.X) {
      outputX = newX;

    // Drag vertical only.
    } else if (this.axis_ === Draggable.Axis.Y) {
      outputY = newY;

    // Drag both directions.
    } else {
      outputX = newX;
      outputY = newY;
    }

    return new Coordinate(outputX, outputY);
  };


  /**
   * Apply a friction value to an pixel position, reducing its value.
   * @param {number} position X or Y position.
   * @return {number} Position multiplied by friction.
   * @private
   */
  Draggable.prototype.applyFriction_ = function(position) {
    return position * this.friction_;
  };


  /**
   * Drag start handler.
   * @param  {jQuery.Event} evt The drag event object.
   * @private
   */
  Draggable.prototype.handleDragStart_ = function(evt) {
    var browserEvent = evt.originalEvent;
    var isTouchEvent = !!browserEvent.changedTouches;
    var isPointerEvent = !!browserEvent.pointerId;

    // Must be left click to drag.
    if (!this.enabled_ || !isTouchEvent && evt.which !== 1) {
      return;
    }

    // Use the first touch for the pageX and pageY.
    if (isTouchEvent) {
      this.startX = browserEvent.changedTouches[0].pageX;
      this.startY = browserEvent.changedTouches[0].pageY;

    // Pointer events have trusted pageX and pageY values, but jQuery doesn't
    // normalize it for us because it doesn't know what pointer events are yet.
    } else if (isPointerEvent) {
      this.startX = browserEvent.pageX;
      this.startY = browserEvent.pageY;
    } else {
      this.startX = evt.pageX; // Normalized by jQuery.
      this.startY = evt.pageY; // Normalized by jQuery.
    }

    this.pageX = this.startX;
    this.pageY = this.startY;
    this.deltaX = 0;
    this.deltaY = 0;

    this.timestamp = $.now();
    this.deltaTime = 0;
    this.setDimensions_();

    this.currentPosition_ = new Coordinate(
        this.startPosition_.x,
        this.startPosition_.y);

    // Give a hook to others
    var isPrevented = this.dispatchEvent(new DraggableEvent(
        Draggable.EventType.START, this, this.startPosition_,
        this.startPosition_));

    if (!isPrevented) {
      this.setupDragHandlers();
    }
  };


  /**
   * Drag move, after applyDraggableElementPosition has happened
   * @param {jQuery.Event} evt The dragger event.
   * @private
   */
  Draggable.prototype.handleDragMove_ = function(evt) {
    if (!this.enabled_ || this.isDeactivated_) {
      return;
    }

    var browserEvent = evt.originalEvent;
    var isTouchEvent = !!browserEvent.changedTouches;
    var isPointerEvent = !!browserEvent.pointerId;


    if (isTouchEvent) {
      this.pageX = browserEvent.changedTouches[0].pageX;
      this.pageY = browserEvent.changedTouches[0].pageY;
    } else if (isPointerEvent) {
      this.pageX = browserEvent.pageX;
      this.pageY = browserEvent.pageY;
    } else {
      this.pageX = evt.pageX; // Normalized by jQuery.
      this.pageY = evt.pageY; // Normalized by jQuery.
    }

    this.deltaX = this.pageX - this.startX;
    this.deltaY = this.pageY - this.startY;


    var newX = this.startPosition_.x + this.applyFriction_(this.deltaX);
    var newY = this.startPosition_.y + this.applyFriction_(this.deltaY);

    this.currentPosition_.x = this.limitX(newX);
    this.currentPosition_.y = this.limitY(newY);
    this.deltaTime = $.now() - this.timestamp;


    // Emit an event.
    var isPrevented = this.dispatchEvent(new DraggableEvent(
        Draggable.EventType.MOVE,
        this, this.startPosition_, this.currentPosition_));

    // Abort if the developer prevented default on the custom event.
    if (isPrevented) {
      return;
    }

    this.hasDragged = true;

    var absX = Math.abs(this.deltaX);
    var absY = Math.abs(this.deltaY);

    // Prevent scrolling if the user has moved past the locking threshold.
    if ((this.axis_ === Draggable.Axis.X && absX > Draggable.LOCK_THRESHOLD) ||
        (this.axis_ === Draggable.Axis.Y && absY > Draggable.LOCK_THRESHOLD)) {
      this.isLocked_ = true;
      evt.preventDefault();
    }

    // Disable dragging if the user is attempting to go the opposite direction
    // of the draggable element.
    if (!this.isLocked_ && (
        (this.axis_ === Draggable.Axis.X && absY > Draggable.DRAG_THRESHOLD) ||
        (this.axis_ === Draggable.Axis.Y && absX > Draggable.DRAG_THRESHOLD))) {
      this.isDeactivated_ = true;
    }

    if (!this.isDeactivated_) {
      this.applyDraggableElementPosition();
    }
  };


  /**
   * Dragging ended.
   * @private
   */
  Draggable.prototype.handleDragEnd_ = function() {
    this.cleanupDragHandlers();

    var start = this.startPosition_;
    var end = this.currentPosition_;

    this.deltaTime = $.now() - this.timestamp;

    // Give a hook to others
    this.dispatchEvent(new DraggableEvent(
        Draggable.EventType.END, this, start, end));

    this.hasDragged = false;
    this.isDeactivated_ = false;
    this.isLocked_ = false;
  };


  /**
   * Sets the position of thd draggable element.
   * @param {Coordinate=} opt_position Position to set the draggable
   *     element. This will optionally override calculating the position
   *     from a drag.
   * @return {Coordinate} The position the draggable element was set to.
   */
  Draggable.prototype.applyDraggableElementPosition = function(opt_position) {
    var pos = this.getElementPosition_(opt_position);

    // Add percentage unit
    var outputX = pos.x + '%';
    var outputY = pos.y + '%';

    if (Device.CAN_TRANSITION_TRANSFORMS) {
      this.el_.style[Device.Js.TRANSFORM] =
          Helpers.getTranslateString(outputX, outputY);
    } else {
      this.el_.style.left = outputX;
      this.el_.style.top = outputY;
    }

    return this.currentPosition_;
  };


  /**
   * Binds events to the document for move, end, and cancel (if cancel events
   * exist for the device).
   */
  Draggable.prototype.setupDragHandlers = function() {
    var $doc = $(document);
    var ns = '.draggable';

    if (Device.HAS_POINTER_EVENTS) {
      $doc.on(EventType.POINTERMOVE + ns, this.handleDragMove_.bind(this));
      $doc.on(EventType.POINTERUP + ns, this.handleDragEnd_.bind(this));

      // Touch and pointers have cancel events for when the user goes into
      // something like the browser chrome.
      $doc.on(EventType.POINTERCANCEL + ns, this.handleDragEnd_.bind(this));

    } else {
      $doc.on(EventType.MOUSEMOVE + ns, this.handleDragMove_.bind(this));
      $doc.on(EventType.MOUSEUP + ns, this.handleDragEnd_.bind(this));

      if (Device.HAS_TOUCH_EVENTS) {
        $doc.on(EventType.TOUCHMOVE + ns, this.handleDragMove_.bind(this));
        $doc.on(EventType.TOUCHEND + ns, this.handleDragEnd_.bind(this));

        // Touch and pointers have cancel events for when the user goes into
        // something like the browser chrome.
        $doc.on(EventType.TOUCHCANCEL + ns, this.handleDragEnd_.bind(this));
      }
    }
  };


  /**
   * Removes the events bound during drag start. The draggable namespace can be
   * used to remove all of them because the drag start event is still bound
   * to the actual element.
   */
  Draggable.prototype.cleanupDragHandlers = function() {
    var $doc = $(document);
    $doc.off('.draggable');
  };


  /**
   * Returns the current position of the draggable element.
   * @param {boolean} opt_asPercent Optionally retrieve percentage values instead
   *     of pixel values.
   * @return {Coordinate} X and Y coordinates of the draggable element.
   */
  Draggable.prototype.getPosition = function(opt_asPercent) {
    if (opt_asPercent) {
      return new Coordinate(
          (this.currentPosition_.x / this.containmentWidth_) * 100,
          (this.currentPosition_.y / this.containmentHeight_) * 100);
    } else {
      return this.currentPosition_;
    }
  };


  /**
   * Set the position of the draggable element.
   * @param {number} x X position as a percentage. Eg. 50 for "50%".
   * @param {number} y Y position as a percentage. Eg. 50 for "50%".
   * @return {Coordinate} The position the draggable element was set to.
   */
  Draggable.prototype.setPosition = function(x, y) {
    return this.applyDraggableElementPosition(new Coordinate(x, y));
  };


  /**
   * Set the friction value.
   * @param {number} friction A number between [1, 0].
   */
  Draggable.prototype.setFriction = function(friction) {
    if (friction !== this.friction_) {
      this.friction_ = friction;
    }
  };


  /**
   * Easy way to trigger setting dimensions. Useful for doing things after this
   * class has been initialized, but no dragging has occurred yet.
   * @return {Draggable} The instance.
   */
  Draggable.prototype.update = function() {
    this.setDimensions_();
    return this;
  };


  /** @override */
  Draggable.prototype.dispose = function() {
    Draggable.base(this, 'dispose');

    this.containerEl_ = null;
    this.el_ = null;

    // Remove pointer/mouse/touch events by namespace.
    $(this.el_).off('.draggable');
  };



  /**
   * Object representing a drag event.
   * @param {string} type Event type.
   * @param {Draggable} draggable The draggable instance.
   * @param {Coordinate} start The starting coordinate.
   * @param {Coordinate} end The ending coordinate.
   * @constructor
   * @extends {jQuery.Event}
   */
  var DraggableEvent = function(type, draggable, start, end) {
    $.Event.call(this, type);

    /**
     * @type {Element}
     */
    this.target = draggable.el_;

    /**
     * The change in x from drag start to end.
     * @type {number}
     */
    this.deltaX = end.x - start.x;

    /**
     * The change in y from drag start to end.
     * @type {number}
     */
    this.deltaY = end.y - start.y;

    /**
     * Time elapsed from mouse/touch down to mouse/touch up.
     * @type {number}
     */
    this.deltaTime = draggable.deltaTime;

    /**
     * Reference to the drag object for this event.
     * @type {Draggable}
     */
    this.draggable = draggable;

    /**
     * Velocity in drag.
     * @type {number}
     */
    this.velocity = Draggable.getVelocity(this.deltaTime,
        this.deltaX, this.deltaY);

    /**
     * Distance dragged.
     * @type {number}
     */
    this.distance = Coordinate.distance(start, end);

    /**
     * Direction of drag.
     * @type {string}
     */
    this.direction = Draggable.getDirection(start, end);

    var onAxis = false;

    // Is X and direction is right or left.
    if (draggable.axis_ === Draggable.Axis.X &&
      (this.direction === Draggable.Direction.LEFT ||
      this.direction === Draggable.Direction.RIGHT)) {
      onAxis = true;

    // Is Y and direction is down or up.
    } else if (draggable.axis_ === Draggable.Axis.Y &&
      (this.direction === Draggable.Direction.UP ||
      this.direction === Draggable.Direction.DOWN)) {
      onAxis = true;

    // Is both and direction is not none.
    } else if (draggable.axis_ === Draggable.Axis.BOTH &&
        this.direction !== Draggable.Direction.NONE) {
      onAxis = true;
    }

    /**
     * Whether the drag direction is on the axis of the draggable element.
     * @type {boolean}
     */
    this.isDirectionOnAxis = onAxis;

    /** @type {Coordinate} */
    this.position = draggable.currentPosition_;
  };

  Helpers.inherits(DraggableEvent, $.Event);


  return Draggable;
});


define('card-shuffle', ['require', 'jquery', 'app/device', 'app/helpers', 'app/base-component', 'app/draggable'], function(require) {
  var $ = require('jquery');
  var Device = require('app/device');
  var Helpers = require('app/helpers');
  var BaseComponent = require('app/base-component');
  var Draggable = require('app/draggable');

  var CardShuffle = function(element, isReverseStacked, isDraggable) {
    CardShuffle.base(this, 'constructor', element, true);
    this.currentIndex = 0;
    this.$cards = this.findByClass(CardShuffle.ClassName.CARD);
    this.totalCards = this.$cards.length;
    this.positions = $.map(this.$cards, function(card) {
      return parseFloat($(card).attr('data-position'));
    });
    this.isTransitioning = false;
    this.isReverseStacked = isReverseStacked;
    this.isDraggable = isDraggable;
    this.draggables = null;
    this.timerId = null;
    this.init();
  };

  Helpers.inherits(CardShuffle, BaseComponent);


  CardShuffle.ClassName = {
    CARD: 'card-shuffle__card',
    NO_TRANSITION: 'card-shuffle--no-transition',
    TO_BOTTOM: 'card-shuffle__card--to-bottom',
    FRONT: 'card-shuffle__card--front',
    START: 'card-shuffle__card--off-deck-start',
    END: 'card-shuffle__card--off-deck-end',
    INNER: 'card-shuffle__card-inner'
  };


  /**
   * When the card pops off the top of the deck, it can go in different
   * directions.
   * @type {Object}
   */
  CardShuffle.Direction = {
    START: CardShuffle.ClassName.START,
    END: CardShuffle.ClassName.END
  };


  CardShuffle.prototype.init = function() {

    var frontIndex = this.isReverseStacked ? 0 : this.totalCards - 1;
    this.$cards.filter('[data-position=' + frontIndex + ']')
      .addClass(CardShuffle.ClassName.FRONT);

    if (this.isDraggable) {
      this.draggables = new Array(this.totalCards);
      var self = this;
      this.$cards.each(function(i, card) {
        self.draggables[i] = new Draggable(card, card.parentNode, Draggable.Axis.X);
        if (i > 0) {
          self.draggables[i].setEnabled(false);
        }
      });
    }

    this.listen();
  };

  CardShuffle.prototype.listen = function() {
    CardShuffle.base(this, 'listen');

    if (this.isDraggable) {
      this.draggables.forEach(function(draggable) {
        $(draggable).on(Draggable.EventType.START, this._handleDraggableStart.bind(this));
        $(draggable).on(Draggable.EventType.END, this._handleDraggableEnd.bind(this));
      }, this);

    }
  };


  CardShuffle.prototype.dispose = function() {
    CardShuffle.base(this, 'dispose');
    if (this.draggables) {
      this.draggables.forEach(function(draggable) {
        $(draggable).off(Draggable.EventType.END);
      }, this);
    }
    this.$cards = null;
  };


  /**
   * Returns the logical index of a neighbor from a given relative number.
   * Non looped carousels at 0 and length - 1 don't have previous and next
   * neighbors, respectively, so 0 or length - 1 will be return, respectively.
   *
   * @param {number} logicalIndex The logical index which the second parameter
   *     is relative to.
   * @param {number} relativePos The relative position to the first parameter.
   * For example, -2 or 2.
   * @return {number} The logical index of the neighbor.
   * @private
   */
  CardShuffle.prototype._getRelativeIndex = function(logicalIndex, relativePos) {
    var index = logicalIndex + relativePos;

    if (index < 0) {
      return this.totalCards + index;
    } else if (index > this.totalCards - 1) {
      return index - this.totalCards;
    } else {
      return index;
    }
  };


  CardShuffle.prototype.advance = function(opt_direction) {
    var previousIndex = this.currentIndex;
    var selectedIndex = previousIndex + 1;
    var direction = opt_direction ? opt_direction : CardShuffle.Direction.END;

    if (selectedIndex >= this.totalCards) {
      selectedIndex = 0;
    }

    this.currentIndex = selectedIndex;

    this._updateCardPositions();
    this._popOffStack(previousIndex, direction);
  };


  CardShuffle.prototype._slideBack = function(draggable) {
    this.isTransitioning = true;

    this.$cards.eq(this.currentIndex).removeClass(CardShuffle.ClassName.NO_TRANSITION);

    var done = function() {
      clearTimeout(this.timerId);
      this.isTransitioning = false;
    }.bind(this);

    Helpers.onTransitionEnd(draggable.getElement(), done);
    this.timerId = setTimeout(done, 500);

    draggable.setPosition(0, 0);
  };


  CardShuffle.prototype._popOffStack = function(cardIndex, direction) {
    var $previousFront = this.$cards.eq(cardIndex);

    this.isTransitioning = true;

    // Animate card out of container
    $previousFront
      .removeClass()
      .addClass([
        CardShuffle.ClassName.CARD,
        CardShuffle.ClassName.TO_BOTTOM,
        direction
      ].join(' '));

    // When done going to the bottom, put it behind the other cards and
    // go back to the top of the container.
    Helpers.onTransitionEnd($previousFront, function($card) {
      $card.removeClass([
        CardShuffle.ClassName.TO_BOTTOM,
        CardShuffle.ClassName.START,
        CardShuffle.ClassName.END,
      ].join(' '));

      Helpers.onTransitionEnd($card, function() {
        this.isTransitioning = false;

        // Toggle dragging.
        this.draggables[cardIndex].setEnabled(false);
        this.draggables[this.currentIndex].setEnabled(true);
      }, this, Device.Css.TRANSFORM);
    }.bind(this, $previousFront));
  };


  CardShuffle.prototype._updateCardPositions = function() {
    this.$cards.each(function(i, el) {
      var currentPosition = this.positions[i];
      var nextPosition = this._getRelativeIndex(currentPosition, this.isReverseStacked ? -1 : 1);
      var $card = $(el);
      $card.attr('data-position', nextPosition);
      this.positions[i] = nextPosition;

      var isFront = false;
      if (this.isReverseStacked) {
        if (nextPosition === 0) {
          isFront = true;
        }
      } else {
        if (nextPosition === this.totalCards - 1) {
          isFront = true;
        }
      }

      $card.toggleClass(CardShuffle.ClassName.FRONT, isFront);
    }.bind(this));
  };


  CardShuffle.prototype._handleTap = function(evt) {
    var target = evt.target;
    var hasCardParent = $(target).closest('.' + CardShuffle.ClassName.INNER).length > 0;

    if (hasCardParent && !this.isTransitioning) {
      this.advance();
    }
  };


  CardShuffle.prototype._handleDraggableStart = function(evt) {
    if (this.isTransitioning) {
      evt.preventDefault();
      return;
    }

    var draggable = evt.draggable;
    var dragger = draggable.getElement();
    $(dragger).addClass([
      CardShuffle.ClassName.NO_TRANSITION,
      Helpers.ClassName.GRABBING
    ].join(' '));
  };


  CardShuffle.prototype._handleDraggableEnd = function(evt) {
    var velocityThreshold = 0.7;
    var velocity = evt.velocity;
    var direction = evt.direction;
    var draggable = evt.draggable;
    var position = draggable.getPosition(true);
    var dragger = evt.target;

    if (
        // Velocity throw
        (velocity.x > velocityThreshold && direction === Draggable.Direction.LEFT) ||
        // Dragged a quarter of the way
        (direction === Draggable.Direction.LEFT && position.x <= -25)) {
      dragger.style[Device.Js.TRANSFORM] = '';
      this.advance(CardShuffle.Direction.START);
    } else if (
        // Velocity throw
        (velocity.x > velocityThreshold && direction === Draggable.Direction.RIGHT) ||
        // Dragged a quarter of the way
        (direction === Draggable.Direction.RIGHT && position.x >= 25)) {
      dragger.style[Device.Js.TRANSFORM] = '';
      this.advance(CardShuffle.Direction.END);

    // As long as it has moved more than 1 pixel, it can be transitioned back to
    // the resting state. If it's zero, the transition end event will never
    // be triggered and the draggable will continue to be disabled.
    } else if (evt.isDirectionOnAxis) {
      this._slideBack(draggable);
    }

    $(draggable.getElement()).removeClass(Helpers.ClassName.GRABBING);
  };

  return CardShuffle;
});


requirejs.config({
  baseUrl: 'js',
  paths: {
    jquery: '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery'
  }
});


define('modernizr', [], window.Modernizr);


require(['card-shuffle'], function(CardShuffle) {
  window.cards = new CardShuffle($('.card-shuffle--stacked')[0], true, true);
});

define("app/main", function(){});


              
            
!
999px

Console