锁定 Outpost 主路径主题,并优化地图编辑属性面板浅色对比。

统一 outpost-light/purple/blue 壳层 token,修复扩展字段与操作按钮在浅色下的可读性。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-07-26 11:31:53 +08:00
co-authored by Cursor
parent 6986370e9d
commit 95c1a74122
18 changed files with 3790 additions and 1651 deletions
@@ -1,10 +1,9 @@
/**
* 主题色板预设
*
* 设计原则:
* - 每个主题提供一组 CSS 变量,运行时通过 document.documentElement.style.setProperty 注入
* - 命名遵循 theme.css 中既有的 --mg-* 体系
* - 工业紫色 (industrial-purple) 为默认主题:紫蓝渐变,深蓝底 + 天蓝高光
* 主路径:Outpost 壳层三套精选(浅色 / 紫色 / 蓝色)
* 高级:旧深色霓虹主题(LEGACY_THEMES
* 规范:docs/superpowers/specs/2026-07-25-migu-outpost-ui-lock-design.md
*/
export interface ThemePreset {
@@ -15,116 +14,229 @@ export interface ThemePreset {
preview: string
/** 强调高光,用于 UI 预览的次代表色 */
previewAccent: string
/** Outpost 浅色壳(纸白主区);legacy 深色为 false */
outpostShell?: boolean
vars: Record<string, string>
}
export const DEFAULT_THEME_ID = 'fame-lavender'
/** Outpost 浅色壳共用表面(纸白卡、深字、关 orb);各主题可覆盖 card/tint */
const OUTPOST_SHELL_VARS: Record<string, string> = {
'--mg-bg-card-rgb': '255, 254, 253',
'--mg-bg-card-hi-rgb': '251, 249, 253',
'--mg-bg-card-darker-rgb': '245, 240, 251',
'--mg-text-tint-rgb': '40, 33, 58',
'--mg-text-hi-rgb': '117, 109, 133',
'--mg-orb-a-opacity': '0',
'--mg-orb-b-opacity': '0',
'--mg-orb-c-opacity': '0',
'--mg-radius-sm': '10px',
'--mg-radius': '16px',
'--mg-radius-lg': '20px',
'--mg-radius-xl': '24px'
}
export const THEMES: ThemePreset[] = [
{
id: 'fame-lavender',
name: '迷毂浅紫',
description: '参考 FAME 系统:深紫侧栏 + 浅紫白主区,柔和耐看,长时间使用不刺眼',
/**
* 地图编辑 chrome(顶栏/工具轨/属性栏/底栏)跟主题色相走。
* 默认真色壳(跟侧栏);浅色 Harbor 等可显式覆盖 --me-* 为浅壳。
* 画布始终保持深色。
*/
function withMapEditorChrome(vars: Record<string, string>): Record<string, string> {
const primaryRgb = vars['--mg-primary-rgb'] ?? '30, 58, 95'
const chrome1 = vars['--mg-bg-aside-1'] ?? '#1e3a5f'
const chrome2 = vars['--mg-bg-aside-2'] ?? '#0b1220'
const chromeRgb = vars['--mg-bg-aside-rgb'] ?? primaryRgb
const lightChrome = vars['--me-chrome-mode'] === 'light'
return {
...vars,
'--me-chrome-1': vars['--me-chrome-1'] ?? chrome1,
'--me-chrome-2': vars['--me-chrome-2'] ?? chrome2,
'--me-chrome-rgb': vars['--me-chrome-rgb'] ?? chromeRgb,
'--me-border': vars['--me-border'] ?? (lightChrome ? 'rgba(30, 58, 95, 0.12)' : 'rgba(255, 255, 255, 0.12)'),
'--me-text': vars['--me-text'] ?? (lightChrome ? 'rgba(11, 18, 32, 0.92)' : 'rgba(245, 242, 255, 0.94)'),
'--me-text-muted': vars['--me-text-muted'] ?? (lightChrome ? 'rgba(11, 18, 32, 0.55)' : 'rgba(210, 200, 235, 0.68)'),
'--me-surface': vars['--me-surface'] ?? (lightChrome ? 'rgba(30, 58, 95, 0.04)' : 'rgba(255, 255, 255, 0.05)'),
'--me-hover': vars['--me-hover'] ?? (lightChrome ? `rgba(${primaryRgb}, 0.08)` : `rgba(${primaryRgb}, 0.28)`),
'--me-active-glow': vars['--me-active-glow'] ?? `rgba(${primaryRgb}, 0.28)`,
'--me-card-bg': vars['--me-card-bg'] ?? (lightChrome ? 'rgba(255, 255, 255, 0.96)' : 'rgba(0, 0, 0, 0.22)'),
'--me-card-border': vars['--me-card-border'] ?? (lightChrome ? '#e2e6ec' : 'rgba(255, 255, 255, 0.08)'),
'--me-inset': vars['--me-inset'] ?? (lightChrome ? '#f4f6f8' : 'rgba(0, 0, 0, 0.28)')
}
}
function outpostPreset(
partial: Omit<ThemePreset, 'outpostShell' | 'vars'> & { vars: Record<string, string> }
): ThemePreset {
return {
...partial,
outpostShell: true,
vars: withMapEditorChrome({ ...OUTPOST_SHELL_VARS, ...partial.vars })
}
}
/** 默认:Outpost 浅色(对齐 fairylandtech.amerc.ai */
export const DEFAULT_THEME_ID = 'outpost-light'
/** 主路径精选三色(顶栏 ThemeSwitcher */
export const PRIMARY_THEMES: ThemePreset[] = [
outpostPreset({
id: 'outpost-light',
name: '浅色',
description: '纯白壳 + 紫色品牌点缀;地图壳白,画布深色',
preview: '#ffffff',
previewAccent: '#7c3aed',
vars: {
'--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237',
'--mg-primary-hover': '#8b5cf6',
'--mg-primary-hover-rgb': '139, 92, 246',
'--mg-primary-active': '#5b21b6',
'--mg-accent': '#a855f7',
'--mg-accent-rgb': '168, 85, 247',
'--mg-primary-light-3': '#a78bfa',
'--mg-primary-light-5': '#c4b5fd',
'--mg-primary-light-7': '#ddd6fe',
'--mg-primary-light-8': '#ede9fe',
'--mg-primary-light-9': '#f5f3ff',
'--mg-primary-dark-2': '#6d28d9',
'--mg-bg-app-1': '#ffffff',
'--mg-bg-app-2': '#ffffff',
'--mg-bg-app-3': '#ffffff',
'--mg-bg-app-deep-rgb': '255, 255, 255',
'--mg-bg-aside-1': '#ffffff',
'--mg-bg-aside-2': '#fafafa',
'--mg-bg-aside-rgb': '255, 255, 255',
'--mg-shadow-rgb': '76, 29, 149',
'--mg-bg-card-rgb': '255, 255, 255',
'--mg-bg-card-hi-rgb': '255, 255, 255',
'--mg-bg-card-darker-rgb': '248, 250, 252',
'--mg-text-tint-rgb': '45, 27, 105',
'--mg-text-hi-rgb': '109, 40, 217',
/* 地图编辑:纯白壳 */
'--me-chrome-mode': 'light',
'--me-chrome-1': '#ffffff',
'--me-chrome-2': '#ffffff',
'--me-chrome-rgb': '255, 255, 255',
'--me-border': 'rgba(124, 58, 237, 0.12)',
'--me-text': 'rgba(45, 27, 105, 0.92)',
'--me-text-muted': 'rgba(76, 29, 149, 0.55)',
'--me-surface': 'rgba(124, 58, 237, 0.04)',
'--me-hover': 'rgba(124, 58, 237, 0.08)',
'--me-active-glow': 'rgba(124, 58, 237, 0.18)',
'--me-card-bg': '#ffffff',
'--me-card-border': '#e5e7eb',
'--me-inset': '#f8fafc'
}
}),
outpostPreset({
id: 'outpost-purple',
name: '紫色',
description: '同壳层、更饱和紫品牌;侧栏深蓝紫',
preview: '#7c3aed',
previewAccent: '#a855f7',
vars: {
// 主色保留紫色(与 FAME 一致),但 hover/accent 使用更明亮的 violet/purple
'--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237',
'--mg-primary-hover': '#8b5cf6',
'--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237',
'--mg-primary-hover': '#8b5cf6',
'--mg-primary-hover-rgb': '139, 92, 246',
'--mg-primary-active': '#5b21b6',
'--mg-accent': '#a855f7',
'--mg-accent-rgb': '168, 85, 247',
'--mg-primary-light-3': '#a78bfa',
'--mg-primary-light-5': '#c4b5fd',
'--mg-primary-light-7': '#ddd6fe',
'--mg-primary-light-8': '#ede9fe',
'--mg-primary-light-9': '#f5f3ff',
'--mg-primary-dark-2': '#6d28d9',
// 主区背景:浅薰衣草灰(v3:从近白下沉到 ~92% 明度,让白卡片靠阴影清晰浮起)
'--mg-bg-app-1': '#edeaf5',
'--mg-bg-app-2': '#e7e4f1',
'--mg-bg-app-3': '#f1eef8',
'--mg-bg-app-deep-rgb': '231, 228, 241',
// 侧栏:FAME 深紫渐变 #2d1b69 -> #1a1040
'--mg-bg-aside-1': '#2d1b69',
'--mg-bg-aside-2': '#1a1040',
'--mg-bg-aside-rgb': '45, 27, 105',
// 卡片:白底(在 fame-lavender 专属规则中走纯 #fff
'--mg-bg-card-rgb': '255, 255, 255',
'--mg-bg-card-hi-rgb': '249, 246, 255',
'--mg-bg-card-darker-rgb':'245, 240, 251',
'--mg-shadow-rgb': '124, 58, 237',
// 文字:深紫标题 #2d1b69, 中灰正文 #606266
'--mg-text-tint-rgb': '45, 27, 105',
'--mg-text-hi-rgb': '96, 98, 102',
// 关闭装饰性 orb
'--mg-orb-a-opacity': '0',
'--mg-orb-b-opacity': '0',
'--mg-orb-c-opacity': '0'
'--mg-primary-active': '#5b21b6',
'--mg-accent': '#a855f7',
'--mg-accent-rgb': '168, 85, 247',
'--mg-primary-light-3': '#a78bfa',
'--mg-primary-light-5': '#c4b5fd',
'--mg-primary-light-7': '#ddd6fe',
'--mg-primary-light-8': '#ede9fe',
'--mg-primary-light-9': '#f5f3ff',
'--mg-primary-dark-2': '#6d28d9',
'--mg-bg-app-1': '#f3eefc',
'--mg-bg-app-2': '#edeaf5',
'--mg-bg-app-3': '#f8f5ff',
'--mg-bg-app-deep-rgb': '243, 238, 252',
'--mg-bg-aside-1': '#2d1b69',
'--mg-bg-aside-2': '#1a1040',
'--mg-bg-aside-rgb': '45, 27, 105',
'--mg-shadow-rgb': '124, 58, 237'
}
},
}),
outpostPreset({
id: 'outpost-blue',
name: '蓝色',
description: '同壳层、蓝科技感;侧栏深蓝',
preview: '#3c78d2',
previewAccent: '#6ea8f0',
vars: {
'--mg-primary': '#3c78d2',
'--mg-primary-rgb': '60, 120, 210',
'--mg-primary-hover': '#4d8ae0',
'--mg-primary-hover-rgb': '77, 138, 224',
'--mg-primary-active': '#2a5fad',
'--mg-accent': '#6ea8f0',
'--mg-accent-rgb': '110, 168, 240',
'--mg-primary-light-3': '#6ea8f0',
'--mg-primary-light-5': '#9bc4f5',
'--mg-primary-light-7': '#c5ddf9',
'--mg-primary-light-8': '#e3effc',
'--mg-primary-light-9': '#f3f7fc',
'--mg-primary-dark-2': '#2a5fad',
'--mg-bg-app-1': '#f3f6fb',
'--mg-bg-app-2': '#eef2f8',
'--mg-bg-app-3': '#f8fafc',
'--mg-bg-app-deep-rgb': '243, 246, 251',
'--mg-bg-aside-1': '#0f2744',
'--mg-bg-aside-2': '#0a1a30',
'--mg-bg-aside-rgb': '15, 39, 68',
'--mg-shadow-rgb': '30, 58, 95'
}
})
]
/** 高级兼容:旧深色霓虹主题(用户菜单「兼容主题」) */
export const LEGACY_THEMES: ThemePreset[] = [
{
id: 'industrial-purple',
name: '星云紫',
description: '科幻紫主色,辅以蓝紫渐变,磨砂玻璃 · 霓虹辉光',
description: '科幻紫主色 · 磨砂玻璃 · 霓虹辉光(兼容)',
preview: '#7c3aed',
previewAccent: '#c4b5fd',
outpostShell: false,
vars: {
// ── 主色:明亮 violet(紫),主导整个画面 ──
'--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237',
// hover/辉光:偏蓝紫 indigo,带来氛围流动感
'--mg-primary-hover': '#8b5cf6',
'--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237',
'--mg-primary-hover': '#8b5cf6',
'--mg-primary-hover-rgb': '139, 92, 246',
'--mg-primary-active': '#5b21b6',
// accent:浅紫高光,边缘霓虹与点缀
'--mg-accent': '#c4b5fd',
'--mg-accent-rgb': '196, 181, 253',
'--mg-primary-active': '#5b21b6',
'--mg-accent': '#c4b5fd',
'--mg-accent-rgb': '196, 181, 253',
'--mg-primary-light-3': '#9d6cf2',
'--mg-primary-light-5': '#b594f7',
'--mg-primary-light-7': '#6f4eab',
'--mg-primary-light-8': '#4c357a',
'--mg-primary-light-9': '#2d1f55',
'--mg-primary-dark-2': '#4c1d95',
// ── 背景:深紫黑底,带 indigo 余晖 ──
'--mg-bg-app-1': '#0e0a24',
'--mg-bg-app-2': '#1f1147',
'--mg-bg-app-3': '#06031a',
'--mg-primary-dark-2': '#4c1d95',
'--mg-bg-app-1': '#0e0a24',
'--mg-bg-app-2': '#1f1147',
'--mg-bg-app-3': '#06031a',
'--mg-bg-app-deep-rgb': '6, 3, 26',
'--mg-bg-aside-1': '#0c0820',
'--mg-bg-aside-2': '#03020e',
'--mg-bg-aside-rgb': '10, 6, 30',
'--mg-bg-card-rgb': '38, 24, 78',
'--mg-bg-card-hi-rgb': '58, 38, 110',
'--mg-bg-aside-1': '#0c0820',
'--mg-bg-aside-2': '#03020e',
'--mg-bg-aside-rgb': '10, 6, 30',
'--mg-bg-card-rgb': '38, 24, 78',
'--mg-bg-card-hi-rgb': '58, 38, 110',
'--mg-bg-card-darker-rgb': '20, 12, 48',
'--mg-shadow-rgb': '8, 2, 24',
'--mg-text-tint-rgb': '242, 232, 255',
'--mg-text-hi-rgb': '220, 200, 252',
'--mg-orb-a-opacity': '0.55',
'--mg-orb-b-opacity': '0.50',
'--mg-orb-c-opacity': '0.40'
'--mg-shadow-rgb': '8, 2, 24',
'--mg-text-tint-rgb': '242, 232, 255',
'--mg-text-hi-rgb': '220, 200, 252',
'--mg-orb-a-opacity': '0.55',
'--mg-orb-b-opacity': '0.50',
'--mg-orb-c-opacity': '0.40'
}
},
{
id: 'deep-azure',
name: '深邃蓝',
description: '科技感深蓝,沉静稳重',
description: '科技感深蓝,沉静稳重(兼容)',
preview: '#1565c0',
previewAccent: '#82b1ff',
outpostShell: false,
vars: {
'--mg-primary': '#1565c0',
'--mg-primary-rgb': '21, 101, 192',
@@ -133,28 +245,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#0d47a1',
'--mg-accent': '#82b1ff',
'--mg-accent-rgb': '130, 177, 255',
'--mg-primary-light-3': '#5a8fce',
'--mg-primary-light-5': '#83aedb',
'--mg-primary-light-7': '#adcde7',
'--mg-primary-light-8': '#c4dbed',
'--mg-primary-light-9': '#e3eef8',
'--mg-primary-dark-2': '#0d47a1',
'--mg-bg-app-1': '#051a3a',
'--mg-bg-app-2': '#0a2f6b',
'--mg-bg-app-3': '#020a1f',
'--mg-bg-app-deep-rgb': '2, 8, 26',
'--mg-bg-aside-1': '#06122a',
'--mg-bg-aside-2': '#02060f',
'--mg-bg-aside-rgb': '8, 18, 42',
'--mg-bg-card-rgb': '12, 36, 80',
'--mg-bg-card-hi-rgb': '18, 50, 110',
'--mg-bg-card-darker-rgb': '6, 18, 44',
'--mg-shadow-rgb': '2, 4, 14',
'--mg-text-tint-rgb': '218, 234, 252',
'--mg-text-hi-rgb': '170, 200, 240'
}
@@ -162,9 +269,10 @@ export const THEMES: ThemePreset[] = [
{
id: 'emerald-forge',
name: '翡翠绿',
description: '机械翡翠绿,沉稳活力',
description: '机械翡翠绿(兼容)',
preview: '#00796b',
previewAccent: '#80cbc4',
outpostShell: false,
vars: {
'--mg-primary': '#00796b',
'--mg-primary-rgb': '0, 121, 107',
@@ -173,28 +281,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#004d40',
'--mg-accent': '#80cbc4',
'--mg-accent-rgb': '128, 203, 196',
'--mg-primary-light-3': '#4ca094',
'--mg-primary-light-5': '#79b9b0',
'--mg-primary-light-7': '#a6d2cc',
'--mg-primary-light-8': '#bfdfdb',
'--mg-primary-light-9': '#e1f0ee',
'--mg-primary-dark-2': '#004d40',
'--mg-bg-app-1': '#06241f',
'--mg-bg-app-2': '#0a4538',
'--mg-bg-app-3': '#020f0c',
'--mg-bg-app-deep-rgb': '2, 12, 10',
'--mg-bg-aside-1': '#061a16',
'--mg-bg-aside-2': '#020a08',
'--mg-bg-aside-rgb': '8, 26, 22',
'--mg-bg-card-rgb': '10, 50, 42',
'--mg-bg-card-hi-rgb': '14, 66, 56',
'--mg-bg-card-darker-rgb': '6, 28, 24',
'--mg-shadow-rgb': '2, 10, 8',
'--mg-text-tint-rgb': '220, 244, 240',
'--mg-text-hi-rgb': '170, 220, 210'
}
@@ -202,9 +305,10 @@ export const THEMES: ThemePreset[] = [
{
id: 'crimson-iron',
name: '熔铁赤',
description: '熔铁炉火般的暗红工业风',
description: '暗红工业风(兼容)',
preview: '#b71c1c',
previewAccent: '#ff8a80',
outpostShell: false,
vars: {
'--mg-primary': '#b71c1c',
'--mg-primary-rgb': '183, 28, 28',
@@ -213,28 +317,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#8a0c0c',
'--mg-accent': '#ff8a80',
'--mg-accent-rgb': '255, 138, 128',
'--mg-primary-light-3': '#c95a5a',
'--mg-primary-light-5': '#d68585',
'--mg-primary-light-7': '#e4afaf',
'--mg-primary-light-8': '#edc8c8',
'--mg-primary-light-9': '#f7e6e6',
'--mg-primary-dark-2': '#8a0c0c',
'--mg-bg-app-1': '#2a0606',
'--mg-bg-app-2': '#4a0c0c',
'--mg-bg-app-3': '#150303',
'--mg-bg-app-deep-rgb': '20, 4, 4',
'--mg-bg-aside-1': '#200505',
'--mg-bg-aside-2': '#0c0202',
'--mg-bg-aside-rgb': '36, 6, 6',
'--mg-bg-card-rgb': '60, 10, 10',
'--mg-bg-card-hi-rgb': '80, 14, 14',
'--mg-bg-card-darker-rgb': '34, 5, 5',
'--mg-shadow-rgb': '10, 1, 1',
'--mg-text-tint-rgb': '252, 220, 220',
'--mg-text-hi-rgb': '240, 175, 175'
}
@@ -242,9 +341,10 @@ export const THEMES: ThemePreset[] = [
{
id: 'graphite-steel',
name: '石墨钢',
description: '中性冷灰,纯粹工业',
description: '中性冷灰(兼容)',
preview: '#455a64',
previewAccent: '#b0bec5',
outpostShell: false,
vars: {
'--mg-primary': '#455a64',
'--mg-primary-rgb': '69, 90, 100',
@@ -253,28 +353,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#263238',
'--mg-accent': '#b0bec5',
'--mg-accent-rgb': '176, 190, 197',
'--mg-primary-light-3': '#7a8a92',
'--mg-primary-light-5': '#9aa6ac',
'--mg-primary-light-7': '#bac2c7',
'--mg-primary-light-8': '#ccd2d6',
'--mg-primary-light-9': '#e8ebed',
'--mg-primary-dark-2': '#263238',
'--mg-bg-app-1': '#161e24',
'--mg-bg-app-2': '#243038',
'--mg-bg-app-3': '#0a0e12',
'--mg-bg-app-deep-rgb': '10, 14, 18',
'--mg-bg-aside-1': '#11181d',
'--mg-bg-aside-2': '#060a0d',
'--mg-bg-aside-rgb': '18, 26, 32',
'--mg-bg-card-rgb': '30, 42, 50',
'--mg-bg-card-hi-rgb': '42, 56, 66',
'--mg-bg-card-darker-rgb': '16, 22, 28',
'--mg-shadow-rgb': '4, 6, 10',
'--mg-text-tint-rgb': '226, 232, 238',
'--mg-text-hi-rgb': '186, 198, 210'
}
@@ -282,9 +377,10 @@ export const THEMES: ThemePreset[] = [
{
id: 'amber-forge',
name: '琥珀金',
description: '高对比的橙金调,警示与活力并存',
description: '橙金调(兼容)',
preview: '#e65100',
previewAccent: '#ffb74d',
outpostShell: false,
vars: {
'--mg-primary': '#e65100',
'--mg-primary-rgb': '230, 81, 0',
@@ -293,44 +389,69 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#bf360c',
'--mg-accent': '#ffb74d',
'--mg-accent-rgb': '255, 183, 77',
'--mg-primary-light-3': '#ec8344',
'--mg-primary-light-5': '#f2a474',
'--mg-primary-light-7': '#f7c4a3',
'--mg-primary-light-8': '#fad6bc',
'--mg-primary-light-9': '#fdeadd',
'--mg-primary-dark-2': '#bf360c',
'--mg-bg-app-1': '#2a1404',
'--mg-bg-app-2': '#4a2a0c',
'--mg-bg-app-3': '#150a02',
'--mg-bg-app-deep-rgb': '20, 10, 4',
'--mg-bg-aside-1': '#200f04',
'--mg-bg-aside-2': '#0c0602',
'--mg-bg-aside-rgb': '34, 16, 6',
'--mg-bg-card-rgb': '62, 32, 8',
'--mg-bg-card-hi-rgb': '82, 44, 12',
'--mg-bg-card-darker-rgb': '34, 16, 4',
'--mg-shadow-rgb': '10, 4, 1',
'--mg-text-tint-rgb': '252, 232, 210',
'--mg-text-hi-rgb': '240, 196, 140'
}
}
]
export function findTheme(id: string | undefined | null): ThemePreset {
if (!id) return THEMES[0]
return THEMES.find((t) => t.id === id) ?? THEMES[0]
/** 全量(查找用):精选 + 兼容 */
export const THEMES: ThemePreset[] = [...PRIMARY_THEMES, ...LEGACY_THEMES]
/** 旧 id → 新精选 id */
const THEME_ALIASES: Record<string, string> = {
'fame-lavender': 'outpost-purple',
'outpost-fairyland': 'outpost-light'
}
/** 把主题变量写入 :root;多次调用幂等,会覆盖同名属性 */
export function resolveThemeId(id: string | undefined | null): string {
if (!id) return DEFAULT_THEME_ID
const aliased = THEME_ALIASES[id] ?? id
if (THEMES.some((t) => t.id === aliased)) return aliased
return DEFAULT_THEME_ID
}
export function findTheme(id: string | undefined | null): ThemePreset {
const resolved = resolveThemeId(id)
return THEMES.find((t) => t.id === resolved) ?? PRIMARY_THEMES[0]
}
export function isLegacyThemeId(id: string | undefined | null): boolean {
if (!id) return false
const resolved = THEME_ALIASES[id] ?? id
return LEGACY_THEMES.some((t) => t.id === resolved)
}
/** 把主题变量写入 :rootOutpost 壳写 data-shell="outpost" 供浅色 CSS 命中 */
export function applyThemeVars(preset: ThemePreset): void {
const root = document.documentElement
for (const [key, value] of Object.entries(preset.vars)) {
// preset.vars 在 outpostPreset 里已跑过 withMapEditorChrome;再跑一次以补全缺省 --me-*
const vars = withMapEditorChrome({ ...preset.vars })
for (const [key, value] of Object.entries(vars)) {
if (key === '--me-chrome-mode') continue
root.style.setProperty(key, value)
}
root.setAttribute('data-theme', preset.id)
if (preset.outpostShell) {
root.setAttribute('data-shell', 'outpost')
} else {
root.removeAttribute('data-shell')
}
}