text: // Color library for Associates Central
  
  // RGB object
  function RGB(red, green, blue) {
      // These are integers between 0 and 255 inclusive.
      this.r = red;
      this.g = green;
      this.b = blue;
  
      // Accessor methods for the RGB components.
      this.getR = function() { return this.r; };
      this.getG = function() { return this.g; };
      this.getB = function() { return this.b; };
  
      // Defines and returns the 6-digit hex representation
      // of this RGB color.
      this.getHex = function() {
          if (!this.hex) {
              var map = '0123456789ABCDEF';
              this.hex = '' +
                         map.substr(Math.floor(this.r / 16), 1) +
                         map.substr((this.r % 16), 1) +
                         map.substr(Math.floor(this.g / 16), 1) +
                         map.substr((this.g % 16), 1) +
                         map.substr(Math.floor(this.b / 16), 1) +
                         map.substr((this.b % 16), 1);
          }
          return this.hex;
      };
  
      // Returns the equivalent HSV object.
      this.toHSV = function() {
          // Normalize RGB values for color space transform.
          var r = this.r / 255;
          var g = this.g / 255;
          var b = this.b / 255;
  
          // Helper values for color space transform.
          var max = Math.max(Math.max(r,g), b);
          var min = Math.min(Math.max(r,g), b);
          var delta = max - min;
  
          // Color space transform.
          var h = 0;
          var s = 0;
          var v = max;
  
          if (max > 0) {
              s = delta / max;
          }
  
          if (delta > 0) {
              if ((max == r) && (max != g)) {
                  h += (g - b) / delta;
              }
              if ((max == g) && (max != b)) {
                  h += (2 + (b - r) / delta);
              }
              if ((max == b) && (max != r)) {
                  h += (4 + (r - g) / delta);
              }
              h = h * 60;
              if (h < 0) {
                  h += 360;
              }
          }
  
          return new HSV(h, s, v);
      };
  
      // for debugging
      this.dump = function() {
          alert('Red: ' + this.r + "\n" +
                'Green: ' + this.g + "\n" +
                'Blue: ' + this.b + "\n" +
                'Hex: ' + this.getHex()
                  );
      };
  }
  
  // HSV object
  function HSV(hue, saturation, value) {
      // These are real numbers between 0 and 1 inclusive.
      this.h = hue;
      this.s = saturation;
      this.v = value;
  
      // Accessor methods for the HSV components.
      this.getH = function() {
          return this.h;
      };
      this.getS = function() {
          return this.s;
      };
      this.getV = function() {
          return this.v;
      };
  
      // Returns the equivalent RGB object.
      this.toRGB = function() {
          var sat_r;
          var sat_g;
          var sat_b;
  
          // Color space transform
          if (this.h < 120) {
              sat_r = (120 - this.h) / 60;
              sat_g = this.h / 60;
              sat_b = 0;
          } else if (this.h < 240) {
              sat_r = 0;
              sat_g = (240 - this.h) / 60;
              sat_b = (this.h - 120) / 60;
          } else {
              sat_r = (this.h - 240) / 60;
              sat_g = 0;
              sat_b = (360 - this.h) / 60;
          }
  
          if (sat_r > 1) {
              sat_r = 1;
          }
          if (sat_g > 1) {
              sat_g = 1;
          }
          if (sat_b > 1) {
              sat_b = 1;
          }
  
          return new RGB(
                  Math.round((1 - this.s + this.s * sat_r) * this.v * 255),
                  Math.round((1 - this.s + this.s * sat_g) * this.v * 255),
                  Math.round((1 - this.s + this.s * sat_b) * this.v * 255)
                  );
      };
  
      // for debugging
      this.dump = function() {
          alert('Hue: ' + this.h + "\n" +
                'Saturation: ' + this.s + "\n" +
                'Value: ' + this.v + "\n"
                  );
      };
  }
  
  /* Container object that holds an RGB object and an HSV object.
     This is the catch-all type that gives you everything
     you will need up front.  With this object, you can create
     a color from RGB values, HSV values, or a hex string.
  
     Upon object creation, you are not guaranteed that any of the
     private properties exist yet (they are created as needed).
     Therefore, you must use accessor methods to get each property
     of this object.
  */
  function Color(type, a, b, c) {
      if (type == 'rgb') {
          this.rgb = new RGB(a, b, c);
          this.r = a;
          this.g = b;
          this.b = c;
      } else if (type == 'hsv') {
          this.hsv = new HSV(a, b, c);
          this.h = a;
          this.s = b;
          this.v = c;
      }else if (type.match(/[0-9a-f]{6}/i)) {
          // 'type' argument was a hex color value
          var map = '0123456789ABCDEF';
          type = type.toUpperCase();
          this.r = map.indexOf(type.substr(0, 1)) * 16 + map.indexOf(type.substr(1, 1));
          this.g = map.indexOf(type.substr(2, 1)) * 16 + map.indexOf(type.substr(3, 1));
          this.b = map.indexOf(type.substr(4, 1)) * 16 + map.indexOf(type.substr(5, 1));
          this.rgb = new RGB(this.r, this.g, this.b);
          this.hex = type;
      } else {
          // nothing to work with
          return null;
      }
  
      // Accessor methods for properties.
      // Requested properties are calculated when first requested if necessary,
      // then stored for later.
      this.getR = function() {
          if (!this.r) {
              if (!this.rgb) {
                  this.rgb = this.hsv.toRGB();
              }
              this.r = this.rgb.getR();
          }
          return this.r;
      };
  
      this.getG = function() {
          if (!this.g) {
              if (!this.rgb) {
                  this.rgb = this.hsv.toRGB();
              }
              this.g = this.rgb.getG();
          }
          return this.g;
      };
  
      this.getB = function() {
          if (!this.b) {
              if (!this.rgb) {
                  this.rgb = this.hsv.toRGB();
              }
              this.b = this.rgb.getB();
          }
          return this.b;
      };
  
      this.getH = function() {
          if (!this.h) {
              if (!this.hsv) {
                  this.hsv = this.rgb.toHSV();
              }
              this.h = this.hsv.getH();
          }
          return this.h;
      };
  
      this.getS = function() {
          if (!this.s) {
              if (!this.hsv) {
                  this.hsv = this.rgb.toHSV();
              }
              this.s = this.hsv.getS();
          }
          return this.s;
      };
  
      this.getV = function() {
          if (!this.v) {
              if (!this.hsv) {
                  this.hsv = this.rgb.toHSV();
              }
              this.v = this.hsv.getV();
          }
          return this.v;
      };
  
      this.getHex = function() {
          if (!this.hex) {
              if (!this.rgb) {
                  this.rgb = this.hsv.toRGB();
              }
              this.hex = this.rgb.getHex();
          }
          return this.hex;
      };
  
      // Return a contrasting color of the same hue.
      this.getContrasting = function() {
          var newH = this.getH();
          var newS = this.getS();
          var newV = this.getV();
  
          if (newS == 0) {
              // Grayscale; don't change saturation
              newV = (newV + 0.5) % 1;
          } else {
              newS = (newS + 0.5) % 1;
              newV = (newV + 0.5) % 1;
  
              // Adjust color for better contrast if it's in the deep blue range.
              if ((newH > 200) && (newH < 275)) {
                  if ((newS >= 0.5) && (newV >= 0.5)) {
                      // High saturation and value, decrease saturation
                      newS = newS / 2;
                  } else if ((newS <= 0.5) && (newV <= 0.5)) {
                      // Low saturation and value, increase value
                      newV = newV + 0.5;
                  }
              }
          }
  
          return new Color('hsv', newH, newS, newV);
      };
  
      this.getBlendingColor = function() {
          var newH = this.getH();
          var newV = this.getV();
          var newS = this.getS();
  
          var tweak = (newS > 0.8) ? 0.1 : 0;
          if( newV > 0.6 ) {
              newV = newV - 0.2 - tweak;
          } else if(newV < 0.4) {
              newV = newV + 0.2 + tweak;
          } else {
              newV = newV - 0.3;
          }
  
          if( newH > 200 && newH < 275 ) {
              if( newS > 0.8 ) {
                  newS = newS - 0.4;
              }
          }
          
          return new Color('hsv', newH, newS, newV);
      };
  
      this.isDark = function() {
          var rval = false;
          var v = this.getV();
  
          if (v < 0.6) {
              // Low value...it's dark.
              rval = true;
          } else {
              var h = this.getH();
              var s = this.getS();
  
              // A strongly-saturated blue or purple is considered "dark".
              if ((h > 200) && (h < 275)) {
                  if (s > 0.5) {
                      rval = true;
                  }
              }
          }
  
          return rval;
      };
  
      // for debugging
      this.dump = function() {
          alert('Red: ' + this.getR() + "\n" +
                'Green: ' + this.getG() + "\n" +
                'Blue: ' + this.getB() + "\n" +
                'Hex: ' + this.getHex() + "\n" +
                'Hue: ' + this.getH() + "\n" +
                'Saturation: ' + this.getS() + "\n" +
                'Value: ' + this.getV()
                  );
      };
  }
  
  