/** * 主题色板预设 * * 主路径:Outpost 壳层三套精选(浅色 / 紫色 / 蓝色) * 高级:旧深色霓虹主题(LEGACY_THEMES) * 规范:docs/superpowers/specs/2026-07-25-migu-outpost-ui-lock-design.md */ export interface ThemePreset { id: string name: string description: string /** 用于 UI 预览的代表色 */ preview: string /** 强调高光,用于 UI 预览的次代表色 */ previewAccent: string /** Outpost 浅色壳(纸白主区);legacy 深色为 false */ outpostShell?: boolean vars: Record } /** Outpost 浅色壳共用表面(纸白卡、深字、关 orb);各主题可覆盖 card/tint */ const OUTPOST_SHELL_VARS: Record = { '--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' } /** * 地图编辑 chrome(顶栏/工具轨/属性栏/底栏)跟主题色相走。 * 默认真色壳(跟侧栏);浅色 Harbor 等可显式覆盖 --me-* 为浅壳。 * 画布始终保持深色。 */ function withMapEditorChrome(vars: Record): Record { 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 & { vars: Record } ): 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: { '--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': '#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: '科幻紫主色 · 磨砂玻璃 · 霓虹辉光(兼容)', preview: '#7c3aed', previewAccent: '#c4b5fd', outpostShell: false, 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': '#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', '--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-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' } }, { id: 'deep-azure', name: '深邃蓝', description: '科技感深蓝,沉静稳重(兼容)', preview: '#1565c0', previewAccent: '#82b1ff', outpostShell: false, vars: { '--mg-primary': '#1565c0', '--mg-primary-rgb': '21, 101, 192', '--mg-primary-hover': '#1976d2', '--mg-primary-hover-rgb': '25, 118, 210', '--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' } }, { id: 'emerald-forge', name: '翡翠绿', description: '机械翡翠绿(兼容)', preview: '#00796b', previewAccent: '#80cbc4', outpostShell: false, vars: { '--mg-primary': '#00796b', '--mg-primary-rgb': '0, 121, 107', '--mg-primary-hover': '#00897b', '--mg-primary-hover-rgb': '0, 137, 123', '--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' } }, { id: 'crimson-iron', name: '熔铁赤', description: '暗红工业风(兼容)', preview: '#b71c1c', previewAccent: '#ff8a80', outpostShell: false, vars: { '--mg-primary': '#b71c1c', '--mg-primary-rgb': '183, 28, 28', '--mg-primary-hover': '#d32f2f', '--mg-primary-hover-rgb': '211, 47, 47', '--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' } }, { id: 'graphite-steel', name: '石墨钢', description: '中性冷灰(兼容)', preview: '#455a64', previewAccent: '#b0bec5', outpostShell: false, vars: { '--mg-primary': '#455a64', '--mg-primary-rgb': '69, 90, 100', '--mg-primary-hover': '#607d8b', '--mg-primary-hover-rgb': '96, 125, 139', '--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' } }, { id: 'amber-forge', name: '琥珀金', description: '橙金调(兼容)', preview: '#e65100', previewAccent: '#ffb74d', outpostShell: false, vars: { '--mg-primary': '#e65100', '--mg-primary-rgb': '230, 81, 0', '--mg-primary-hover': '#f57c00', '--mg-primary-hover-rgb': '245, 124, 0', '--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 const THEMES: ThemePreset[] = [...PRIMARY_THEMES, ...LEGACY_THEMES] /** 旧 id → 新精选 id */ const THEME_ALIASES: Record = { 'fame-lavender': 'outpost-purple', 'outpost-fairyland': 'outpost-light' } 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) } /** 把主题变量写入 :root;Outpost 壳写 data-shell="outpost" 供浅色 CSS 命中 */ export function applyThemeVars(preset: ThemePreset): void { const root = document.documentElement // 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') } }