104 lines
3.6 KiB
JavaScript
104 lines
3.6 KiB
JavaScript
|
|
/* 客服主题派生——单品牌色 → 7 槽位色板(spec 2026-08-31 §5)。
|
||
|
|
* 注意:此文件与 static/widget.js 内联拷贝必须逐字符一致(空白归一化后),
|
||
|
|
* 由 tests/test_theme_static.py 强制;修改务必两边同步。 */
|
||
|
|
(function (global) {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
var DEFAULT_COLOR = "#066fc0";
|
||
|
|
|
||
|
|
var DEFAULT_PALETTE = {
|
||
|
|
primary: "#066fc0",
|
||
|
|
primaryRgb: "6,111,192",
|
||
|
|
gradientEnd: "#0666bb",
|
||
|
|
deepText: "#14517a",
|
||
|
|
captionText: "#2174ab",
|
||
|
|
softBg: "#f3f9fe",
|
||
|
|
borderSoft: "#bbdcf2"
|
||
|
|
};
|
||
|
|
|
||
|
|
function clampNum(v, lo, hi) {
|
||
|
|
return Math.min(hi, Math.max(lo, v));
|
||
|
|
}
|
||
|
|
|
||
|
|
function hexToRgb(hex) {
|
||
|
|
hex = String(hex || "").replace(/^#/, "");
|
||
|
|
if (hex.length === 3) {
|
||
|
|
hex = hex.split("").map(function (ch) { return ch + ch; }).join("");
|
||
|
|
}
|
||
|
|
if (!/^[0-9a-fA-F]{6}$/.test(hex)) return null;
|
||
|
|
return {
|
||
|
|
r: parseInt(hex.slice(0, 2), 16),
|
||
|
|
g: parseInt(hex.slice(2, 4), 16),
|
||
|
|
b: parseInt(hex.slice(4, 6), 16)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function rgbToHsl(r, g, b) {
|
||
|
|
r /= 255; g /= 255; b /= 255;
|
||
|
|
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||
|
|
var l = (max + min) / 2;
|
||
|
|
var d = max - min;
|
||
|
|
var h = 0, s = 0;
|
||
|
|
if (d !== 0) {
|
||
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||
|
|
if (max === r) h = ((g - b) / d) % 6;
|
||
|
|
else if (max === g) h = (b - r) / d + 2;
|
||
|
|
else h = (r - g) / d + 4;
|
||
|
|
h *= 60;
|
||
|
|
if (h < 0) h += 360;
|
||
|
|
}
|
||
|
|
return { h: h, s: s * 100, l: l * 100 };
|
||
|
|
}
|
||
|
|
|
||
|
|
function toHex2(v) {
|
||
|
|
var s = Math.round(clampNum(v, 0, 255)).toString(16);
|
||
|
|
return s.length < 2 ? "0" + s : s;
|
||
|
|
}
|
||
|
|
|
||
|
|
function hslToHex(h, s, l) {
|
||
|
|
h = ((h % 360) + 360) % 360;
|
||
|
|
s = clampNum(s, 0, 100) / 100;
|
||
|
|
l = clampNum(l, 0, 100) / 100;
|
||
|
|
var c = (1 - Math.abs(2 * l - 1)) * s;
|
||
|
|
var x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
||
|
|
var m = l - c / 2;
|
||
|
|
var rp = 0, gp = 0, bp = 0;
|
||
|
|
if (h < 60) { rp = c; gp = x; }
|
||
|
|
else if (h < 120) { rp = x; gp = c; }
|
||
|
|
else if (h < 180) { gp = c; bp = x; }
|
||
|
|
else if (h < 240) { gp = x; bp = c; }
|
||
|
|
else if (h < 300) { rp = x; bp = c; }
|
||
|
|
else { rp = c; bp = x; }
|
||
|
|
return "#" + toHex2((rp + m) * 255) + toHex2((gp + m) * 255) + toHex2((bp + m) * 255);
|
||
|
|
}
|
||
|
|
|
||
|
|
function derivePalette(color) {
|
||
|
|
try {
|
||
|
|
var rgb = hexToRgb(color);
|
||
|
|
if (!rgb) {
|
||
|
|
if (typeof console !== "undefined" && console.warn) console.warn("[chat-theme] invalid color, fallback:", color);
|
||
|
|
return DEFAULT_PALETTE;
|
||
|
|
}
|
||
|
|
var hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
|
||
|
|
var h = hsl.h, s = hsl.s, l = hsl.l;
|
||
|
|
return {
|
||
|
|
primary: "#" + toHex2(rgb.r) + toHex2(rgb.g) + toHex2(rgb.b),
|
||
|
|
primaryRgb: rgb.r + "," + rgb.g + "," + rgb.b,
|
||
|
|
gradientEnd: hslToHex(h + 2, s, l - 1),
|
||
|
|
deepText: hslToHex(h - 2, s * 0.76, clampNum(l - 24, 28, 40)),
|
||
|
|
captionText: hslToHex(h - 2, s * 0.72, clampNum(l - 10, 40, 52)),
|
||
|
|
softBg: hslToHex(h, s * 0.9, 97.5),
|
||
|
|
borderSoft: hslToHex(h - 2, s * 0.72, 84)
|
||
|
|
};
|
||
|
|
} catch (e) {
|
||
|
|
return DEFAULT_PALETTE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
global.ChatTheme = {
|
||
|
|
derivePalette: derivePalette,
|
||
|
|
DEFAULT_COLOR: DEFAULT_COLOR,
|
||
|
|
DEFAULT_PALETTE: DEFAULT_PALETTE
|
||
|
|
};
|
||
|
|
})(typeof window !== "undefined" ? window : this);
|