/* Minification failed. Returning unminified contents.
(27,20-21): run-time error JS1195: Expected expression: )
(27,23-24): run-time error JS1195: Expected expression: >
(28,27-28): run-time error JS1195: Expected expression: >
(34,30-31): run-time error JS1195: Expected expression: )
(34,33-34): run-time error JS1195: Expected expression: >
(39,1-2): run-time error JS1002: Syntax error: }
(40,23-24): run-time error JS1195: Expected expression: >
(42,1-2): run-time error JS1002: Syntax error: }
(43,56-57): run-time error JS1195: Expected expression: >
(48,27-37): run-time error JS1004: Expected ';': readAsText
(54,65-66): run-time error JS1003: Expected ':': }
(54,69-70): run-time error JS1195: Expected expression: >
(55,53-54): run-time error JS1195: Expected expression: >
(55,76-77): run-time error JS1004: Expected ';': )
(58,1-2): run-time error JS1002: Syntax error: }
(86,11-12): run-time error JS1195: Expected expression: )
(86,13-14): run-time error JS1004: Expected ';': {
(1810,2-3): run-time error JS1195: Expected expression: )
(1825,22-23): run-time error JS1004: Expected ';': {
(5078,3-4): run-time error JS1004: Expected ';': )
(5080,2-8): run-time error JS1197: Too many errors. The file might not be a JavaScript file: jQuery
(57,5-77): run-time error JS1018: 'return' statement outside of function: return disallowedSvgElements.length === 0 && !hasScriptAttr ? svg : null
(47,9-19): run-time error JS1018: 'return' statement outside of function: return svg
(41,5-34): run-time error JS1018: 'return' statement outside of function: return obj.size !== undefined
 */
const svgDisallowed = [
    'a',
    'animate',
    'color-profile',
    'cursor',
    'discard',
    'fedropshadow',
    'font-face',
    'font-face-format',
    'font-face-name',
    'font-face-src',
    'font-face-uri',
    'foreignobject',
    'hatch',
    'hatchpath',
    'mesh',
    'meshgradient',
    'meshpatch',
    'meshrow',
    'missing-glyph',
    'script',
    'set',
    'solidcolor',
    'unknown',
    'use'
];
const getWindow = () => (typeof window === 'undefined' ? null : window);
const readAsText = (svg) => new Promise((resolve) => {
    if (!isFile(svg)) {
        resolve(svg.toString('utf-8'));
    }
    else {
        const fileReader = new FileReader();
        fileReader.onload = () => {
            resolve(fileReader.result);
        };
        fileReader.readAsText(svg);
    }
});
const isFile = (obj) => {
    return obj.size !== undefined;
};
const sanitizeSVG = async (svg, window = getWindow()) => {
    if (!window)
        throw new Error('DOM window required');
    if (isFile(svg) && svg.type !== 'image/svg+xml')
        return svg;
    const svgText = await readAsText(svg);
    if (!svgText)
        throw new Error('Image corrupt');
    const playground = window.document.createElement('template');
    playground.innerHTML = svgText;
    const svgEl = playground.content.firstElementChild;
    const attributes = Array.from(svgEl.attributes).map(({ name }) => name);
    const hasScriptAttr = !!attributes.find((attr) => attr.startsWith('on'));
    const disallowedSvgElements = svgEl.querySelectorAll(svgDisallowed.join(','));
    return disallowedSvgElements.length === 0 && !hasScriptAttr ? svg : null;
};;

/*
 *
 * More info at [www.dropzonejs.com](http://www.dropzonejs.com)
 *
 * Copyright (c) 2012, Matias Meno
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

(function() {
  var Dropzone, Emitter, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without,
    __slice = [].slice,
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

  noop = function() {};

  Emitter = (function() {
    function Emitter() {}

    Emitter.prototype.addEventListener = Emitter.prototype.on;

    Emitter.prototype.on = function(event, fn) {
      this._callbacks = this._callbacks || {};
      if (!this._callbacks[event]) {
        this._callbacks[event] = [];
      }
      this._callbacks[event].push(fn);
      return this;
    };

    Emitter.prototype.emit = function() {
      var args, callback, callbacks, event, _i, _len;
      event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      this._callbacks = this._callbacks || {};
      callbacks = this._callbacks[event];
      if (callbacks) {
        for (_i = 0, _len = callbacks.length; _i < _len; _i++) {
          callback = callbacks[_i];
          callback.apply(this, args);
        }
      }
      return this;
    };

    Emitter.prototype.removeListener = Emitter.prototype.off;

    Emitter.prototype.removeAllListeners = Emitter.prototype.off;

    Emitter.prototype.removeEventListener = Emitter.prototype.off;

    Emitter.prototype.off = function(event, fn) {
      var callback, callbacks, i, _i, _len;
      if (!this._callbacks || arguments.length === 0) {
        this._callbacks = {};
        return this;
      }
      callbacks = this._callbacks[event];
      if (!callbacks) {
        return this;
      }
      if (arguments.length === 1) {
        delete this._callbacks[event];
        return this;
      }
      for (i = _i = 0, _len = callbacks.length; _i < _len; i = ++_i) {
        callback = callbacks[i];
        if (callback === fn) {
          callbacks.splice(i, 1);
          break;
        }
      }
      return this;
    };

    return Emitter;

  })();

  Dropzone = (function(_super) {
    var extend, resolveOption;

    __extends(Dropzone, _super);

    Dropzone.prototype.Emitter = Emitter;


    /*
    This is a list of all available events you can register on a dropzone object.
    
    You can register an event handler like this:
    
        dropzone.on("dragEnter", function() { });
     */

    Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"];

    Dropzone.prototype.defaultOptions = {
      url: null,
      method: "post",
      withCredentials: false,
      parallelUploads: 2,
      uploadMultiple: false,
      maxFilesize: 256,
      paramName: "file",
      createImageThumbnails: true,
      maxThumbnailFilesize: 10,
      thumbnailWidth: 120,
      thumbnailHeight: 120,
      filesizeBase: 1000,
      maxFiles: null,
      params: {},
      clickable: true,
      ignoreHiddenFiles: true,
      acceptedFiles: null,
      acceptedMimeTypes: null,
      autoProcessQueue: true,
      autoQueue: true,
      addRemoveLinks: false,
      previewsContainer: null,
      hiddenInputContainer: "body",
      capture: null,
      dictDefaultMessage: "Drop files here to upload",
      dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.",
      dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.",
      dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",
      dictInvalidFileType: "You can't upload files of this type.",
      dictResponseError: "Server responded with {{statusCode}} code.",
      dictCancelUpload: "Cancel upload",
      dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
      dictRemoveFile: "Remove file",
      dictRemoveFileConfirmation: null,
      dictMaxFilesExceeded: "You can not upload any more files.",
      accept: function(file, done) {
        return done();
      },
      init: function() {
        return noop;
      },
      forceFallback: false,
      fallback: function() {
        var child, messageElement, span, _i, _len, _ref;
        this.element.className = "" + this.element.className + " dz-browser-not-supported";
        _ref = this.element.getElementsByTagName("div");
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          child = _ref[_i];
          if (/(^| )dz-message($| )/.test(child.className)) {
            messageElement = child;
            child.className = "dz-message";
            continue;
          }
        }
        if (!messageElement) {
          messageElement = Dropzone.createElement("<div class=\"dz-message\"><span></span></div>");
          this.element.appendChild(messageElement);
        }
        span = messageElement.getElementsByTagName("span")[0];
        if (span) {
          if (span.textContent != null) {
            span.textContent = this.options.dictFallbackMessage;
          } else if (span.innerText != null) {
            span.innerText = this.options.dictFallbackMessage;
          }
        }
        return this.element.appendChild(this.getFallbackForm());
      },
      resize: function(file) {
        var info, srcRatio, trgRatio;
        info = {
          srcX: 0,
          srcY: 0,
          srcWidth: file.width,
          srcHeight: file.height
        };
        srcRatio = file.width / file.height;
        info.optWidth = this.options.thumbnailWidth;
        info.optHeight = this.options.thumbnailHeight;
        if ((info.optWidth == null) && (info.optHeight == null)) {
          info.optWidth = info.srcWidth;
          info.optHeight = info.srcHeight;
        } else if (info.optWidth == null) {
          info.optWidth = srcRatio * info.optHeight;
        } else if (info.optHeight == null) {
          info.optHeight = (1 / srcRatio) * info.optWidth;
        }
        trgRatio = info.optWidth / info.optHeight;
        if (file.height < info.optHeight || file.width < info.optWidth) {
          info.trgHeight = info.srcHeight;
          info.trgWidth = info.srcWidth;
        } else {
          if (srcRatio > trgRatio) {
            info.srcHeight = file.height;
            info.srcWidth = info.srcHeight * trgRatio;
          } else {
            info.srcWidth = file.width;
            info.srcHeight = info.srcWidth / trgRatio;
          }
        }
        info.srcX = (file.width - info.srcWidth) / 2;
        info.srcY = (file.height - info.srcHeight) / 2;
        return info;
      },

      /*
      Those functions register themselves to the events on init and handle all
      the user interface specific stuff. Overwriting them won't break the upload
      but can break the way it's displayed.
      You can overwrite them if you don't like the default behavior. If you just
      want to add an additional event handler, register it on the dropzone object
      and don't overwrite those options.
       */
      drop: function(e) {
        return this.element.classList.remove("dz-drag-hover");
      },
      dragstart: noop,
      dragend: function(e) {
        return this.element.classList.remove("dz-drag-hover");
      },
      dragenter: function(e) {
        return this.element.classList.add("dz-drag-hover");
      },
      dragover: function(e) {
        return this.element.classList.add("dz-drag-hover");
      },
      dragleave: function(e) {
        return this.element.classList.remove("dz-drag-hover");
      },
      paste: noop,
      reset: function() {
        return this.element.classList.remove("dz-started");
      },
      addedfile: function(file) {
        var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
        if (this.element === this.previewsContainer) {
          this.element.classList.add("dz-started");
        }
        if (this.previewsContainer) {
          file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
          file.previewTemplate = file.previewElement;
          this.previewsContainer.appendChild(file.previewElement);
          _ref = file.previewElement.querySelectorAll("[data-dz-name]");
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            node = _ref[_i];
            node.textContent = file.name;
          }
          _ref1 = file.previewElement.querySelectorAll("[data-dz-size]");
          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
            node = _ref1[_j];
            node.innerHTML = this.filesize(file.size);
          }
          if (this.options.addRemoveLinks) {
            file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>" + this.options.dictRemoveFile + "</a>");
            file.previewElement.appendChild(file._removeLink);
          }
          removeFileEvent = (function(_this) {
            return function(e) {
              e.preventDefault();
              e.stopPropagation();
              if (file.status === Dropzone.UPLOADING) {
                return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() {
                  return _this.removeFile(file);
                });
              } else {
                if (_this.options.dictRemoveFileConfirmation) {
                  return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() {
                    return _this.removeFile(file);
                  });
                } else {
                  return _this.removeFile(file);
                }
              }
            };
          })(this);
          _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]");
          _results = [];
          for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
            removeLink = _ref2[_k];
            _results.push(removeLink.addEventListener("click", removeFileEvent));
          }
          return _results;
        }
      },
      removedfile: function(file) {
        var _ref;
        if (file.previewElement) {
          if ((_ref = file.previewElement) != null) {
            _ref.parentNode.removeChild(file.previewElement);
          }
        }
        return this._updateMaxFilesReachedClass();
      },
      thumbnail: function(file, dataUrl) {
        var thumbnailElement, _i, _len, _ref;
        if (file.previewElement) {
          file.previewElement.classList.remove("dz-file-preview");
          _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]");
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            thumbnailElement = _ref[_i];
            thumbnailElement.alt = file.name;
            thumbnailElement.src = dataUrl;
          }
          return setTimeout(((function(_this) {
            return function() {
              return file.previewElement.classList.add("dz-image-preview");
            };
          })(this)), 1);
        }
      },
      error: function(file, message) {
        var node, _i, _len, _ref, _results;
        if (file.previewElement) {
          file.previewElement.classList.add("dz-error");
          if (typeof message !== "String" && message.error) {
            message = message.error;
          }
          _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]");
          _results = [];
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            node = _ref[_i];
            _results.push(node.textContent = message);
          }
          return _results;
        }
      },
      errormultiple: noop,
      processing: function(file) {
        if (file.previewElement) {
          file.previewElement.classList.add("dz-processing");
          if (file._removeLink) {
            return file._removeLink.textContent = this.options.dictCancelUpload;
          }
        }
      },
      processingmultiple: noop,
      uploadprogress: function(file, progress, bytesSent) {
        var node, _i, _len, _ref, _results;
        if (file.previewElement) {
          _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]");
          _results = [];
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            node = _ref[_i];
            if (node.nodeName === 'PROGRESS') {
              _results.push(node.value = progress);
            } else {
              _results.push(node.style.width = "" + progress + "%");
            }
          }
          return _results;
        }
      },
      totaluploadprogress: noop,
      sending: noop,
      sendingmultiple: noop,
      success: function(file) {
        if (file.previewElement) {
          return file.previewElement.classList.add("dz-success");
        }
      },
      successmultiple: noop,
      canceled: function(file) {
        return this.emit("error", file, "Upload canceled.");
      },
      canceledmultiple: noop,
      complete: function(file) {
        if (file._removeLink) {
          file._removeLink.textContent = this.options.dictRemoveFile;
        }
        if (file.previewElement) {
          return file.previewElement.classList.add("dz-complete");
        }
      },
      completemultiple: noop,
      maxfilesexceeded: noop,
      maxfilesreached: noop,
      queuecomplete: noop,
      addedfiles: noop,
      previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n  <div class=\"dz-image\"><img data-dz-thumbnail /></div>\n  <div class=\"dz-details\">\n    <div class=\"dz-size\"><span data-dz-size></span></div>\n    <div class=\"dz-filename\"><span data-dz-name></span></div>\n  </div>\n  <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n  <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n  <div class=\"dz-success-mark\">\n    <svg width=\"54px\" height=\"54px\" viewBox=\"0 0 54 54\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n      <title>Check</title>\n      <defs></defs>\n      <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n        <path d=\"M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z\" id=\"Oval-2\" stroke-opacity=\"0.198794158\" stroke=\"#747474\" fill-opacity=\"0.816519475\" fill=\"#FFFFFF\" sketch:type=\"MSShapeGroup\"></path>\n      </g>\n    </svg>\n  </div>\n  <div class=\"dz-error-mark\">\n    <svg width=\"54px\" height=\"54px\" viewBox=\"0 0 54 54\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n      <title>Error</title>\n      <defs></defs>\n      <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n        <g id=\"Check-+-Oval-2\" sketch:type=\"MSLayerGroup\" stroke=\"#747474\" stroke-opacity=\"0.198794158\" fill=\"#FFFFFF\" fill-opacity=\"0.816519475\">\n          <path d=\"M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z\" id=\"Oval-2\" sketch:type=\"MSShapeGroup\"></path>\n        </g>\n      </g>\n    </svg>\n  </div>\n</div>"
    };

    extend = function() {
      var key, object, objects, target, val, _i, _len;
      target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      for (_i = 0, _len = objects.length; _i < _len; _i++) {
        object = objects[_i];
        for (key in object) {
          val = object[key];
          target[key] = val;
        }
      }
      return target;
    };

    function Dropzone(element, options) {
      var elementOptions, fallback, _ref;
      this.element = element;
      this.version = Dropzone.version;
      this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, "");
      this.clickableElements = [];
      this.listeners = [];
      this.files = [];
      if (typeof this.element === "string") {
        this.element = document.querySelector(this.element);
      }
      if (!(this.element && (this.element.nodeType != null))) {
        throw new Error("Invalid dropzone element.");
      }
      if (this.element.dropzone) {
        throw new Error("Dropzone already attached.");
      }
      Dropzone.instances.push(this);
      this.element.dropzone = this;
      elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {};
      this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {});
      if (this.options.forceFallback || !Dropzone.isBrowserSupported()) {
        return this.options.fallback.call(this);
      }
      if (this.options.url == null) {
        this.options.url = this.element.getAttribute("action");
      }
      if (!this.options.url) {
        throw new Error("No URL provided.");
      }
      if (this.options.acceptedFiles && this.options.acceptedMimeTypes) {
        throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");
      }
      if (this.options.acceptedMimeTypes) {
        this.options.acceptedFiles = this.options.acceptedMimeTypes;
        delete this.options.acceptedMimeTypes;
      }
      this.options.method = this.options.method.toUpperCase();
      if ((fallback = this.getExistingFallback()) && fallback.parentNode) {
        fallback.parentNode.removeChild(fallback);
      }
      if (this.options.previewsContainer !== false) {
        if (this.options.previewsContainer) {
          this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer");
        } else {
          this.previewsContainer = this.element;
        }
      }
      if (this.options.clickable) {
        if (this.options.clickable === true) {
          this.clickableElements = [this.element];
        } else {
          this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable");
        }
      }
      this.init();
    }

    Dropzone.prototype.getAcceptedFiles = function() {
      var file, _i, _len, _ref, _results;
      _ref = this.files;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        file = _ref[_i];
        if (file.accepted) {
          _results.push(file);
        }
      }
      return _results;
    };

    Dropzone.prototype.getRejectedFiles = function() {
      var file, _i, _len, _ref, _results;
      _ref = this.files;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        file = _ref[_i];
        if (!file.accepted) {
          _results.push(file);
        }
      }
      return _results;
    };

    Dropzone.prototype.getFilesWithStatus = function(status) {
      var file, _i, _len, _ref, _results;
      _ref = this.files;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        file = _ref[_i];
        if (file.status === status) {
          _results.push(file);
        }
      }
      return _results;
    };

    Dropzone.prototype.getQueuedFiles = function() {
      return this.getFilesWithStatus(Dropzone.QUEUED);
    };

    Dropzone.prototype.getUploadingFiles = function() {
      return this.getFilesWithStatus(Dropzone.UPLOADING);
    };

    Dropzone.prototype.getAddedFiles = function() {
      return this.getFilesWithStatus(Dropzone.ADDED);
    };

    Dropzone.prototype.getActiveFiles = function() {
      var file, _i, _len, _ref, _results;
      _ref = this.files;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        file = _ref[_i];
        if (file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED) {
          _results.push(file);
        }
      }
      return _results;
    };

    Dropzone.prototype.init = function() {
      var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1;
      if (this.element.tagName === "form") {
        this.element.setAttribute("enctype", "multipart/form-data");
      }
      if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) {
        this.element.appendChild(Dropzone.createElement("<div class=\"dz-default dz-message\"><span>" + this.options.dictDefaultMessage + "</span></div>"));
      }
      if (this.clickableElements.length) {
        setupHiddenFileInput = (function(_this) {
          return function() {
            if (_this.hiddenFileInput) {
              _this.hiddenFileInput.parentNode.removeChild(_this.hiddenFileInput);
            }
            _this.hiddenFileInput = document.createElement("input");
            _this.hiddenFileInput.setAttribute("type", "file");
            if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) {
              _this.hiddenFileInput.setAttribute("multiple", "multiple");
            }
            _this.hiddenFileInput.className = "dz-hidden-input";
            if (_this.options.acceptedFiles != null) {
              _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles);
            }
            if (_this.options.capture != null) {
              _this.hiddenFileInput.setAttribute("capture", _this.options.capture);
            }
            _this.hiddenFileInput.style.visibility = "hidden";
            _this.hiddenFileInput.style.position = "absolute";
            _this.hiddenFileInput.style.top = "0";
            _this.hiddenFileInput.style.left = "0";
            _this.hiddenFileInput.style.height = "0";
            _this.hiddenFileInput.style.width = "0";
            document.querySelector(_this.options.hiddenInputContainer).appendChild(_this.hiddenFileInput);
            return _this.hiddenFileInput.addEventListener("change", function() {
              var file, files, _i, _len;
              files = _this.hiddenFileInput.files;
              if (files.length) {
                for (_i = 0, _len = files.length; _i < _len; _i++) {
                  file = files[_i];
                  _this.addFile(file);
                }
              }
              _this.emit("addedfiles", files);
              return setupHiddenFileInput();
            });
          };
        })(this);
        setupHiddenFileInput();
      }
      this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL;
      _ref1 = this.events;
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        eventName = _ref1[_i];
        this.on(eventName, this.options[eventName]);
      }
      this.on("uploadprogress", (function(_this) {
        return function() {
          return _this.updateTotalUploadProgress();
        };
      })(this));
      this.on("removedfile", (function(_this) {
        return function() {
          return _this.updateTotalUploadProgress();
        };
      })(this));
      this.on("canceled", (function(_this) {
        return function(file) {
          return _this.emit("complete", file);
        };
      })(this));
      this.on("complete", (function(_this) {
        return function(file) {
          if (_this.getAddedFiles().length === 0 && _this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) {
            return setTimeout((function() {
              return _this.emit("queuecomplete");
            }), 0);
          }
        };
      })(this));
      noPropagation = function(e) {
        e.stopPropagation();
        if (e.preventDefault) {
          return e.preventDefault();
        } else {
          return e.returnValue = false;
        }
      };
      this.listeners = [
        {
          element: this.element,
          events: {
            "dragstart": (function(_this) {
              return function(e) {
                return _this.emit("dragstart", e);
              };
            })(this),
            "dragenter": (function(_this) {
              return function(e) {
                noPropagation(e);
                return _this.emit("dragenter", e);
              };
            })(this),
            "dragover": (function(_this) {
              return function(e) {
                var efct;
                try {
                  efct = e.dataTransfer.effectAllowed;
                } catch (_error) {}
                e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy';
                noPropagation(e);
                return _this.emit("dragover", e);
              };
            })(this),
            "dragleave": (function(_this) {
              return function(e) {
                return _this.emit("dragleave", e);
              };
            })(this),
            "drop": (function(_this) {
              return function(e) {
                noPropagation(e);
                return _this.drop(e);
              };
            })(this),
            "dragend": (function(_this) {
              return function(e) {
                return _this.emit("dragend", e);
              };
            })(this)
          }
        }
      ];
      this.clickableElements.forEach((function(_this) {
        return function(clickableElement) {
          return _this.listeners.push({
            element: clickableElement,
            events: {
              "click": function(evt) {
                if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) {
                  _this.hiddenFileInput.click();
                }
                return true;
              }
            }
          });
        };
      })(this));
      this.enable();
      return this.options.init.call(this);
    };

    Dropzone.prototype.destroy = function() {
      var _ref;
      this.disable();
      this.removeAllFiles(true);
      if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) {
        this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput);
        this.hiddenFileInput = null;
      }
      delete this.element.dropzone;
      return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1);
    };

    Dropzone.prototype.updateTotalUploadProgress = function() {
      var activeFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref;
      totalBytesSent = 0;
      totalBytes = 0;
      activeFiles = this.getActiveFiles();
      if (activeFiles.length) {
        _ref = this.getActiveFiles();
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          file = _ref[_i];
          totalBytesSent += file.upload.bytesSent;
          totalBytes += file.upload.total;
        }
        totalUploadProgress = 100 * totalBytesSent / totalBytes;
      } else {
        totalUploadProgress = 100;
      }
      return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent);
    };

    Dropzone.prototype._getParamName = function(n) {
      if (typeof this.options.paramName === "function") {
        return this.options.paramName(n);
      } else {
        return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : "");
      }
    };

    Dropzone.prototype.getFallbackForm = function() {
      var existingFallback, fields, fieldsString, form;
      if (existingFallback = this.getExistingFallback()) {
        return existingFallback;
      }
      fieldsString = "<div class=\"dz-fallback\">";
      if (this.options.dictFallbackText) {
        fieldsString += "<p>" + this.options.dictFallbackText + "</p>";
      }
      fieldsString += "<input type=\"file\" name=\"" + (this._getParamName(0)) + "\" " + (this.options.uploadMultiple ? 'multiple="multiple"' : void 0) + " /><input type=\"submit\" value=\"Upload!\"></div>";
      fields = Dropzone.createElement(fieldsString);
      if (this.element.tagName !== "FORM") {
        form = Dropzone.createElement("<form action=\"" + this.options.url + "\" enctype=\"multipart/form-data\" method=\"" + this.options.method + "\"></form>");
        form.appendChild(fields);
      } else {
        this.element.setAttribute("enctype", "multipart/form-data");
        this.element.setAttribute("method", this.options.method);
      }
      return form != null ? form : fields;
    };

    Dropzone.prototype.getExistingFallback = function() {
      var fallback, getFallback, tagName, _i, _len, _ref;
      getFallback = function(elements) {
        var el, _i, _len;
        for (_i = 0, _len = elements.length; _i < _len; _i++) {
          el = elements[_i];
          if (/(^| )fallback($| )/.test(el.className)) {
            return el;
          }
        }
      };
      _ref = ["div", "form"];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        tagName = _ref[_i];
        if (fallback = getFallback(this.element.getElementsByTagName(tagName))) {
          return fallback;
        }
      }
    };

    Dropzone.prototype.setupEventListeners = function() {
      var elementListeners, event, listener, _i, _len, _ref, _results;
      _ref = this.listeners;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        elementListeners = _ref[_i];
        _results.push((function() {
          var _ref1, _results1;
          _ref1 = elementListeners.events;
          _results1 = [];
          for (event in _ref1) {
            listener = _ref1[event];
            _results1.push(elementListeners.element.addEventListener(event, listener, false));
          }
          return _results1;
        })());
      }
      return _results;
    };

    Dropzone.prototype.removeEventListeners = function() {
      var elementListeners, event, listener, _i, _len, _ref, _results;
      _ref = this.listeners;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        elementListeners = _ref[_i];
        _results.push((function() {
          var _ref1, _results1;
          _ref1 = elementListeners.events;
          _results1 = [];
          for (event in _ref1) {
            listener = _ref1[event];
            _results1.push(elementListeners.element.removeEventListener(event, listener, false));
          }
          return _results1;
        })());
      }
      return _results;
    };

    Dropzone.prototype.disable = function() {
      var file, _i, _len, _ref, _results;
      this.clickableElements.forEach(function(element) {
        return element.classList.remove("dz-clickable");
      });
      this.removeEventListeners();
      _ref = this.files;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        file = _ref[_i];
        _results.push(this.cancelUpload(file));
      }
      return _results;
    };

    Dropzone.prototype.enable = function() {
      this.clickableElements.forEach(function(element) {
        return element.classList.add("dz-clickable");
      });
      return this.setupEventListeners();
    };

    Dropzone.prototype.filesize = function(size) {
      var cutoff, i, selectedSize, selectedUnit, unit, units, _i, _len;
      selectedSize = 0;
      selectedUnit = "b";
      if (size > 0) {
        units = ['TB', 'GB', 'MB', 'KB', 'b'];
        for (i = _i = 0, _len = units.length; _i < _len; i = ++_i) {
          unit = units[i];
          cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10;
          if (size >= cutoff) {
            selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i);
            selectedUnit = unit;
            break;
          }
        }
        selectedSize = Math.round(10 * selectedSize) / 10;
      }
      return "<strong>" + selectedSize + "</strong> " + selectedUnit;
    };

    Dropzone.prototype._updateMaxFilesReachedClass = function() {
      if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) {
        if (this.getAcceptedFiles().length === this.options.maxFiles) {
          this.emit('maxfilesreached', this.files);
        }
        return this.element.classList.add("dz-max-files-reached");
      } else {
        return this.element.classList.remove("dz-max-files-reached");
      }
    };

    Dropzone.prototype.drop = function(e) {
      var files, items;
      if (!e.dataTransfer) {
        return;
      }
      this.emit("drop", e);
      files = e.dataTransfer.files;
      this.emit("addedfiles", files);
      if (files.length) {
        items = e.dataTransfer.items;
        if (items && items.length && (items[0].webkitGetAsEntry != null)) {
          this._addFilesFromItems(items);
        } else {
          this.handleFiles(files);
        }
      }
    };

    Dropzone.prototype.paste = function(e) {
      var items, _ref;
      if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) {
        return;
      }
      this.emit("paste", e);
      items = e.clipboardData.items;
      if (items.length) {
        return this._addFilesFromItems(items);
      }
    };

    Dropzone.prototype.handleFiles = function(files) {
      var file, _i, _len, _results;
      _results = [];
      for (_i = 0, _len = files.length; _i < _len; _i++) {
        file = files[_i];
        _results.push(this.addFile(file));
      }
      return _results;
    };

    Dropzone.prototype._addFilesFromItems = function(items) {
      var entry, item, _i, _len, _results;
      _results = [];
      for (_i = 0, _len = items.length; _i < _len; _i++) {
        item = items[_i];
        if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) {
          if (entry.isFile) {
            _results.push(this.addFile(item.getAsFile()));
          } else if (entry.isDirectory) {
            _results.push(this._addFilesFromDirectory(entry, entry.name));
          } else {
            _results.push(void 0);
          }
        } else if (item.getAsFile != null) {
          if ((item.kind == null) || item.kind === "file") {
            _results.push(this.addFile(item.getAsFile()));
          } else {
            _results.push(void 0);
          }
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };

    Dropzone.prototype._addFilesFromDirectory = function(directory, path) {
      var dirReader, entriesReader;
      dirReader = directory.createReader();
      entriesReader = (function(_this) {
        return function(entries) {
          var entry, _i, _len;
          for (_i = 0, _len = entries.length; _i < _len; _i++) {
            entry = entries[_i];
            if (entry.isFile) {
              entry.file(function(file) {
                if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') {
                  return;
                }
                file.fullPath = "" + path + "/" + file.name;
                return _this.addFile(file);
              });
            } else if (entry.isDirectory) {
              _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name);
            }
          }
        };
      })(this);
      return dirReader.readEntries(entriesReader, function(error) {
        return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0;
      });
    };

    Dropzone.prototype.accept = function(file, done) {
      if (file.size > this.options.maxFilesize * 1024 * 1024) {
        return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize));
      } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) {
        return done(this.options.dictInvalidFileType);
      } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) {
        done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles));
        return this.emit("maxfilesexceeded", file);
      } else {
        return this.options.accept.call(this, file, done);
      }
    };

    Dropzone.prototype.addFile = function(file) {
      file.upload = {
        progress: 0,
        total: file.size,
        bytesSent: 0
      };
      this.files.push(file);
      file.status = Dropzone.ADDED;
      this.emit("addedfile", file);
      this._enqueueThumbnail(file);
      return this.accept(file, (function(_this) {
        return function(error) {
          if (error) {
            file.accepted = false;
            _this._errorProcessing([file], error);
          } else {
            file.accepted = true;
            if (_this.options.autoQueue) {
              _this.enqueueFile(file);
            }
          }
          return _this._updateMaxFilesReachedClass();
        };
      })(this));
    };

    Dropzone.prototype.enqueueFiles = function(files) {
      var file, _i, _len;
      for (_i = 0, _len = files.length; _i < _len; _i++) {
        file = files[_i];
        this.enqueueFile(file);
      }
      return null;
    };

    Dropzone.prototype.enqueueFile = function(file) {
      if (file.status === Dropzone.ADDED && file.accepted === true) {
        file.status = Dropzone.QUEUED;
        if (this.options.autoProcessQueue) {
          return setTimeout(((function(_this) {
            return function() {
              return _this.processQueue();
            };
          })(this)), 0);
        }
      } else {
        throw new Error("This file can't be queued because it has already been processed or was rejected.");
      }
    };

    Dropzone.prototype._thumbnailQueue = [];

    Dropzone.prototype._processingThumbnail = false;

    Dropzone.prototype._enqueueThumbnail = function(file) {
      if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) {
        this._thumbnailQueue.push(file);
        return setTimeout(((function(_this) {
          return function() {
            return _this._processThumbnailQueue();
          };
        })(this)), 0);
      }
    };

    Dropzone.prototype._processThumbnailQueue = function() {
      if (this._processingThumbnail || this._thumbnailQueue.length === 0) {
        return;
      }
      this._processingThumbnail = true;
      return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) {
        return function() {
          _this._processingThumbnail = false;
          return _this._processThumbnailQueue();
        };
      })(this));
    };

    Dropzone.prototype.removeFile = function(file) {
      if (file.status === Dropzone.UPLOADING) {
        this.cancelUpload(file);
      }
      this.files = without(this.files, file);
      this.emit("removedfile", file);
      if (this.files.length === 0) {
        return this.emit("reset");
      }
    };

    Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) {
      var file, _i, _len, _ref;
      if (cancelIfNecessary == null) {
        cancelIfNecessary = false;
      }
      _ref = this.files.slice();
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        file = _ref[_i];
        if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) {
          this.removeFile(file);
        }
      }
      return null;
    };

    Dropzone.prototype.createThumbnail = function(file, callback) {
      var fileReader;
      fileReader = new FileReader;
      fileReader.onload = (function(_this) {
        return function() {
          if (file.type === "image/svg+xml") {
            _this.emit("thumbnail", file, fileReader.result);
            if (callback != null) {
              callback();
            }
            return;
          }
          return _this.createThumbnailFromUrl(file, fileReader.result, callback);
        };
      })(this);
      return fileReader.readAsDataURL(file);
    };

    Dropzone.prototype.createThumbnailFromUrl = function(file, imageUrl, callback, crossOrigin) {
      var img;
      img = document.createElement("img");
      if (crossOrigin) {
        img.crossOrigin = crossOrigin;
      }
      img.onload = (function(_this) {
        return function() {
          var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3;
          file.width = img.width;
          file.height = img.height;
          resizeInfo = _this.options.resize.call(_this, file);
          if (resizeInfo.trgWidth == null) {
            resizeInfo.trgWidth = resizeInfo.optWidth;
          }
          if (resizeInfo.trgHeight == null) {
            resizeInfo.trgHeight = resizeInfo.optHeight;
          }
          canvas = document.createElement("canvas");
          ctx = canvas.getContext("2d");
          canvas.width = resizeInfo.trgWidth;
          canvas.height = resizeInfo.trgHeight;
          drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight);
          thumbnail = canvas.toDataURL("image/png");
          _this.emit("thumbnail", file, thumbnail);
          if (callback != null) {
            return callback();
          }
        };
      })(this);
      if (callback != null) {
        img.onerror = callback;
      }
      return img.src = imageUrl;
    };

    Dropzone.prototype.processQueue = function() {
      var i, parallelUploads, processingLength, queuedFiles;
      parallelUploads = this.options.parallelUploads;
      processingLength = this.getUploadingFiles().length;
      i = processingLength;
      if (processingLength >= parallelUploads) {
        return;
      }
      queuedFiles = this.getQueuedFiles();
      if (!(queuedFiles.length > 0)) {
        return;
      }
      if (this.options.uploadMultiple) {
        return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength));
      } else {
        while (i < parallelUploads) {
          if (!queuedFiles.length) {
            return;
          }
          this.processFile(queuedFiles.shift());
          i++;
        }
      }
    };

    Dropzone.prototype.processFile = function(file) {
      return this.processFiles([file]);
    };

    Dropzone.prototype.processFiles = function(files) {
      var file, _i, _len;
      for (_i = 0, _len = files.length; _i < _len; _i++) {
        file = files[_i];
        file.processing = true;
        file.status = Dropzone.UPLOADING;
        this.emit("processing", file);
      }
      if (this.options.uploadMultiple) {
        this.emit("processingmultiple", files);
      }
      return this.uploadFiles(files);
    };

    Dropzone.prototype._getFilesWithXhr = function(xhr) {
      var file, files;
      return files = (function() {
        var _i, _len, _ref, _results;
        _ref = this.files;
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          file = _ref[_i];
          if (file.xhr === xhr) {
            _results.push(file);
          }
        }
        return _results;
      }).call(this);
    };

    Dropzone.prototype.cancelUpload = function(file) {
      var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref;
      if (file.status === Dropzone.UPLOADING) {
        groupedFiles = this._getFilesWithXhr(file.xhr);
        for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) {
          groupedFile = groupedFiles[_i];
          groupedFile.status = Dropzone.CANCELED;
        }
        file.xhr.abort();
        for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) {
          groupedFile = groupedFiles[_j];
          this.emit("canceled", groupedFile);
        }
        if (this.options.uploadMultiple) {
          this.emit("canceledmultiple", groupedFiles);
        }
      } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) {
        file.status = Dropzone.CANCELED;
        this.emit("canceled", file);
        if (this.options.uploadMultiple) {
          this.emit("canceledmultiple", [file]);
        }
      }
      if (this.options.autoProcessQueue) {
        return this.processQueue();
      }
    };

    resolveOption = function() {
      var args, option;
      option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      if (typeof option === 'function') {
        return option.apply(this, args);
      }
      return option;
    };

    Dropzone.prototype.uploadFile = function(file) {
      return this.uploadFiles([file]);
    };

    Dropzone.prototype.uploadFiles = function(files) {
      var file, formData, handleError, headerName, headerValue, headers, i, input, inputName, inputType, key, method, option, progressObj, response, updateProgress, url, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
      xhr = new XMLHttpRequest();
      for (_i = 0, _len = files.length; _i < _len; _i++) {
        file = files[_i];
        file.xhr = xhr;
      }
      method = resolveOption(this.options.method, files);
      url = resolveOption(this.options.url, files);
      xhr.open(method, url, true);
      xhr.withCredentials = !!this.options.withCredentials;
      response = null;
      handleError = (function(_this) {
        return function() {
          var _j, _len1, _results;
          _results = [];
          for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
            file = files[_j];
            _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr));
          }
          return _results;
        };
      })(this);
      updateProgress = (function(_this) {
        return function(e) {
          var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results;
          if (e != null) {
            progress = 100 * e.loaded / e.total;
            for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
              file = files[_j];
              file.upload = {
                progress: progress,
                total: e.total,
                bytesSent: e.loaded
              };
            }
          } else {
            allFilesFinished = true;
            progress = 100;
            for (_k = 0, _len2 = files.length; _k < _len2; _k++) {
              file = files[_k];
              if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) {
                allFilesFinished = false;
              }
              file.upload.progress = progress;
              file.upload.bytesSent = file.upload.total;
            }
            if (allFilesFinished) {
              return;
            }
          }
          _results = [];
          for (_l = 0, _len3 = files.length; _l < _len3; _l++) {
            file = files[_l];
            _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent));
          }
          return _results;
        };
      })(this);
      xhr.onload = (function(_this) {
        return function(e) {
          var _ref;
          if (files[0].status === Dropzone.CANCELED) {
            return;
          }
          if (xhr.readyState !== 4) {
            return;
          }
          response = xhr.responseText;
          if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) {
            try {
              response = JSON.parse(response);
            } catch (_error) {
              e = _error;
              response = "Invalid JSON response from server.";
            }
          }
          updateProgress();
          if (!((200 <= (_ref = xhr.status) && _ref < 300))) {
            return handleError();
          } else {
            return _this._finished(files, response, e);
          }
        };
      })(this);
      xhr.onerror = (function(_this) {
        return function() {
          if (files[0].status === Dropzone.CANCELED) {
            return;
          }
          return handleError();
        };
      })(this);
      progressObj = (_ref = xhr.upload) != null ? _ref : xhr;
      progressObj.onprogress = updateProgress;
      headers = {
        "Accept": "application/json",
        "Cache-Control": "no-cache",
        "X-Requested-With": "XMLHttpRequest"
      };
      if (this.options.headers) {
        extend(headers, this.options.headers);
      }
      for (headerName in headers) {
        headerValue = headers[headerName];
        if (headerValue) {
          xhr.setRequestHeader(headerName, headerValue);
        }
      }
      formData = new FormData();
      if (this.options.params) {
        _ref1 = this.options.params;
        for (key in _ref1) {
          value = _ref1[key];
          formData.append(key, value);
        }
      }
      for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
        file = files[_j];
        this.emit("sending", file, xhr, formData);
      }
      if (this.options.uploadMultiple) {
        this.emit("sendingmultiple", files, xhr, formData);
      }
      if (this.element.tagName === "FORM") {
        _ref2 = this.element.querySelectorAll("input, textarea, select, button");
        for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
          input = _ref2[_k];
          inputName = input.getAttribute("name");
          inputType = input.getAttribute("type");
          if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
            _ref3 = input.options;
            for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
              option = _ref3[_l];
              if (option.selected) {
                formData.append(inputName, option.value);
              }
            }
          } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) {
            formData.append(inputName, input.value);
          }
        }
      }
      for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) {
        formData.append(this._getParamName(i), files[i], files[i].name);
      }
      return this.submitRequest(xhr, formData, files);
    };

    Dropzone.prototype.submitRequest = function(xhr, formData, files) {
      return xhr.send(formData);
    };

    Dropzone.prototype._finished = function(files, responseText, e) {
      var file, _i, _len;
      for (_i = 0, _len = files.length; _i < _len; _i++) {
        file = files[_i];
        file.status = Dropzone.SUCCESS;
        this.emit("success", file, responseText, e);
        this.emit("complete", file);
      }
      if (this.options.uploadMultiple) {
        this.emit("successmultiple", files, responseText, e);
        this.emit("completemultiple", files);
      }
      if (this.options.autoProcessQueue) {
        return this.processQueue();
      }
    };

    Dropzone.prototype._errorProcessing = function(files, message, xhr) {
      var file, _i, _len;
      for (_i = 0, _len = files.length; _i < _len; _i++) {
        file = files[_i];
        file.status = Dropzone.ERROR;
        this.emit("error", file, message, xhr);
        this.emit("complete", file);
      }
      if (this.options.uploadMultiple) {
        this.emit("errormultiple", files, message, xhr);
        this.emit("completemultiple", files);
      }
      if (this.options.autoProcessQueue) {
        return this.processQueue();
      }
    };

    return Dropzone;

  })(Emitter);

  Dropzone.version = "4.2.0";

  Dropzone.options = {};

  Dropzone.optionsForElement = function(element) {
    if (element.getAttribute("id")) {
      return Dropzone.options[camelize(element.getAttribute("id"))];
    } else {
      return void 0;
    }
  };

  Dropzone.instances = [];

  Dropzone.forElement = function(element) {
    if (typeof element === "string") {
      element = document.querySelector(element);
    }
    if ((element != null ? element.dropzone : void 0) == null) {
      throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");
    }
    return element.dropzone;
  };

  Dropzone.autoDiscover = true;

  Dropzone.discover = function() {
    var checkElements, dropzone, dropzones, _i, _len, _results;
    if (document.querySelectorAll) {
      dropzones = document.querySelectorAll(".dropzone");
    } else {
      dropzones = [];
      checkElements = function(elements) {
        var el, _i, _len, _results;
        _results = [];
        for (_i = 0, _len = elements.length; _i < _len; _i++) {
          el = elements[_i];
          if (/(^| )dropzone($| )/.test(el.className)) {
            _results.push(dropzones.push(el));
          } else {
            _results.push(void 0);
          }
        }
        return _results;
      };
      checkElements(document.getElementsByTagName("div"));
      checkElements(document.getElementsByTagName("form"));
    }
    _results = [];
    for (_i = 0, _len = dropzones.length; _i < _len; _i++) {
      dropzone = dropzones[_i];
      if (Dropzone.optionsForElement(dropzone) !== false) {
        _results.push(new Dropzone(dropzone));
      } else {
        _results.push(void 0);
      }
    }
    return _results;
  };

  Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i];

  Dropzone.isBrowserSupported = function() {
    var capableBrowser, regex, _i, _len, _ref;
    capableBrowser = true;
    if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) {
      if (!("classList" in document.createElement("a"))) {
        capableBrowser = false;
      } else {
        _ref = Dropzone.blacklistedBrowsers;
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          regex = _ref[_i];
          if (regex.test(navigator.userAgent)) {
            capableBrowser = false;
            continue;
          }
        }
      }
    } else {
      capableBrowser = false;
    }
    return capableBrowser;
  };

  without = function(list, rejectedItem) {
    var item, _i, _len, _results;
    _results = [];
    for (_i = 0, _len = list.length; _i < _len; _i++) {
      item = list[_i];
      if (item !== rejectedItem) {
        _results.push(item);
      }
    }
    return _results;
  };

  camelize = function(str) {
    return str.replace(/[\-_](\w)/g, function(match) {
      return match.charAt(1).toUpperCase();
    });
  };

  Dropzone.createElement = function(string) {
    var div;
    div = document.createElement("div");
    div.innerHTML = string;
    return div.childNodes[0];
  };

  Dropzone.elementInside = function(element, container) {
    if (element === container) {
      return true;
    }
    while (element = element.parentNode) {
      if (element === container) {
        return true;
      }
    }
    return false;
  };

  Dropzone.getElement = function(el, name) {
    var element;
    if (typeof el === "string") {
      element = document.querySelector(el);
    } else if (el.nodeType != null) {
      element = el;
    }
    if (element == null) {
      throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element.");
    }
    return element;
  };

  Dropzone.getElements = function(els, name) {
    var e, el, elements, _i, _j, _len, _len1, _ref;
    if (els instanceof Array) {
      elements = [];
      try {
        for (_i = 0, _len = els.length; _i < _len; _i++) {
          el = els[_i];
          elements.push(this.getElement(el, name));
        }
      } catch (_error) {
        e = _error;
        elements = null;
      }
    } else if (typeof els === "string") {
      elements = [];
      _ref = document.querySelectorAll(els);
      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
        el = _ref[_j];
        elements.push(el);
      }
    } else if (els.nodeType != null) {
      elements = [els];
    }
    if (!((elements != null) && elements.length)) {
      throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");
    }
    return elements;
  };

  Dropzone.confirm = function(question, accepted, rejected) {
    if (window.confirm(question)) {
      return accepted();
    } else if (rejected != null) {
      return rejected();
    }
  };

  Dropzone.isValidFile = function(file, acceptedFiles) {
    var baseMimeType, mimeType, validType, _i, _len;
    if (!acceptedFiles) {
      return true;
    }
    acceptedFiles = acceptedFiles.split(",");
    mimeType = file.type;
    baseMimeType = mimeType.replace(/\/.*$/, "");
    for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) {
      validType = acceptedFiles[_i];
      validType = validType.trim();
      if (validType.charAt(0) === ".") {
        if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) {
          return true;
        }
      } else if (/\/\*$/.test(validType)) {
        if (baseMimeType === validType.replace(/\/.*$/, "")) {
          return true;
        }
      } else {
        if (mimeType === validType) {
          return true;
        }
      }
    }
    return false;
  };

  if (typeof jQuery !== "undefined" && jQuery !== null) {
    jQuery.fn.dropzone = function(options) {
      return this.each(function() {
        return new Dropzone(this, options);
      });
    };
  }

  if (typeof module !== "undefined" && module !== null) {
    module.exports = Dropzone;
  } else {
    window.Dropzone = Dropzone;
  }

  Dropzone.ADDED = "added";

  Dropzone.QUEUED = "queued";

  Dropzone.ACCEPTED = Dropzone.QUEUED;

  Dropzone.UPLOADING = "uploading";

  Dropzone.PROCESSING = Dropzone.UPLOADING;

  Dropzone.CANCELED = "canceled";

  Dropzone.ERROR = "error";

  Dropzone.SUCCESS = "success";


  /*
  
  Bugfix for iOS 6 and 7
  Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
  based on the work of https://github.com/stomita/ios-imagefile-megapixel
   */

  detectVerticalSquash = function(img) {
    var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy;
    iw = img.naturalWidth;
    ih = img.naturalHeight;
    canvas = document.createElement("canvas");
    canvas.width = 1;
    canvas.height = ih;
    ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    data = ctx.getImageData(0, 0, 1, ih).data;
    sy = 0;
    ey = ih;
    py = ih;
    while (py > sy) {
      alpha = data[(py - 1) * 4 + 3];
      if (alpha === 0) {
        ey = py;
      } else {
        sy = py;
      }
      py = (ey + sy) >> 1;
    }
    ratio = py / ih;
    if (ratio === 0) {
      return 1;
    } else {
      return ratio;
    }
  };

  drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
    var vertSquashRatio;
    vertSquashRatio = detectVerticalSquash(img);
    return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
  };


  /*
   * contentloaded.js
   *
   * Author: Diego Perini (diego.perini at gmail.com)
   * Summary: cross-browser wrapper for DOMContentLoaded
   * Updated: 20101020
   * License: MIT
   * Version: 1.2
   *
   * URL:
   * http://javascript.nwbox.com/ContentLoaded/
   * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
   */

  contentLoaded = function(win, fn) {
    var add, doc, done, init, poll, pre, rem, root, top;
    done = false;
    top = true;
    doc = win.document;
    root = doc.documentElement;
    add = (doc.addEventListener ? "addEventListener" : "attachEvent");
    rem = (doc.addEventListener ? "removeEventListener" : "detachEvent");
    pre = (doc.addEventListener ? "" : "on");
    init = function(e) {
      if (e.type === "readystatechange" && doc.readyState !== "complete") {
        return;
      }
      (e.type === "load" ? win : doc)[rem](pre + e.type, init, false);
      if (!done && (done = true)) {
        return fn.call(win, e.type || e);
      }
    };
    poll = function() {
      var e;
      try {
        root.doScroll("left");
      } catch (_error) {
        e = _error;
        setTimeout(poll, 50);
        return;
      }
      return init("poll");
    };
    if (doc.readyState !== "complete") {
      if (doc.createEventObject && root.doScroll) {
        try {
          top = !win.frameElement;
        } catch (_error) {}
        if (top) {
          poll();
        }
      }
      doc[add](pre + "DOMContentLoaded", init, false);
      doc[add](pre + "readystatechange", init, false);
      return win[add](pre + "load", init, false);
    }
  };

  Dropzone._autoDiscoverFunction = function() {
    if (Dropzone.autoDiscover) {
      return Dropzone.discover();
    }
  };

  contentLoaded(window, Dropzone._autoDiscoverFunction);

}).call(this);
;
/*jslint devel: true, bitwise: true, regexp: true, browser: true, confusion: true, unparam: true, eqeq: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
/*globals jQuery,Color,define */

/*!
 * ColorPicker
 *
 * Copyright (c) 2011-2016 Martijn W. van der Lee
 * Licensed under the MIT.
 */
/* Full-featured colorpicker for jQueryUI with full theming support.
 * Most images from jPicker by Christopher T. Tillman.
 * Sourcecode created from scratch by Martijn W. van der Lee.
 */
(function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define([
			"jquery"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} (function ($) {
	"use strict";

	var _colorpicker_index = 0,

		_container_popup = '<div class="ui-colorpicker ui-colorpicker-dialog ui-dialog ui-widget ui-widget-content ui-corner-all" style="display: none;"></div>',
		_container_inlineFrame = '<div class="ui-colorpicker ui-colorpicker-inline ui-dialog ui-widget ui-widget-content ui-corner-all"></div>',
		_container_inline = '<div class="ui-colorpicker ui-colorpicker-inline"></div>',

		_intToHex = function (dec) {
			var result = Math.round(dec).toString(16);
			if (result.length === 1) {
				result = ('0' + result);
			}
			return result.toLowerCase();
		},
		
		_keycode = {
			isPrint: function(keycode) {
				return keycode == 32						// spacebar
					|| (keycode >= 48 && keycode <= 57)		// number keys
					|| (keycode >= 65 && keycode <= 90)		// letter keys
					|| (keycode >= 96 && keycode <= 111)	// numpad keys
					|| (keycode >= 186 && keycode < 192)	// ;=,-./` (in order)
					|| (keycode >= 219 && keycode < 222);	// [\]' (in order)
			},			
			isHex: function(keycode) {
				return (keycode >= 48 && keycode <= 57)		// number keys
					|| (keycode >= 65 && keycode <= 70);	// a-f
			}
		},

		_layoutTable = function(layout, callback) {
			var bitmap,
				x, y,
				width, height,
				columns, rows,
				index,
				cell,
				html,
				w, h,
				colspan,
				walked;

			layout.sort(function(a, b) {
				if (a.pos[1] === b.pos[1]) {
					return a.pos[0] - b.pos[0];
				}
				return a.pos[1] - b.pos[1];
			});

			// Determine dimensions of the table
			width = 0;
			height = 0;
			$.each (layout, function(index, part) {
				width = Math.max(width, part.pos[0] + part.pos[2]);
				height = Math.max(height, part.pos[1] + part.pos[3]);
			});

			// Initialize bitmap
			bitmap = [];
			for (x = 0; x < width; ++x) {
				bitmap.push([]);
			}

			// Mark rows and columns which have layout assigned
			rows	= [];
			columns = [];
			$.each(layout, function(index, part) {
				// mark columns
				for (x = 0; x < part.pos[2]; x += 1) {
					columns[part.pos[0] + x] = true;
				}
				for (y = 0; y < part.pos[3]; y += 1) {
					rows[part.pos[1] + y] = true;
				}
			});

			// Generate the table
			html = '';
			cell = layout[index = 0];
			for (y = 0; y < height; ++y) {
				html += '<tr>';
                x = 0;
                while (x < width) {
					if (typeof cell !== 'undefined' && x === cell.pos[0] && y === cell.pos[1]) {
						// Create a "real" cell
						html += callback(cell, x, y);

						for (h = 0; h < cell.pos[3]; h +=1) {
							for (w = 0; w < cell.pos[2]; w +=1) {
								bitmap[x + w][y + h] = true;
							}
						}

						x += cell.pos[2];
						cell = layout[++index];
					} else {
						// Fill in the gaps
						colspan = 0;
						walked = false;

						while (x < width && bitmap[x][y] === undefined && (cell === undefined || y < cell.pos[1] || (y === cell.pos[1] && x < cell.pos[0]))) {
							if (columns[x] === true) {
								colspan += 1;
							}
							walked = true;
							x += 1;
						}

						if (colspan > 0) {
							html += '<td colspan="'+colspan+'"></td>';
						} else if (!walked) {
							x += 1;
						}
					}
				}
				html += '</tr>';
			}

			return '<table cellspacing="0" cellpadding="0" border="0"><tbody>' + html + '</tbody></table>';
		};

	$.colorpicker = new function() {
		this.regional = {
			'':	{
				ok:				'OK',
				cancel:			'Cancel',
				none:			'None',
				button:			'Color',
				title:			'Pick a color',
				transparent:	'Transparent',
				hsvH:			'H',
				hsvS:			'S',
				hsvV:			'V',
				rgbR:			'R',
				rgbG:			'G',
				rgbB:			'B',
				labL:			'L',
				labA:			'a',
				labB:			'b',
				hslH:			'H',
				hslS:			'S',
				hslL:			'L',
				cmykC:			'C',
				cmykM:			'M',
				cmykY:			'Y',
				cmykK:			'K',
				alphaA:			'A'
			}
		};

		this.swatchesNames = {
			'html':		'HTML'
		};

		this.swatches = {
			'html':	[
				{name: 'black',					r: 0, g: 0, b: 0},
				{name: 'dimgray',				r: 0.4117647058823529, g: 0.4117647058823529, b: 0.4117647058823529},
				{name: 'gray',					r: 0.5019607843137255, g: 0.5019607843137255, b: 0.5019607843137255},
				{name: 'darkgray',				r: 0.6627450980392157, g: 0.6627450980392157, b: 0.6627450980392157},
				{name: 'silver',				r: 0.7529411764705882, g: 0.7529411764705882, b: 0.7529411764705882},
				{name: 'lightgrey',				r: 0.8274509803921568, g: 0.8274509803921568, b: 0.8274509803921568},
				{name: 'gainsboro',				r: 0.8627450980392157, g: 0.8627450980392157, b: 0.8627450980392157},
				{name: 'whitesmoke',			r: 0.9607843137254902, g: 0.9607843137254902, b: 0.9607843137254902},
				{name: 'white',					r: 1, g: 1, b: 1},
				{name: 'rosybrown',				r: 0.7372549019607844, g: 0.5607843137254902, b: 0.5607843137254902},
				{name: 'indianred',				r: 0.803921568627451, g: 0.3607843137254902, b: 0.3607843137254902},
				{name: 'brown',					r: 0.6470588235294118, g: 0.16470588235294117, b: 0.16470588235294117},
				{name: 'firebrick',				r: 0.6980392156862745, g: 0.13333333333333333, b: 0.13333333333333333},
				{name: 'lightcoral',			r: 0.9411764705882353, g: 0.5019607843137255, b: 0.5019607843137255},
				{name: 'maroon',				r: 0.5019607843137255, g: 0, b: 0},
				{name: 'darkred',				r: 0.5450980392156862, g: 0, b: 0},
				{name: 'red',					r: 1, g: 0, b: 0},
				{name: 'snow',					r: 1, g: 0.9803921568627451, b: 0.9803921568627451},
				{name: 'salmon',				r: 0.9803921568627451, g: 0.5019607843137255, b: 0.4470588235294118},
				{name: 'mistyrose',				r: 1, g: 0.8941176470588236, b: 0.8823529411764706},
				{name: 'tomato',				r: 1, g: 0.38823529411764707, b: 0.2784313725490196},
				{name: 'darksalmon',			r: 0.9137254901960784, g: 0.5882352941176471, b: 0.47843137254901963},
				{name: 'orangered',				r: 1, g: 0.27058823529411763, b: 0},
				{name: 'coral',					r: 1, g: 0.4980392156862745, b: 0.3137254901960784},
				{name: 'lightsalmon',			r: 1, g: 0.6274509803921569, b: 0.47843137254901963},
				{name: 'sienna',				r: 0.6274509803921569, g: 0.3215686274509804, b: 0.17647058823529413},
				{name: 'seashell',				r: 1, g: 0.9607843137254902, b: 0.9333333333333333},
				{name: 'chocolate',				r: 0.8235294117647058, g: 0.4117647058823529, b: 0.11764705882352941},
				{name: 'saddlebrown',			r: 0.5450980392156862, g: 0.27058823529411763, b: 0.07450980392156863},
				{name: 'sandybrown',			r: 0.9568627450980393, g: 0.6431372549019608, b: 0.3764705882352941},
				{name: 'peachpuff',				r: 1, g: 0.8549019607843137, b: 0.7254901960784313},
				{name: 'peru',					r: 0.803921568627451, g: 0.5215686274509804, b: 0.24705882352941178},
				{name: 'linen',					r: 0.9803921568627451, g: 0.9411764705882353, b: 0.9019607843137255},
				{name: 'darkorange',			r: 1, g: 0.5490196078431373, b: 0},
				{name: 'bisque',				r: 1, g: 0.8941176470588236, b: 0.7686274509803922},
				{name: 'burlywood',				r: 0.8705882352941177, g: 0.7215686274509804, b: 0.5294117647058824},
				{name: 'tan',					r: 0.8235294117647058, g: 0.7058823529411765, b: 0.5490196078431373},
				{name: 'antiquewhite',			r: 0.9803921568627451, g: 0.9215686274509803, b: 0.8431372549019608},
				{name: 'navajowhite',			r: 1, g: 0.8705882352941177, b: 0.6784313725490196},
				{name: 'blanchedalmond',		r: 1, g: 0.9215686274509803, b: 0.803921568627451},
				{name: 'papayawhip',			r: 1, g: 0.9372549019607843, b: 0.8352941176470589},
				{name: 'orange',				r: 1, g: 0.6470588235294118, b: 0},
				{name: 'moccasin',				r: 1, g: 0.8941176470588236, b: 0.7098039215686275},
				{name: 'wheat',					r: 0.9607843137254902, g: 0.8705882352941177, b: 0.7019607843137254},
				{name: 'oldlace',				r: 0.9921568627450981, g: 0.9607843137254902, b: 0.9019607843137255},
				{name: 'floralwhite',			r: 1, g: 0.9803921568627451, b: 0.9411764705882353},
				{name: 'goldenrod',				r: 0.8549019607843137, g: 0.6470588235294118, b: 0.12549019607843137},
				{name: 'darkgoldenrod',			r: 0.7215686274509804, g: 0.5254901960784314, b: 0.043137254901960784},
				{name: 'cornsilk',				r: 1, g: 0.9725490196078431, b: 0.8627450980392157},
				{name: 'gold',					r: 1, g: 0.8431372549019608, b: 0},
				{name: 'palegoldenrod',			r: 0.9333333333333333, g: 0.9098039215686274, b: 0.6666666666666666},
				{name: 'khaki',					r: 0.9411764705882353, g: 0.9019607843137255, b: 0.5490196078431373},
				{name: 'lemonchiffon',			r: 1, g: 0.9803921568627451, b: 0.803921568627451},
				{name: 'darkkhaki',				r: 0.7411764705882353, g: 0.7176470588235294, b: 0.4196078431372549},
				{name: 'beige',					r: 0.9607843137254902, g: 0.9607843137254902, b: 0.8627450980392157},
				{name: 'lightgoldenrodyellow',	r: 0.9803921568627451, g: 0.9803921568627451, b: 0.8235294117647058},
				{name: 'olive',					r: 0.5019607843137255, g: 0.5019607843137255, b: 0},
				{name: 'yellow',				r: 1, g: 1, b: 0},
				{name: 'lightyellow',			r: 1, g: 1, b: 0.8784313725490196},
				{name: 'ivory',					r: 1, g: 1, b: 0.9411764705882353},
				{name: 'olivedrab',				r: 0.4196078431372549, g: 0.5568627450980392, b: 0.13725490196078433},
				{name: 'yellowgreen',			r: 0.6039215686274509, g: 0.803921568627451, b: 0.19607843137254902},
				{name: 'darkolivegreen',		r: 0.3333333333333333, g: 0.4196078431372549, b: 0.1843137254901961},
				{name: 'greenyellow',			r: 0.6784313725490196, g: 1, b: 0.1843137254901961},
				{name: 'lawngreen',				r: 0.48627450980392156, g: 0.9882352941176471, b: 0},
				{name: 'chartreuse',			r: 0.4980392156862745, g: 1, b: 0},
				{name: 'darkseagreen',			r: 0.5607843137254902, g: 0.7372549019607844, b: 0.5607843137254902},
				{name: 'forestgreen',			r: 0.13333333333333333, g: 0.5450980392156862, b: 0.13333333333333333},
				{name: 'limegreen',				r: 0.19607843137254902, g: 0.803921568627451, b: 0.19607843137254902},
				{name: 'lightgreen',			r: 0.5647058823529412, g: 0.9333333333333333, b: 0.5647058823529412},
				{name: 'palegreen',				r: 0.596078431372549, g: 0.984313725490196, b: 0.596078431372549},
				{name: 'darkgreen',				r: 0, g: 0.39215686274509803, b: 0},
				{name: 'green',					r: 0, g: 0.5019607843137255, b: 0},
				{name: 'lime',					r: 0, g: 1, b: 0},
				{name: 'honeydew',				r: 0.9411764705882353, g: 1, b: 0.9411764705882353},
				{name: 'mediumseagreen',		r: 0.23529411764705882, g: 0.7019607843137254, b: 0.44313725490196076},
				{name: 'seagreen',				r: 0.1803921568627451, g: 0.5450980392156862, b: 0.3411764705882353},
				{name: 'springgreen',			r: 0, g: 1, b: 0.4980392156862745},
				{name: 'mintcream',				r: 0.9607843137254902, g: 1, b: 0.9803921568627451},
				{name: 'mediumspringgreen',		r: 0, g: 0.9803921568627451, b: 0.6039215686274509},
				{name: 'mediumaquamarine',		r: 0.4, g: 0.803921568627451, b: 0.6666666666666666},
				{name: 'aquamarine',			r: 0.4980392156862745, g: 1, b: 0.8313725490196079},
				{name: 'turquoise',				r: 0.25098039215686274, g: 0.8784313725490196, b: 0.8156862745098039},
				{name: 'lightseagreen',			r: 0.12549019607843137, g: 0.6980392156862745, b: 0.6666666666666666},
				{name: 'mediumturquoise',		r: 0.2823529411764706, g: 0.8196078431372549, b: 0.8},
				{name: 'darkslategray',			r: 0.1843137254901961, g: 0.30980392156862746, b: 0.30980392156862746},
				{name: 'paleturquoise',			r: 0.6862745098039216, g: 0.9333333333333333, b: 0.9333333333333333},
				{name: 'teal',					r: 0, g: 0.5019607843137255, b: 0.5019607843137255},
				{name: 'darkcyan',				r: 0, g: 0.5450980392156862, b: 0.5450980392156862},
				{name: 'darkturquoise',			r: 0, g: 0.807843137254902, b: 0.8196078431372549},
				{name: 'aqua',					r: 0, g: 1, b: 1},
				{name: 'cyan',					r: 0, g: 1, b: 1},
				{name: 'lightcyan',				r: 0.8784313725490196, g: 1, b: 1},
				{name: 'azure',					r: 0.9411764705882353, g: 1, b: 1},
				{name: 'cadetblue',				r: 0.37254901960784315, g: 0.6196078431372549, b: 0.6274509803921569},
				{name: 'powderblue',			r: 0.6901960784313725, g: 0.8784313725490196, b: 0.9019607843137255},
				{name: 'lightblue',				r: 0.6784313725490196, g: 0.8470588235294118, b: 0.9019607843137255},
				{name: 'deepskyblue',			r: 0, g: 0.7490196078431373, b: 1},
				{name: 'skyblue',				r: 0.5294117647058824, g: 0.807843137254902, b: 0.9215686274509803},
				{name: 'lightskyblue',			r: 0.5294117647058824, g: 0.807843137254902, b: 0.9803921568627451},
				{name: 'steelblue',				r: 0.27450980392156865, g: 0.5098039215686274, b: 0.7058823529411765},
				{name: 'aliceblue',				r: 0.9411764705882353, g: 0.9725490196078431, b: 1},
				{name: 'dodgerblue',			r: 0.11764705882352941, g: 0.5647058823529412, b: 1},
				{name: 'slategray',				r: 0.4392156862745098, g: 0.5019607843137255, b: 0.5647058823529412},
				{name: 'lightslategray',		r: 0.4666666666666667, g: 0.5333333333333333, b: 0.6},
				{name: 'lightsteelblue',		r: 0.6901960784313725, g: 0.7686274509803922, b: 0.8705882352941177},
				{name: 'cornflowerblue',		r: 0.39215686274509803, g: 0.5843137254901961, b: 0.9294117647058824},
				{name: 'royalblue',				r: 0.2549019607843137, g: 0.4117647058823529, b: 0.8823529411764706},
				{name: 'midnightblue',			r: 0.09803921568627451, g: 0.09803921568627451, b: 0.4392156862745098},
				{name: 'lavender',				r: 0.9019607843137255, g: 0.9019607843137255, b: 0.9803921568627451},
				{name: 'navy',					r: 0, g: 0, b: 0.5019607843137255},
				{name: 'darkblue',				r: 0, g: 0, b: 0.5450980392156862},
				{name: 'mediumblue',			r: 0, g: 0, b: 0.803921568627451},
				{name: 'blue',					r: 0, g: 0, b: 1},
				{name: 'ghostwhite',			r: 0.9725490196078431, g: 0.9725490196078431, b: 1},
				{name: 'darkslateblue',			r: 0.2823529411764706, g: 0.23921568627450981, b: 0.5450980392156862},
				{name: 'slateblue',				r: 0.41568627450980394, g: 0.35294117647058826, b: 0.803921568627451},
				{name: 'mediumslateblue',		r: 0.4823529411764706, g: 0.40784313725490196, b: 0.9333333333333333},
				{name: 'mediumpurple',			r: 0.5764705882352941, g: 0.4392156862745098, b: 0.8588235294117647},
				{name: 'blueviolet',			r: 0.5411764705882353, g: 0.16862745098039217, b: 0.8862745098039215},
				{name: 'indigo',				r: 0.29411764705882354, g: 0, b: 0.5098039215686274},
				{name: 'darkorchid',			r: 0.6, g: 0.19607843137254902, b: 0.8},
				{name: 'darkviolet',			r: 0.5803921568627451, g: 0, b: 0.8274509803921568},
				{name: 'mediumorchid',			r: 0.7294117647058823, g: 0.3333333333333333, b: 0.8274509803921568},
				{name: 'thistle',				r: 0.8470588235294118, g: 0.7490196078431373, b: 0.8470588235294118},
				{name: 'plum',					r: 0.8666666666666667, g: 0.6274509803921569, b: 0.8666666666666667},
				{name: 'violet',				r: 0.9333333333333333, g: 0.5098039215686274, b: 0.9333333333333333},
				{name: 'purple',				r: 0.5019607843137255, g: 0, b: 0.5019607843137255},
				{name: 'darkmagenta',			r: 0.5450980392156862, g: 0, b: 0.5450980392156862},
				{name: 'magenta',				r: 1, g: 0, b: 1},
				{name: 'fuchsia',				r: 1, g: 0, b: 1},
				{name: 'orchid',				r: 0.8549019607843137, g: 0.4392156862745098, b: 0.8392156862745098},
				{name: 'mediumvioletred',		r: 0.7803921568627451, g: 0.08235294117647059, b: 0.5215686274509804},
				{name: 'deeppink',				r: 1, g: 0.0784313725490196, b: 0.5764705882352941},
				{name: 'hotpink',				r: 1, g: 0.4117647058823529, b: 0.7058823529411765},
				{name: 'palevioletred',			r: 0.8588235294117647, g: 0.4392156862745098, b: 0.5764705882352941},
				{name: 'lavenderblush',			r: 1, g: 0.9411764705882353, b: 0.9607843137254902},
				{name: 'crimson',				r: 0.8627450980392157, g: 0.0784313725490196, b: 0.23529411764705882},
				{name: 'pink',					r: 1, g: 0.7529411764705882, b: 0.796078431372549},
				{name: 'lightpink',				r: 1, g: 0.7137254901960784, b: 0.7568627450980392}
			]
		};

		this.writers = {
			'#HEX':		function(color, that) {
							return that._formatColor('#rxgxbx', color);
						}
		,	'#HEX3':	function(color, that) {
							var hex3 = $.colorpicker.writers.HEX3(color);
							return hex3 === false? false : '#'+hex3;
						}
		,	'HEX':		function(color, that) {
							return that._formatColor('rxgxbx', color);
						}
		,	'HEX3':		function(color, that) {
							var rgb = color.getRGB(),
								r = Math.round(rgb.r * 255),
								g = Math.round(rgb.g * 255),
								b = Math.round(rgb.b * 255);

							if (((r >>> 4) === (r &= 0xf))
							 && ((g >>> 4) === (g &= 0xf))
							 && ((b >>> 4) === (b &= 0xf))) {
								return r.toString(16)+g.toString(16)+b.toString(16);
							}
							return false;
						}
		,	'#HEXA':	function(color, that) {
							return that._formatColor('#rxgxbxax', color);
						}						
		,	'#HEXA4':	function(color, that) {
							var hexa4 = $.colorpicker.writers.HEXA4(color, that);
							return hexa4 === false? false : '#'+hexa4;
						}						
		,	'HEXA':	function(color, that) {
							return that._formatColor('rxgxbxax', color);
						}		
		,	'HEXA4':		function(color, that) {
							var a = Math.round(color.getAlpha() * 255);
						
							if ((a >>> 4) === (a &= 0xf)) {
								return $.colorpicker.writers.HEX3(color, that)+a.toString(16);
							}
							return false;
						}						
		,	'RGB':		function(color, that) {
							return color.getAlpha() >= 1
									? that._formatColor('rgb(rd,gd,bd)', color)
									: false;
						}
		,	'RGBA':		function(color, that) {
							return that._formatColor('rgba(rd,gd,bd,af)', color);
						}
		,	'RGB%':		function(color, that) {
							return color.getAlpha() >= 1
									? that._formatColor('rgb(rp%,gp%,bp%)', color)
									: false;
						}
		,	'RGBA%':	function(color, that) {
							return that._formatColor('rgba(rp%,gp%,bp%,af)', color);
						}
		,	'HSL':		function(color, that) {
							return color.getAlpha() >= 1
									? that._formatColor('hsl(hd,sd,vd)', color)
									: false;
						}
		,	'HSLA':		function(color, that) {
							return that._formatColor('hsla(hd,sd,vd,af)', color);
						}
		,	'HSL%':		function(color, that) {
							return color.getAlpha() >= 1
									? that._formatColor('hsl(hp%,sp%,vp%)', color)
									: false;
						}
		,	'HSLA%':	function(color, that) {
							return that._formatColor('hsla(hp%,sp%,vp%,af)', color);
						}
		,	'NAME':		function(color, that) {
							return that._closestName(color);
						}
		,	'EXACT':	function(color, that) {
							return that._exactName(color);
						}
		};

		this.parsers = {
			'':			function(color) {
				            if (color === '') {
								return new $.colorpicker.Color();
							}
						}
		,	'NAME':		function(color, that) {
							var c = that._getSwatch($.trim(color));
							if (c) {
								return new $.colorpicker.Color(c.r, c.g, c.b);
							}
						}
		,	'RGBA':		function(color) {
							var m = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
									m[1] / 255,
									m[2] / 255,
									m[3] / 255,
									parseFloat(m[4])
								);
							}
						}
		,	'RGBA%':	function(color) {
							var m = /^rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
									m[1] / 100,
									m[2] / 100,
									m[3] / 100,
									m[4] / 100
								);
							}
						}
		,	'HSLA':		function(color) {
							var m = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)$/.exec(color);
							if (m) {
								return (new $.colorpicker.Color()).setHSL(
									m[1] / 255,
									m[2] / 255,
									m[3] / 255).setAlpha(parseFloat(m[4]));
							}
						}
		,	'HSLA%':	function(color) {
							var m = /^hsla?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)$/.exec(color);
							if (m) {
								return (new $.colorpicker.Color()).setHSL(
									m[1] / 100,
									m[2] / 100,
									m[3] / 100).setAlpha(m[4] / 100);
							}
						}
		,	'#HEX':		function(color) {
							var m = /^#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
									parseInt(m[1], 16) / 255,
									parseInt(m[2], 16) / 255,
									parseInt(m[3], 16) / 255
								);
							}
						}
		,	'#HEX3':	function(color) {
							var m = /^#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
								   parseInt(String(m[1]) + m[1], 16) / 255,
								   parseInt(String(m[2]) + m[2], 16) / 255,
								   parseInt(String(m[3]) + m[3], 16) / 255
								);
							}
						}
		,	'HEX':		function(color) {
							var m = /^([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
									parseInt(m[1], 16) / 255,
									parseInt(m[2], 16) / 255,
									parseInt(m[3], 16) / 255
								);
							}
						}
		,	'HEX3':		function(color) {
							var m = /^([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
								   parseInt(String(m[1]) + m[1], 16) / 255,
								   parseInt(String(m[2]) + m[2], 16) / 255,
								   parseInt(String(m[3]) + m[3], 16) / 255
								);
							}
						}
		,	'#HEXA':	function(color) {
							var m = /^#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
									parseInt(m[1], 16) / 255,
									parseInt(m[2], 16) / 255,
									parseInt(m[3], 16) / 255,
									parseInt(m[4], 16) / 255
								);
							}
						}
		,	'#HEXA4':	function(color) {
							var m = /^#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
								   parseInt(String(m[1]) + m[1], 16) / 255,
								   parseInt(String(m[2]) + m[2], 16) / 255,
								   parseInt(String(m[3]) + m[3], 16) / 255,
								   parseInt(String(m[4]) + m[4], 16) / 255
								);
							}
						}
		,	'HEXA':		function(color) {
							var m = /^([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
									parseInt(m[1], 16) / 255,
									parseInt(m[2], 16) / 255,
									parseInt(m[3], 16) / 255,
									parseInt(m[4], 16) / 255
								);
							}
						}
		,	'HEXA4':	function(color) {
							var m = /^([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/.exec(color);
							if (m) {
								return new $.colorpicker.Color(
								   parseInt(String(m[1]) + m[1], 16) / 255,
								   parseInt(String(m[2]) + m[2], 16) / 255,
								   parseInt(String(m[3]) + m[3], 16) / 255,
								   parseInt(String(m[4]) + m[4], 16) / 255
								);
							}
						}
		};

		this.partslists = {
			'full':			['header', 'map', 'bar', 'hex', 'hsv', 'rgb', 'alpha', 'lab', 'cmyk', 'preview', 'swatches', 'footer'],
			'popup':		['map', 'bar', 'hex', 'hsv', 'rgb', 'alpha', 'preview', 'footer'],
			'draggable':	['header', 'map', 'bar', 'hex', 'hsv', 'rgb', 'alpha', 'preview', 'footer'],
			'inline':		['map', 'bar', 'hex', 'hsv', 'rgb', 'alpha', 'preview']
		};

		this.limits = {
			'websafe':		function(color) {
								color.limit(6);
							},
			'nibble':		function(color) {
								color.limit(16);
							},
			'binary':		function(color) {
								color.limit(2);
							},
			'name':			function(color, that) {
								var swatch = that._getSwatch(that._closestName(color));
								color.setRGB(swatch.r, swatch.g, swatch.b);
							}
		};

		this.parts = {
			header: function (inst) {
				var that	= this,
					part	= null,
					_html	= function() {
						var title = inst.options.title || inst._getRegional('title'),
							html = '<span class="ui-dialog-title">' + title + '</span>';

						if (!inst.inline && inst.options.showCloseButton) {
							html += '<a href="#" class="ui-dialog-titlebar-close ui-corner-all" role="button">'
								+ '<span class="ui-icon ui-icon-closethick">close</span></a>';
						}

						return '<div class="ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix">' + html + '</div>';
					},
					_onclick = function(event) {
						event.preventDefault();
						inst.close(inst.options.revert);
					};

				this.init = function() {
					part = $(_html()).prependTo(inst.dialog);

					var close = $('.ui-dialog-titlebar-close', part);
					inst._hoverable(close);
					inst._focusable(close);
					close.bind('click', _onclick);

					if (!inst.inline && inst.options.draggable) {
						var draggableOptions = {
							handle: part,
						}
						if (inst.options.containment) {
							draggableOptions.containment = inst.options.containment;
						}
						inst.dialog.draggable(draggableOptions);
					}
				};

				this.disable = function (disable) {
					$('.ui-dialog-titlebar-close', part)[disable ? 'unbind' : 'bind']('click', _onclick);
				};
			},

			map: function (inst) {
				var that	= this,
					part	= null,
					pointer, width, height, layers = {},
					_mousedown, _mouseup, _mousemove, _keydown, _html;

				_mousedown = function (event) {
					if (!inst.opened) {
						return;
					}

					var offset	= layers.p.offset(),
						x		= event.pageX - offset.left,
						y		= event.pageY - offset.top;

					if (x >= 0 && x < width && y >= 0 && y < height) {
						event.stopImmediatePropagation();
						event.preventDefault();
						part.unbind('mousedown', _mousedown).focus();
						$(document).bind('mouseup', _mouseup);
						$(document).bind('mousemove', _mousemove);
						_mousemove(event);
					}
				};

				_mouseup = function (event) {
					event.stopImmediatePropagation();
					event.preventDefault();
					$(document).unbind('mouseup', _mouseup);
					$(document).unbind('mousemove', _mousemove);
					part.bind('mousedown', _mousedown);
					
					inst._callback('stop');
				};

				_mousemove = function (event) {
					event.stopImmediatePropagation();
					event.preventDefault();

					if (event.pageX === that.x && event.pageY === that.y) {
						return;
					}
					that.x = event.pageX;
					that.y = event.pageY;

					var offset	= layers.p.offset(),
						x		= event.pageX - offset.left,
						y		= event.pageY - offset.top;

					x = Math.max(0, Math.min(x / width, 1));
					y = Math.max(0, Math.min(y / height, 1));

					// interpret values
					switch (inst.mode) {
						case 'h':
							inst.color.setHSV(null, x, 1 - y);
							break;

						case 's':
						case 'a':
							inst.color.setHSV(x, null, 1 - y);
							break;

						case 'v':
							inst.color.setHSV(x, 1 - y, null);
							break;

						case 'r':
							inst.color.setRGB(null, 1 - y, x);
							break;

						case 'g':
							inst.color.setRGB(1 - y, null, x);
							break;

						case 'b':
							inst.color.setRGB(x, 1 - y, null);
							break;
					}

					inst._change(false);
				};

				_keydown = function(event) {
					var x_channel_map = {
							'h': 's',
							's': 'h',
							'v': 'h',
							'r': 'b',
							'g': 'b',
							'b': 'r',
							'a': 'h'
						},
						x_change = {
							37: -1,
							39: 1,
						},
						y_channel_map = {
							'h': 'v',
							's': 'v',
							'v': 's',
							'r': 'g',
							'g': 'r',
							'b': 'g',
							'a': 'v'
						},
						y_change = {
							38: 1,
							40: -1
						},
						set = {
							35: 0,
							36: 1
						},
						change, value;

						if (typeof x_change[event.which] !== 'undefined') {
							value = inst.color.getChannel(x_channel_map[inst.mode]) * width;
							change = x_change[event.which];
							if (event.shiftKey) {
								change *= 10;
							} else if (event.ctrlKey) {
								change *= width;
							}
							inst.color.setChannel(x_channel_map[inst.mode], (value + change) / width);
							inst._change(false);
						} else if (typeof y_change[event.which] !== 'undefined') {
							value = inst.color.getChannel(y_channel_map[inst.mode]) * height;
							change = y_change[event.which];
							if (event.shiftKey) {
								change *= 10;
							} else if (event.ctrlKey) {
								change *= height;
							}
							inst.color.setChannel(y_channel_map[inst.mode], (value + change) / height);
							inst._change(false);
						} else if (typeof set[event.which] !== 'undefined') {
							inst.color.setChannel(x_channel_map[inst.mode], 1 - set[event.which]);
							inst.color.setChannel(y_channel_map[inst.mode], set[event.which]);
							inst._change(false);							
						}					
				};

				_html = function () {
					var html = '<div class="ui-colorpicker-map ui-colorpicker-map-'+(inst.options.part.map.size || 256)+' ui-colorpicker-border" tabindex="0">'
							+ '<span class="ui-colorpicker-map-layer-1">&nbsp;</span>'
							+ '<span class="ui-colorpicker-map-layer-2">&nbsp;</span>'
							+ (inst.options.alpha ? '<span class="ui-colorpicker-map-layer-alpha">&nbsp;</span>' : '')
							+ '<span class="ui-colorpicker-map-layer-pointer"><span class="ui-colorpicker-map-pointer"></span></span></div>';
					return html;
				};

				this.init = function () {
					part = $(_html()).appendTo($('.ui-colorpicker-map-container', inst.dialog));

					part.bind('mousedown', _mousedown);
					part.bind('keydown', _keydown);
					
					// cache					
					layers[1]	= $('.ui-colorpicker-map-layer-1', part);
					layers[2]	= $('.ui-colorpicker-map-layer-2', part);
					layers.a	= $('.ui-colorpicker-map-layer-alpha', part);
					layers.p	= $('.ui-colorpicker-map-layer-pointer', part);
					width		= layers.p.width();
					height		= layers.p.height();
					
					pointer		= $('.ui-colorpicker-map-pointer', part);
				};

				this.update = function () {
					var step = ((inst.options.part.map.size || 256) * 65 / 64);

					switch (inst.mode) {
						case 'h':
							layers[1].css({'background-position': '0 0', 'opacity': ''}).show();
							layers[2].hide();
							break;

						case 's':
						case 'a':
							layers[1].css({'background-position': '0 ' + (-step) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 2) + 'px', 'opacity': ''}).show();
							break;

						case 'v':
							part.css('background-color', 'black');
							layers[1].css({'background-position': '0 ' + (-step * 3) + 'px', 'opacity': ''}).show();
							layers[2].hide();
							break;

						case 'r':
							layers[1].css({'background-position': '0 ' + (-step * 4) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 5) + 'px', 'opacity': ''}).show();
							break;

						case 'g':
							layers[1].css({'background-position': '0 ' + (-step * 6) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 7) + 'px', 'opacity': ''}).show();
							break;

						case 'b':
							layers[1].css({'background-position': '0 ' + (-step * 8) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 9) + 'px', 'opacity': ''}).show();
							break;
					}
					
					that.repaint();
				};

				this.repaint = function () {
					var x = 0,
						y = 0;

					switch (inst.mode) {
						case 'h':
							var hsv = inst.color.getHSV();
							x = hsv.s * width;
							y = (1 - hsv.v) * width;
							part.css('background-color', inst.color.copy().setHSV(null, 1, 1).toCSS());
							break;

						case 's':
						case 'a':
							var hsv = inst.color.getHSV();
							x = hsv.h * width;
							y = (1 - hsv.v) * width;
							layers[2].css('opacity', 1 - hsv.s);
							break;

						case 'v':
							var hsv = inst.color.getHSV();
							x = hsv.h * width;
							y = (1 - hsv.s) * width;
							layers[1].css('opacity', hsv.v);
							break;

						case 'r':
							var rgb = inst.color.getRGB()
							x = rgb.b * width;
							y = (1 - rgb.g) * width;
							layers[2].css('opacity', rgb.r);
							break;

						case 'g':
							var rgb = inst.color.getRGB();
							x = rgb.b * width;
							y = (1 - rgb.r) * width;
							layers[2].css('opacity', rgb.g);
							break;

						case 'b':
							var rgb = inst.color.getRGB()
							x = rgb.r * width;
							y = (1 - rgb.g) * width;
							layers[2].css('opacity', rgb.b);
							break;
					}

					if (inst.options.alpha) {
						layers.a.css('opacity', 1 - inst.color.getAlpha());
					}

					pointer.css({
						'left': x - 7,
						'top': y - 7
					});
				};

				this.disable = function (disable) {
					part[disable ? 'unbind' : 'bind']('mousedown', _mousedown);
					part[disable ? 'unbind' : 'bind']('keydown', _keydown);
				};
			},

			bar: function (inst) {
				var that		= this,
					part		= null,
					pointer, width, height, layers = {},
					_mousedown, _mouseup, _mousemove, _keydown, _html;

				_mousedown = function (event) {
					if (!inst.opened) {
						return;
					}

					var offset	= layers.p.offset(),
						x		= event.pageX - offset.left,
						y		= event.pageY - offset.top;

					if (x >= 0 && x < width && y >= 0 && y < height) {
						event.stopImmediatePropagation();
						event.preventDefault();
						part.unbind('mousedown', _mousedown).focus();
						$(document).bind('mouseup', _mouseup);
						$(document).bind('mousemove', _mousemove);
						_mousemove(event);
					}
				};

				_mouseup = function (event) {
					event.stopImmediatePropagation();
					event.preventDefault();
					$(document).unbind('mouseup', _mouseup);
					$(document).unbind('mousemove', _mousemove);
					part.bind('mousedown', _mousedown);
					
					inst._callback('stop');					
				};

				_mousemove = function (event) {
					event.stopImmediatePropagation();
					event.preventDefault();

					if (event.pageY === that.y) {
						return;
					}
					that.y = event.pageY;

					var offset  = layers.p.offset(),
						y		= event.pageY - offset.top;

					y = Math.max(0, Math.min(y / height, 1));

					// interpret values
					switch (inst.mode) {
						case 'h':
							inst.color.setHSV(1 - y, null, null);
							break;

						case 's':
							inst.color.setHSV(null, 1 - y, null);
							break;

						case 'v':
							inst.color.setHSV(null, null, 1 - y);
							break;

						case 'r':
							inst.color.setRGB(1 - y, null, null);
							break;

						case 'g':
							inst.color.setRGB(null, 1 - y, null);
							break;

						case 'b':
							inst.color.setRGB(null, null, 1 - y);
							break;

						case 'a':
							inst.color.setAlpha(1 - y);
							break;
					}

					inst._change(false);
				};
				
				_keydown = function(event) {
					var change = {
							38: 1,
							40: -1,
							33: 10,
							34: -10
						},
						set = {
							35: 0,
							36: 1
						},
						change, value;

					if (typeof change[event.which] !== 'undefined') {
						value = inst.color.getChannel(inst.mode) * height;
						change = change[event.which];
						if (event.shiftKey) {
							change *= 10;
						} else if (event.ctrlKey) {
							change *= height;
						}
						inst.color.setChannel(inst.mode, (value + change) / height);
						inst._change(false);
					} else if (typeof set[event.which] !== 'undefined') {
						inst.color.setChannel(inst.mode, set[event.which]);
						inst._change(false);							
					}					
				};

				_html = function () {
					var html = '<div class="ui-colorpicker-bar ui-colorpicker-bar-'+(inst.options.part.bar.size || 256)+'  ui-colorpicker-border" tabindex="0">'
							+ '<span class="ui-colorpicker-bar-layer-1">&nbsp;</span>'
							+ '<span class="ui-colorpicker-bar-layer-2">&nbsp;</span>'
							+ '<span class="ui-colorpicker-bar-layer-3">&nbsp;</span>'
							+ '<span class="ui-colorpicker-bar-layer-4">&nbsp;</span>';

					if (inst.options.alpha) {
						html += '<span class="ui-colorpicker-bar-layer-alpha">&nbsp;</span>'
							+ '<span class="ui-colorpicker-bar-layer-alphabar">&nbsp;</span>';
					}

					html += '<span class="ui-colorpicker-bar-layer-pointer"><span class="ui-colorpicker-bar-pointer"></span></span></div>';

					return html;
				};

				this.init = function () {
					part = $(_html()).appendTo($('.ui-colorpicker-bar-container', inst.dialog));

					part.bind('mousedown', _mousedown);
					part.bind('keydown', _keydown);
					
					// cache				
					layers[1]	= $('.ui-colorpicker-bar-layer-1', part);
					layers[2]	= $('.ui-colorpicker-bar-layer-2', part);
					layers[3]	= $('.ui-colorpicker-bar-layer-3', part);
					layers[4]	= $('.ui-colorpicker-bar-layer-4', part);
					layers.a	= $('.ui-colorpicker-bar-layer-alpha', part);
					layers.ab	= $('.ui-colorpicker-bar-layer-alphabar', part);
					layers.p	= $('.ui-colorpicker-bar-layer-pointer', part);
					width		= layers.p.width();
					height		= layers.p.height();		
					
					pointer		= $('.ui-colorpicker-bar-pointer', part);
				};
				
				this.update = function () {
					var step = ((inst.options.part.bar.size || 256) * 65 / 64);

					switch (inst.mode) {
						case 'h':
						case 's':
						case 'v':
						case 'r':
						case 'g':
						case 'b':
							layers.a.show();
							layers.ab.hide();
							break;

						case 'a':
							layers.a.hide();
							layers.ab.show();
							break;
					}

					switch (inst.mode) {
						case 'h':
							layers[1].css({'background-position': '0 0', 'opacity': ''}).show();
							layers[2].hide();
							layers[3].hide();
							layers[4].hide();
							break;

						case 's':
							layers[1].css({'background-position': '0 ' + (-step) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 2) + 'px', 'opacity': ''}).show();
							layers[3].hide();
							layers[4].hide();
							break;

						case 'v':
							layers[1].css({'background-position': '0 ' + (-step * 2) + 'px', 'opacity': ''}).show();
							layers[2].hide();
							layers[3].hide();
							layers[4].hide();
							break;

						case 'r':
							layers[1].css({'background-position': '0 ' + (-step * 6) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 5) + 'px', 'opacity': ''}).show();
							layers[3].css({'background-position': '0 ' + (-step * 3) + 'px', 'opacity': ''}).show();
							layers[4].css({'background-position': '0 ' + (-step * 4) + 'px', 'opacity': ''}).show();
							break;

						case 'g':
							layers[1].css({'background-position': '0 ' + (-step * 10) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 9) + 'px', 'opacity': ''}).show();
							layers[3].css({'background-position': '0 ' + (-step * 7) + 'px', 'opacity': ''}).show();
							layers[4].css({'background-position': '0 ' + (-step * 8) + 'px', 'opacity': ''}).show();
							break;

						case 'b':
							layers[1].css({'background-position': '0 ' + (-step * 14) + 'px', 'opacity': ''}).show();
							layers[2].css({'background-position': '0 ' + (-step * 13) + 'px', 'opacity': ''}).show();
							layers[3].css({'background-position': '0 ' + (-step * 11) + 'px', 'opacity': ''}).show();
							layers[4].css({'background-position': '0 ' + (-step * 12) + 'px', 'opacity': ''}).show();
							break;

						case 'a':
							layers[1].hide();
							layers[2].hide();
							layers[3].hide();
							layers[4].hide();
							break;
					}
					
					that.repaint();
				};

				this.repaint = function () {
					var y = 0;

					switch (inst.mode) {
						case 'h':
							y = (1 - inst.color.getHSV().h) * height;
							break;

						case 's':
							var hsv = inst.color.getHSV();
							y = (1 - hsv.s) * height;
							layers[2].css('opacity', 1 - hsv.v);
							part.css('background-color', inst.color.copy().setHSV(null, 1, null).toCSS());
							break;

						case 'v':
							y = (1 - inst.color.getHSV().v) * height;
							part.css('background-color', inst.color.copy().setHSV(null, null, 1).toCSS());
							break;

						case 'r':
							var rgb = inst.color.getRGB();
							y = (1 - rgb.r) * height;
							layers[2].css('opacity', Math.max(0, (rgb.b - rgb.g)));
							layers[3].css('opacity', Math.max(0, (rgb.g - rgb.b)));
							layers[4].css('opacity', Math.min(rgb.b, rgb.g));
							break;

						case 'g':
							var rgb = inst.color.getRGB();
							y = (1 - rgb.g) * height;
							layers[2].css('opacity', Math.max(0, (rgb.b - rgb.r)));
							layers[3].css('opacity', Math.max(0, (rgb.r - rgb.b)));
							layers[4].css('opacity', Math.min(rgb.r, rgb.b));
							break;

						case 'b':
							var rgb = inst.color.getRGB();
							y = (1 - rgb.b) * height;
							layers[2].css('opacity', Math.max(0, (rgb.r - rgb.g)));
							layers[3].css('opacity', Math.max(0, (rgb.g - rgb.r)));
							layers[4].css('opacity', Math.min(rgb.r, rgb.g));
							break;

						case 'a':
							y = (1 - inst.color.getAlpha()) * height;
							part.css('background-color', inst.color.copy().toCSS());
							break;
					}

					if (inst.mode !== 'a') {
						layers.a.css('opacity', 1 - inst.color.getAlpha());
					}

					pointer.css('top', y - 3);
				};

				this.disable = function (disable) {
					part[disable ? 'unbind' : 'bind']('mousedown', _mousedown);
					part[disable ? 'unbind' : 'bind']('keydown', _keydown);
				};
			},

			preview: function (inst) {
				var that = this,
					part = null,
					both,
					initial, initial_alpha,
					current, current_alpha,
					_html,
					onclick = function () {
						inst.color = inst.currentColor.copy();
						inst._change();
					};

				_html = function () {
					return '<div class="ui-colorpicker-preview ui-colorpicker-border">'
						+ '<div class="ui-colorpicker-preview-initial"><div class="ui-colorpicker-preview-initial-alpha"></div></div>'
						+ '<div class="ui-colorpicker-preview-current"><div class="ui-colorpicker-preview-current-alpha"></div></div>'
						+ '</div>';
				};

				this.init = function () {
					part = $(_html()).appendTo($('.ui-colorpicker-preview-container', inst.dialog));

					$('.ui-colorpicker-preview-initial', part).bind('click', onclick);

					// cache
					initial			= $('.ui-colorpicker-preview-initial', part);
					initial_alpha	= $('.ui-colorpicker-preview-initial-alpha', part);
					current			= $('.ui-colorpicker-preview-current', part);
					current_alpha	= $('.ui-colorpicker-preview-current-alpha', part);
					both			= $('.ui-colorpicker-preview-initial-alpha, .ui-colorpicker-preview-current-alpha', part);
				};

				this.update = function () {
					both[inst.options.alpha ? 'show' : 'hide']();

					this.repaint();
				};

				this.repaint = function () {
					initial.css('background-color', inst.currentColor.set ? inst.currentColor.toCSS() : '').attr('title', inst.currentColor.set ? inst.currentColor.toCSS() : '');
					initial_alpha.css('opacity', 1 - inst.currentColor.getAlpha());
					current.css('background-color', inst.color.set ? inst.color.toCSS() : '').attr('title', inst.color.set ? inst.color.toCSS() : '');
					current_alpha.css('opacity', 1 - inst.color.getAlpha());
				};

				this.disable = function (disable) {
					$('.ui-colorpicker-preview-initial', part)[disable ? 'unbind' : 'bind']('click', onclick);
				};
			},

			hsv: function (inst) {
				var that = this,
					part = null,
					inputs = {},
					_html;

				_html = function () {
					var html = '';

					if (inst.options.hsv) {
						html +=	'<div class="ui-colorpicker-hsv-h"><input class="ui-colorpicker-mode" type="radio" value="h"/><label>' + inst._getRegional('hsvH') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="360" size="10"/><span class="ui-colorpicker-unit">&deg;</span></div>'
							+ '<div class="ui-colorpicker-hsv-s"><input class="ui-colorpicker-mode" type="radio" value="s"/><label>' + inst._getRegional('hsvS') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100" size="10"/><span class="ui-colorpicker-unit">%</span></div>'
							+ '<div class="ui-colorpicker-hsv-v"><input class="ui-colorpicker-mode" type="radio" value="v"/><label>' + inst._getRegional('hsvV') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100" size="10"/><span class="ui-colorpicker-unit">%</span></div>';
					}

					return '<div class="ui-colorpicker-hsv">' + html + '</div>';
				};

				this.init = function () {
					part = $(_html()).appendTo($('.ui-colorpicker-hsv-container', inst.dialog));

					$('.ui-colorpicker-mode', part).click(function () {
						inst.mode = $(this).val();
						inst._updateAllParts();
					});
					
					inputs.h = $('.ui-colorpicker-hsv-h .ui-colorpicker-number', part);
					inputs.s = $('.ui-colorpicker-hsv-s .ui-colorpicker-number', part);
					inputs.v = $('.ui-colorpicker-hsv-v .ui-colorpicker-number', part);

					$('.ui-colorpicker-number', part).bind('change keyup', function () {
						inst.color.setHSV(
							inputs.h.val() / 360,
							inputs.s.val() / 100,
							inputs.v.val() / 100
						);
						inst._change();
					});
				};

				this.repaint = function () {
					var hsv = inst.color.getHSV();
					inputs.h.val(Math.round(hsv.h * 360));
					inputs.s.val(Math.round(hsv.s * 100));
					inputs.v.val(Math.round(hsv.v * 100));
				};

				this.update = function () {
					$('.ui-colorpicker-mode', part).each(function () {
						var $this = $(this);
						$this.attr('checked', $this.val() === inst.mode);
					});
					this.repaint();
				};

				this.disable = function (disable) {
					$(':input', part).prop('disabled', disable);
				};
			},

			rgb: function (inst) {
				var that = this,
					part = null,
					inputs = {},
					_html;

				_html = function () {
					var html = '';

					if (inst.options.rgb) {
						html += '<div class="ui-colorpicker-rgb-r"><input class="ui-colorpicker-mode" type="radio" value="r"/><label>' + inst._getRegional('rgbR') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="255"/></div>'
							+ '<div class="ui-colorpicker-rgb-g"><input class="ui-colorpicker-mode" type="radio" value="g"/><label>' + inst._getRegional('rgbG') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="255"/></div>'
							+ '<div class="ui-colorpicker-rgb-b"><input class="ui-colorpicker-mode" type="radio" value="b"/><label>' + inst._getRegional('rgbB') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="255"/></div>';
					}

					return '<div class="ui-colorpicker-rgb">' + html + '</div>';
				};

				this.init = function () {
					part = $(_html()).appendTo($('.ui-colorpicker-rgb-container', inst.dialog));

					$('.ui-colorpicker-mode', part).click(function () {
						inst.mode = $(this).val();
						inst._updateAllParts();
					});
					
					inputs.r = $('.ui-colorpicker-rgb-r .ui-colorpicker-number', part);
					inputs.g = $('.ui-colorpicker-rgb-g .ui-colorpicker-number', part);
					inputs.b = $('.ui-colorpicker-rgb-b .ui-colorpicker-number', part);

					$('.ui-colorpicker-number', part).bind('change keyup', function () {
						var r = $('.ui-colorpicker-rgb-r .ui-colorpicker-number', part).val();
						inst.color.setRGB(
							inputs.r.val() / 255,
							inputs.g.val() / 255,
							inputs.b.val() / 255
						);

						inst._change();
					});
				};

				this.repaint = function () {
					var rgb = inst.color.getRGB();
					inputs.r.val(Math.round(rgb.r * 255));
					inputs.g.val(Math.round(rgb.g * 255));
					inputs.b.val(Math.round(rgb.b * 255));
				};

				this.update = function () {
					$('.ui-colorpicker-mode', part).each(function () {
						var $this = $(this);
						$this.attr('checked', $this.val() === inst.mode);
					});
					this.repaint();
				};

				this.disable = function (disable) {
					$(':input', part).prop('disabled', disable);
				};
			},

			lab: function (inst) {
				var that = this,
					part = null,
					inputs = {},
					html = function () {
						var html = '';

						if (inst.options.hsv) {
							html +=	'<div class="ui-colorpicker-lab-l"><label>' + inst._getRegional('labL') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100"/></div>'
								+ '<div class="ui-colorpicker-lab-a"><label>' + inst._getRegional('labA') + '</label><input class="ui-colorpicker-number" type="number" min="-128" max="127"/></div>'
								+ '<div class="ui-colorpicker-lab-b"><label>' + inst._getRegional('labB') + '</label><input class="ui-colorpicker-number" type="number" min="-128" max="127"/></div>';
						}

						return '<div class="ui-colorpicker-lab">' + html + '</div>';
					};

				this.init = function () {
					var data = 0;

					part = $(html()).appendTo($('.ui-colorpicker-lab-container', inst.dialog));
					
					inputs.l = $('.ui-colorpicker-lab-l .ui-colorpicker-number', part);
					inputs.a = $('.ui-colorpicker-lab-a .ui-colorpicker-number', part);
					inputs.b = $('.ui-colorpicker-lab-b .ui-colorpicker-number', part);

					$('.ui-colorpicker-number', part).bind('change keyup', function (event) {
						inst.color.setLAB(
							parseInt(inputs.l.val(), 10) / 100,
							(parseInt(inputs.a.val(), 10) + 128) / 255,
							(parseInt(inputs.b.val(), 10) + 128) / 255
						);
						inst._change();
					});
				};

				this.repaint = function () {
					var lab = inst.color.getLAB();
					inputs.l.val(Math.round(lab.l * 100));
					inputs.a.val(Math.round(lab.a * 255) - 128);
					inputs.b.val(Math.round(lab.b * 255) - 128);
				};

				this.update = this.repaint;
				
				this.disable = function (disable) {
					$(':input', part).prop('disabled', disable);
				};
			},

			cmyk: function (inst) {
				var that = this,
					part = null,
					inputs = {},
					html = function () {
						var html = '';

						if (inst.options.hsv) {
							html +=	'<div class="ui-colorpicker-cmyk-c"><label>' + inst._getRegional('cmykC') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100"/><span class="ui-colorpicker-unit">%</span></div>'
								+ '<div class="ui-colorpicker-cmyk-m"><label>' + inst._getRegional('cmykM') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100"/><span class="ui-colorpicker-unit">%</span></div>'
								+ '<div class="ui-colorpicker-cmyk-y"><label>' + inst._getRegional('cmykY') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100"/><span class="ui-colorpicker-unit">%</span></div>'
								+ '<div class="ui-colorpicker-cmyk-k"><label>' + inst._getRegional('cmykK') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100"/><span class="ui-colorpicker-unit">%</span></div>';
						}

						return '<div class="ui-colorpicker-cmyk">' + html + '</div>';
					};

				this.init = function () {
					part = $(html()).appendTo($('.ui-colorpicker-cmyk-container', inst.dialog));
					
					inputs.c = $('.ui-colorpicker-cmyk-c .ui-colorpicker-number', part);
					inputs.m = $('.ui-colorpicker-cmyk-m .ui-colorpicker-number', part);
					inputs.y = $('.ui-colorpicker-cmyk-y .ui-colorpicker-number', part);
					inputs.k = $('.ui-colorpicker-cmyk-k .ui-colorpicker-number', part);
					
					$('.ui-colorpicker-number', part).bind('change keyup', function (event) {
						inst.color.setCMYK(
							parseInt(inputs.c.val(), 10) / 100,
							parseInt(inputs.m.val(), 10) / 100,
							parseInt(inputs.y.val(), 10) / 100,
							parseInt(inputs.k.val(), 10) / 100
						);
						inst._change();
					});
				};

				this.repaint = function () {
					var cmyk = inst.color.getCMYK();
					inputs.c.val(Math.round(cmyk.c * 100));
					inputs.m.val(Math.round(cmyk.m * 100));
					inputs.y.val(Math.round(cmyk.y * 100));
					inputs.k.val(Math.round(cmyk.k * 100));
				};

				this.update = this.repaint;

				this.disable = function (disable) {
					$(':input', part).prop('disabled', disable);
				};
			},

			alpha: function (inst) {
				var that = this,
					part = null,
					input,
					html = function () {
						var html = '';

						if (inst.options.alpha) {
							html += '<div class="ui-colorpicker-a"><input class="ui-colorpicker-mode" name="mode" type="radio" value="a"/><label>' + inst._getRegional('alphaA') + '</label><input class="ui-colorpicker-number" type="number" min="0" max="100"/><span class="ui-colorpicker-unit">%</span></div>';
						}

						return '<div class="ui-colorpicker-alpha">' + html + '</div>';
					};

				this.init = function () {
					part = $(html()).appendTo($('.ui-colorpicker-alpha-container', inst.dialog));

					$('.ui-colorpicker-mode', part).click(function () {
						inst.mode = $(this).val();
						inst._updateAllParts();
					});
					
					input = $('.ui-colorpicker-a .ui-colorpicker-number', part);

					$('.ui-colorpicker-number', part).bind('change keyup', function () {
						inst.color.setAlpha(input.val() / 100);
						inst._change();
					});
				};

				this.update = function () {
					$('.ui-colorpicker-mode', part).each(function () {
						$(this).attr('checked', $(this).val() === inst.mode);
					});
					this.repaint();
				};

				this.repaint = function () {
					input.val(Math.round(inst.color.getAlpha() * 100));
				};
				
				this.disable = function (disable) {
					$(':input', part).prop('disabled', disable);
				};
			},

			hex: function (inst) {
				var that = this,
					part = null,
					inputs = {},
					parseHex = function(color) {
						var c,
							m;

						// {#}rgb
						m = /^#?([a-fA-F0-9]{1,3})$/.exec(color);
						if (m) {
							c = parseInt(m[1], 16);
							return new $.colorpicker.Color(
								((c >> 8) & 0xF) / 15,
								((c >> 4) & 0xF) / 15,
								(c & 0xF) / 15
							);
						}

						// {#}rrggbb
						m = /^#?([a-fA-F0-9]{1,6})$/.exec(color);
						if (m) {
							c = parseInt(m[1], 16);
							return new $.colorpicker.Color(
								((c >> 16) & 0xFF) / 255,
								((c >>  8) & 0xFF) / 255,
								(c & 0xFF) / 255
							);
						}
						
						return new $.colorpicker.Color();
					},
					html = function () {
						var html = '';

						if (inst.options.alpha) {
							html += '<input class="ui-colorpicker-hex-alpha" type="text" maxlength="2" size="2"/>';
						}

						html += '<input class="ui-colorpicker-hex-input" type="text" maxlength="6" size="6"/>';

						return '<div class="ui-colorpicker-hex"><label>#</label>' + html + '</div>';
					};

				this.init = function () {
					part = $(html()).appendTo($('.ui-colorpicker-hex-container', inst.dialog));

					inputs.color = $('.ui-colorpicker-hex-input', part);
					inputs.alpha = $('.ui-colorpicker-hex-alpha', part);

					inputs.color.bind('keydown keyup', function(e) {	
						return e.ctrlKey || _keycode.isHex(e.which) || !_keycode.isPrint(e.which);
					});

					// repeat here makes the invalid input disappear faster
					inputs.color.bind('change', function () {
						if (/[^a-fA-F0-9]/.test(inputs.color.val())) {
							inputs.color.val(inputs.color.val().replace(/[^a-fA-F0-9]/, ''));
						}
					});

					inputs.color.bind('change keyup', function () {
						// repeat here makes sure that the invalid input doesn't get parsed
						inst.color = parseHex(inputs.color.val()).setAlpha(inst.color.getAlpha());
						inst._change();
					});

					inputs.alpha.bind('keydown keyup', function(e) {
						return e.ctrlKey || _keycode.isHex(e.which) || !_keycode.isPrint(e.which);
					});
					
					inputs.alpha.bind('change', function () {
						if (/[^a-fA-F0-9]/.test(inputs.alpha)) {
							inputs.alpha.val(inputs.alpha.val().replace(/[^a-fA-F0-9]/, ''));
						}
					});

					inputs.alpha.bind('change keyup', function () {
						inst.color.setAlpha(parseInt(inputs.alpha.val(), 16) / 255);
						inst._change();
					});
				};

				this.repaint = function () {
					if (!inputs.color.is(':focus')) {
						inputs.color.val(inst.color.toHex(true));
					}

					if (!inputs.alpha.is(':focus')) {
						inputs.alpha.val(_intToHex(inst.color.getAlpha() * 255));
					}
				};

				this.update = this.repaint;

				this.disable = function (disable) {
					$(':input', part).prop('disabled', disable);
				};
			},

			swatches: function (inst) {
				var that = this,
					part = null,
					html = function () {
						var html = '';

						inst._eachSwatch(function (name, color) {
							var c = new $.colorpicker.Color(color.r, color.g, color.b),
								css = c.toCSS();
							html += '<div class="ui-colorpicker-swatch" style="background-color:' + css + '" title="' + name + '"></div>';
						});

						return '<div class="ui-colorpicker-swatches ui-colorpicker-border" style="width:' + inst.options.swatchesWidth + 'px">' + html + '</div>';
					},
					onclick = function () {
						inst.color	= inst._parseColor($(this).css('background-color')) || new $.colorpicker.Color();
						inst._change();
					};

				this.init = function () {
					part = $(html());
					$('.ui-colorpicker-swatches-container', inst.dialog).html(part);
					$('.ui-colorpicker-swatch', part).bind('click', onclick);
				};

				this.disable = function (disable) {
					$('.ui-colorpicker-swatch', part)[disable ? 'unbind' : 'bind']('click', onclick);
				};
			},

			footer: function (inst) {
				var that = this,
					part = null,
					id_transparent = 'ui-colorpicker-special-transparent-' + inst.colorpicker_index,
					id_none = 'ui-colorpicker-special-none-' + inst.colorpicker_index,
					html = function () {
						var html = '';

						if (inst.options.alpha || (!inst.inline && inst.options.showNoneButton)) {
							html += '<div class="ui-colorpicker-buttonset">';

							if (inst.options.alpha) {
								html += '<input type="radio" name="ui-colorpicker-special" id="'+id_transparent+'" class="ui-colorpicker-special-transparent"/><label for="'+id_transparent+'">' + inst._getRegional('transparent') + '</label>';
							}
							if (!inst.inline && inst.options.showNoneButton) {
								html += '<input type="radio" name="ui-colorpicker-special" id="'+id_none+'" class="ui-colorpicker-special-none"><label for="'+id_none+'">' + inst._getRegional('none') + '</label>';
							}
							html += '</div>';
						}

						if (!inst.inline) {
							html += '<div class="ui-dialog-buttonset">';
							if (inst.options.showCancelButton) {
								html += '<button class="btn btn-custom btn-custom-secondary ui-colorpicker-cancel">' + inst._getRegional('cancel') + '</button>';
							}
							html += '<button class="btn btn-custom btn-custom-secondary ui-colorpicker-ok">' + inst._getRegional('ok') + '</button>';
							html += '</div>';
						}

						return '<div class="ui-dialog-buttonpane ui-widget-content">' + html + '</div>';
					};

				this.init = function () {
					part = $(html()).appendTo(inst.dialog);

					$('.ui-colorpicker-ok', part).button().click(function () {
						inst.close();
					});

					$('.ui-colorpicker-cancel', part).button().click(function () {
						inst.close(true);   //cancel
					});

					$('.ui-colorpicker-buttonset', part)[$.fn.controlgroup ? 'controlgroup' : 'buttonset']();

					$('.ui-colorpicker-special-color', part).click(function () {
						inst._change();
					});

					$('#'+id_none, part).click(function () {
						inst.color.set = false;
						inst._change();
					});

					$('#'+id_transparent, part).click(function () {
						inst.color.setAlpha(0);
						inst._change();
					});
				};

				this.repaint = function () {
					$('.ui-colorpicker-special-none', part).prop('checked', !inst.color.set).button('refresh');
					$('.ui-colorpicker-special-transparent', part).prop('checked', inst.color.set && inst.color.getAlpha() === 0).button('refresh');
					$('.ui-colorpicker-ok', part).button(inst.changed ? 'enable' : 'disable');
				};

				this.update = function () {};

				this.disable = function (disabled) {
					$(':input, :button', part).button(disabled ? 'disable' : 'enable');
					if (!disabled) {
						$('.ui-colorpicker-ok', part).button(inst.changed ? 'enable' : 'disable');
					}
				};
			}
		};

		this.Color = function () {
			var spaces = {	rgb:	{r: 0, g: 0, b: 0},
							hsv:	{h: 0, s: 0, v: 0},
							hsl:	{h: 0, s: 0, l: 0},
							lab:	{l: 0, a: 0, b: 0},
							cmyk:	{c: 0, m: 0, y: 0, k: 1}
						},
				a = 1,
				illuminant = [0.9504285, 1, 1.0889],	// CIE-L*ab D65/2' 1931
				args = arguments,
				_clip = function(v) {
					if (isNaN(v) || v === null) {
						return 0;
					}
					if (typeof v == 'string') {
						v = parseInt(v, 10);
					}
					return Math.max(0, Math.min(v, 1));
				},
				_hexify = function (number) {
					var number = Math.round(number),
						digits = '0123456789abcdef',
						lsd = number % 16,
						msd = (number - lsd) / 16,
						hexified = digits.charAt(msd) + digits.charAt(lsd);
					return hexified;
				},
				_rgb_to_xyz = function(rgb) {
					var r = (rgb.r > 0.04045) ? Math.pow((rgb.r + 0.055) / 1.055, 2.4) : rgb.r / 12.92,
						g = (rgb.g > 0.04045) ? Math.pow((rgb.g + 0.055) / 1.055, 2.4) : rgb.g / 12.92,
						b = (rgb.b > 0.04045) ? Math.pow((rgb.b + 0.055) / 1.055, 2.4) : rgb.b / 12.92;

					return {
						x: r * 0.4124 + g * 0.3576 + b * 0.1805,
						y: r * 0.2126 + g * 0.7152 + b * 0.0722,
						z: r * 0.0193 + g * 0.1192 + b * 0.9505
					};
				},
				_xyz_to_rgb = function(xyz) {
					var rgb = {
						r: xyz.x *  3.2406 + xyz.y * -1.5372 + xyz.z * -0.4986,
						g: xyz.x * -0.9689 + xyz.y *  1.8758 + xyz.z *  0.0415,
						b: xyz.x *  0.0557 + xyz.y * -0.2040 + xyz.z *  1.0570
					};

					rgb.r = (rgb.r > 0.0031308) ? 1.055 * Math.pow(rgb.r, (1 / 2.4)) - 0.055 : 12.92 * rgb.r;
					rgb.g = (rgb.g > 0.0031308) ? 1.055 * Math.pow(rgb.g, (1 / 2.4)) - 0.055 : 12.92 * rgb.g;
					rgb.b = (rgb.b > 0.0031308) ? 1.055 * Math.pow(rgb.b, (1 / 2.4)) - 0.055 : 12.92 * rgb.b;

					return rgb;
				},
				_rgb_to_hsv = function(rgb) {
					var minVal = Math.min(rgb.r, rgb.g, rgb.b),
						maxVal = Math.max(rgb.r, rgb.g, rgb.b),
						delta = maxVal - minVal,
						del_R, del_G, del_B,
						hsv = {
							h: 0,
							s: 0,
							v: maxVal
						};

					if (delta === 0) {
						hsv.h = 0;
						hsv.s = 0;
					} else {
						hsv.s = delta / maxVal;

						del_R = (((maxVal - rgb.r) / 6) + (delta / 2)) / delta;
						del_G = (((maxVal - rgb.g) / 6) + (delta / 2)) / delta;
						del_B = (((maxVal - rgb.b) / 6) + (delta / 2)) / delta;

						if (rgb.r === maxVal) {
							hsv.h = del_B - del_G;
						} else if (rgb.g === maxVal) {
							hsv.h = (1 / 3) + del_R - del_B;
						} else if (rgb.b === maxVal) {
							hsv.h = (2 / 3) + del_G - del_R;
						}

						if (hsv.h < 0) {
							hsv.h += 1;
						} else if (hsv.h > 1) {
							hsv.h -= 1;
						}
					}

					return hsv;
				},
				_hsv_to_rgb = function(hsv) {
					var rgb = {
							r: 0,
							g: 0,
							b: 0
						},
						var_h,
						var_i,
						var_1,
						var_2,
						var_3;

					if (hsv.s === 0) {
						rgb.r = rgb.g = rgb.b = hsv.v;
					} else {
						var_h = hsv.h === 1 ? 0 : hsv.h * 6;
						var_i = Math.floor(var_h);
						var_1 = hsv.v * (1 - hsv.s);
						var_2 = hsv.v * (1 - hsv.s * (var_h - var_i));
						var_3 = hsv.v * (1 - hsv.s * (1 - (var_h - var_i)));

						if (var_i === 0) {
							rgb.r = hsv.v;
							rgb.g = var_3;
							rgb.b = var_1;
						} else if (var_i === 1) {
							rgb.r = var_2;
							rgb.g = hsv.v;
							rgb.b = var_1;
						} else if (var_i === 2) {
							rgb.r = var_1;
							rgb.g = hsv.v;
							rgb.b = var_3;
						} else if (var_i === 3) {
							rgb.r = var_1;
							rgb.g = var_2;
							rgb.b = hsv.v;
						} else if (var_i === 4) {
							rgb.r = var_3;
							rgb.g = var_1;
							rgb.b = hsv.v;
						} else {
							rgb.r = hsv.v;
							rgb.g = var_1;
							rgb.b = var_2;
						}
					}

					return rgb;
				},
				_rgb_to_hsl = function(rgb) {
					var minVal = Math.min(rgb.r, rgb.g, rgb.b),
						maxVal = Math.max(rgb.r, rgb.g, rgb.b),
						delta = maxVal - minVal,
						del_R, del_G, del_B,
						hsl = {
							h: 0,
							s: 0,
							l: (maxVal + minVal) / 2
						};

					if (delta === 0) {
						hsl.h = 0;
						hsl.s = 0;
					} else {
						hsl.s = hsl.l < 0.5 ? delta / (maxVal + minVal) : delta / (2 - maxVal - minVal);

						del_R = (((maxVal - rgb.r) / 6) + (delta / 2)) / delta;
						del_G = (((maxVal - rgb.g) / 6) + (delta / 2)) / delta;
						del_B = (((maxVal - rgb.b) / 6) + (delta / 2)) / delta;

						if (rgb.r === maxVal) {
							hsl.h = del_B - del_G;
						} else if (rgb.g === maxVal) {
							hsl.h = (1 / 3) + del_R - del_B;
						} else if (rgb.b === maxVal) {
							hsl.h = (2 / 3) + del_G - del_R;
						}

						if (hsl.h < 0) {
							hsl.h += 1;
						} else if (hsl.h > 1) {
							hsl.h -= 1;
						}
					}

					return hsl;
				},
				_hsl_to_rgb = function(hsl) {
					var var_1,
						var_2,
						hue_to_rgb	= function(v1, v2, vH) {
										if (vH < 0) {
											vH += 1;
										}
										if (vH > 1) {
											vH -= 1;
										}
										if ((6 * vH) < 1) {
											return v1 + (v2 - v1) * 6 * vH;
										}
										if ((2 * vH) < 1) {
											return v2;
										}
										if ((3 * vH) < 2) {
											return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
										}
										return v1;
									};

					if (hsl.s === 0) {
						return {
							r: hsl.l,
							g: hsl.l,
							b: hsl.l
						};
					}

					var_2 = (hsl.l < 0.5) ? hsl.l * (1 + hsl.s) : (hsl.l + hsl.s) - (hsl.s * hsl.l);
					var_1 = 2 * hsl.l - var_2;

					return {
						r: hue_to_rgb(var_1, var_2, hsl.h + (1 / 3)),
						g: hue_to_rgb(var_1, var_2, hsl.h),
						b: hue_to_rgb(var_1, var_2, hsl.h - (1 / 3))
					};
				},
				_xyz_to_lab = function(xyz) {
					var x = xyz.x / illuminant[0],
						y = xyz.y / illuminant[1],
						z = xyz.z / illuminant[2];

					x = (x > 0.008856) ? Math.pow(x, (1/3)) : (7.787 * x) + (16/116);
					y = (y > 0.008856) ? Math.pow(y, (1/3)) : (7.787 * y) + (16/116);
					z = (z > 0.008856) ? Math.pow(z, (1/3)) : (7.787 * z) + (16/116);

					return {
						l: ((116 * y) - 16) / 100,	// [0,100]
						a: ((500 * (x - y)) + 128) / 255,	// [-128,127]
						b: ((200 * (y - z))	+ 128) / 255	// [-128,127]
					};
				},
				_lab_to_xyz = function(lab) {
					var lab2 = {
							l: lab.l * 100,
							a: (lab.a * 255) - 128,
							b: (lab.b * 255) - 128
						},
						xyz = {
							x: 0,
							y: (lab2.l + 16) / 116,
							z: 0
						};

					xyz.x = lab2.a / 500 + xyz.y;
					xyz.z = xyz.y - lab2.b / 200;

					xyz.x = (Math.pow(xyz.x, 3) > 0.008856) ? Math.pow(xyz.x, 3) : (xyz.x - 16 / 116) / 7.787;
					xyz.y = (Math.pow(xyz.y, 3) > 0.008856) ? Math.pow(xyz.y, 3) : (xyz.y - 16 / 116) / 7.787;
					xyz.z = (Math.pow(xyz.z, 3) > 0.008856) ? Math.pow(xyz.z, 3) : (xyz.z - 16 / 116) / 7.787;

					xyz.x *= illuminant[0];
					xyz.y *= illuminant[1];
					xyz.z *= illuminant[2];

					return xyz;
				},
				_rgb_to_cmy = function(rgb) {
					return {
						c: 1 - (rgb.r),
						m: 1 - (rgb.g),
						y: 1 - (rgb.b)
					};
				},
				_cmy_to_rgb = function(cmy) {
					return {
						r: 1 - (cmy.c),
						g: 1 - (cmy.m),
						b: 1 - (cmy.y)
					};
				},
				_cmy_to_cmyk = function(cmy) {
					var K = 1;

					if (cmy.c < K) {
						K = cmy.c;
					}
					if (cmy.m < K) {
						K = cmy.m;
					}
					if (cmy.y < K) {
						K = cmy.y;
					}

					if (K === 1) {
						return {
							c: 0,
							m: 0,
							y: 0,
							k: 1
						};
					}

					return {
						c: (cmy.c - K) / (1 - K),
						m: (cmy.m - K) / (1 - K),
						y: (cmy.y - K) / (1 - K),
						k: K
					};
				},
				_cmyk_to_cmy = function(cmyk) {
					return {
						c: cmyk.c * (1 - cmyk.k) + cmyk.k,
						m: cmyk.m * (1 - cmyk.k) + cmyk.k,
						y: cmyk.y * (1 - cmyk.k) + cmyk.k
					};
				};

			this.set = false;

			this.setAlpha = function(_a) {
				if (_a !== null) {
					a = _clip(_a);
				}
				this.set = true;

				return this;
			};

			this.getAlpha = function() {
				return a;
			};

			this.setRGB = function(r, g, b) {
				spaces = { rgb: this.getRGB() };
				if (r !== null) {
					spaces.rgb.r = _clip(r);
				}
				if (g !== null) {
					spaces.rgb.g = _clip(g);
				}
				if (b !== null) {
					spaces.rgb.b = _clip(b);
				}
				this.set = true;
				
				return this;
			};
			
			this.getChannel = function(channel) {
				switch (channel) {
					case 'h':
					case 's':
					case 'v':
						return this.getHSV()[channel];

					case 'r':
					case 'g':
					case 'b':
						return this.getRGB()[channel];

					case 'a':
						return this.getAlpha();
				}			
				
				return null;
			};
			
			this.setChannel = function(channel, value) {
				switch (channel) {
					case 'h':
						return this.setHSV(value, null, null);
						
					case 's':
						return this.setHSV(null, value, null);
						
					case 'v':
						return this.setHSV(null, null, value);

					case 'r':
						return this.setRGB(value, null, null);
						
					case 'g':
						return this.setRGB(null, value, null);
						
					case 'b':
						return this.setRGB(null, null, value);

					case 'a':
						return this.setAlpha(value);
				}
				
				return this;
			};

			this.setHSV = function(h, s, v) {
				spaces = {hsv: this.getHSV()};
				if (h !== null) {
					spaces.hsv.h = _clip(h);
				}
				if (s !== null)	{
					spaces.hsv.s = _clip(s);
				}
				if (v !== null)	{
					spaces.hsv.v = _clip(v);
				}
				this.set = true;

				return this;
			};

			this.setHSL = function(h, s, l) {
				spaces = {hsl: this.getHSL()};
				if (h !== null)	{
					spaces.hsl.h = _clip(h);
				}
				if (s !== null) {
					spaces.hsl.s = _clip(s);
				}
				if (l !== null) {
					spaces.hsl.l = _clip(l);
				}
				this.set = true;

				return this;
			};

			this.setLAB = function(l, a, b) {
				spaces = {lab: this.getLAB()};
				if (l !== null) {
					spaces.lab.l = _clip(l);
				}
				if (a !== null) {
					spaces.lab.a = _clip(a);
				}
				if (b !== null) {
					spaces.lab.b = _clip(b);
				}
				this.set = true;

				return this;
			};

			this.setCMYK = function(c, m, y, k) {
				spaces = {cmyk: this.getCMYK()};
				if (c !== null) {
					spaces.cmyk.c = _clip(c);
				}
				if (m !== null) {
					spaces.cmyk.m = _clip(m);
				}
				if (y !== null) {
					spaces.cmyk.y = _clip(y);
				}
				if (k !== null) {
					spaces.cmyk.k = _clip(k);
				}
				this.set = true;

				return this;
			};

			this.getRGB = function() {
				if (!spaces.rgb) {
					spaces.rgb	= spaces.lab ?	_xyz_to_rgb(_lab_to_xyz(spaces.lab))
								: spaces.hsv ?	_hsv_to_rgb(spaces.hsv)
								: spaces.hsl ?	_hsl_to_rgb(spaces.hsl)
								: spaces.cmyk ?	_cmy_to_rgb(_cmyk_to_cmy(spaces.cmyk))
								: {r: 0, g: 0, b: 0};
					spaces.rgb.r = _clip(spaces.rgb.r);
					spaces.rgb.g = _clip(spaces.rgb.g);
					spaces.rgb.b = _clip(spaces.rgb.b);
				}
				return $.extend({}, spaces.rgb);
			};

			this.getHSV = function() {
				if (!spaces.hsv) {
					spaces.hsv	= spaces.lab ? _rgb_to_hsv(this.getRGB())
								: spaces.rgb ?	_rgb_to_hsv(spaces.rgb)
								: spaces.hsl ?	_rgb_to_hsv(this.getRGB())
								: spaces.cmyk ?	_rgb_to_hsv(this.getRGB())
								: {h: 0, s: 0, v: 0};
					spaces.hsv.h = _clip(spaces.hsv.h);
					spaces.hsv.s = _clip(spaces.hsv.s);
					spaces.hsv.v = _clip(spaces.hsv.v);
				}
				return $.extend({}, spaces.hsv);
			};

			this.getHSL = function() {
				if (!spaces.hsl) {
					spaces.hsl	= spaces.rgb ?	_rgb_to_hsl(spaces.rgb)
								: spaces.hsv ?	_rgb_to_hsl(this.getRGB())
								: spaces.cmyk ?	_rgb_to_hsl(this.getRGB())
								: spaces.hsv ?	_rgb_to_hsl(this.getRGB())
								: {h: 0, s: 0, l: 0};
					spaces.hsl.h = _clip(spaces.hsl.h);
					spaces.hsl.s = _clip(spaces.hsl.s);
					spaces.hsl.l = _clip(spaces.hsl.l);
				}
				return $.extend({}, spaces.hsl);
			};

			this.getCMYK = function() {
				if (!spaces.cmyk) {
					spaces.cmyk	= spaces.rgb ?	_cmy_to_cmyk(_rgb_to_cmy(spaces.rgb))
								: spaces.hsv ?	_cmy_to_cmyk(_rgb_to_cmy(this.getRGB()))
								: spaces.hsl ?	_cmy_to_cmyk(_rgb_to_cmy(this.getRGB()))
								: spaces.lab ?	_cmy_to_cmyk(_rgb_to_cmy(this.getRGB()))
								: {c: 0, m: 0, y: 0, k: 1};
					spaces.cmyk.c = _clip(spaces.cmyk.c);
					spaces.cmyk.m = _clip(spaces.cmyk.m);
					spaces.cmyk.y = _clip(spaces.cmyk.y);
					spaces.cmyk.k = _clip(spaces.cmyk.k);
				}
				return $.extend({}, spaces.cmyk);
			};

			this.getLAB = function() {
				if (!spaces.lab) {
					spaces.lab	= spaces.rgb ?	_xyz_to_lab(_rgb_to_xyz(spaces.rgb))
								: spaces.hsv ?	_xyz_to_lab(_rgb_to_xyz(this.getRGB()))
								: spaces.hsl ?	_xyz_to_lab(_rgb_to_xyz(this.getRGB()))
								: spaces.cmyk ?	_xyz_to_lab(_rgb_to_xyz(this.getRGB()))
								: {l: 0, a: 0, b: 0};
					spaces.lab.l = _clip(spaces.lab.l);
					spaces.lab.a = _clip(spaces.lab.a);
					spaces.lab.b = _clip(spaces.lab.b);
				}
				return $.extend({}, spaces.lab);
			};

			this.getChannels = function() {
				return {
					r:	this.getRGB().r,
					g:	this.getRGB().g,
					b:	this.getRGB().b,
					a:	this.getAlpha(),
					h:	this.getHSV().h,
					s:	this.getHSV().s,
					v:	this.getHSV().v,
					c:	this.getCMYK().c,
					m:	this.getCMYK().m,
					y:	this.getCMYK().y,
					k:	this.getCMYK().k,
					L:	this.getLAB().l,
					A:	this.getLAB().a,
					B:	this.getLAB().b
				};
			};

			this.getSpaces = function() {
				return $.extend(true, {}, spaces);
			};

			this.distance = function(color) {
				var space	= 'lab',
					getter	= 'get'+space.toUpperCase(),
					a = this[getter](),
					b = color[getter](),
					distance = 0,
					channel;

				for (channel in a) {
					distance += Math.pow(a[channel] - b[channel], 2);
				}

				return distance;
			};

			this.equals = function(color) {
				if (color) {
					var a = this.getRGB(),
						b = color.getRGB();

					return this.set === color.set
						&& this.getAlpha() === color.getAlpha()
						&& a.r === b.r
						&& a.g === b.g
						&& a.b === b.b;
				}
				return false;
			};

			this.limit = function(steps) {
				steps -= 1;
				var rgb = this.getRGB();
				this.setRGB(
					Math.round(rgb.r * steps) / steps,
					Math.round(rgb.g * steps) / steps,
					Math.round(rgb.b * steps) / steps
				);
			};

			this.toHex = function() {
				var rgb = this.getRGB();
				return _hexify(rgb.r * 255) + _hexify(rgb.g * 255) + _hexify(rgb.b * 255);
			};

			this.toCSS = function() {
				return '#' + this.toHex();
			};

			this.copy = function() {
				var color = new $.colorpicker.Color(this.getSpaces(), this.getAlpha());
				color.set = this.set;
				return color;
			};

			// Construct
			if (args.length === 2) {
				spaces = args[0];
				this.setAlpha(args[1] === 0 ? 0 : args[1] || 1);
				this.set = true;
			}
			if (args.length > 2) {
				this.setRGB(args[0], args[1], args[2]);
				this.setAlpha(args[3] === 0 ? 0 : args[3] || 1);
				this.set = true;
			}
		};
	}();

	$.widget("vanderlee.colorpicker", {
		options: {
			alpha:				false,		// Show alpha controls and mode
			altAlpha:			true,		// change opacity of altField as well?
			altField:			'',			// selector for DOM elements which change background color on change.
			altOnChange:		true,		// true to update on each change, false to update only on close.
			altProperties:		'background-color',	// comma separated list of any of 'background-color', 'color', 'border-color', 'outline-color'
			autoOpen:			false,		// Open dialog automatically upon creation
			buttonClass:		null,		// If set, the button will get this/these classname(s).
			buttonColorize:		false,
			buttonImage:		'/Content/images/ui-colorpicker.png',
			buttonImageOnly:	false,
			buttonText:			null,		// Text on the button and/or title of button image.
			closeOnEscape:		true,		// Close the dialog when the escape key is pressed.
			closeOnOutside:		true,		// Close the dialog when clicking outside the dialog (not for inline)
			color:				'#00FF00',	// Initial color (for inline only)
			colorFormat:		'HEX',		// Format string for output color format
			disabled:			false,		// Disable or enable the colorpicker initially
			draggable:			true,		// Make popup dialog draggable if header is visible.
			containment:		null,		// Constrains dragging to within the bounds of the specified element or region.
			duration:			'fast',
			hideOn:				'button',	// 'focus', 'click', 'button', 'alt', 'all'
			hsv:				true,		// Show HSV controls and modes
			inline:				true,		// Show any divs as inline by default
			inlineFrame:		true,		// Show a border and background when inline.
			layout: {
				map:		[0, 0, 1, 5],	// Left, Top, Width, Height (in table cells).
				bar:		[1, 0, 1, 5],
				preview:	[2, 0, 1, 1],
				hsv:		[2, 1, 1, 1],
				rgb:		[2, 2, 1, 1],
				alpha:		[2, 3, 1, 1],
				hex:		[2, 4, 1, 1],
				lab:		[3, 1, 1, 1],
				cmyk:		[3, 2, 1, 2],
				swatches:	[4, 0, 1, 5]
			},
			limit:				'',			// Limit color "resolution": '', 'websafe', 'nibble', 'binary', 'name'
			modal:				false,		// Modal dialog?
			mode:				'h',		// Initial editing mode, h, s, v, r, g, b or a
			okOnEnter:			false,		// Close (with OK) when pressing the enter key
			parts:				'',			// leave empty for automatic selection
			part: {
				map:		{ size: 256 },
				bar:		{ size: 256 }
			},			// options per part
			position:			null,
			regional:			'',
			revert:				false,		// Revert color upon non
			rgb:				true,		// Show RGB controls and modes
			showAnim:			'fadeIn',
			showCancelButton:	true,
			showNoneButton:		false,
			showCloseButton:	true,
			showOn:				'focus click alt',		// 'focus', 'click', 'button', 'alt', 'all'
			showOptions:		{},
			swatches:			null,		// null for default or kv-object or names swatches set
			swatchesWidth:		84,			// width (in number of pixels) of swatches box.
			title:				null,

			cancel:             null,
            close:              null,
			init:				null,
            ok:                 null,
			open:               null,
			select:             null,
			stop:				null
		},
		
		_create: function () {
			var that = this,
				text;

			that.colorpicker_index = _colorpicker_index++;

			that.widgetEventPrefix = 'colorpicker';

			that.opened		= false;
			that.generated	= false;
			that.inline		= false;
			that.changed	= false;

			that.dialog		= null;
			that.button		= null;
			that.image		= null;
			that.overlay	= null;
			
			that.events = {
				window_resize:			null,
				document_keydown:		null,
				document_click_html:	null
			};

			that.mode		= that.options.mode;

			if (that.element.is('input') || that.options.inline === false) {
				// Initial color
				that._setColor(that.element.is('input') ? that.element.val() : that.options.color);
				that._callback('init');

				// showOn focus
				if (/\bfocus|all|both\b/.test(that.options.showOn)) {
					that.element.bind('focus', function () {
						that.open();
					});
				}
				if (/\bfocus|all|both\b/.test(that.options.hideOn)) {
					that.element.bind('focusout', function (e) {
						that.close();
					});
				}

				// showOn click
				if (/\bclick|all|both\b/.test(that.options.showOn)) {
					that.element.bind('click', function (e) {						
						if (that.opened && /\bclick|all|both\b/.test(that.options.hideOn)) {
							that.close();
						} else {
							that.open();
						}
					});
				}

				// showOn button
				if (/\bbutton|all|both\b/.test(that.options.showOn)) {
					if (that.options.buttonImage !== '') {
						text = that.options.buttonText || that._getRegional('button');

						that.image = $('<img/>').attr({
							'src':		that.options.buttonImage,
							'alt':		text,
							'title':	text
						});
						if (that.options.buttonClass) {
							that.image.attr('class', that.options.buttonClass);
						}

						that._setImageBackground();
					}

					if (that.options.buttonImageOnly && that.image) {
						that.button = that.image;
					} else {
						that.button = $('<button type="button"></button>').html(that.image || that.options.buttonText).button();
						that.image = that.image ? $('img', that.button).first() : null;
					}
					that.button.insertAfter(that.element).click(function () {
						if (that.opened && /\bbutton|all|both\b/.test(that.options.hideOn)) {
							that.close();
						} else {
							that.open();
						}
					});
				}

				// showOn alt
				if (/\balt|all|both\b/.test(that.options.showOn)) {					
					$(that.options.altField).bind('click', function () {
						if (that.opened && /\balt|all|both\b/.test(that.options.hideOn)) {
							that.close();
						} else {
							that.open();
						}
					});
				}

				if (that.options.autoOpen) {
					that.open();
				}

			} else {
				that.inline = true;

				that._generate();
				that.opened = true;
			}

			// Disable Widget-style
			(that.element.is(':disabled') || that.options.disabled) && that.disable();

			return this;
		},

		_setOption: function(key, value) {
			switch (key) {
				case 'disabled':
					this[value ? 'disable' : 'enable']();
					break;

				case 'swatches':
					this.options.swatches = value;
					this.parts.swatches.init();
					break;
			}

			$.Widget.prototype._setOption.apply(this, arguments);
		},

		enable: function () {
			//$.Widget.prototype.enable.call(this);
			this.element && this.element.prop('disabled', false);
			this.button && this.button.prop('disabled', false);
			this.dialog && this.dialog.removeClass('ui-colorpicker-disabled');
			this.options.disabled = false;

			this.parts && $.each(this.parts, function (index, part) {
				part.disable && part.disable(false);
			});
		},
		
		disable: function () {
			//$.Widget.prototype.disable.call(this);
			this.element && this.element.prop('disabled', true);
			this.button && this.button.prop('disabled', true);
			this.dialog && this.dialog.addClass('ui-colorpicker-disabled');
			this.options.disabled = true;

			this.parts && $.each(this.parts, function (index, part) {
				part.disable && part.disable(true);
			});
		},

		_setImageBackground: function() {
			if (this.image && this.options.buttonColorize) {
				this.image.css('background-color', this.color.set? this._formatColor('RGBA', this.color) : '');
			}
		},

		/**
		 * If an alternate field is specified, set it according to the current color.
		 */
		_setAltField: function () {
			if (this.options.altOnChange && this.options.altField && this.options.altProperties) {
				var index,
					property,
					properties = this.options.altProperties.split(',');

				for (index = 0; index <= properties.length; ++index) {
					property = $.trim(properties[index]);
					switch (property) {
						case 'color':
						case 'fill':
						case 'stroke':
						case 'background-color':
						case 'backgroundColor':
						case 'outline-color':
						case 'border-color':
							$(this.options.altField).css(property, this.color.set? this.color.toCSS() : '');
							break;
					}
				}

				this.options.altAlpha && 
					$(this.options.altField).css('opacity', this.color.set? this.color.getAlpha() : '');
			}
		},

		_setColor: function (text) {
			this.color			= this._parseColor(text) || new $.colorpicker.Color();
			this.currentColor	= this.color.copy();

			this._setImageBackground();
			this._setAltField();
		},

		setColor: function(text) {
			this._setColor(text);
			this._change();
		},

		getColor: function (format) {
		    if (!this.color.set)
		        return null
            else
			    return this._formatColor(format || this.options.colorFormat, this.color);
		},

		_generateInline: function() {
			var that = this;

			$(that.element).html(that.options.inlineFrame ? _container_inlineFrame : _container_inline);

			that.dialog = $('.ui-colorpicker', that.element);
		},

		_generatePopup: function() {
			var that = this;

			that.dialog = $(_container_popup).appendTo('body');

			// Close on clicking outside window and controls
			if (that.events.document_click_html === null) {
				$(document).delegate('html', 'touchstart click', that.events.document_click_html = function (event) {
					if (!that.opened || event.target === that.element[0] || that.overlay) {
						return;
					}

					// Check if clicked on any part of dialog
					if (that.dialog.is(event.target) || that.dialog.has(event.target).length > 0) {
						that.element.blur();	// inside window!
						return;
					}

					// Check if clicked on known external elements
					var p,
						parents = $(event.target).parents();
					// add the event.target in case of buttonImageOnly and closeOnOutside both are set to true
					parents.push(event.target);
					for (p = 0; p <= parents.length; ++p) {
						// button
						if (that.button !== null && parents[p] === that.button[0]) {
							return;
						}
						// showOn alt
						if (/\balt|all|both\b/.test(that.options.showOn) && $(that.options.altField).is(parents[p])) {
							return;
						}
					}

					// no closeOnOutside
					if (!that.options.closeOnOutside) {
						return;
					}

					that.close(that.options.revert);
				});
			}

			if (that.events.document_keydown === null) {
				$(document).bind('keydown', that.events.document_keydown = function (event) {
					// close on ESC key
					if (that.opened && event.keyCode === 27 && that.options.closeOnEscape) {
						that.close(that.options.revert);
					}

					// OK on Enter key
					if (that.opened && event.keyCode === 13 && that.options.okOnEnter) {
						that.close();
					}
				});
			}

			// Close (with OK) on tab key in element
			that.element.keydown(function (event) {
				if (event.keyCode === 9) {
					that.close();
				}
			}).keyup(function (event) {
				var color = that._parseColor(that.element.val());
				if (color && !that.color.equals(color)) {
					that.color = color;
					that._change();
				}
			});
		},

		_generate: function () {
			var that = this,
				index,
				part,
				parts_list,
				layout_parts,
				table,
				classes;

			that._setColor(that.inline || !that.element.is('input') ? that.options.color : that.element.val());

			that[that.inline ? '_generateInline' : '_generatePopup']();

			// Determine the parts to include in this colorpicker
			if (typeof that.options.parts === 'string') {
				if ($.colorpicker.partslists[that.options.parts]) {
					parts_list = $.colorpicker.partslists[that.options.parts];
				} else {
					// automatic
					parts_list = $.colorpicker.partslists[that.inline ? 'inline' : 'popup'];
				}
			} else {
				parts_list = that.options.parts;
			}

			// Add any parts to the internal parts list
			that.parts = {};
			$.each(parts_list, function(index, part) {
				if ($.colorpicker.parts[part]) {
					that.parts[part] = new $.colorpicker.parts[part](that);
				}
			});

			if (!that.generated) {
				layout_parts = [];

				$.each(that.options.layout, function(part, pos) {
					if (that.parts[part]) {
						layout_parts.push({
							'part': part,
							'pos':  pos
						});
					}
				});

				table = $(_layoutTable(layout_parts, function(cell, x, y) {
					classes = ['ui-colorpicker-' + cell.part + '-container'];

					if (x > 0) {
						classes.push('ui-colorpicker-padding-left');
					}

					if (y > 0) {
						classes.push('ui-colorpicker-padding-top');
					}

					return '<td  class="' + classes.join(' ') + '"'
						+ (cell.pos[2] > 1 ? ' colspan="' + cell.pos[2] + '"' : '')
						+ (cell.pos[3] > 1 ? ' rowspan="' + cell.pos[3] + '"' : '')
						+ ' valign="top"></td>';
				})).appendTo(that.dialog);
				if (that.options.inlineFrame) {
					table.addClass('ui-dialog-content ui-widget-content');
				}

				that._initAllParts();
				that._updateAllParts();
				that.generated = true;
			}
		},

		_effectGeneric: function (element, show, slide, fade, callback) {
			var that = this;

			if ($.effects && $.effects[that.options.showAnim]) {
				element[show](that.options.showAnim, that.options.showOptions, that.options.duration, callback);
			} else {
				element[(that.options.showAnim === 'slideDown' ?
								slide
							:	(that.options.showAnim === 'fadeIn' ?
									fade
								:	show))]((that.options.showAnim ? that.options.duration : null), callback);
				if (!that.options.showAnim || !that.options.duration) {
					callback();
				}
			}
		},

		_effectShow: function(element, callback) {
			this._effectGeneric(element, 'show', 'slideDown', 'fadeIn', callback);
		},

		_effectHide: function(element, callback) {
			this._effectGeneric(element, 'hide', 'slideUp', 'fadeOut', callback);
		},
				
		open: function() {
			var that = this,
				offset,
				bottom, right,
				height, width,
				x, y,
				zIndex,
				element,
				position;

			if (!that.opened) {
				that._generate();
				
				if (that.element.is(':hidden')) {
					element = $('<div/>').insertBefore(that.element);
				} else {
					element = that.element;
				}			
				
				if (that.element.is(':hidden')) {
					element.remove();
				}
				
				// Automatically find highest z-index.
				zIndex = 0;
				$(that.element[0]).parents().each(function() {
					var z = $(this).css('z-index');
					if ((typeof(z) === 'number' || typeof(z) === 'string') && z !== '' && !isNaN(z)) {
						if (z > zIndex) {
							zIndex = parseInt(z, 10);
							return false;
						}
					}
					else {
						$(this).siblings().each(function() {
							var z = $(this).css('z-index');
							if ((typeof(z) === 'number' || typeof(z) === 'string') && z !== '' && !isNaN(z)) {
								if (z > zIndex) {
									zIndex = parseInt(z, 10);
								}
							}
						});
					}
				});

				zIndex += 2;
				that.dialog.css('z-index', zIndex);
								
				if (that.options.modal) {
					that.overlay = $('<div class="ui-widget-overlay"></div>').appendTo('body').css('z-index', zIndex - 1);										

					if (that.events.window_resize !== null) {
						$(window).unbind('resize', that.events.window_resize);					
					}
					
					that.events.window_resize = function() {
						if (that.overlay) {
							that.overlay.width($(document).width());
							that.overlay.height($(document).height());					
						}
					},
															
					$(window).bind('resize', that.events.window_resize);
					that.events.window_resize();			
				}

				that._effectShow(this.dialog);

				if (that.options.position) {
					position = $.extend({}, that.options.position);
					if (position.of === 'element') {
						position.of = element;
					}
				} else {
					position = {
						my:			'left top',
						at:			'left bottom',
						of:			element,
						collision:	'flip'
					};
				}
				that.dialog.position(position);
				
				that.opened = true;
				that._callback('open', true);

				// Without waiting for domready the width of the map is 0 and we
				// wind up with the cursor stuck in the upper left corner
				$(function() {
					that._repaintAllParts();
				});
			}
		},

		close: function (cancel) {
			var that = this;

			if (!that.opened) {
				return;
			}				
				
            if (cancel) {
				that.color = that.currentColor.copy();
                that._change();
                that._callback('cancel', true);
            } else {
				that.currentColor	= that.color.copy();
                that._callback('ok', true);
            }
			that.changed		= false;

			if (that.overlay) {
				$(window).unbind('resize', that.events.window_resize);					
				that.overlay.remove();
			}
			
			// tear down the interface
			that._effectHide(that.dialog, function () {
				that.dialog.remove();
				that.dialog		= null;
				that.generated	= false;

				that.opened		= false;
				that._callback('close', true);
			});
		},

		destroy: function() {
			var that = this;
			if (that.events.document_click_html !== null) {
				$(document).undelegate('html', 'touchstart click', that.events.document_click_html);
			}
			
			if (that.events.document_keydown !== null) {
				$(document).unbind('keydown', that.events.document_keydown);
			}
			
			if (that.events.resizeOverlay !== null) {
				$(window).unbind('resize', that.events.resizeOverlay);					
			}			
			
			this.element.unbind();

			if (this.overlay) {
				this.overlay.remove();
			}
			
			if (this.dialog !== null) {
				this.dialog.remove();
			}
			
			if (this.image !== null) {
				this.image.remove();
			}

			if (this.button !== null) {
				this.button.remove();
			}
		},

		_callback: function (callback, spaces) {
			var that = this,
				data,
				lab;

			if (that.color.set) {
				data = {
					formatted: that._formatColor(that.options.colorFormat, that.color),
					colorPicker: that
				};

				lab = that.color.getLAB();
				lab.a = (lab.a * 2) - 1;
				lab.b = (lab.b * 2) - 1;

				if (spaces === true) {
					data.a		= that.color.getAlpha();
					data.rgb	= that.color.getRGB();
					data.hsv	= that.color.getHSV();
					data.cmyk	= that.color.getCMYK();
					data.hsl	= that.color.getHSL();
					data.lab	= lab;
				}

				return that._trigger(callback, null, data);
			} else {
				return that._trigger(callback, null, {
					formatted: '',
					colorPicker: that
				});
			}
		},

		_initAllParts: function () {
			$.each(this.parts, function (index, part) {
				if (part.init) {
					part.init();
				}
			});
		},

		_updateAllParts: function () {
			$.each(this.parts, function (index, part) {
				if (part.update) {
					part.update();
				}
			});
		},

		_repaintAllParts: function () {
			$.each(this.parts, function (index, part) {
				if (part.repaint) {
					part.repaint();
				}
			});
		},
		
		_change: function (stoppedChanging /* = true */) {
			// Limit color palette
			if (this.color.set && this.options.limit && $.colorpicker.limits[this.options.limit]) {
				$.colorpicker.limits[this.options.limit](this.color, this);
			}

			// Set changed if different from starting color
			this.changed = !this.color.equals(this.currentColor);

			// update input element content
			if (!this.inline) {
				if (!this.color.set) {
					if (this.element.val() !== '') {
						this.element.val('').change();
					}
				} else if (!this.color.equals(this._parseColor(this.element.val()))) {
					this.element.val(this._formatColor(this.options.colorFormat, this.color)).change();
				}

				this._setImageBackground();
			}

			// Set the alt field
			this._setAltField();

			// update color option
			this.options.color = this.color.set ? this.color.toCSS() : '';

			if (this.opened) {
				this._repaintAllParts();
			}

			// callbacks
			this._callback('select');

			if (typeof stoppedChanging === 'undefined' ? true : !!stoppedChanging) {
				this._callback('stop');
			}
		},

		// This will be deprecated by jQueryUI 1.9 widget
		_hoverable: function (e) {
			e.hover(function () {
				e.addClass("ui-state-hover");
			}, function () {
				e.removeClass("ui-state-hover");
			});
		},

		// This will be deprecated by jQueryUI 1.9 widget
		_focusable: function (e) {
			e.focus(function () {
				e.addClass("ui-state-focus");
			}).blur(function () {
				e.removeClass("ui-state-focus");
			});
		},

		_getRegional: function(name) {
			return $.colorpicker.regional[this.options.regional][name] !== undefined ?
				$.colorpicker.regional[this.options.regional][name] : $.colorpicker.regional[''][name];
        },

		_getSwatches: function() {
			if (typeof(this.options.swatches) === 'string') {
				return $.colorpicker.swatches[this.options.swatches];
			}

			if ($.isPlainObject(this.options.swatches)) {
				return this.options.swatches;
			}

			return $.colorpicker.swatches.html;
		},
		
		_eachSwatch: function (callback) {
			var currentSwatches = this._getSwatches(),
				name;
			$.each(currentSwatches, function (nameOrIndex, swatch) {
				name = $.isArray(currentSwatches) ? swatch.name : nameOrIndex;
				return callback(name, swatch);
			});
		},

		_getSwatch: function(name) {
			var swatch = false;

			this._eachSwatch(function(swatchName, current) {
				if (swatchName.toLowerCase() == name.toLowerCase()) {
					swatch = current;
					return false;
				}
			});

			return swatch;
        },
		
		_parseFormat: function(format, text) {
			var that = this,
				typeRegexps = {
					x:	function() {return '([0-9a-fA-F]{2})';}
				,	d:	function() {return '([12]?[0-9]{1,2})';}
				,	f:	function() {return '([0-9]*\\.?[0-9]*)';}
				,	p:	function() {return '([0-9]*\\.?[0-9]*)';}
				},
				typeConverters = {
					x:	function(v)	{return parseInt(v, 16) / 255.;}
				,	d:	function(v)	{return v / 255.;}
				,	f:	function(v)	{return v;}
				,	p:	function(v)	{return v * 0.01;}
				},
				setters = {
					r:	'setRGB'
				,	g:	'setRGB'
				,	b:	'setRGB'
				,	h:	'setHSV'
				,	s:	'setHSV'
				,	v:	'setHSV'
				,	c:	'setCMYK'
				,	m:	'setCMYK'
				,	y:	'setCMYK'
				,	k:	'setCMYK'
				,	L:	'setLAB'
				,	A:	'setLAB'
				,	B:	'setLAB'
				},
				setterChannels = {
					setRGB:		[ 'r', 'g', 'b']
				,	setHSV:		[ 'h', 's', 'v' ]
				,	setCMYK:	[ 'c', 'm', 'y', 'k' ]
				,	setLAB:		[ 'L', 'A', 'B' ]
				},
				channels = [],
				converters = [],						
				setter = null,
				color,
				pattern;

			// Construct pattern
			pattern = format.replace(/[()\\^$.|?*+[\]]/g, function(m) {
				return '\\'+m;
			});


			pattern = pattern.replace(/\\?[argbhsvcmykLAB][xdfp]/g, function(variable) {
				if (variable.match(/^\\/)) {
					return variable.slice(1);
				}

				var channel = variable.charAt(0),
					type = variable.charAt(1);

				channels.push(channel);
				converters.push(typeConverters[type]);
				if (setters[channel]) {
					setter = setters[channel];
				}

				return typeRegexps[type]();
			});

			if (setter) {
				var values = text.match(new RegExp(pattern));
				if (values) {
					var args = [],
						channelIndex;
					
					values.shift();
									
					$.each(setterChannels[setter], function(index, channel) {
						channelIndex = $.inArray(channel, channels);
						args[index] = converters[channelIndex](values[channelIndex]);
					});

					color = new $.colorpicker.Color();
					color[setter].apply(color, args);
				}
			}
			
			return color;
		},

        _parseColor: function(text) {
            var that = this,
				color;
		
			var formats = $.isArray(that.options.colorFormat)
					? that.options.colorFormat
					: [ that.options.colorFormat ];

			$.each(formats, function(index, format) {
				if ($.colorpicker.parsers[format]) {
					color = $.colorpicker.parsers[format](text, that);
				} else {
					color = that._parseFormat(format, text);
				}
			
				if (color) {
					return false;
				}
			});
			
			if (!color) {
				// fallback; check all registered parsers
				$.each($.colorpicker.parsers, function(name, parser) {
					color = parser(text, that);
					if (color) {
						return false;
					}
				});
			}

			if (color) {
				return color;
			}

			return false;
        },

		_exactName: function(color) {
			var name	= false;

			this._eachSwatch(function(n, swatch) {
				if (color.equals(new $.colorpicker.Color(swatch.r, swatch.g, swatch.b))) {
					name = n;
					return false;
				}
			});

			return name;
		},

		_closestName: function(color) {
			var rgb			= color.getRGB(),
				distance	= null,
				name		= false,
				d;

			this._eachSwatch(function(n, swatch) {
				d = color.distance(new $.colorpicker.Color(swatch.r, swatch.g, swatch.b));
				if (d < distance || distance === null) {
					name = n;
					if (d <= 1e-20) {	// effectively 0 by maximum rounding error
						return false;	// can't get much closer than 0
					}
					distance = d;	// safety net
				}
			});

			return name;
		},

		_formatColor: function (formats, color) {			
			var that		= this,
				text		= null,
				types		= {	'x':	function(v) {return _intToHex(v * 255);}
							,	'd':	function(v) {return Math.round(v * 255);}
							,	'f':	function(v) {return v;}
							,	'p':	function(v) {return v * 100.;}
							},
				channels	= color.getChannels();

			if (!$.isArray(formats)) {
				formats = [formats];
			}

			$.each(formats, function(index, format) {
				if ($.colorpicker.writers[format]) {
					text = $.colorpicker.writers[format](color, that);		
					return (text === false);
				} else {
					text = format.replace(/\\?[argbhsvcmykLAB][xdfp]/g, function(m) {
						if (m.match(/^\\/)) {
							return m.slice(1);
						}
						return types[m.charAt(1)](channels[m.charAt(0)]);
					});
					return false;
				}
			});
			
			return text;
		}
	});
	
	return $.vanderlee.colorpicker;
}));
;
;jQuery(function($) {
	$.colorpicker.swatchesNames['pantone'] = 'Pantone';
	$.colorpicker.swatches['pantone'] = [
		{name: '100', r: 0.956862745098039, g: 0.929411764705882, b: 0.486274509803922},
		{name: '101', r: 0.956862745098039, g: 0.929411764705882, b: 0.27843137254902},
		{name: '102', r: 0.976470588235294, g: 0.909803921568627, b: 0.0784313725490196},
		{name: '103', r: 0.776470588235294, g: 0.67843137254902, b: 0.0588235294117647},
		{name: '104', r: 0.67843137254902, g: 0.607843137254902, b: 0.0470588235294118},
		{name: '105', r: 0.509803921568627, g: 0.458823529411765, b: 0.0588235294117647},
		{name: '106', r: 0.968627450980392, g: 0.909803921568627, b: 0.349019607843137},
		{name: '107', r: 0.976470588235294, g: 0.898039215686275, b: 0.149019607843137},
		{name: '108', r: 0.976470588235294, g: 0.866666666666667, b: 0.0862745098039216},
		{name: '109', r: 0.976470588235294, g: 0.83921568627451, b: 0.0862745098039216},
		{name: '110', r: 0.847058823529412, g: 0.709803921568627, b: 0.0666666666666667},
		{name: '111', r: 0.666666666666667, g: 0.576470588235294, b: 0.0392156862745098},
		{name: '112', r: 0.6, g: 0.517647058823529, b: 0.0392156862745098},
		{name: '113', r: 0.976470588235294, g: 0.898039215686275, b: 0.356862745098039},
		{name: '114', r: 0.976470588235294, g: 0.886274509803922, b: 0.298039215686275},
		{name: '115', r: 0.976470588235294, g: 0.87843137254902, b: 0.298039215686275},
		{name: '116', r: 0.988235294117647, g: 0.819607843137255, b: 0.0862745098039216},
		{name: '116 2X', r: 0.968627450980392, g: 0.709803921568627, b: 0.0470588235294118},
		{name: '117', r: 0.776470588235294, g: 0.627450980392157, b: 0.0470588235294118},
		{name: '118', r: 0.666666666666667, g: 0.556862745098039, b: 0.0392156862745098},
		{name: '119', r: 0.537254901960784, g: 0.466666666666667, b: 0.0980392156862745},
		{name: '120', r: 0.976470588235294, g: 0.886274509803922, b: 0.498039215686275},
		{name: '1205', r: 0.968627450980392, g: 0.909803921568627, b: 0.666666666666667},
		{name: '121', r: 0.976470588235294, g: 0.87843137254902, b: 0.43921568627451},
		{name: '1215', r: 0.976470588235294, g: 0.87843137254902, b: 0.549019607843137},
		{name: '122', r: 0.988235294117647, g: 0.847058823529412, b: 0.337254901960784},
		{name: '1225', r: 1, g: 0.8, b: 0.286274509803922},
		{name: '123', r: 1, g: 0.776470588235294, b: 0.117647058823529},
		{name: '1235', r: 0.988235294117647, g: 0.709803921568627, b: 0.0784313725490196},
		{name: '124', r: 0.87843137254902, g: 0.666666666666667, b: 0.0588235294117647},
		{name: '1245', r: 0.749019607843137, g: 0.568627450980392, b: 0.0470588235294118},
		{name: '125', r: 0.709803921568627, g: 0.549019607843137, b: 0.0392156862745098},
		{name: '1255', r: 0.63921568627451, g: 0.498039215686275, b: 0.0784313725490196},
		{name: '126', r: 0.63921568627451, g: 0.509803921568627, b: 0.0196078431372549},
		{name: '1265', r: 0.486274509803922, g: 0.388235294117647, b: 0.0862745098039216},
		{name: '127', r: 0.956862745098039, g: 0.886274509803922, b: 0.529411764705882},
		{name: '128', r: 0.956862745098039, g: 0.858823529411765, b: 0.376470588235294},
		{name: '129', r: 0.949019607843137, g: 0.819607843137255, b: 0.23921568627451},
		{name: '130', r: 0.917647058823529, g: 0.686274509803922, b: 0.0588235294117647},
		{name: '130 2X', r: 0.886274509803922, g: 0.568627450980392, b: 0},
		{name: '131', r: 0.776470588235294, g: 0.576470588235294, b: 0.0392156862745098},
		{name: '132', r: 0.619607843137255, g: 0.486274509803922, b: 0.0392156862745098},
		{name: '133', r: 0.43921568627451, g: 0.356862745098039, b: 0.0392156862745098},
		{name: '134', r: 1, g: 0.847058823529412, b: 0.498039215686275},
		{name: '1345', r: 1, g: 0.83921568627451, b: 0.568627450980392},
		{name: '135', r: 0.988235294117647, g: 0.788235294117647, b: 0.388235294117647},
		{name: '1355', r: 0.988235294117647, g: 0.807843137254902, b: 0.529411764705882},
		{name: '136', r: 0.988235294117647, g: 0.749019607843137, b: 0.286274509803922},
		{name: '1365', r: 0.988235294117647, g: 0.729411764705882, b: 0.368627450980392},
		{name: '137', r: 0.988235294117647, g: 0.63921568627451, b: 0.0666666666666667},
		{name: '1375', r: 0.976470588235294, g: 0.607843137254902, b: 0.0470588235294118},
		{name: '138', r: 0.847058823529412, g: 0.549019607843137, b: 0.00784313725490196},
		{name: '1385', r: 0.8, g: 0.47843137254902, b: 0.00784313725490196},
		{name: '139', r: 0.686274509803922, g: 0.458823529411765, b: 0.0196078431372549},
		{name: '1395', r: 0.6, g: 0.376470588235294, b: 0.0274509803921569},
		{name: '140', r: 0.47843137254902, g: 0.356862745098039, b: 0.0666666666666667},
		{name: '1405', r: 0.419607843137255, g: 0.27843137254902, b: 0.0784313725490196},
		{name: '141', r: 0.949019607843137, g: 0.807843137254902, b: 0.407843137254902},
		{name: '142', r: 0.949019607843137, g: 0.749019607843137, b: 0.286274509803922},
		{name: '143', r: 0.937254901960784, g: 0.698039215686274, b: 0.176470588235294},
		{name: '144', r: 0.886274509803922, g: 0.549019607843137, b: 0.0196078431372549},
		{name: '145', r: 0.776470588235294, g: 0.498039215686275, b: 0.0274509803921569},
		{name: '146', r: 0.619607843137255, g: 0.419607843137255, b: 0.0196078431372549},
		{name: '147', r: 0.447058823529412, g: 0.368627450980392, b: 0.149019607843137},
		{name: '148', r: 1, g: 0.83921568627451, b: 0.607843137254902},
		{name: '1485', r: 1, g: 0.717647058823529, b: 0.466666666666667},
		{name: '149', r: 0.988235294117647, g: 0.8, b: 0.576470588235294},
		{name: '1495', r: 1, g: 0.6, b: 0.247058823529412},
		{name: '150', r: 0.988235294117647, g: 0.67843137254902, b: 0.337254901960784},
		{name: '1505', r: 0.956862745098039, g: 0.486274509803922, b: 0},
		{name: '151', r: 0.968627450980392, g: 0.498039215686275, b: 0},
		{name: '152', r: 0.866666666666667, g: 0.458823529411765, b: 0},
		{name: '1525', r: 0.709803921568627, g: 0.329411764705882, b: 0},
		{name: '153', r: 0.737254901960784, g: 0.427450980392157, b: 0.0392156862745098},
		{name: '1535', r: 0.549019607843137, g: 0.266666666666667, b: 0},
		{name: '154', r: 0.6, g: 0.349019607843137, b: 0.0196078431372549},
		{name: '1545', r: 0.27843137254902, g: 0.133333333333333, b: 0},
		{name: '155', r: 0.956862745098039, g: 0.858823529411765, b: 0.666666666666667},
		{name: '1555', r: 0.976470588235294, g: 0.749019607843137, b: 0.619607843137255},
		{name: '156', r: 0.949019607843137, g: 0.776470588235294, b: 0.549019607843137},
		{name: '1565', r: 0.988235294117647, g: 0.647058823529412, b: 0.466666666666667},
		{name: '157', r: 0.929411764705882, g: 0.627450980392157, b: 0.309803921568627},
		{name: '1575', r: 0.988235294117647, g: 0.529411764705882, b: 0.266666666666667},
		{name: '158', r: 0.909803921568627, g: 0.458823529411765, b: 0.0666666666666667},
		{name: '1585', r: 0.976470588235294, g: 0.419607843137255, b: 0.0274509803921569},
		{name: '159', r: 0.776470588235294, g: 0.376470588235294, b: 0.0196078431372549},
		{name: '1595', r: 0.819607843137255, g: 0.356862745098039, b: 0.0196078431372549},
		{name: '160', r: 0.619607843137255, g: 0.329411764705882, b: 0.0392156862745098},
		{name: '1605', r: 0.627450980392157, g: 0.309803921568627, b: 0.0666666666666667},
		{name: '161', r: 0.388235294117647, g: 0.227450980392157, b: 0.0666666666666667},
		{name: '1615', r: 0.517647058823529, g: 0.247058823529412, b: 0.0588235294117647},
		{name: '162', r: 0.976470588235294, g: 0.776470588235294, b: 0.666666666666667},
		{name: '1625', r: 0.976470588235294, g: 0.647058823529412, b: 0.549019607843137},
		{name: '163', r: 0.988235294117647, g: 0.619607843137255, b: 0.43921568627451},
		{name: '1635', r: 0.976470588235294, g: 0.556862745098039, b: 0.427450980392157},
		{name: '164', r: 0.988235294117647, g: 0.498039215686275, b: 0.247058823529412},
		{name: '1645', r: 0.976470588235294, g: 0.447058823529412, b: 0.258823529411765},
		{name: '165', r: 0.976470588235294, g: 0.388235294117647, b: 0.00784313725490196},
		{name: '165 2X', r: 0.917647058823529, g: 0.309803921568627, b: 0},
		{name: '1655', r: 0.976470588235294, g: 0.337254901960784, b: 0.00784313725490196},
		{name: '166', r: 0.866666666666667, g: 0.349019607843137, b: 0},
		{name: '1665', r: 0.866666666666667, g: 0.309803921568627, b: 0.0196078431372549},
		{name: '167', r: 0.737254901960784, g: 0.309803921568627, b: 0.0274509803921569},
		{name: '1675', r: 0.647058823529412, g: 0.247058823529412, b: 0.0588235294117647},
		{name: '168', r: 0.427450980392157, g: 0.188235294117647, b: 0.0666666666666667},
		{name: '1685', r: 0.517647058823529, g: 0.207843137254902, b: 0.0666666666666667},
		{name: '169', r: 0.976470588235294, g: 0.729411764705882, b: 0.666666666666667},
		{name: '170', r: 0.976470588235294, g: 0.537254901960784, b: 0.447058823529412},
		{name: '171', r: 0.976470588235294, g: 0.376470588235294, b: 0.227450980392157},
		{name: '172', r: 0.968627450980392, g: 0.286274509803922, b: 0.00784313725490196},
		{name: '173', r: 0.819607843137255, g: 0.266666666666667, b: 0.0784313725490196},
		{name: '174', r: 0.576470588235294, g: 0.2, b: 0.0666666666666667},
		{name: '175', r: 0.427450980392157, g: 0.2, b: 0.129411764705882},
		{name: '176', r: 0.976470588235294, g: 0.686274509803922, b: 0.67843137254902},
		{name: '1765', r: 0.976470588235294, g: 0.619607843137255, b: 0.63921568627451},
		{name: '1767', r: 0.976470588235294, g: 0.698039215686274, b: 0.717647058823529},
		{name: '177', r: 0.976470588235294, g: 0.509803921568627, b: 0.498039215686275},
		{name: '1775', r: 0.976470588235294, g: 0.517647058823529, b: 0.556862745098039},
		{name: '1777', r: 0.988235294117647, g: 0.4, b: 0.458823529411765},
		{name: '178', r: 0.976470588235294, g: 0.368627450980392, b: 0.349019607843137},
		{name: '1785', r: 0.988235294117647, g: 0.309803921568627, b: 0.349019607843137},
		{name: '1787', r: 0.956862745098039, g: 0.247058823529412, b: 0.309803921568627},
		{name: '1788', r: 0.937254901960784, g: 0.168627450980392, b: 0.176470588235294},
		{name: '1788 2X', r: 0.83921568627451, g: 0.129411764705882, b: 0},
		{name: '179', r: 0.886274509803922, g: 0.23921568627451, b: 0.156862745098039},
		{name: '1795', r: 0.83921568627451, g: 0.156862745098039, b: 0.156862745098039},
		{name: '1797', r: 0.8, g: 0.176470588235294, b: 0.188235294117647},
		{name: '180', r: 0.756862745098039, g: 0.219607843137255, b: 0.156862745098039},
		{name: '1805', r: 0.686274509803922, g: 0.149019607843137, b: 0.149019607843137},
		{name: '1807', r: 0.627450980392157, g: 0.188235294117647, b: 0.2},
		{name: '181', r: 0.486274509803922, g: 0.176470588235294, b: 0.137254901960784},
		{name: '1810', r: 0.486274509803922, g: 0.129411764705882, b: 0.117647058823529},
		{name: '1817', r: 0.356862745098039, g: 0.176470588235294, b: 0.156862745098039},
		{name: '182', r: 0.976470588235294, g: 0.749019607843137, b: 0.756862745098039},
		{name: '183', r: 0.988235294117647, g: 0.549019607843137, b: 0.6},
		{name: '184', r: 0.988235294117647, g: 0.368627450980392, b: 0.447058823529412},
		{name: '185', r: 0.909803921568627, g: 0.0666666666666667, b: 0.176470588235294},
		{name: '185 2X', r: 0.819607843137255, g: 0.0862745098039216, b: 0},
		{name: '186', r: 0.807843137254902, g: 0.0666666666666667, b: 0.149019607843137},
		{name: '187', r: 0.686274509803922, g: 0.117647058823529, b: 0.176470588235294},
		{name: '188', r: 0.486274509803922, g: 0.129411764705882, b: 0.156862745098039},
		{name: '189', r: 1, g: 0.63921568627451, b: 0.698039215686274},
		{name: '1895', r: 0.988235294117647, g: 0.749019607843137, b: 0.788235294117647},
		{name: '190', r: 0.988235294117647, g: 0.458823529411765, b: 0.556862745098039},
		{name: '1905', r: 0.988235294117647, g: 0.607843137254902, b: 0.698039215686274},
		{name: '191', r: 0.956862745098039, g: 0.27843137254902, b: 0.419607843137255},
		{name: '1915', r: 0.956862745098039, g: 0.329411764705882, b: 0.486274509803922},
		{name: '192', r: 0.898039215686275, g: 0.0196078431372549, b: 0.227450980392157},
		{name: '1925', r: 0.87843137254902, g: 0.0274509803921569, b: 0.27843137254902},
		{name: '193', r: 0.768627450980392, g: 0, b: 0.262745098039216},
		{name: '1935', r: 0.756862745098039, g: 0.0196078431372549, b: 0.219607843137255},
		{name: '194', r: 0.6, g: 0.129411764705882, b: 0.207843137254902},
		{name: '1945', r: 0.658823529411765, g: 0.0470588235294118, b: 0.207843137254902},
		{name: '1955', r: 0.576470588235294, g: 0.0862745098039216, b: 0.219607843137255},
		{name: '196', r: 0.980392156862745, g: 0.835294117647059, b: 0.882352941176471},
		{name: '197', r: 0.964705882352941, g: 0.647058823529412, b: 0.745098039215686},
		{name: '198', r: 0.937254901960784, g: 0.356862745098039, b: 0.517647058823529},
		{name: '199', r: 0.627450980392157, g: 0.152941176470588, b: 0.294117647058824},
		{name: '200', r: 0.768627450980392, g: 0.117647058823529, b: 0.227450980392157},
		{name: '201', r: 0.63921568627451, g: 0.149019607843137, b: 0.219607843137255},
		{name: '202', r: 0.549019607843137, g: 0.149019607843137, b: 0.2},
		{name: '203', r: 0.949019607843137, g: 0.686274509803922, b: 0.756862745098039},
		{name: '204', r: 0.929411764705882, g: 0.47843137254902, b: 0.619607843137255},
		{name: '205', r: 0.898039215686275, g: 0.298039215686275, b: 0.486274509803922},
		{name: '206', r: 0.827450980392157, g: 0.0196078431372549, b: 0.27843137254902},
		{name: '207', r: 0.752941176470588, g: 0, b: 0.305882352941176},
		{name: '208', r: 0.556862745098039, g: 0.137254901960784, b: 0.266666666666667},
		{name: '209', r: 0.458823529411765, g: 0.149019607843137, b: 0.23921568627451},
		{name: '210', r: 1, g: 0.627450980392157, b: 0.749019607843137},
		{name: '211', r: 1, g: 0.466666666666667, b: 0.658823529411765},
		{name: '212', r: 0.976470588235294, g: 0.309803921568627, b: 0.556862745098039},
		{name: '213', r: 0.917647058823529, g: 0.0588235294117647, b: 0.419607843137255},
		{name: '214', r: 0.8, g: 0.00784313725490196, b: 0.337254901960784},
		{name: '215', r: 0.647058823529412, g: 0.0196078431372549, b: 0.266666666666667},
		{name: '216', r: 0.486274509803922, g: 0.117647058823529, b: 0.247058823529412},
		{name: '217', r: 0.956862745098039, g: 0.749019607843137, b: 0.819607843137255},
		{name: '218', r: 0.929411764705882, g: 0.447058823529412, b: 0.666666666666667},
		{name: '219', r: 0.886274509803922, g: 0.156862745098039, b: 0.509803921568627},
		{name: '220', r: 0.666666666666667, g: 0, b: 0.309803921568627},
		{name: '221', r: 0.576470588235294, g: 0, b: 0.258823529411765},
		{name: '222', r: 0.43921568627451, g: 0.0980392156862745, b: 0.23921568627451},
		{name: '223', r: 0.976470588235294, g: 0.576470588235294, b: 0.768627450980392},
		{name: '224', r: 0.956862745098039, g: 0.419607843137255, b: 0.686274509803922},
		{name: '225', r: 0.929411764705882, g: 0.156862745098039, b: 0.576470588235294},
		{name: '226', r: 0.83921568627451, g: 0.00784313725490196, b: 0.43921568627451},
		{name: '227', r: 0.67843137254902, g: 0, b: 0.356862745098039},
		{name: '228', r: 0.549019607843137, g: 0, b: 0.298039215686275},
		{name: '229', r: 0.427450980392157, g: 0.129411764705882, b: 0.247058823529412},
		{name: '230', r: 1, g: 0.627450980392157, b: 0.8},
		{name: '231', r: 0.988235294117647, g: 0.43921568627451, b: 0.729411764705882},
		{name: '232', r: 0.956862745098039, g: 0.247058823529412, b: 0.647058823529412},
		{name: '233', r: 0.807843137254902, g: 0, b: 0.486274509803922},
		{name: '234', r: 0.666666666666667, g: 0, b: 0.4},
		{name: '235', r: 0.556862745098039, g: 0.0196078431372549, b: 0.329411764705882},
		{name: '236', r: 0.976470588235294, g: 0.686274509803922, b: 0.827450980392157},
		{name: '2365', r: 0.968627450980392, g: 0.768627450980392, b: 0.847058823529412},
		{name: '237', r: 0.956862745098039, g: 0.517647058823529, b: 0.768627450980392},
		{name: '2375', r: 0.917647058823529, g: 0.419607843137255, b: 0.749019607843137},
		{name: '238', r: 0.929411764705882, g: 0.309803921568627, b: 0.686274509803922},
		{name: '2385', r: 0.858823529411765, g: 0.156862745098039, b: 0.647058823529412},
		{name: '239', r: 0.87843137254902, g: 0.129411764705882, b: 0.619607843137255},
		{name: '2395', r: 0.768627450980392, g: 0, b: 0.549019607843137},
		{name: '240', r: 0.768627450980392, g: 0.0588235294117647, b: 0.537254901960784},
		{name: '2405', r: 0.658823529411765, g: 0, b: 0.47843137254902},
		{name: '241', r: 0.67843137254902, g: 0, b: 0.458823529411765},
		{name: '2415', r: 0.607843137254902, g: 0, b: 0.43921568627451},
		{name: '242', r: 0.486274509803922, g: 0.109803921568627, b: 0.317647058823529},
		{name: '2425', r: 0.529411764705882, g: 0, b: 0.356862745098039},
		{name: '243', r: 0.949019607843137, g: 0.729411764705882, b: 0.847058823529412},
		{name: '244', r: 0.929411764705882, g: 0.627450980392157, b: 0.827450980392157},
		{name: '245', r: 0.909803921568627, g: 0.498039215686275, b: 0.788235294117647},
		{name: '246', r: 0.8, g: 0, b: 0.627450980392157},
		{name: '247', r: 0.717647058823529, g: 0, b: 0.556862745098039},
		{name: '248', r: 0.63921568627451, g: 0.0196078431372549, b: 0.498039215686275},
		{name: '249', r: 0.498039215686275, g: 0.156862745098039, b: 0.376470588235294},
		{name: '250', r: 0.929411764705882, g: 0.768627450980392, b: 0.866666666666667},
		{name: '251', r: 0.886274509803922, g: 0.619607843137255, b: 0.83921568627451},
		{name: '252', r: 0.827450980392157, g: 0.419607843137255, b: 0.776470588235294},
		{name: '253', r: 0.686274509803922, g: 0.137254901960784, b: 0.647058823529412},
		{name: '254', r: 0.627450980392157, g: 0.176470588235294, b: 0.588235294117647},
		{name: '255', r: 0.466666666666667, g: 0.176470588235294, b: 0.419607843137255},
		{name: '256', r: 0.898039215686275, g: 0.768627450980392, b: 0.83921568627451},
		{name: '2562', r: 0.847058823529412, g: 0.658823529411765, b: 0.847058823529412},
		{name: '2563', r: 0.819607843137255, g: 0.627450980392157, b: 0.8},
		{name: '2567', r: 0.749019607843137, g: 0.576470588235294, b: 0.8},
		{name: '257', r: 0.827450980392157, g: 0.647058823529412, b: 0.788235294117647},
		{name: '2572', r: 0.776470588235294, g: 0.529411764705882, b: 0.819607843137255},
		{name: '2573', r: 0.729411764705882, g: 0.486274509803922, b: 0.737254901960784},
		{name: '2577', r: 0.666666666666667, g: 0.447058823529412, b: 0.749019607843137},
		{name: '258', r: 0.607843137254902, g: 0.309803921568627, b: 0.588235294117647},
		{name: '2582', r: 0.666666666666667, g: 0.27843137254902, b: 0.729411764705882},
		{name: '2583', r: 0.619607843137255, g: 0.309803921568627, b: 0.647058823529412},
		{name: '2587', r: 0.556862745098039, g: 0.27843137254902, b: 0.67843137254902},
		{name: '259', r: 0.447058823529412, g: 0.0862745098039216, b: 0.419607843137255},
		{name: '2592', r: 0.576470588235294, g: 0.0588235294117647, b: 0.647058823529412},
		{name: '2593', r: 0.529411764705882, g: 0.168627450980392, b: 0.576470588235294},
		{name: '2597', r: 0.4, g: 0, b: 0.549019607843137},
		{name: '260', r: 0.407843137254902, g: 0.117647058823529, b: 0.356862745098039},
		{name: '2602', r: 0.509803921568627, g: 0.0470588235294118, b: 0.556862745098039},
		{name: '2603', r: 0.43921568627451, g: 0.0784313725490196, b: 0.47843137254902},
		{name: '2607', r: 0.356862745098039, g: 0.00784313725490196, b: 0.47843137254902},
		{name: '261', r: 0.368627450980392, g: 0.129411764705882, b: 0.329411764705882},
		{name: '2612', r: 0.43921568627451, g: 0.117647058823529, b: 0.447058823529412},
		{name: '2613', r: 0.4, g: 0.0666666666666667, b: 0.427450980392157},
		{name: '2617', r: 0.337254901960784, g: 0.0470588235294118, b: 0.43921568627451},
		{name: '262', r: 0.329411764705882, g: 0.137254901960784, b: 0.266666666666667},
		{name: '2622', r: 0.376470588235294, g: 0.176470588235294, b: 0.349019607843137},
		{name: '2623', r: 0.356862745098039, g: 0.0980392156862745, b: 0.368627450980392},
		{name: '2627', r: 0.298039215686275, g: 0.0784313725490196, b: 0.368627450980392},
		{name: '263', r: 0.87843137254902, g: 0.807843137254902, b: 0.87843137254902},
		{name: '2635', r: 0.788235294117647, g: 0.67843137254902, b: 0.847058823529412},
		{name: '264', r: 0.776470588235294, g: 0.666666666666667, b: 0.858823529411765},
		{name: '2645', r: 0.709803921568627, g: 0.568627450980392, b: 0.819607843137255},
		{name: '265', r: 0.588235294117647, g: 0.388235294117647, b: 0.768627450980392},
		{name: '2655', r: 0.607843137254902, g: 0.427450980392157, b: 0.776470588235294},
		{name: '266', r: 0.427450980392157, g: 0.156862745098039, b: 0.666666666666667},
		{name: '2665', r: 0.537254901960784, g: 0.309803921568627, b: 0.749019607843137},
		{name: '267', r: 0.349019607843137, g: 0.0666666666666667, b: 0.556862745098039},
		{name: '268', r: 0.309803921568627, g: 0.129411764705882, b: 0.43921568627451},
		{name: '2685', r: 0.337254901960784, g: 0, b: 0.549019607843137},
		{name: '269', r: 0.266666666666667, g: 0.137254901960784, b: 0.349019607843137},
		{name: '2695', r: 0.266666666666667, g: 0.137254901960784, b: 0.368627450980392},
		{name: '270', r: 0.729411764705882, g: 0.686274509803922, b: 0.827450980392157},
		{name: '2705', r: 0.67843137254902, g: 0.619607843137255, b: 0.827450980392157},
		{name: '2706', r: 0.819607843137255, g: 0.807843137254902, b: 0.866666666666667},
		{name: '2707', r: 0.749019607843137, g: 0.819607843137255, b: 0.898039215686275},
		{name: '2708', r: 0.686274509803922, g: 0.737254901960784, b: 0.858823529411765},
		{name: '271', r: 0.619607843137255, g: 0.568627450980392, b: 0.776470588235294},
		{name: '2715', r: 0.576470588235294, g: 0.47843137254902, b: 0.8},
		{name: '2716', r: 0.647058823529412, g: 0.627450980392157, b: 0.83921568627451},
		{name: '2717', r: 0.647058823529412, g: 0.729411764705882, b: 0.87843137254902},
		{name: '2718', r: 0.356862745098039, g: 0.466666666666667, b: 0.8},
		{name: '272', r: 0.537254901960784, g: 0.466666666666667, b: 0.729411764705882},
		{name: '2725', r: 0.447058823529412, g: 0.317647058823529, b: 0.737254901960784},
		{name: '2726', r: 0.4, g: 0.337254901960784, b: 0.737254901960784},
		{name: '2727', r: 0.368627450980392, g: 0.407843137254902, b: 0.768627450980392},
		{name: '2728', r: 0.188235294117647, g: 0.266666666666667, b: 0.709803921568627},
		{name: '273', r: 0.219607843137255, g: 0.0980392156862745, b: 0.47843137254902},
		{name: '2735', r: 0.309803921568627, g: 0, b: 0.576470588235294},
		{name: '2736', r: 0.286274509803922, g: 0.188235294117647, b: 0.67843137254902},
		{name: '2738', r: 0.176470588235294, g: 0, b: 0.556862745098039},
		{name: '274', r: 0.168627450980392, g: 0.0666666666666667, b: 0.4},
		{name: '2745', r: 0.247058823529412, g: 0, b: 0.466666666666667},
		{name: '2746', r: 0.247058823529412, g: 0.156862745098039, b: 0.576470588235294},
		{name: '2747', r: 0.109803921568627, g: 0.0784313725490196, b: 0.419607843137255},
		{name: '2748', r: 0.117647058823529, g: 0.109803921568627, b: 0.466666666666667},
		{name: '275', r: 0.149019607843137, g: 0.0588235294117647, b: 0.329411764705882},
		{name: '2755', r: 0.207843137254902, g: 0, b: 0.427450980392157},
		{name: '2756', r: 0.2, g: 0.156862745098039, b: 0.458823529411765},
		{name: '2757', r: 0.0784313725490196, g: 0.0862745098039216, b: 0.329411764705882},
		{name: '2758', r: 0.0980392156862745, g: 0.129411764705882, b: 0.407843137254902},
		{name: '276', r: 0.168627450980392, g: 0.129411764705882, b: 0.27843137254902},
		{name: '2765', r: 0.168627450980392, g: 0.0470588235294118, b: 0.337254901960784},
		{name: '2766', r: 0.168627450980392, g: 0.149019607843137, b: 0.356862745098039},
		{name: '2767', r: 0.0784313725490196, g: 0.129411764705882, b: 0.23921568627451},
		{name: '2768', r: 0.0666666666666667, g: 0.129411764705882, b: 0.317647058823529},
		{name: '277', r: 0.709803921568627, g: 0.819607843137255, b: 0.909803921568627},
		{name: '278', r: 0.6, g: 0.729411764705882, b: 0.866666666666667},
		{name: '279', r: 0.4, g: 0.537254901960784, b: 0.8},
		{name: '280', r: 0, g: 0.168627450980392, b: 0.498039215686275},
		{name: '281', r: 0, g: 0.156862745098039, b: 0.407843137254902},
		{name: '282', r: 0, g: 0.149019607843137, b: 0.329411764705882},
		{name: '283', r: 0.607843137254902, g: 0.768627450980392, b: 0.886274509803922},
		{name: '284', r: 0.458823529411765, g: 0.666666666666667, b: 0.858823529411765},
		{name: '285', r: 0.227450980392157, g: 0.458823529411765, b: 0.768627450980392},
		{name: '286', r: 0, g: 0.219607843137255, b: 0.658823529411765},
		{name: '287', r: 0, g: 0.219607843137255, b: 0.576470588235294},
		{name: '288', r: 0, g: 0.2, b: 0.498039215686275},
		{name: '289', r: 0, g: 0.149019607843137, b: 0.286274509803922},
		{name: '290', r: 0.768627450980392, g: 0.847058823529412, b: 0.886274509803922},
		{name: '2905', r: 0.576470588235294, g: 0.776470588235294, b: 0.87843137254902},
		{name: '291', r: 0.658823529411765, g: 0.807843137254902, b: 0.886274509803922},
		{name: '2915', r: 0.376470588235294, g: 0.686274509803922, b: 0.866666666666667},
		{name: '292', r: 0.458823529411765, g: 0.698039215686274, b: 0.866666666666667},
		{name: '2925', r: 0, g: 0.556862745098039, b: 0.83921568627451},
		{name: '293', r: 0, g: 0.317647058823529, b: 0.729411764705882},
		{name: '2935', r: 0, g: 0.356862745098039, b: 0.749019607843137},
		{name: '294', r: 0, g: 0.247058823529412, b: 0.529411764705882},
		{name: '2945', r: 0, g: 0.329411764705882, b: 0.627450980392157},
		{name: '295', r: 0, g: 0.219607843137255, b: 0.419607843137255},
		{name: '2955', r: 0, g: 0.23921568627451, b: 0.419607843137255},
		{name: '296', r: 0, g: 0.176470588235294, b: 0.27843137254902},
		{name: '2965', r: 0, g: 0.2, b: 0.298039215686275},
		{name: '297', r: 0.509803921568627, g: 0.776470588235294, b: 0.886274509803922},
		{name: '2975', r: 0.729411764705882, g: 0.87843137254902, b: 0.886274509803922},
		{name: '298', r: 0.317647058823529, g: 0.709803921568627, b: 0.87843137254902},
		{name: '2985', r: 0.317647058823529, g: 0.749019607843137, b: 0.886274509803922},
		{name: '299', r: 0, g: 0.63921568627451, b: 0.866666666666667},
		{name: '2995', r: 0, g: 0.647058823529412, b: 0.858823529411765},
		{name: '300', r: 0, g: 0.447058823529412, b: 0.776470588235294},
		{name: '3005', r: 0, g: 0.517647058823529, b: 0.788235294117647},
		{name: '301', r: 0, g: 0.356862745098039, b: 0.6},
		{name: '3015', r: 0, g: 0.43921568627451, b: 0.619607843137255},
		{name: '302', r: 0, g: 0.309803921568627, b: 0.427450980392157},
		{name: '3025', r: 0, g: 0.329411764705882, b: 0.419607843137255},
		{name: '303', r: 0, g: 0.247058823529412, b: 0.329411764705882},
		{name: '3035', r: 0, g: 0.266666666666667, b: 0.329411764705882},
		{name: '304', r: 0.647058823529412, g: 0.866666666666667, b: 0.886274509803922},
		{name: '305', r: 0.43921568627451, g: 0.807843137254902, b: 0.886274509803922},
		{name: '306', r: 0, g: 0.737254901960784, b: 0.886274509803922},
		{name: '306 2X', r: 0, g: 0.63921568627451, b: 0.819607843137255},
		{name: '307', r: 0, g: 0.47843137254902, b: 0.647058823529412},
		{name: '308', r: 0, g: 0.376470588235294, b: 0.486274509803922},
		{name: '309', r: 0, g: 0.247058823529412, b: 0.286274509803922},
		{name: '310', r: 0.447058823529412, g: 0.819607843137255, b: 0.866666666666667},
		{name: '3105', r: 0.498039215686275, g: 0.83921568627451, b: 0.858823529411765},
		{name: '311', r: 0.156862745098039, g: 0.768627450980392, b: 0.847058823529412},
		{name: '3115', r: 0.176470588235294, g: 0.776470588235294, b: 0.83921568627451},
		{name: '312', r: 0, g: 0.67843137254902, b: 0.776470588235294},
		{name: '3125', r: 0, g: 0.717647058823529, b: 0.776470588235294},
		{name: '313', r: 0, g: 0.6, b: 0.709803921568627},
		{name: '3135', r: 0, g: 0.607843137254902, b: 0.666666666666667},
		{name: '314', r: 0, g: 0.509803921568627, b: 0.607843137254902},
		{name: '3145', r: 0, g: 0.517647058823529, b: 0.556862745098039},
		{name: '315', r: 0, g: 0.419607843137255, b: 0.466666666666667},
		{name: '3155', r: 0, g: 0.427450980392157, b: 0.458823529411765},
		{name: '316', r: 0, g: 0.286274509803922, b: 0.309803921568627},
		{name: '3165', r: 0, g: 0.337254901960784, b: 0.356862745098039},
		{name: '317', r: 0.788235294117647, g: 0.909803921568627, b: 0.866666666666667},
		{name: '318', r: 0.576470588235294, g: 0.866666666666667, b: 0.858823529411765},
		{name: '319', r: 0.298039215686275, g: 0.807843137254902, b: 0.819607843137255},
		{name: '320', r: 0, g: 0.619607843137255, b: 0.627450980392157},
		{name: '320 2X', r: 0, g: 0.498039215686275, b: 0.509803921568627},
		{name: '321', r: 0, g: 0.529411764705882, b: 0.537254901960784},
		{name: '322', r: 0, g: 0.447058823529412, b: 0.447058823529412},
		{name: '323', r: 0, g: 0.4, b: 0.388235294117647},
		{name: '324', r: 0.666666666666667, g: 0.866666666666667, b: 0.83921568627451},
		{name: '3242', r: 0.529411764705882, g: 0.866666666666667, b: 0.819607843137255},
		{name: '3245', r: 0.549019607843137, g: 0.87843137254902, b: 0.819607843137255},
		{name: '3248', r: 0.47843137254902, g: 0.827450980392157, b: 0.756862745098039},
		{name: '325', r: 0.337254901960784, g: 0.788235294117647, b: 0.756862745098039},
		{name: '3252', r: 0.337254901960784, g: 0.83921568627451, b: 0.788235294117647},
		{name: '3255', r: 0.27843137254902, g: 0.83921568627451, b: 0.756862745098039},
		{name: '3258', r: 0.207843137254902, g: 0.768627450980392, b: 0.686274509803922},
		{name: '326', r: 0, g: 0.698039215686274, b: 0.666666666666667},
		{name: '3262', r: 0, g: 0.756862745098039, b: 0.709803921568627},
		{name: '3265', r: 0, g: 0.776470588235294, b: 0.698039215686274},
		{name: '3268', r: 0, g: 0.686274509803922, b: 0.6},
		{name: '327', r: 0, g: 0.549019607843137, b: 0.509803921568627},
		{name: '327 2X', r: 0, g: 0.537254901960784, b: 0.466666666666667},
		{name: '3272', r: 0, g: 0.666666666666667, b: 0.619607843137255},
		{name: '3275', r: 0, g: 0.698039215686274, b: 0.627450980392157},
		{name: '3278', r: 0, g: 0.607843137254902, b: 0.517647058823529},
		{name: '328', r: 0, g: 0.466666666666667, b: 0.43921568627451},
		{name: '3282', r: 0, g: 0.549019607843137, b: 0.509803921568627},
		{name: '3285', r: 0, g: 0.6, b: 0.529411764705882},
		{name: '3288', r: 0, g: 0.509803921568627, b: 0.43921568627451},
		{name: '329', r: 0, g: 0.427450980392157, b: 0.4},
		{name: '3292', r: 0, g: 0.376470588235294, b: 0.337254901960784},
		{name: '3295', r: 0, g: 0.509803921568627, b: 0.447058823529412},
		{name: '3298', r: 0, g: 0.419607843137255, b: 0.356862745098039},
		{name: '330', r: 0, g: 0.349019607843137, b: 0.317647058823529},
		{name: '3302', r: 0, g: 0.286274509803922, b: 0.247058823529412},
		{name: '3305', r: 0, g: 0.309803921568627, b: 0.258823529411765},
		{name: '3308', r: 0, g: 0.266666666666667, b: 0.219607843137255},
		{name: '331', r: 0.729411764705882, g: 0.917647058823529, b: 0.83921568627451},
		{name: '332', r: 0.627450980392157, g: 0.898039215686275, b: 0.807843137254902},
		{name: '333', r: 0.368627450980392, g: 0.866666666666667, b: 0.756862745098039},
		{name: '334', r: 0, g: 0.6, b: 0.486274509803922},
		{name: '335', r: 0, g: 0.486274509803922, b: 0.4},
		{name: '336', r: 0, g: 0.407843137254902, b: 0.329411764705882},
		{name: '337', r: 0.607843137254902, g: 0.858823529411765, b: 0.756862745098039},
		{name: '3375', r: 0.556862745098039, g: 0.886274509803922, b: 0.737254901960784},
		{name: '338', r: 0.47843137254902, g: 0.819607843137255, b: 0.709803921568627},
		{name: '3385', r: 0.329411764705882, g: 0.847058823529412, b: 0.658823529411765},
		{name: '339', r: 0, g: 0.698039215686274, b: 0.549019607843137},
		{name: '3395', r: 0, g: 0.788235294117647, b: 0.576470588235294},
		{name: '340', r: 0, g: 0.6, b: 0.466666666666667},
		{name: '3405', r: 0, g: 0.698039215686274, b: 0.47843137254902},
		{name: '341', r: 0, g: 0.47843137254902, b: 0.368627450980392},
		{name: '3415', r: 0, g: 0.486274509803922, b: 0.349019607843137},
		{name: '342', r: 0, g: 0.419607843137255, b: 0.329411764705882},
		{name: '3425', r: 0, g: 0.407843137254902, b: 0.27843137254902},
		{name: '343', r: 0, g: 0.337254901960784, b: 0.247058823529412},
		{name: '3435', r: 0.00784313725490196, g: 0.286274509803922, b: 0.188235294117647},
		{name: '344', r: 0.709803921568627, g: 0.886274509803922, b: 0.749019607843137},
		{name: '345', r: 0.588235294117647, g: 0.847058823529412, b: 0.686274509803922},
		{name: '346', r: 0.43921568627451, g: 0.807843137254902, b: 0.607843137254902},
		{name: '347', r: 0, g: 0.619607843137255, b: 0.376470588235294},
		{name: '348', r: 0, g: 0.529411764705882, b: 0.317647058823529},
		{name: '349', r: 0, g: 0.419607843137255, b: 0.247058823529412},
		{name: '350', r: 0.137254901960784, g: 0.309803921568627, b: 0.2},
		{name: '351', r: 0.709803921568627, g: 0.909803921568627, b: 0.749019607843137},
		{name: '352', r: 0.6, g: 0.898039215686275, b: 0.698039215686274},
		{name: '353', r: 0.517647058823529, g: 0.886274509803922, b: 0.658823529411765},
		{name: '354', r: 0, g: 0.717647058823529, b: 0.376470588235294},
		{name: '355', r: 0, g: 0.619607843137255, b: 0.286274509803922},
		{name: '356', r: 0, g: 0.47843137254902, b: 0.23921568627451},
		{name: '357', r: 0.129411764705882, g: 0.356862745098039, b: 0.2},
		{name: '358', r: 0.666666666666667, g: 0.866666666666667, b: 0.588235294117647},
		{name: '359', r: 0.627450980392157, g: 0.858823529411765, b: 0.556862745098039},
		{name: '360', r: 0.376470588235294, g: 0.776470588235294, b: 0.349019607843137},
		{name: '361', r: 0.117647058823529, g: 0.709803921568627, b: 0.227450980392157},
		{name: '362', r: 0.2, g: 0.619607843137255, b: 0.207843137254902},
		{name: '363', r: 0.23921568627451, g: 0.556862745098039, b: 0.2},
		{name: '364', r: 0.227450980392157, g: 0.466666666666667, b: 0.156862745098039},
		{name: '365', r: 0.827450980392157, g: 0.909803921568627, b: 0.63921568627451},
		{name: '366', r: 0.768627450980392, g: 0.898039215686275, b: 0.556862745098039},
		{name: '367', r: 0.666666666666667, g: 0.866666666666667, b: 0.427450980392157},
		{name: '368', r: 0.356862745098039, g: 0.749019607843137, b: 0.129411764705882},
		{name: '368 2X', r: 0, g: 0.619607843137255, b: 0.0588235294117647},
		{name: '369', r: 0.337254901960784, g: 0.666666666666667, b: 0.109803921568627},
		{name: '370', r: 0.337254901960784, g: 0.556862745098039, b: 0.0784313725490196},
		{name: '371', r: 0.337254901960784, g: 0.419607843137255, b: 0.129411764705882},
		{name: '372', r: 0.847058823529412, g: 0.929411764705882, b: 0.588235294117647},
		{name: '373', r: 0.807843137254902, g: 0.917647058823529, b: 0.509803921568627},
		{name: '374', r: 0.729411764705882, g: 0.909803921568627, b: 0.376470588235294},
		{name: '375', r: 0.549019607843137, g: 0.83921568627451, b: 0},
		{name: '375 2X', r: 0.329411764705882, g: 0.737254901960784, b: 0},
		{name: '376', r: 0.498039215686275, g: 0.729411764705882, b: 0},
		{name: '377', r: 0.43921568627451, g: 0.576470588235294, b: 0.00784313725490196},
		{name: '378', r: 0.337254901960784, g: 0.388235294117647, b: 0.0784313725490196},
		{name: '379', r: 0.87843137254902, g: 0.917647058823529, b: 0.407843137254902},
		{name: '380', r: 0.83921568627451, g: 0.898039215686275, b: 0.258823529411765},
		{name: '381', r: 0.8, g: 0.886274509803922, b: 0.149019607843137},
		{name: '382', r: 0.729411764705882, g: 0.847058823529412, b: 0.0392156862745098},
		{name: '382 2X', r: 0.619607843137255, g: 0.768627450980392, b: 0},
		{name: '383', r: 0.63921568627451, g: 0.686274509803922, b: 0.0274509803921569},
		{name: '384', r: 0.576470588235294, g: 0.6, b: 0.0196078431372549},
		{name: '385', r: 0.43921568627451, g: 0.43921568627451, b: 0.0784313725490196},
		{name: '386', r: 0.909803921568627, g: 0.929411764705882, b: 0.376470588235294},
		{name: '387', r: 0.87843137254902, g: 0.929411764705882, b: 0.266666666666667},
		{name: '388', r: 0.83921568627451, g: 0.909803921568627, b: 0.0588235294117647},
		{name: '389', r: 0.807843137254902, g: 0.87843137254902, b: 0.0274509803921569},
		{name: '390', r: 0.729411764705882, g: 0.768627450980392, b: 0.0196078431372549},
		{name: '391', r: 0.619607843137255, g: 0.619607843137255, b: 0.0274509803921569},
		{name: '392', r: 0.517647058823529, g: 0.509803921568627, b: 0.0196078431372549},
		{name: '393', r: 0.949019607843137, g: 0.937254901960784, b: 0.529411764705882},
		{name: '3935', r: 0.949019607843137, g: 0.929411764705882, b: 0.427450980392157},
		{name: '394', r: 0.917647058823529, g: 0.929411764705882, b: 0.207843137254902},
		{name: '3945', r: 0.937254901960784, g: 0.917647058823529, b: 0.0274509803921569},
		{name: '395', r: 0.898039215686275, g: 0.909803921568627, b: 0.0666666666666667},
		{name: '3955', r: 0.929411764705882, g: 0.886274509803922, b: 0.0666666666666667},
		{name: '396', r: 0.87843137254902, g: 0.886274509803922, b: 0.0470588235294118},
		{name: '3965', r: 0.909803921568627, g: 0.866666666666667, b: 0.0666666666666667},
		{name: '397', r: 0.756862745098039, g: 0.749019607843137, b: 0.0392156862745098},
		{name: '3975', r: 0.709803921568627, g: 0.658823529411765, b: 0.0470588235294118},
		{name: '398', r: 0.686274509803922, g: 0.658823529411765, b: 0.0392156862745098},
		{name: '3985', r: 0.6, g: 0.549019607843137, b: 0.0392156862745098},
		{name: '399', r: 0.6, g: 0.556862745098039, b: 0.0274509803921569},
		{name: '3995', r: 0.427450980392157, g: 0.376470588235294, b: 0.00784313725490196},
		{name: '400', r: 0.819607843137255, g: 0.776470588235294, b: 0.709803921568627},
		{name: '401', r: 0.756862745098039, g: 0.709803921568627, b: 0.647058823529412},
		{name: '402', r: 0.686274509803922, g: 0.647058823529412, b: 0.576470588235294},
		{name: '403', r: 0.6, g: 0.549019607843137, b: 0.486274509803922},
		{name: '404', r: 0.509803921568627, g: 0.458823529411765, b: 0.4},
		{name: '405', r: 0.419607843137255, g: 0.368627450980392, b: 0.309803921568627},
		{name: '406', r: 0.807843137254902, g: 0.756862745098039, b: 0.709803921568627},
		{name: '408', r: 0.658823529411765, g: 0.6, b: 0.549019607843137},
		{name: '409', r: 0.6, g: 0.537254901960784, b: 0.486274509803922},
		{name: '410', r: 0.486274509803922, g: 0.427450980392157, b: 0.388235294117647},
		{name: '411', r: 0.4, g: 0.349019607843137, b: 0.298039215686275},
		{name: '412', r: 0.23921568627451, g: 0.188235294117647, b: 0.156862745098039},
		{name: '413', r: 0.776470588235294, g: 0.756862745098039, b: 0.698039215686274},
		{name: '414', r: 0.709803921568627, g: 0.686274509803922, b: 0.627450980392157},
		{name: '415', r: 0.63921568627451, g: 0.619607843137255, b: 0.549019607843137},
		{name: '416', r: 0.556862745098039, g: 0.549019607843137, b: 0.47843137254902},
		{name: '417', r: 0.466666666666667, g: 0.447058823529412, b: 0.388235294117647},
		{name: '418', r: 0.376470588235294, g: 0.368627450980392, b: 0.309803921568627},
		{name: '419', r: 0.156862745098039, g: 0.156862745098039, b: 0.129411764705882},
		{name: '420', r: 0.819607843137255, g: 0.8, b: 0.749019607843137},
		{name: '421', r: 0.749019607843137, g: 0.729411764705882, b: 0.686274509803922},
		{name: '422', r: 0.686274509803922, g: 0.666666666666667, b: 0.63921568627451},
		{name: '423', r: 0.588235294117647, g: 0.576470588235294, b: 0.556862745098039},
		{name: '424', r: 0.509803921568627, g: 0.498039215686275, b: 0.466666666666667},
		{name: '425', r: 0.376470588235294, g: 0.376470588235294, b: 0.356862745098039},
		{name: '426', r: 0.168627450980392, g: 0.168627450980392, b: 0.156862745098039},
		{name: '427', r: 0.866666666666667, g: 0.858823529411765, b: 0.819607843137255},
		{name: '428', r: 0.819607843137255, g: 0.807843137254902, b: 0.776470588235294},
		{name: '429', r: 0.67843137254902, g: 0.686274509803922, b: 0.666666666666667},
		{name: '430', r: 0.568627450980392, g: 0.588235294117647, b: 0.576470588235294},
		{name: '431', r: 0.4, g: 0.427450980392157, b: 0.43921568627451},
		{name: '432', r: 0.266666666666667, g: 0.309803921568627, b: 0.317647058823529},
		{name: '433', r: 0.188235294117647, g: 0.219607843137255, b: 0.227450980392157},
		{name: '433 2X', r: 0.0392156862745098, g: 0.0470588235294118, b: 0.0666666666666667},
		{name: '434', r: 0.87843137254902, g: 0.819607843137255, b: 0.776470588235294},
		{name: '435', r: 0.827450980392157, g: 0.749019607843137, b: 0.717647058823529},
		{name: '436', r: 0.737254901960784, g: 0.647058823529412, b: 0.619607843137255},
		{name: '437', r: 0.549019607843137, g: 0.43921568627451, b: 0.419607843137255},
		{name: '438', r: 0.349019607843137, g: 0.247058823529412, b: 0.23921568627451},
		{name: '439', r: 0.286274509803922, g: 0.207843137254902, b: 0.2},
		{name: '440', r: 0.247058823529412, g: 0.188235294117647, b: 0.168627450980392},
		{name: '441', r: 0.819607843137255, g: 0.819607843137255, b: 0.776470588235294},
		{name: '442', r: 0.729411764705882, g: 0.749019607843137, b: 0.717647058823529},
		{name: '443', r: 0.63921568627451, g: 0.658823529411765, b: 0.63921568627451},
		{name: '444', r: 0.537254901960784, g: 0.556862745098039, b: 0.549019607843137},
		{name: '445', r: 0.337254901960784, g: 0.349019607843137, b: 0.349019607843137},
		{name: '446', r: 0.286274509803922, g: 0.298039215686275, b: 0.286274509803922},
		{name: '447', r: 0.247058823529412, g: 0.247058823529412, b: 0.219607843137255},
		{name: '448', r: 0.329411764705882, g: 0.27843137254902, b: 0.176470588235294},
		{name: '4485', r: 0.376470588235294, g: 0.298039215686275, b: 0.0666666666666667},
		{name: '449', r: 0.329411764705882, g: 0.27843137254902, b: 0.149019607843137},
		{name: '4495', r: 0.529411764705882, g: 0.458823529411765, b: 0.188235294117647},
		{name: '450', r: 0.376470588235294, g: 0.329411764705882, b: 0.168627450980392},
		{name: '4505', r: 0.627450980392157, g: 0.568627450980392, b: 0.317647058823529},
		{name: '451', r: 0.67843137254902, g: 0.627450980392157, b: 0.47843137254902},
		{name: '4515', r: 0.737254901960784, g: 0.67843137254902, b: 0.458823529411765},
		{name: '452', r: 0.768627450980392, g: 0.717647058823529, b: 0.588235294117647},
		{name: '4525', r: 0.8, g: 0.749019607843137, b: 0.556862745098039},
		{name: '453', r: 0.83921568627451, g: 0.8, b: 0.686274509803922},
		{name: '4535', r: 0.858823529411765, g: 0.807843137254902, b: 0.647058823529412},
		{name: '454', r: 0.886274509803922, g: 0.847058823529412, b: 0.749019607843137},
		{name: '4545', r: 0.898039215686275, g: 0.858823529411765, b: 0.729411764705882},
		{name: '455', r: 0.4, g: 0.337254901960784, b: 0.0784313725490196},
		{name: '456', r: 0.6, g: 0.529411764705882, b: 0.0784313725490196},
		{name: '457', r: 0.709803921568627, g: 0.607843137254902, b: 0.0470588235294118},
		{name: '458', r: 0.866666666666667, g: 0.8, b: 0.419607843137255},
		{name: '459', r: 0.886274509803922, g: 0.83921568627451, b: 0.486274509803922},
		{name: '460', r: 0.917647058823529, g: 0.866666666666667, b: 0.588235294117647},
		{name: '461', r: 0.929411764705882, g: 0.898039215686275, b: 0.67843137254902},
		{name: '462', r: 0.356862745098039, g: 0.27843137254902, b: 0.137254901960784},
		{name: '4625', r: 0.27843137254902, g: 0.137254901960784, b: 0.0666666666666667},
		{name: '463', r: 0.458823529411765, g: 0.329411764705882, b: 0.149019607843137},
		{name: '4635', r: 0.549019607843137, g: 0.349019607843137, b: 0.2},
		{name: '464', r: 0.529411764705882, g: 0.376470588235294, b: 0.156862745098039},
		{name: '464 2X', r: 0.43921568627451, g: 0.258823529411765, b: 0.0784313725490196},
		{name: '4645', r: 0.698039215686274, g: 0.509803921568627, b: 0.376470588235294},
		{name: '465', r: 0.756862745098039, g: 0.658823529411765, b: 0.458823529411765},
		{name: '4655', r: 0.768627450980392, g: 0.6, b: 0.466666666666667},
		{name: '466', r: 0.819607843137255, g: 0.749019607843137, b: 0.568627450980392},
		{name: '4665', r: 0.847058823529412, g: 0.709803921568627, b: 0.588235294117647},
		{name: '467', r: 0.866666666666667, g: 0.8, b: 0.647058823529412},
		{name: '4675', r: 0.898039215686275, g: 0.776470588235294, b: 0.666666666666667},
		{name: '468', r: 0.886274509803922, g: 0.83921568627451, b: 0.709803921568627},
		{name: '4685', r: 0.929411764705882, g: 0.827450980392157, b: 0.737254901960784},
		{name: '469', r: 0.376470588235294, g: 0.2, b: 0.0666666666666667},
		{name: '4695', r: 0.317647058823529, g: 0.149019607843137, b: 0.109803921568627},
		{name: '470', r: 0.607843137254902, g: 0.309803921568627, b: 0.0980392156862745},
		{name: '4705', r: 0.486274509803922, g: 0.317647058823529, b: 0.23921568627451},
		{name: '471', r: 0.737254901960784, g: 0.368627450980392, b: 0.117647058823529},
		{name: '471 2X', r: 0.63921568627451, g: 0.266666666666667, b: 0.00784313725490196},
		{name: '4715', r: 0.6, g: 0.43921568627451, b: 0.356862745098039},
		{name: '472', r: 0.917647058823529, g: 0.666666666666667, b: 0.47843137254902},
		{name: '4725', r: 0.709803921568627, g: 0.568627450980392, b: 0.486274509803922},
		{name: '473', r: 0.956862745098039, g: 0.768627450980392, b: 0.627450980392157},
		{name: '4735', r: 0.8, g: 0.686274509803922, b: 0.607843137254902},
		{name: '474', r: 0.956862745098039, g: 0.8, b: 0.666666666666667},
		{name: '4745', r: 0.847058823529412, g: 0.749019607843137, b: 0.666666666666667},
		{name: '475', r: 0.968627450980392, g: 0.827450980392157, b: 0.709803921568627},
		{name: '4755', r: 0.886274509803922, g: 0.8, b: 0.729411764705882},
		{name: '476', r: 0.349019607843137, g: 0.23921568627451, b: 0.168627450980392},
		{name: '477', r: 0.388235294117647, g: 0.219607843137255, b: 0.149019607843137},
		{name: '478', r: 0.47843137254902, g: 0.247058823529412, b: 0.156862745098039},
		{name: '479', r: 0.686274509803922, g: 0.537254901960784, b: 0.43921568627451},
		{name: '480', r: 0.827450980392157, g: 0.717647058823529, b: 0.63921568627451},
		{name: '481', r: 0.87843137254902, g: 0.8, b: 0.729411764705882},
		{name: '482', r: 0.898039215686275, g: 0.827450980392157, b: 0.756862745098039},
		{name: '483', r: 0.419607843137255, g: 0.188235294117647, b: 0.129411764705882},
		{name: '484', r: 0.607843137254902, g: 0.188235294117647, b: 0.109803921568627},
		{name: '485', r: 0.847058823529412, g: 0.117647058823529, b: 0.0196078431372549},
		{name: '485 2X', r: 0.8, g: 0.0470588235294118, b: 0},
		{name: '486', r: 0.929411764705882, g: 0.619607843137255, b: 0.517647058823529},
		{name: '487', r: 0.937254901960784, g: 0.709803921568627, b: 0.627450980392157},
		{name: '488', r: 0.949019607843137, g: 0.768627450980392, b: 0.686274509803922},
		{name: '489', r: 0.949019607843137, g: 0.819607843137255, b: 0.749019607843137},
		{name: '490', r: 0.356862745098039, g: 0.149019607843137, b: 0.149019607843137},
		{name: '491', r: 0.458823529411765, g: 0.156862745098039, b: 0.156862745098039},
		{name: '492', r: 0.568627450980392, g: 0.2, b: 0.219607843137255},
		{name: '494', r: 0.949019607843137, g: 0.67843137254902, b: 0.698039215686274},
		{name: '495', r: 0.956862745098039, g: 0.737254901960784, b: 0.749019607843137},
		{name: '496', r: 0.968627450980392, g: 0.788235294117647, b: 0.776470588235294},
		{name: '497', r: 0.317647058823529, g: 0.156862745098039, b: 0.149019607843137},
		{name: '4975', r: 0.266666666666667, g: 0.117647058823529, b: 0.109803921568627},
		{name: '498', r: 0.427450980392157, g: 0.2, b: 0.168627450980392},
		{name: '4985', r: 0.517647058823529, g: 0.286274509803922, b: 0.286274509803922},
		{name: '499', r: 0.47843137254902, g: 0.219607843137255, b: 0.176470588235294},
		{name: '4995', r: 0.647058823529412, g: 0.419607843137255, b: 0.427450980392157},
		{name: '500', r: 0.807843137254902, g: 0.537254901960784, b: 0.549019607843137},
		{name: '5005', r: 0.737254901960784, g: 0.529411764705882, b: 0.529411764705882},
		{name: '501', r: 0.917647058823529, g: 0.698039215686274, b: 0.698039215686274},
		{name: '5015', r: 0.847058823529412, g: 0.67843137254902, b: 0.658823529411765},
		{name: '502', r: 0.949019607843137, g: 0.776470588235294, b: 0.768627450980392},
		{name: '5025', r: 0.886274509803922, g: 0.737254901960784, b: 0.717647058823529},
		{name: '503', r: 0.956862745098039, g: 0.819607843137255, b: 0.8},
		{name: '5035', r: 0.929411764705882, g: 0.807843137254902, b: 0.776470588235294},
		{name: '504', r: 0.317647058823529, g: 0.117647058823529, b: 0.149019607843137},
		{name: '505', r: 0.4, g: 0.117647058823529, b: 0.168627450980392},
		{name: '506', r: 0.47843137254902, g: 0.149019607843137, b: 0.219607843137255},
		{name: '507', r: 0.847058823529412, g: 0.537254901960784, b: 0.607843137254902},
		{name: '508', r: 0.909803921568627, g: 0.647058823529412, b: 0.686274509803922},
		{name: '509', r: 0.949019607843137, g: 0.729411764705882, b: 0.749019607843137},
		{name: '510', r: 0.956862745098039, g: 0.776470588235294, b: 0.788235294117647},
		{name: '511', r: 0.376470588235294, g: 0.129411764705882, b: 0.266666666666667},
		{name: '5115', r: 0.309803921568627, g: 0.129411764705882, b: 0.227450980392157},
		{name: '512', r: 0.517647058823529, g: 0.129411764705882, b: 0.419607843137255},
		{name: '5125', r: 0.458823529411765, g: 0.27843137254902, b: 0.376470588235294},
		{name: '513', r: 0.619607843137255, g: 0.137254901960784, b: 0.529411764705882},
		{name: '5135', r: 0.576470588235294, g: 0.419607843137255, b: 0.498039215686275},
		{name: '514', r: 0.847058823529412, g: 0.517647058823529, b: 0.737254901960784},
		{name: '5145', r: 0.67843137254902, g: 0.529411764705882, b: 0.6},
		{name: '515', r: 0.909803921568627, g: 0.63921568627451, b: 0.788235294117647},
		{name: '5155', r: 0.8, g: 0.686274509803922, b: 0.717647058823529},
		{name: '516', r: 0.949019607843137, g: 0.729411764705882, b: 0.827450980392157},
		{name: '5165', r: 0.87843137254902, g: 0.788235294117647, b: 0.8},
		{name: '517', r: 0.956862745098039, g: 0.8, b: 0.847058823529412},
		{name: '5175', r: 0.909803921568627, g: 0.83921568627451, b: 0.819607843137255},
		{name: '518', r: 0.317647058823529, g: 0.176470588235294, b: 0.266666666666667},
		{name: '5185', r: 0.27843137254902, g: 0.156862745098039, b: 0.207843137254902},
		{name: '519', r: 0.388235294117647, g: 0.188235294117647, b: 0.368627450980392},
		{name: '5195', r: 0.349019607843137, g: 0.2, b: 0.266666666666667},
		{name: '520', r: 0.43921568627451, g: 0.207843137254902, b: 0.447058823529412},
		{name: '5205', r: 0.556862745098039, g: 0.407843137254902, b: 0.466666666666667},
		{name: '521', r: 0.709803921568627, g: 0.549019607843137, b: 0.698039215686274},
		{name: '5215', r: 0.709803921568627, g: 0.576470588235294, b: 0.607843137254902},
		{name: '522', r: 0.776470588235294, g: 0.63921568627451, b: 0.756862745098039},
		{name: '5225', r: 0.8, g: 0.67843137254902, b: 0.686274509803922},
		{name: '523', r: 0.827450980392157, g: 0.717647058823529, b: 0.8},
		{name: '5235', r: 0.866666666666667, g: 0.776470588235294, b: 0.768627450980392},
		{name: '524', r: 0.886274509803922, g: 0.8, b: 0.827450980392157},
		{name: '5245', r: 0.898039215686275, g: 0.827450980392157, b: 0.8},
		{name: '525', r: 0.317647058823529, g: 0.149019607843137, b: 0.329411764705882},
		{name: '5255', r: 0.207843137254902, g: 0.149019607843137, b: 0.309803921568627},
		{name: '526', r: 0.407843137254902, g: 0.129411764705882, b: 0.47843137254902},
		{name: '5265', r: 0.286274509803922, g: 0.23921568627451, b: 0.388235294117647},
		{name: '527', r: 0.47843137254902, g: 0.117647058823529, b: 0.6},
		{name: '5275', r: 0.376470588235294, g: 0.337254901960784, b: 0.466666666666667},
		{name: '528', r: 0.686274509803922, g: 0.447058823529412, b: 0.756862745098039},
		{name: '5285', r: 0.549019607843137, g: 0.509803921568627, b: 0.6},
		{name: '529', r: 0.807843137254902, g: 0.63921568627451, b: 0.827450980392157},
		{name: '5295', r: 0.698039215686274, g: 0.658823529411765, b: 0.709803921568627},
		{name: '530', r: 0.83921568627451, g: 0.686274509803922, b: 0.83921568627451},
		{name: '5305', r: 0.8, g: 0.756862745098039, b: 0.776470588235294},
		{name: '531', r: 0.898039215686275, g: 0.776470588235294, b: 0.858823529411765},
		{name: '5315', r: 0.858823529411765, g: 0.827450980392157, b: 0.827450980392157},
		{name: '532', r: 0.207843137254902, g: 0.219607843137255, b: 0.258823529411765},
		{name: '533', r: 0.207843137254902, g: 0.247058823529412, b: 0.356862745098039},
		{name: '534', r: 0.227450980392157, g: 0.286274509803922, b: 0.447058823529412},
		{name: '535', r: 0.607843137254902, g: 0.63921568627451, b: 0.717647058823529},
		{name: '536', r: 0.67843137254902, g: 0.698039215686274, b: 0.756862745098039},
		{name: '537', r: 0.768627450980392, g: 0.776470588235294, b: 0.807843137254902},
		{name: '538', r: 0.83921568627451, g: 0.827450980392157, b: 0.83921568627451},
		{name: '539', r: 0, g: 0.188235294117647, b: 0.286274509803922},
		{name: '5395', r: 0.00784313725490196, g: 0.156862745098039, b: 0.227450980392157},
		{name: '540', r: 0, g: 0.2, b: 0.356862745098039},
		{name: '5405', r: 0.247058823529412, g: 0.376470588235294, b: 0.458823529411765},
		{name: '541', r: 0, g: 0.247058823529412, b: 0.466666666666667},
		{name: '5415', r: 0.376470588235294, g: 0.486274509803922, b: 0.549019607843137},
		{name: '542', r: 0.4, g: 0.576470588235294, b: 0.737254901960784},
		{name: '5425', r: 0.517647058823529, g: 0.6, b: 0.647058823529412},
		{name: '543', r: 0.576470588235294, g: 0.717647058823529, b: 0.819607843137255},
		{name: '5435', r: 0.686274509803922, g: 0.737254901960784, b: 0.749019607843137},
		{name: '544', r: 0.717647058823529, g: 0.8, b: 0.858823529411765},
		{name: '5445', r: 0.768627450980392, g: 0.8, b: 0.8},
		{name: '545', r: 0.768627450980392, g: 0.827450980392157, b: 0.866666666666667},
		{name: '5455', r: 0.83921568627451, g: 0.847058823529412, b: 0.827450980392157},
		{name: '546', r: 0.0470588235294118, g: 0.219607843137255, b: 0.266666666666667},
		{name: '5463', r: 0, g: 0.207843137254902, b: 0.227450980392157},
		{name: '5467', r: 0.0980392156862745, g: 0.219607843137255, b: 0.2},
		{name: '547', r: 0, g: 0.247058823529412, b: 0.329411764705882},
		{name: '5473', r: 0.149019607843137, g: 0.407843137254902, b: 0.427450980392157},
		{name: '5477', r: 0.227450980392157, g: 0.337254901960784, b: 0.309803921568627},
		{name: '548', r: 0, g: 0.266666666666667, b: 0.349019607843137},
		{name: '5483', r: 0.376470588235294, g: 0.568627450980392, b: 0.568627450980392},
		{name: '5487', r: 0.4, g: 0.486274509803922, b: 0.447058823529412},
		{name: '549', r: 0.368627450980392, g: 0.6, b: 0.666666666666667},
		{name: '5493', r: 0.549019607843137, g: 0.686274509803922, b: 0.67843137254902},
		{name: '5497', r: 0.568627450980392, g: 0.63921568627451, b: 0.6},
		{name: '550', r: 0.529411764705882, g: 0.686274509803922, b: 0.749019607843137},
		{name: '5503', r: 0.666666666666667, g: 0.768627450980392, b: 0.749019607843137},
		{name: '5507', r: 0.686274509803922, g: 0.729411764705882, b: 0.698039215686274},
		{name: '551', r: 0.63921568627451, g: 0.756862745098039, b: 0.788235294117647},
		{name: '5513', r: 0.807843137254902, g: 0.847058823529412, b: 0.819607843137255},
		{name: '5517', r: 0.788235294117647, g: 0.807843137254902, b: 0.768627450980392},
		{name: '552', r: 0.768627450980392, g: 0.83921568627451, b: 0.83921568627451},
		{name: '5523', r: 0.83921568627451, g: 0.866666666666667, b: 0.83921568627451},
		{name: '5527', r: 0.807843137254902, g: 0.819607843137255, b: 0.776470588235294},
		{name: '553', r: 0.137254901960784, g: 0.266666666666667, b: 0.207843137254902},
		{name: '5535', r: 0.129411764705882, g: 0.23921568627451, b: 0.188235294117647},
		{name: '554', r: 0.0980392156862745, g: 0.368627450980392, b: 0.27843137254902},
		{name: '5545', r: 0.309803921568627, g: 0.427450980392157, b: 0.368627450980392},
		{name: '555', r: 0.0274509803921569, g: 0.427450980392157, b: 0.329411764705882},
		{name: '5555', r: 0.466666666666667, g: 0.568627450980392, b: 0.509803921568627},
		{name: '556', r: 0.47843137254902, g: 0.658823529411765, b: 0.568627450980392},
		{name: '5565', r: 0.588235294117647, g: 0.666666666666667, b: 0.6},
		{name: '557', r: 0.63921568627451, g: 0.756862745098039, b: 0.67843137254902},
		{name: '5575', r: 0.686274509803922, g: 0.749019607843137, b: 0.67843137254902},
		{name: '558', r: 0.717647058823529, g: 0.807843137254902, b: 0.737254901960784},
		{name: '5585', r: 0.768627450980392, g: 0.807843137254902, b: 0.749019607843137},
		{name: '559', r: 0.776470588235294, g: 0.83921568627451, b: 0.768627450980392},
		{name: '5595', r: 0.847058823529412, g: 0.858823529411765, b: 0.8},
		{name: '560', r: 0.168627450980392, g: 0.298039215686275, b: 0.247058823529412},
		{name: '5605', r: 0.137254901960784, g: 0.227450980392157, b: 0.176470588235294},
		{name: '561', r: 0.149019607843137, g: 0.4, b: 0.349019607843137},
		{name: '5615', r: 0.329411764705882, g: 0.407843137254902, b: 0.337254901960784},
		{name: '562', r: 0.117647058823529, g: 0.47843137254902, b: 0.427450980392157},
		{name: '5625', r: 0.447058823529412, g: 0.517647058823529, b: 0.43921568627451},
		{name: '563', r: 0.498039215686275, g: 0.737254901960784, b: 0.666666666666667},
		{name: '5635', r: 0.619607843137255, g: 0.666666666666667, b: 0.6},
		{name: '564', r: 0.0196078431372549, g: 0.43921568627451, b: 0.368627450980392},
		{name: '5645', r: 0.737254901960784, g: 0.756862745098039, b: 0.698039215686274},
		{name: '565', r: 0.737254901960784, g: 0.858823529411765, b: 0.8},
		{name: '5655', r: 0.776470588235294, g: 0.8, b: 0.729411764705882},
		{name: '566', r: 0.819607843137255, g: 0.886274509803922, b: 0.827450980392157},
		{name: '5665', r: 0.83921568627451, g: 0.83921568627451, b: 0.776470588235294},
		{name: '567', r: 0.149019607843137, g: 0.317647058823529, b: 0.258823529411765},
		{name: '568', r: 0, g: 0.447058823529412, b: 0.388235294117647},
		{name: '569', r: 0, g: 0.529411764705882, b: 0.447058823529412},
		{name: '570', r: 0.498039215686275, g: 0.776470588235294, b: 0.698039215686274},
		{name: '571', r: 0.666666666666667, g: 0.858823529411765, b: 0.776470588235294},
		{name: '572', r: 0.737254901960784, g: 0.886274509803922, b: 0.807843137254902},
		{name: '573', r: 0.8, g: 0.898039215686275, b: 0.83921568627451},
		{name: '574', r: 0.286274509803922, g: 0.349019607843137, b: 0.156862745098039},
		{name: '5743', r: 0.247058823529412, g: 0.286274509803922, b: 0.149019607843137},
		{name: '5747', r: 0.258823529411765, g: 0.27843137254902, b: 0.0862745098039216},
		{name: '575', r: 0.329411764705882, g: 0.466666666666667, b: 0.188235294117647},
		{name: '5753', r: 0.368627450980392, g: 0.4, b: 0.227450980392157},
		{name: '5757', r: 0.419607843137255, g: 0.43921568627451, b: 0.168627450980392},
		{name: '576', r: 0.376470588235294, g: 0.556862745098039, b: 0.227450980392157},
		{name: '5763', r: 0.466666666666667, g: 0.486274509803922, b: 0.309803921568627},
		{name: '5767', r: 0.549019607843137, g: 0.568627450980392, b: 0.309803921568627},
		{name: '577', r: 0.709803921568627, g: 0.8, b: 0.556862745098039},
		{name: '5773', r: 0.607843137254902, g: 0.619607843137255, b: 0.447058823529412},
		{name: '5777', r: 0.666666666666667, g: 0.67843137254902, b: 0.458823529411765},
		{name: '578', r: 0.776470588235294, g: 0.83921568627451, b: 0.627450980392157},
		{name: '5783', r: 0.709803921568627, g: 0.709803921568627, b: 0.556862745098039},
		{name: '5787', r: 0.776470588235294, g: 0.776470588235294, b: 0.6},
		{name: '579', r: 0.788235294117647, g: 0.83921568627451, b: 0.63921568627451},
		{name: '5793', r: 0.776470588235294, g: 0.776470588235294, b: 0.647058823529412},
		{name: '5797', r: 0.827450980392157, g: 0.819607843137255, b: 0.666666666666667},
		{name: '580', r: 0.847058823529412, g: 0.866666666666667, b: 0.709803921568627},
		{name: '5803', r: 0.847058823529412, g: 0.83921568627451, b: 0.717647058823529},
		{name: '5807', r: 0.87843137254902, g: 0.866666666666667, b: 0.737254901960784},
		{name: '581', r: 0.376470588235294, g: 0.368627450980392, b: 0.0666666666666667},
		{name: '5815', r: 0.286274509803922, g: 0.266666666666667, b: 0.0666666666666667},
		{name: '582', r: 0.529411764705882, g: 0.537254901960784, b: 0.0196078431372549},
		{name: '5825', r: 0.458823529411765, g: 0.43921568627451, b: 0.168627450980392},
		{name: '583', r: 0.666666666666667, g: 0.729411764705882, b: 0.0392156862745098},
		{name: '5835', r: 0.619607843137255, g: 0.6, b: 0.349019607843137},
		{name: '584', r: 0.807843137254902, g: 0.83921568627451, b: 0.286274509803922},
		{name: '5845', r: 0.698039215686274, g: 0.666666666666667, b: 0.43921568627451},
		{name: '585', r: 0.858823529411765, g: 0.87843137254902, b: 0.419607843137255},
		{name: '5855', r: 0.8, g: 0.776470588235294, b: 0.576470588235294},
		{name: '586', r: 0.886274509803922, g: 0.898039215686275, b: 0.517647058823529},
		{name: '5865', r: 0.83921568627451, g: 0.807843137254902, b: 0.63921568627451},
		{name: '587', r: 0.909803921568627, g: 0.909803921568627, b: 0.607843137254902},
		{name: '5875', r: 0.87843137254902, g: 0.858823529411765, b: 0.709803921568627},
		{name: '600', r: 0.956862745098039, g: 0.929411764705882, b: 0.686274509803922},
		{name: '601', r: 0.949019607843137, g: 0.929411764705882, b: 0.619607843137255},
		{name: '602', r: 0.949019607843137, g: 0.917647058823529, b: 0.529411764705882},
		{name: '603', r: 0.929411764705882, g: 0.909803921568627, b: 0.356862745098039},
		{name: '604', r: 0.909803921568627, g: 0.866666666666667, b: 0.129411764705882},
		{name: '605', r: 0.866666666666667, g: 0.807843137254902, b: 0.0666666666666667},
		{name: '606', r: 0.827450980392157, g: 0.749019607843137, b: 0.0666666666666667},
		{name: '607', r: 0.949019607843137, g: 0.917647058823529, b: 0.737254901960784},
		{name: '608', r: 0.937254901960784, g: 0.909803921568627, b: 0.67843137254902},
		{name: '609', r: 0.917647058823529, g: 0.898039215686275, b: 0.588235294117647},
		{name: '610', r: 0.886274509803922, g: 0.858823529411765, b: 0.447058823529412},
		{name: '611', r: 0.83921568627451, g: 0.807843137254902, b: 0.286274509803922},
		{name: '612', r: 0.768627450980392, g: 0.729411764705882, b: 0},
		{name: '613', r: 0.686274509803922, g: 0.627450980392157, b: 0.0470588235294118},
		{name: '614', r: 0.917647058823529, g: 0.886274509803922, b: 0.717647058823529},
		{name: '615', r: 0.886274509803922, g: 0.858823529411765, b: 0.666666666666667},
		{name: '616', r: 0.866666666666667, g: 0.83921568627451, b: 0.607843137254902},
		{name: '617', r: 0.8, g: 0.768627450980392, b: 0.486274509803922},
		{name: '618', r: 0.709803921568627, g: 0.666666666666667, b: 0.349019607843137},
		{name: '619', r: 0.588235294117647, g: 0.549019607843137, b: 0.156862745098039},
		{name: '620', r: 0.517647058823529, g: 0.466666666666667, b: 0.0666666666666667},
		{name: '621', r: 0.847058823529412, g: 0.866666666666667, b: 0.807843137254902},
		{name: '622', r: 0.756862745098039, g: 0.819607843137255, b: 0.749019607843137},
		{name: '623', r: 0.647058823529412, g: 0.749019607843137, b: 0.666666666666667},
		{name: '624', r: 0.498039215686275, g: 0.627450980392157, b: 0.549019607843137},
		{name: '625', r: 0.356862745098039, g: 0.529411764705882, b: 0.447058823529412},
		{name: '626', r: 0.129411764705882, g: 0.329411764705882, b: 0.247058823529412},
		{name: '627', r: 0.0470588235294118, g: 0.188235294117647, b: 0.149019607843137},
		{name: '628', r: 0.8, g: 0.886274509803922, b: 0.866666666666667},
		{name: '629', r: 0.698039215686274, g: 0.847058823529412, b: 0.847058823529412},
		{name: '630', r: 0.549019607843137, g: 0.8, b: 0.827450980392157},
		{name: '631', r: 0.329411764705882, g: 0.717647058823529, b: 0.776470588235294},
		{name: '632', r: 0, g: 0.627450980392157, b: 0.729411764705882},
		{name: '633', r: 0, g: 0.498039215686275, b: 0.6},
		{name: '634', r: 0, g: 0.4, b: 0.498039215686275},
		{name: '635', r: 0.729411764705882, g: 0.87843137254902, b: 0.87843137254902},
		{name: '636', r: 0.6, g: 0.83921568627451, b: 0.866666666666667},
		{name: '637', r: 0.419607843137255, g: 0.788235294117647, b: 0.858823529411765},
		{name: '638', r: 0, g: 0.709803921568627, b: 0.83921568627451},
		{name: '639', r: 0, g: 0.627450980392157, b: 0.768627450980392},
		{name: '640', r: 0, g: 0.549019607843137, b: 0.698039215686274},
		{name: '641', r: 0, g: 0.47843137254902, b: 0.647058823529412},
		{name: '642', r: 0.819607843137255, g: 0.847058823529412, b: 0.847058823529412},
		{name: '643', r: 0.776470588235294, g: 0.819607843137255, b: 0.83921568627451},
		{name: '644', r: 0.607843137254902, g: 0.686274509803922, b: 0.768627450980392},
		{name: '645', r: 0.466666666666667, g: 0.588235294117647, b: 0.698039215686274},
		{name: '646', r: 0.368627450980392, g: 0.509803921568627, b: 0.63921568627451},
		{name: '647', r: 0.149019607843137, g: 0.329411764705882, b: 0.486274509803922},
		{name: '648', r: 0, g: 0.188235294117647, b: 0.368627450980392},
		{name: '649', r: 0.83921568627451, g: 0.83921568627451, b: 0.847058823529412},
		{name: '650', r: 0.749019607843137, g: 0.776470588235294, b: 0.819607843137255},
		{name: '651', r: 0.607843137254902, g: 0.666666666666667, b: 0.749019607843137},
		{name: '652', r: 0.427450980392157, g: 0.529411764705882, b: 0.658823529411765},
		{name: '653', r: 0.2, g: 0.337254901960784, b: 0.529411764705882},
		{name: '654', r: 0.0588235294117647, g: 0.168627450980392, b: 0.356862745098039},
		{name: '655', r: 0.0470588235294118, g: 0.109803921568627, b: 0.27843137254902},
		{name: '656', r: 0.83921568627451, g: 0.858823529411765, b: 0.87843137254902},
		{name: '657', r: 0.756862745098039, g: 0.788235294117647, b: 0.866666666666667},
		{name: '658', r: 0.647058823529412, g: 0.686274509803922, b: 0.83921568627451},
		{name: '659', r: 0.498039215686275, g: 0.549019607843137, b: 0.749019607843137},
		{name: '660', r: 0.349019607843137, g: 0.376470588235294, b: 0.658823529411765},
		{name: '661', r: 0.176470588235294, g: 0.2, b: 0.556862745098039},
		{name: '662', r: 0.0470588235294118, g: 0.0980392156862745, b: 0.458823529411765},
		{name: '663', r: 0.886274509803922, g: 0.827450980392157, b: 0.83921568627451},
		{name: '664', r: 0.847058823529412, g: 0.8, b: 0.819607843137255},
		{name: '665', r: 0.776470588235294, g: 0.709803921568627, b: 0.768627450980392},
		{name: '666', r: 0.658823529411765, g: 0.576470588235294, b: 0.67843137254902},
		{name: '667', r: 0.498039215686275, g: 0.4, b: 0.537254901960784},
		{name: '668', r: 0.4, g: 0.286274509803922, b: 0.458823529411765},
		{name: '669', r: 0.27843137254902, g: 0.168627450980392, b: 0.349019607843137},
		{name: '670', r: 0.949019607843137, g: 0.83921568627451, b: 0.847058823529412},
		{name: '671', r: 0.937254901960784, g: 0.776470588235294, b: 0.827450980392157},
		{name: '672', r: 0.917647058823529, g: 0.666666666666667, b: 0.768627450980392},
		{name: '673', r: 0.87843137254902, g: 0.549019607843137, b: 0.698039215686274},
		{name: '674', r: 0.827450980392157, g: 0.419607843137255, b: 0.619607843137255},
		{name: '675', r: 0.737254901960784, g: 0.219607843137255, b: 0.466666666666667},
		{name: '676', r: 0.627450980392157, g: 0, b: 0.329411764705882},
		{name: '677', r: 0.929411764705882, g: 0.83921568627451, b: 0.83921568627451},
		{name: '678', r: 0.917647058823529, g: 0.8, b: 0.807843137254902},
		{name: '679', r: 0.898039215686275, g: 0.749019607843137, b: 0.776470588235294},
		{name: '680', r: 0.827450980392157, g: 0.619607843137255, b: 0.686274509803922},
		{name: '681', r: 0.717647058823529, g: 0.447058823529412, b: 0.556862745098039},
		{name: '682', r: 0.627450980392157, g: 0.317647058823529, b: 0.458823529411765},
		{name: '683', r: 0.498039215686275, g: 0.156862745098039, b: 0.309803921568627},
		{name: '684', r: 0.937254901960784, g: 0.8, b: 0.807843137254902},
		{name: '685', r: 0.917647058823529, g: 0.749019607843137, b: 0.768627450980392},
		{name: '686', r: 0.87843137254902, g: 0.666666666666667, b: 0.729411764705882},
		{name: '687', r: 0.788235294117647, g: 0.537254901960784, b: 0.619607843137255},
		{name: '688', r: 0.698039215686274, g: 0.4, b: 0.517647058823529},
		{name: '689', r: 0.576470588235294, g: 0.258823529411765, b: 0.4},
		{name: '690', r: 0.43921568627451, g: 0.137254901960784, b: 0.258823529411765},
		{name: '691', r: 0.937254901960784, g: 0.819607843137255, b: 0.788235294117647},
		{name: '692', r: 0.909803921568627, g: 0.749019607843137, b: 0.729411764705882},
		{name: '693', r: 0.858823529411765, g: 0.658823529411765, b: 0.647058823529412},
		{name: '694', r: 0.788235294117647, g: 0.549019607843137, b: 0.549019607843137},
		{name: '695', r: 0.698039215686274, g: 0.419607843137255, b: 0.43921568627451},
		{name: '696', r: 0.556862745098039, g: 0.27843137254902, b: 0.286274509803922},
		{name: '697', r: 0.498039215686275, g: 0.219607843137255, b: 0.227450980392157},
		{name: '698', r: 0.968627450980392, g: 0.819607843137255, b: 0.8},
		{name: '699', r: 0.968627450980392, g: 0.749019607843137, b: 0.749019607843137},
		{name: '700', r: 0.949019607843137, g: 0.647058823529412, b: 0.666666666666667},
		{name: '701', r: 0.909803921568627, g: 0.529411764705882, b: 0.556862745098039},
		{name: '702', r: 0.83921568627451, g: 0.376470588235294, b: 0.427450980392157},
		{name: '703', r: 0.717647058823529, g: 0.219607843137255, b: 0.266666666666667},
		{name: '704', r: 0.619607843137255, g: 0.156862745098039, b: 0.156862745098039},
		{name: '705', r: 0.976470588235294, g: 0.866666666666667, b: 0.83921568627451},
		{name: '706', r: 0.988235294117647, g: 0.788235294117647, b: 0.776470588235294},
		{name: '707', r: 0.988235294117647, g: 0.67843137254902, b: 0.686274509803922},
		{name: '708', r: 0.976470588235294, g: 0.556862745098039, b: 0.6},
		{name: '709', r: 0.949019607843137, g: 0.407843137254902, b: 0.466666666666667},
		{name: '710', r: 0.87843137254902, g: 0.258823529411765, b: 0.317647058823529},
		{name: '711', r: 0.819607843137255, g: 0.176470588235294, b: 0.2},
		{name: '712', r: 1, g: 0.827450980392157, b: 0.666666666666667},
		{name: '713', r: 0.976470588235294, g: 0.788235294117647, b: 0.63921568627451},
		{name: '714', r: 0.976470588235294, g: 0.729411764705882, b: 0.509803921568627},
		{name: '715', r: 0.988235294117647, g: 0.619607843137255, b: 0.286274509803922},
		{name: '716', r: 0.949019607843137, g: 0.517647058823529, b: 0.0666666666666667},
		{name: '717', r: 0.827450980392157, g: 0.427450980392157, b: 0},
		{name: '718', r: 0.749019607843137, g: 0.356862745098039, b: 0},
		{name: '719', r: 0.956862745098039, g: 0.819607843137255, b: 0.686274509803922},
		{name: '720', r: 0.937254901960784, g: 0.768627450980392, b: 0.619607843137255},
		{name: '721', r: 0.909803921568627, g: 0.698039215686274, b: 0.509803921568627},
		{name: '722', r: 0.819607843137255, g: 0.556862745098039, b: 0.329411764705882},
		{name: '723', r: 0.729411764705882, g: 0.458823529411765, b: 0.188235294117647},
		{name: '724', r: 0.556862745098039, g: 0.286274509803922, b: 0.0196078431372549},
		{name: '725', r: 0.458823529411765, g: 0.219607843137255, b: 0.00784313725490196},
		{name: '726', r: 0.929411764705882, g: 0.827450980392157, b: 0.709803921568627},
		{name: '727', r: 0.886274509803922, g: 0.749019607843137, b: 0.607843137254902},
		{name: '728', r: 0.827450980392157, g: 0.658823529411765, b: 0.486274509803922},
		{name: '729', r: 0.756862745098039, g: 0.556862745098039, b: 0.376470588235294},
		{name: '730', r: 0.666666666666667, g: 0.458823529411765, b: 0.247058823529412},
		{name: '731', r: 0.447058823529412, g: 0.247058823529412, b: 0.0392156862745098},
		{name: '732', r: 0.376470588235294, g: 0.2, b: 0.0392156862745098},
		{name: '801', r: 0, g: 0.666666666666667, b: 0.8},
		{name: '801 2X', r: 0, g: 0.537254901960784, b: 0.686274509803922},
		{name: '802', r: 0.376470588235294, g: 0.866666666666667, b: 0.286274509803922},
		{name: '802 2X', r: 0.109803921568627, g: 0.807843137254902, b: 0.156862745098039},
		{name: '803', r: 1, g: 0.929411764705882, b: 0.219607843137255},
		{name: '803 2X', r: 1, g: 0.847058823529412, b: 0.0862745098039216},
		{name: '804', r: 1, g: 0.576470588235294, b: 0.219607843137255},
		{name: '804 2X', r: 1, g: 0.498039215686275, b: 0.117647058823529},
		{name: '805', r: 0.976470588235294, g: 0.349019607843137, b: 0.317647058823529},
		{name: '805 2X', r: 0.976470588235294, g: 0.227450980392157, b: 0.168627450980392},
		{name: '806', r: 1, g: 0, b: 0.576470588235294},
		{name: '806 2X', r: 0.968627450980392, g: 0.00784313725490196, b: 0.486274509803922},
		{name: '807', r: 0.83921568627451, g: 0, b: 0.619607843137255},
		{name: '807 2X', r: 0.749019607843137, g: 0, b: 0.549019607843137},
		{name: '808', r: 0, g: 0.709803921568627, b: 0.607843137254902},
		{name: '808 2X', r: 0, g: 0.627450980392157, b: 0.529411764705882},
		{name: '809', r: 0.866666666666667, g: 0.87843137254902, b: 0.0588235294117647},
		{name: '809 2X', r: 0.83921568627451, g: 0.83921568627451, b: 0.0470588235294118},
		{name: '810', r: 1, g: 0.8, b: 0.117647058823529},
		{name: '810 2X', r: 1, g: 0.737254901960784, b: 0.129411764705882},
		{name: '811', r: 1, g: 0.447058823529412, b: 0.27843137254902},
		{name: '811 2X', r: 1, g: 0.329411764705882, b: 0.0862745098039216},
		{name: '812', r: 0.988235294117647, g: 0.137254901960784, b: 0.4},
		{name: '812 2X', r: 0.988235294117647, g: 0.0274509803921569, b: 0.309803921568627},
		{name: '813', r: 0.898039215686275, g: 0, b: 0.6},
		{name: '813 2X', r: 0.819607843137255, g: 0, b: 0.517647058823529},
		{name: '814', r: 0.549019607843137, g: 0.376470588235294, b: 0.756862745098039},
		{name: '814 2X', r: 0.43921568627451, g: 0.247058823529412, b: 0.686274509803922 },
        { name: 'Black', r: 0, g: 0, b: 0 },
        { name: 'White', r: 1, g: 1, b: 1 }
	];
});;
/** Class representing the Product Grid. */
var ProductGrid = (function () {
    //Current indexes within quantity ranges
    var indexes = {};
    //The product price grids
    var priceGrids = {};
    //The product quantity ranges
    var quantityRanges = {};
    //The selected quantity range
    var selectedQuantityRange = {};

    /**
     * Initialize the product grids and quantities in order to better extract grid data in a predicable format
     * @param {object} grids - The product pricing grids
     * @param {object} quantities - The product quantity ranges
     */
    function initGrids(grids, quantities) {
        if (typeof (grids) !== 'undefined' && typeof (quantities) !== 'undefined') {
            priceGrids = grids;
            quantityRanges = quantities;

            for (grid in priceGrids) {
                indexes[grid] = null;
            }
        }
    }

    /**
     * Update the grid with a highlighted cell corresponding to the correct quantity range
     * @param {object} table - The target table to highlight cell
     * @param {number} decorationMethodId - The decoration method ID
     * @param {number} totalQuantityCount - The total quantity count 
     */
    function updateGrid(table, decorationMethodId, totalQuantityCount) {
        if (totalQuantityCount > 0) {
            //Filter passed in quantity ranges in an attempt to match the range with passed in quantity count
            selectedQuantityRange = quantityRanges[decorationMethodId].filter(function (value) {
                if ((totalQuantityCount >= value.QuantityFrom) && ((totalQuantityCount <= value.QuantityTo) || (!value.QuantityTo)))
                    return value;
            })[0];

            if (typeof (selectedQuantityRange) !== 'undefined') {
                $(table).find('td').removeClass('bolded');

                if (table.hasClass('table-horizontal')) {
                    $(table).find('td:nth-of-type(' + selectedQuantityRange.Index + ')').addClass('bolded');
                } else if (table.hasClass('table-vertical')) {
                    $(table).find('tr:nth-of-type(' + (selectedQuantityRange.Index + 1) + ') td').addClass('bolded');
                }

                if (indexes[decorationMethodId] != selectedQuantityRange.Index) {
                    //Set the global index to the new index 
                    indexes[decorationMethodId] = selectedQuantityRange.Index;

                    if (table.hasClass('table-horizontal')) {
                        //Highlight the cell once when the range changes
                        $(table).find('td:nth-of-type(' + selectedQuantityRange.Index + ')').effect("highlight", { color: '#333' }, 500);
                    } else if (table.hasClass('table-vertical')) {
                        //Highlight the cell once when the range changes
                        $(table).find('tr:nth-of-type(' + (selectedQuantityRange.Index + 1) + ') td').effect("highlight", { color: '#333' }, 500);
                    }
                }
            } else {
                indexes[decorationMethodId] = null;
                $(table).find('td').removeClass('bolded');
            }
        }
    }

    /**
     * Get the current price of the selected quantity range
     * @param {number} decorationMethodId - The decoration method ID
     * @param {array} gridIdArray - The grid ID array
     * @param {object} colourElement - The colour object
     * @return {number} The current price
     */
    function getPrice(decorationMethodId, gridIdArray, colourElement) {
        var price = null;

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId]) !== 'undefined') {
                //Get possible grids for a chosen colour
                var colourGridIdArray = colourElement.length ? colourElement.data('grid-id').toString().split(',') : [];
                //Find intersecting grids between the colour and quantity, if any. There should only be one.
                var intersectingGrids = gridIdArray.filter(function (n) {
                    return colourGridIdArray.indexOf(n) > -1;
                });
                var gridId = intersectingGrids.length ? intersectingGrids[0] : 0;

                if (typeof (priceGrids[decorationMethodId][gridId]) !== 'undefined') {
                    //If the grid id key is defined, set the price to match the ListPrice of the selected quantity range index in a given decoration method
                    price = priceGrids[decorationMethodId][gridId][0].Prices.filter(function (value) {
                        if (selectedQuantityRange.Index == value.Index)
                            return value;
                    })[0].ListPrice;
                } else {
                    //If not, simply look in the first price grid available in a given decoration method
                    price = priceGrids[decorationMethodId][gridIdArray[0]][0].Prices.filter(function (value) {
                        if (selectedQuantityRange.Index == value.Index)
                            return value;
                    })[0].ListPrice;
                }
            }
        }

        return price;
    }

    /**
     * Get the current setup charge of the selected quantity range
     * @param {number} decorationMethodId - The decoration method ID
     * @return {number} The current price
     */
    function getSetupCharge(decorationMethodId) {
        var price = null;

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId]) !== 'undefined') {
                //Get the setup charge of the first grid available in a given decoration method
                price = priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]][0].SetupCharge;
            }
        }

        return price;
    }

    /**
     * Get maximum number of decoration colours for a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @return {number} The current price
     */
    function getDecorationColourMax(decorationMethodId) {
        var max = 1;

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]]) !== 'undefined') {
                //Look in the first price grid available in a given decoration method to match the AdditionalCost of the selected quantity range index
                var additionalCost = (priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]][0].Prices.filter(function (value) {
                    if (selectedQuantityRange.Index == value.Index)
                        return value;
                })[0].AdditionalCost);

                //Get the maximum CostIndex and set it as the max
                if (additionalCost.length)
                    max = Math.max.apply(Math, additionalCost.map(function (value) { return value.CostIndex; }))
            }
        }

        return max;
    }

    /**
     * Get the decoration colour cost of a given colour in a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @param {number} index - The cost index
     * @return {number} The decoration colour cost
     */
    function getDecorationColourCost(decorationMethodId, index) {
        var cost = null;

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]]) !== 'undefined') {
                //Look in the first price grid available in a given decoration method to match the Prices of the selected quantity range index
                var priceGrid = priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]][0].Prices.filter(function (value) {
                    if (selectedQuantityRange.Index == value.Index)
                        return value;
                });

                if (priceGrid.length) {
                    //Find the cost index in the AdditionalCost array matching the index received
                    var colourArr = priceGrid[0].AdditionalCost.filter(function (colour) {
                        if (colour.CostIndex == index)
                            return colour;
                    });

                    if (colourArr.length)
                        cost = colourArr[0].Sell;
                }
            }
        }

        return cost;
    }

    /**
     * Get the decoration colour setup charge in a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @param {number} index - The cost index
     * @return {number} The decoration colour setup charge
     */
    function getDecorationColourSetupCharge(decorationMethodId, index) {
        var setupCharge = null;

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]]) !== 'undefined') {
                //Look in the first price grid available in a given decoration method to match the Prices of the selected quantity range index
                var priceGrid = priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]][0].Prices.filter(function (value) {
                    if (selectedQuantityRange.Index == value.Index)
                        return value;
                });

                if (priceGrid.length) {
                    //Find the cost index in the AdditionalCost array matching the index received
                    var colourArr = priceGrid[0].AdditionalCost.filter(function (colour) {
                        if (colour.CostIndex == index)
                            return colour;
                    });

                    //If there is a match, set setupCharge to the SetupCharge property
                    if (colourArr.length)
                        setupCharge = colourArr[0].SetupCharge;
                }
            }
        }

        return setupCharge;
    }

    /**
     * Get the grid ID of a given colour in a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @param {number} colourId - The colour id
     * @return {number} The grid id of the colour
     */
    function getColourGridId(decorationMethodId, colourId) {
        var id = null;

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId]) !== 'undefined') {
                if (typeof (colourId) !== 'undefined') {
                    for (grid in priceGrids[decorationMethodId]) {
                        //Look through all grids in a given decoration method
                        priceGrids[decorationMethodId][grid].forEach(function (gridValue) {
                            //If the colour is found in the grid that matches the passed in colour id
                            var match = gridValue.Colors.filter(function (color) {
                                if (color.Id == colourId)
                                    return color.Id;
                            })[0];

                            //Set the matched grid id
                            if (match) {
                                id = match['GridId'];
                            }
                        });
                    }
                } else {
                    //If not, simply get the first grid ID available in a given decoration method
                    id = priceGrids[decorationMethodId][Object.keys(priceGrids[decorationMethodId])[0]][0].GridId;
                }
            }
        }

        return id;
    }

    /**
     * Get the supported colours of a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @return {array} The array of colours
     */
    function getDecorationMethodColours(decorationMethodId) {
        var colours = [];

        if (typeof (selectedQuantityRange) !== 'undefined') {
            if (typeof (priceGrids[decorationMethodId]) !== 'undefined') {
                for (grid in priceGrids[decorationMethodId]) {
                    //Look through all grids in a given decoration method and return all enabled colours
                    var enabledColors = priceGrids[decorationMethodId][grid][0].Colors.filter(function (value) {
                        if (value.Enabled)
                            return value;
                    });

                    enabledColors.forEach(function (value) {
                        var colour = [];

                        //If the colour is already in the colours array, get a reference to it
                        colour = colours.filter(function (color) {
                            return color.Id == value.Id;
                        });

                        //If we are editing a colour, make sure to append all possible grid IDs
                        if (colour.length) {
                            var gridIds = colour[0]['GridId'].toString().split();
                            gridIds.push(grid);
                            colour[0]['GridId'] = gridIds.join(',');
                        } else {
                            //Set the grid id for each enabled colour to the current grid
                            value['GridId'] = parseInt(grid);
                            colours.push(value);
                        }
                    });
                }
            }
        }

        return colours;
    }

    /**
     * Get the price grid HTML of a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @return {string} The price grid HTML
     */
    function getGridHtml(decorationMethodId) {
        var source = $('#productPricingGrid').html();
        var template = Handlebars.compile(source);
        var html = '';

        if (typeof (decorationMethodId) !== 'undefined') {
            //Pass in the selected grid and selected quantity range to the template
            var selectedMethodGrids = priceGrids[decorationMethodId];
            var selectedQuantityRanges = quantityRanges[decorationMethodId];

            //Get the first grid available in the decoration method grids
            var firstGrid = Object.keys(selectedMethodGrids)[0];
            var price = 0;
            //Get first list price in prices array
            if (selectedMethodGrids[firstGrid][0].hasOwnProperty('Prices') && selectedMethodGrids[firstGrid][0].Prices.length > 0)
                price = numeral().unformat(selectedMethodGrids[firstGrid][0].Prices[0].ListPrice);

            html = template({
                quantities: selectedQuantityRanges,
                comment: selectedMethodGrids[firstGrid][0].Comment,
                grids: selectedMethodGrids,
                hidePrice: (price > 0) ? false : true
            });
        }

        return html;
    }

    /**
     * Get the vertical price grid HTML of a given decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @return {string} The price grid HTML
     */
    function getVerticalGridHtml(decorationMethodId) {
        var source = $('#productPricingVerticalGrid').html();
        var template = Handlebars.compile(source);
        var html = '';

        if (typeof (decorationMethodId) !== 'undefined') {
            //Pass in the selected grid and selected quantity range to the template
            var selectedMethodGrids = priceGrids[decorationMethodId];
            var selectedQuantityRanges = quantityRanges[decorationMethodId];

            //Get the first grid available in the decoration method grids
            var firstGrid = Object.keys(selectedMethodGrids)[0];
            var price = 0;
            //Get first list price in prices array
            if (selectedMethodGrids[firstGrid][0].hasOwnProperty('Prices') && selectedMethodGrids[firstGrid][0].Prices.length > 0)
                price = numeral().unformat(selectedMethodGrids[firstGrid][0].Prices[0].ListPrice);

            //Return a custom data format for selected quantity ranges and leave price as an empty array
            var quantitiesPrices = selectedQuantityRanges.map(function (value) {
                return {
                    QuantityFrom: value.QuantityFrom,
                    QuantityTo: value.QuantityTo,
                    Prices: []
                }
            });

            //Loop through selected method grids and push the grid prices
            for (grid in selectedMethodGrids) {
                for (var i = 0; i < selectedMethodGrids[grid][0].Prices.length; i++) {
                    if (typeof (quantitiesPrices[i]) !== 'undefined') {
                        quantitiesPrices[i].Prices.push(selectedMethodGrids[grid][0].Prices[i]);
                    }
                }
            }

            html = template({
                quantities: quantitiesPrices,
                comment: selectedMethodGrids[firstGrid][0].Comment,
                grids: selectedMethodGrids,
                hidePrice: (price > 0) ? false : true
            });
        }

        return html;
    }

    /**
     * Get quantity range of a given selected decoration method
     * @param {number} decorationMethodId - The decoration method ID
     * @return {array} The quantity range array
     */
    function getQuantityRange(decorationMethodId) {
        if (typeof (decorationMethodId) !== 'undefined') {
            return quantityRanges[decorationMethodId];
        }

        return [];
    }

    /**
     * Get valid grid ids for selected colour
     * @param {number} decorationMethodId - The decoration method ID
     * @param {number} colourId - The colour ID
     * @return {object} The grid ids object
     */
    function getColorEnabledGrids(decorationMethodId, colourId) {
        var gridIds = {};

        if (decorationMethodId && colourId) {
            for (grid in priceGrids[decorationMethodId]) {
                priceGrids[decorationMethodId][grid].forEach(function (gridValue) {
                    //Loop through all grids in a given decoration method and attempt to match passed in colour in the Colors array
                    var match = gridValue.Colors.filter(function (color) {
                        if (color.Id == colourId)
                            return color.Id;
                    })[0];

                    //If there is a match, create a new grid ID key with array as value and push the color 
                    if (match) {
                        if (typeof (gridIds[grid]) === 'undefined') {
                            gridIds[grid] = [];
                        }

                        gridIds[grid].push(match['Id'])
                    }
                });
            }
        }
        return gridIds;
    }

    return {
        InitGrids: function (grids, quantities) {
            initGrids(grids, quantities);
        },
        GetQuantityRange: function (decorationMethodId) {
            return getQuantityRange(decorationMethodId);
        },
        UpdateGrid: function (table, decorationMethodId, totalQuantityCount) {
            updateGrid(table, decorationMethodId, totalQuantityCount)
        },
        GetPrice: function (decorationMethodId, gridIdArray, colourElement) {
            return getPrice(decorationMethodId, gridIdArray, colourElement);
        },
        GetSetupCharge: function (decorationMethodId) {
            return getSetupCharge(decorationMethodId);
        },
        GetGridHtml: function (decorationMethodId) {
            return getGridHtml(decorationMethodId);
        },
        GetVerticalGridHtml: function (decorationMethodId) {
            return getVerticalGridHtml(decorationMethodId);
        },
        GetDecorationColourMax: function (decorationMethodId) {
            return getDecorationColourMax(decorationMethodId);
        },
        GetDecorationColourCost: function (decorationMethodId, index) {
            return getDecorationColourCost(decorationMethodId, index);
        },
        GetDecorationColourSetupCharge: function (decorationMethodId, index) {
            return getDecorationColourSetupCharge(decorationMethodId, index);
        },
        GetColourGridId: function (decorationMethodId, colourGridId) {
            return getColourGridId(decorationMethodId, colourGridId);
        },
        GetDecorationMethodColours: function (decorationMethodId) {
            return getDecorationMethodColours(decorationMethodId);
        },
        GetColorEnabledGrids: function (decorationMethodId, colourId) {
            return getColorEnabledGrids(decorationMethodId, colourId);
        }
    }
}());;
/** Class representing the Product Configure pages. */
var ProductConfigure = (function () {
    //Reference to the product configure quantity grids
    var quantityGrids = [];
    //Reference to the product configure quantity count
    var currentQuantityCount = 0;
    //Reference to the selected colour label
    var selectedColourLabel = null;
    //Table representing the product configure total data
    var table = $("#product-config-total table");
    //Reference to the current product config page
    var currentPage = 1;
    //Used to hold product parameters
    var productParams = {};
    //Used to hold Product configure parameters 
    var productConfigureParams = {};
    //Used to load cart line items when editing an order item
    var productDisplayParams = {};
    //Used to hold product price grids
    var priceGrids = {};
    //Used to hold all validation rules for product config areas
    var validateRules = {};
    //Used to hold references to all valid forms on product config pages
    var validForms = {};
    //Used to hold product colours
    var coloursArr = [];
    //Default list of PMS colours
    var filteredDecorationColours = [{ "HexCode": "#000000", "Pms": "Black" }, { "HexCode": "#0038a8", "Pms": "286" }, { "HexCode": "#876028", "Pms": "464" }, { "HexCode": "#8c2633", "Pms": "202" }, { "HexCode": "#00bce2", "Pms": "306" }, { "HexCode": "#603311", "Pms": "469" }, { "HexCode": "#a09151", "Pms": "4505" }, { "HexCode": "#006847", "Pms": "3425" }, { "HexCode": "#ddcc6b", "Pms": "458" }, { "HexCode": "#009e49", "Pms": "355" }, { "HexCode": "#60dd49", "Pms": "802" }, { "HexCode": "#666d70", "Pms": "431" }, { "HexCode": "#75aadb", "Pms": "284" }, { "HexCode": "#afaaa3", "Pms": "422" }, { "HexCode": "#00386b", "Pms": "295" }, { "HexCode": "#f47c00", "Pms": "1505" }, { "HexCode": "#F3B6D2", "Pms": "165 2X" }, { "HexCode": "#ff0093", "Pms": "806" }, { "HexCode": "#fc8c99", "Pms": "183" }, { "HexCode": "#72166b", "Pms": "259" }, { "HexCode": "#8c60c1", "Pms": "814" }, { "HexCode": "#e8112d", "Pms": "185" }, { "HexCode": "#d81e05", "Pms": "485" }, { "HexCode": "#005bbf", "Pms": "2935" }, { "HexCode": "#e0dbb5", "Pms": "5875" }, { "HexCode": "#008789", "Pms": "321" }, { "HexCode": "#FFFFFF", "Pms": "White" }, { "HexCode": "#f9e814", "Pms": "102" }];

    /**
     * Initialize the product configure page
     * @param {object} params - The parameters passed in to properly initialize Product Configure
     */
    function init(params) {
        if (typeof (params) !== 'undefined') {
            productParams = params;
            productConfigureParams.CurrentQuantity = [];

            priceGrids = Main.GetFormattedPriceGrid(productParams.PriceGrids, productParams.Sizes);

            if (productParams.hasOwnProperty('ProductId') && !isNaN(productParams.ProductId))
                productConfigureParams.CurrentProductId = productParams.ProductId;
            //If product has sizes, populate current quantity array with correct size ids
            if ((productParams.hasOwnProperty('SizeId')) && (productParams.SizeId > 0) && (productParams.hasOwnProperty('Quantity')) && (productParams.Quantity > 0)) {
                productConfigureParams.CurrentQuantity.push(
                    {
                        Id: productParams.SizeId,
                        Quantity: productParams.Quantity,
                        GridId: productParams.GridId
                    }
                );
            //If no sizes available, size ID will be null
            } else if (productParams.hasOwnProperty('Quantity') && (productParams.Quantity > 0)) {
                productConfigureParams.CurrentQuantity.push(
                    {
                        Id: null,
                        Quantity: productParams.Quantity,
                        GridId: productParams.GridId
                    }
                );
            }
            if (productParams.hasOwnProperty('DecorationMethodId') && (productParams.DecorationMethodId > 0))
                productConfigureParams.CurrentMethod = productParams.DecorationMethodId;
            if (productParams.hasOwnProperty('ColourId') && (productParams.ColourId > 0))
                productConfigureParams.CurrentColour = productParams.ColourId;
            //If editing and cart items already found, display them
            if (productParams.hasOwnProperty('CartItems') && productParams.CartItems !== null) {
                initCartItems(productParams.CartItems);
                displayCartItems(productParams.CartItems);
            }
            if (productParams.hasOwnProperty('Colors') && productParams.Colors.length)
                coloursArr = productParams.Colors;
        }

        //Output the HTML for the current page
        displayPageTemplate(currentPage);
        //Update the breadcrumb to save search parameters
        updateBreadcrumb();

        //If JS media queries are supported
        if (matchMedia) {
            //Match all viewports below 992px in width
            var smallMediaMatch = window.matchMedia("(max-width: 991px)");
            //Subscribe smallWidthChange to match media
            smallMediaMatch.addListener(smallWidthChange);
            //Immediately call small width change with active settings
            smallWidthChange(smallMediaMatch);
        }
    }

    /**
     * If user hasn't already selected the same product with same colour, get selected value of the custom control for product colour (which is not in a form) and set the original control with this value to ensure that it can be serialized in a form
     * @param {object} listItem - The link in the list item of the unordered list that was created for the custom control
     */
    function selectProductSwatch(listItem) {
        if (typeof ($(listItem).data('id')) !== 'undefined' && Number.isInteger($(listItem).data('id'))) {
            var options = {
                callback: {
                    done: function (response) {
                        //If another product was found with the same colour 
                        if ((response.ItemId > 0) && (response.ItemId != productParams.ItemId)) {
                            swal({
                                title: 'Duplicate Product',
                                text: "There is a duplicate product in cart with the same colour. Would you like to load this product and edit it instead?",
                                type: 'warning',
                                showCancelButton: true,
                                confirmButtonClass: 'swal2-confirm swal2-styled',
                                cancelButtonClass: 'swal2-cancel swal2-styled'
                            }).then(function () {
                                //If the user clicks OK, edit the product that was found with the same colour
                                location.href = '/product/configure/?productid=' + productConfigureParams.CurrentProductId + '&itemid=' + response.ItemId + '&edit=true';
                            }, function (dismiss) {
                            });
                        } else {
                            $(listItem).closest('ul').find('li > a').removeClass('active');
                            //Get the original form-friendly element that this custom control corresponds to
                            var select = $(listItem).closest('ul').data('original-element');
                            //Set its value to the value that was clicked on the custom control
                            $(select).val($(listItem).data('id')).change();

                            //If the current product we are configuring only has a single quantity
                            if ($('#product-config-quantity-form #single-quantity').length) {
                                //Set the grid id to the passed in value
                                $('#product-config-quantity-form #single-quantity').data('grid-id', $(listItem).data('grid-id'));

                                //Update quantity and totals
                                ProductConfigure.SetQuantity($('#product-config-quantity-form input'));
                                ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(1));
                            }

                            //Add colour to global product colour
                            setColour($(listItem));
                        }
                    },
                    fail: function () {
                        // Throw an exception if the request failed.
                        AsyncException({
                            ErrorType: 2,
                            Message: 'An error occurred while attempting to check if product exists in selected swatch.'
                        });
                    }
                },
                params: {
                    colourId: $(listItem).data('id'),
                    productId: productConfigureParams.CurrentProductId
                },
                method: 'GET'
            }

            DataManager.process('Product', 'CheckColour', options);
        }
    }

    /**
     * Sets the quantity values of the given product based on the quantity input group 
     * @param {object} quantitiesObj - The input(s) corresponding to the entered quantities
     */
    function setQuantity(quantitiesObj) {
        quantityGrids = [];
        currentQuantityCount = 0;

        if ($(quantitiesObj).length) {
            $(quantitiesObj).each(function () {
                if (!isNaN($(this).val()) && $(this).val() > 0) {
                    //Get the current grid id of given input
                    var gridId = $(this).data('grid-id');
                        quantityGrids.push({
                            Id: parseInt($(this).attr('id')),
                            Quantity: parseInt($(this).val()),
                            GridId: parseInt(gridId)
                        });

                    //Add the value to current quantity count
                    currentQuantityCount += parseInt($(this).val());
                }
            });
        }

        var quantityRanges = ProductGrid.GetQuantityRange(getSelectedDecorationMethodId());
        productConfigureParams.CurrentQuantity = quantityGrids;

        if (quantityRanges.length > 0) {
            //Get the difference between the lowest quantity and the current quantity
            var difference = quantityRanges[0].QuantityFrom - currentQuantityCount;
            //If the user has exceeded the minimum quantity count, display the current quantity
            if (difference <= 0)
                $('#product-selected-quantity').html(currentQuantityCount + " item(s)");
        }

        //Redraw the product config pricing table
        $('#product-config-pricing-table').empty()
            .append(ProductGrid.GetGridHtml(getSelectedDecorationMethodId()))
            .append(ProductGrid.GetVerticalGridHtml(getSelectedDecorationMethodId()));
        //If there are any discount prices available, cross out the regular prices
        if (Main.GetDiscount() > 0)
            $('#product-config-pricing-table td span.regular-price').css('text-decoration', 'line-through');

        //Update the product grid with the current quantity
        ProductGrid.UpdateGrid($('#product-config-pricing-table table:visible'), getSelectedDecorationMethodId(), currentQuantityCount);
    }

    /**
     * Sets the colour of the given product
     * @param {object} colourObj - Reference to the colour element
     */
    function setColour(colourObj) {
        if (colourObj && colourObj.length) {
            selectedColourLabel = $(colourObj).data('label');
            $('#product-config-colour-form select').val($(colourObj).data('id'));
            $(colourObj).addClass('active');

            productConfigureParams.CurrentColour = parseInt($(colourObj).data('id'));

            var colors = $(colourObj).data('bg-color').split(",");
            var swatch;

            //If more than 2 colours in list, display a dual tone swatch. If not, simply display a solid color
            if (colors.length > 1) {
                swatch = $('<span />', { 'class': 'swatch' }).css('background', 'linear-gradient(to bottom right, ' + colors[0] + ' 49%, ' + colors[1] + ' 51%)');
            } else {
                swatch = $('<span />', { 'class': 'swatch' }).css('background-color', colors[0]);
            }

            //Set the display text to the current colour
            $('#product-selected-colour').html(
                $('<div />', { 'class': 'product-colours' }).append(
                    swatch,
                    $('<span />', { 'class': 'swatch-title', 'text': selectedColourLabel })
                )
            );

            //Set the display text to the current colour in the total column
            $('.item-colour').each(function () {
                $(this).text(' ('+selectedColourLabel+')');
            });
        } else {
            //Reset all colour settings
            selectedColourLabel = null;
            $('#product-config-colour-form select').val('');
            productConfigureParams.CurrentColour = null;

            $('#product-selected-colour').empty();
            $('.item-colour').empty();
        }

        //Sets the quantity values of the given product based on the quantity input group 
        setQuantity($('#product-config-quantity-form input'));
        //Validate form passed in based on custom predefined validation rules on a given page
        validateForms(['product-config-quantity-form']);
        //Update total column on product configure with current page config object
        updateTotalColumn(ProductConfigureSteps.GetPageObj(1));
    }

    /**
     * Update decoration colour based on serialized decoration colour form
     */
    function updateDecorationColour() {
        productConfigureParams.CurrentDecorationColours = [];

        //Get reference to checked values in product configure decoration colour form
        var decorationColoursArr = $('#product-config-decoration-colour-form input');
        $(decorationColoursArr).each(function () {
            if ($(this).val()) {
                var selectedColour = ($(this).val() == 'White' || $(this).val() == 'Black') ? $(this).val() : 'PMS ' + $(this).val();
                var selectedColourValue = $(this).val();
                var selectedBgColour = $(this).data('bg-color');

                productConfigureParams.CurrentDecorationColours.push(
                    {
                        Pms: selectedColourValue,
                        Color: selectedColour,
                        BgColor: selectedBgColour
                    }
                );
            }
        });

        //If no product colours are present, re-append product colours container. If not, simply empty it
        if (!$('#product-decoration-selected-colour .product-colours').length) {
            $('#product-decoration-selected-colour').empty();
            $('<div />', { 'class': 'product-colours' }).appendTo($('#product-decoration-selected-colour'));
        } else {
            $('#product-decoration-selected-colour .product-colours').empty();
        }

        //Append swatches to current decoration colour list
        productConfigureParams.CurrentDecorationColours.forEach(function (value) {
            $('<span />', { 'class': 'swatch' }).css('background-color',value.BgColor).appendTo($('#product-decoration-selected-colour .product-colours'));
            $('<span />', { 'class': 'swatch-title', 'text': value.Color }).appendTo('#product-decoration-selected-colour .product-colours');
        });
    }

    /**
     * Add a decoration method for a given product
     * @param {object} methodObj - Reference to the method element
     */
    function setMethod(methodObj) {
        if (typeof (methodObj) !== 'undefined') {
            var currentMethod = methodObj.data('label');
            //Manually trigger the check event on the method element
            $(methodObj).iCheck('check');
            productConfigureParams.CurrentMethod = parseInt(methodObj.val());

            //Hide the old decoration method image
            $('#product-config-decoration-method .decoration-method').hide();
            //Show the new decoration method image
            $('#product-config-decoration-method .decoration-method[data-decoration-id="' + methodObj.val() + '"]').show();

            if (typeof (currentMethod) !== 'undefined')
                $('#product-selected-method').text(currentMethod);
            else
                $('#product-selected-method').empty();
        }
    }

    /**
     * Add a decoration location for a given product
     * @param {object} locationObj - Reference to the location element
     */
    function setLocation(locationObj) {
        if (typeof (locationObj) !== 'undefined') {
            var selectedLocation = locationObj.data('label');
            //Manually trigger the check event on the location element
            $(locationObj).iCheck('check');
            productConfigureParams.CurrentLocation = parseInt(locationObj.val());

            if (typeof (selectedLocation) !== 'undefined')
                $('#product-selected-location').text(selectedLocation);
            else
                $('#product-selected-location').empty();
        }
    }

    /**
     * Add decoration artwork for a given product
     * @param {object} artworkObj - Reference to the artwork element
     */
    function setArtwork(artworkObj) {
        if (typeof (artworkObj) !== 'undefined') {
            var currentArtwork = artworkObj.data('label');
            //Manually trigger the check event on the artwork element
            $(artworkObj).iCheck('check');
            productConfigureParams.CurrentArtwork = parseInt(artworkObj.val());

            if (typeof (currentArtwork) !== 'undefined')
                $('#product-selected-artwork').text(currentArtwork);
            else
                $('#product-selected-artwork').empty();
        }
    }

    /**
     * Set artwork file based on passed in file options object
     * @param {object} fileObj - A object with file options
     */
    function setArtworkFile(fileObj) {
        if (typeof (fileObj) !== 'undefined') {
            productConfigureParams.CurrentArtworkFile = fileObj;
        }
    }

    /**
     * Add comments for artwork options
     * @param {string} comments - A comment string
     */
    function setComments(comments) {
        if (comments.length) {
            $('#product-config-comments-form #comments').val(comments);

            productConfigureParams.CurrentComments = comments;
        }
    }

    /**
     * Update and display a given product configuration page
     * @param {number} page - Page number
     */
    function changePage(page) {
        currentPage = page;
        //Set all pages beyond next page to disabled
        updateDisabledPages();
   
        //Remove done class on each list item in progress bar
        $('.product-config-progress > li').each(function () {
            $(this).removeClass('done');
        });
        //If we are on any page besides the first...
        if (currentPage > 1) {
            //Add done class to all steps prior to the previous page on the progress bar
            for (var i = currentPage; i > 0; i--)
                $('.product-config-progress > li').eq(i - 1).attr('class', 'done');
        }

        $('.product-config-progress > li').each(function () {
            //Remove active class from all steps in progress bar
            if ($(this).hasClass('active'))
                $(this).removeClass('active');
        });
        //Set the active class on the current step in progress bar
        $('.product-config-progress > li').eq((currentPage - 1)).attr('class', 'active');
        //Set the product config content to the HTML response
        displayPageTemplate(currentPage);

        //Call the JavaScript for this specific page
        ProductConfigureSteps.UpdateJS(page);
    }

    /**
     * Update the product config progress bar and add disabled class to all pages beyond the next page
     */
    function updateDisabledPages() {
        $('.product-config-progress li a').each(function () {
            if ($(this).data('page') >= (currentPage + 2)) {
                $(this).parent().addClass('disabled');
            } else {
                $(this).parent().removeClass('disabled');
            }
        });
    }

    /**
     * Update product configure total column
     * @param {object} obj - Object that includes form groups with page number and titles
     */
    function updateTotalColumn(obj) {
        if (typeof (obj) !== 'undefined') {
            if (obj.forms.length) {
                var elements = [];
                var rowHtml = "";
                var validForms = getValidForms(obj);

                if (validForms.length) {
                    for (var i = 0; i < obj.forms.length; i++) {
                        //Loop through all the forms and set elements variable to serialized key/values pairs that are not empty
                        elements = $('#' + obj.forms[i].name).serializeArray().filter(function (input) {
                            if (input.value != '')
                                return input;
                        });

                        //Return validated status of a particular form name
                        if (getFormStatus(obj.forms[i].name)) {
                            if (elements.length) {
                                //Create a title and total row for a given form
                                createTitleRow(obj);
                                //Concatenate all total row HTML strings for the given form
                                rowHtml += getTotalRows(obj.forms[i].name, elements, obj.page);
                            }
                        }
                    }
                } else {
                    //If current quantity is below minimum...remove both title row and data
                    var nextTitle = $('#' + obj.rowTitle).nextAll('tr[id]').first();

                    //Remove all titles and rows  for the given form
                    if ($('#' + obj.rowTitle).nextUntil(nextTitle, 'tr').andSelf().length)
                        $('#' + obj.rowTitle).nextUntil(nextTitle, 'tr').andSelf().remove();
                }

                $(rowHtml).insertAfter('#' + obj.rowTitle);

                //Update the total price of the order
                updateTotalPrice();
                //Localize all relevant elements
                Main.LocalizePage();
            }
        }
    }

    /**
     * Update product configure total column
     */
    function loadTotalColumn() {
        var rowHtml = "";

        for (var page in productDisplayParams) {
            //Get the given page object and create a title row
            var pageObj = ProductConfigureSteps.GetPageObj(page);
            //Create a title row for a given form
            createTitleRow(pageObj);

            //Loop through all forms of the given page
            productDisplayParams[page].forEach(function (value) {
                //Concatenate all HTML for total rows for the given elements in a given form
                rowHtml += getTotalRows(value.form, value.elements, page);
            });

            //Insert total rows after the row title
            $(rowHtml).insertAfter('#' + pageObj.rowTitle);
            rowHtml = "";
        }

        //Display updated total price
        updateTotalPrice();
    }

    /**
     * Update total price
     */
    function updateTotalPrice() {
        var currentTotal = 0;
        //Get all price values in the total column and unformat them into floating numbers
        var totalPrice = $(table).find('.product-price').map(function (key, value) {
            if (parseInt(numeral().unformat($(this).html())))
                return numeral().unformat($(this).html());
        });
        if (totalPrice.length)
            //Add all those numbers together
            var currentTotal = $.makeArray(totalPrice).reduce(function (previousValue, currentValue) {
                return previousValue + currentValue;
            });

        //If the total is greater than 0, display it
        if (currentTotal > 0)
            $('#product-total').text(numeral(currentTotal).format('$0,0.00'));
        else
            $('#product-total').html("&mdash;");
    }

    /**
     * Get the size input HTML of a given decoration method 
     * @param {number} decorationMethodId - The decoration method ID
     * @return {string} The HTML for the size input controls
     */
    function getSizesHtml(decorationMethodId) {
        var source = $('#productSizes').html();
        var template = Handlebars.compile(source);
        var html = '';
        var singleGridId = '';
        
        if (typeof (decorationMethodId) !== 'undefined') {
            var sizeArr = [];

            for (var grid in priceGrids[decorationMethodId]) {
                priceGrids[decorationMethodId][grid][0].Sizes.forEach(function (value) {
                    //For each size, if not already present in the size array, push it
                    if (!sizeArr.some(function(size) {
                        return size.Id == value.Id 
                    })) {
                        sizeArr.push(value);
                    } else {
                        //If the current size was found in the size array
                        var record = sizeArr.find(function (size) {
                            return size.Id == value.Id
                        });
                        if (record) {
                            //Get the grid IDs of the current size
                            var currentGrids = record.GridId.toString();
                            //Get a grid ID array if more than one ID was found
                            var gridIdArr = currentGrids.split(',');
                            
                            //If the grid ID wasn't found in the grid ID array, push it
                            if (gridIdArr.indexOf(value.GridId.toString()) == -1)
                                gridIdArr.push(value.GridId);
                            
                            //Set the grid ID in record to joined elements in gridIdArr
                            record.GridId = gridIdArr.join(',');
                        }
                    }
                });
            }

            //If a current colour is present, set the single grid ID to that particular colour grid ID
            if (typeof (productConfigureParams.CurrentColour) !== 'undefined')
                singleGridId = ProductGrid.GetColourGridId(decorationMethodId, productConfigureParams.CurrentColour);
            else
                singleGridId = ProductGrid.GetColourGridId(decorationMethodId);

            var html = template({
                sizes: sizeArr,
                gridId: singleGridId
            });
        }

        return html;
    }

    /**
     * Get the selected decoration method ID
     * @return {number} The selected decoration method ID
     */
    function getSelectedDecorationMethodId() {
        var selectedDecorationMethod;

        if (productParams.PriceGrids.length > 0) {
            //Search the available price grids and return the decoration method ID if default grid is set to true
            var defaultDecorationMethodId = productParams.PriceGrids.filter(function (value) {
                if (value.DefaultGrid)
                    return value;
            })[0].DecorationMethodId;
        }

        //If a current method is specified, set it. If not, get the default decoration method ID.
        selectedDecorationMethod = ((productConfigureParams.hasOwnProperty('CurrentMethod') && productConfigureParams.CurrentMethod > 0) ? productConfigureParams.CurrentMethod : defaultDecorationMethodId);

        return selectedDecorationMethod;
    }

    /**
     * Display the given product configure page template
     * @param {number} page - The page number
     */
    function displayPageTemplate(page) {
        if (productParams.Decorations.Methods.length > 0) {
            //Get product config page template
            var source = (page != 1) ? $('#productConfigPage' + page).html() : $('#productConfigPage').html();
            var template = Handlebars.compile(source);

            var htmlObj = {
                colors: productParams.Colors,
                decorations: productParams.Decorations.Methods,
                edit: productParams.Edit
            };

            var selectedDecorationMethod = [];
            if (!isNaN(productConfigureParams.CurrentMethod)) {
                //Search passed in decoration methods and return the current method value
                selectedDecorationMethod = productParams.Decorations.Methods.filter(function (value) {
                    if (value.Id == productConfigureParams.CurrentMethod)
                        return value;
                });

                //Set the decoration colours to a sorted array of colours
                selectedDecorationMethod[0].decorationColours = sortColorsByHue(getFormattedColors(filteredDecorationColours));
                //Set method to the returned decoration method
                htmlObj['method'] = selectedDecorationMethod
            }

            var html = template(htmlObj);

            $('#product-config-container').empty().append(html).hide().fadeIn(600);
            //Localize all relevant elements
            Main.LocalizePage();
            //Remove all down states for total columns
            resetLineItemDownstates();
        }
    }

    /**
     * Populate the input values after the template has been output
     * @param {number} page - The page number
     */
    function updatePageValues(page) {
        switch (page) {
            //Page 1
            case 1:
                //If current quantity exists, populate all quantity inputs
                if (productConfigureParams.hasOwnProperty('CurrentQuantity') && productConfigureParams.CurrentQuantity.length) {
                    populateQuantityInputs();
                }
                //If current colour exists, trigger a click event on that given colour
                if (productConfigureParams.hasOwnProperty('CurrentColour') && typeof(productConfigureParams.CurrentColour) !== 'undefined') {
                    $('#product-colours ul li a[data-id=' + productConfigureParams.CurrentColour + ']').trigger('click');
                }
                //If current method exists, set the method radio button to the current method
                if (productConfigureParams.hasOwnProperty('CurrentMethod') && typeof (productConfigureParams.CurrentMethod) !== 'undefined') {
                    setMethod($('#product-config-method-form input[value=' + productConfigureParams.CurrentMethod + ']'));
                }
                break;
            //Page 2
            case 2:
                //If location exists, set the location radio button to the current location
                if (productConfigureParams.hasOwnProperty('CurrentLocation') && typeof (productConfigureParams.CurrentLocation) !== 'undefined') {
                    setLocation($('#product-config-location-form input[value=' + productConfigureParams.CurrentLocation + ']'));
                }
                //If decoration colours exist, loop through them and set colours in the colour picker
                if (productConfigureParams.hasOwnProperty('CurrentDecorationColours') && productConfigureParams.CurrentDecorationColours.length) {
                    var currentDecorationColours = productConfigureParams.CurrentDecorationColours;

                    for (var i = 0; i < currentDecorationColours.length; i++) {
                        $('#product-config-decoration-colour-form input#color-picker-' + (i + 1)).colorpicker('setColor', currentDecorationColours[i].Pms);
                    }
                }
                break;
            //Page 3
            case 3:
                //If artwork option exists, set the artwork radio button to the current artwork option
                if (productConfigureParams.hasOwnProperty('CurrentArtwork') && typeof (productConfigureParams.CurrentArtwork) !== 'undefined') {
                    setArtwork($('#product-config-artwork-form input[value=' + productConfigureParams.CurrentArtwork + ']'));
                } else {
                    setArtwork($('#product-config-artwork-form input:checked'));
                }
                if (productConfigureParams.hasOwnProperty('CurrentArtworkFile') && typeof (productConfigureParams.CurrentArtworkFile) !== 'undefined') {
                    setArtworkFile(productConfigureParams.CurrentArtworkFile);

                    //Initialize existing file object
                    var file = {
                        name: productConfigureParams.CurrentArtworkFile.Filename,
                        size: productConfigureParams.CurrentArtworkFile.Size,
                        path: productParams.ArtworkPath
                    };

                    if ($("#upload-artwork").length) {
                        //Create a dropzone for upload artwork element
                        var thisDropzone = Dropzone.forElement("#upload-artwork");

                        //Call the default addedfile event handler
                        thisDropzone.emit("addedfile", file);
                        //And optionally show the thumbnail of the file:
                        thisDropzone.emit("thumbnail", file, file.path + file.name);
                        thisDropzone.createThumbnailFromUrl(file, file.path + file.name, function () { }, true);
                        //Call the complete event
                        thisDropzone.emit("complete", file);
                    }
                }
                //If the comment exists, set it
                if (productConfigureParams.hasOwnProperty('CurrentComments') && productConfigureParams.CurrentComments.length > 0) {
                    setComments(productConfigureParams.CurrentComments);
                }
                break;
        }
    }

    /**
     * When editing a product in your cart, ensure that product configure parameters are initialized with values from the cart item
     * @param {cartItems} cartItems - The items in your cart
     */
    function initCartItems(cartItems) {
        var currentItem = cartItems[0];

        if (currentItem.hasOwnProperty('Id'))
            productConfigureParams.CurrentItemId = currentItem.Id;
        if (currentItem.hasOwnProperty('Quantities')) {
            productConfigureParams.CurrentQuantity = [];
            //Loop through all cart item quantities and push quantity objects
            for (var i = 0; i < currentItem.Quantities.length; i++) {
                productConfigureParams.CurrentQuantity.push(
                    {
                        Id: currentItem.Quantities[i].SizeId,
                        Quantity: currentItem.Quantities[i].Quantity,
                        GridId: currentItem.Quantities[i].GridId
                    }
                );
            }
        }
        if(currentItem.hasOwnProperty('ColourId'))
            productConfigureParams.CurrentColour = currentItem.ColourId;
        if (currentItem.hasOwnProperty('DecorationMethodId'))
            productConfigureParams.CurrentMethod = currentItem.DecorationMethodId;
        if (currentItem.hasOwnProperty('DecorationLocation'))
            productConfigureParams.CurrentLocation = currentItem.DecorationLocation.Id;
        if (currentItem.hasOwnProperty('DecorationColours')) {
            if (!productConfigureParams.hasOwnProperty('CurrentDecorationColours'))
                productConfigureParams.CurrentDecorationColours = [];
                
                //Loop through all decoration colours and push decoration colour objects
                currentItem.DecorationColours.forEach(function (value) {
                    productConfigureParams.CurrentDecorationColours.push(
                        {
                            Pms: value.Color.Pms,
                            Color: 'PMS ' + value.Color.Label
                        }
                    );
                });
        }
        if (currentItem.hasOwnProperty('Comment'))
            productConfigureParams.CurrentComments = currentItem.Comment;
        //If the current item has an artwork file, set it and ensure that current artwork option is set to 1 (upload art) or 2 (contact me)
        if (currentItem.hasOwnProperty('ArtworkFile') && currentItem.ArtworkFile.Filename) {
            productConfigureParams.CurrentArtworkFile = currentItem.ArtworkFile;
            productConfigureParams.CurrentArtwork = 1;
        } else {
            productConfigureParams.CurrentArtwork = 2;
        }
    }

    /**
     * Display total rows based on passed in values from cart item. Each display configuration object will follow a consistent pattern so that total rows can properly handle the values. 
     * @param {cartItems} cartItems - The items in your cart
     */
    function displayCartItems(cartItems) {
        var currentItem = cartItems[0];
        //Get the number of pages available
        var pageCount = ProductConfigureSteps.GetPageCount();

        for (var i = 0; i < pageCount; i++)
            productDisplayParams[i + 1] = [];

        if (currentItem.hasOwnProperty('Quantities')) {
            var elements = [];
            for (var i = 0; i < currentItem.Quantities.length; i++) {
                //Push a quantity object with name and value into elements
                elements.push({
                    "name": currentItem.Quantities[i].Size.Title.length ? currentItem.Quantities[i].Size.Title : "Quantity",
                    "value": currentItem.Quantities[i].Quantity
                });
            }

            //For quantities, push an object with populated form class and form elements to display title and total rows
            productDisplayParams[1].push(
            {
                'form': 'product-config-quantity-form',
                'elements': elements
            });
        }

        if (currentItem.hasOwnProperty('DecorationMethodId')) {
            //For decoration method, push an object with populated form class and form elements to display title and total rows
            productDisplayParams[1].push({
                'form': 'product-config-method-form',
                'elements': [
                    {
                        "name": "Decoration Method",
                        "value": currentItem.DecorationMethod.Title
                    }
                ]
            });
        }

        if (currentItem.hasOwnProperty('DecorationLocation')) {
            //For decoration location, push an object with populated form class and form elements to display title and total rows
            productDisplayParams[2].push({
                'form': 'product-config-location-form',
                'elements': [
                    {
                        'name': "Location",
                        'value': currentItem.DecorationLocation.Title
                    }
                ]
            });
        }

        if (currentItem.hasOwnProperty('DecorationColours')) {
            var elements = [];
            //For each decoration colour, push an object with name and value
            for (var i = 0; i < currentItem.DecorationColours.length; i++) {
                elements.push({
                    "name": "decorationColours[]",
                    "value": currentItem.DecorationColours[i].Colour.Title
                });
            }

            //For decoration colours, push an object with populated form class and form elements to display title and total rows
            productDisplayParams[2].push({
                'form': 'product-config-decoration-colour-form',
                'elements': elements
            });
        }

        if (currentItem.hasOwnProperty('ArtworkFile')) {
            //For artwork file, push an object with populated form class and form elements to display title and total rows
            productDisplayParams[3].push({
                'form': 'product-config-artwork-form',
                'elements': [
                    {
                        'name': "Product Artwork",
                        'value': (currentItem.ArtworkFile.Filename.length > 0) ? "Upload Art" : "Contact Me"
                    }
                ]
            });
        }
    }

    /**
     * Populate product quantity inputs with existing values
     */
    function populateQuantityInputs() {
        if (productConfigureParams.hasOwnProperty('CurrentQuantity') && typeof(productConfigureParams.CurrentQuantity) !== 'undefined') {
            if ($('#product-config-quantity-form input').length > 1) {
                if (productConfigureParams.CurrentQuantity.length) {
                    //Loop through all quantities and set the value of each populated quantity
                    for (var quantity in productConfigureParams.CurrentQuantity) {
                        $('#product-config-quantity-form input[id="' + productConfigureParams.CurrentQuantity[quantity].Id + '"]').val(productConfigureParams.CurrentQuantity[quantity].Quantity);
                    }
                }
            } else if ($('#product-config-quantity-form input').length == 1) {
                if (productConfigureParams.CurrentQuantity.length) {
                    //Simply set the single input to the populated quantity
                    $('#product-config-quantity-form input').val(productConfigureParams.CurrentQuantity[0].Quantity);
                    //Used to revert value to a legal number (higher than the minimum quantity)
                    $('#product-config-quantity-form input').data('current-value', productConfigureParams.CurrentQuantity[0].Quantity);
                } else {
                    //If there are no populated quantities, get the minimum quantity from the price grid and set it 
                    if (typeof (Main.GetQuantityRanges(priceGrids)[getSelectedDecorationMethodId()] !== 'undefined')) {
                        //Get the quantity range for the selected decoration method id
                        var quantityRanges = Main.GetQuantityRanges(priceGrids)[getSelectedDecorationMethodId()];

                        if(quantityRanges.length) {
                            $('#product-config-quantity-form input').val(quantityRanges[0].QuantityFrom);
                            //Used to revert value to a legal number (higher than the minimum quantity)
                            $('#product-config-quantity-form input').data('current-value', quantityRanges[0].QuantityFrom);
                        }
                    }
                }
            }
        }
    }

    /**
     * Validate all the forms passed in based on custom predefined validation rules on a given page
     * @param {array} formArr - The array of form elements
     * @param {number} page - The page number
     */
    function validateForms(formArr, page) {
        //If validForms doesn't have a key for current page, create one
        if (typeof (validForms[currentPage]) === 'undefined')
            validForms[currentPage] = {};

        //Set local forms variable to passed in form array, or keys of validate rules object
        var forms = (typeof (page) === 'undefined') ? formArr : Object.keys(validateRules[page]);

        for (var i = 0; i < forms.length; i++) {
            //If a validation rules exist for this form
            if (typeof (validateRules[currentPage][forms[i]]) !== 'undefined') {
                //If there is no valid form key in valid forms, create one and initialize it to an empty array
                if (typeof (validForms[currentPage][forms[i]]) === 'undefined')
                    validForms[currentPage][forms[i]] = [];

                if ($('#' + forms[i]).length) {
                    //Get a reference to the form to get at the validation rules
                    var form = validateRules[currentPage][forms[i]];
                    for (var rule in form) {
                        //If the given validation function returns false
                        if (!form[rule].validateFunc()) {
                            //Display the custom error for that rule
                            form[rule].displayValidateError();
                            //Set the valid status to false
                            validForms[currentPage][forms[i]] = false;
                            break;
                        } else {
                            //Set the valid status to true
                            validForms[currentPage][forms[i]] = true;
                            //Run the success function
                            form[rule].displayValidateSuccess();
                        }
                    }
                }
            }
        }
    }

    /**
     * Get the status for a given form
     * @param {string} form - The name of a given form
     * @return {bool} The form status
     */
    function getFormStatus(form) {
        if (form.length > 0) {
            if (typeof (validForms[currentPage]) === 'undefined')
                //Set a new key for the current page if it doesn't exist
                validForms[currentPage] = {};
            if (typeof (validForms[currentPage][form]) !== 'undefined') {
                //Return the status for passed in form name
                return validForms[currentPage][form];
            }
        }
    }

    /**
     * Get the name of the failing form in a given page
     * @param {number} page - The page number
     * @return {string} The form name
     */
    function getFailedForm(page) {
        if (typeof (validForms[page]) === 'undefined')
            //Set a new key for the passed in page if it doesn't exist
            validForms[page] = {};

        if (Object.keys(validForms[page]).length) {
            //Loop through all forms in valid forms
            for (var form in validForms[page]) {
                //If not valid, return form name
                if (!validForms[page][form]) {
                    return form;
                }
            }
        }

        return '';
    }

    /**
     * Add validation rules for all forms on a given page
     * @param {object} obj - The object containing all pages and forms
     */
    function addValidationRules(obj) {
        if (typeof (validateRules[obj.page]) === 'undefined')
            //Create a key for given page
            validateRules[obj.page] = {};

        if (obj.hasOwnProperty('forms')) {
            if (typeof (validateRules[obj.page]) !== 'undefined') {

                obj.forms.forEach(function (value) {
                    if (typeof (validateRules[obj.page][value.name]) === 'undefined')
                        //Create a key for given form
                        validateRules[obj.page][value.name] = {};

                    //Loop through all validation rules
                    for (var rule in value.validateRulesList) {
                        if (typeof (validateRules[obj.page][value.name][rule]) === 'undefined')
                            //Create a key for given rule
                            validateRules[obj.page][value.name][rule] = {};

                        //For each rule, populate a rule object with the validation methods
                        validateRules[obj.page][value.name][rule] = {
                            validateFunc: value.validateRulesList[rule],
                            displayValidateError: value.displayValidateErrorList[rule],
                            displayValidateSuccess: value.displayValidateSuccessList[rule]
                        };
                    }
                });
            }
        }
    }

    /**
     * Remove current artwork file key from product configure parameters
     */
    function removeArtworkFile() {
        if(productConfigureParams.hasOwnProperty('CurrentArtworkFile'))
            delete productConfigureParams['CurrentArtworkFile'];
    }

    /**
     * Update breadcrumb to previously saved product search parameters
     */
    function updateBreadcrumb() {
        //If cookies are enabled
        if (navigator.cookieEnabled) {
            //If storage API supported
            if (typeof (Storage) !== "undefined") {
                //If session storage key exists
                if (sessionStorage.hasOwnProperty('breadcrumbSearchParams')) {
                    //Parse JSON for breadcrumb search params
                    var breadcrumbString = JSON.parse(sessionStorage.breadcrumbSearchParams);
                    //If breadcrumbString has current product id as key
                    if (breadcrumbString.hasOwnProperty(productConfigureParams.CurrentProductId)) {
                        //If product key is defined
                        if (breadcrumbString[productConfigureParams.CurrentProductId])
                            //Set breadcrumb link to saved search parameters
                            $('.breadcrumb li a[data-type="search"]').attr('href', '/search/' + breadcrumbString[productConfigureParams.CurrentProductId]);
                    }
                }
            }
        }
    }

    /**
     * Reset all states for total rows and remove down class
     */
    function resetLineItemDownstates() {
        $('#product-config-total table td').each(function () {
            if ($(this).hasClass('down'))
                $(this).removeClass('down');
        });
    }

    /**
     * Get total rows HTML based on passed in form, elements, and page
     * @param {string} form - The name of the form
     * @param {array} elements - The key/value pairs of that form
     * @param {number} page - The page number
     * @return {string} The total rows HTML
     */
    function getTotalRows(form, elements, page) {
        var rowHtml = "";

        //Get the page object for passed in page
        var pageObj = ProductConfigureSteps.GetPageObj(page);
        //Get a reference to the form configuration of the form name that was passed in
        var selectedForm = pageObj.forms.filter(function (value) {
            if (value.name == form)
                return value;
        })[0];

        if (selectedForm !== 'undefined' && elements.length) {
            for (var k = 0; k < elements.length; k++) {
                if (typeof (selectedForm.writeLineTotal) !== 'undefined') {
                    //Build the row HTML based on the custom function passed in
                    rowHtml += selectedForm.writeLineTotal(elements[k].name, elements[k].value, pageObj.page, selectedColourLabel, (k + 1));
                }
            }
        }

        return rowHtml;
    }

    /**
     * Get total rows HTML based on passed in form, elements, and page
     * @param {object} obj - The object containing row titles and pages
     */
    function createTitleRow(obj) {
        //Create HTML for a new title row in product config total
        var titleRow = $('<tr id="' + obj.rowTitle + '" data-page="' + obj.page + '"><th>' + obj.title + '</th><th>&nbsp;</th></tr>');
        //Get a reference to the next title row, if any
        var nextTitle = $('#' + obj.rowTitle).nextAll('tr[id]').first();
        //If we have a title row that comes after the current title row, remove all data (not title) rows between them. This will allow the key/value forms to constantly "refresh", but keep the title row as reference
        if ($('#' + obj.rowTitle).nextUntil(nextTitle, 'tr').length)
            $('#' + obj.rowTitle).nextUntil(nextTitle, 'tr').remove();

        //If the current title row doesn't exist...
        if (!$('#' + obj.rowTitle).length) {
            //...and there is a title row that should come after it on the next page (eg. you are editing a page by going back a step)
            if (($("tr[data-page=" + (obj.page + 1) + "]").length))
                //Insert the title row before that row
                $(titleRow).insertBefore($("tr[data-page=" + (obj.page + 1) + "]"));
            else
                //If not, insert it before the total row (after that row)
                $(titleRow).insertBefore($("tr#total-row"));
        }
    }

    /**
     * Get a list of all valid forms
     * @param {object} obj - The object containing all pages and forms
     * @return {array} An array of all valid form names
     */
    function getValidForms(obj) {
        //Get all form names where the inner key/value pairs of those forms are not empty
        var validForms = obj.forms.map(function (value) {
            var elements = ($('#' + value.name).serializeArray().filter(function (input) {
                if (input.value != '')
                    return input;
            }));
            if (elements.length)
                return value.name;
            else
                return '';
        }).filter(function (value) {
            if (value != '')
                return value;
        });

        return validForms;
    }

    /**
     * Toggle visibility of elements based value of media queries
     * @param {object} match - A media query list that matches a pre-determined breakpoint
     */
    function smallWidthChange(match) {
        //Match all viewports below 992px in width
        if (match.matches) {
            $('#rep-window').hide();
        } else {
            $('#rep-window').show();
        }

        //Update the grid as required
        ProductGrid.UpdateGrid($('#product-config-pricing-table table:visible'), getSelectedDecorationMethodId(), currentQuantityCount);
    }

    return {
        Init: function (params) {
            init(params);
        },
        ChangePage: function (page) {
            changePage(page);
        },
        UpdateTotalColumn: function (obj) {
            updateTotalColumn(obj);
        },
        SetColour: function (colour) {
            setColour(colour);
        },
        GetColour: function() {
            return productConfigureParams.CurrentColour;
        },
        GetProductColours: function() {
            return coloursArr;
        },
        SetQuantity: function (quantitiesObj) {
            setQuantity(quantitiesObj);
        },
        GetQuantity: function () {
            return currentQuantity;
        },
        GetQuantityCount: function () {
            return currentQuantityCount;
        },
        SetMethod: function (methodObj) {
            setMethod(methodObj);
        },
        SetLocation: function (locationObj) {
            setLocation(locationObj);
        },
        SetArtwork: function (artworkObj) {
            setArtwork(artworkObj);
        },
        SetComments: function (comments) {
            setComments(comments);
        },
        UpdatePageValues: function (page) {
            updatePageValues(page);
        },
        GetPriceGrids: function() {
            return priceGrids;
        },
        GetProductConfigureParams: function () {
            return productConfigureParams;
        },
        GetSizesHtml: function (decorationMethodId) {
            return getSizesHtml(decorationMethodId);
        },
        GetSelectedDecorationMethodId: function() {
            return getSelectedDecorationMethodId();
        },
        GetCurrentPage: function() {
            return currentPage;
        },
        PopulateQuantityInputs: function () {
            populateQuantityInputs();
        },
        SetArtworkFile: function (fileObj) {
            setArtworkFile(fileObj);
        },
        GetArtworkFile: function () {
            return productConfigureParams.CurrentArtworkFile;
        },
        RemoveArtworkFile: function () {
            removeArtworkFile();
        },
        ValidateForms: function (formArr, page) {
            validateForms(formArr, page);
        },
        AddValidationRules: function(forms) {
            addValidationRules(forms);
        },
        GetProductId: function() {
            return productConfigureParams.CurrentProductId;
        },
        GetFormStatus: function(form) {
            return getFormStatus(form);
        },
        GetFailedForm: function (page) {
            return getFailedForm(page);
        },
        UpdateDisabledPages: function () {
            updateDisabledPages();
        },
        ResetLineItemDownstates: function () {
            resetLineItemDownstates();
        },
        SelectProductSwatch: function (listItem) {
            selectProductSwatch(listItem);
        },
        LoadTotalColumn: function() {
            loadTotalColumn();
        },
        UpdateDecorationColour: function () {
            updateDecorationColour();
        }
    }

}());;
/** Class representing the Product Configure steps. This class holds all of the unique configuration data for all 3 product config pages */
var ProductConfigureSteps = (function () {
    //Used to hold all configuration objects for product configure pages
    var productConfigPages = {};
    //Used to hold dropzone instance
    var myDropzone = {};

    /**
     * Initialize the product configure steps with the appropriate page data
     */
    function init() {
        //Set options for page 1
        productConfigPages[1] = {
            //Page numbre
            page: 1,
            //Row title ID
            rowTitle: 'product-quantity-title',
            //List of forms to group under title
            forms: [
                {
                    //Set the form name
                    name: 'product-config-quantity-form',
                    //Custom function that handles how an HTML string is constructed for key/value pairs in this particular form
                    writeLineTotal: function (name, value, page, colour) {
                        //Get the quantity ranges from the product grid
                        var quantityRanges = ProductGrid.GetQuantityRange(ProductConfigure.GetSelectedDecorationMethodId());
                        //Get the product configure quantity count
                        var updatedQuantity = ProductConfigure.GetQuantityCount();

                        //If an integer is successfully parsed and quantity is greater than minimum allowed quantity
                        if ((parseInt(value)) && (updatedQuantity >= quantityRanges[0].QuantityFrom)) {
                            //Get grid array from form
                            var gridArray = $('form input[name="' + name + '"]').length ? $('form input[name="' + name + '"]').data('grid-id').toString().split(',') : [];

                            //Get the current price of the selected quantity range
                            var price = ProductGrid.GetPrice(ProductConfigure.GetSelectedDecorationMethodId(), gridArray, $('#product-colours ul li a.active'));

                            //If there are any discount prices available, cross out the regular prices
                            if (Main.GetDiscount() > 0)
                                var priceUnformatted = numeral().unformat(price) * (1 - Main.GetDiscount());
                            else
                                var priceUnformatted = numeral().unformat(price);

                            //Calculate price based on price and value passed in
                            var calculatedPrice = priceUnformatted * value;
                            //Format price into dollar value
                            var parsedPrice = numeral(calculatedPrice).format('$0,0.00');
                            //If a colour is defined, create a span with the colour label
                            var colour = (colour != null) ? " <span class='item-colour'>(" + colour + ")</span>" : "<span class='item-colour'></span>";

                            //Return row HTML for display in product config total
                            return "<tr class='line-item' data-type='quantity' data-name='" + name + "' data-next-page=" + page + "><td>" + value + " " + name + colour + "</td><td style='text-align:right;'><span class='product-price'>" + parsedPrice + "</span></td></tr>";
                        } else {
                            return "";
                        }
                    },
                    //Object that holds all the validation rules for this form
                    validateRulesList: {
                        //Sizes rule - if there are no sizes inputted, validation check will fail
                        'sizes': function () {
                            var sizes = $('#product-config-quantity-form input').filter(function () {
                                return $(this).val() != "";
                            });

                            if (!sizes.length)
                                return false;
                            else
                                return true;
                        },
                        //Invalid chars rule - if the inputted entries are not integers, validation check will fail
                        'invalidchars': function () {
                            var invalidEntries = $('#product-config-quantity-form input').filter(function () {
                                if (!Number.isInteger(parseInt($(this).val())))
                                    return $(this).val();
                            });

                            if (invalidEntries.length)
                                return false;
                            else
                                return true;
                        },
                        //Minimum quantity rule - if the total quantity falls below the minimum, validation check will fail 
                        'minimumquantity': function () {
                            var quantityRanges = ProductGrid.GetQuantityRange(ProductConfigure.GetSelectedDecorationMethodId());
                            if ((ProductConfigure.GetQuantityCount() < quantityRanges[0].QuantityFrom))
                                return false;
                            else
                                return true;
                        }
                    },
                    displayValidateErrorList: {
                        //For sizes rule fail, output the following
                        'sizes': function () {
                            $('#product-selected-quantity').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Enter a quantity below your required sizes.</span>');
                            $('#product-config-quantity-form input').css('border', '1px solid #a94442');
                        },
                        //For invalidchars rule fail, output the following
                        'invalidchars': function () {
                            $('#product-selected-quantity').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Please ensure that only numbers are entered.</span>');
                            $('#product-config-quantity-form input').css('border', '1px solid #a94442');
                        },
                        //For minimumquantity rule fail, output the following
                        'minimumquantity': function () {
                            var quantityRanges = ProductGrid.GetQuantityRange(ProductConfigure.GetSelectedDecorationMethodId());
                            //If this is a positive number, that means that we haven't reached the minimum yet
                            var difference = quantityRanges[0].QuantityFrom - ProductConfigure.GetQuantityCount();

                            if (difference > 0)
                                $('#product-selected-quantity').html("<span class='validation-error'><i class='fa fa-exclamation-circle'></i> " + ProductConfigure.GetQuantityCount() + " item(s), add " + difference + " more to meet minimum.</span>");

                            $('#product-config-quantity-form input').css('border', '1px solid #a94442');
                        }
                    },
                    displayValidateSuccessList: {
                        //For sizes rule success, output the following
                        'sizes': function () {
                            $('#product-config-quantity-form input').css('border', '1px solid #e7e7e7');
                        },
                        //For invalidchars rule success, output the following
                        'invalidchars': function () {
                            $('#product-config-quantity-form input').css('border', '1px solid #e7e7e7');
                        },
                        //For minimumquantity rule success, output the following
                        'minimumquantity': function () {
                            $('#product-config-quantity-form input').css('border', '1px solid #e7e7e7');
                        }
                    }
                },
                {
                    name: 'product-config-colour-form',
                    //Object that holds all the validation rules for this form
                    validateRulesList: {
                        //Required rule - if there are no colours checked, validation check will fail
                        'required': function () {
                            var checkedColours = $('#product-colours ul li a.active');

                            if (!checkedColours.length)
                                return false;
                            else
                                return true;
                        }
                    },
                    displayValidateErrorList: {
                        //For required rule fail, output the following
                        'required': function () {
                            $('#product-selected-colour').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Please select a ' + Localize.GetLocaleString('color') + '.</span>');
                        }
                    },
                    displayValidateSuccessList: {
                        'required': function () { }
                    },
                    writeLineTotal: function () {
                        return "";
                    }
                },
                {
                    //Set the form name
                    name: 'product-config-method-form',
                    //Custom function that handles how an HTML string is constructed for key/value pairs in this particular form
                    writeLineTotal: function (name, value, page, colour) {
                        //Get name value to display in total grid column
                        var totalName = $('form input[name="' + name + '"]:checked').length ? $('form input[name="' + name + '"]:checked').data('name') : name;
                        //Get the setup charge of passed in decoration method ID
                        var price = ProductGrid.GetSetupCharge(ProductConfigure.GetSelectedDecorationMethodId());
                        //Get custom label in checkbox data attribute
                        var label = $('form input[name="' + name + '"]:checked').length ? $('form input[name="' + name + '"]:checked').data('label') : value;
                        //Format price into dollar value
                        var parsedPrice = numeral(price).format('$0,0.00');
                        //Return row HTML for display in product config total
                        return "<tr class='line-item' data-type='method' data-name='DecorationMethod' data-next-page=" + page + "><td>" + totalName + " - " + label + "</td><td style='text-align:right;'><span class='product-price'>" + parsedPrice + "</span></td></tr>";
                    },
                    //Object that holds all the validation rules for this form
                    validateRulesList: {
                        //Required rule - if there are no methods checked, validation check will fail
                        'required': function () {
                            var checkedMethods = $('#product-config-method-form input[name="DecorationMethod"]:checked');

                            if (!checkedMethods.length)
                                return false;
                            else
                                return true;
                        }
                    },
                    displayValidateErrorList: {
                        //For required rule fail, output the following
                        'required': function () {
                            $('#product-selected-method').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Please select a decoration method.</span>');
                        }
                    },
                    displayValidateSuccessList: {
                        'required': function () {
                        }
                    }
                }
            ],
            //Row title label
            title: 'Product Quantity'
        };
        //Set options for page 2
        productConfigPages[2] = {
            //Page number
            page: 2,
            //Row title ID
            rowTitle: 'product-decoration-title',
            //List of forms to group under title
            forms: [
                {
                    //Set the form name
                    name: 'product-config-location-form',
                    //Custom function that handles how an HTML string is constructed for key/value pairs in this particular form
                    writeLineTotal: function (name, value, page) {
                        var label = $('form input[name="' + name + '"]').length ? $('form input[name="' + name + '"]:checked').data('label') : value;
                        return "<tr class='line-item' data-type='location' data-name='" + name + "' data-next-page=" + page + "><td colspan='2'>" + name + " - " + label + "</td></tr>";
                    },
                    //Object that holds all the validation rules for this form
                    validateRulesList: {
                        //Required rule - if there are no locations checked, validation check will fail
                        'required': function () {
                            var checkedMethods = $('#product-config-location-form input[name="location"]:checked');

                            if (!checkedMethods.length)
                                return false;
                            else
                                return true;
                        }
                    },
                    displayValidateErrorList: {
                        //For required rule fail, output the following
                        'required': function () {
                            $('#product-selected-location').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Please select a location.</span>');
                        }
                    },
                    displayValidateSuccessList: {
                        'required': function () {
                        }
                    }
                },
                {
                    //Set the form name
                    name: 'product-config-decoration-colour-form',
                    //Custom function that handles how an HTML string is constructed for key/value pairs in this particular form
                    writeLineTotal: function (name, value, page, colour, count) {
                        var parsedPrice = "";
                        var parsedSetupCharge = "";

                        //All other colours should automatically calculate cost
                        if (count != 1) {
                            //Get decoration colour cost based on passed in decoration method ID and count
                            var price = ProductGrid.GetDecorationColourCost(ProductConfigure.GetSelectedDecorationMethodId(), count);
                            //Get decoration colour setup charge based on passed in decoration method ID and count
                            var setupCharge = ProductGrid.GetDecorationColourSetupCharge(ProductConfigure.GetSelectedDecorationMethodId(), count);
                            //Multiply price by quantity count
                            var calculatedPrice = price * ProductConfigure.GetQuantityCount();
                            //Format the price into currency 
                            parsedPrice = numeral(calculatedPrice).format('$0,0.00');

                            var calculatedPrice = setupCharge;
                            //Format the setup charge into currency
                            parsedSetupCharge = numeral(calculatedPrice).format('$0,0.00');
                        } else {
                            //The first colour is free
                            parsedPrice = "INCLUDED";
                            parsedSetupCharge = "INCLUDED";
                        }

                        //Get unique colour label from data attribute of colour radio button
                        var label = $('form input[name="' + name + '"][value="' + value + '"]:checked').length ? $('form input[name="' + name + '"][value="' + value + '"]:checked').data('label') : value;
                        //If label is black or white, don't prepend PMS
                        var updatedLabel = ((label == 'Black') || (label == 'White')) ? label : 'PMS ' + label;

                        //Return row HTML for display in product config total
                        return "<tr class='line-item' data-type='decoration-colour' data-name='" + name + "' data-next-page=" + page + "><td>" + getOrdinal(count) + " <span class='localized'>Color</span> - <strong> " + updatedLabel + "</strong></td><td style='text-align:right;'><span class='product-price'>" + parsedPrice + "</span></td></tr>" +
                                "<tr class='line-item' data-type='decoration-colour' data-name='" + name + "' data-next-page=" + page + "><td>Setup Charge</td><td style='text-align:right;'><span class='product-price'>" + parsedSetupCharge + "</span></td></tr>";
                    },
                    //Object that holds all the validation rules for this form
                    validateRulesList: {
                        'required': function () {
                            //Required rule - if there are no product decoration colours checked, validation check will fail
                            var checkedColours = $('#product-config-input input.colorpicker-popup').map(function (value) {
                                if ($(this).val()) {
                                    return $(this).val();
                                }
                            });

                            if (!checkedColours.length)
                                return false;
                            else
                                return true;
                        }
                    },
                    displayValidateErrorList: {
                        //For required rule fail, output the following
                        'required': function () {
                            $('#product-decoration-selected-colour').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Please select a ' + Localize.GetLocaleString('color') + '.</span>');
                        }
                    },
                    displayValidateSuccessList: {
                        'required': function () { }
                    }
                }
            ],
            //Row title label
            title: 'Decorating Options'
        };
        //Set options for page 3
        productConfigPages[3] = {
            //Page number
            page: 3,
            //Row title ID
            rowTitle: 'product-artwork-location-title',
            //List of forms to group under title
            forms: [
                {
                    //Set the form name
                    name: 'product-config-artwork-form',
                    //Custom function that handles how an HTML string is constructed for key/value pairs in this particular form
                    writeLineTotal: function (name, value, page) {
                        //Get name value to display in total grid column
                        var newName = $('form input[name="' + name + '"]:checked').length ? $('form input[name="' + name + '"]:checked').data('name') : name;
                        //Get custom label in radio input
                        var label = $('form input[name="' + name + '"]:checked').length ? $('form input[name="' + name + '"]:checked').data('label') : value;

                        return "<tr class='line-item' data-type='artwork' data-name='" + name + "' data-next-page=" + page + "><td>" + newName + " - " + label + "</td><td style='text-align:right;'>N/A</td></tr>";
                    },
                    //Object that holds all the validation rules for this form
                    validateRulesList: {
                        //Required rule - if there are no artwork options checked, validation check will fail
                        'required': function () {
                            var checkedArtwork = $('#product-config-artwork-form input[name="artwork"]:checked');

                            if (!checkedArtwork.length)
                                return false;
                            else
                                return true;
                        }
                    },
                    displayValidateErrorList: {
                        //For required rule fail, output the following
                        'required': function () {
                            $('#product-selected-artwork').html('<span class="validation-error"><i class="fa fa-exclamation-circle"></i> Please select a method.</span>');
                        }
                    },
                    displayValidateSuccessList: {
                        'required': function () {
                        }
                    }
                }
            ],
            //Row title label
            title: 'Product Artwork'
        };
    }

    /**
     * Retrieve the configuration options for a specific product configure page
     * @param {number} page - The page number
     * @return {object} The product configure page object
     */
    function getPageObj(page) {
        if (typeof (productConfigPages[page]) !== 'undefined') {
            return productConfigPages[page];
        }
    }

    /**
     * Get the product configure page count
     * @return {number} The number of product configure pages
     */
    function getPageCount() {
        return Object.keys(productConfigPages).length;
    }

    /**
     * Enable or disable dropzone based on current value of decoration method
     * @param {number} artworkType - The artwork radio value
     */
    function checkMethodStatus(artworkType) {
        if (artworkType == 1) {
            myDropzone.enable();
            $('#upload-artwork').fadeTo("fast", 1);
        } else {
            myDropzone.disable();
            $('#upload-artwork').fadeTo("fast", 0.3);
        }
    };

    /**
     * Update the JavaScript based on the page that is loaded in product configure
     * @param {number} page - Page number
     */
    function updateJS(page) {
        //Get the page number
        switch (page) {
            case 1:
                //Initialize product grid with product config pricing table
                ProductGrid.InitGrids(ProductConfigure.GetPriceGrids(), Main.GetQuantityRanges(ProductConfigure.GetPriceGrids()));
                //Append the sizes HTML to the quantity container for the selected decoration method
                $('#product-config-quantity-container').empty().append(ProductConfigure.GetSizesHtml(ProductConfigure.GetSelectedDecorationMethodId()));
                //Redraw the product config pricing table
                $('#product-config-pricing-table').empty()
                    .append(ProductGrid.GetGridHtml(ProductConfigure.GetSelectedDecorationMethodId()))
                    .append(ProductGrid.GetVerticalGridHtml(ProductConfigure.GetSelectedDecorationMethodId()));
                //Add validation rules found in page configuration object
                ProductConfigure.AddValidationRules(getPageObj(page));

                //If a decoration method is already defined, set it in form
                if (typeof (ProductConfigure.GetSelectedDecorationMethodId()) !== 'undefined') {
                    $('#product-config-method-form input[value=' + ProductConfigure.GetSelectedDecorationMethodId() + ']').iCheck('check');
                }

                //Update inputs with selected data, if any
                ProductConfigure.UpdatePageValues(page);

                //Set quantity in product grid
                ProductConfigure.SetQuantity($('#product-config-quantity-form input'));
                //If there are any discount prices available, cross out the regular prices
                if(Main.GetDiscount() > 0)
                    $('#product-config-pricing-table td span.regular-price').css('text-decoration', 'line-through');

                //When in edit mode, if any of the parameters below are present, validate forms on first page
                if ((typeof (getUrlParameter('Edit')) !== 'undefined' && getUrlParameter('Edit')) ||
                        (
                            (typeof (getUrlParameter('Quantity')) !== 'undefined') &&
                            (typeof (getUrlParameter('SizeId')) !== 'undefined')
                        ) ||
                        (typeof (getUrlParameter('ColourId')) !== 'undefined') ||
                        (typeof (getUrlParameter('DecorationMethodId')) !== 'undefined')
                    )
                    //Validate the forms for page 1 and save the results of those tests
                    ProductConfigure.ValidateForms([], 1);

                //Update total column on product configure with custom object
                if($('#product-config-quantity-form input#single-quantity').length)
                    ProductConfigure.ValidateForms(['product-config-quantity-form']);
                //Update total column on product configure with current page config object
                ProductConfigure.UpdateTotalColumn(getPageObj(page));

                // Set the NumericInput plugin on quantity inputs to prevent decimal inputs.
                $('.quantity-input').numericInput({ min: 0, allowNegative: false, allowFloat: false });

                break;
            case 2:
                //Add validation rules found in page configuration object
                ProductConfigure.AddValidationRules(getPageObj(page));

                //If there is only one location, simply check it
                if ($('#product-config-location-form input').length == 1) {
                    $('#product-config-location-form input').iCheck('check');
                }

                //Get reference to product decoration colour checkboxes
                var $checkboxesObj = $('#product-config-colours');
                //Hide original product decoration colour checkboxes
                $checkboxesObj.find('div.checkbox').hide();
                //Parse the checkboxes and return an object representing its values
                var listObject = Main.ParseElement($checkboxesObj);
                //Create an unordered list with custom colour swatches and add a custom validation function
                var swatchSelector = Main.CreateSwatches(listObject);

                //Prepend the HTML for the custom colour swatches in the correct location
                $('#product-decoration-colours').prepend(swatchSelector);

                //Get the maximum amount of decoration colours
                var maxInputs = ProductGrid.GetDecorationColourMax(ProductConfigure.GetSelectedDecorationMethodId());

                //Output the number of max input controls for decoration colours
                for (i = 0; i < maxInputs; i++) {
                    $('#product-config-input').append(
                        $('<div />', { 'class': 'color-line' }).append(
                            $('<label />', { 'for': 'color-picker-' + (i + 1), 'html': getOrdinal(i + 1) + ' <span class="localized">color</span>' }),
                            $('<input />', { 'type': 'text', 'class': 'form-control colorpicker-popup', 'id': 'color-picker-' + (i + 1), 'name': 'color-picker-' + (i + 1) }),
                            $('<button />', { 'type': 'button', 'class': 'color-picker-remove', 'data-id': (i + 1) }).append(
                                $('<i />', { 'class': 'fa fa-close' })
                            )
                        )
                    );
                }

                //Localize all relevant elements
                Main.LocalizePage();


                //Intialize colour picker for decoration colours
                $('.colorpicker-popup').colorpicker({
                    showOn: 'both',
                    buttonColorize: true,
                    parts: ['map', 'swatches', 'footer'],
                    swatches: 'pantone',
                    colorFormat: 'NAME',
                    swatchesWidth: 170,
                    limit: 'name',
                    select: function (e, color) {
                        $(e.target).data('bg-color', color.colorPicker.options.color);

                        //Update decoration colour based on serialized decoration colour form
                        ProductConfigure.UpdateDecorationColour();
                        //Validate the form and save the results of those tests
                        ProductConfigure.ValidateForms(['product-config-decoration-colour-form']);
                        //Update total column on product configure with current page config object
                        ProductConfigure.UpdateTotalColumn(getPageObj(page));
                    }
                });

                //Update inputs with selected data, if any
                ProductConfigure.UpdatePageValues(page);
                //If editing a product
                if (typeof (getUrlParameter('Edit')) !== 'undefined' && getUrlParameter('Edit'))
                    //Validate the forms for page 2 and save the results of those tests
                    ProductConfigure.ValidateForms([], 2);
                //Update total column on product configure with custom object
                ProductConfigure.UpdateTotalColumn(getPageObj(page));
                break;
            case 3:
                //Add validation rules found in page configuration object
                ProductConfigure.AddValidationRules(getPageObj(page));

                //Get HTML for "Dropzone" template
                var template = $('#dropzoneTemplate').html();
                //Compile template using handlebars
                var handlebarsTemplate = Handlebars.compile(template);
                var html = handlebarsTemplate();
                //Empty and append dropzone html
                $('#artwork-tab').empty().append(html);

                //Disabling autoDiscover, otherwise Dropzone will try to attach twice.
                Dropzone.autoDiscover = false;
                //Initialize Dropzone for drag-and-drop file uploading
                myDropzone = new Dropzone("#upload-artwork",
                {
                    //Target URL
                    url: "/Cart/AsyncAddArtwork",
                    //Only accept the file types below for decoration artwork
                    acceptedFiles: ".jpg,.jpeg,.png,.gif,.tif,.eps,.dst,.ai,.pdf,.svg",
                    addRemoveLinks: true,
                    maxFiles: 1,
                    paramName: "file",
                    init: function () {
                        this.on("addedfile", function (file) {
                            //Check if svg image is clean
                            sanitizeSVG(file).then(cleanResult => {
                                if (!cleanResult) {
                                    this.cancelUpload(file);
                                    swal('Upload Error', "Invalid file upload", 'error');
                                }
                            })
                            
                            //If there are 2 files, remove the first one in array
                            if (this.files[1] != null) {
                                this.removeFile(this.files[0]);
                            }
                        });
                        this.on("success", function (file) {
                            //If upload was successful, set the artwork file
                            if (typeof (file) !== 'undefined') {
                                var newDataObj = {
                                    'Filename': file.name,
                                    'Size': file.size
                                };

                                ProductConfigure.SetArtworkFile(newDataObj);
                            }
                        });
                        this.on("error", function (file, errorMessage) {
                            //If upload not successful, remove file and show alert
                            this.removeFile(file);

                            swal('Error', errorMessage, 'error');
                        });
                        this.on("removedfile", function (file) {
                            //When a file is removed, get artwork file
                            var currentFileObj = ProductConfigure.GetArtworkFile();

                            if (typeof (currentFileObj) !== 'undefined') {
                                //Is the saved file equal to the file attempting to be removed?
                                if (file.name == currentFileObj.Filename) {
                                    if (!myDropzone.files.length) {
                                        var options = {
                                            callback: {
                                                done: function (response) {
                                                    swal({
                                                        title: 'File deleted.',
                                                        text: "Your file " + file.name + " was deleted.",
                                                        type: 'success',
                                                        showCancelButton: false,
                                                        confirmButtonClass: 'swal2-confirm swal2-styled',
                                                        cancelButtonClass: 'swal2-cancel swal2-styled'
                                                    }).then(function () {
                                                        //Remove file and check the appropriate artwork option
                                                        ProductConfigure.RemoveArtworkFile();

                                                        $('#product-config-artwork-form input[value=2]').iCheck('check');
                                                    });
                                                },
                                                fail: function () {
                                                    // Throw an exception if the request failed.
                                                    AsyncException({
                                                        ErrorType: 2,
                                                        Message: 'An error occurred while attempting to remove file.'
                                                    });
                                                }
                                            },
                                            params: {
                                                fileName: file.name
                                            }
                                        }

                                        DataManager.process('Cart', 'RemoveArtwork', options);
                                    }
                                }
                            }
                        });
                    }
                });

                //Check the appropriate artwork option
                $('#product-config-artwork-form input[value=2]').iCheck('check');

                //Update inputs with selected data, if any
                ProductConfigure.UpdatePageValues(page);
                //If editing a product
                if (typeof (getUrlParameter('Edit')) !== 'undefined' && getUrlParameter('Edit'))
                    //Validate the forms for page 1 and save the results of those tests
                    ProductConfigure.ValidateForms([], 3);
                //Update total column on product configure with current page config object
                ProductConfigure.UpdateTotalColumn(getPageObj(page));

                //Enable or disable dropzone based on current value of decoration method
                checkMethodStatus(parseInt($('#product-config-artwork-form input[name="artwork"]:checked').val()));
                break;
        }

        //Convert all radios to iCheck display
        $('#product-config-content input[type=radio]').iCheck({
            radioClass: 'iradio_minimal'
        });
    }

    return {
        Init: function() {
            init();
        },
        UpdateJS: function (page) {
            updateJS(page);
        },
        GetPageObj: function (page) {
            return getPageObj(page);
        },
        GetPageCount: function () {
            return getPageCount();
        },
        CheckMethodStatus: function (artworkType) {
            checkMethodStatus(artworkType);
        }
    }
}());;
//When an AJAX call is made, block the product config content
$(document).ajaxStart(function () {
    $('section#product-config-content').block({
        message: null,
        overlayCSS: {
            backgroundColor: '#ffffff'
        }
    });
});

//When an AJAX call is done, unblock it
$(document).ajaxStop(function () {
    $('section#product-config-content').unblock();
});

//When the next button is clicked, change the page
$('body').on('click', '#product-config-next', function () {
    //Validate the forms for the current page and save the results of those tests
    ProductConfigure.ValidateForms([], ProductConfigure.GetCurrentPage());

    //If there is no failing form
    if (!ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage()).length) {
        //Get the next page number and update the page display
        if (parseInt($(this).data('next-page'))) {
            ProductConfigure.ChangePage($(this).data('next-page'));
        }
    } else {
        //If there is a failing form, automatically scroll to it
        if ($("#" + ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage())).parents('section').first().length)
            $('html,body').animate({ scrollTop: $("#" + ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage())).parents('section').first().offset().top - $('#top-wrapper').height() }, 'slow');
    }
});

//When the previous button is clicked, change the page
$('body').on('click', '#product-config-previous', function () {
    if (parseInt($(this).data('previous-page'))) {
        ProductConfigure.ChangePage($(this).data('previous-page'));
    }
});

//Update the product config progress bar and add disabled class to all pages beyond the next page
ProductConfigure.UpdateDisabledPages();

//When any numbers on the progress bar are clicked, move to that page
$('body').on('click', '.product-config-progress li a', function () {
    //If the page number can be parsed successfully
    if (parseInt($(this).data('page'))) {
        //Do not proceed if the button is disabled
        if ($(this).parent().hasClass('disabled')) {
            return;
        }
        
        //Validate the forms for the current page and save the results of those tests
        ProductConfigure.ValidateForms([],ProductConfigure.GetCurrentPage());

        //If there is no failing form, the page clicked is equal to this page plus 1 OR the page clicked is the current page or a page preceeding it
        if (!(ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage()).length && ($(this).data('page') == (ProductConfigure.GetCurrentPage() + 1))) || (parseInt($(this).data('page')) <= ProductConfigure.GetCurrentPage())) {
            ProductConfigure.ChangePage($(this).data('page'));
        } else {
            //If there is a failing form, automatically scroll to it
            if($("#" + ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage())).parents('section').first().length)
                $('html,body').animate({ scrollTop: $("#" + ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage())).parents('section').first().offset().top - $('#top-wrapper').height() }, 'slow');
        }
    }
});

//Whenever a total row is moused over, add active class
$(document).on('mouseover', '#product-config-total tr.line-item', function () {
    $(this).find('td').addClass('active')
});

//Whenever a total row is moused out, remove active class
$('body').on('mouseleave', '#product-config-total tr.line-item', function () {
    $(this).find('td').removeClass('active')
});

//When a total row is clicked
$('body').on('click', '#product-config-total tr.line-item', function () {
    //Remove all down states for total columns
    ProductConfigure.ResetLineItemDownstates();
    //Add down state to line item
    $(this).find('td').addClass('down')

    if (typeof ($(this).data('next-page')) !== 'undefined') {
        //If next page number is defined and it's not equal to the current page, go to that page
        if (ProductConfigure.GetCurrentPage() != parseInt($(this).data('next-page'))) {
            ProductConfigure.ChangePage($(this).data('next-page'));
        }
    }

    if (typeof ($(this).data('type')) !== 'undefined') {
        //Depending on the total row clicked, go to corresponding form
        switch ($(this).data('type')) {
            //If quantity row clicked, scroll to quantity form
            case 'quantity':
                if (typeof ($(this).data('name')) !== 'undefined') {
                    $('html,body').animate({ scrollTop: $("#product-config-container").offset().top }, 'slow');
                    $('#product-config-quantity-form input[name="' + $(this).data('name') + '"]').focus()
                }
                break;
            //If method row clicked, scroll to method form
            case 'method':
                if (typeof ($(this).data('name')) !== 'undefined') {
                    $('html,body').animate({ scrollTop: $("#product-config-decoration-method").offset().top }, 'slow');
                    $('#product-config-method-form input[name="' + $(this).data('name') + '"]').focus()
                }
                break;
            //If location row clicked, scroll to location form
            case 'location':
                if (typeof ($(this).data('name')) !== 'undefined') {
                    $('html,body').animate({ scrollTop: $("#product-config-decoration-location").offset().top }, 'slow');
                    $('#product-config-location-form input[name="' + $(this).data('name') + '"]').focus()
                }
                break;
            //If decoration colour row clicked, scroll to decoration colour form
            case 'decoration-colour':
                if (typeof ($(this).data('name')) !== 'undefined') {
                    $('html,body').animate({ scrollTop: $("#product-config-colour").offset().top }, 'slow');
                }
                break;
            //If artwork row clicked, scroll to artwork form
            case 'artwork':
                if (typeof ($(this).data('name')) !== 'undefined') {
                    $('html,body').animate({ scrollTop: $("#product-config-artwork-options").offset().top }, 'slow');
                    $('#product-config-artwork-form input[name="' + $(this).data('name') + '"]').focus()
                }
                break;
        }
    }
});

//When back to cart button is clicked, get product configure parameters and submit via form POST
$('#back-to-cart').on('click', function () {
    let params = TpcUtility.stripHtmlTags(ProductConfigure.GetProductConfigureParams());
    $.form('/cart/additem/', params, 'POST').submit();
});

//When product config quantity form is submitted, cancel event
$('body').on('submit', '#product-config-quantity-form', function (e) {
    e.preventDefault();
});

//Add click handler to handle changing of quantity manually
$('body').on('change', '#product-config-quantity-form input', function () {
    var page = 1;

    //Get quantity range from product grid
    var quantityRange = ProductGrid.GetQuantityRange(ProductConfigure.GetSelectedDecorationMethodId());

    if (quantityRange.length) {
        //If quantity is greater or equal to the lowest possible value...
        if (parseInt($('.product-quantity').val()) >= quantityRange[0].QuantityFrom)
            //Update newly decremented value in input
            $('.product-quantity-container .product-quantity').val($(this).val());

        //Set quantity in product configure class
        ProductConfigure.SetQuantity($('#product-config-quantity-form input'));
        //If there are any discount prices available, cross out the regular prices
        if (Main.GetDiscount() > 0)
            $('#product-config-pricing-table td span.regular-price').css('text-decoration', 'line-through');
        //Validate the form and save the results of those tests
        ProductConfigure.ValidateForms(['product-config-quantity-form']);
        //Update total column on product configure with current page config object
        ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(page));
    }
});

//Add click handler to add quantity button
$('body').on('click', '.product-quantity-container .add-quantity', function () {
    var page = 1;

    //Parse integer from quantity input values
    var newValue = parseInt($('.product-quantity-container input').val()) + 1;
    //Update newly incremented value in input
    $('.product-quantity-container input').val(newValue);
    //Used to revert value to a legal number (higher than the minimum quantity)
    $('.product-quantity-container input').data('current-value', $('.product-quantity-container input').val());
    //Set quantity in product configure class
    ProductConfigure.SetQuantity($('#product-config-quantity-form input'));
    //If there are any discount prices available, cross out the regular prices
    if (Main.GetDiscount() > 0)
        $('#product-config-pricing-table td span.regular-price').css('text-decoration', 'line-through');
    //Validate the forms for the current page and save the results of those tests
    ProductConfigure.ValidateForms(['product-config-quantity-form']);
    //Update total column on product configure with current page config object
    ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(page));
});

//Add click handler to the remove quantity button
$('body').on('click', '.product-quantity-container .remove-quantity', function () {
    var page = 1;

    //Parse integer from quantity input values
    var newValue = parseInt($('.product-quantity-container input').val()) - 1;
    //Get quantity range from product grid
    var quantityRange = ProductGrid.GetQuantityRange(ProductConfigure.GetSelectedDecorationMethodId());

    if (quantityRange.length) {
        //If quantity is greater than the lowest possible value...
        if (parseInt($('.product-quantity-container input').val()) > quantityRange[0].QuantityFrom) {
            //Update newly decremented value in input
            $('.product-quantity-container input').val(newValue);
            $('.product-quantity-container input').data('current-value', $('.product-quantity-container input').val());
        }
        //Set quantity in product configure class
        ProductConfigure.SetQuantity($('#product-config-quantity-form input'));
        //If there are any discount prices available, cross out the regular prices
        if (Main.GetDiscount() > 0)
            $('#product-config-pricing-table td span.regular-price').css('text-decoration', 'line-through');
        //Validate the forms for the current page and save the results of those tests
        ProductConfigure.ValidateForms(['product-config-quantity-form']);
        //Update total column on product configure with current page config object
        ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(page));
    }
});

$('body').on('ifChecked', '#product-config-method-form input', function () {
    var page = 1;

    //Set decoration method to the selected radio button
    ProductConfigure.SetMethod($(this));

    //Append the sizes HTML to the quantity container for the selected decoration method
    $('#product-config-quantity-container').empty().append(ProductConfigure.GetSizesHtml(ProductConfigure.GetSelectedDecorationMethodId()));
    //Redraw the product config pricing table
    $('#product-config-pricing-table').empty()
        .append(ProductGrid.GetGridHtml(ProductConfigure.GetSelectedDecorationMethodId()))
        .append(ProductGrid.GetVerticalGridHtml(ProductConfigure.GetSelectedDecorationMethodId()));
    
    //Get the quantity range for selected decoration method
    var quantityRange = ProductGrid.GetQuantityRange(ProductConfigure.GetSelectedDecorationMethodId());
    if (quantityRange.length) {
        //If the current quantity is out of bounds...
        if ((ProductConfigure.GetQuantityCount() > 0) && (ProductConfigure.GetQuantityCount() < quantityRange[0].QuantityFrom)) {
            //Set the product quantity to the lowest possible value
            $('.product-quantity-container .product-quantity').val(quantityRange[0].QuantityFrom);
            $('.product-quantity-container .product-quantity').data('current-value', quantityRange[0].QuantityFrom);

            //Display a notification when user selects a different decoration method
            swal('Price updated!', 'You have selected a different decoration method. <strong>Minimum quantity</strong> and <strong>pricing</strong> have been updated.', 'info');

            //Scroll back up to the product grid after user selects a different method
            $('html,body').animate({ scrollTop: $("#product-config-steps").offset().top }, 'slow');
        }
    }

    if ($('#colour-selector').length) {
        //Empty the colour selector and get colours based on passed in decoration method
        $('#colour-selector').empty();
        var colours = (ProductGrid.GetDecorationMethodColours(ProductConfigure.GetSelectedDecorationMethodId()).length) ? ProductGrid.GetDecorationMethodColours(ProductConfigure.GetSelectedDecorationMethodId()) : ProductConfigure.GetProductColours();
        //Get all colour IDs from the colours array
        var colourIds = colours.map(function (value) {
            return value.Id;
        });

        //Append a default option
        $('#colour-selector').append($('<option />').val("").text(''));
        colours.forEach(function (value) {
            //For each returned colour, build options and append relevant attributes
            var listItem = $('<option />').val(value.Id);
            listItem.text(value.Label);
            listItem.data('bg-color', value.HexCodes.map(function (value) {
                return value.HexCode;
            }));
            listItem.data('grid-id', value.GridId);

            $('#colour-selector').append(listItem);
        });

        //Get reference to product colour select
        var $selectObj = $('#colour-selector').hide();
        //Parse the select and return an object representing its values
        var listObject = Main.ParseElement($selectObj);
        //Create an unordered list with custom colour swatches
        var swatchSelector = Main.CreateSwatches(listObject);
        //Prepend the HTML for the custom colour swatches in the correct location
        $('#product-colours > ul').remove();
        $('#product-colours').prepend(swatchSelector);

        //If only one colour returned, trigger a click event
        if ($('#product-config-colour #product-colours ul li').length == 1) {
            $('#product-config-colour #product-colours ul li a').trigger('click');
        } else {
            //Otherwise, set colour to null
            if($.inArray(ProductConfigure.GetColour(),colourIds) == -1)
                ProductConfigure.SetColour(null);
        }
    }

    //Populate product quantity inputs with existing values
    ProductConfigure.PopulateQuantityInputs();
    //Sets the quantity values of the given product based on the quantity input group 
    ProductConfigure.SetQuantity($('#product-config-quantity-form input'));
    //Validate form passed in based on custom predefined validation rules on a given page
    ProductConfigure.ValidateForms(['product-config-method-form']);
    //Update total column on product configure with current page config object
    ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(page));
});

$('body').on('click touchstart', '#product-config-colour #product-colours ul li a', function (e) {
    e.stopPropagation();
    e.preventDefault();

    var page = 1;

    // Get valid grids for selected colour and enable only relevant sizes
    var gridIds = Object.keys(ProductGrid.GetColorEnabledGrids(ProductConfigure.GetSelectedDecorationMethodId(), $(e.target).data('id')));

    if (gridIds.length && $('#product-config-quantity-form input').length > 1) {
        // Disable all sizes by default
        $('#product-config-quantity-form input').prop('disabled', true);

        //Loop through colour enabled grid ids
        gridIds.forEach(function (value) {
            $('#product-config-quantity-form input').each(function () {
                //For each quantity input, get the grid ID and split it on the comma
                var ids = $(this).data('grid-id').toString().split(',');

                //If the current grid ID isn't found in the ids array
                if ($.inArray(value, ids) != -1) {
                    //Disable the current colour
                    $(this).prop('disabled', false);
                }
            });
        });

        // If there are still disabled inputs, clear the values
        $('#product-config-quantity-form input:disabled').each(function () {
            $(this).val('');
        });
    }

    ProductConfigure.SelectProductSwatch(e.target);
});

//Add click handler when location is checked
$('body').on('ifChecked', '#product-config-location-form input', function () {
    var page = 2;

    //Set quantity in product configure class
    if ($(this).data('src').length)
        $('#decoration-location-img').empty().append($('<img />', { 'src': "/Images/" + $(this).data('src'), 'class': 'img-responsive', 'alt': '' }));
    else
        $('#decoration-location-img').empty();

    //Set a decoration location for a given product
    ProductConfigure.SetLocation($(this));
    //Validate form passed in based on custom predefined validation rules on a given page
    ProductConfigure.ValidateForms(['product-config-location-form']);
    //Update total column on product configure with current page config object
    ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(page));
});

//When product config decoration colour form is submitted, cancel event
$('body').on('submit', '#product-config-decoration-colour-form', function (e) {
    e.preventDefault();
});

//When add to cart button is clicked
$('body').on('click', '#add-to-cart', function (e) {
    var page = 3;

    e.preventDefault();

    //Validate form passed in based on custom predefined validation rules on selected page
    ProductConfigure.ValidateForms([], page);
    //If there are no failed forms on given page, get product configure parameters and submit via form POST
    if (!ProductConfigure.GetFailedForm(page).length) {
        let params = TpcUtility.stripHtmlTags(ProductConfigure.GetProductConfigureParams());
        $.form('/cart/additem/', params, 'POST').submit();
    } else {
        //If there is a failing form, automatically scroll to it
        $('html,body').animate({ scrollTop: $("#" + ProductConfigure.GetFailedForm(ProductConfigure.GetCurrentPage())).parents('section').first().offset().top - $('#top-wrapper').height() }, 'slow');
    }
});

//When artwork radio button is checked
$('body').on('ifChecked', '#product-config-artwork-form input', function () {
    var page = 3;

    //Set artwork to value of input that was clicked in product configure class
    ProductConfigure.SetArtwork($(this));
    //Validate the passed in form and save the results of those tests
    ProductConfigure.ValidateForms(['product-config-artwork-form']);
    //Update total column on product configure with current page config object
    ProductConfigure.UpdateTotalColumn(ProductConfigureSteps.GetPageObj(page));
    //Fade upload artwork control in or out depending on passed in value
    ProductConfigureSteps.CheckMethodStatus(parseInt($(this).val()))
});

//Set value of comments on every key press
$('body').on('keyup', '#product-config-comments-form #comments', function () {
    ProductConfigure.SetComments($(this).val());
});

//When a decoration colour swatch is clicked
$('body').on('click touchstart', '#product-decoration-colours ul li a', function (e) {
    e.stopPropagation();
    e.preventDefault();

    //Get PMS value
    if ($(e.target).data('pms')) {
        $('.colorpicker-popup').each(function () {
            if (!$(this).colorpicker('getColor')) {
                //Set the colour in colour picker with passed in PMS value
                $(this).colorpicker('setColor', $(e.target).data('pms'));
                return false;
            }
        });
    }
});

//When remove button next to decoration colour is clicked
$('body').on('click', 'button.color-picker-remove', function (e) {
    if ($('#color-picker-' + $(e.currentTarget).data('id')).length) {
        //Set colour to null 
        $('#color-picker-' + $(e.currentTarget).data('id')).colorpicker('setColor', null);
    }
});

//Initialize the main slick carousel with predetermined options
$('.product-slider').slick({
    slidesToShow: 1,
    slidesToScroll: 1,
    prevArrow: "<i class='fa fa-chevron-left slick-prev'></i>",
    nextArrow: "<i class='fa fa-chevron-right slick-next'></i>",
    fade: true
});;
