I'm not a designer, and I've never been happy with the color scheme for kavanot.name, so I decided to try something new. I use scssphp with scssphp server (and yes, you need both) to generate the CSS, and it allows variables, so I can do:
$hue = 250;
$saturation = '40%';
$textcolor: hsl($hue, $saturation, 10%);
$displaytextcolor: hsl($hue, $saturation, 40%);
$backcolor: hsl($hue, $saturation, 95%);
$bordercolor: hsl($hue, $saturation, 90%);
$deepbackcolor: hsl($hue, $saturation, 85%);
to make all the colors a variation on one single color. And I like that. But what color? Fortunately, scssphp allows for importing variables, so my `styles.php` includes:
setcookie('hue', $_GET['hue'], time()+12*60*60*24*30, '/'); // one year expiration
setcookie('saturation', $_GET['saturation'], time()+12*60*60*24*30, '/'); // one year expiration
$scss = new Compiler();
$scss->setVariables(array(
'hue' => intval($_GET['hue']),
'saturation' => intval($_GET['saturation']).'%',
));
instead of the in-file $hue = 250; $saturation = '40%';
. Now I can include those values in the URL for the stylesheet itself,
and the cookies mean that I don't have to write it each time.
To get the query variables to the stylesheet, I have to pass them from the main page:
<link rel="stylesheet" id=mainstylesheet type="text/css" href="/css/style.php/style.scss?hue=<?=$_GET['hue']?>&saturation=<?=$_GET['saturation']?>" />
(I am ignoring the input sanitization you need to avoid XSS. intval
is your friend here!)
But retyping http://kavanot.name?hue=250&saturation=40
with minor variations is a pain. Can I make it interactive? Yes, with a Javascript
color picker. <input type=color />
is supported in all the major browsers, so I can do:
var colorPicker = $('input[type=color]');
let baseHSL = getBaseHSL(); // get the body color as [hue, saturation, lightness]
baseHSL[2] = 50; // start with a color that can be easily seen, 50% lightness
colorPicker.val(hsl2hex(...baseHSL)); // set the initial value
const stylesheet = $('#mainstylesheet')[0];
colorPicker.on('input', function(){
let [h, s] = hex2hsl(this.value);
// change the stylesheet href and the styles change on the fly!
stylesheet.href = stylesheet.href.replace(/hue=\d+/, `hue=${h}`).replace(/saturation=\d+/, `saturation=${s}`);
});
function getBaseHSL(){
// getComputedStyle.color is rgb(r,g,b)
let [, r, g, b] = /(\d+),\s*(\d+),\s*(\d+)/.exec(window.getComputedStyle(document.body).color);
return rgb2hsl (r, g, b);
}
Now I need the functions rgb2hsl
, hex2hsl
and hsl2hex
to convert between the various color spaces. Luckily,
Jon Kantner has done the work for me.
function hex2hsl(hex){
let [, r, g, b] = /#(..)(..)(..)/.exec(hex);
return rgb2hsl (+('0x'+r), +('0x'+g), +('0x'+b));
}
function rgb2hsl (r, g, b){
// convert to fractions
r = r/255;
b = b/255;
g = g/255;
// find greatest and smallest channel values
const cmin = Math.min(r,g,b);
const cmax = Math.max(r,g,b);
const delta = cmax - cmin;
let h;
// calculate hue
if (delta == 0){ // no difference
h = 0;
}else if (cmax == r){ // red is max
h = ((g - b) / delta) % 6;
}else if (cmax == g){
h = (b - r) / delta + 2; // green is max
}else{
h = (r - g) / delta + 4; // blue is max
}
h = Math.round(h * 60);
if (h < 0) h += 360;
// calculate lightness
let l = (cmax + cmin) / 2;
// calculate saturation
let s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
// make s and l percents
return [h, Math.round(s*100), Math.round(l*100)];
}
function hsl2hex(h, s, l){
let c = (1 - Math.abs(2 * l/100 - 1)) * s/100;
let x = c * (1 - Math.abs((h / 60) % 2 - 1));
let m = l/100 - c/2;
let r, g, b;
if (0 <= h && h < 60) {
r = c; g = x; b = 0;
} else if (60 <= h && h < 120) {
r = x; g = c; b = 0;
} else if (120 <= h && h < 180) {
r = 0; g = c; b = x;
} else if (180 <= h && h < 240) {
r = 0; g = x; b = c;
} else if (240 <= h && h < 300) {
r = x; g = 0; b = c;
} else if (300 <= h && h < 360) {
r = c; g = 0; b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function toHex(d) {
d = d.toString(16);
if (d.length == 1) d = `0${d}`;
return d.toUpperCase();
}
and now I can play with colors to my heart's content!