<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title>Colour Contrast Investigation</title>
  <style type="text/css">
   map { display: block; }
   table { display: table; margin: 1em; empty-cells: show; float: left; }
   row { display: table-row; }
   cell { display: table-cell; padding: 0.1em; width: 2em; text-align: center; }
   cell[header] { font-weight: bold; }
   row:first-child > cell:first-child { border-style: none solid solid none; border-width: thin; }
   p { clear: left; }
  </style>
  <script type="application/x-javascript"><![CDATA[

   function newTable(hue, steps) {
     var table = document.createElementNS('', 'table');
     var row = document.createElementNS('', 'row');
     var cell = document.createElementNS('', 'cell');
     cell.appendChild(document.createTextNode(hue.toFixed(0)));
     row.appendChild(cell);
     row.setAttribute('header', '');
     for (var lightness = 0.0; lightness <= 1.0; lightness += 1.0/steps) {
       cell = document.createElementNS('', 'cell');
       cell.setAttribute('header', '');
       cell.appendChild(document.createTextNode((lightness * 100).toFixed(1) + '%'));
       row.appendChild(cell);
     }
     table.appendChild(row);
     return table;
   }

   function newRow(hue, saturation, steps) {
     var row = document.createElementNS('', 'row');
     var cell = document.createElementNS('', 'cell');
     cell.setAttribute('header', '');
     cell.appendChild(document.createTextNode((saturation * 100).toFixed(1) + '%'));
     row.appendChild(cell);
     return row;
   }

   function newCell(hue, saturation, lightness, steps, stylesheet) {
     var cell = document.createElementNS('', 'cell');
     var code = 'h' + hue + 's' + saturation + 'l' + lightness;
     cell.setAttribute('color', code);
     cell.appendChild(document.createTextNode(''));
     var rule = '[color="' + code + '"] { background: hsl(' + hue.toFixed(0) + ', '
                                                            + (saturation * 100) + '%, '
                                                            + (lightness * 100) + '%); }';
     stylesheet.insertRule(rule, 0);
     return cell;
   }

   function spawn() {
     var startTime = new Date();
     const steps = 12;
     var map = document.getElementsByTagNameNS('', 'map')[0];
     var stylesheet = document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', 'style')[0].sheet;
     for (var hue = 0.0; hue < 360.0; hue += 360.0/steps) {
       var table = newTable(hue, steps);
       for (var saturation = 0.0; saturation <= 1.0; saturation += 1.0/steps) {
         var row = newRow(hue, saturation, steps);
         for (var lightness = 0.0; lightness <= 1.0; lightness += 1.0/steps) {
           row.appendChild(newCell(hue, saturation, lightness, steps, stylesheet));
         }
         table.appendChild(row);
       }
       map.appendChild(table);
     }
     var timer = document.getElementById('timer');
     var time = (new Date() - startTime) / 1000;
     timer.appendChild(document.createTextNode('Took ' + time + 's'));
   }
  ]]></script>
 </head>
 <body onload="spawn()">
  <map xmlns=""/>
  <p id="timer"/>
 </body>
</html>