<main v-cloak>
    <div class="tools">
      <div>
        <input type="color" v-model="color1" />
        <input type="text" v-model="color1" />
      </div>
      <div>
        <div class="vertical-range"><input type="range" :min="min" :max="max" v-model="steps" /></div>
        <div class="steps">{{visualSteps}} {{visualStepsLabel}}</div>
      </div>
      <div>
        <input type="color" v-model="color2" />
        <input type="text" v-model="color2" />
      </div>
    </div>
    <div class="colors">
      <div class="color" v-for="color in colors" :style="setStyles(color)">&nbsp;{{colorName(color)}}</div>
    </div>
  </main>

  <script src="https://unpkg.com/vue@2"></script>
@import url("https://fonts.googleapis.com/css?family=Work+Sans:300");
:root {
  --fg: #fff;
  --bg: #343338;
}

[v-cloak] {
  display: none;
}

.tools {
  flex: none;
  padding: 2rem;
  color: #fff;
}
.tools div {
  display: flex;
  position: relative;
}
.tools div + div {
  margin-top: 0.5em;
}
.tools .steps {
  position: absolute;
  top: 50%;
  left: 15vh;
  transform: translate(25%, -50%);
  padding: 0;
  margin: 0;
}

.colors {
  flex: 1;
  display: flex;
  flex-direction: column;
  font-size: 0.85em;
  height: 100vh;
  overflow: auto;
}
.colors .color {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 2rem;
  transition: 300ms linear;
}

.vertical-range {
  display: flex;
  align-items: center;
  width: 30vh;
  height: 30vh;
  transform: rotate(-90deg);
}

input {
  margin: 0;
  padding: 0 0.5em;
  font-size: inherit;
  font-family: inherit;
  border: none;
  width: 7em;
  outline: none;
  border-radius: 0 1em 1em 0;
  background: var(--fg);
  color: var(--bg);
  flex: 1;
}
input[type=color] {
  padding: 0;
  width: 2em;
  height: 2em;
  border-radius: 1em 0 0 1em;
}
input[type=color]::-moz-color-swatch {
  border: none;
  border-radius: 1em;
  transform: scale(0.75);
}
input[type=color]::-webkit-color-swatch {
  border: none;
  border-radius: 1em;
}
input[type=color]::-webkit-color-swatch-wrapper {
  padding: 0.25em;
  border-radius: 1em;
}
input[type=range] {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  outline: none;
  display: inline-block;
  padding: 0;
  margin: 0;
  border: 0;
  width: 100%;
  height: 0.25em;
  border-radius: 1em;
  cursor: pointer;
  font-size: inherit;
  background: var(--fg);
}
input[type=range]::-moz-range-track {
  -moz-appearance: none;
       appearance: none;
  background: var(--fg);
  outline: none;
}
input[type=range]::-moz-focus-outer {
  border: 0;
}
input[type=range]::-moz-range-thumb {
  -moz-appearance: none;
       appearance: none;
  width: 1em;
  height: 1em;
  border: none;
  border-radius: 1em;
  box-shadow: 0 0 0 0.3em var(--bg);
  background: var(--fg);
  transform: scale(1);
  -moz-transition: transform 0.3s ease-out;
  transition: transform 0.3s ease-out;
}
input[type=range]::-moz-range-thumb:focus, input[type=range]::-moz-range-thumb:active {
  -moz-appearance: none;
       appearance: none;
  transform: scale(0.85);
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
          appearance: none;
  width: 1em;
  height: 1em;
  border: none;
  border-radius: 1em;
  box-shadow: 0 0 0 0.3em var(--bg);
  background: var(--fg);
  transform: scale(1);
  -webkit-transition: transform 0.3s ease-out;
  transition: transform 0.3s ease-out;
}
input[type=range]::-webkit-slider-thumb:focus, input[type=range]::-webkit-slider-thumb:active {
  -webkit-appearance: none;
          appearance: none;
  transform: scale(0.85);
}

*, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  background: var(--bg);
  font-family: "Work Sans", sans-serif;
}

main {
  flex: 1;
  display: flex;
  font-size: calc(1em + 1vmin);
}

@supports (-webkit-backdrop-filter: blur(2em)) {
  input[type=color] {
    display: none;
  }

  input {
    border-radius: 1em;
    padding: 0.25em 0.5em;
    width: 9em;
  }
}
const vueMixinColorFunctions = {
  created() {
    // console.log('color mixin called')
  },
  methods: {
    randomHex() {
      return '#' + ( '00' + Math.floor( Math.random() * 16777216 ).toString(16) ).substr(-6)
    },
    hexArray(hex) {
      return /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
    },
    rgbObjectFromHex(hex) {
      const result = this.hexArray(hex)
      return result ? {
          r: parseInt(result[1], 16),
          g: parseInt(result[2], 16),
          b: parseInt(result[3], 16),
      } : null;
    },
    rgbArrayFromHex(hex) {
      const rgb = this.rgbObjectFromHex(hex)
      return [rgb.r, rgb.g, rgb.b]
    },
    hslObjectFromHex(hex) {
      const hsl = {}
      const rgb = this.rgbObjectFromHex(hex)

      rgb.r /= 255, rgb.g /= 255, rgb.b /= 255
      let max = Math.max(rgb.r, rgb.g, rgb.b)
      let min = Math.min(rgb.r, rgb.g, rgb.b)
      
      hsl.l = (max + min) / 2

      if ( max == min ) {
        hsl.h = hsl.s = 0 // achromatic
      } else {
        let d = max - min
        
        hsl.s = hsl.l > 0.5 ? (d / (2 - max - min)) : (d / (max + min))

        switch(max) {
          case rgb.r: 
            hsl.h = (rgb.g - rgb.b) / d + (rgb.g < rgb.b ? 6 : 0)
            break
          case rgb.g: 
            hsl.h = (rgb.b - rgb.r) / d + 2
            break
          case rgb.b:
            hsl.h = (rgb.r - rgb.g) / d + 4
            break
        }
        
        hsl.h /= 6;
      }
      
      return {
        h: Math.round(hsl.h * 360),
        s: Math.round(hsl.s * 100),
        l: Math.round(hsl.l * 100),
      }
    },
    hslArrayFromHex(hex) {
      const hsl = this.hslObjectFromHex(hex)
      return [hsl.h, hsl.s, hsl.l]
    },
    hexToRgb(hex) {
      const rgb = this.rgbObjectFromHex(hex)
      return `rgb(${rgb.r},${rgb.g},${rgb.b})`
    },
    hexToRgbArray(hex) {
      const rgb = this.rgbObjectFromHex(hex)
      return `${rgb.r}, ${rgb.g}, ${rgb.b}`
    },
    hexToHsl(hex) {
      const hsl = this.hslObjectFromHex(hex)
      return `hsl(${hsl.h},${hsl.s}%,${hsl.l}%)`
    },
    rgbChannelToHex(channel) {
      return channel.toString(16).padStart(2, '0');
    },
    rgbToHex(r, g, b) {
      return "#" + [r,g,b].map(x=>Math.round(x).toString(16).padStart(2,0) ).join('');
    },
    rgbArrayToHex (color) {
      return this.rgbToHex(color[0], color[1], color[2]) 
    },
    hslToRgbObject(h, s, l) {
      let r, g, b;

      if(s == 0){
        r = g = b = l; // achromatic
      } else {
        const hue2rgb = function hue2rgb(p, q, t) {
          if(t < 0) t += 1;
          if(t > 1) t -= 1;
          if(t < 1/6) return p + (q - p) * 6 * t;
          if(t < 1/2) return q;
          if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
          
          return p;
        }

        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;

        
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
      }

      console.log(r, g, b);

      return {
        r: Math.round(r * 255),
        g: Math.round(g * 255),
        b: Math.round(b * 255)
      }
    },
    hslToRgb(h, s, l) {
      let a = s*Math.min(l,1-l);
      let f = (n,k=(n+h/30)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1);
      return [f(0),f(8),f(4)];
    },
    hslToHex(h, s, l) {
      l /= 100;
      
      const a = s * Math.min(l, 1 - l) / 100;
      
      const hex = n => {
        const k = (n + h / 30) % 12;
        const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
        
        return Math.round(255 * color).toString(16).padStart(2, '0');
      }
      
      return `#${hex(0)}${hex(8)}${hex(4)}`;
    },
    brightness(hex) {
      const rgb = this.rgbObjectFromHex(hex)
      return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000
    },
    foregroundAdjust(hex, test, lightHex, darkHex) {
      test = test || 154
      lightHex = lightHex || '#fff'
      darkHex = darkHex || '#000'
      return this.brightness(hex) < test ? lightHex : darkHex
    },
    interpolateColor(color1, color2, factor) {
      if (arguments.length < 3) { 
        factor = 0.5
      }
      const result = color1.slice()
      
      for (var i = 0; i < 3; i++) {
        result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]))
      }
      return result
    },
    interpolateColors(color1, color2, steps) {
      let stepFactor = 1 / (steps - 1)
      let interpolatedColorArray = []

      color1 = this.rgbArrayFromHex(color1).map(Number)
      color2 = this.rgbArrayFromHex(color2).map(Number)

      for (var i = 0; i < steps; i++) {
        interpolatedColorArray.push(this.interpolateColor(color1, color2, stepFactor * i))
      }

      return interpolatedColorArray
    }
  },
}

let vm = new Vue({
  el: 'main',
  mixins: [vueMixinColorFunctions],
  data: {
    min: 3,
    max: 27,
    delta: 10,
    steps: 11,
    color1: '#0ebeff',
    color2: '#ff42b3',
  },
  computed: {
    colors () {
      return this.interpolateColors(this.color1, this.color2, this.steps)
    },
    visualSteps () {
      return (this.steps - 2)
    },
    visualStepsLabel () {
      return (this.visualSteps === 1 ? 'step' : 'steps')
    },
  },
  methods: {
    adjust (color) {
      const hex = this.rgbToHex(color[0], color[1], color[2])
      return this.foregroundAdjust(hex)
    },
    setStyles(color) {
      return `background: rgb(${color}); color: ${this.adjust(color)}`
    },
    colorName (color) {
      return this.rgbArrayToHex(color)
    },
  }
})