df2profiler map drawer - Dead Frontier 2

a simple map drawer for df2profiler.com by clicking on the map to draw color on area

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         df2profiler map drawer - Dead Frontier 2
// @name:zh-TW   df2profiler 地圖繪製器 - 死亡邊境 2
// @namespace    http://tampermonkey.net/
// @version      0.5.0
// @description  a simple map drawer for df2profiler.com by clicking on the map to draw color on area
// @description:zh-TW 透過點擊地圖來繪製顏色的簡易地圖繪製器
// @author       Archer_Wn
// @match        https://df2profiler.com/gamemap/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=df2profiler.com
// @grant        none
// @license      GPL-3.0
// ==/UserScript==

// Options
let color = "rgba(255, 255, 0, 0.3)"; // default color for drawing
const pvpColor = "rgba(255, 0, 0, 0.4)"; // color for pvp area
const outpostColor = "rgba(0, 255, 0, 0.4)"; // color for outpost area
const chunkSize = 6; // echo chunk have (chunkSize * chunkSize) cells

// Main
let mouseDown = false;
let drawingMode = false;
let drewCells = [];
function onPickerInput(value) {
  color = value;
}

function onSavedMap() {
  const tbody = document.querySelector("#map tbody");

  const backgroundImgUrl = window
    .getComputedStyle(document.querySelector("#map"))
    .getPropertyValue("background-image");
  const canvas = document.createElement("canvas");
  canvas.width = tbody.clientWidth;
  canvas.height = tbody.clientHeight;
  const ctx = canvas.getContext("2d");
  const img = new Image();
  img.src = backgroundImgUrl.slice(5, -2);

  img.onload = () => {
    // draw background image
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

    // draw pvp area
    const pvpCells = tbody.querySelectorAll(".pvpZone");
    pvpCells.forEach((cell) => {
      const rect = cell.getBoundingClientRect();
      const x = rect.left - tbody.getBoundingClientRect().left;
      const y = rect.top - tbody.getBoundingClientRect().top;
      ctx.fillStyle = pvpColor;
      ctx.fillRect(x, y, rect.width, rect.height);
    });

    // draw outpost area
    const outpostCells = tbody.querySelectorAll(".outpost");
    outpostCells.forEach((cell) => {
      const rect = cell.getBoundingClientRect();
      const x = rect.left - tbody.getBoundingClientRect().left;
      const y = rect.top - tbody.getBoundingClientRect().top;
      ctx.fillStyle = outpostColor;
      ctx.fillRect(x, y, rect.width, rect.height);
    });

    // draw area
    const cells = tbody.querySelectorAll("td");
    cells.forEach((cell) => {
      if (cell.style.backgroundColor === "") return;
      const rect = cell.getBoundingClientRect();
      const x = rect.left - tbody.getBoundingClientRect().left;
      const y = rect.top - tbody.getBoundingClientRect().top;
      ctx.fillStyle = cell.style.backgroundColor;
      ctx.fillRect(x, y, rect.width, rect.height);
    });

    // draw grid (6x6 cells)
    const td = tbody.querySelector("td");
    const chunkWidth = td.clientWidth * chunkSize;
    const chunkHeight = td.clientHeight * chunkSize;
    ctx.strokeStyle = "white";
    ctx.lineWidth = 1;
    ctx.beginPath();
    for (let x = chunkWidth; x < canvas.width; x += chunkWidth) {
      ctx.moveTo(x, 0);
      ctx.lineTo(x, canvas.height);
    }
    for (let y = chunkHeight; y < canvas.height; y += chunkHeight) {
      ctx.moveTo(0, y);
      ctx.lineTo(canvas.width, y);
    }
    ctx.stroke();

    // download image
    const a = document.createElement("a");
    a.href = canvas.toDataURL("image/png");
    a.download = "map.png";
    a.click();
  };
}

(function () {
  "use strict";

  const tbody = document.querySelector("#map tbody");
  tbody.addEventListener("click", (e) => {
    const cell = e.target.closest("td");
    if (!cell) return;

    if (drawingMode) {
      cell.style.backgroundColor = color;
    } else {
      cell.style.backgroundColor = "";
    }
  });
  tbody.addEventListener("mousedown", (e) => {
    mouseDown = true;
    const cell = e.target.closest("td");
    if (!cell) return;
    if (cell.style.backgroundColor === "") {
      drawingMode = true;
    } else {
      drawingMode = false;
    }
  });
  tbody.addEventListener("mousemove", (e) => {
    if (!mouseDown) return;
    const cell = e.target.closest("td");
    if (!cell) return;
    if (drewCells.includes(cell)) return;

    if (drawingMode) {
      cell.style.backgroundColor = color;
    } else {
      cell.style.backgroundColor = "";
    }
    drewCells.push(cell);
  });
  tbody.addEventListener("mouseup", (e) => {
    mouseDown = false;
    drewCells = [];
  });

  const navbarLinks = document.querySelector("#navbar-links");

  // color picker (rgba)
  const colorPicker = document.createElement("input");
  colorPicker.value = color;
  colorPicker.style.padding = "0 0.5rem";
  colorPicker.style.margin = "0.3rem 1rem";
  colorPicker.style.borderRadius =
    navbarLinks.getBoundingClientRect().height / 2 + "px";
  colorPicker.style.marginLeft = "auto";
  colorPicker.style.cursor = "pointer";
  colorPicker.setAttribute("data-jscolor", {});
  colorPicker.addEventListener("input", function () {
    onPickerInput(this.jscolor.toRGBAString());
  });
  navbarLinks.appendChild(colorPicker);

  // screenshot button
  const screenshotBtn = document.createElement("button");
  screenshotBtn.textContent = "Screenshot Map";
  screenshotBtn.style.padding = "0 1rem";
  screenshotBtn.style.margin = "0.3rem 0rem";
  screenshotBtn.style.borderRadius =
    navbarLinks.getBoundingClientRect().height / 2 + "px";
  screenshotBtn.style.cursor = "pointer";
  navbarLinks.appendChild(screenshotBtn);
  screenshotBtn.addEventListener("click", onSavedMap);
})();

/**
 * jscolor - JavaScript Color Picker
 *
 * @link    http://jscolor.com
 * @license For open source use: GPLv3
 *          For commercial use: JSColor Commercial License
 * @author  Jan Odvarko - East Desire
 * @version 2.5.2
 *
 * See usage examples at http://jscolor.com/examples/
 */

(function (global, factory) {
  "use strict";

  if (typeof module === "object" && typeof module.exports === "object") {
    // Export jscolor as a module
    module.exports = global.document
      ? factory(global)
      : function (win) {
          if (!win.document) {
            throw new Error("jscolor needs a window with document");
          }
          return factory(win);
        };
    return;
  }

  // Default use (no module export)
  factory(global);
})(typeof window !== "undefined" ? window : this, function (window) {
  // BEGIN factory

  // BEGIN jscolor code

  "use strict";

  var jscolor = (function () {
    // BEGIN jscolor

    var jsc = {
      initialized: false,

      instances: [], // created instances of jscolor

      readyQueue: [], // functions waiting to be called after init

      register: function () {
        if (typeof window !== "undefined" && window.document) {
          if (window.document.readyState !== "loading") {
            jsc.pub.init();
          } else {
            window.document.addEventListener(
              "DOMContentLoaded",
              jsc.pub.init,
              false
            );
          }
        }
      },

      installBySelector: function (selector, rootNode) {
        rootNode = rootNode ? jsc.node(rootNode) : window.document;
        if (!rootNode) {
          throw new Error("Missing root node");
        }

        var elms = rootNode.querySelectorAll(selector);

        // for backward compatibility with DEPRECATED installation/configuration using className
        var matchClass = new RegExp(
          "(^|\\s)(" + jsc.pub.lookupClass + ")(\\s*(\\{[^}]*\\})|\\s|$)",
          "i"
        );

        for (var i = 0; i < elms.length; i += 1) {
          if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) {
            continue; // jscolor already installed on this element
          }

          if (
            elms[i].type !== undefined &&
            elms[i].type.toLowerCase() == "color" &&
            jsc.isColorAttrSupported
          ) {
            continue; // skips inputs of type 'color' if supported by the browser
          }

          var dataOpts, m;

          if (
            (dataOpts = jsc.getDataAttr(elms[i], "jscolor")) !== null ||
            (elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED)
          ) {
            var targetElm = elms[i];

            var optsStr = "";
            if (dataOpts !== null) {
              optsStr = dataOpts;
            } else if (m) {
              // installation using className (DEPRECATED)
              console.warn(
                'Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' +
                  jsc.docsRef
              );
              if (m[4]) {
                optsStr = m[4];
              }
            }

            var opts = null;
            if (optsStr.trim()) {
              try {
                opts = jsc.parseOptionsStr(optsStr);
              } catch (e) {
                console.warn(e + "\n" + optsStr);
              }
            }

            try {
              new jsc.pub(targetElm, opts);
            } catch (e) {
              console.warn(e);
            }
          }
        }
      },

      parseOptionsStr: function (str) {
        var opts = null;

        try {
          opts = JSON.parse(str);
        } catch (eParse) {
          if (!jsc.pub.looseJSON) {
            throw new Error(
              "Could not parse jscolor options as JSON: " + eParse
            );
          } else {
            // loose JSON syntax is enabled -> try to evaluate the options string as JavaScript object
            try {
              opts = new Function(
                "var opts = (" +
                  str +
                  '); return typeof opts === "object" ? opts : {};'
              )();
            } catch (eEval) {
              throw new Error("Could not evaluate jscolor options: " + eEval);
            }
          }
        }
        return opts;
      },

      getInstances: function () {
        var inst = [];
        for (var i = 0; i < jsc.instances.length; i += 1) {
          // if the targetElement still exists, the instance is considered "alive"
          if (jsc.instances[i] && jsc.instances[i].targetElement) {
            inst.push(jsc.instances[i]);
          }
        }
        return inst;
      },

      createEl: function (tagName) {
        var el = window.document.createElement(tagName);
        jsc.setData(el, "gui", true);
        return el;
      },

      node: function (nodeOrSelector) {
        if (!nodeOrSelector) {
          return null;
        }

        if (typeof nodeOrSelector === "string") {
          // query selector
          var sel = nodeOrSelector;
          var el = null;
          try {
            el = window.document.querySelector(sel);
          } catch (e) {
            console.warn(e);
            return null;
          }
          if (!el) {
            console.warn("No element matches the selector: %s", sel);
          }
          return el;
        }

        if (jsc.isNode(nodeOrSelector)) {
          // DOM node
          return nodeOrSelector;
        }

        console.warn(
          "Invalid node of type %s: %s",
          typeof nodeOrSelector,
          nodeOrSelector
        );
        return null;
      },

      // See https://stackoverflow.com/questions/384286/
      isNode: function (val) {
        if (typeof Node === "object") {
          return val instanceof Node;
        }
        return (
          val &&
          typeof val === "object" &&
          typeof val.nodeType === "number" &&
          typeof val.nodeName === "string"
        );
      },

      nodeName: function (node) {
        if (node && node.nodeName) {
          return node.nodeName.toLowerCase();
        }
        return false;
      },

      removeChildren: function (node) {
        while (node.firstChild) {
          node.removeChild(node.firstChild);
        }
      },

      isTextInput: function (el) {
        return (
          el && jsc.nodeName(el) === "input" && el.type.toLowerCase() === "text"
        );
      },

      isButton: function (el) {
        if (!el) {
          return false;
        }
        var n = jsc.nodeName(el);
        return (
          n === "button" ||
          (n === "input" &&
            ["button", "submit", "reset"].indexOf(el.type.toLowerCase()) > -1)
        );
      },

      isButtonEmpty: function (el) {
        switch (jsc.nodeName(el)) {
          case "input":
            return !el.value || el.value.trim() === "";
          case "button":
            return el.textContent.trim() === "";
        }
        return null; // could not determine element's text
      },

      // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
      isPassiveEventSupported: (function () {
        var supported = false;

        try {
          var opts = Object.defineProperty({}, "passive", {
            get: function () {
              supported = true;
            },
          });
          window.addEventListener("testPassive", null, opts);
          window.removeEventListener("testPassive", null, opts);
        } catch (e) {}

        return supported;
      })(),

      isColorAttrSupported: (function () {
        var elm = window.document.createElement("input");
        if (elm.setAttribute) {
          elm.setAttribute("type", "color");
          if (elm.type.toLowerCase() == "color") {
            return true;
          }
        }
        return false;
      })(),

      dataProp: "_data_jscolor",

      // usage:
      //   setData(obj, prop, value)
      //   setData(obj, {prop:value, ...})
      //
      setData: function () {
        var obj = arguments[0];

        if (arguments.length === 3) {
          // setting a single property
          var data = obj.hasOwnProperty(jsc.dataProp)
            ? obj[jsc.dataProp]
            : (obj[jsc.dataProp] = {});
          var prop = arguments[1];
          var value = arguments[2];

          data[prop] = value;
          return true;
        } else if (arguments.length === 2 && typeof arguments[1] === "object") {
          // setting multiple properties
          var data = obj.hasOwnProperty(jsc.dataProp)
            ? obj[jsc.dataProp]
            : (obj[jsc.dataProp] = {});
          var map = arguments[1];

          for (var prop in map) {
            if (map.hasOwnProperty(prop)) {
              data[prop] = map[prop];
            }
          }
          return true;
        }

        throw new Error("Invalid arguments");
      },

      // usage:
      //   removeData(obj, prop, [prop...])
      //
      removeData: function () {
        var obj = arguments[0];
        if (!obj.hasOwnProperty(jsc.dataProp)) {
          return true; // data object does not exist
        }
        for (var i = 1; i < arguments.length; i += 1) {
          var prop = arguments[i];
          delete obj[jsc.dataProp][prop];
        }
        return true;
      },

      getData: function (obj, prop, setDefault) {
        if (!obj.hasOwnProperty(jsc.dataProp)) {
          // data object does not exist
          if (setDefault !== undefined) {
            obj[jsc.dataProp] = {}; // create data object
          } else {
            return undefined; // no value to return
          }
        }
        var data = obj[jsc.dataProp];

        if (!data.hasOwnProperty(prop) && setDefault !== undefined) {
          data[prop] = setDefault;
        }
        return data[prop];
      },

      getDataAttr: function (el, name) {
        var attrName = "data-" + name;
        var attrValue = el.getAttribute(attrName);
        return attrValue;
      },

      setDataAttr: function (el, name, value) {
        var attrName = "data-" + name;
        el.setAttribute(attrName, value);
      },

      _attachedGroupEvents: {},

      attachGroupEvent: function (groupName, el, evnt, func) {
        if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
          jsc._attachedGroupEvents[groupName] = [];
        }
        jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
        el.addEventListener(evnt, func, false);
      },

      detachGroupEvents: function (groupName) {
        if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
          for (
            var i = 0;
            i < jsc._attachedGroupEvents[groupName].length;
            i += 1
          ) {
            var evt = jsc._attachedGroupEvents[groupName][i];
            evt[0].removeEventListener(evt[1], evt[2], false);
          }
          delete jsc._attachedGroupEvents[groupName];
        }
      },

      preventDefault: function (e) {
        if (e.preventDefault) {
          e.preventDefault();
        }
        e.returnValue = false;
      },

      triggerEvent: function (el, eventName, bubbles, cancelable) {
        if (!el) {
          return;
        }

        var ev = null;

        if (typeof Event === "function") {
          ev = new Event(eventName, {
            bubbles: bubbles,
            cancelable: cancelable,
          });
        } else {
          // IE
          ev = window.document.createEvent("Event");
          ev.initEvent(eventName, bubbles, cancelable);
        }

        if (!ev) {
          return false;
        }

        // so that we know that the event was triggered internally
        jsc.setData(ev, "internal", true);

        el.dispatchEvent(ev);
        return true;
      },

      triggerInputEvent: function (el, eventName, bubbles, cancelable) {
        if (!el) {
          return;
        }
        if (jsc.isTextInput(el)) {
          jsc.triggerEvent(el, eventName, bubbles, cancelable);
        }
      },

      eventKey: function (ev) {
        var keys = {
          9: "Tab",
          13: "Enter",
          27: "Escape",
        };
        if (typeof ev.code === "string") {
          return ev.code;
        } else if (
          ev.keyCode !== undefined &&
          keys.hasOwnProperty(ev.keyCode)
        ) {
          return keys[ev.keyCode];
        }
        return null;
      },

      strList: function (str) {
        if (!str) {
          return [];
        }
        return str.replace(/^\s+|\s+$/g, "").split(/\s+/);
      },

      // The className parameter (str) can only contain a single class name
      hasClass: function (elm, className) {
        if (!className) {
          return false;
        }
        if (elm.classList !== undefined) {
          return elm.classList.contains(className);
        }
        // polyfill
        return (
          -1 !=
          (" " + elm.className.replace(/\s+/g, " ") + " ").indexOf(
            " " + className + " "
          )
        );
      },

      // The className parameter (str) can contain multiple class names separated by whitespace
      addClass: function (elm, className) {
        var classNames = jsc.strList(className);

        if (elm.classList !== undefined) {
          for (var i = 0; i < classNames.length; i += 1) {
            elm.classList.add(classNames[i]);
          }
          return;
        }
        // polyfill
        for (var i = 0; i < classNames.length; i += 1) {
          if (!jsc.hasClass(elm, classNames[i])) {
            elm.className += (elm.className ? " " : "") + classNames[i];
          }
        }
      },

      // The className parameter (str) can contain multiple class names separated by whitespace
      removeClass: function (elm, className) {
        var classNames = jsc.strList(className);

        if (elm.classList !== undefined) {
          for (var i = 0; i < classNames.length; i += 1) {
            elm.classList.remove(classNames[i]);
          }
          return;
        }
        // polyfill
        for (var i = 0; i < classNames.length; i += 1) {
          var repl = new RegExp(
            "^\\s*" +
              classNames[i] +
              "\\s*|" +
              "\\s*" +
              classNames[i] +
              "\\s*$|" +
              "\\s+" +
              classNames[i] +
              "(\\s+)",
            "g"
          );
          elm.className = elm.className.replace(repl, "$1");
        }
      },

      getCompStyle: function (elm) {
        var compStyle = window.getComputedStyle
          ? window.getComputedStyle(elm)
          : elm.currentStyle;

        // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
        // that's why we need to check if the returned value is non-empty
        if (!compStyle) {
          return {};
        }
        return compStyle;
      },

      // Note:
      //   Setting a property to NULL reverts it to the state before it was first set
      //   with the 'reversible' flag enabled
      //
      setStyle: function (elm, styles, important, reversible) {
        // using '' for standard priority (IE10 apparently doesn't like value undefined)
        var priority = important ? "important" : "";
        var origStyle = null;

        for (var prop in styles) {
          if (styles.hasOwnProperty(prop)) {
            var setVal = null;

            if (styles[prop] === null) {
              // reverting a property value

              if (!origStyle) {
                // get the original style object, but dont't try to create it if it doesn't exist
                origStyle = jsc.getData(elm, "origStyle");
              }
              if (origStyle && origStyle.hasOwnProperty(prop)) {
                // we have property's original value -> use it
                setVal = origStyle[prop];
              }
            } else {
              // setting a property value

              if (reversible) {
                if (!origStyle) {
                  // get the original style object and if it doesn't exist, create it
                  origStyle = jsc.getData(elm, "origStyle", {});
                }
                if (!origStyle.hasOwnProperty(prop)) {
                  // original property value not yet stored -> store it
                  origStyle[prop] = elm.style[prop];
                }
              }
              setVal = styles[prop];
            }

            if (setVal !== null) {
              elm.style.setProperty(prop, setVal, priority);
            }
          }
        }
      },

      appendCss: function (css) {
        var head = document.querySelector("head");
        var style = document.createElement("style");
        style.innerText = css;
        head.appendChild(style);
      },

      appendDefaultCss: function (css) {
        jsc.appendCss(
          [
            ".jscolor-wrap, .jscolor-wrap div, .jscolor-wrap canvas { " +
              "position:static; display:block; visibility:visible; overflow:visible; margin:0; padding:0; " +
              "border:none; border-radius:0; outline:none; z-index:auto; float:none; " +
              "width:auto; height:auto; left:auto; right:auto; top:auto; bottom:auto; min-width:0; min-height:0; max-width:none; max-height:none; " +
              "background:none; clip:auto; opacity:1; transform:none; box-shadow:none; box-sizing:content-box; " +
              "}",
            ".jscolor-wrap { clear:both; }",
            ".jscolor-wrap .jscolor-picker { position:relative; }",
            ".jscolor-wrap .jscolor-shadow { position:absolute; left:0; top:0; width:100%; height:100%; }",
            ".jscolor-wrap .jscolor-border { position:relative; }",
            ".jscolor-wrap .jscolor-palette { position:absolute; }",
            ".jscolor-wrap .jscolor-palette-sw { position:absolute; display:block; cursor:pointer; }",
            ".jscolor-wrap .jscolor-btn { position:absolute; overflow:hidden; white-space:nowrap; font:13px sans-serif; text-align:center; cursor:pointer; }",
          ].join("\n")
        );
      },

      hexColor: function (r, g, b) {
        return (
          "#" +
          (
            ("0" + Math.round(r).toString(16)).slice(-2) +
            ("0" + Math.round(g).toString(16)).slice(-2) +
            ("0" + Math.round(b).toString(16)).slice(-2)
          ).toUpperCase()
        );
      },

      hexaColor: function (r, g, b, a) {
        return (
          "#" +
          (
            ("0" + Math.round(r).toString(16)).slice(-2) +
            ("0" + Math.round(g).toString(16)).slice(-2) +
            ("0" + Math.round(b).toString(16)).slice(-2) +
            ("0" + Math.round(a * 255).toString(16)).slice(-2)
          ).toUpperCase()
        );
      },

      rgbColor: function (r, g, b) {
        return (
          "rgb(" +
          Math.round(r) +
          "," +
          Math.round(g) +
          "," +
          Math.round(b) +
          ")"
        );
      },

      rgbaColor: function (r, g, b, a) {
        return (
          "rgba(" +
          Math.round(r) +
          "," +
          Math.round(g) +
          "," +
          Math.round(b) +
          "," +
          Math.round((a === undefined || a === null ? 1 : a) * 100) / 100 +
          ")"
        );
      },

      linearGradient: (function () {
        function getFuncName() {
          var stdName = "linear-gradient";
          var prefixes = ["", "-webkit-", "-moz-", "-o-", "-ms-"];
          var helper = window.document.createElement("div");

          for (var i = 0; i < prefixes.length; i += 1) {
            var tryFunc = prefixes[i] + stdName;
            var tryVal = tryFunc + "(to right, rgba(0,0,0,0), rgba(0,0,0,0))";

            helper.style.background = tryVal;
            if (helper.style.background) {
              // CSS background successfully set -> function name is supported
              return tryFunc;
            }
          }
          return stdName; // fallback to standard 'linear-gradient' without vendor prefix
        }

        var funcName = getFuncName();

        return function () {
          return (
            funcName + "(" + Array.prototype.join.call(arguments, ", ") + ")"
          );
        };
      })(),

      setBorderRadius: function (elm, value) {
        jsc.setStyle(elm, { "border-radius": value || "0" });
      },

      setBoxShadow: function (elm, value) {
        jsc.setStyle(elm, { "box-shadow": value || "none" });
      },

      getElementPos: function (e, relativeToViewport) {
        var x = 0,
          y = 0;
        var rect = e.getBoundingClientRect();
        x = rect.left;
        y = rect.top;
        if (!relativeToViewport) {
          var viewPos = jsc.getViewPos();
          x += viewPos[0];
          y += viewPos[1];
        }
        return [x, y];
      },

      getElementSize: function (e) {
        return [e.offsetWidth, e.offsetHeight];
      },

      // get pointer's X/Y coordinates relative to viewport
      getAbsPointerPos: function (e) {
        var x = 0,
          y = 0;
        if (
          typeof e.changedTouches !== "undefined" &&
          e.changedTouches.length
        ) {
          // touch devices
          x = e.changedTouches[0].clientX;
          y = e.changedTouches[0].clientY;
        } else if (typeof e.clientX === "number") {
          x = e.clientX;
          y = e.clientY;
        }
        return { x: x, y: y };
      },

      // get pointer's X/Y coordinates relative to target element
      getRelPointerPos: function (e) {
        var target = e.target || e.srcElement;
        var targetRect = target.getBoundingClientRect();

        var x = 0,
          y = 0;

        var clientX = 0,
          clientY = 0;
        if (
          typeof e.changedTouches !== "undefined" &&
          e.changedTouches.length
        ) {
          // touch devices
          clientX = e.changedTouches[0].clientX;
          clientY = e.changedTouches[0].clientY;
        } else if (typeof e.clientX === "number") {
          clientX = e.clientX;
          clientY = e.clientY;
        }

        x = clientX - targetRect.left;
        y = clientY - targetRect.top;
        return { x: x, y: y };
      },

      getViewPos: function () {
        var doc = window.document.documentElement;
        return [
          (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
          (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0),
        ];
      },

      getViewSize: function () {
        var doc = window.document.documentElement;
        return [
          window.innerWidth || doc.clientWidth,
          window.innerHeight || doc.clientHeight,
        ];
      },

      // r: 0-255
      // g: 0-255
      // b: 0-255
      //
      // returns: [ 0-360, 0-100, 0-100 ]
      //
      RGB_HSV: function (r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        var n = Math.min(Math.min(r, g), b);
        var v = Math.max(Math.max(r, g), b);
        var m = v - n;
        if (m === 0) {
          return [null, 0, 100 * v];
        }
        var h =
          r === n
            ? 3 + (b - g) / m
            : g === n
            ? 5 + (r - b) / m
            : 1 + (g - r) / m;
        return [60 * (h === 6 ? 0 : h), 100 * (m / v), 100 * v];
      },

      // h: 0-360
      // s: 0-100
      // v: 0-100
      //
      // returns: [ 0-255, 0-255, 0-255 ]
      //
      HSV_RGB: function (h, s, v) {
        var u = 255 * (v / 100);

        if (h === null) {
          return [u, u, u];
        }

        h /= 60;
        s /= 100;

        var i = Math.floor(h);
        var f = i % 2 ? h - i : 1 - (h - i);
        var m = u * (1 - s);
        var n = u * (1 - s * f);
        switch (i) {
          case 6:
          case 0:
            return [u, n, m];
          case 1:
            return [n, u, m];
          case 2:
            return [m, u, n];
          case 3:
            return [m, n, u];
          case 4:
            return [n, m, u];
          case 5:
            return [u, m, n];
        }
      },

      parseColorString: function (str) {
        var ret = {
          rgba: null,
          format: null, // 'hex' | 'hexa' | 'rgb' | 'rgba'
        };

        var m;

        if ((m = str.match(/^\W*([0-9A-F]{3,8})\W*$/i))) {
          // HEX notation

          if (m[1].length === 8) {
            // 8-char notation (= with alpha)
            ret.format = "hexa";
            ret.rgba = [
              parseInt(m[1].slice(0, 2), 16),
              parseInt(m[1].slice(2, 4), 16),
              parseInt(m[1].slice(4, 6), 16),
              parseInt(m[1].slice(6, 8), 16) / 255,
            ];
          } else if (m[1].length === 6) {
            // 6-char notation
            ret.format = "hex";
            ret.rgba = [
              parseInt(m[1].slice(0, 2), 16),
              parseInt(m[1].slice(2, 4), 16),
              parseInt(m[1].slice(4, 6), 16),
              null,
            ];
          } else if (m[1].length === 3) {
            // 3-char notation
            ret.format = "hex";
            ret.rgba = [
              parseInt(m[1].charAt(0) + m[1].charAt(0), 16),
              parseInt(m[1].charAt(1) + m[1].charAt(1), 16),
              parseInt(m[1].charAt(2) + m[1].charAt(2), 16),
              null,
            ];
          } else {
            return false;
          }

          return ret;
        }

        if ((m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i))) {
          // rgb(...) or rgba(...) notation

          var par = m[1].split(",");
          var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/;
          var mR, mG, mB, mA;
          if (
            par.length >= 3 &&
            (mR = par[0].match(re)) &&
            (mG = par[1].match(re)) &&
            (mB = par[2].match(re))
          ) {
            ret.format = "rgb";
            ret.rgba = [
              parseFloat(mR[1]) || 0,
              parseFloat(mG[1]) || 0,
              parseFloat(mB[1]) || 0,
              null,
            ];

            if (par.length >= 4 && (mA = par[3].match(re))) {
              ret.format = "rgba";
              ret.rgba[3] = parseFloat(mA[1]) || 0;
            }
            return ret;
          }
        }

        return false;
      },

      parsePaletteValue: function (mixed) {
        var vals = [];

        if (typeof mixed === "string") {
          // input is a string of space separated color values
          // rgb() and rgba() may contain spaces too, so let's find all color values by regex
          mixed.replace(
            /#[0-9A-F]{3}\b|#[0-9A-F]{6}([0-9A-F]{2})?\b|rgba?\(([^)]*)\)/gi,
            function (val) {
              vals.push(val);
            }
          );
        } else if (Array.isArray(mixed)) {
          // input is an array of color values
          vals = mixed;
        }

        // convert all values into uniform color format

        var colors = [];

        for (var i = 0; i < vals.length; i++) {
          var color = jsc.parseColorString(vals[i]);
          if (color) {
            colors.push(color);
          }
        }

        return colors;
      },

      containsTranparentColor: function (colors) {
        for (var i = 0; i < colors.length; i++) {
          var a = colors[i].rgba[3];
          if (a !== null && a < 1.0) {
            return true;
          }
        }
        return false;
      },

      isAlphaFormat: function (format) {
        switch (format.toLowerCase()) {
          case "hexa":
          case "rgba":
            return true;
        }
        return false;
      },

      // Canvas scaling for retina displays
      //
      // adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/
      //
      scaleCanvasForHighDPR: function (canvas) {
        var dpr = window.devicePixelRatio || 1;
        canvas.width *= dpr;
        canvas.height *= dpr;
        var ctx = canvas.getContext("2d");
        ctx.scale(dpr, dpr);
      },

      genColorPreviewCanvas: function (
        color,
        separatorPos,
        specWidth,
        scaleForHighDPR
      ) {
        var sepW = Math.round(jsc.pub.previewSeparator.length);
        var sqSize = jsc.pub.chessboardSize;
        var sqColor1 = jsc.pub.chessboardColor1;
        var sqColor2 = jsc.pub.chessboardColor2;

        var cWidth = specWidth ? specWidth : sqSize * 2;
        var cHeight = sqSize * 2;

        var canvas = jsc.createEl("canvas");
        var ctx = canvas.getContext("2d");

        canvas.width = cWidth;
        canvas.height = cHeight;
        if (scaleForHighDPR) {
          jsc.scaleCanvasForHighDPR(canvas);
        }

        // transparency chessboard - background
        ctx.fillStyle = sqColor1;
        ctx.fillRect(0, 0, cWidth, cHeight);

        // transparency chessboard - squares
        ctx.fillStyle = sqColor2;
        for (var x = 0; x < cWidth; x += sqSize * 2) {
          ctx.fillRect(x, 0, sqSize, sqSize);
          ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize);
        }

        if (color) {
          // actual color in foreground
          ctx.fillStyle = color;
          ctx.fillRect(0, 0, cWidth, cHeight);
        }

        var start = null;
        switch (separatorPos) {
          case "left":
            start = 0;
            ctx.clearRect(0, 0, sepW / 2, cHeight);
            break;
          case "right":
            start = cWidth - sepW;
            ctx.clearRect(cWidth - sepW / 2, 0, sepW / 2, cHeight);
            break;
        }
        if (start !== null) {
          ctx.lineWidth = 1;
          for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) {
            ctx.beginPath();
            ctx.strokeStyle = jsc.pub.previewSeparator[i];
            ctx.moveTo(0.5 + start + i, 0);
            ctx.lineTo(0.5 + start + i, cHeight);
            ctx.stroke();
          }
        }

        return {
          canvas: canvas,
          width: cWidth,
          height: cHeight,
        };
      },

      // if position or width is not set => fill the entire element (0%-100%)
      genColorPreviewGradient: function (color, position, width) {
        var params = [];

        if (position && width) {
          params = [
            "to " + { left: "right", right: "left" }[position],
            color + " 0%",
            color + " " + width + "px",
            "rgba(0,0,0,0) " + (width + 1) + "px",
            "rgba(0,0,0,0) 100%",
          ];
        } else {
          params = ["to right", color + " 0%", color + " 100%"];
        }

        return jsc.linearGradient.apply(this, params);
      },

      redrawPosition: function () {
        if (!jsc.picker || !jsc.picker.owner) {
          return; // picker is not shown
        }

        var thisObj = jsc.picker.owner;

        if (thisObj.container !== window.document.body) {
          jsc._drawPosition(thisObj, 0, 0, "relative", false);
        } else {
          var tp, vp;

          if (thisObj.fixed) {
            // Fixed elements are positioned relative to viewport,
            // therefore we can ignore the scroll offset
            tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
            vp = [0, 0]; // view pos
          } else {
            tp = jsc.getElementPos(thisObj.targetElement); // target pos
            vp = jsc.getViewPos(); // view pos
          }

          var ts = jsc.getElementSize(thisObj.targetElement); // target size
          var vs = jsc.getViewSize(); // view size
          var pd = jsc.getPickerDims(thisObj);
          var ps = [pd.borderW, pd.borderH]; // picker outer size
          var a, b, c;
          switch (thisObj.position.toLowerCase()) {
            case "left":
              a = 1;
              b = 0;
              c = -1;
              break;
            case "right":
              a = 1;
              b = 0;
              c = 1;
              break;
            case "top":
              a = 0;
              b = 1;
              c = -1;
              break;
            default:
              a = 0;
              b = 1;
              c = 1;
              break;
          }
          var l = (ts[b] + ps[b]) / 2;

          // compute picker position
          if (!thisObj.smartPosition) {
            var pp = [tp[a], tp[b] + ts[b] - l + l * c];
          } else {
            var pp = [
              -vp[a] + tp[a] + ps[a] > vs[a]
                ? -vp[a] + tp[a] + ts[a] / 2 > vs[a] / 2 &&
                  tp[a] + ts[a] - ps[a] >= 0
                  ? tp[a] + ts[a] - ps[a]
                  : tp[a]
                : tp[a],
              -vp[b] + tp[b] + ts[b] + ps[b] - l + l * c > vs[b]
                ? -vp[b] + tp[b] + ts[b] / 2 > vs[b] / 2 &&
                  tp[b] + ts[b] - l - l * c >= 0
                  ? tp[b] + ts[b] - l - l * c
                  : tp[b] + ts[b] - l + l * c
                : tp[b] + ts[b] - l + l * c >= 0
                ? tp[b] + ts[b] - l + l * c
                : tp[b] + ts[b] - l - l * c,
            ];
          }

          var x = pp[a];
          var y = pp[b];
          var positionValue = thisObj.fixed ? "fixed" : "absolute";
          var contractShadow =
            (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
            pp[1] + ps[1] < tp[1] + ts[1];

          jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
        }
      },

      _drawPosition: function (thisObj, x, y, positionValue, contractShadow) {
        var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px

        jsc.picker.wrap.style.position = positionValue;

        if (
          // To avoid unnecessary repositioning during scroll
          Math.round(parseFloat(jsc.picker.wrap.style.left)) !==
            Math.round(x) ||
          Math.round(parseFloat(jsc.picker.wrap.style.top)) !== Math.round(y)
        ) {
          jsc.picker.wrap.style.left = x + "px";
          jsc.picker.wrap.style.top = y + "px";
        }

        jsc.setBoxShadow(
          jsc.picker.boxS,
          thisObj.shadow
            ? new jsc.BoxShadow(
                0,
                vShadow,
                thisObj.shadowBlur,
                0,
                thisObj.shadowColor
              )
            : null
        );
      },

      getPickerDims: function (thisObj) {
        var w = 2 * thisObj.controlBorderWidth + thisObj.width;
        var h = 2 * thisObj.controlBorderWidth + thisObj.height;

        var sliderSpace =
          2 * thisObj.controlBorderWidth +
          2 * jsc.getControlPadding(thisObj) +
          thisObj.sliderSize;

        if (jsc.getSliderChannel(thisObj)) {
          w += sliderSpace;
        }
        if (thisObj.hasAlphaChannel()) {
          w += sliderSpace;
        }

        var pal = jsc.getPaletteDims(thisObj, w);

        if (pal.height) {
          h += pal.height + thisObj.padding;
        }
        if (thisObj.closeButton) {
          h +=
            2 * thisObj.controlBorderWidth +
            thisObj.padding +
            thisObj.buttonHeight;
        }

        var pW = w + 2 * thisObj.padding;
        var pH = h + 2 * thisObj.padding;

        return {
          contentW: w,
          contentH: h,
          paddedW: pW,
          paddedH: pH,
          borderW: pW + 2 * thisObj.borderWidth,
          borderH: pH + 2 * thisObj.borderWidth,
          palette: pal,
        };
      },

      getPaletteDims: function (thisObj, width) {
        var cols = 0,
          rows = 0,
          cellW = 0,
          cellH = 0,
          height = 0;
        var sampleCount = thisObj._palette ? thisObj._palette.length : 0;

        if (sampleCount) {
          cols = thisObj.paletteCols;
          rows = cols > 0 ? Math.ceil(sampleCount / cols) : 0;

          // color sample's dimensions (includes border)
          cellW = Math.max(
            1,
            Math.floor((width - (cols - 1) * thisObj.paletteSpacing) / cols)
          );
          cellH = thisObj.paletteHeight
            ? Math.min(thisObj.paletteHeight, cellW)
            : cellW;
        }

        if (rows) {
          height = rows * cellH + (rows - 1) * thisObj.paletteSpacing;
        }

        return {
          cols: cols,
          rows: rows,
          cellW: cellW,
          cellH: cellH,
          width: width,
          height: height,
        };
      },

      getControlPadding: function (thisObj) {
        return Math.max(
          thisObj.padding / 2,
          2 * thisObj.pointerBorderWidth +
            thisObj.pointerThickness -
            thisObj.controlBorderWidth
        );
      },

      getPadYChannel: function (thisObj) {
        switch (thisObj.mode.charAt(1).toLowerCase()) {
          case "v":
            return "v";
            break;
        }
        return "s";
      },

      getSliderChannel: function (thisObj) {
        if (thisObj.mode.length > 2) {
          switch (thisObj.mode.charAt(2).toLowerCase()) {
            case "s":
              return "s";
              break;
            case "v":
              return "v";
              break;
          }
        }
        return null;
      },

      // calls function specified in picker's property
      triggerCallback: function (thisObj, prop) {
        if (!thisObj[prop]) {
          return; // callback func not specified
        }
        var callback = null;

        if (typeof thisObj[prop] === "string") {
          // string with code
          try {
            callback = new Function(thisObj[prop]);
          } catch (e) {
            console.error(e);
          }
        } else {
          // function
          callback = thisObj[prop];
        }

        if (callback) {
          callback.call(thisObj);
        }
      },

      // Triggers a color change related event(s) on all picker instances.
      // It is possible to specify multiple events separated with a space.
      triggerGlobal: function (eventNames) {
        var inst = jsc.getInstances();
        for (var i = 0; i < inst.length; i += 1) {
          inst[i].trigger(eventNames);
        }
      },

      _pointerMoveEvent: {
        mouse: "mousemove",
        touch: "touchmove",
      },
      _pointerEndEvent: {
        mouse: "mouseup",
        touch: "touchend",
      },

      _pointerOrigin: null,

      onDocumentKeyUp: function (e) {
        if (["Tab", "Escape"].indexOf(jsc.eventKey(e)) !== -1) {
          if (jsc.picker && jsc.picker.owner) {
            jsc.picker.owner.tryHide();
          }
        }
      },

      onWindowResize: function (e) {
        jsc.redrawPosition();
      },

      onWindowScroll: function (e) {
        jsc.redrawPosition();
      },

      onParentScroll: function (e) {
        // hide the picker when one of the parent elements is scrolled
        if (jsc.picker && jsc.picker.owner) {
          jsc.picker.owner.tryHide();
        }
      },

      onDocumentMouseDown: function (e) {
        var target = e.target || e.srcElement;

        if (target.jscolor && target.jscolor instanceof jsc.pub) {
          // clicked targetElement -> show picker
          if (target.jscolor.showOnClick && !target.disabled) {
            target.jscolor.show();
          }
        } else if (jsc.getData(target, "gui")) {
          // clicked jscolor's GUI element
          var control = jsc.getData(target, "control");
          if (control) {
            // jscolor's control
            jsc.onControlPointerStart(
              e,
              target,
              jsc.getData(target, "control"),
              "mouse"
            );
          }
        } else {
          // mouse is outside the picker's controls -> hide the color picker!
          if (jsc.picker && jsc.picker.owner) {
            jsc.picker.owner.tryHide();
          }
        }
      },

      onPickerTouchStart: function (e) {
        var target = e.target || e.srcElement;

        if (jsc.getData(target, "control")) {
          jsc.onControlPointerStart(
            e,
            target,
            jsc.getData(target, "control"),
            "touch"
          );
        }
      },

      onControlPointerStart: function (e, target, controlName, pointerType) {
        var thisObj = jsc.getData(target, "instance");

        jsc.preventDefault(e);

        var registerDragEvents = function (doc, offset) {
          jsc.attachGroupEvent(
            "drag",
            doc,
            jsc._pointerMoveEvent[pointerType],
            jsc.onDocumentPointerMove(
              e,
              target,
              controlName,
              pointerType,
              offset
            )
          );
          jsc.attachGroupEvent(
            "drag",
            doc,
            jsc._pointerEndEvent[pointerType],
            jsc.onDocumentPointerEnd(e, target, controlName, pointerType)
          );
        };

        registerDragEvents(window.document, [0, 0]);

        if (window.parent && window.frameElement) {
          var rect = window.frameElement.getBoundingClientRect();
          var ofs = [-rect.left, -rect.top];
          registerDragEvents(window.parent.window.document, ofs);
        }

        var abs = jsc.getAbsPointerPos(e);
        var rel = jsc.getRelPointerPos(e);
        jsc._pointerOrigin = {
          x: abs.x - rel.x,
          y: abs.y - rel.y,
        };

        switch (controlName) {
          case "pad":
            // if the value slider is at the bottom, move it up
            if (
              jsc.getSliderChannel(thisObj) === "v" &&
              thisObj.channels.v === 0
            ) {
              thisObj.fromHSVA(null, null, 100, null);
            }
            jsc.setPad(thisObj, e, 0, 0);
            break;

          case "sld":
            jsc.setSld(thisObj, e, 0);
            break;

          case "asld":
            jsc.setASld(thisObj, e, 0);
            break;
        }
        thisObj.trigger("input");
      },

      onDocumentPointerMove: function (
        e,
        target,
        controlName,
        pointerType,
        offset
      ) {
        return function (e) {
          var thisObj = jsc.getData(target, "instance");
          switch (controlName) {
            case "pad":
              jsc.setPad(thisObj, e, offset[0], offset[1]);
              break;

            case "sld":
              jsc.setSld(thisObj, e, offset[1]);
              break;

            case "asld":
              jsc.setASld(thisObj, e, offset[1]);
              break;
          }
          thisObj.trigger("input");
        };
      },

      onDocumentPointerEnd: function (e, target, controlName, pointerType) {
        return function (e) {
          var thisObj = jsc.getData(target, "instance");
          jsc.detachGroupEvents("drag");

          // Always trigger changes AFTER detaching outstanding mouse handlers,
          // in case some color change that occured in user-defined onChange/onInput handler
          // intruded into current mouse events
          thisObj.trigger("input");
          thisObj.trigger("change");
        };
      },

      onPaletteSampleClick: function (e) {
        var target = e.currentTarget;
        var thisObj = jsc.getData(target, "instance");
        var color = jsc.getData(target, "color");

        // when format is flexible, use the original format of this color sample
        if (thisObj.format.toLowerCase() === "any") {
          thisObj._setFormat(color.format); // adapt format
          if (!jsc.isAlphaFormat(thisObj.getFormat())) {
            color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
          }
        }

        // if this color doesn't specify alpha, use alpha of 1.0 (if applicable)
        if (color.rgba[3] === null) {
          if (
            thisObj.paletteSetsAlpha === true ||
            (thisObj.paletteSetsAlpha === "auto" &&
              thisObj._paletteHasTransparency)
          ) {
            color.rgba[3] = 1.0;
          }
        }

        thisObj.fromRGBA.apply(thisObj, color.rgba);

        thisObj.trigger("input");
        thisObj.trigger("change");

        if (thisObj.hideOnPaletteClick) {
          thisObj.hide();
        }
      },

      setPad: function (thisObj, e, ofsX, ofsY) {
        var pointerAbs = jsc.getAbsPointerPos(e);
        var x =
          ofsX +
          pointerAbs.x -
          jsc._pointerOrigin.x -
          thisObj.padding -
          thisObj.controlBorderWidth;
        var y =
          ofsY +
          pointerAbs.y -
          jsc._pointerOrigin.y -
          thisObj.padding -
          thisObj.controlBorderWidth;

        var xVal = x * (360 / (thisObj.width - 1));
        var yVal = 100 - y * (100 / (thisObj.height - 1));

        switch (jsc.getPadYChannel(thisObj)) {
          case "s":
            thisObj.fromHSVA(xVal, yVal, null, null);
            break;
          case "v":
            thisObj.fromHSVA(xVal, null, yVal, null);
            break;
        }
      },

      setSld: function (thisObj, e, ofsY) {
        var pointerAbs = jsc.getAbsPointerPos(e);
        var y =
          ofsY +
          pointerAbs.y -
          jsc._pointerOrigin.y -
          thisObj.padding -
          thisObj.controlBorderWidth;
        var yVal = 100 - y * (100 / (thisObj.height - 1));

        switch (jsc.getSliderChannel(thisObj)) {
          case "s":
            thisObj.fromHSVA(null, yVal, null, null);
            break;
          case "v":
            thisObj.fromHSVA(null, null, yVal, null);
            break;
        }
      },

      setASld: function (thisObj, e, ofsY) {
        var pointerAbs = jsc.getAbsPointerPos(e);
        var y =
          ofsY +
          pointerAbs.y -
          jsc._pointerOrigin.y -
          thisObj.padding -
          thisObj.controlBorderWidth;
        var yVal = 1.0 - y * (1.0 / (thisObj.height - 1));

        if (yVal < 1.0) {
          // if format is flexible and the current format doesn't support alpha, switch to a suitable one
          var fmt = thisObj.getFormat();
          if (
            thisObj.format.toLowerCase() === "any" &&
            !jsc.isAlphaFormat(fmt)
          ) {
            thisObj._setFormat(fmt === "hex" ? "hexa" : "rgba");
          }
        }

        thisObj.fromHSVA(null, null, null, yVal);
      },

      createPadCanvas: function () {
        var ret = {
          elm: null,
          draw: null,
        };

        var canvas = jsc.createEl("canvas");
        var ctx = canvas.getContext("2d");

        var drawFunc = function (width, height, type) {
          canvas.width = width;
          canvas.height = height;

          ctx.clearRect(0, 0, canvas.width, canvas.height);

          var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
          hGrad.addColorStop(0 / 6, "#F00");
          hGrad.addColorStop(1 / 6, "#FF0");
          hGrad.addColorStop(2 / 6, "#0F0");
          hGrad.addColorStop(3 / 6, "#0FF");
          hGrad.addColorStop(4 / 6, "#00F");
          hGrad.addColorStop(5 / 6, "#F0F");
          hGrad.addColorStop(6 / 6, "#F00");

          ctx.fillStyle = hGrad;
          ctx.fillRect(0, 0, canvas.width, canvas.height);

          var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
          switch (type.toLowerCase()) {
            case "s":
              vGrad.addColorStop(0, "rgba(255,255,255,0)");
              vGrad.addColorStop(1, "rgba(255,255,255,1)");
              break;
            case "v":
              vGrad.addColorStop(0, "rgba(0,0,0,0)");
              vGrad.addColorStop(1, "rgba(0,0,0,1)");
              break;
          }
          ctx.fillStyle = vGrad;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
        };

        ret.elm = canvas;
        ret.draw = drawFunc;

        return ret;
      },

      createSliderGradient: function () {
        var ret = {
          elm: null,
          draw: null,
        };

        var canvas = jsc.createEl("canvas");
        var ctx = canvas.getContext("2d");

        var drawFunc = function (width, height, color1, color2) {
          canvas.width = width;
          canvas.height = height;

          ctx.clearRect(0, 0, canvas.width, canvas.height);

          var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
          grad.addColorStop(0, color1);
          grad.addColorStop(1, color2);

          ctx.fillStyle = grad;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
        };

        ret.elm = canvas;
        ret.draw = drawFunc;

        return ret;
      },

      createASliderGradient: function () {
        var ret = {
          elm: null,
          draw: null,
        };

        var canvas = jsc.createEl("canvas");
        var ctx = canvas.getContext("2d");

        var drawFunc = function (width, height, color) {
          canvas.width = width;
          canvas.height = height;

          ctx.clearRect(0, 0, canvas.width, canvas.height);

          var sqSize = canvas.width / 2;
          var sqColor1 = jsc.pub.chessboardColor1;
          var sqColor2 = jsc.pub.chessboardColor2;

          // dark gray background
          ctx.fillStyle = sqColor1;
          ctx.fillRect(0, 0, canvas.width, canvas.height);

          if (sqSize > 0) {
            // to avoid infinite loop
            for (var y = 0; y < canvas.height; y += sqSize * 2) {
              // light gray squares
              ctx.fillStyle = sqColor2;
              ctx.fillRect(0, y, sqSize, sqSize);
              ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize);
            }
          }

          var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
          grad.addColorStop(0, color);
          grad.addColorStop(1, "rgba(0,0,0,0)");

          ctx.fillStyle = grad;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
        };

        ret.elm = canvas;
        ret.draw = drawFunc;

        return ret;
      },

      BoxShadow: (function () {
        var BoxShadow = function (
          hShadow,
          vShadow,
          blur,
          spread,
          color,
          inset
        ) {
          this.hShadow = hShadow;
          this.vShadow = vShadow;
          this.blur = blur;
          this.spread = spread;
          this.color = color;
          this.inset = !!inset;
        };

        BoxShadow.prototype.toString = function () {
          var vals = [
            Math.round(this.hShadow) + "px",
            Math.round(this.vShadow) + "px",
            Math.round(this.blur) + "px",
            Math.round(this.spread) + "px",
            this.color,
          ];
          if (this.inset) {
            vals.push("inset");
          }
          return vals.join(" ");
        };

        return BoxShadow;
      })(),

      flags: {
        leaveValue: 1 << 0,
        leaveAlpha: 1 << 1,
        leavePreview: 1 << 2,
      },

      enumOpts: {
        format: ["auto", "any", "hex", "hexa", "rgb", "rgba"],
        previewPosition: ["left", "right"],
        mode: ["hsv", "hvs", "hs", "hv"],
        position: ["left", "right", "top", "bottom"],
        alphaChannel: ["auto", true, false],
        paletteSetsAlpha: ["auto", true, false],
      },

      deprecatedOpts: {
        // <old_option>: <new_option>  (<new_option> can be null)
        styleElement: "previewElement",
        onFineChange: "onInput",
        overwriteImportant: "forceStyle",
        closable: "closeButton",
        insetWidth: "controlBorderWidth",
        insetColor: "controlBorderColor",
        refine: null,
      },

      docsRef: " " + "See https://jscolor.com/docs/",

      //
      // Usage:
      // var myPicker = new JSColor(<targetElement> [, <options>])
      //
      // (constructor is accessible via both 'jscolor' and 'JSColor' name)
      //

      pub: function (targetElement, opts) {
        var THIS = this;

        if (!opts) {
          opts = {};
        }

        this.channels = {
          r: 255, // red [0-255]
          g: 255, // green [0-255]
          b: 255, // blue [0-255]
          h: 0, // hue [0-360]
          s: 0, // saturation [0-100]
          v: 100, // value (brightness) [0-100]
          a: 1.0, // alpha (opacity) [0.0 - 1.0]
        };

        // General options
        //
        this.format = "auto"; // 'auto' | 'any' | 'hex' | 'hexa' | 'rgb' | 'rgba' - Format of the input/output value
        this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel()
        this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', <value>)
        this.random = false; // whether to randomize the initial color. Either true | false, or an array of ranges: [minV, maxV, minS, maxS, minH, maxH, minA, maxA]
        this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code.
        this.onInput = undefined; // called repeatedly as the color is being changed, e.g. while dragging a slider. Value can be either a function or a string with JS code.
        this.valueElement = undefined; // element that will be used to display and input the color value
        this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value
        this.previewElement = undefined; // element that will preview the picked color using CSS background
        this.previewPosition = "left"; // 'left' | 'right' - position of the color preview in previewElement
        this.previewSize = 32; // (px) width of the color preview displayed in previewElement
        this.previewPadding = 8; // (px) space between color preview and content of the previewElement
        this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty.
        this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format)
        this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format)
        this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag

        // Color Picker options
        //
        this.width = 181; // width of the color spectrum (in px)
        this.height = 101; // height of the color spectrum (in px)
        this.mode = "HSV"; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls
        this.alphaChannel = "auto"; // 'auto' | true | false - if alpha channel is enabled, the alpha slider will be visible. If 'auto', it will be determined according to color format
        this.position = "bottom"; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element
        this.smartPosition = true; // automatically change picker position when there is not enough space for it
        this.showOnClick = true; // whether to show the picker when user clicks its target element
        this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document)
        this.palette = []; // colors to be displayed in the palette, specified as an array or a string of space separated color values (in any supported format)
        this.paletteCols = 10; // number of columns in the palette
        this.paletteSetsAlpha = "auto"; // 'auto' | true | false - if true, palette colors that don't specify alpha will set alpha to 1.0
        this.paletteHeight = 16; // maximum height (px) of a row in the palette
        this.paletteSpacing = 4; // distance (px) between color samples in the palette
        this.hideOnPaletteClick = false; // when set to true, clicking the palette will also hide the color picker
        this.sliderSize = 16; // px
        this.crossSize = 8; // px
        this.closeButton = false; // whether to display the Close button
        this.closeText = "Close";
        this.buttonColor = "rgba(0,0,0,1)"; // CSS color
        this.buttonHeight = 18; // px
        this.padding = 12; // px
        this.backgroundColor = "rgba(255,255,255,1)"; // CSS color
        this.borderWidth = 1; // px
        this.borderColor = "rgba(187,187,187,1)"; // CSS color
        this.borderRadius = 8; // px
        this.controlBorderWidth = 1; // px
        this.controlBorderColor = "rgba(187,187,187,1)"; // CSS color
        this.shadow = true; // whether to display a shadow
        this.shadowBlur = 15; // px
        this.shadowColor = "rgba(0,0,0,0.2)"; // CSS color
        this.pointerColor = "rgba(76,76,76,1)"; // CSS color
        this.pointerBorderWidth = 1; // px
        this.pointerBorderColor = "rgba(255,255,255,1)"; // CSS color
        this.pointerThickness = 2; // px
        this.zIndex = 5000;
        this.container = undefined; // where to append the color picker (BODY element by default)

        // Experimental
        //
        this.minS = 0; // min allowed saturation (0 - 100)
        this.maxS = 100; // max allowed saturation (0 - 100)
        this.minV = 0; // min allowed value (brightness) (0 - 100)
        this.maxV = 100; // max allowed value (brightness) (0 - 100)
        this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0)
        this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0)

        // Getter: option(name)
        // Setter: option(name, value)
        //         option({name:value, ...})
        //
        this.option = function () {
          if (!arguments.length) {
            throw new Error("No option specified");
          }

          if (arguments.length === 1 && typeof arguments[0] === "string") {
            // getting a single option
            try {
              return getOption(arguments[0]);
            } catch (e) {
              console.warn(e);
            }
            return false;
          } else if (
            arguments.length >= 2 &&
            typeof arguments[0] === "string"
          ) {
            // setting a single option
            try {
              if (!setOption(arguments[0], arguments[1])) {
                return false;
              }
            } catch (e) {
              console.warn(e);
              return false;
            }
            this.redraw(); // immediately redraws the picker, if it's displayed
            this.exposeColor(); // in case some preview-related or format-related option was changed
            return true;
          } else if (
            arguments.length === 1 &&
            typeof arguments[0] === "object"
          ) {
            // setting multiple options
            var opts = arguments[0];
            var success = true;
            for (var opt in opts) {
              if (opts.hasOwnProperty(opt)) {
                try {
                  if (!setOption(opt, opts[opt])) {
                    success = false;
                  }
                } catch (e) {
                  console.warn(e);
                  success = false;
                }
              }
            }
            this.redraw(); // immediately redraws the picker, if it's displayed
            this.exposeColor(); // in case some preview-related or format-related option was changed
            return success;
          }

          throw new Error("Invalid arguments");
        };

        // Getter: channel(name)
        // Setter: channel(name, value)
        //
        this.channel = function (name, value) {
          if (typeof name !== "string") {
            throw new Error("Invalid value for channel name: " + name);
          }

          if (value === undefined) {
            // getting channel value
            if (!this.channels.hasOwnProperty(name.toLowerCase())) {
              console.warn("Getting unknown channel: " + name);
              return false;
            }
            return this.channels[name.toLowerCase()];
          } else {
            // setting channel value
            var res = false;
            switch (name.toLowerCase()) {
              case "r":
                res = this.fromRGBA(value, null, null, null);
                break;
              case "g":
                res = this.fromRGBA(null, value, null, null);
                break;
              case "b":
                res = this.fromRGBA(null, null, value, null);
                break;
              case "h":
                res = this.fromHSVA(value, null, null, null);
                break;
              case "s":
                res = this.fromHSVA(null, value, null, null);
                break;
              case "v":
                res = this.fromHSVA(null, null, value, null);
                break;
              case "a":
                res = this.fromHSVA(null, null, null, value);
                break;
              default:
                console.warn("Setting unknown channel: " + name);
                return false;
            }
            if (res) {
              this.redraw(); // immediately redraws the picker, if it's displayed
              return true;
            }
          }

          return false;
        };

        // Triggers given input event(s) by:
        // - executing on<Event> callback specified as picker's option
        // - triggering standard DOM event listeners attached to the value element
        //
        // It is possible to specify multiple events separated with a space.
        //
        this.trigger = function (eventNames) {
          var evs = jsc.strList(eventNames);
          for (var i = 0; i < evs.length; i += 1) {
            var ev = evs[i].toLowerCase();

            // trigger a callback
            var callbackProp = null;
            switch (ev) {
              case "input":
                callbackProp = "onInput";
                break;
              case "change":
                callbackProp = "onChange";
                break;
            }
            if (callbackProp) {
              jsc.triggerCallback(this, callbackProp);
            }

            // trigger standard DOM event listeners on the value element
            jsc.triggerInputEvent(this.valueElement, ev, true, true);
          }
        };

        // h: 0-360
        // s: 0-100
        // v: 0-100
        // a: 0.0-1.0
        //
        this.fromHSVA = function (h, s, v, a, flags) {
          // null = don't change
          if (h === undefined) {
            h = null;
          }
          if (s === undefined) {
            s = null;
          }
          if (v === undefined) {
            v = null;
          }
          if (a === undefined) {
            a = null;
          }

          if (h !== null) {
            if (isNaN(h)) {
              return false;
            }
            this.channels.h = Math.max(0, Math.min(360, h));
          }
          if (s !== null) {
            if (isNaN(s)) {
              return false;
            }
            this.channels.s = Math.max(
              0,
              Math.min(100, this.maxS, s),
              this.minS
            );
          }
          if (v !== null) {
            if (isNaN(v)) {
              return false;
            }
            this.channels.v = Math.max(
              0,
              Math.min(100, this.maxV, v),
              this.minV
            );
          }
          if (a !== null) {
            if (isNaN(a)) {
              return false;
            }
            this.channels.a = this.hasAlphaChannel()
              ? Math.max(0, Math.min(1, this.maxA, a), this.minA)
              : 1.0; // if alpha channel is disabled, the color should stay 100% opaque
          }

          var rgb = jsc.HSV_RGB(
            this.channels.h,
            this.channels.s,
            this.channels.v
          );
          this.channels.r = rgb[0];
          this.channels.g = rgb[1];
          this.channels.b = rgb[2];

          this.exposeColor(flags);
          return true;
        };

        // r: 0-255
        // g: 0-255
        // b: 0-255
        // a: 0.0-1.0
        //
        this.fromRGBA = function (r, g, b, a, flags) {
          // null = don't change
          if (r === undefined) {
            r = null;
          }
          if (g === undefined) {
            g = null;
          }
          if (b === undefined) {
            b = null;
          }
          if (a === undefined) {
            a = null;
          }

          if (r !== null) {
            if (isNaN(r)) {
              return false;
            }
            r = Math.max(0, Math.min(255, r));
          }
          if (g !== null) {
            if (isNaN(g)) {
              return false;
            }
            g = Math.max(0, Math.min(255, g));
          }
          if (b !== null) {
            if (isNaN(b)) {
              return false;
            }
            b = Math.max(0, Math.min(255, b));
          }
          if (a !== null) {
            if (isNaN(a)) {
              return false;
            }
            this.channels.a = this.hasAlphaChannel()
              ? Math.max(0, Math.min(1, this.maxA, a), this.minA)
              : 1.0; // if alpha channel is disabled, the color should stay 100% opaque
          }

          var hsv = jsc.RGB_HSV(
            r === null ? this.channels.r : r,
            g === null ? this.channels.g : g,
            b === null ? this.channels.b : b
          );
          if (hsv[0] !== null) {
            this.channels.h = Math.max(0, Math.min(360, hsv[0]));
          }
          if (hsv[2] !== 0) {
            // fully black color stays black through entire saturation range, so let's not change saturation
            this.channels.s = Math.max(
              0,
              this.minS,
              Math.min(100, this.maxS, hsv[1])
            );
          }
          this.channels.v = Math.max(
            0,
            this.minV,
            Math.min(100, this.maxV, hsv[2])
          );

          // update RGB according to final HSV, as some values might be trimmed
          var rgb = jsc.HSV_RGB(
            this.channels.h,
            this.channels.s,
            this.channels.v
          );
          this.channels.r = rgb[0];
          this.channels.g = rgb[1];
          this.channels.b = rgb[2];

          this.exposeColor(flags);
          return true;
        };

        // DEPRECATED. Use .fromHSVA() instead
        //
        this.fromHSV = function (h, s, v, flags) {
          console.warn(
            "fromHSV() method is DEPRECATED. Using fromHSVA() instead." +
              jsc.docsRef
          );
          return this.fromHSVA(h, s, v, null, flags);
        };

        // DEPRECATED. Use .fromRGBA() instead
        //
        this.fromRGB = function (r, g, b, flags) {
          console.warn(
            "fromRGB() method is DEPRECATED. Using fromRGBA() instead." +
              jsc.docsRef
          );
          return this.fromRGBA(r, g, b, null, flags);
        };

        this.fromString = function (str, flags) {
          if (!this.required && str.trim() === "") {
            // setting empty string to an optional color input
            this.setPreviewElementBg(null);
            this.setValueElementValue("");
            return true;
          }

          var color = jsc.parseColorString(str);
          if (!color) {
            return false; // could not parse
          }
          if (this.format.toLowerCase() === "any") {
            this._setFormat(color.format); // adapt format
            if (!jsc.isAlphaFormat(this.getFormat())) {
              color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
            }
          }
          this.fromRGBA(
            color.rgba[0],
            color.rgba[1],
            color.rgba[2],
            color.rgba[3],
            flags
          );
          return true;
        };

        this.randomize = function (
          minV,
          maxV,
          minS,
          maxS,
          minH,
          maxH,
          minA,
          maxA
        ) {
          if (minV === undefined) {
            minV = 0;
          }
          if (maxV === undefined) {
            maxV = 100;
          }
          if (minS === undefined) {
            minS = 0;
          }
          if (maxS === undefined) {
            maxS = 100;
          }
          if (minH === undefined) {
            minH = 0;
          }
          if (maxH === undefined) {
            maxH = 359;
          }
          if (minA === undefined) {
            minA = 1;
          }
          if (maxA === undefined) {
            maxA = 1;
          }

          this.fromHSVA(
            minH + Math.floor(Math.random() * (maxH - minH + 1)),
            minS + Math.floor(Math.random() * (maxS - minS + 1)),
            minV + Math.floor(Math.random() * (maxV - minV + 1)),
            (100 * minA +
              Math.floor(Math.random() * (100 * (maxA - minA) + 1))) /
              100
          );
        };

        this.toString = function (format) {
          if (format === undefined) {
            format = this.getFormat(); // format not specified -> use the current format
          }
          switch (format.toLowerCase()) {
            case "hex":
              return this.toHEXString();
              break;
            case "hexa":
              return this.toHEXAString();
              break;
            case "rgb":
              return this.toRGBString();
              break;
            case "rgba":
              return this.toRGBAString();
              break;
          }
          return false;
        };

        this.toHEXString = function () {
          return jsc.hexColor(
            this.channels.r,
            this.channels.g,
            this.channels.b
          );
        };

        this.toHEXAString = function () {
          return jsc.hexaColor(
            this.channels.r,
            this.channels.g,
            this.channels.b,
            this.channels.a
          );
        };

        this.toRGBString = function () {
          return jsc.rgbColor(
            this.channels.r,
            this.channels.g,
            this.channels.b
          );
        };

        this.toRGBAString = function () {
          return jsc.rgbaColor(
            this.channels.r,
            this.channels.g,
            this.channels.b,
            this.channels.a
          );
        };

        this.toGrayscale = function () {
          return (
            0.213 * this.channels.r +
            0.715 * this.channels.g +
            0.072 * this.channels.b
          );
        };

        this.toCanvas = function () {
          return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas;
        };

        this.toDataURL = function () {
          return this.toCanvas().toDataURL();
        };

        this.toBackground = function () {
          return jsc.pub.background(this.toRGBAString());
        };

        this.isLight = function () {
          return this.toGrayscale() > 255 / 2;
        };

        this.hide = function () {
          if (isPickerOwner()) {
            detachPicker();
          }
        };

        this.show = function () {
          drawPicker();
        };

        this.redraw = function () {
          if (isPickerOwner()) {
            drawPicker();
          }
        };

        this.getFormat = function () {
          return this._currentFormat;
        };

        this._setFormat = function (format) {
          this._currentFormat = format.toLowerCase();
        };

        this.hasAlphaChannel = function () {
          if (this.alphaChannel === "auto") {
            return (
              this.format.toLowerCase() === "any" || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled
              jsc.isAlphaFormat(this.getFormat()) || // the current format supports alpha channel
              this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel
              this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel
            );
          }

          return this.alphaChannel; // the alpha channel is explicitly set
        };

        this.processValueInput = function (str) {
          if (!this.fromString(str)) {
            // could not parse the color value - let's just expose the current color
            this.exposeColor();
          }
        };

        this.processAlphaInput = function (str) {
          if (!this.fromHSVA(null, null, null, parseFloat(str))) {
            // could not parse the alpha value - let's just expose the current color
            this.exposeColor();
          }
        };

        this.exposeColor = function (flags) {
          var colorStr = this.toString();
          var fmt = this.getFormat();

          // reflect current color in data- attribute
          jsc.setDataAttr(this.targetElement, "current-color", colorStr);

          if (!(flags & jsc.flags.leaveValue) && this.valueElement) {
            if (fmt === "hex" || fmt === "hexa") {
              if (!this.uppercase) {
                colorStr = colorStr.toLowerCase();
              }
              if (!this.hash) {
                colorStr = colorStr.replace(/^#/, "");
              }
            }
            this.setValueElementValue(colorStr);
          }

          if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) {
            var alphaVal = Math.round(this.channels.a * 100) / 100;
            this.setAlphaElementValue(alphaVal);
          }

          if (!(flags & jsc.flags.leavePreview) && this.previewElement) {
            var previewPos = null; // 'left' | 'right' (null -> fill the entire element)

            if (
              jsc.isTextInput(this.previewElement) || // text input
              (jsc.isButton(this.previewElement) &&
                !jsc.isButtonEmpty(this.previewElement)) // button with text
            ) {
              previewPos = this.previewPosition;
            }

            this.setPreviewElementBg(this.toRGBAString());
          }

          if (isPickerOwner()) {
            redrawPad();
            redrawSld();
            redrawASld();
          }
        };

        this.setPreviewElementBg = function (color) {
          if (!this.previewElement) {
            return;
          }

          var position = null; // color preview position:  null | 'left' | 'right'
          var width = null; // color preview width:  px | null = fill the entire element
          if (
            jsc.isTextInput(this.previewElement) || // text input
            (jsc.isButton(this.previewElement) &&
              !jsc.isButtonEmpty(this.previewElement)) // button with text
          ) {
            position = this.previewPosition;
            width = this.previewSize;
          }

          var backgrounds = [];

          if (!color) {
            // there is no color preview to display -> let's remove any previous background image
            backgrounds.push({
              image: "none",
              position: "left top",
              size: "auto",
              repeat: "no-repeat",
              origin: "padding-box",
            });
          } else {
            // CSS gradient for background color preview
            backgrounds.push({
              image: jsc.genColorPreviewGradient(
                color,
                position,
                width ? width - jsc.pub.previewSeparator.length : null
              ),
              position: "left top",
              size: "auto",
              repeat: position ? "repeat-y" : "repeat",
              origin: "padding-box",
            });

            // data URL of generated PNG image with a gray transparency chessboard
            var preview = jsc.genColorPreviewCanvas(
              "rgba(0,0,0,0)",
              position ? { left: "right", right: "left" }[position] : null,
              width,
              true
            );
            backgrounds.push({
              image: "url('" + preview.canvas.toDataURL() + "')",
              position: (position || "left") + " top",
              size: preview.width + "px " + preview.height + "px",
              repeat: position ? "repeat-y" : "repeat",
              origin: "padding-box",
            });
          }

          var bg = {
            image: [],
            position: [],
            size: [],
            repeat: [],
            origin: [],
          };
          for (var i = 0; i < backgrounds.length; i += 1) {
            bg.image.push(backgrounds[i].image);
            bg.position.push(backgrounds[i].position);
            bg.size.push(backgrounds[i].size);
            bg.repeat.push(backgrounds[i].repeat);
            bg.origin.push(backgrounds[i].origin);
          }

          // set previewElement's background-images
          var sty = {
            "background-image": bg.image.join(", "),
            "background-position": bg.position.join(", "),
            "background-size": bg.size.join(", "),
            "background-repeat": bg.repeat.join(", "),
            "background-origin": bg.origin.join(", "),
          };
          jsc.setStyle(this.previewElement, sty, this.forceStyle);

          // set/restore previewElement's padding
          var padding = {
            left: null,
            right: null,
          };
          if (position) {
            padding[position] = this.previewSize + this.previewPadding + "px";
          }

          var sty = {
            "padding-left": padding.left,
            "padding-right": padding.right,
          };
          jsc.setStyle(this.previewElement, sty, this.forceStyle, true);
        };

        this.setValueElementValue = function (str) {
          if (this.valueElement) {
            if (jsc.nodeName(this.valueElement) === "input") {
              this.valueElement.value = str;
            } else {
              this.valueElement.innerHTML = str;
            }
          }
        };

        this.setAlphaElementValue = function (str) {
          if (this.alphaElement) {
            if (jsc.nodeName(this.alphaElement) === "input") {
              this.alphaElement.value = str;
            } else {
              this.alphaElement.innerHTML = str;
            }
          }
        };

        this._processParentElementsInDOM = function () {
          if (this._parentElementsProcessed) {
            return;
          }
          this._parentElementsProcessed = true;

          var elm = this.targetElement;
          do {
            // If the target element or one of its parent nodes has fixed position,
            // then use fixed positioning instead
            var compStyle = jsc.getCompStyle(elm);
            if (
              compStyle.position &&
              compStyle.position.toLowerCase() === "fixed"
            ) {
              this.fixed = true;
            }

            if (elm !== this.targetElement) {
              // Ensure to attach onParentScroll only once to each parent element
              // (multiple targetElements can share the same parent nodes)
              //
              // Note: It's not just offsetParents that can be scrollable,
              // that's why we loop through all parent nodes
              if (!jsc.getData(elm, "hasScrollListener")) {
                elm.addEventListener("scroll", jsc.onParentScroll, false);
                jsc.setData(elm, "hasScrollListener", true);
              }
            }
          } while ((elm = elm.parentNode) && jsc.nodeName(elm) !== "body");
        };

        this.tryHide = function () {
          if (this.hideOnLeave) {
            this.hide();
          }
        };

        this.set__palette = function (val) {
          this.palette = val;
          this._palette = jsc.parsePaletteValue(val);
          this._paletteHasTransparency = jsc.containsTranparentColor(
            this._palette
          );
        };

        function setOption(option, value) {
          if (typeof option !== "string") {
            throw new Error("Invalid value for option name: " + option);
          }

          // enum option
          if (jsc.enumOpts.hasOwnProperty(option)) {
            if (typeof value === "string") {
              // enum string values are case insensitive
              value = value.toLowerCase();
            }
            if (jsc.enumOpts[option].indexOf(value) === -1) {
              throw new Error(
                "Option '" + option + "' has invalid value: " + value
              );
            }
          }

          // deprecated option
          if (jsc.deprecatedOpts.hasOwnProperty(option)) {
            var oldOpt = option;
            var newOpt = jsc.deprecatedOpts[option];
            if (newOpt) {
              // if we have a new name for this option, let's log a warning and use the new name
              console.warn(
                "Option '%s' is DEPRECATED, using '%s' instead." + jsc.docsRef,
                oldOpt,
                newOpt
              );
              option = newOpt;
            } else {
              // new name not available for the option
              throw new Error("Option '" + option + "' is DEPRECATED");
            }
          }

          var setter = "set__" + option;

          if (typeof THIS[setter] === "function") {
            // a setter exists for this option
            THIS[setter](value);
            return true;
          } else if (option in THIS) {
            // option exists as a property
            THIS[option] = value;
            return true;
          }

          throw new Error("Unrecognized configuration option: " + option);
        }

        function getOption(option) {
          if (typeof option !== "string") {
            throw new Error("Invalid value for option name: " + option);
          }

          // deprecated option
          if (jsc.deprecatedOpts.hasOwnProperty(option)) {
            var oldOpt = option;
            var newOpt = jsc.deprecatedOpts[option];
            if (newOpt) {
              // if we have a new name for this option, let's log a warning and use the new name
              console.warn(
                "Option '%s' is DEPRECATED, using '%s' instead." + jsc.docsRef,
                oldOpt,
                newOpt
              );
              option = newOpt;
            } else {
              // new name not available for the option
              throw new Error("Option '" + option + "' is DEPRECATED");
            }
          }

          var getter = "get__" + option;

          if (typeof THIS[getter] === "function") {
            // a getter exists for this option
            return THIS[getter](value);
          } else if (option in THIS) {
            // option exists as a property
            return THIS[option];
          }

          throw new Error("Unrecognized configuration option: " + option);
        }

        function detachPicker() {
          jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName);
          jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
          delete jsc.picker.owner;
        }

        function drawPicker() {
          // At this point, when drawing the picker, we know what the parent elements are
          // and we can do all related DOM operations, such as registering events on them
          // or checking their positioning
          THIS._processParentElementsInDOM();

          if (!jsc.picker) {
            jsc.picker = {
              owner: null, // owner picker instance
              wrap: jsc.createEl("div"),
              box: jsc.createEl("div"),
              boxS: jsc.createEl("div"), // shadow area
              boxB: jsc.createEl("div"), // border
              pad: jsc.createEl("div"),
              padB: jsc.createEl("div"), // border
              padM: jsc.createEl("div"), // mouse/touch area
              padCanvas: jsc.createPadCanvas(),
              cross: jsc.createEl("div"),
              crossBY: jsc.createEl("div"), // border Y
              crossBX: jsc.createEl("div"), // border X
              crossLY: jsc.createEl("div"), // line Y
              crossLX: jsc.createEl("div"), // line X
              sld: jsc.createEl("div"), // slider
              sldB: jsc.createEl("div"), // border
              sldM: jsc.createEl("div"), // mouse/touch area
              sldGrad: jsc.createSliderGradient(),
              sldPtrS: jsc.createEl("div"), // slider pointer spacer
              sldPtrIB: jsc.createEl("div"), // slider pointer inner border
              sldPtrMB: jsc.createEl("div"), // slider pointer middle border
              sldPtrOB: jsc.createEl("div"), // slider pointer outer border
              asld: jsc.createEl("div"), // alpha slider
              asldB: jsc.createEl("div"), // border
              asldM: jsc.createEl("div"), // mouse/touch area
              asldGrad: jsc.createASliderGradient(),
              asldPtrS: jsc.createEl("div"), // slider pointer spacer
              asldPtrIB: jsc.createEl("div"), // slider pointer inner border
              asldPtrMB: jsc.createEl("div"), // slider pointer middle border
              asldPtrOB: jsc.createEl("div"), // slider pointer outer border
              pal: jsc.createEl("div"), // palette
              btn: jsc.createEl("div"),
              btnT: jsc.createEl("div"), // text
            };

            jsc.picker.pad.appendChild(jsc.picker.padCanvas.elm);
            jsc.picker.padB.appendChild(jsc.picker.pad);
            jsc.picker.cross.appendChild(jsc.picker.crossBY);
            jsc.picker.cross.appendChild(jsc.picker.crossBX);
            jsc.picker.cross.appendChild(jsc.picker.crossLY);
            jsc.picker.cross.appendChild(jsc.picker.crossLX);
            jsc.picker.padB.appendChild(jsc.picker.cross);
            jsc.picker.box.appendChild(jsc.picker.padB);
            jsc.picker.box.appendChild(jsc.picker.padM);

            jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
            jsc.picker.sldB.appendChild(jsc.picker.sld);
            jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
            jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
            jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
            jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
            jsc.picker.box.appendChild(jsc.picker.sldB);
            jsc.picker.box.appendChild(jsc.picker.sldM);

            jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm);
            jsc.picker.asldB.appendChild(jsc.picker.asld);
            jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB);
            jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB);
            jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB);
            jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS);
            jsc.picker.box.appendChild(jsc.picker.asldB);
            jsc.picker.box.appendChild(jsc.picker.asldM);

            jsc.picker.box.appendChild(jsc.picker.pal);

            jsc.picker.btn.appendChild(jsc.picker.btnT);
            jsc.picker.box.appendChild(jsc.picker.btn);

            jsc.picker.boxB.appendChild(jsc.picker.box);
            jsc.picker.wrap.appendChild(jsc.picker.boxS);
            jsc.picker.wrap.appendChild(jsc.picker.boxB);

            jsc.picker.wrap.addEventListener(
              "touchstart",
              jsc.onPickerTouchStart,
              jsc.isPassiveEventSupported ? { passive: false } : false
            );
          }

          var p = jsc.picker;

          var displaySlider = !!jsc.getSliderChannel(THIS);
          var displayAlphaSlider = THIS.hasAlphaChannel();
          var pickerDims = jsc.getPickerDims(THIS);
          var crossOuterSize =
            2 * THIS.pointerBorderWidth +
            THIS.pointerThickness +
            2 * THIS.crossSize;
          var controlPadding = jsc.getControlPadding(THIS);
          var borderRadius = Math.min(
            THIS.borderRadius,
            Math.round(THIS.padding * Math.PI)
          ); // px
          var padCursor = "crosshair";

          // wrap
          p.wrap.className = "jscolor-wrap";
          p.wrap.style.width = pickerDims.borderW + "px";
          p.wrap.style.height = pickerDims.borderH + "px";
          p.wrap.style.zIndex = THIS.zIndex;

          // picker
          p.box.className = "jscolor-picker";
          p.box.style.width = pickerDims.paddedW + "px";
          p.box.style.height = pickerDims.paddedH + "px";

          // picker shadow
          p.boxS.className = "jscolor-shadow";
          jsc.setBorderRadius(p.boxS, borderRadius + "px");

          // picker border
          p.boxB.className = "jscolor-border";
          p.boxB.style.border = THIS.borderWidth + "px solid";
          p.boxB.style.borderColor = THIS.borderColor;
          p.boxB.style.background = THIS.backgroundColor;
          jsc.setBorderRadius(p.boxB, borderRadius + "px");

          // IE hack:
          // If the element is transparent, IE will trigger the event on the elements under it,
          // e.g. on Canvas or on elements with border
          p.padM.style.background = "rgba(255,0,0,.2)";
          p.sldM.style.background = "rgba(0,255,0,.2)";
          p.asldM.style.background = "rgba(0,0,255,.2)";

          p.padM.style.opacity =
            p.sldM.style.opacity =
            p.asldM.style.opacity =
              "0";

          // pad
          p.pad.style.position = "relative";
          p.pad.style.width = THIS.width + "px";
          p.pad.style.height = THIS.height + "px";

          // pad - color spectrum (HSV and HVS)
          p.padCanvas.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS));

          // pad border
          p.padB.style.position = "absolute";
          p.padB.style.left = THIS.padding + "px";
          p.padB.style.top = THIS.padding + "px";
          p.padB.style.border = THIS.controlBorderWidth + "px solid";
          p.padB.style.borderColor = THIS.controlBorderColor;

          // pad mouse area
          p.padM.style.position = "absolute";
          p.padM.style.left = 0 + "px";
          p.padM.style.top = 0 + "px";
          p.padM.style.width =
            THIS.padding +
            2 * THIS.controlBorderWidth +
            THIS.width +
            controlPadding +
            "px";
          p.padM.style.height =
            2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
          p.padM.style.cursor = padCursor;
          jsc.setData(p.padM, {
            instance: THIS,
            control: "pad",
          });

          // pad cross
          p.cross.style.position = "absolute";
          p.cross.style.left = p.cross.style.top = "0";
          p.cross.style.width = p.cross.style.height = crossOuterSize + "px";

          // pad cross border Y and X
          p.crossBY.style.position = p.crossBX.style.position = "absolute";
          p.crossBY.style.background = p.crossBX.style.background =
            THIS.pointerBorderColor;
          p.crossBY.style.width = p.crossBX.style.height =
            2 * THIS.pointerBorderWidth + THIS.pointerThickness + "px";
          p.crossBY.style.height = p.crossBX.style.width =
            crossOuterSize + "px";
          p.crossBY.style.left = p.crossBX.style.top =
            Math.floor(crossOuterSize / 2) -
            Math.floor(THIS.pointerThickness / 2) -
            THIS.pointerBorderWidth +
            "px";
          p.crossBY.style.top = p.crossBX.style.left = "0";

          // pad cross line Y and X
          p.crossLY.style.position = p.crossLX.style.position = "absolute";
          p.crossLY.style.background = p.crossLX.style.background =
            THIS.pointerColor;
          p.crossLY.style.height = p.crossLX.style.width =
            crossOuterSize - 2 * THIS.pointerBorderWidth + "px";
          p.crossLY.style.width = p.crossLX.style.height =
            THIS.pointerThickness + "px";
          p.crossLY.style.left = p.crossLX.style.top =
            Math.floor(crossOuterSize / 2) -
            Math.floor(THIS.pointerThickness / 2) +
            "px";
          p.crossLY.style.top = p.crossLX.style.left =
            THIS.pointerBorderWidth + "px";

          // slider
          p.sld.style.overflow = "hidden";
          p.sld.style.width = THIS.sliderSize + "px";
          p.sld.style.height = THIS.height + "px";

          // slider gradient
          p.sldGrad.draw(THIS.sliderSize, THIS.height, "#000", "#000");

          // slider border
          p.sldB.style.display = displaySlider ? "block" : "none";
          p.sldB.style.position = "absolute";
          p.sldB.style.left =
            THIS.padding +
            THIS.width +
            2 * THIS.controlBorderWidth +
            2 * controlPadding +
            "px";
          p.sldB.style.top = THIS.padding + "px";
          p.sldB.style.border = THIS.controlBorderWidth + "px solid";
          p.sldB.style.borderColor = THIS.controlBorderColor;

          // slider mouse area
          p.sldM.style.display = displaySlider ? "block" : "none";
          p.sldM.style.position = "absolute";
          p.sldM.style.left =
            THIS.padding +
            THIS.width +
            2 * THIS.controlBorderWidth +
            controlPadding +
            "px";
          p.sldM.style.top = 0 + "px";
          p.sldM.style.width =
            THIS.sliderSize +
            2 * controlPadding +
            2 * THIS.controlBorderWidth +
            (displayAlphaSlider
              ? 0
              : Math.max(0, THIS.padding - controlPadding)) + // remaining padding to the right edge
            "px";
          p.sldM.style.height =
            2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
          p.sldM.style.cursor = "default";
          jsc.setData(p.sldM, {
            instance: THIS,
            control: "sld",
          });

          // slider pointer inner and outer border
          p.sldPtrIB.style.border = p.sldPtrOB.style.border =
            THIS.pointerBorderWidth + "px solid " + THIS.pointerBorderColor;

          // slider pointer outer border
          p.sldPtrOB.style.position = "absolute";
          p.sldPtrOB.style.left =
            -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + "px";
          p.sldPtrOB.style.top = "0";

          // slider pointer middle border
          p.sldPtrMB.style.border =
            THIS.pointerThickness + "px solid " + THIS.pointerColor;

          // slider pointer spacer
          p.sldPtrS.style.width = THIS.sliderSize + "px";
          p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + "px";

          // alpha slider
          p.asld.style.overflow = "hidden";
          p.asld.style.width = THIS.sliderSize + "px";
          p.asld.style.height = THIS.height + "px";

          // alpha slider gradient
          p.asldGrad.draw(THIS.sliderSize, THIS.height, "#000");

          // alpha slider border
          p.asldB.style.display = displayAlphaSlider ? "block" : "none";
          p.asldB.style.position = "absolute";
          p.asldB.style.left =
            THIS.padding +
            THIS.width +
            2 * THIS.controlBorderWidth +
            controlPadding +
            (displaySlider
              ? THIS.sliderSize +
                3 * controlPadding +
                2 * THIS.controlBorderWidth
              : 0) +
            "px";
          p.asldB.style.top = THIS.padding + "px";
          p.asldB.style.border = THIS.controlBorderWidth + "px solid";
          p.asldB.style.borderColor = THIS.controlBorderColor;

          // alpha slider mouse area
          p.asldM.style.display = displayAlphaSlider ? "block" : "none";
          p.asldM.style.position = "absolute";
          p.asldM.style.left =
            THIS.padding +
            THIS.width +
            2 * THIS.controlBorderWidth +
            controlPadding +
            (displaySlider
              ? THIS.sliderSize +
                2 * controlPadding +
                2 * THIS.controlBorderWidth
              : 0) +
            "px";
          p.asldM.style.top = 0 + "px";
          p.asldM.style.width =
            THIS.sliderSize +
            2 * controlPadding +
            2 * THIS.controlBorderWidth +
            Math.max(0, THIS.padding - controlPadding) + // remaining padding to the right edge
            "px";
          p.asldM.style.height =
            2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";
          p.asldM.style.cursor = "default";
          jsc.setData(p.asldM, {
            instance: THIS,
            control: "asld",
          });

          // alpha slider pointer inner and outer border
          p.asldPtrIB.style.border = p.asldPtrOB.style.border =
            THIS.pointerBorderWidth + "px solid " + THIS.pointerBorderColor;

          // alpha slider pointer outer border
          p.asldPtrOB.style.position = "absolute";
          p.asldPtrOB.style.left =
            -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + "px";
          p.asldPtrOB.style.top = "0";

          // alpha slider pointer middle border
          p.asldPtrMB.style.border =
            THIS.pointerThickness + "px solid " + THIS.pointerColor;

          // alpha slider pointer spacer
          p.asldPtrS.style.width = THIS.sliderSize + "px";
          p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + "px";

          // palette
          p.pal.className = "jscolor-palette";
          p.pal.style.display = pickerDims.palette.rows ? "block" : "none";
          p.pal.style.left = THIS.padding + "px";
          p.pal.style.top =
            2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height + "px";

          // palette's color samples

          p.pal.innerHTML = "";

          var chessboard = jsc.genColorPreviewCanvas("rgba(0,0,0,0)");

          var si = 0; // color sample's index
          for (var r = 0; r < pickerDims.palette.rows; r++) {
            for (
              var c = 0;
              c < pickerDims.palette.cols && si < THIS._palette.length;
              c++, si++
            ) {
              var sampleColor = THIS._palette[si];
              var sampleCssColor = jsc.rgbaColor.apply(null, sampleColor.rgba);

              var sc = jsc.createEl("div"); // color sample's color
              sc.style.width =
                pickerDims.palette.cellW - 2 * THIS.controlBorderWidth + "px";
              sc.style.height =
                pickerDims.palette.cellH - 2 * THIS.controlBorderWidth + "px";
              sc.style.backgroundColor = sampleCssColor;

              var sw = jsc.createEl("div"); // color sample's wrap
              sw.className = "jscolor-palette-sw";
              sw.style.left =
                (pickerDims.palette.cols <= 1
                  ? 0
                  : Math.round(
                      10 *
                        (c *
                          ((pickerDims.contentW - pickerDims.palette.cellW) /
                            (pickerDims.palette.cols - 1)))
                    ) / 10) + "px";
              sw.style.top =
                r * (pickerDims.palette.cellH + THIS.paletteSpacing) + "px";
              sw.style.border = THIS.controlBorderWidth + "px solid";
              sw.style.borderColor = THIS.controlBorderColor;
              if (sampleColor.rgba[3] !== null && sampleColor.rgba[3] < 1.0) {
                // only create chessboard background if the sample has transparency
                sw.style.backgroundImage =
                  "url('" + chessboard.canvas.toDataURL() + "')";
                sw.style.backgroundRepeat = "repeat";
                sw.style.backgroundPosition = "center center";
              }
              jsc.setData(sw, {
                instance: THIS,
                control: "palette-sw",
                color: sampleColor,
              });
              sw.addEventListener("click", jsc.onPaletteSampleClick, false);
              sw.appendChild(sc);
              p.pal.appendChild(sw);
            }
          }

          // the Close button
          function setBtnBorder() {
            var insetColors = THIS.controlBorderColor.split(/\s+/);
            var outsetColor =
              insetColors.length < 2
                ? insetColors[0]
                : insetColors[1] +
                  " " +
                  insetColors[0] +
                  " " +
                  insetColors[0] +
                  " " +
                  insetColors[1];
            p.btn.style.borderColor = outsetColor;
          }
          var btnPadding = 15; // px
          p.btn.className = "jscolor-btn jscolor-btn-close";
          p.btn.style.display = THIS.closeButton ? "block" : "none";
          p.btn.style.left = THIS.padding + "px";
          p.btn.style.bottom = THIS.padding + "px";
          p.btn.style.padding = "0 " + btnPadding + "px";
          p.btn.style.maxWidth =
            pickerDims.contentW -
            2 * THIS.controlBorderWidth -
            2 * btnPadding +
            "px";
          p.btn.style.height = THIS.buttonHeight + "px";
          p.btn.style.border = THIS.controlBorderWidth + "px solid";
          setBtnBorder();
          p.btn.style.color = THIS.buttonColor;
          p.btn.onmousedown = function () {
            THIS.hide();
          };
          p.btnT.style.display = "inline";
          p.btnT.style.lineHeight = THIS.buttonHeight + "px";
          p.btnT.innerText = THIS.closeText;

          // reposition the pointers
          redrawPad();
          redrawSld();
          redrawASld();

          // If we are changing the owner without first closing the picker,
          // make sure to first deal with the old owner
          if (jsc.picker.owner && jsc.picker.owner !== THIS) {
            jsc.removeClass(
              jsc.picker.owner.targetElement,
              jsc.pub.activeClassName
            );
          }

          // Set a new picker owner
          jsc.picker.owner = THIS;

          // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
          // after setting the owner
          jsc.redrawPosition();

          if (p.wrap.parentNode !== THIS.container) {
            THIS.container.appendChild(p.wrap);
          }

          jsc.addClass(THIS.targetElement, jsc.pub.activeClassName);
        }

        function redrawPad() {
          // redraw the pad pointer
          var yChannel = jsc.getPadYChannel(THIS);
          var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1));
          var y = Math.round(
            (1 - THIS.channels[yChannel] / 100) * (THIS.height - 1)
          );
          var crossOuterSize =
            2 * THIS.pointerBorderWidth +
            THIS.pointerThickness +
            2 * THIS.crossSize;
          var ofs = -Math.floor(crossOuterSize / 2);
          jsc.picker.cross.style.left = x + ofs + "px";
          jsc.picker.cross.style.top = y + ofs + "px";

          // redraw the slider
          switch (jsc.getSliderChannel(THIS)) {
            case "s":
              var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v);
              var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v);
              var color1 =
                "rgb(" +
                Math.round(rgb1[0]) +
                "," +
                Math.round(rgb1[1]) +
                "," +
                Math.round(rgb1[2]) +
                ")";
              var color2 =
                "rgb(" +
                Math.round(rgb2[0]) +
                "," +
                Math.round(rgb2[1]) +
                "," +
                Math.round(rgb2[2]) +
                ")";
              jsc.picker.sldGrad.draw(
                THIS.sliderSize,
                THIS.height,
                color1,
                color2
              );
              break;
            case "v":
              var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100);
              var color1 =
                "rgb(" +
                Math.round(rgb[0]) +
                "," +
                Math.round(rgb[1]) +
                "," +
                Math.round(rgb[2]) +
                ")";
              var color2 = "#000";
              jsc.picker.sldGrad.draw(
                THIS.sliderSize,
                THIS.height,
                color1,
                color2
              );
              break;
          }

          // redraw the alpha slider
          jsc.picker.asldGrad.draw(
            THIS.sliderSize,
            THIS.height,
            THIS.toHEXString()
          );
        }

        function redrawSld() {
          var sldChannel = jsc.getSliderChannel(THIS);
          if (sldChannel) {
            // redraw the slider pointer
            var y = Math.round(
              (1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1)
            );
            jsc.picker.sldPtrOB.style.top =
              y -
              (2 * THIS.pointerBorderWidth + THIS.pointerThickness) -
              Math.floor(jsc.pub.sliderInnerSpace / 2) +
              "px";
          }

          // redraw the alpha slider
          jsc.picker.asldGrad.draw(
            THIS.sliderSize,
            THIS.height,
            THIS.toHEXString()
          );
        }

        function redrawASld() {
          var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1));
          jsc.picker.asldPtrOB.style.top =
            y -
            (2 * THIS.pointerBorderWidth + THIS.pointerThickness) -
            Math.floor(jsc.pub.sliderInnerSpace / 2) +
            "px";
        }

        function isPickerOwner() {
          return jsc.picker && jsc.picker.owner === THIS;
        }

        function onValueKeyDown(ev) {
          if (jsc.eventKey(ev) === "Enter") {
            if (THIS.valueElement) {
              THIS.processValueInput(THIS.valueElement.value);
            }
            THIS.tryHide();
          }
        }

        function onAlphaKeyDown(ev) {
          if (jsc.eventKey(ev) === "Enter") {
            if (THIS.alphaElement) {
              THIS.processAlphaInput(THIS.alphaElement.value);
            }
            THIS.tryHide();
          }
        }

        function onValueChange(ev) {
          if (jsc.getData(ev, "internal")) {
            return; // skip if the event was internally triggered by jscolor
          }

          var oldVal = THIS.valueElement.value;

          THIS.processValueInput(THIS.valueElement.value); // this might change the value

          jsc.triggerCallback(THIS, "onChange");

          if (THIS.valueElement.value !== oldVal) {
            // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
            jsc.triggerInputEvent(THIS.valueElement, "change", true, true);
          }
        }

        function onAlphaChange(ev) {
          if (jsc.getData(ev, "internal")) {
            return; // skip if the event was internally triggered by jscolor
          }

          var oldVal = THIS.alphaElement.value;

          THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value

          jsc.triggerCallback(THIS, "onChange");

          // triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format)
          jsc.triggerInputEvent(THIS.valueElement, "change", true, true);

          if (THIS.alphaElement.value !== oldVal) {
            // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
            jsc.triggerInputEvent(THIS.alphaElement, "change", true, true);
          }
        }

        function onValueInput(ev) {
          if (jsc.getData(ev, "internal")) {
            return; // skip if the event was internally triggered by jscolor
          }

          if (THIS.valueElement) {
            THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue);
          }

          jsc.triggerCallback(THIS, "onInput");

          // triggering valueElement's onInput
          // (not needed, it was dispatched normally by the browser)
        }

        function onAlphaInput(ev) {
          if (jsc.getData(ev, "internal")) {
            return; // skip if the event was internally triggered by jscolor
          }

          if (THIS.alphaElement) {
            THIS.fromHSVA(
              null,
              null,
              null,
              parseFloat(THIS.alphaElement.value),
              jsc.flags.leaveAlpha
            );
          }

          jsc.triggerCallback(THIS, "onInput");

          // triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format)
          jsc.triggerInputEvent(THIS.valueElement, "input", true, true);
        }

        // let's process the DEPRECATED 'options' property (this will be later removed)
        if (jsc.pub.options) {
          // let's set custom default options, if specified
          for (var opt in jsc.pub.options) {
            if (jsc.pub.options.hasOwnProperty(opt)) {
              try {
                setOption(opt, jsc.pub.options[opt]);
              } catch (e) {
                console.warn(e);
              }
            }
          }
        }

        // let's apply configuration presets
        //
        var presetsArr = [];

        if (opts.preset) {
          if (typeof opts.preset === "string") {
            presetsArr = opts.preset.split(/\s+/);
          } else if (Array.isArray(opts.preset)) {
            presetsArr = opts.preset.slice(); // slice() to clone
          } else {
            console.warn("Unrecognized preset value");
          }
        }

        // always use the 'default' preset. If it's not listed, append it to the end.
        if (presetsArr.indexOf("default") === -1) {
          presetsArr.push("default");
        }

        // let's apply the presets in reverse order, so that should there be any overlapping options,
        // the formerly listed preset will override the latter
        for (var i = presetsArr.length - 1; i >= 0; i -= 1) {
          var pres = presetsArr[i];
          if (!pres) {
            continue; // preset is empty string
          }
          if (!jsc.pub.presets.hasOwnProperty(pres)) {
            console.warn("Unknown preset: %s", pres);
            continue;
          }
          for (var opt in jsc.pub.presets[pres]) {
            if (jsc.pub.presets[pres].hasOwnProperty(opt)) {
              try {
                setOption(opt, jsc.pub.presets[pres][opt]);
              } catch (e) {
                console.warn(e);
              }
            }
          }
        }

        // let's set specific options for this color picker
        var nonProperties = [
          // these options won't be set as instance properties
          "preset",
        ];
        for (var opt in opts) {
          if (opts.hasOwnProperty(opt)) {
            if (nonProperties.indexOf(opt) === -1) {
              try {
                setOption(opt, opts[opt]);
              } catch (e) {
                console.warn(e);
              }
            }
          }
        }

        //
        // Install the color picker on chosen element(s)
        //

        // Determine picker's container element
        if (this.container === undefined) {
          this.container = window.document.body; // default container is BODY element
        } else {
          // explicitly set to custom element
          this.container = jsc.node(this.container);
        }

        if (!this.container) {
          throw new Error(
            "Cannot instantiate color picker without a container element"
          );
        }

        // Fetch the target element
        this.targetElement = jsc.node(targetElement);

        if (!this.targetElement) {
          // temporarily customized error message to help with migrating from versions prior to 2.2
          if (
            typeof targetElement === "string" &&
            /^[a-zA-Z][\w:.-]*$/.test(targetElement)
          ) {
            // targetElement looks like valid ID
            var possiblyId = targetElement;
            throw new Error(
              "If '" +
                possiblyId +
                "' is supposed to be an ID, please use '#" +
                possiblyId +
                "' or any valid CSS selector."
            );
          }

          throw new Error(
            "Cannot instantiate color picker without a target element"
          );
        }

        if (
          this.targetElement.jscolor &&
          this.targetElement.jscolor instanceof jsc.pub
        ) {
          throw new Error("Color picker already installed on this element");
        }

        // link this instance with the target element
        this.targetElement.jscolor = this;
        jsc.addClass(this.targetElement, jsc.pub.className);

        // register this instance
        jsc.instances.push(this);

        // if target is BUTTON
        if (jsc.isButton(this.targetElement)) {
          if (this.targetElement.type.toLowerCase() !== "button") {
            // on buttons, always force type to be 'button', e.g. in situations the target <button> has no type
            // and thus defaults to 'submit' and would submit the form when clicked
            this.targetElement.type = "button";
          }

          if (jsc.isButtonEmpty(this.targetElement)) {
            // empty button
            // it is important to clear element's contents first.
            // if we're re-instantiating color pickers on DOM that has been modified by changing page's innerHTML,
            // we would keep adding more non-breaking spaces to element's content (because element's contents survive
            // innerHTML changes, but picker instances don't)
            jsc.removeChildren(this.targetElement);

            // let's insert a non-breaking space
            this.targetElement.appendChild(
              window.document.createTextNode("\xa0")
            );

            // set min-width = previewSize, if not already greater
            var compStyle = jsc.getCompStyle(this.targetElement);
            var currMinWidth = parseFloat(compStyle["min-width"]) || 0;
            if (currMinWidth < this.previewSize) {
              jsc.setStyle(
                this.targetElement,
                {
                  "min-width": this.previewSize + "px",
                },
                this.forceStyle
              );
            }
          }
        }

        // Determine the value element
        if (this.valueElement === undefined) {
          if (jsc.isTextInput(this.targetElement)) {
            // for text inputs, default valueElement is targetElement
            this.valueElement = this.targetElement;
          } else {
            // leave it undefined
          }
        } else if (this.valueElement === null) {
          // explicitly set to null
          // leave it null
        } else {
          // explicitly set to custom element
          this.valueElement = jsc.node(this.valueElement);
        }

        // Determine the alpha element
        if (this.alphaElement) {
          this.alphaElement = jsc.node(this.alphaElement);
        }

        // Determine the preview element
        if (this.previewElement === undefined) {
          this.previewElement = this.targetElement; // default previewElement is targetElement
        } else if (this.previewElement === null) {
          // explicitly set to null
          // leave it null
        } else {
          // explicitly set to custom element
          this.previewElement = jsc.node(this.previewElement);
        }

        // valueElement
        if (this.valueElement && jsc.isTextInput(this.valueElement)) {
          // If the value element has onInput event already set, we need to detach it and attach AFTER our listener.
          // otherwise the picker instance would still contain the old color when accessed from the onInput handler.
          var valueElementOrigEvents = {
            onInput: this.valueElement.oninput,
          };
          this.valueElement.oninput = null;

          this.valueElement.addEventListener("keydown", onValueKeyDown, false);
          this.valueElement.addEventListener("change", onValueChange, false);
          this.valueElement.addEventListener("input", onValueInput, false);
          // the original event listener must be attached AFTER our handler (to let it first set picker's color)
          if (valueElementOrigEvents.onInput) {
            this.valueElement.addEventListener(
              "input",
              valueElementOrigEvents.onInput,
              false
            );
          }

          this.valueElement.setAttribute("autocomplete", "off");
          this.valueElement.setAttribute("autocorrect", "off");
          this.valueElement.setAttribute("autocapitalize", "off");
          this.valueElement.setAttribute("spellcheck", false);
        }

        // alphaElement
        if (this.alphaElement && jsc.isTextInput(this.alphaElement)) {
          this.alphaElement.addEventListener("keydown", onAlphaKeyDown, false);
          this.alphaElement.addEventListener("change", onAlphaChange, false);
          this.alphaElement.addEventListener("input", onAlphaInput, false);

          this.alphaElement.setAttribute("autocomplete", "off");
          this.alphaElement.setAttribute("autocorrect", "off");
          this.alphaElement.setAttribute("autocapitalize", "off");
          this.alphaElement.setAttribute("spellcheck", false);
        }

        // determine initial color value
        //
        var initValue = "FFFFFF";

        if (this.value !== undefined) {
          initValue = this.value; // get initial color from the 'value' property
        } else if (this.valueElement && this.valueElement.value !== undefined) {
          initValue = this.valueElement.value; // get initial color from valueElement's value
        }

        // determine initial alpha value
        //
        var initAlpha = undefined;

        if (this.alpha !== undefined) {
          initAlpha = "" + this.alpha; // get initial alpha value from the 'alpha' property
        } else if (this.alphaElement && this.alphaElement.value !== undefined) {
          initAlpha = this.alphaElement.value; // get initial color from alphaElement's value
        }

        // determine current format based on the initial color value
        //
        this._currentFormat = null;

        if (["auto", "any"].indexOf(this.format.toLowerCase()) > -1) {
          // format is 'auto' or 'any' -> let's auto-detect current format
          var color = jsc.parseColorString(initValue);
          this._currentFormat = color ? color.format : "hex";
        } else {
          // format is specified
          this._currentFormat = this.format.toLowerCase();
        }

        // let's parse the initial color value and expose color's preview
        this.processValueInput(initValue);

        // let's also parse and expose the initial alpha value, if any
        //
        // Note: If the initial color value contains alpha value in it (e.g. in rgba format),
        // this will overwrite it. So we should only process alpha input if there was initial
        // alpha explicitly set, otherwise we could needlessly lose initial value's alpha
        if (initAlpha !== undefined) {
          this.processAlphaInput(initAlpha);
        }

        if (this.random) {
          // randomize the initial color value
          this.randomize.apply(
            this,
            Array.isArray(this.random) ? this.random : []
          );
        }
      },
    };

    //================================
    // Public properties and methods
    //================================

    //
    // These will be publicly available via jscolor.<name> and JSColor.<name>
    //

    // class that will be set to elements having jscolor installed on them
    jsc.pub.className = "jscolor";

    // class that will be set to elements having jscolor active on them
    jsc.pub.activeClassName = "jscolor-active";

    // whether to try to parse the options string by evaluating it using 'new Function()'
    // in case it could not be parsed with JSON.parse()
    jsc.pub.looseJSON = true;

    // presets
    jsc.pub.presets = {};

    // built-in presets
    jsc.pub.presets["default"] = {}; // baseline for customization

    jsc.pub.presets["light"] = {
      // default color scheme
      backgroundColor: "rgba(255,255,255,1)",
      controlBorderColor: "rgba(187,187,187,1)",
      buttonColor: "rgba(0,0,0,1)",
    };
    jsc.pub.presets["dark"] = {
      backgroundColor: "rgba(51,51,51,1)",
      controlBorderColor: "rgba(153,153,153,1)",
      buttonColor: "rgba(240,240,240,1)",
    };

    jsc.pub.presets["small"] = {
      width: 101,
      height: 101,
      padding: 10,
      sliderSize: 14,
      paletteCols: 8,
    };
    jsc.pub.presets["medium"] = {
      width: 181,
      height: 101,
      padding: 12,
      sliderSize: 16,
      paletteCols: 10,
    }; // default size
    jsc.pub.presets["large"] = {
      width: 271,
      height: 151,
      padding: 12,
      sliderSize: 24,
      paletteCols: 15,
    };

    jsc.pub.presets["thin"] = {
      borderWidth: 1,
      controlBorderWidth: 1,
      pointerBorderWidth: 1,
    }; // default thickness
    jsc.pub.presets["thick"] = {
      borderWidth: 2,
      controlBorderWidth: 2,
      pointerBorderWidth: 2,
    };

    // size of space in the sliders
    jsc.pub.sliderInnerSpace = 3; // px

    // transparency chessboard
    jsc.pub.chessboardSize = 8; // px
    jsc.pub.chessboardColor1 = "#666666";
    jsc.pub.chessboardColor2 = "#999999";

    // preview separator
    jsc.pub.previewSeparator = [
      "rgba(255,255,255,.65)",
      "rgba(128,128,128,.65)",
    ];

    // Initializes jscolor
    jsc.pub.init = function () {
      if (jsc.initialized) {
        return;
      }

      // attach some necessary handlers
      window.document.addEventListener(
        "mousedown",
        jsc.onDocumentMouseDown,
        false
      );
      window.document.addEventListener("keyup", jsc.onDocumentKeyUp, false);
      window.addEventListener("resize", jsc.onWindowResize, false);
      window.addEventListener("scroll", jsc.onWindowScroll, false);

      // append default CSS to HEAD
      jsc.appendDefaultCss();

      // install jscolor on current DOM
      jsc.pub.install();

      jsc.initialized = true;

      // call functions waiting in the queue
      while (jsc.readyQueue.length) {
        var func = jsc.readyQueue.shift();
        func();
      }
    };

    // Installs jscolor on current DOM tree
    jsc.pub.install = function (rootNode) {
      var success = true;

      try {
        jsc.installBySelector("[data-jscolor]", rootNode);
      } catch (e) {
        success = false;
        console.warn(e);
      }

      // for backward compatibility with DEPRECATED installation using class name
      if (jsc.pub.lookupClass) {
        try {
          jsc.installBySelector(
            "input." +
              jsc.pub.lookupClass +
              ", " +
              "button." +
              jsc.pub.lookupClass,
            rootNode
          );
        } catch (e) {}
      }

      return success;
    };

    // Registers function to be called as soon as jscolor is initialized (or immediately, if it already is).
    //
    jsc.pub.ready = function (func) {
      if (typeof func !== "function") {
        console.warn("Passed value is not a function");
        return false;
      }

      if (jsc.initialized) {
        func();
      } else {
        jsc.readyQueue.push(func);
      }
      return true;
    };

    // Triggers given input event(s) (e.g. 'input' or 'change') on all color pickers.
    //
    // It is possible to specify multiple events separated with a space.
    // If called before jscolor is initialized, then the events will be triggered after initialization.
    //
    jsc.pub.trigger = function (eventNames) {
      var triggerNow = function () {
        jsc.triggerGlobal(eventNames);
      };

      if (jsc.initialized) {
        triggerNow();
      } else {
        jsc.pub.ready(triggerNow);
      }
    };

    // Hides current color picker box
    jsc.pub.hide = function () {
      if (jsc.picker && jsc.picker.owner) {
        jsc.picker.owner.hide();
      }
    };

    // Returns a data URL of a gray chessboard image that indicates transparency
    jsc.pub.chessboard = function (color) {
      if (!color) {
        color = "rgba(0,0,0,0)";
      }
      var preview = jsc.genColorPreviewCanvas(color);
      return preview.canvas.toDataURL();
    };

    // Returns a data URL of a gray chessboard image that indicates transparency
    jsc.pub.background = function (color) {
      var backgrounds = [];

      // CSS gradient for background color preview
      backgrounds.push(jsc.genColorPreviewGradient(color));

      // data URL of generated PNG image with a gray transparency chessboard
      var preview = jsc.genColorPreviewCanvas();
      backgrounds.push(
        [
          "url('" + preview.canvas.toDataURL() + "')",
          "left top",
          "repeat",
        ].join(" ")
      );

      return backgrounds.join(", ");
    };

    //
    // DEPRECATED properties and methods
    //

    // DEPRECATED. Use jscolor.presets.default instead.
    //
    // Custom default options for all color pickers, e.g. { hash: true, width: 300 }
    jsc.pub.options = {};

    // DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
    //
    // By default, we'll search for all elements with class="jscolor" and install a color picker on them.
    //
    // You can change what class name will be looked for by setting the property jscolor.lookupClass
    // anywhere in your HTML document. To completely disable the automatic lookup, set it to null.
    //
    jsc.pub.lookupClass = "jscolor";

    // DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
    //
    // Install jscolor on all elements that have the specified class name
    jsc.pub.installByClassName = function () {
      console.error(
        'jscolor.installByClassName() is DEPRECATED. Use data-jscolor="" attribute instead of a class name.' +
          jsc.docsRef
      );
      return false;
    };

    jsc.register();

    return jsc.pub;
  })(); // END jscolor

  if (typeof window.jscolor === "undefined") {
    window.jscolor = window.JSColor = jscolor;
  }

  // END jscolor code

  return jscolor;
}); // END factory