a.github-corner(href='//github.com/meodai/color-names', target='_blank', aria-label='View source on Github')
  svg(width='80', height='80', viewbox='0 0 250 250', aria-hidden='true')
    path.triange(d='M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z')
    path.octo-arm(d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2', style='transform-origin: 130px 106px;')
    path.octo-body(d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z')
.options
  label
    span.label List
    select(data-list)
      option(value="default") Color Name List
      option(value="bestOf") Best of
      option(value="short") Short Names
      option(value="basic") Basic Colors
      option(value="html") HTML / CSS
      option(value="wikipedia") Wikipedia
      option(value="french") French Colors
      option(value="spanish") Spanish Colors
      option(value="japaneseTraditional") Japanese Traditional
      option(value="chineseTraditional") Chinese Traditional
      option(value="leCorbusier") Le Corbusier
      option(value="nbsIscc") NBS-ISCC
      option(value="ntc") NTC.js
      option(value="osxcrayons") OSX Crayon
      option(value="ral") RAL color
      option(value="ridgway") Ridgway's Nomenclature 
      option(value="sanzoWadaI") Sanzo Wada
      option(value="thesaurus") Thesaurus
      option(value="werner") Werner's Nomenclature
      option(value="windows") MS Windows
      option(value="x11") x11
      option(value="xkcd") xkcd survey
      
  
  label
    span.label Model
    select(data-model)
      option(value="rgb") rgb
      option(value="xyz") xyz
      option(value="cat02") cat02
      option(value="jab") jab
      option(value="luv") luv
      option(value="yuv") yuv
      option(value="lab") lab
      option(value="oklab") OK lab
      option(value="lch") lch
      option(value="hsv") hsv
      option(value="hsl") hsl
      option(value="hsi") hsi
      option(value="hwb") hwb
      option(value="hcg") hcg
      
button 
  span toggle darkmode
  strong.
    <svg xmlns="http://www.w3.org/2000/svg" width="184" height="184" viewBox="0 0 184 184">
      <g>
        <path class="line" d="M43.27,54.49A7.93,7.93,0,0,0,54.49,43.27l-8.33-8.33A7.93,7.93,0,0,0,34.95,46.16Z" transform="translate(-8 -8)"/>
        <path class="line" d="M159.45,167.38a7.93,7.93,0,0,0,5.61-13.54l-8.33-8.33a7.93,7.93,0,0,0-11.21,11.21l8.33,8.33A7.9,7.9,0,0,0,159.45,167.38Z" transform="translate(-8 -8)"/>
        <path class="line" d="M46.16,165.05l8.33-8.33a7.93,7.93,0,1,0-11.21-11.21l-8.33,8.33a7.93,7.93,0,1,0,11.21,11.21Z" transform="translate(-8 -8)"/>
        <path class="line" d="M151.12,56.81a7.91,7.91,0,0,0,5.61-2.32l8.33-8.33a7.93,7.93,0,0,0-11.21-11.21l-8.33,8.33a7.93,7.93,0,0,0,5.61,13.54Z" transform="translate(-8 -8)"/>
        <path class="line" d="M15.93,107.93H27.71a7.93,7.93,0,0,0,0-15.86H15.93a7.93,7.93,0,0,0,0,15.86Z" transform="translate(-8 -8)"/>
        <path class="line" d="M164.36,100a7.93,7.93,0,0,0,7.93,7.93h11.78a7.93,7.93,0,0,0,0-15.86H172.29A7.93,7.93,0,0,0,164.36,100Z" transform="translate(-8 -8)"/>
        <path class="line" d="M100,164.36a7.93,7.93,0,0,0-7.93,7.93v11.78a7.93,7.93,0,0,0,15.86,0V172.29A7.93,7.93,0,0,0,100,164.36Z" transform="translate(-8 -8)"/>
        <path class="line" d="M100,35.64a7.93,7.93,0,0,0,7.93-7.93V15.93a7.93,7.93,0,0,0-15.86,0V27.71A7.93,7.93,0,0,0,100,35.64Z" transform="translate(-8 -8)"/>
        <path class="circle" d="M100,153.09A53.09,53.09,0,1,0,46.91,100,53.15,53.15,0,0,0,100,153.09Zm0-90.33A37.23,37.23,0,1,1,62.77,100,37.27,37.27,0,0,1,100,62.77Z" transform="translate(-8 -8)"/>
      </g>
    </svg>

.currentColor

#container
View Compiled
$c-black: #202124;
$c-white: #fff;
$bg: $c-white;

:root {
  --dark: $c-black;
  --light: $c-white;
  --background: var($c-black);
  --foreground: var($c-white);
}

// <link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
//@import 'https://fonts.googleapis.com/css?family=Inconsolata';
//$t-code: 'Inconsolata', ipm, Menlo, 'Courier New', monospace;

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200&display=swap');

$t-code: 'Inter', sans-serif;
//@import 'https://fonts.googleapis.com/css?family=Space+Mono';
//$t-code: 'Space Mono', ipm, Menlo, 'Courier New', monospace;

body {
  overflow: hidden;
  background: $c-black;
  background: var(--background);
  font-family: $t-code;
  color: #fff;
  color: var(--foreground, #{$c-white});
}
.github-corner {
  position: absolute;
  top: 0; right: 0;
  transform: translate3d(0,0,0);
  transition: 333ms transform cubic-bezier(.7,.3,.25,1.15) 1200ms;
  will-change: transform;
  svg {
    width: 4rem; height: 4rem;
  }
  path {
    will-chrange: fill;
  }
  .octo-arm,
  .octo-body {
    fill: $c-black;
    fill: var(--background, #{$c-black});
  }
  .triange {
    fill: $c-white;
    fill: var(--foreground, #{$c-white});
  }
}
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}

button,
select {
  position: absolute;
  font-family: $t-code;
  box-sizing: border-box;
  
  appearance: none;
  -webkit-appearance: none;
  border: none;
  
  color: $c-white;
  color: var(--foreground, #{$c-white});
  background-color: transparent;
  
  cursor: pointer;
}
select {
  position: relative;
  //max-width: 10rem;
}
.options {
  position: absolute;
  bottom: 0;
  right: 0;
  padding: 1rem;
}

button {
  bottom: 1rem; left: 1rem;
  padding: 0;
  outline: none;
  padding: 0 0 0.5em 0.3em;
  span {
    display: none;
  }
  stonrg {
    display: block;
  }
  svg {
    display: block;
    width: 2rem; height: 2rem;
  }
  path {
    fill: currentColor;
  }
  .line {
    display: none;
  }
}

.isDark {
  button {
    .line {
      display: block;
    }
  }
}
select {
  font-size: 1.5rem;
  //text-decoration: underline;
  appearance: none;
  -webkit-appearance: none;
  border-radius: 0;
  color: turquoise;
  //text-decoration-color: pink;
  padding: 0.25em 1.2em 0.25em 0.3em;
  background-color: rgba(#202124,.5);
  background-size: auto 25%;
  background-repeat: no-repeat;
  background-position: calc(100% - .7rem) 55%;
  background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%20%3C%21--%20Generator%3A%20IcoMoon.io%20--%3E%20%3C%21DOCTYPE%20svg%20PUBLIC%20%22-//W3C//DTD%20SVG%201.1//EN%22%20%22http%3A//www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd%22%3E%20%3Csvg%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20fill%3D%22%23ffffff%22%3E%3Cpath%20d%3D%22M%2096.00%2C96.00l-96.00%2C96.00l%20256.00%2C256.00l%20256.00-256.00l-96.00-96.00L%20256.00%2C256.00L%2096.00%2C96.00z%22%20%3E%3C/path%3E%3C/svg%3E');
transition: 150ms background-color;
  
  &:focus {
    outline: none;
  }
  &:hover {
    background-color: rgba(#202124,.8);
  }
}
label {
  display: inline-block;
}


@media (max-width: 480px) {
  button {
    left: auto;
    right: 1rem;
    bottom: 7rem;
  }
  select,
  label {
    width: 100%;
  }
}

.label {
  font-size: .7em;
  display: block;
  padding: 0 0.5em;
}
.currentColor {
  pointer-events: none;
  position: absolute;
  left: 1.5rem;
  top: 1rem;
  text-shadow: 0 1px 10px #202124;
  h2 {
    margin: 0;
  }
  span {
    display: block;
    margin-top: .1em;
    &::before {
      position: relative;
      top: -.2em;
      display: inline-block;
      margin-right: 0.25em;
      color: var(--color);
      content: '⬤';
      font-size: .9em;
    }
  }
}
View Compiled
console.clear();

import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
import { OrbitControls } from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/examples/jsm/controls/OrbitControls.js';

let cam, scene, root, renderer, controls, layers,
  objects = [], cubeSize = 100, dotSize = 1.25,
  width = window.innerWidth + 1,
  height = window.innerHeight + 1,
  $select = document.querySelector('[data-model]'),
  $selectList = document.querySelector('[data-list]'),
  cDark = '#202124', cLight = '#ffffff',
  bg = cDark, colorMode = 'rgb', spaceCube, isDark = true;

const colorModes = {
  hsv: {
    func: 'hsv',
    x: [0, 360],
    y: [1, 1],
    z: [2, 1]
  },
  hsi: {
    func: 'hsi',
    x: [0, 360],
    y: [1, 1],
    z: [2, 1]
  },
  hsl: {
    func: 'hsl',
    x: [0, 360],
    y: [1, 1],
    z: [2, 1]
  },
  rgb: {
    func: 'rgb',
    x: [0, 255],
    y: [1, 255],
    z: [2, 255]
  },
  xyz: {
    func: 'xyz',
    x: [0, 95.047],
    y: [1, 100],
    z: [2, 108.883]
  },
  'cat02': {
    func: 'cat02',
    x: [0, 95.047],
    y: [1, 104],
    z: [2, 108.883],
  },
  jab: {
    func: 'jzazbz',
    x: [0, .2],
    y: [1, .16, -.16],
    z: [2, .16, -.16],
  },
  luv: {
    func: 'luv',
    x: [0, 100],
    y: [1, 224, -134],
    z: [2, 122, -140],
  },
  lab: {
    func: 'lab',
    z: [0, 100],
    y: [1, 127, -128],
    x: [2, 127, -128]
  },
  oklab: {
    func: 'oklab',
    z: [0, 1],
    y: [1, .3, -.3],
    x: [2, .35, -.35]
  },
  lch: {
    func: 'lch',
    z: [0, 100],
    y: [1, 100],
    x: [2, 0, 360]
  },
  yuv: {
    func: 'yuv',
    z: [0, 255],
    y: [1, 255],
    x: [2, 255]
  },
  hwb: {
    func: 'hwb',
    x: [0, 360],
    y: [1, 1],
    z: [2, 1]
  },
  hcg: {
    func: 'hcg',
    x: [0, 360],
    y: [1, 1],
    z: [2, 1]
  }
};

init();

function onWindowResize() {
  width = window.innerWidth + 1;
  height = window.innerHeight + 1;
  cam.aspect = width / height;
  cam.updateProjectionMatrix();
  renderer.setSize(width, height);
}

let colorList = [];

function fetchList (listname='default') {
  fetch(`https://api.color.pizza/v1/?list=${listname}`).then(d => d.json()).then(d => {
    colorList = d.colors;
    addParticles(colorList, colorMode);
  })
}

fetchList()

let part;

function createCanvasMaterial(color, size = 256) {
  var matCanvas = document.createElement('canvas');
  matCanvas.width = matCanvas.height = size;
  var matContext = matCanvas.getContext('2d');
  // create exture object from canvas.
  var texture = new THREE.Texture(matCanvas);
  // Draw a circle
  var center = size / 2;

  matContext.beginPath();
  matContext.arc(center, center, size / 2, 0, 2 * Math.PI, false);
  matContext.closePath();
  matContext.fillStyle = color;
  matContext.fill();
  // need to set needsUpdate
  texture.needsUpdate = true;
  // return a texture made from the canvas
  return texture;
}

let pMaterial, particles;

function addParticles(colorNames, cMode) {
  // create the particle variables
  const particleCount = colorNames.length;
  
  if (particles) {
    particles.dispose();
  }
  
  particles = new THREE.Geometry();
  
  if (pMaterial) {
    pMaterial.dispose();
  }
  
  dotSize = 255/Math.sqrt(colorNames.length/3) * .4;
  dotSize = Math.max(Math.min(dotSize, 4), 1.25);
  
  pMaterial = new THREE.PointsMaterial({
    vertexColors: THREE.VertexColors,
    size: dotSize,
    alphaMap: createCanvasMaterial('#ffffff', dotSize * 100),
    flatShading: true,
    //fog: false,
    //depthWrite: false,
    transparent: true,
    alphaTest: .5,
    //sizeAttenuation: true,
  });


  let colors = [];

  const mode = colorModes[cMode];

  colorNames.forEach((col, i) => {
    let colorComp;
    let color = new Color({
        "color": col.hex,
        "type": "hex"
    });
    
    if (mode.func === 'oklab') {
      colorComp = linear_srgb_to_oklab(chroma(col.hex).rgb());
    } else if (mode.func === 'yuv') {
      colorComp = yuv(chroma(col.hex).rgb());
    } else if (mode.func === "luv") {
      colorComp = color.luv;
    } else if (mode.func === "xyz" || mode.func === "cat02" || mode.func === "jzazbz") {
      
      colorComp = color.xyz;
      
      if (mode.func === "jzazbz") {
        colorComp = Jzazbz(colorComp);
      }
      
      if (mode.func === "cat02") {
        colorComp = xyz2cat02(colorComp);  
      }
      
    } else if (mode.func === 'hwb') {
      const [hsvH, hsvS, hsvV] = chroma(col.hex).hsv();
      colorComp = [
        hsvH,
        (1 - hsvS) * hsvV,
        1 - hsvV
      ]
    } else {
      colorComp = chroma(col.hex)[mode.func]();
      if (mode.func === 'hcg') {
        colorComp = [colorComp[0],colorComp[1] / 100,colorComp[2] / 100];
      }
    }
    


    let pX = translate(colorComp[mode.x[0]], mode.x[2] || 0, mode.x[1], -cubeSize * .5, cubeSize * .5),
        pY = translate(colorComp[mode.y[0]], mode.y[2] || 0, mode.y[1], -cubeSize * .5, cubeSize * .5),
        pZ = translate(colorComp[mode.z[0]], mode.z[2] || 0, mode.z[1], -cubeSize * .5, cubeSize * .5);

    if (mode.func === 'hsl' || mode.func === 'hsv' || mode.func === 'hsi' || mode.func === 'hcg') {
      let theta = Math.PI * colorComp[mode.x[0]] / 180;
      let r = colorComp[mode.y[0]] * cubeSize;

      if (mode.func === 'hsi') {
        r *= colorComp[mode.z[0]] * 0.75;
        /*
        let intensity = colorComp[mode.z[0]];
        let factor = Math.min(intensity, 1 - intensity) * 0.75;
        r *= factor;
        */
      } else if (mode.func === 'hsv') {
        r *= colorComp[mode.z[0]] * 0.5;
      } else if (mode.func === 'hcg') {
        r *= .5;
      } else {
        r *= colorComp[mode.z[0]] < 0.5 ? colorComp[mode.z[0]] : 1 - colorComp[mode.z[0]];
      }

      pY = r * Math.cos(theta);
      pX = r * Math.sin(theta);
    }
    

    if (mode.func === 'lch') {
      let theta = Math.PI * colorComp[mode.x[0]] / 180;
      let r = translate(colorComp[mode.y[0]], 0, mode.y[1], 0, cubeSize * .5);

      pY = r * Math.cos(theta);
      pX = r * Math.sin(theta);
    }


    let particle = new THREE.Vector3(pX, pY, pZ),
          Tcolor = new THREE.Color(col.hex);

    colors.push(Tcolor)

    // add it to the geometry
    particles.vertices.push(particle);
  });


  // create the particle system
  const particleSystem = new THREE.Points(
    particles,
    pMaterial
  );

  particleSystem.name = 'colors';
  particles.colors = colors;

  // add it to the scene
  objects.push(particleSystem)
  scene.add(particleSystem);
  part = particleSystem;
}

renderer.render(scene, cam);


animate();

function setSceneColor(color) {
  scene.background = new THREE.Color(color);
  scene.fog = new THREE.Fog(color, 150, 300); //new THREE.FogExp2(0x000000, 0.0007);
}
/*
var aspect = window.innerWidth / window.innerHeight;
var d = 20;
camera = new THREE.OrthographicCamera( - d * aspect, d * aspect, d, - d, 1, 1000 );

camera.position.set( 20, 20, 20 ); // all components equal
camera.lookAt( scene.position ); // or the origin
*/

function init() {
  cam = new THREE.PerspectiveCamera(75, width / height, 1, 500);
  cam.position.z = cubeSize * 1.5;
  scene = new THREE.Scene();
  setSceneColor(bg)
  root = new THREE.Object3D();

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);

  addCube();

  controls = new OrbitControls(cam, renderer.domElement);
  // controls.addEventListener( 'change', render ); // remove when using animation loop
  // enable animation loop when using damping or autorotation
  controls.enableDamping = true;
  controls.dampingFactor = .75;
  controls.enableZoom = true;
  controls.zoomSpeed = .25;
  controls.autoRotate = true;
  controls.autoRotateSpeed = 2.0;
  controls.maxDistance = cubeSize * 1.75;
  controls.maxPolarAngle = Math.PI * 4;
  //controls.minPolarAngle = 0;
  controls.maxAzimuthAngle = Infinity;
  controls.minAzimuthAngle = -Infinity;

  controls.noPan = true;
  controls.noKeys = true;
  //controls.noZoom = true;

  const container = document.querySelector('#container');
  container.appendChild(renderer.domElement);

  window.addEventListener('resize', onWindowResize, false);
  
  document.querySelector('button').addEventListener('click', toggleDarkMode, false);
}

function toggleDarkMode() {
  isDark = !isDark;

  document.querySelector('body').classList.toggle('isDark');
  var newColor = isDark ? cDark : cLight;

  setSceneColor(newColor);
  var colorspace = scene.getObjectByName('colorspace');
  scene.remove(colorspace);
  addCube(isDark ? '#ffffff' : cDark);

  document.documentElement.style.setProperty('--background', newColor);
  document.documentElement.style.setProperty('--foreground', isDark ? '#ffffff' : cDark);
}

function addCube(color) {
  let geometryCube = cube(cubeSize);
  //geometryCube.computeLineDistances();
  const material = new THREE.LineBasicMaterial({
    color: color || 0xffffff,
    linewidth: 1,
    linecap: 'round', //ignored by WebGLRenderer
    linejoin: 'round' //ignored by WebGLRenderer
  });
  
  const colorspace = new THREE.LineSegments(
    geometryCube,
    material
  );

  colorspace.name = 'colorspace';

  objects.push(colorspace);
  scene.add(colorspace);

  spaceCube = colorspace;
}

function cube(size) {
  const h = size * 0.5;
  const geometry = new THREE.Geometry();

  geometry.vertices.push(
    new THREE.Vector3(-h, -h, -h),
    new THREE.Vector3(-h, h, -h),
    new THREE.Vector3(-h, h, -h),
    new THREE.Vector3(h, h, -h),
    new THREE.Vector3(h, h, -h),
    new THREE.Vector3(h, -h, -h),
    new THREE.Vector3(h, -h, -h),
    new THREE.Vector3(-h, -h, -h),
    new THREE.Vector3(-h, -h, h),
    new THREE.Vector3(-h, h, h),
    new THREE.Vector3(-h, h, h),
    new THREE.Vector3(h, h, h),
    new THREE.Vector3(h, h, h),
    new THREE.Vector3(h, -h, h),
    new THREE.Vector3(h, -h, h),
    new THREE.Vector3(-h, -h, h),
    new THREE.Vector3(-h, -h, -h),
    new THREE.Vector3(-h, -h, h),
    new THREE.Vector3(-h, h, -h),
    new THREE.Vector3(-h, h, h),
    new THREE.Vector3(h, h, -h),
    new THREE.Vector3(h, h, h),
    new THREE.Vector3(h, -h, -h),
    new THREE.Vector3(h, -h, h)
  );

  return geometry;
}


function render() {
  const time = Date.now() * 0.0001;
  renderer.render(scene, cam);
  //controls.update();
  objects.forEach(object => {
    //object.rotation.x = 0.25 * time * ( i%2 == 1 ? 1 : -1);
    object.rotation.x = 0.25 * time;
    object.rotation.y = 0.25 * time;
  })
}

function animate() {
  requestAnimationFrame(animate);
  render();
}


$select.addEventListener('change', e => {
  colorMode = $select.value;
  objects = [];
  const colorspace = scene.getObjectByName('colorspace');
  scene.remove(colorspace);
  const colors = scene.getObjectByName('colors');
  scene.remove(colors);
  addParticles(colorList, colorMode);
  addCube(isDark ? cLight : cDark);
}, false);

$selectList.addEventListener('change', e => {
  const listName = $selectList.value;
  objects = [];
  const colorspace = scene.getObjectByName('colorspace');
  scene.remove(colorspace);
  const colors = scene.getObjectByName('colors');
  scene.remove(colors);
  fetchList(listName);
  //addParticles(colorList, colorMode);
  addCube(isDark ? cLight : cDark);
}, false);

function translate(value, low1, high1, low2, high2) {
  return low2 + (high2 - low2) * ((value - low1) / (high1 - low1));
}

const PQ = function perceptual_quantizer(X) {
  const XX = Math.pow(X*1e-4, 0.1593017578125);
  return Math.pow(
    (0.8359375 + 18.8515625*XX) / (1 + 18.6875*XX),
    134.034375); 
};

function Jzazbz([X, Y, Z]) {
  const
    Lp = PQ(0.674207838*X + 0.382799340*Y - 0.047570458*Z),
    Mp = PQ(0.149284160*X + 0.739628340*Y + 0.083327300*Z),
    Sp = PQ(0.070941080*X + 0.174768000*Y + 0.670970020*Z),
    Iz = 0.5 * (Lp + Mp),
    az = 3.524000*Lp - 4.066708*Mp + 0.542708*Sp,
    bz = 0.199076*Lp + 1.096799*Mp - 1.295875*Sp,
    Jz = (0.44 * Iz) / (1 - 0.56*Iz) - 1.6295499532821566e-11;
  return [Jz, az, bz];
}

function cat022hpe(l,m,s) {
  var lh = ( 0.7409792 * l) + (0.2180250 * m) + (0.0410058 * s),
      mh = ( 0.2853532 * l) + (0.6242014 * m) + (0.0904454 * s),
      sh = (-0.0096280 * l) - (0.0056980 * m) + (1.0153260 * s);

  return {lh: lh, mh: mh, sh: sh};
}

function xyz2cat02([x,y,z]) {
  
  const l = ( 0.7328 * x) + (0.4296 * y) - (0.1624 * z),
        m = (-0.7036 * x) + (1.6975 * y) + (0.0061 * z),
        s = ( 0.0030 * x) + (0.0136 * y) + (0.9834 * z);
  
  return [l, m, s];
}

function yuv(rgb) {
  return [
    /*Y*/ rgb[0] * .299000 + rgb[1] * .587000 + rgb[2] * .114000,
    /*U*/ rgb[0] * -.168736 + rgb[1] * -.331264 + rgb[2] * .500000 + 128,
    /*V*/ rgb[0] * .500000 + rgb[1] * -.418688 + rgb[2] * -.081312 + 128
  ]
}

function f(x)
{
    if (x >= 0.0031308)
        return (1.055) * x^(1.0/2.4) - 0.055
    else
        return 12.92 * x;
}

function linear_srgb_to_oklab(rgb) 
{
  rgb = rgb.map(comp => comp / 255);
  
  let l = 0.4121656120 * rgb[0] + 0.5362752080 * rgb[1] + 0.0514575653 * rgb[2];
  let m = 0.2118591070 * rgb[0] + 0.6807189584 * rgb[1] + 0.1074065790 * rgb[2];
  let s = 0.0883097947 * rgb[0] + 0.2818474174 * rgb[1] + 0.6302613616 * rgb[2];

  l = Math.cbrt(l);
  m = Math.cbrt(m);
  s = Math.cbrt(s);

  return [
    (0.2104542553*l + 0.7936177850*m - 0.0040720468*s),
    (1.9779984951*l - 2.4285922050*m + 0.4505937099*s),
    (0.0259040371*l + 0.7827717662*m - 0.8086757660*s),
  ];
}

let rayCaster = new THREE.Raycaster();

document.querySelector('body').addEventListener('mousemove', onDocumentMouseMove);

const $currentColor = document.querySelector('.currentColor');

let currentColorTimer;

function showColor(name, hex) {
  clearTimeout(currentColorTimer);
  $currentColor.innerHTML = `<div><h2>${name}</h2><span>${hex}</span></div>`
  $currentColor.style = `--color: ${hex}`;
  currentColorTimer = setTimeout(() => {
    $currentColor.innerHTML = '';
  }, 3000);
}

// Highlight a color name using a raycaster at some point
function onDocumentMouseMove(event) {
  let mousePosition = {};
  event.preventDefault();
  mousePosition.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
  mousePosition.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
  rayCaster.setFromCamera(mousePosition, cam);
  var intersects = rayCaster.intersectObjects([scene.getObjectByName('colors')], false);
  if (intersects.length > 0) {
    var descriptions = [];
    for (var i = 0; i < intersects.length; i++) {
      //Only display those points we can SEE due to the near clipping plane.
      //Without this check, the ray caster will list them all, even if they're clipped by the near plane.
      //".distance" is relative to the camera, not absolute world units.
      if (intersects[i].distance > cam.near) {
        var description = "  " + colorList[intersects[i].index].name + ", ";
        description += "  " + colorList[intersects[i].index].hex + ", ";
        description += "  Distance: " + intersects[i].distance.toFixed(2) + ", ";
        description += "  Ray to point dist: " + intersects[i].distanceToRay.toFixed(2) + ", ";
        description += "  Index: " + intersects[i].index + ", ";
        description += "  Coords: [" + intersects[i].point.x.toFixed(2) + ", " + intersects[i].point.y.toFixed(2) + ", " + intersects[i].point.z.toFixed(2) + "]";
        descriptions.push(description);

        showColor(colorList[intersects[i].index].name, colorList[intersects[i].index].hex);
        break;
      }

      if (descriptions.length > 0) {
        console.log("Mouse pointer intersected the following points, closest to furthest: ");
        for (var v = 0; v < descriptions.length; v++)
          console.log(descriptions[v]);
      }
    }
  }
}

View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/ac-colors@1/dist/ac-colors.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js