<canvas></canvas>
<canvas class="clone"></canvas>
<select data-cube></select>
body {
margin: 0;
padding: 0;
}
body * {
all: unset;
}
html {
overflow: hidden;
}
canvas {
display: block;
}
.clone {
opacity: 0;
pointer-events: none;
}
select {
position: absolute;
bottom: 0;
right: 0;
max-width: 80vw;
z-index: 10;
text-align: right;
background: #fff;
padding: 1em 2em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background: transparent;
text-decoration: underline;
color: var(--c);
/*mix-blend-mode: difference;*/
font-weight: bold;
cursor: pointer;
}
@supports (mix-blend-mode: hue) {
.clone {
opacity: 1;
position: absolute;
top: 0;
left: 0;
mix-blend-mode: hue;
filter: blur(3vmin);
pointer-events: none;
}
}
View Compiled
import { ryb2rgb, rybHsl2rgb, cubes } from "https://esm.sh/rybitten/";
import { generateColorRamp } from 'https://esm.sh/rampensau/';
import seedrandom from "https://esm.sh/seedrandom@3.0.5";
let seed = Math.random();
let rnd = new seedrandom(seed);
let currentCube = cubes.get('itten').cube;
const formatCSS = (rgb) => {
return `rgb(${Math.round(rgb[0] * 255)} ${Math.round(rgb[1] * 255)} ${Math.round(rgb[2] * 255)})`;
};
const newOptions = () => ({
total: 6,
hStart: rnd() * 360,
hCycles: rnd() < .5 ? -1.25 + rnd() : 1.25 + rnd(),
sRange: rnd() < .5 ? [
.2 + rnd() * .2,
.25 + rnd() * .3
] : rnd() < .5 ? [1, rnd()] : [
.4 + rnd() * .2, 1
],
sEasing: x => Math.pow(x, 2),
lRange: rnd() < 1 ? [
rnd() < .5 ? // half of the palettes will become pretty bright
.55 + rnd() * .3 :
.88 + rnd() * .12,
.01 + rnd() * .4,
] : [1, .5 + rnd() * .2],
lEasing: x => Math.pow(x, 1.1),// x => -(Math.cos(Math.PI * x)),
});
const $can = document.querySelector('canvas');
const $canClone = document.querySelector('canvas.clone');
const ctx = $can.getContext('2d');
const ctxClone = $canClone.getContext('2d');
function shuffleArray(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex > 0) {
// Pick a remaining element.
randomIndex = Math.floor(rnd() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
function drawShape (x, y, size, colorsIn, hasGradient = false) {
const colors = rnd() < .5 ? shuffleArray(colorsIn) : colorsIn;
ctx.fillStyle = colors[3];
ctx.fillRect(x, y, size, size);
let minimalBg = true;
if (rnd() < .5) {
minimalBg = false;
ctx.fillStyle = colors[4];
ctx.fillRect(x + size * .5, y, size * .5, size);
}
const circleSize = size * .3;
const circleX = x + size * .5;
const circleY = y + size * .5;
ctx.save();
ctx.translate(circleX, circleY);
const rotation = Math.PI / 2 * Math.floor(rnd() * 2);
if (rnd() < .5) {
ctx.scale(-1, 1);
}
ctx.rotate(rotation);
let fillGradients = hasGradient ? rnd() < .75 : false;
if (fillGradients) {
const gradient = ctx.createLinearGradient(-circleSize, 0, circleSize, 0);
gradient.addColorStop(0, colors[0]);
gradient.addColorStop(1, colors[1]);
ctx.fillStyle = gradient;
} else {
ctx.fillStyle = colors[0];
}
ctx.beginPath();
ctx.arc(0, 0, circleSize, 0, 2 * Math.PI);
ctx.fill();
if (rnd() < .2) {
ctx.fillRect(-size * .3 + circleSize, -size * .3 + circleSize - size / 2, size / 2, size / 2);
}
if (rnd() < .75 || minimalBg) {
if (fillGradients && rnd() < .5) {
const gradient = ctx.createLinearGradient(-circleSize, 0, circleSize, 0);
gradient.addColorStop(0, colors[1]);
gradient.addColorStop(1, colors[2]);
ctx.fillStyle = gradient;
} else {
ctx.fillStyle = colors[1];
}
ctx.beginPath();
ctx.arc(0, 0, circleSize, 0, Math.PI);
ctx.fill();
if (fillGradients && rnd() < .5) {
const gradient = ctx.createLinearGradient(-circleSize, 0, circleSize, 0);
gradient.addColorStop(0, colors[2]);
gradient.addColorStop(1, colors[3]);
ctx.fillStyle = gradient;
} else {
ctx.fillStyle = colors[2];
}
ctx.rotate(Math.PI);
ctx.beginPath();
ctx.arc(0, 0, circleSize, 0, Math.PI / 2);
ctx.lineTo(0, 0);
ctx.fill();
}
ctx.restore();
}
let t = 0;
function doIt () {
const w = window.innerWidth;
const h = window.innerHeight;
$can.width = w;
$can.height = h;
const outerShapeSize = Math.min(w, h) * .4;
const options = newOptions();
const colorHSL = generateColorRamp(options);
const colors = colorHSL.map(hsl => formatCSS(rybHsl2rgb(hsl, {cube: currentCube})));
document.documentElement.style.setProperty('--w', formatCSS(ryb2rgb([0,0,0], {cube: currentCube})));
document.documentElement.style.setProperty('--b', formatCSS(ryb2rgb([1,1,1], {cube: currentCube})));
if (rnd() < .5) {
ctx.fillStyle = colors.shift();
} else {
if (rnd() < .5) {
colors.unshift(colors.pop());
}
colors.pop()
ctx.fillStyle = colors.pop();
}
ctx.fillRect(0, 0, w, h);
const hasGradient = rnd() < .3;
if ( rnd() < .8 ) {
const shapeCount = 2 + Math.floor(rnd() * 4);
const shapeSize = outerShapeSize / shapeCount * 1.2;
const shapeMargin = shapeSize * .25;
const shapeTotalSize = shapeSize + shapeMargin;
const shapeTotalCount = shapeCount * shapeCount;
const shapeTotalWidth = shapeTotalSize * shapeCount;
const shapeTotalHeight = shapeTotalSize * shapeCount;
const shapeStartX = w/2 - shapeTotalWidth/2;
const shapeStartY = h/2 - shapeTotalHeight/2;
for (let i = 0; i < shapeTotalCount; i++) {
options.hStart += 360 / shapeTotalCount;
options.hStart %= 360;
const newColors = generateColorRamp(options).map(hsl => formatCSS(rybHsl2rgb(hsl, {cube: currentCube})));
const x = shapeStartX + (i % shapeCount) * shapeTotalSize + shapeMargin/2;
const y = shapeStartY + Math.floor(i / shapeCount) * shapeTotalSize + shapeMargin/2;
drawShape(x, y, shapeSize, newColors, hasGradient);
}
} else {
drawShape(w/2 - outerShapeSize/2, h/2 - outerShapeSize/2, outerShapeSize, colors, hasGradient);
}
$canClone.width = w;
$canClone.height = h;
if (rnd() < .3) {
ctxClone.drawImage($can, 0, 0);
} else {
ctxClone.clearRect(0, 0, w, h);
}
}
doIt();
let isRunning = true;
let int = setInterval(() => {
if (isRunning) {
seed = Math.random();
rnd = new seedrandom(seed);
doIt();
}
}, 1100);
$can.addEventListener('click', () => {
if(!isRunning) {
seed = Math.random();
rnd = new seedrandom(seed);
doIt();
}
clearInterval(isRunning);
isRunning = false;
});
const $select = document.querySelector('[data-cube]');
for (const [cubeKey, cube] of cubes) {
const $opt = document.createElement('option');
$opt.value = cubeKey;
$opt.innerHTML = cube.title + ' — ' + cube.author + ` / ${cube.year}`;
if (cubeKey === 'itten') {
$opt.selected = 'selected';
}
$select.appendChild($opt);
}
$select.addEventListener('change', (e) => {
isRunning = false;
rnd = new seedrandom(seed);
currentCube = cubes.get(e.target.value).cube;
doIt();
});
window.addEventListener('resize', () => {
rnd = new seedrandom(seed);
doIt();
})
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.