Files
admins 702d080a1e New Addon
Google BK
2023-04-23 18:17:03 +07:00

248 lines
7.4 KiB
JavaScript

const themeStyleContainer = document.getElementById("theme");
class Color {
constructor(r = 0, g = 0, b = 0, a = 1) {
this.r = this._clamp(r);
this.g = this._clamp(g);
this.b = this._clamp(b);
this.a = this._clamp(a, 0, 1);
}
static black() {
return new Color(0, 0, 0);
}
static white() {
return new Color(255, 255, 255);
}
static grey() {
return new Color(128, 128, 128);
}
static defaultAccent() {
return new Color(3, 169, 260);
}
static defaultBackgroundLight() {
return Color.white();
}
static defaultBackgroundDark() {
return new Color(26, 26, 26);
}
static defaultBackground() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return Color.defaultBackgroundDark();
} else {
return Color.defaultBackgroundLight();
}
}
static hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
static parse(color) {
const rgb = this.hexToRgb(color);
return new Color(rgb.r, rgb.g, rgb.b);
}
tint(to, amount) {
amount = this._clamp(amount, 0, 1);
const r_new = this.r + (to.r - this.r) * amount;
const g_new = this.g + (to.g - this.g) * amount;
const b_new = this.b + (to.b - this.b) * amount;
const a_new = this.a + (to.a - this.a) * amount;
return new Color(r_new, g_new, b_new, a_new);
}
darken(amount) {
return this.tint(Color.black(), amount);
}
lighten(amount) {
return this.tint(Color.white(), amount);
}
shift(amount) {
return this.luminance() >= 0.5 ? this.darken(amount) : this.lighten(amount);
}
saturate(change) {
const Pr = 0.299;
const Pg = 0.587;
const Pb = 0.114;
const P = Math.sqrt(this.r * this.r * Pr + this.g * this.g * Pg + this.b * this.b * Pb);
const R = this._clamp(P + (this.r - P) * change);
const G = this._clamp(P + (this.g - P) * change);
const B = this._clamp(P + (this.b - P) * change);
return new Color(R, G, B, this.a);
}
_clamp(value, min = 0, max = 255) {
if (value > max) {
return max;
}
if (value < min) {
return min;
}
return value;
}
toCss() {
return "rgba(" + Math.floor(this.r) + "," + Math.floor(this.g) + "," + Math.floor(this.b) + "," + this.a + ")";
}
toHex() {
const componentToHex = (c) => {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
};
return (
"#" + componentToHex(Math.floor(this.r)) + componentToHex(Math.floor(this.g)) + componentToHex(Math.floor(this.b))
);
}
textColor() {
const luma = (0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255;
return luma > 0.53 ? Color.black() : Color.white();
}
luminance() {
const rg = Math.floor(this.r) <= 10 ? this.r / 3294.0 : Math.pow(this.r / 269.0 + 0.0513, 2.4);
const gg = Math.floor(this.g) <= 10 ? this.g / 3294.0 : Math.pow(this.g / 269.0 + 0.0513, 2.4);
const bg = Math.floor(this.b) <= 10 ? this.b / 3294.0 : Math.pow(this.b / 269.0 + 0.0513, 2.4);
return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg;
}
contrast(other) {
let big = this.luminance();
let small = other.luminance();
if (big < small) {
const temp = big;
big = small;
small = temp;
}
return (big + 0.05) / (small + 0.05);
}
withAlpha(alpha) {
return new Color(this.r, this.g, this.b, alpha);
}
// Return a color close to this but also legible against bg_color.
// This is used in some places to make the UI minimally useable even
// if the user picks stupid bg/accent colors.
makeLegible(bg_color) {
let ideal = bg_color.textColor();
let contrast = bg_color.contrast(this);
// This has a bunch of magic constants that were choosen experimentally.
// Don't fuck with them.
let contrast_threshold = 2.5;
if (contrast > contrast_threshold) {
return this;
} else {
const scale = 1 - (contrast - 1) / (contrast_threshold - 1);
return this.tint(ideal, scale * 0.5);
}
}
}
function setColors(background, accent) {
if (!background || (typeof(background) == 'string' && background.length == 0)) {
background = Color.defaultBackground();
} else if (typeof(background) == 'string') {
background = Color.parse(background);
}
if (!accent || (typeof(accent) == 'string' && accent.length == 0)) {
accent = Color.defaultAccent();
} else if (typeof(accent) == 'string') {
accent = Color.parse(accent);
}
themeStyleContainer.dataset.backgroundColor = background.toHex();
themeStyleContainer.dataset.accentColor = accent.toHex();
let text = background.textColor();
accent = accent.makeLegible(background);
let linkAccent = accent.makeLegible(background);
let drop_shadow = Color.black();
let bg_lum = background.luminance();
if (bg_lum < 0.01) {
drop_shadow = Color.white().tint(Color.black(), 0.9);
}
let icon_warn = Color.parse("#f57f17").makeLegible(background);
let input_label = Color.parse("#9e9e9e").makeLegible(background);
let danger_text = Color.parse("#FF0000").tint(text, 0.25).makeLegible(background);
let danger_bg = danger_text.textColor();
let blue_icon = Color.parse("#0D47A1").makeLegible(background);
const styleSheet = {
":root": {
// Cls colors
"--cls-color": text.toCss(),
"--cls-sec-color": text.toCss(),
"--cls-size": "2rem",
"--cls-margin": "1rem",
"--cls-speed": "4s",
// Texts & icons colors
"--helper-text-color": text.tint(background, 0.25).toCss(),
"--text-primary-color": text.shift(0.13).toCss(),
"--text-secondary-color": text.shift(0.26).toCss(),
"--divider-color": text.withAlpha(0.12).toCss(),
"--shadow-color": drop_shadow.withAlpha(0.14).toCss(),
"--icon-color": text.shift(0.13).withAlpha(0.6).toCss(),
"--icon-warn": icon_warn.toCss(),
"--input-label": input_label.toCss(),
"--danger-text": danger_text.toCss(),
"--danger-bg": danger_text.textColor().toCss(),
"--blue-icon": blue_icon.toCss(),
// Accent colors
"--accent-color": accent.toCss(),
"--accent-dark": accent.darken(0.3).toCss(),
"--accent-text-color": accent.textColor().toCss(),
"--accent-focus-color": accent.saturate(1.2).toCss(),
"--accent-hover-color": accent.textColor().toCss(),
"--accent-bg-hover-color": accent.toCss(),
"--accent-link-color": linkAccent.toCss(),
"--progress-bg": accent.tint(background, 0.75).toCss(),
"--accent-ripple": linkAccent.withAlpha(0.2).toCss(),
// Background colors
"--background-color": background.toCss(),
"--background-sidenav-color": background.shift(0.03).toCss(),
"--background-hover": background.shift(0.065),
"--background-modal-color": background.tint(text, 0.02).toCss(),
"--background-primary-color": background.shift(0.02).toCss(),
},
};
const properties = Object.keys(styleSheet).map((selector) => {
const selectorProperties = Object.keys(styleSheet[selector]).map(
(property) => ` ${property}: ${styleSheet[selector][property]};`,
);
return `${selector} {\n${selectorProperties.join("\n")}\n}`;
});
themeStyleContainer.innerHTML = properties.join("\n");
}
setColors(themeStyleContainer.dataset.backgroundColor, themeStyleContainer.dataset.accentColor);