text: /* global jQuery*/
  (function($) {
      /**
       * Generate an indented list of links from a nav. Meant for use with panel().
       * @return {jQuery} jQuery object.
       */
      $.fn.navList = function() {
          var $this = $(this),
              $a = $this.find('a'),
              b = [];
  
          $a.each(function() {
              var $this = $(this),
                  indent = Math.max(0, $this.parents('li').length - 1),
                  href = $this.attr('href'),
                  target = $this.attr('target');
  
              b.push(
                  '<a ' +
                      'class="link depth-' +
                      indent +
                      '"' +
                      (typeof target !== 'undefined' && target != '' ?
                          ' target="' + target + '"' :
                          '') +
                      (typeof href !== 'undefined' && href != '' ?
                          ' href="' + href + '"' :
                          '') +
                      '>' +
                      '<span class="indent-' +
                      indent +
                      '"></span>' +
                      $this.text() +
                      '</a>'
              );
          });
  
          return b.join('');
      };
  
      /**
       * Panel-ify an element.
       * @param {object} userConfig User config.
       * @return {jQuery} jQuery object.
       */
      $.fn.panel = function(userConfig) {
          // No elements?
          if (this.length == 0) {
              return $this;
          }
  
          // Multiple elements?
          if (this.length > 1) {
              for (var i = 0; i < this.length; i++) {
                  $(this[i]).panel(userConfig);
              }
  
              return $this;
          }
  
          // Vars.
          var $this = $(this),
              $body = $('body'),
              $window = $(window),
              id = $this.attr('id'),
              config;
  
          // Config.
          config = $.extend(
              {
                  // Delay.
                  delay: 0,
  
                  // Hide panel on link click.
                  hideOnClick: false,
  
                  // Hide panel on escape keypress.
                  hideOnEscape: false,
  
                  // Hide panel on swipe.
                  hideOnSwipe: false,
  
                  // Reset scroll position on hide.
                  resetScroll: false,
  
                  // Reset forms on hide.
                  resetForms: false,
  
                  // Side of viewport the panel will appear.
                  side: null,
  
                  // Target element for "class".
                  target: $this,
  
                  // Class to toggle.
                  visibleClass: 'visible',
              },
              userConfig
          );
  
          // Expand "target".
          config.target = $(config.target);
  
          // Panel.
  
          // Methods.
          $this._hide = function(event) {
              // Already hidden? Bail.
              if (!config.target.hasClass(config.visibleClass)) {
                  return;
              }
  
              // If an event was provided, cancel it.
              if (event) {
                  event.preventDefault();
                  event.stopPropagation();
              }
  
              // Hide.
              config.target.removeClass(config.visibleClass);
  
              // Post-hide stuff.
              window.setTimeout(function() {
                  // Reset scroll position.
                  if (config.resetScroll) {
                      $this.scrollTop(0);
                  }
  
                  // Reset forms.
                  if (config.resetForms) {
                      $this.find('form').each(function() {
                          this.reset();
                      });
                  }
              }, config.delay);
          };
  
          // Vendor fixes.
          $this
              .css('-ms-overflow-style', '-ms-autohiding-scrollbar')
              .css('-webkit-overflow-scrolling', 'touch');
  
          // Hide on click.
          if (config.hideOnClick) {
              $this.find('a').css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
  
              $this.on('click', 'a', function(event) {
                  var $a = $(this),
                      href = $a.attr('href'),
                      target = $a.attr('target');
  
                  if (!href || href == '#' || href == '' || href == '#' + id) {
                      return;
                  }
  
                  // Cancel original event.
                  event.preventDefault();
                  event.stopPropagation();
  
                  // Hide panel.
                  $this._hide();
  
                  // Redirect to href.
                  window.setTimeout(function() {
                      if (target == '_blank') {
                          window.open(href);
                      } else {
                          window.location.href = href;
                      }
                  }, config.delay + 10);
              });
          }
  
          // Event: Touch stuff.
          $this.on('touchstart', function(event) {
              $this.touchPosX = event.originalEvent.touches[0].pageX;
              $this.touchPosY = event.originalEvent.touches[0].pageY;
          });
  
          $this.on('touchmove', function(event) {
              if ($this.touchPosX === null || $this.touchPosY === null) {
                  return;
              }
  
              var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
                  diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
                  th = $this.outerHeight(),
                  ts = $this.get(0).scrollHeight - $this.scrollTop();
  
              // Hide on swipe?
              if (config.hideOnSwipe) {
                  var result = false,
                      boundary = 20,
                      delta = 50;
  
                  switch (config.side) {
                  case 'left':
                      result =
                              diffY < boundary &&
                              diffY > -1 * boundary &&
                              diffX > delta;
                      break;
  
                  case 'right':
                      result =
                              diffY < boundary &&
                              diffY > -1 * boundary &&
                              diffX < -1 * delta;
                      break;
  
                  case 'top':
                      result =
                              diffX < boundary &&
                              diffX > -1 * boundary &&
                              diffY > delta;
                      break;
  
                  case 'bottom':
                      result =
                              diffX < boundary &&
                              diffX > -1 * boundary &&
                              diffY < -1 * delta;
                      break;
  
                  default:
                      break;
                  }
  
                  if (result) {
                      $this.touchPosX = null;
                      $this.touchPosY = null;
                      $this._hide();
  
                      return false;
                  }
              }
  
              // Prevent vertical scrolling past the top or bottom.
              if (
                  ($this.scrollTop() < 0 && diffY < 0) ||
                  (ts > th - 2 && ts < th + 2 && diffY > 0)
              ) {
                  event.preventDefault();
                  event.stopPropagation();
              }
          });
  
          // Event: Prevent certain events inside the panel from bubbling.
          $this.on('click touchend touchstart touchmove', function(event) {
              event.stopPropagation();
          });
  
          // Event: Hide panel if a child anchor tag pointing to its ID is clicked.
          $this.on('click', 'a[href="#' + id + '"]', function(event) {
              event.preventDefault();
              event.stopPropagation();
  
              config.target.removeClass(config.visibleClass);
          });
  
          // Body.
  
          // Event: Hide panel on body click/tap.
          $body.on('click touchend', function(event) {
              $this._hide(event);
          });
  
          // Event: Toggle.
          $body.on('click', 'a[href="#' + id + '"]', function(event) {
              event.preventDefault();
              event.stopPropagation();
  
              config.target.toggleClass(config.visibleClass);
          });
  
          // Window.
  
          // Event: Hide on ESC.
          if (config.hideOnEscape) {
              $window.on('keydown', function(event) {
                  if (event.keyCode == 27) {
                      $this._hide(event);
                  }
              });
          }
  
          return $this;
      };
  
      /**
       * Apply "placeholder" attribute polyfill to one or more forms.
       * @return {jQuery} jQuery object.
       */
      $.fn.placeholder = function() {
          // Browser natively supports placeholders? Bail.
          if (typeof document.createElement('input').placeholder != 'undefined') {
              return $(this);
          }
  
          // No elements?
          if (this.length == 0) {
              return $this;
          }
  
          // Multiple elements?
          if (this.length > 1) {
              for (var i = 0; i < this.length; i++) {
                  $(this[i]).placeholder();
              }
  
              return $this;
          }
  
          // Vars.
          var $this = $(this);
  
          // Text, TextArea.
          $this
              .find('input[type=text],textarea')
              .each(function() {
                  var i = $(this);
  
                  if (i.val() == '' || i.val() == i.attr('placeholder')) {
                      i.addClass('polyfill-placeholder').val(
                          i.attr('placeholder')
                      );
                  }
              })
              .on('blur', function() {
                  var i = $(this);
  
                  if (i.attr('name').match(/-polyfill-field$/)) {
                      return;
                  }
  
                  if (i.val() == '') {
                      i.addClass('polyfill-placeholder').val(
                          i.attr('placeholder')
                      );
                  }
              })
              .on('focus', function() {
                  var i = $(this);
  
                  if (i.attr('name').match(/-polyfill-field$/)) {
                      return;
                  }
  
                  if (i.val() == i.attr('placeholder')) {
                      i.removeClass('polyfill-placeholder').val('');
                  }
              });
  
          // Password.
          $this.find('input[type=password]').each(function() {
              var i = $(this),
                  x = $(
                      $('<div>')
                          .append(i.clone())
                          .remove()
                          .html()
                          .replace(/type="password"/i, 'type="text"')
                          .replace(/type=password/i, 'type=text')
                  );
  
              if (i.attr('id') != '') {
                  x.attr('id', i.attr('id') + '-polyfill-field');
              }
  
              if (i.attr('name') != '') {
                  x.attr('name', i.attr('name') + '-polyfill-field');
              }
  
              x.addClass('polyfill-placeholder')
                  .val(x.attr('placeholder'))
                  .insertAfter(i);
  
              if (i.val() == '') {
                  i.hide();
              } else {
                  x.hide();
              }
  
              i.on('blur', function(event) {
                  event.preventDefault();
  
                  var x = i
                      .parent()
                      .find('input[name=' + i.attr('name') + '-polyfill-field]');
  
                  if (i.val() == '') {
                      i.hide();
                      x.show();
                  }
              });
  
              x.on('focus', function(event) {
                  event.preventDefault();
  
                  var i = x
                      .parent()
                      .find(
                          'input[name=' +
                              x.attr('name').replace('-polyfill-field', '') +
                              ']'
                      );
  
                  x.hide();
  
                  i.show().focus();
              }).on('keypress', function(event) {
                  event.preventDefault();
                  x.val('');
              });
          });
  
          // Events.
          $this
              .on('submit', function() {
                  $this
                      .find('input[type=text],input[type=password],textarea')
                      .each(function(event) {
                          var i = $(this);
  
                          if (i.attr('name').match(/-polyfill-field$/)) {
                              i.attr('name', '');
                          }
  
                          if (i.val() == i.attr('placeholder')) {
                              i.removeClass('polyfill-placeholder');
                              i.val('');
                          }
                      });
              })
              .on('reset', function(event) {
                  event.preventDefault();
  
                  $this.find('select').val($('option:first').val());
  
                  $this.find('input,textarea').each(function() {
                      var i = $(this),
                          x;
  
                      i.removeClass('polyfill-placeholder');
  
                      switch (this.type) {
                      case 'submit':
                      case 'reset':
                          break;
  
                      case 'password':
                          i.val(i.attr('defaultValue'));
  
                          x = i
                              .parent()
                              .find(
                                  'input[name=' +
                                          i.attr('name') +
                                          '-polyfill-field]'
                              );
  
                          if (i.val() == '') {
                              i.hide();
                              x.show();
                          } else {
                              i.show();
                              x.hide();
                          }
  
                          break;
  
                      case 'checkbox':
                      case 'radio':
                          i.attr('checked', i.attr('defaultValue'));
                          break;
  
                      case 'text':
                      case 'textarea':
                          i.val(i.attr('defaultValue'));
  
                          if (i.val() == '') {
                              i.addClass('polyfill-placeholder');
                              i.val(i.attr('placeholder'));
                          }
  
                          break;
  
                      default:
                          i.val(i.attr('defaultValue'));
                          break;
                      }
                  });
              });
  
          return $this;
      };
  
      /**
       * Moves elements to/from the first positions of their respective parents.
       * @param {jQuery} $elements Elements (or selector) to move.
       * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
       */
      $.prioritize = function($elements, condition) {
          var key = '__prioritize';
  
          // Expand $elements.
          $elements = $($elements);
  
          // Step through elements.
          $elements.each(function() {
              var $e = $(this),
                  $p,
                  $parent = $e.parent();
  
              // No parent? Bail.
              if ($parent.length == 0) {
                  return;
              }
  
              // Not moved? Move it.
              if (!$e.data(key)) {
                  // Condition is false? Bail.
                  if (!condition) {
                      return;
                  }
  
                  // Get placeholder (which will serve as our point of reference for when this element needs to move back).
                  $p = $e.prev();
  
                  // Couldn't find anything? Means this element's already at the top, so bail.
                  if ($p.length == 0) {
                      return;
                  }
  
                  // Move element to top of parent.
                  $e.prependTo($parent);
  
                  // Mark element as moved.
                  $e.data(key, $p);
              } else {
                  // Moved already?
                  // Condition is true? Bail.
                  if (condition) {
                      return;
                  }
  
                  $p = $e.data(key);
  
                  // Move element back to its original location (using our placeholder).
                  $e.insertAfter($p);
  
                  // Unmark element as moved.
                  $e.removeData(key);
              }
          });
      };
  })(jQuery);
  