text: (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)
  
              var indent = Math.max(0, $this.parents('li').length - 1)
  
              var href = $this.attr('href')
  
              var 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)
  
          var $body = $('body')
  
          var $window = $(window)
  
          var id = $this.attr('id')
  
          var 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" if it's not a jQuery object already.
          if (typeof config.target !== 'jQuery') 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)
  
                  var href = $a.attr('href')
  
                  var 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
  
              var diffY = $this.touchPosY - event.originalEvent.touches[0].pageY
  
              var th = $this.outerHeight()
  
              var ts = $this.get(0).scrollHeight - $this.scrollTop()
  
              // Hide on swipe?
              if (config.hideOnSwipe) {
                  var result = false
  
                  var boundary = 20
  
                  var 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)
              var 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)
  
                      var 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 if it's not already a jQuery object.
          if (typeof $elements !== 'jQuery') $elements = $($elements)
  
          // Step through elements.
          $elements.each(function() {
              var $e = $(this)
  
              var $p
  
              var $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)
              }
  
              // Moved already?
              else {
                  // 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)