themer
Version:
Customizable theme creator for editors, terminals, wallpaper, and more.
149 lines (144 loc) • 5.12 kB
text/typescript
import { listOutputFiles, Template } from './index.js';
import { Accents, colorSetToVariants } from '../color-set/index.js';
import { source } from 'common-tags';
const SIZE = 70;
const MAX_DISTANCE = SIZE * 5;
const LEAK_FACTOR = 0.25;
function getClampedPoint(
x1: number,
y1: number,
maxX: number,
maxY: number,
): [number, number] {
const distance = Math.sqrt(Math.pow(maxX - x1, 2) + Math.pow(maxY - y1, 2));
const x2 = x1 + (MAX_DISTANCE / distance) * (maxX - x1);
const y2 = y1 + (MAX_DISTANCE / distance) * (maxY - y1);
const croppedDistance = Math.sqrt(
Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2),
);
return croppedDistance > distance ? [maxX, maxY] : [x2, y2];
}
const template: Template = {
name: 'Burst wallpaper',
render: async function* (colorSet, options) {
const variants = colorSetToVariants(colorSet);
for (const variant of variants) {
for (const size of options.wallpaperSizes) {
const focalPoint = {
x: size.w / 2,
y: size.h / 2,
};
const svg = source`
<svg
xmlns="http://www.w3.org/2000/svg"
width="${size.w}"
height="${size.h}"
viewBox="0 0 ${size.w} ${size.h}"
>
<defs>
<radialGradient id="burst">
<stop
offset="0%"
stop-color="${
colorSet.name === 'dark'
? variant.colors.shade1
: variant.colors.shade0
}"
stop-opacity="0.25"
/>
<stop
offset="70%"
stop-color="${
colorSet.name === 'dark'
? variant.colors.shade0
: variant.colors.shade1
}"
/>
</radialGradient>
</defs>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="${variant.colors.shade0}"
/>
<rect x="0" y="0" width="100%" height="100%" fill="url(#burst)" />
${Array(Math.round(size.w / SIZE))
.fill(null)
.map((_, i, { length: xCount }) =>
Array(Math.round(size.h / SIZE))
.fill(null)
.map((_, j, { length: yCount }) => {
const cellWidth = size.w / xCount;
const cellHeight = size.h / yCount;
const x1 = i * cellWidth + Math.random() * cellWidth;
const y1 = j * cellHeight + Math.random() * cellHeight;
const [x2, y2] = getClampedPoint(
x1,
y1,
focalPoint.x,
focalPoint.y,
);
const keys: (keyof Accents)[] = [
'accent0',
'accent1',
'accent2',
'accent3',
'accent4',
'accent5',
'accent6',
'accent7',
];
const accentKey =
keys[
(Math.round(
(i / xCount) * keys.length +
(Math.random() * LEAK_FACTOR * 2 - LEAK_FACTOR),
) +
keys.length) %
keys.length
];
const color = variant.colors[accentKey!];
const id = `${i}-${j}`;
return source`
<defs>
<linearGradient
id="${id}"
gradientUnits="userSpaceOnUse"
x1="${x1}"
y1="${y1}"
x2="${x2}"
y2="${y2}"
>
<stop offset="2%" stop-color="${variant.colors.shade7}" />
<stop offset="30%" stop-color="${color}" />
<stop offset="50%" stop-color="${color}" stop-opacity="0" />
</linearGradient>
</defs>
<line
x1="${x1}"
y1="${y1}"
x2="${x2}"
y2="${y2}"
stroke-width="3"
stroke-linecap="round"
stroke="url(#${id})"
/>
`;
})
.join('\n'),
)
.join('\n')}
</svg>
`;
yield {
path: `${variant.title.kebab}-${size.w}x${size.h}.svg`,
content: svg,
};
}
}
},
renderInstructions: listOutputFiles,
};
export default template;