535 lines
21 KiB
JavaScript
535 lines
21 KiB
JavaScript
/* global module, wx */
|
||
|
||
/**
|
||
* 小程序主题预设与 shared/Web 保持同一套内部值和顺序:
|
||
* 1. 这里保留运行时可直接消费的扁平对象,避免小程序侧额外依赖 TS 共享模块;
|
||
* 2. 若 shared 新增主题,这里也要同步补齐,保证多端主题配置可互通;
|
||
* 3. shell cursor 仍按当前小程序规则实时推导,因此这里只保留 bg/text 基色。
|
||
*/
|
||
const THEME_PRESETS = {
|
||
tide: {
|
||
dark: { bg: "#192b4d", text: "#e6f0ff", accent: "#5bd2ff" },
|
||
light: { bg: "#e6f0ff", text: "#192b4d", accent: "#3D86FF" },
|
||
shellDark: { bg: "#192b4d", text: "#e6f0ff", cursor: "#5bd2ff" },
|
||
shellLight: { bg: "#e6f0ff", text: "#192b4d", cursor: "#3D86FF" }
|
||
},
|
||
暮砂: {
|
||
dark: { bg: "#3D405B", text: "#F4F1DE", accent: "#81B29A" },
|
||
light: { bg: "#F4F1DE", text: "#3D405B", accent: "#E07A5F" },
|
||
shellDark: { bg: "#3D405B", text: "#F4F1DE", cursor: "#81B29A" },
|
||
shellLight: { bg: "#F4F1DE", text: "#3D405B", cursor: "#E07A5F" }
|
||
},
|
||
霓潮: {
|
||
dark: { bg: "#073B4C", text: "#FFD166", accent: "#06D6A0" },
|
||
light: { bg: "#FFD166", text: "#073B4C", accent: "#EF476F" },
|
||
shellDark: { bg: "#073B4C", text: "#FFD166", cursor: "#06D6A0" },
|
||
shellLight: { bg: "#FFD166", text: "#073B4C", cursor: "#EF476F" }
|
||
},
|
||
苔暮: {
|
||
dark: { bg: "#282C75", text: "#A8B868", accent: "#7A71E4" },
|
||
light: { bg: "#A8B868", text: "#282C75", accent: "#4E4CC3" },
|
||
shellDark: { bg: "#282C75", text: "#A8B868", cursor: "#7A71E4" },
|
||
shellLight: { bg: "#A8B868", text: "#282C75", cursor: "#4E4CC3" }
|
||
},
|
||
焰岩: {
|
||
dark: { bg: "#0F4C5C", text: "#FB8B24", accent: "#E36414" },
|
||
light: { bg: "#FB8B24", text: "#0F4C5C", accent: "#CB4721" },
|
||
shellDark: { bg: "#0F4C5C", text: "#FB8B24", cursor: "#E36414" },
|
||
shellLight: { bg: "#FB8B24", text: "#0F4C5C", cursor: "#CB4721" }
|
||
},
|
||
岩陶: {
|
||
dark: { bg: "#283D3B", text: "#EDDDD4", accent: "#E9B5AF" },
|
||
light: { bg: "#EDDDD4", text: "#283D3B", accent: "#D99185" },
|
||
shellDark: { bg: "#283D3B", text: "#EDDDD4", cursor: "#E9B5AF" },
|
||
shellLight: { bg: "#EDDDD4", text: "#283D3B", cursor: "#D99185" }
|
||
},
|
||
靛雾: {
|
||
dark: { bg: "#292281", text: "#F1F0CD", accent: "#9D96BA" },
|
||
light: { bg: "#F1F0CD", text: "#292281", accent: "#4A3BA6" },
|
||
shellDark: { bg: "#292281", text: "#F1F0CD", cursor: "#9D96BA" },
|
||
shellLight: { bg: "#F1F0CD", text: "#292281", cursor: "#4A3BA6" }
|
||
},
|
||
绛霓: {
|
||
dark: { bg: "#3A0CA3", text: "#4CC9F0", accent: "#4895EF" },
|
||
light: { bg: "#4CC9F0", text: "#3A0CA3", accent: "#F72585" },
|
||
shellDark: { bg: "#3A0CA3", text: "#4CC9F0", cursor: "#4895EF" },
|
||
shellLight: { bg: "#4CC9F0", text: "#3A0CA3", cursor: "#F72585" }
|
||
},
|
||
玫蓝: {
|
||
dark: { bg: "#3D1F94", text: "#629FEB", accent: "#D06A79" },
|
||
light: { bg: "#629FEB", text: "#3D1F94", accent: "#D06A79" },
|
||
shellDark: { bg: "#3D1F94", text: "#629FEB", cursor: "#567BE3" },
|
||
shellLight: { bg: "#629FEB", text: "#3D1F94", cursor: "#D06A79" }
|
||
},
|
||
珊湾: {
|
||
dark: { bg: "#37615B", text: "#52DFDD", accent: "#EC5B57" },
|
||
light: { bg: "#52DFDD", text: "#37615B", accent: "#EC5B57" },
|
||
shellDark: { bg: "#37615B", text: "#52DFDD", cursor: "#44B0AB" },
|
||
shellLight: { bg: "#52DFDD", text: "#37615B", cursor: "#EC5B57" }
|
||
},
|
||
苔荧: {
|
||
dark: { bg: "#252157", text: "#D1D942", accent: "#99A32C" },
|
||
light: { bg: "#D1D942", text: "#252157", accent: "#909636" },
|
||
shellDark: { bg: "#252157", text: "#D1D942", cursor: "#C2CB37" },
|
||
shellLight: { bg: "#D1D942", text: "#252157", cursor: "#909636" }
|
||
},
|
||
铜暮: {
|
||
dark: { bg: "#1A375A", text: "#E6B030", accent: "#B27225" },
|
||
light: { bg: "#E6B030", text: "#1A375A", accent: "#B27225" },
|
||
shellDark: { bg: "#1A375A", text: "#E6B030", cursor: "#D99F27" },
|
||
shellLight: { bg: "#E6B030", text: "#1A375A", cursor: "#B27225" }
|
||
},
|
||
炽潮: {
|
||
dark: { bg: "#125554", text: "#F55054", accent: "#C43133" },
|
||
light: { bg: "#F55054", text: "#125554", accent: "#AA3E40" },
|
||
shellDark: { bg: "#125554", text: "#F55054", cursor: "#E94347" },
|
||
shellLight: { bg: "#F55054", text: "#125554", cursor: "#AA3E40" }
|
||
},
|
||
藕夜: {
|
||
dark: { bg: "#322F4F", text: "#DBDD85", accent: "#554C93" },
|
||
light: { bg: "#DBDD85", text: "#322F4F", accent: "#D5DB74" },
|
||
shellDark: { bg: "#322F4F", text: "#DBDD85", cursor: "#C8CF67" },
|
||
shellLight: { bg: "#DBDD85", text: "#322F4F", cursor: "#554C93" }
|
||
},
|
||
沙海: {
|
||
dark: { bg: "#2B3B51", text: "#E2D075", accent: "#3F7690" },
|
||
light: { bg: "#E2D075", text: "#2B3B51", accent: "#355971" },
|
||
shellDark: { bg: "#2B3B51", text: "#E2D075", cursor: "#DBAA5F" },
|
||
shellLight: { bg: "#E2D075", text: "#2B3B51", cursor: "#3F7690" }
|
||
},
|
||
珀岚: {
|
||
dark: { bg: "#274D4C", text: "#E79094", accent: "#2F9595" },
|
||
light: { bg: "#E79094", text: "#274D4C", accent: "#2B7171" },
|
||
shellDark: { bg: "#274D4C", text: "#E79094", cursor: "#EC7578" },
|
||
shellLight: { bg: "#E79094", text: "#274D4C", cursor: "#2F9595" }
|
||
},
|
||
炫虹: {
|
||
dark: { bg: "#8338EC", text: "#FFBE0B", accent: "#FF006E" },
|
||
light: { bg: "#FFBE0B", text: "#8338EC", accent: "#FD2B3B" },
|
||
shellDark: { bg: "#8338EC", text: "#FFBE0B", cursor: "#3A86FF" },
|
||
shellLight: { bg: "#FFBE0B", text: "#8338EC", cursor: "#FF006E" }
|
||
},
|
||
鎏霓: {
|
||
dark: { bg: "#7550D5", text: "#F3D321", accent: "#A2529A" },
|
||
light: { bg: "#F3D321", text: "#7550D5", accent: "#D67039" },
|
||
shellDark: { bg: "#7550D5", text: "#F3D321", cursor: "#476CEF" },
|
||
shellLight: { bg: "#F3D321", text: "#7550D5", cursor: "#A2529A" }
|
||
},
|
||
珊汐: {
|
||
dark: { bg: "#7F9E96", text: "#FB5860", accent: "#3DCAC5" },
|
||
light: { bg: "#FB5860", text: "#7F9E96", accent: "#F2292C" },
|
||
shellDark: { bg: "#7F9E96", text: "#FB5860", cursor: "#3DCAC5" },
|
||
shellLight: { bg: "#FB5860", text: "#7F9E96", cursor: "#F2292C" }
|
||
},
|
||
黛苔: {
|
||
dark: { bg: "#2F2E3B", text: "#E7E8D6", accent: "#788031" },
|
||
light: { bg: "#E7E8D6", text: "#2F2E3B", accent: "#788031" },
|
||
shellDark: { bg: "#2F2E3B", text: "#E7E8D6", cursor: "#949D3A" },
|
||
shellLight: { bg: "#E7E8D6", text: "#2F2E3B", cursor: "#788031" }
|
||
},
|
||
霜绯: {
|
||
dark: { bg: "#293B3B", text: "#ECD7D8", accent: "#1D7575" },
|
||
light: { bg: "#ECD7D8", text: "#293B3B", accent: "#993333" },
|
||
shellDark: { bg: "#293B3B", text: "#ECD7D8", cursor: "#BD3C3D" },
|
||
shellLight: { bg: "#ECD7D8", text: "#293B3B", cursor: "#993333" }
|
||
}
|
||
};
|
||
|
||
const DEFAULT_UI = THEME_PRESETS.tide.dark;
|
||
const SHELL_ACCENT_BLEND_T = 0.64;
|
||
const DEFAULT_SHELL = {
|
||
...THEME_PRESETS.tide.shellDark,
|
||
cursor: pickShellAccentColor(THEME_PRESETS.tide.shellDark.bg, THEME_PRESETS.tide.shellDark.text)
|
||
};
|
||
const DEFAULT_SHELL_FONT_FAMILY = 'JetBrains Mono, "SFMono-Regular", Menlo, monospace';
|
||
const DEFAULT_SHELL_FONT_SIZE = 15;
|
||
const DEFAULT_SHELL_LINE_HEIGHT = 1.4;
|
||
const DEFAULT_NAVIGATION_BAR_THEME = {
|
||
backgroundColor: DEFAULT_UI.bg,
|
||
frontColor: "#ffffff"
|
||
};
|
||
/**
|
||
* 小程序终端当前采用“固定 cell 网格 + 自绘 caret”模型。
|
||
* 因此这里只允许已验证度量稳定的终端字体;比例字体或中英文度量不稳定的字体
|
||
* 会导致字间空隙拉大、光标横向偏移。
|
||
*/
|
||
const TERMINAL_SAFE_FONT_OPTIONS = [
|
||
{
|
||
label: "等宽默认",
|
||
value: 'ui-monospace, "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace'
|
||
},
|
||
{
|
||
label: "JetBrains Mono",
|
||
value: '"JetBrains Mono", "SFMono-Regular", Menlo, Consolas, monospace'
|
||
},
|
||
{ label: "SF Mono", value: '"SFMono-Regular", Menlo, monospace' },
|
||
{ label: "Menlo", value: "Menlo, Monaco, monospace" },
|
||
{ label: "Monaco", value: "Monaco, Menlo, monospace" },
|
||
{ label: "Consolas", value: 'Consolas, "Courier New", monospace' },
|
||
{
|
||
label: "Roboto Mono",
|
||
value: '"Roboto Mono", "SFMono-Regular", Menlo, Consolas, monospace'
|
||
}
|
||
];
|
||
const TERMINAL_SAFE_FONT_VALUE_SET = new Set(
|
||
TERMINAL_SAFE_FONT_OPTIONS.map((item) =>
|
||
String(item.value || "")
|
||
.trim()
|
||
.toLowerCase()
|
||
)
|
||
);
|
||
|
||
function clampChannel(value) {
|
||
if (!Number.isFinite(value)) return 0;
|
||
if (value < 0) return 0;
|
||
if (value > 255) return 255;
|
||
return Math.round(value);
|
||
}
|
||
|
||
function clampNumber(value, min, max, fallback) {
|
||
const parsed = Number(value);
|
||
if (!Number.isFinite(parsed)) return fallback;
|
||
if (parsed < min) return min;
|
||
if (parsed > max) return max;
|
||
return parsed;
|
||
}
|
||
|
||
function sanitizeCssToken(value, fallback) {
|
||
const raw = String(value == null ? "" : value)
|
||
.replace(/[;\n\r]/g, " ")
|
||
.trim();
|
||
return raw || fallback;
|
||
}
|
||
|
||
function normalizeTerminalFontFamily(value) {
|
||
const raw = sanitizeCssToken(value, DEFAULT_SHELL_FONT_FAMILY);
|
||
return TERMINAL_SAFE_FONT_VALUE_SET.has(raw.toLowerCase()) ? raw : DEFAULT_SHELL_FONT_FAMILY;
|
||
}
|
||
|
||
function hexToRgb(hex) {
|
||
const raw = String(hex || "").trim();
|
||
const match = raw.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
||
if (!match) return null;
|
||
const normalized =
|
||
match[1].length === 3
|
||
? match[1]
|
||
.split("")
|
||
.map((ch) => ch + ch)
|
||
.join("")
|
||
: match[1];
|
||
return {
|
||
r: parseInt(normalized.slice(0, 2), 16),
|
||
g: parseInt(normalized.slice(2, 4), 16),
|
||
b: parseInt(normalized.slice(4, 6), 16)
|
||
};
|
||
}
|
||
|
||
function rgbToHex(rgb) {
|
||
const toHex = (value) => clampChannel(value).toString(16).padStart(2, "0");
|
||
return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
|
||
}
|
||
|
||
function hexToRgba(hex, alpha, fallback) {
|
||
const rgb = hexToRgb(hex);
|
||
if (!rgb) return fallback;
|
||
const normalizedAlpha = clampNumber(alpha, 0, 1, 1);
|
||
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${normalizedAlpha})`;
|
||
}
|
||
|
||
function mixHex(a, b, t) {
|
||
const left = hexToRgb(a);
|
||
const right = hexToRgb(b);
|
||
if (!left || !right) return b;
|
||
return rgbToHex({
|
||
r: left.r + (right.r - left.r) * t,
|
||
g: left.g + (right.g - left.g) * t,
|
||
b: left.b + (right.b - left.b) * t
|
||
});
|
||
}
|
||
|
||
function pickBtnColor(bg, text) {
|
||
return mixHex(bg, text, 0.72);
|
||
}
|
||
|
||
/**
|
||
* Shell 强调色与 Web/shared 规则保持一致:
|
||
* 1. 不直接复用 UI accent,避免终端层级过亮;
|
||
* 2. 在 shell 背景和前景之间取“中间偏前景”的颜色;
|
||
* 3. 这样终端按钮、光标和状态提示可以跟随 shell 主体变化。
|
||
*/
|
||
function pickShellAccentColor(bg, text) {
|
||
return mixHex(bg, text, SHELL_ACCENT_BLEND_T);
|
||
}
|
||
|
||
function resolveRelativeLuminance(hex) {
|
||
const rgb = hexToRgb(hex);
|
||
if (!rgb) return 0;
|
||
const channelToLinear = (value) => {
|
||
const normalized = clampChannel(value) / 255;
|
||
if (normalized <= 0.03928) {
|
||
return normalized / 12.92;
|
||
}
|
||
return ((normalized + 0.055) / 1.055) ** 2.4;
|
||
};
|
||
return channelToLinear(rgb.r) * 0.2126 + channelToLinear(rgb.g) * 0.7152 + channelToLinear(rgb.b) * 0.0722;
|
||
}
|
||
|
||
/**
|
||
* 解析原生导航栏配色:
|
||
* 1. backgroundColor 跟随当前 UI 背景色;
|
||
* 2. frontColor 仅允许微信支持的 #ffffff / #000000,两者按背景亮度择优。
|
||
*/
|
||
function resolveNavigationBarTheme(settings) {
|
||
const palette = resolveRuntimeTheme(settings);
|
||
const luminance = resolveRelativeLuminance(palette.bg);
|
||
return {
|
||
backgroundColor: palette.bg,
|
||
frontColor: luminance >= 0.42 ? "#000000" : "#ffffff"
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 将当前主题同步到微信原生导航栏,解决“页面工具栏上方颜色锁定”的问题。
|
||
* 调用失败时静默返回,避免在单测或非微信运行环境中抛错。
|
||
*/
|
||
function applyNavigationBarTheme(settings, wxLike) {
|
||
const theme = resolveNavigationBarTheme(settings);
|
||
const api = wxLike || (typeof wx !== "undefined" ? wx : null);
|
||
if (!api || typeof api.setNavigationBarColor !== "function") {
|
||
return theme;
|
||
}
|
||
try {
|
||
api.setNavigationBarColor({
|
||
frontColor: theme.frontColor,
|
||
backgroundColor: theme.backgroundColor,
|
||
animation: {
|
||
duration: 0,
|
||
timingFunc: "linear"
|
||
}
|
||
});
|
||
} catch {
|
||
return theme;
|
||
}
|
||
return theme;
|
||
}
|
||
|
||
function getPreset(preset) {
|
||
const key = String(preset || "tide");
|
||
return THEME_PRESETS[key] || THEME_PRESETS.tide;
|
||
}
|
||
|
||
function getUiThemeVariant(preset, mode) {
|
||
const entry = getPreset(preset);
|
||
return mode === "light" ? entry.light : entry.dark;
|
||
}
|
||
|
||
function getShellThemeVariant(preset, mode) {
|
||
const entry = getPreset(preset);
|
||
const variant = mode === "light" ? entry.shellLight : entry.shellDark;
|
||
return {
|
||
...variant,
|
||
cursor: pickShellAccentColor(variant.bg, variant.text)
|
||
};
|
||
}
|
||
|
||
function applyUiThemeSelection(form) {
|
||
const source = { ...(form || {}) };
|
||
const mode = source.uiThemeMode === "light" ? "light" : "dark";
|
||
const variant = getUiThemeVariant(source.uiThemePreset, mode);
|
||
source.uiBgColor = variant.bg;
|
||
source.uiTextColor = variant.text;
|
||
source.uiAccentColor = variant.accent;
|
||
source.uiBtnColor = pickBtnColor(variant.bg, variant.text);
|
||
return source;
|
||
}
|
||
|
||
function applyShellThemeSelection(form) {
|
||
const source = { ...(form || {}) };
|
||
const mode = source.shellThemeMode === "light" ? "light" : "dark";
|
||
const variant = getShellThemeVariant(source.shellThemePreset, mode);
|
||
source.shellBgColor = variant.bg;
|
||
source.shellTextColor = variant.text;
|
||
source.shellAccentColor = pickShellAccentColor(variant.bg, variant.text);
|
||
return source;
|
||
}
|
||
|
||
function resolveRuntimeTheme(settings) {
|
||
const s = settings || {};
|
||
const uiBg = s.uiBgColor || DEFAULT_UI.bg;
|
||
const uiText = s.uiTextColor || DEFAULT_UI.text;
|
||
const shellBg = s.shellBgColor || DEFAULT_SHELL.bg;
|
||
const shellText = s.shellTextColor || DEFAULT_SHELL.text;
|
||
return {
|
||
bg: uiBg,
|
||
text: uiText,
|
||
accent: s.uiAccentColor || DEFAULT_UI.accent,
|
||
btn: s.uiBtnColor || pickBtnColor(uiBg, uiText),
|
||
shellBg,
|
||
shellText,
|
||
shellAccent: s.shellAccentColor || pickShellAccentColor(shellBg, shellText),
|
||
shellFontFamily: normalizeTerminalFontFamily(s.shellFontFamily),
|
||
shellFontSize: clampNumber(s.shellFontSize, 12, 22, DEFAULT_SHELL_FONT_SIZE),
|
||
shellLineHeight: clampNumber(s.shellLineHeight, 1, 2, DEFAULT_SHELL_LINE_HEIGHT)
|
||
};
|
||
}
|
||
|
||
function resolveButtonTokens(palette) {
|
||
return {
|
||
btnBorder: mixHex(palette.bg, palette.btn, 0.68),
|
||
btnBorderStrong: mixHex(palette.bg, palette.btn, 0.84),
|
||
btnBg: mixHex(palette.bg, palette.btn, 0.2),
|
||
btnBgStrong: mixHex(palette.bg, palette.btn, 0.34),
|
||
btnBgActive: mixHex(palette.bg, palette.btn, 0.26),
|
||
btnText: mixHex(palette.text, palette.btn, 0.18),
|
||
btnDangerBorder: mixHex(palette.bg, palette.accent, 0.8),
|
||
btnDangerBg: mixHex(palette.bg, palette.accent, 0.22),
|
||
chipBg: mixHex(palette.bg, palette.accent, 0.28),
|
||
chipText: palette.text,
|
||
accentDivider: hexToRgba(palette.accent, 0.6, "rgba(91, 210, 255, 0.6)"),
|
||
switchOnBg: mixHex(palette.bg, palette.btn, 0.58),
|
||
switchOffBg: mixHex(palette.bg, palette.text, 0.24),
|
||
switchKnob: mixHex(palette.text, "#ffffff", 0.45),
|
||
iconBtnBg: mixHex(palette.bg, palette.btn, 0.14),
|
||
iconBtnBgStrong: mixHex(palette.bg, palette.btn, 0.24),
|
||
accentBg: mixHex(palette.bg, palette.accent, 0.18),
|
||
accentBgStrong: mixHex(palette.bg, palette.accent, 0.32),
|
||
accentBorder: mixHex(palette.bg, palette.accent, 0.72),
|
||
accentRing: hexToRgba(palette.accent, 0.22, "rgba(91, 210, 255, 0.22)"),
|
||
accentShadow: hexToRgba(palette.accent, 0.28, "rgba(91, 210, 255, 0.28)"),
|
||
shellBtnBg: mixHex(palette.shellBg, palette.shellAccent, 0.42),
|
||
shellBtnText: mixHex(palette.shellText, palette.shellAccent, 0.5),
|
||
shellAccentBg: mixHex(palette.shellBg, palette.shellAccent, 0.18),
|
||
shellAccentBgStrong: mixHex(palette.shellBg, palette.shellAccent, 0.32),
|
||
shellAccentBorder: mixHex(palette.shellBg, palette.shellAccent, 0.74),
|
||
shellAccentRing: hexToRgba(palette.shellAccent, 0.24, "rgba(156, 169, 191, 0.24)"),
|
||
shellAccentShadow: hexToRgba(palette.shellAccent, 0.3, "rgba(156, 169, 191, 0.3)"),
|
||
terminalTouchToolsBg: hexToRgba(palette.shellBg, 0.8, "rgba(25, 43, 77, 0.8)")
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 主界面卡片与 about 卡片共用同一套表面 token:
|
||
* 1. 浅色模式下边框直接从当前 text 推导,避免继续沿用深色基线;
|
||
* 2. 面板底色保持轻微前景混合,保证和页面背景拉开层次;
|
||
* 3. 阴影也跟随当前强调色派生,后续其他页面可直接复用。
|
||
*/
|
||
function resolveSurfaceTokens(palette) {
|
||
return {
|
||
surface: mixHex(palette.bg, palette.text, 0.05),
|
||
surfaceBorder: hexToRgba(palette.text, 0.18, "rgba(230, 240, 255, 0.18)"),
|
||
surfaceShadow: hexToRgba(palette.accent, 0.18, "rgba(91, 210, 255, 0.18)")
|
||
};
|
||
}
|
||
|
||
/**
|
||
* About 页面虽然布局更自由,但配色仍应跟随“界面配置”:
|
||
* 1. 面板底色从 bg/text 混合得到,避免再写死一套米白主题;
|
||
* 2. 强调色继续复用当前 UI accent,保证切换主题后 about 也同步变;
|
||
* 3. 背景光斑只作为氛围层,仍从当前主题色推导,不脱离主界面。
|
||
*/
|
||
function resolveAboutTokens(palette, buttonTokens, surfaceTokens) {
|
||
return {
|
||
bg: palette.bg,
|
||
surface: surfaceTokens.surface,
|
||
surfaceBorder: surfaceTokens.surfaceBorder,
|
||
glow: surfaceTokens.surfaceShadow,
|
||
textStrong: palette.text,
|
||
text: mixHex(palette.bg, palette.text, 0.84),
|
||
textMuted: mixHex(palette.bg, palette.text, 0.62),
|
||
accent: palette.accent,
|
||
accentSoft: hexToRgba(palette.accent, 0.22, "rgba(91, 210, 255, 0.22)"),
|
||
accentLine: hexToRgba(palette.accent, 0.42, "rgba(91, 210, 255, 0.42)"),
|
||
actionBg: buttonTokens.btnBgStrong,
|
||
actionText: buttonTokens.btnText,
|
||
actionBorder: buttonTokens.btnBorderStrong,
|
||
orbLeftStart: palette.accent,
|
||
orbLeftEnd: mixHex(palette.accent, palette.bg, 0.56),
|
||
orbRightStart: mixHex(palette.btn, palette.bg, 0.34),
|
||
orbRightEnd: mixHex(palette.text, palette.bg, 0.16)
|
||
};
|
||
}
|
||
|
||
function buildThemeStyle(settings) {
|
||
const palette = resolveRuntimeTheme(settings);
|
||
const buttonTokens = resolveButtonTokens(palette);
|
||
const surfaceTokens = resolveSurfaceTokens(palette);
|
||
const aboutTokens = resolveAboutTokens(palette, buttonTokens, surfaceTokens);
|
||
const muted = mixHex(palette.bg, palette.text, 0.58);
|
||
return [
|
||
`--bg:${palette.bg}`,
|
||
`--text:${palette.text}`,
|
||
`--accent:${palette.accent}`,
|
||
`--btn:${palette.btn}`,
|
||
`--muted:${muted}`,
|
||
`--surface:${surfaceTokens.surface}`,
|
||
`--surface-border:${surfaceTokens.surfaceBorder}`,
|
||
`--surface-shadow:${surfaceTokens.surfaceShadow}`,
|
||
`--shell-bg:${palette.shellBg}`,
|
||
`--shell-text:${palette.shellText}`,
|
||
`--shell-accent:${palette.shellAccent}`,
|
||
`--shell-font-family:${palette.shellFontFamily}`,
|
||
`--shell-font-size:${palette.shellFontSize}px`,
|
||
`--shell-line-height:${palette.shellLineHeight}`,
|
||
`--btn-border:${buttonTokens.btnBorder}`,
|
||
`--btn-border-strong:${buttonTokens.btnBorderStrong}`,
|
||
`--btn-bg:${buttonTokens.btnBg}`,
|
||
`--btn-bg-strong:${buttonTokens.btnBgStrong}`,
|
||
`--btn-bg-active:${buttonTokens.btnBgActive}`,
|
||
`--btn-text:${buttonTokens.btnText}`,
|
||
`--btn-danger-border:${buttonTokens.btnDangerBorder}`,
|
||
`--btn-danger-bg:${buttonTokens.btnDangerBg}`,
|
||
`--chip-bg:${buttonTokens.chipBg}`,
|
||
`--chip-text:${buttonTokens.chipText}`,
|
||
`--accent-divider:${buttonTokens.accentDivider}`,
|
||
`--switch-on-bg:${buttonTokens.switchOnBg}`,
|
||
`--switch-off-bg:${buttonTokens.switchOffBg}`,
|
||
`--switch-knob:${buttonTokens.switchKnob}`,
|
||
`--icon-btn-bg:${buttonTokens.iconBtnBg}`,
|
||
`--icon-btn-bg-strong:${buttonTokens.iconBtnBgStrong}`,
|
||
`--accent-bg:${buttonTokens.accentBg}`,
|
||
`--accent-bg-strong:${buttonTokens.accentBgStrong}`,
|
||
`--accent-border:${buttonTokens.accentBorder}`,
|
||
`--accent-ring:${buttonTokens.accentRing}`,
|
||
`--accent-shadow:${buttonTokens.accentShadow}`,
|
||
`--about-bg:${aboutTokens.bg}`,
|
||
`--about-surface:${aboutTokens.surface}`,
|
||
`--about-surface-border:${aboutTokens.surfaceBorder}`,
|
||
`--about-glow:${aboutTokens.glow}`,
|
||
`--about-text-strong:${aboutTokens.textStrong}`,
|
||
`--about-text:${aboutTokens.text}`,
|
||
`--about-text-muted:${aboutTokens.textMuted}`,
|
||
`--about-accent:${aboutTokens.accent}`,
|
||
`--about-accent-soft:${aboutTokens.accentSoft}`,
|
||
`--about-accent-line:${aboutTokens.accentLine}`,
|
||
`--about-action-bg:${aboutTokens.actionBg}`,
|
||
`--about-action-text:${aboutTokens.actionText}`,
|
||
`--about-action-border:${aboutTokens.actionBorder}`,
|
||
`--about-orb-left-start:${aboutTokens.orbLeftStart}`,
|
||
`--about-orb-left-end:${aboutTokens.orbLeftEnd}`,
|
||
`--about-orb-right-start:${aboutTokens.orbRightStart}`,
|
||
`--about-orb-right-end:${aboutTokens.orbRightEnd}`,
|
||
`--shell-btn-bg:${buttonTokens.shellBtnBg}`,
|
||
`--shell-btn-text:${buttonTokens.shellBtnText}`,
|
||
`--shell-accent-bg:${buttonTokens.shellAccentBg}`,
|
||
`--shell-accent-bg-strong:${buttonTokens.shellAccentBgStrong}`,
|
||
`--shell-accent-border:${buttonTokens.shellAccentBorder}`,
|
||
`--shell-accent-ring:${buttonTokens.shellAccentRing}`,
|
||
`--shell-accent-shadow:${buttonTokens.shellAccentShadow}`,
|
||
`--terminal-touch-tools-bg:${buttonTokens.terminalTouchToolsBg}`
|
||
].join(";");
|
||
}
|
||
|
||
module.exports = {
|
||
buildThemeStyle,
|
||
applyUiThemeSelection,
|
||
applyShellThemeSelection,
|
||
pickBtnColor,
|
||
pickShellAccentColor,
|
||
resolveNavigationBarTheme,
|
||
applyNavigationBarTheme,
|
||
DEFAULT_NAVIGATION_BAR_THEME,
|
||
DEFAULT_SHELL_FONT_FAMILY,
|
||
TERMINAL_SAFE_FONT_OPTIONS,
|
||
normalizeTerminalFontFamily
|
||
};
|