上传站点辅助脚本
Browse files- scripts/color-palettes.js +274 -0
scripts/color-palettes.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Global color palettes generator and watcher
|
| 2 |
+
// - Observes CSS variable --primary-color and theme changes
|
| 3 |
+
// - Generates categorical, sequential, and diverging palettes (OKLCH/OKLab)
|
| 4 |
+
// - Exposes results as CSS variables on :root
|
| 5 |
+
// - Supports variable color counts per palette via CSS vars
|
| 6 |
+
// - Dispatches a 'palettes:updated' CustomEvent after each update
|
| 7 |
+
|
| 8 |
+
(() => {
|
| 9 |
+
const MODE = { cssRoot: document.documentElement };
|
| 10 |
+
|
| 11 |
+
const getCssVar = (name) => {
|
| 12 |
+
try { return getComputedStyle(MODE.cssRoot).getPropertyValue(name).trim(); } catch { return ''; }
|
| 13 |
+
};
|
| 14 |
+
const getIntFromCssVar = (name, fallback) => {
|
| 15 |
+
const raw = getCssVar(name);
|
| 16 |
+
if (!raw) return fallback;
|
| 17 |
+
const v = parseInt(String(raw), 10);
|
| 18 |
+
if (Number.isNaN(v)) return fallback;
|
| 19 |
+
return v;
|
| 20 |
+
};
|
| 21 |
+
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
| 22 |
+
|
| 23 |
+
// Color math (OKLab/OKLCH)
|
| 24 |
+
const srgbToLinear = (u) => (u <= 0.04045 ? u / 12.92 : Math.pow((u + 0.055) / 1.055, 2.4));
|
| 25 |
+
const linearToSrgb = (u) => (u <= 0.0031308 ? 12.92 * u : 1.055 * Math.pow(Math.max(0, u), 1 / 2.4) - 0.055);
|
| 26 |
+
const rgbToOklab = (r, g, b) => {
|
| 27 |
+
const rl = srgbToLinear(r), gl = srgbToLinear(g), bl = srgbToLinear(b);
|
| 28 |
+
const l = Math.cbrt(0.4122214708 * rl + 0.5363325363 * gl + 0.0514459929 * bl);
|
| 29 |
+
const m = Math.cbrt(0.2119034982 * rl + 0.6806995451 * gl + 0.1073969566 * bl);
|
| 30 |
+
const s = Math.cbrt(0.0883024619 * rl + 0.2817188376 * gl + 0.6299787005 * bl);
|
| 31 |
+
const L = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s;
|
| 32 |
+
const a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s;
|
| 33 |
+
const b2 = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s;
|
| 34 |
+
return { L, a, b: b2 };
|
| 35 |
+
};
|
| 36 |
+
const oklabToRgb = (L, a, b) => {
|
| 37 |
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
| 38 |
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
| 39 |
+
const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
|
| 40 |
+
const l = l_ * l_ * l_;
|
| 41 |
+
const m = m_ * m_ * m_;
|
| 42 |
+
const s = s_ * s_ * s_;
|
| 43 |
+
const r = linearToSrgb(+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s);
|
| 44 |
+
const g = linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s);
|
| 45 |
+
const b3 = linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s);
|
| 46 |
+
return { r, g, b: b3 };
|
| 47 |
+
};
|
| 48 |
+
const oklchToOklab = (L, C, hDeg) => { const h = (hDeg * Math.PI) / 180; return { L, a: C * Math.cos(h), b: C * Math.sin(h) }; };
|
| 49 |
+
const oklabToOklch = (L, a, b) => { const C = Math.sqrt(a * a + b * b); let h = Math.atan2(b, a) * 180 / Math.PI; if (h < 0) h += 360; return { L, C, h }; };
|
| 50 |
+
const clamp01 = (x) => Math.min(1, Math.max(0, x));
|
| 51 |
+
const isInGamut = ({ r, g, b }) => r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1;
|
| 52 |
+
const toHex = ({ r, g, b }) => {
|
| 53 |
+
const R = Math.round(clamp01(r) * 255), G = Math.round(clamp01(g) * 255), B = Math.round(clamp01(b) * 255);
|
| 54 |
+
const h = (n) => n.toString(16).padStart(2, '0');
|
| 55 |
+
return `#${h(R)}${h(G)}${h(B)}`.toUpperCase();
|
| 56 |
+
};
|
| 57 |
+
const oklchToHexSafe = (L, C, h) => { let c = C; for (let i = 0; i < 12; i++) { const { a, b } = oklchToOklab(L, c, h); const rgb = oklabToRgb(L, a, b); if (isInGamut(rgb)) return toHex(rgb); c = Math.max(0, c - 0.02); } return toHex(oklabToRgb(L, 0, 0)); };
|
| 58 |
+
const parseCssColorToRgb = (css) => { try { const el = document.createElement('span'); el.style.color = css; document.body.appendChild(el); const cs = getComputedStyle(el).color; document.body.removeChild(el); const m = cs.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i); if (!m) return null; return { r: Number(m[1]) / 255, g: Number(m[2]) / 255, b: Number(m[3]) / 255 }; } catch { return null; } };
|
| 59 |
+
|
| 60 |
+
// Get primary color in OKLCH format to preserve precision
|
| 61 |
+
const getPrimaryOKLCH = () => {
|
| 62 |
+
const css = getCssVar('--primary-color');
|
| 63 |
+
if (!css) return null;
|
| 64 |
+
|
| 65 |
+
// For OKLCH colors, return the exact values without conversion
|
| 66 |
+
if (css.includes('oklch')) {
|
| 67 |
+
const oklchMatch = css.match(/oklch\(([^)]+)\)/);
|
| 68 |
+
if (oklchMatch) {
|
| 69 |
+
const values = oklchMatch[1].split(/\s+/).map(v => parseFloat(v.trim()));
|
| 70 |
+
if (values.length >= 3) {
|
| 71 |
+
const [L, C, h] = values;
|
| 72 |
+
return { L, C, h };
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// For non-OKLCH colors, convert to OKLCH for consistency
|
| 78 |
+
const rgb = parseCssColorToRgb(css);
|
| 79 |
+
if (rgb) {
|
| 80 |
+
const { L, a, b } = rgbToOklab(rgb.r, rgb.g, rgb.b);
|
| 81 |
+
const { C, h } = oklabToOklch(L, a, b);
|
| 82 |
+
return { L, C, h };
|
| 83 |
+
}
|
| 84 |
+
return null;
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
// Keep getPrimaryHex for backward compatibility, but now it converts from OKLCH
|
| 88 |
+
const getPrimaryHex = () => {
|
| 89 |
+
const oklch = getPrimaryOKLCH();
|
| 90 |
+
if (!oklch) return null;
|
| 91 |
+
|
| 92 |
+
const { a, b } = oklchToOklab(oklch.L, oklch.C, oklch.h);
|
| 93 |
+
const rgb = oklabToRgb(oklch.L, a, b);
|
| 94 |
+
return toHex(rgb);
|
| 95 |
+
};
|
| 96 |
+
// No count management via CSS anymore; counts are passed directly to the API
|
| 97 |
+
|
| 98 |
+
const generators = {
|
| 99 |
+
categorical: (baseOKLCH, count) => {
|
| 100 |
+
const { L, C, h } = baseOKLCH;
|
| 101 |
+
const L0 = Math.min(0.85, Math.max(0.4, L));
|
| 102 |
+
const C0 = Math.min(0.35, Math.max(0.1, C || 0.2));
|
| 103 |
+
const total = Math.max(1, Math.min(12, count || 8));
|
| 104 |
+
const hueStep = 360 / total;
|
| 105 |
+
const results = [];
|
| 106 |
+
for (let i = 0; i < total; i++) {
|
| 107 |
+
const hDeg = (h + i * hueStep) % 360;
|
| 108 |
+
const lVar = ((i % 3) - 1) * 0.04;
|
| 109 |
+
results.push(oklchToHexSafe(Math.max(0.4, Math.min(0.85, L0 + lVar)), C0, hDeg));
|
| 110 |
+
}
|
| 111 |
+
return results;
|
| 112 |
+
},
|
| 113 |
+
sequential: (baseOKLCH, count) => {
|
| 114 |
+
const { L, C, h } = baseOKLCH;
|
| 115 |
+
const total = Math.max(1, Math.min(12, count || 8));
|
| 116 |
+
const startL = Math.max(0.25, L - 0.18);
|
| 117 |
+
const endL = Math.min(0.92, L + 0.18);
|
| 118 |
+
const cBase = Math.min(0.33, Math.max(0.08, C * 0.9 + 0.06));
|
| 119 |
+
const out = [];
|
| 120 |
+
for (let i = 0; i < total; i++) {
|
| 121 |
+
const t = total === 1 ? 0 : i / (total - 1);
|
| 122 |
+
const lNow = startL * (1 - t) + endL * t;
|
| 123 |
+
const cNow = cBase * (0.85 + 0.15 * (1 - Math.abs(0.5 - t) * 2));
|
| 124 |
+
out.push(oklchToHexSafe(lNow, cNow, h));
|
| 125 |
+
}
|
| 126 |
+
return out;
|
| 127 |
+
},
|
| 128 |
+
diverging: (baseOKLCH, count) => {
|
| 129 |
+
const { L, C, h } = baseOKLCH;
|
| 130 |
+
const total = Math.max(1, Math.min(12, count || 8));
|
| 131 |
+
|
| 132 |
+
// Left endpoint: EXACT primary color (no darkening)
|
| 133 |
+
const leftLab = oklchToOklab(L, C, h);
|
| 134 |
+
// Right endpoint: complement with same L and similar C (clamped safe)
|
| 135 |
+
const compH = (h + 180) % 360;
|
| 136 |
+
const cSafe = Math.min(0.35, Math.max(0.08, C));
|
| 137 |
+
const rightLab = oklchToOklab(L, cSafe, compH);
|
| 138 |
+
const whiteLab = { L: 0.98, a: 0, b: 0 }; // center near‑white
|
| 139 |
+
|
| 140 |
+
const hexFromOKLab = (L, a, b) => toHex(oklabToRgb(L, a, b));
|
| 141 |
+
const lerp = (a, b, t) => a + (b - a) * t;
|
| 142 |
+
const lerpOKLabHex = (A, B, t) => hexFromOKLab(lerp(A.L, B.L, t), lerp(A.a, B.a, t), lerp(A.b, B.b, t));
|
| 143 |
+
|
| 144 |
+
const out = [];
|
| 145 |
+
if (total % 2 === 1) {
|
| 146 |
+
const nSide = (total - 1) >> 1; // items on each side
|
| 147 |
+
// Left side: include left endpoint exactly at index 0
|
| 148 |
+
for (let i = 0; i < nSide; i++) {
|
| 149 |
+
const t = nSide <= 1 ? 0 : (i / (nSide - 1)); // 0 .. 1
|
| 150 |
+
// Move from leftLab to a value close (but not equal) to white; ensure last before center is lighter
|
| 151 |
+
const tt = t * 0.9; // keep some distance from pure white before center
|
| 152 |
+
out.push(lerpOKLabHex(leftLab, whiteLab, tt));
|
| 153 |
+
}
|
| 154 |
+
// Center
|
| 155 |
+
out.push(hexFromOKLab(whiteLab.L, whiteLab.a, whiteLab.b));
|
| 156 |
+
// Right side: start near white and end EXACTLY at rightLab
|
| 157 |
+
for (let i = 0; i < nSide; i++) {
|
| 158 |
+
const t = nSide <= 1 ? 1 : ((i + 1) / nSide); // (1/n)..1
|
| 159 |
+
const tt = Math.max(0.1, t); // avoid starting at pure white
|
| 160 |
+
out.push(lerpOKLabHex(whiteLab, rightLab, tt));
|
| 161 |
+
}
|
| 162 |
+
// Ensure first and last are exact endpoints
|
| 163 |
+
if (out.length) { out[0] = hexFromOKLab(leftLab.L, leftLab.a, leftLab.b); out[out.length - 1] = hexFromOKLab(rightLab.L, rightLab.a, rightLab.b); }
|
| 164 |
+
} else {
|
| 165 |
+
const nSide = total >> 1;
|
| 166 |
+
// Left half including left endpoint, approaching white but not reaching it
|
| 167 |
+
for (let i = 0; i < nSide; i++) {
|
| 168 |
+
const t = nSide <= 1 ? 0 : (i / (nSide - 1)); // 0 .. 1
|
| 169 |
+
const tt = t * 0.9;
|
| 170 |
+
out.push(lerpOKLabHex(leftLab, whiteLab, tt));
|
| 171 |
+
}
|
| 172 |
+
// Right half: mirror from near white to exact right endpoint
|
| 173 |
+
for (let i = 0; i < nSide; i++) {
|
| 174 |
+
const t = nSide <= 1 ? 1 : ((i + 1) / nSide); // (1/n)..1
|
| 175 |
+
const tt = Math.max(0.1, t);
|
| 176 |
+
out.push(lerpOKLabHex(whiteLab, rightLab, tt));
|
| 177 |
+
}
|
| 178 |
+
if (out.length) { out[0] = hexFromOKLab(leftLab.L, leftLab.a, leftLab.b); out[out.length - 1] = hexFromOKLab(rightLab.L, rightLab.a, rightLab.b); }
|
| 179 |
+
}
|
| 180 |
+
return out;
|
| 181 |
+
}
|
| 182 |
+
};
|
| 183 |
+
|
| 184 |
+
let lastSignature = '';
|
| 185 |
+
|
| 186 |
+
const updatePalettes = () => {
|
| 187 |
+
const primaryOKLCH = getPrimaryOKLCH();
|
| 188 |
+
const primaryHex = getPrimaryHex();
|
| 189 |
+
const signature = `${primaryOKLCH?.L},${primaryOKLCH?.C},${primaryOKLCH?.h}`;
|
| 190 |
+
if (signature === lastSignature) return;
|
| 191 |
+
lastSignature = signature;
|
| 192 |
+
try { document.dispatchEvent(new CustomEvent('palettes:updated', { detail: { primary: primaryHex, primaryOKLCH } })); } catch { }
|
| 193 |
+
};
|
| 194 |
+
|
| 195 |
+
const bootstrap = () => {
|
| 196 |
+
// Initial setup - only run once on page load
|
| 197 |
+
updatePalettes();
|
| 198 |
+
|
| 199 |
+
// Observer will handle all subsequent changes
|
| 200 |
+
const mo = new MutationObserver(() => updatePalettes());
|
| 201 |
+
mo.observe(MODE.cssRoot, { attributes: true, attributeFilter: ['style', 'data-theme'] });
|
| 202 |
+
|
| 203 |
+
// Utility: choose high-contrast (or softened) text style against an arbitrary background color
|
| 204 |
+
const pickTextStyleForBackground = (bgCss, opts = {}) => {
|
| 205 |
+
const cssRoot = document.documentElement;
|
| 206 |
+
const getCssVar = (name) => {
|
| 207 |
+
try { return getComputedStyle(cssRoot).getPropertyValue(name).trim(); } catch { return ''; }
|
| 208 |
+
};
|
| 209 |
+
const resolveCssToRgb01 = (css) => {
|
| 210 |
+
const rgb = parseCssColorToRgb(css);
|
| 211 |
+
if (!rgb) return null;
|
| 212 |
+
return rgb; // already 0..1
|
| 213 |
+
};
|
| 214 |
+
const mixRgb01 = (a, b, t) => ({ r: a.r * (1 - t) + b.r * t, g: a.g * (1 - t) + b.g * t, b: a.b * (1 - t) + b.b * t });
|
| 215 |
+
const relLum = (rgb) => {
|
| 216 |
+
const f = (u) => srgbToLinear(u);
|
| 217 |
+
return 0.2126 * f(rgb.r) + 0.7152 * f(rgb.g) + 0.0722 * f(rgb.b);
|
| 218 |
+
};
|
| 219 |
+
const contrast = (fg, bg) => {
|
| 220 |
+
const L1 = relLum(fg), L2 = relLum(bg); const a = Math.max(L1, L2), b = Math.min(L1, L2);
|
| 221 |
+
return (a + 0.05) / (b + 0.05);
|
| 222 |
+
};
|
| 223 |
+
try {
|
| 224 |
+
const bg = resolveCssToRgb01(bgCss);
|
| 225 |
+
if (!bg) return { fill: getCssVar('--text-color') || '#000', stroke: 'var(--transparent-page-contrast)', strokeWidth: 1 };
|
| 226 |
+
const candidatesCss = [getCssVar('--text-color') || '#111', getCssVar('--on-primary') || '#0f1115', '#000', '#fff'];
|
| 227 |
+
const candidates = candidatesCss
|
| 228 |
+
.map(css => ({ css, rgb: resolveCssToRgb01(css) }))
|
| 229 |
+
.filter(x => !!x.rgb);
|
| 230 |
+
// Pick the max contrast
|
| 231 |
+
let best = candidates[0]; let bestCR = contrast(best.rgb, bg);
|
| 232 |
+
for (let i = 1; i < candidates.length; i++) {
|
| 233 |
+
const cr = contrast(candidates[i].rgb, bg);
|
| 234 |
+
if (cr > bestCR) { best = candidates[i]; bestCR = cr; }
|
| 235 |
+
}
|
| 236 |
+
// Optional softening via blend factor (0..1), blending towards muted color
|
| 237 |
+
const blend = Math.min(1, Math.max(0, Number(opts.blend || 0)));
|
| 238 |
+
let finalRgb = best.rgb;
|
| 239 |
+
if (blend > 0) {
|
| 240 |
+
const mutedCss = getCssVar('--muted-color') || (getCssVar('--text-color') || '#111');
|
| 241 |
+
const mutedRgb = resolveCssToRgb01(mutedCss) || best.rgb;
|
| 242 |
+
finalRgb = mixRgb01(best.rgb, mutedRgb, blend);
|
| 243 |
+
}
|
| 244 |
+
const haloStrength = Math.min(1, Math.max(0, Number(opts.haloStrength == null ? 0.5 : opts.haloStrength)));
|
| 245 |
+
const stroke = (best.css === '#000' || best.css.toLowerCase() === 'black') ? `rgba(255,255,255,${0.30 + 0.40 * haloStrength})` : `rgba(0,0,0,${0.30 + 0.30 * haloStrength})`;
|
| 246 |
+
return { fill: toHex(finalRgb), stroke, strokeWidth: (opts.haloWidth == null ? 1 : Number(opts.haloWidth)) };
|
| 247 |
+
} catch {
|
| 248 |
+
return { fill: getCssVar('--text-color') || '#000', stroke: 'var(--transparent-page-contrast)', strokeWidth: 1 };
|
| 249 |
+
}
|
| 250 |
+
};
|
| 251 |
+
window.ColorPalettes = {
|
| 252 |
+
refresh: updatePalettes,
|
| 253 |
+
notify: () => { try { const primaryOKLCH = getPrimaryOKLCH(); const primaryHex = getPrimaryHex(); document.dispatchEvent(new CustomEvent('palettes:updated', { detail: { primary: primaryHex, primaryOKLCH } })); } catch { } },
|
| 254 |
+
getPrimary: () => getPrimaryHex(),
|
| 255 |
+
getPrimaryOKLCH: () => getPrimaryOKLCH(),
|
| 256 |
+
getColors: (key, count = 6) => {
|
| 257 |
+
const primaryOKLCH = getPrimaryOKLCH();
|
| 258 |
+
if (!primaryOKLCH) return [];
|
| 259 |
+
const total = Math.max(1, Math.min(12, Number(count) || 6));
|
| 260 |
+
if (key === 'categorical') return generators.categorical(primaryOKLCH, total);
|
| 261 |
+
if (key === 'sequential') return generators.sequential(primaryOKLCH, total);
|
| 262 |
+
if (key === 'diverging') return generators.diverging(primaryOKLCH, total);
|
| 263 |
+
return [];
|
| 264 |
+
},
|
| 265 |
+
getTextStyleForBackground: (bgCss, opts) => pickTextStyleForBackground(bgCss, opts || {}),
|
| 266 |
+
chooseReadableText: (bgCss, opts) => pickTextStyleForBackground(bgCss, opts || {})
|
| 267 |
+
};
|
| 268 |
+
};
|
| 269 |
+
|
| 270 |
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
|
| 271 |
+
else bootstrap();
|
| 272 |
+
})();
|
| 273 |
+
|
| 274 |
+
|