feat: jc 引擎主线搬入 app/engine(315 测试基线全绿:320 去掉 admin 5 用例)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/* 客服主题派生——单品牌色 → 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);
|
||||
Reference in New Issue
Block a user