2023-10-09 16:30:25 +02:00
|
|
|
import { reactive } from "petite-vue";
|
|
|
|
|
2023-10-05 13:41:55 +02:00
|
|
|
var style = document.createElement("style");
|
|
|
|
|
|
|
|
addEventListener("load", () => {
|
2023-10-09 16:30:25 +02:00
|
|
|
|
|
|
|
document.querySelector("head")!.appendChild(style);
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
});
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
export namespace Exporter {
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
export let parts = reactive([
|
|
|
|
{name: "Buttons", file: "button", enabled: true},
|
|
|
|
{name: "Fields", file: "input", enabled: true},
|
|
|
|
{name: "Toggles", file: "toggle", enabled: true},
|
|
|
|
{name: "Checkboxes and radios", file: "checkbox", enabled: true},
|
|
|
|
]);
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
interface Result {
|
|
|
|
name: string,
|
|
|
|
css: string,
|
|
|
|
size: number,
|
|
|
|
size_gzip: number
|
|
|
|
}
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
export async function get(theme: Theme) {
|
|
|
|
|
|
|
|
let cssParts = [theme.generate()];
|
|
|
|
for(let p of parts) {
|
|
|
|
if(p.enabled) {
|
|
|
|
let value = await (await fetch(`./${p.file}.css`)).text();
|
|
|
|
value = `/* ${p.name} */\n\n${value}`;
|
|
|
|
cssParts.push(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let css = cssParts.join("\n\n/* ------------------- */\n\n");
|
|
|
|
|
|
|
|
let results: Result[] = [];
|
|
|
|
await addResult(results, "synergy.min.css", minify(css));
|
|
|
|
await addResult(results, "synergy.css", css);
|
|
|
|
|
|
|
|
return results;
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function addResult(results: Result[], name: string, css: string) {
|
|
|
|
results.push({
|
|
|
|
name,
|
|
|
|
css,
|
|
|
|
size: getSize(css),
|
|
|
|
size_gzip: await getCompressedSize(css)
|
2023-10-05 13:41:55 +02:00
|
|
|
});
|
2023-10-09 16:30:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getCompressedSize(content: string) {
|
|
|
|
let ds = new CompressionStream("gzip");
|
|
|
|
let blob = new Blob([content]);
|
|
|
|
let compressedStream = blob.stream().pipeThrough(ds);
|
|
|
|
return (await new Response(compressedStream).blob()).size;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSize(content: string) {
|
|
|
|
return (new TextEncoder().encode(content)).length
|
|
|
|
}
|
|
|
|
|
|
|
|
function minify(value: string) {
|
|
|
|
return value
|
|
|
|
.replace(/([^0-9a-zA-Z\.#])\s+/g, "$1")
|
|
|
|
.replace(/\s([^0-9a-zA-Z\.#]+)/g, "$1")
|
|
|
|
.replace(/;}/g, "}")
|
|
|
|
.replace(/\/\*.*?\*\//g, "");
|
|
|
|
}
|
|
|
|
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
export interface Preset {
|
|
|
|
main: string,
|
|
|
|
text: string,
|
|
|
|
bg: string,
|
|
|
|
siteBg?: string
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
export class Color {
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
r: number;
|
|
|
|
g: number;
|
|
|
|
b: number;
|
|
|
|
a: number;
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
constructor(r: number, g: number, b: number, a: number = 1) {
|
2023-10-05 13:41:55 +02:00
|
|
|
this.r = r;
|
|
|
|
this.g = g;
|
|
|
|
this.b = b;
|
|
|
|
this.a = a;
|
|
|
|
}
|
|
|
|
|
|
|
|
clone() {
|
|
|
|
return new Color(this.r, this.g, this.b, this.a);
|
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
static fromHex(hex: string) {
|
|
|
|
let [r, g, b] = this.hexToRgb(hex);
|
|
|
|
return new Color(r, g, b);
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
static hexToRgb(hex: string) {
|
2023-10-05 13:41:55 +02:00
|
|
|
hex = hex.replace(/^#/, '');
|
|
|
|
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
|
|
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
|
|
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
|
|
return [r, g, b];
|
|
|
|
}
|
|
|
|
|
|
|
|
rgbFormat() {
|
2023-10-22 01:08:15 +02:00
|
|
|
let rgb = `${this.r*255}, ${this.g*255}, ${this.b*255}`;
|
|
|
|
return this.a == 1 ? `rgb(${rgb})` : `rgba(${rgb}, ${this.a})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
hexFormat() {
|
|
|
|
let hex = `#${(1 << 24 | (this.r*255) << 16 | (this.g*255) << 8 | (this.b*255)).toString(16).slice(1)}`;
|
|
|
|
return this.a != 1 ? `${hex}${(Math.floor(this.a * 255).toString(16).padStart(2, '0'))}` : hex;
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
contrast(otherColor: Color) {
|
|
|
|
const getRelativeLuminance = (rgb: number) => {
|
2023-10-05 13:41:55 +02:00
|
|
|
const sRGB = rgb / 255;
|
|
|
|
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4);
|
|
|
|
};
|
|
|
|
const luminance1 = getRelativeLuminance(this.r) * 0.2126 +
|
|
|
|
getRelativeLuminance(this.g) * 0.7152 +
|
|
|
|
getRelativeLuminance(this.b) * 0.0722;
|
|
|
|
const luminance2 = getRelativeLuminance(otherColor.r) * 0.2126 +
|
|
|
|
getRelativeLuminance(otherColor.g) * 0.7152 +
|
|
|
|
getRelativeLuminance(otherColor.b) * 0.0722;
|
|
|
|
const contrastRatio = (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05);
|
|
|
|
return (contrastRatio*100)-100;
|
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
equals(otherColor: Color) {
|
2023-10-22 01:08:15 +02:00
|
|
|
return this.r == otherColor.r && this.g == otherColor.g && this.b == otherColor.b;
|
|
|
|
}
|
|
|
|
|
|
|
|
mix(color: Color, ratio: number): Color {
|
|
|
|
const r = Math.round(this.r*255 * (1 - ratio) + color.r*255 * ratio);
|
|
|
|
const g = Math.round(this.g*255 * (1 - ratio) + color.g*255 * ratio);
|
|
|
|
const b = Math.round(this.b*255 * (1 - ratio) + color.b*255 * ratio);
|
|
|
|
return new Color(r/255, g/255, b/255);
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
export class Theme {
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
main: Color;
|
|
|
|
text: Color;
|
|
|
|
bg: Color;
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
constructor(opt: Preset) {
|
2023-10-05 13:41:55 +02:00
|
|
|
|
|
|
|
this.main = Color.fromHex(opt.main);
|
|
|
|
this.text = Color.fromHex(opt.text);
|
|
|
|
this.bg = Color.fromHex(opt.bg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
apply() {
|
|
|
|
style.innerHTML = this.generate();
|
|
|
|
}
|
|
|
|
|
|
|
|
generate() {
|
|
|
|
|
|
|
|
let variables = [];
|
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
variables.push(this.var("border", this.cAlpha(this.main, .4)));
|
|
|
|
variables.push(this.var("border-active", this.cAlpha(this.main)));
|
2023-10-05 19:47:44 +02:00
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
variables.push(this.var("focus-highlight", this.cMix(this.main, this.bg, .25)));
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
variables.push(this.var("label", this.cMix(this.main, this.bg, .8)));
|
|
|
|
variables.push(this.var("label-active", this.cMix(this.main, this.bg)));
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
variables.push(this.var("btn-primary-bg", this.cMix(this.main, this.bg, .8)));
|
|
|
|
variables.push(this.var("btn-primary-bg-active", this.cMix(this.main, this.bg, .5)));
|
|
|
|
variables.push(this.var("btn-primary-bg-hover", this.cMix(this.main, this.bg)));
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
let btnBg = this.bg.mix(new Color(.6, .6, .6), .8).mix(this.main, .1);
|
|
|
|
variables.push(this.var("btn-bg", this.cMix(btnBg, this.bg, .3)));
|
|
|
|
variables.push(this.var("btn-bg-active", this.cMix(btnBg, this.bg, .6)));
|
|
|
|
variables.push(this.var("btn-bg-hover", this.cMix(btnBg, this.bg, .4)));
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
variables.push(this.var("text-color", this.text.hexFormat()));
|
|
|
|
variables.push(this.var("bg", this.bg.hexFormat()));
|
2023-10-05 13:41:55 +02:00
|
|
|
|
|
|
|
if(this.main.contrast(this.bg) < .3) alert("Contrast between main color and the background is low!");
|
|
|
|
if(this.bg.contrast(this.text) < .3) alert("Contrast between text color and the background is low!");
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
let styles = [`:root {\n${variables.join("\n")}\n}`];
|
2023-10-05 13:41:55 +02:00
|
|
|
|
|
|
|
let btnColor = this.getBtnColor(this.main, this.text);
|
2023-10-22 01:08:15 +02:00
|
|
|
if(btnColor != this.text) styles.push(`.btn.btn-primary {\n${this.var("text-color", btnColor.hexFormat())}\n}`);
|
2023-10-05 13:41:55 +02:00
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
return styles.join("\n\n");
|
2023-10-05 13:41:55 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
getBtnColor(main: Color, text: Color) {
|
2023-10-05 13:41:55 +02:00
|
|
|
let white = new Color(1, 1, 1);
|
|
|
|
let black = new Color(0, 0, 0);
|
|
|
|
let cText = main.contrast(text);
|
|
|
|
let cWhite = main.contrast(white);
|
|
|
|
return cWhite > .3 ? white : cText > .3 ? text : black;
|
|
|
|
}
|
|
|
|
|
2023-10-22 01:08:15 +02:00
|
|
|
cMix(color: Color, color2: Color, ratio: number = 1) {
|
|
|
|
let c = color2.mix(color, ratio);
|
|
|
|
return c.hexFormat();
|
|
|
|
}
|
|
|
|
|
|
|
|
cAlpha(color: Color, alpha: number = 1) {
|
2023-10-05 13:41:55 +02:00
|
|
|
let c = color.clone();
|
|
|
|
c.a = alpha;
|
2023-10-22 01:08:15 +02:00
|
|
|
return c.hexFormat();
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
2023-10-09 16:30:25 +02:00
|
|
|
var(name: string, value: string) {
|
|
|
|
return `\t--synergy-${name}: ${value};`;
|
2023-10-05 13:41:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|