16 KiB
主题架构研究:新增并存 Warframe/Wasteland 主题
结论
cc-web 的主题系统是一个纯前端、CSS 变量驱动的架构:
- 主题注册和设置候选统一来自
public/app.js的THEME_OPTIONS。 - 当前主题持久化在
localStorage['cc-web-theme'],DOM 状态写到html[data-theme]。 public/index.html在 CSS 加载前执行 bootstrap,负责首屏避免闪烁;当前还会把历史gilded主动迁移回washi。- 样式主体在
public/style.css:先用html[data-theme='<id>']定义语义变量,再用共享暗色 completion selector 与主题专属组件层覆盖具体 UI。 - Warframe 当前 ID 是
gilded,但已hidden: true,因此不会出现在设置页,也不会被normalizeTheme()视为有效持久化主题。
如果目标是“新增一个可并存、可选择的 Warframe/Wasteland 主题”,最稳妥的路径是新增一个可见 ID(例如 wasteland),保留 gilded 作为隐藏历史/实验 ID 或迁移别名,避免直接复用当前已被首屏 bootstrap 迁移掉的 gilded。
codebase-memory 检索记录
| 项 | 结果 |
|---|---|
| 项目 | home-cc-web |
| 索引状态 | ready |
| 主要检索 | theme settings data theme localStorage、gilded warframe regression theme assets CSS |
| 关键符号 | normalizeTheme、applyTheme、buildThemePickerHtml、mountThemePicker、openThemeSubpage、assertFrontendGildedThemeContract |
| 调用链核验 | applyTheme <- mountThemePicker <- openThemeSubpage;assertFrontendGildedThemeContract <- main |
主题注册列表
主题注册点在 public/app.js:109 的 THEME_OPTIONS。每个主题对象包含:
| 字段 | 用途 |
|---|---|
value |
写入 html[data-theme] 和 localStorage 的稳定主题 ID |
label |
设置界面展示名 |
desc |
设置界面说明文案 |
swatches |
设置主题卡片色块 |
hidden |
设置候选过滤与持久化合法性过滤 |
当前注册列表:
| ID | Label | 可见性 | 位置 |
|---|---|---|---|
washi |
Washi Warm |
可见 | public/app.js:111 |
coolvibe |
CoolVibe Light |
可见 | public/app.js:117 |
editorial |
Editorial Sand |
可见 | public/app.js:123 |
sage |
Sage Console |
可见 | public/app.js:129 |
ink |
Ink Focus |
可见 | public/app.js:135 |
dawn |
Dawn Studio |
可见 | public/app.js:141 |
carbon |
Carbon Mint |
可见暗色 | public/app.js:147 |
nocturne |
Nocturne Teal |
可见暗色 | public/app.js:153 |
cinder |
Cinder Rose |
可见暗色 | public/app.js:159 |
gilded |
Warframe Wasteland |
隐藏 | public/app.js:165 |
gilded 当前注册为:
label: 'Warframe Wasteland'desc: '四幅 Warframe 画作融合成 Orokin 档案界面...'swatches: ['#070a0b', '#d8c38f', '#78edf0', '#d86b4c']hidden: true
关键含义:只加 CSS 不会让主题进入设置页;必须保证 THEME_OPTIONS 中有对应 ID,且没有 hidden: true。
主题持久化与 DOM 绑定
| 链路 | 实现 | 位置 |
|---|---|---|
| 首屏读取 | `localStorage.getItem('cc-web-theme') | |
| 首屏 DOM 绑定 | document.documentElement.dataset.theme = theme |
public/index.html:23 |
| 运行时初始状态 | `document.documentElement.dataset.theme | |
| 合法性过滤 | THEME_OPTIONS.some((item) => item.value === theme && !item.hidden) ? theme : 'washi' |
public/app.js:1013 |
| 运行时应用 | 写 document.documentElement.dataset.theme |
public/app.js:1030 |
| 运行时持久化 | 写 localStorage.setItem('cc-web-theme', currentTheme) |
public/app.js:1031 |
| 摘要刷新 | [data-theme-summary] 同步当前 label |
public/app.js:1021 |
当前有一个特别重要的迁移逻辑:
if (theme === 'gilded') {
theme = 'washi';
localStorage.setItem('cc-web-theme', theme);
}
位置:public/index.html:18
这意味着用户浏览器里即使已有 gilded,首屏也会被改回 washi。新增并存主题时有两种策略:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 新 ID 并存 | 新增 wasteland,gilded 继续隐藏或迁移到 wasteland |
推荐,避免污染已有隐藏/历史语义 |
复活 gilded |
删除 hidden: true,移除 index.html 的 gilded -> washi 迁移 |
只适合确认不再需要隐藏 Warframe 时 |
设置界面主题候选
设置页不维护第二份主题列表,候选直接来自 THEME_OPTIONS:
| 入口 | 实现 | 位置 |
|---|---|---|
| 设置主面板外观入口 | buildAppearanceSettingsHtml() 输出 data-open-theme-page 和当前主题摘要 |
public/app.js:1084 |
| 入口事件绑定 | mountAppearanceSettings() 监听 data-open-theme-page,打开主题子页 |
public/app.js:1109 |
| 主题卡片 HTML | buildThemePickerHtml() |
public/app.js:1055 |
| 候选过滤 | THEME_OPTIONS.filter((theme) => !theme.hidden).map(...) |
public/app.js:1060 |
| 点击切换 | mountThemePicker() 调用 applyTheme(button.dataset.themeValue) |
public/app.js:1073 |
| 子页挂载 | openThemeSubpage() 生成 overlay,挂载 picker |
public/app.js:1248 |
结论:新增可见主题只需在 THEME_OPTIONS 中加入非 hidden 条目,设置界面会自动出现;如果只想保留隐藏实验主题,设置候选不会展示。
Warframe 主题样式与资产
静态资产
当前 Warframe 资产位于 public/assets/themes/:
| 文件 | 尺寸/格式约束 | 来源于回归断言 |
|---|---|---|
gilded-wasteland.png |
PNG,1000x1500,SHA-256 固定 | scripts/regression.js:18 |
warframe-ember-squad.jpg |
JPEG,480x673,SHA-256 固定 | scripts/regression.js:25 |
warframe-orokin-figure.jpg |
JPEG,480x672,SHA-256 固定 | scripts/regression.js:32 |
warframe-sacrifice.jpg |
JPEG,480x672,SHA-256 固定 | scripts/regression.js:39 |
静态服务器 MIME 映射支持 JPEG:
.jpg -> image/jpeg:server.js:1289.jpeg -> image/jpeg:server.js:1290- 读取静态文件时用
MIME_TYPES[ext]写Content-Type:server.js:6281
如果新主题继续使用 WebP/PNG/JPEG,通常不需要改 server.js;若引入 AVIF、字体、视频等新类型,需要补 MIME 与回归断言。
Welcome DOM
Warframe welcome 图层同时存在于动态和静态首屏:
| 文件 | 位置 | 内容 |
|---|---|---|
public/app.js |
public/app.js:320 |
buildWelcomeMarkup(agent) 动态生成 .welcome-copy + .welcome-visual |
public/index.html |
public/index.html:91 |
静态首屏 welcome DOM |
图层类名:
.warframe-visual-core.warframe-shard--wasteland.warframe-shard--sacrifice.warframe-shard--orokin.warframe-shard--ember.warframe-visual-axis.warframe-visual-code
关键隔离点:通用主题默认隐藏 .welcome-visual:
.welcome-art, .welcome-visual { display: none; }:public/style.css:2029html:not([data-theme='gilded']) .welcome-visual { display: none !important; }:public/style.css:6558- 只有
html[data-theme='gilded'] .welcome-msg > .welcome-copy + .welcome-visual[aria-hidden]才显示视觉舞台:public/style.css:6689
如果新增 wasteland 并存主题,需要把这些选择器复制/泛化到 html[data-theme='wasteland'],或者用 :is(html[data-theme='gilded'], html[data-theme='wasteland']) 包住 Warframe 专属层。
CSS 分层
当前 style.css 的可复用模式是四层:
| 层 | 作用 | 位置 |
|---|---|---|
| 语义变量层 | html[data-theme='gilded'] 定义颜色、面板、Warframe token |
public/style.css:683 |
| 共享暗色 completion 层 | 用 :is(carbon, nocturne, cinder, gilded) 给暗色主题补齐通用组件 |
public/style.css:745 和 public/style.css:6140 |
| 基础 UI 组件层 | .theme-grid、.theme-card 等设置 UI |
public/style.css:4323 |
| Warframe 专属组件层 | welcome 舞台、四图布局、Orokin 面板、暗金交互 | public/style.css:6557 |
Warframe 专属层中的关键设计:
- 常规工作区不把图片铺到消息背后,
.chat-main只用渐变和--page-background:public/style.css:6598 - welcome 页面用
.messages:has(> .welcome-msg)限定视觉舞台:public/style.css:6605 - 四个真实
<img>图层由 CSS 定位裁切,不用 CSSurl(...)壁纸:public/style.css:6739 - 输入、搜索、面板使用不透明 obsidian surfaces,降低图片干扰:
public/style.css:6965 - 选择器 chevron、focus ring 复用
--warframe-energy:public/style.css:6975、public/style.css:7175 - 用户/助手消息和附件卡有专门的不透明覆盖:
public/style.css:7188、public/style.css:7222 - 移动布局在
1060px和560px两档重新堆叠四图:public/style.css:7316、public/style.css:7373 prefers-reduced-motion禁用 gilded 动画和 transition:public/style.css:7444
相关回归测试
主题相关回归集中在 scripts/regression.js:
| 检查 | 位置 |
|---|---|
| 资产清单与 hash/尺寸 | scripts/regression.js:16 |
| 主题契约函数 | scripts/regression.js:685 |
target 入口 gilded-theme |
scripts/regression.js:2204 |
| full regression 会调用该主题契约 | scripts/regression.js:2219 |
assertFrontendGildedThemeContract() 覆盖的关键面:
gilded已注册且hidden: truenormalizeTheme()会排除 hidden 主题- 主题 picker 排除 hidden 主题
index.html首屏会把gilded迁移回washi- JPEG MIME 映射存在
- Warframe label、swatches、desc 正确
- 动态与静态 welcome DOM 都有四个 Warframe 图层
- 不保留旧
.welcome-art单图 placeholder - 项目 UI 不引入
cdn.displate.com、pinterest.com、pinimg.com - 本地资产 hash、格式、尺寸固定
- gilded CSS 变量块不含
url(...) - 共享暗色 selector 必须包含
gilded - Warframe 专属层必须包含 welcome scope、真实图片节点、无远程 URL、移动布局和 reduced-motion
- 关键文本对比度满足断言阈值
style.css/app.jscache-busting 参数仍是20260717-warframe-hidden
并存新主题时,需要同步调整这份测试:
| 目标 | 测试调整 |
|---|---|
新 ID wasteland 可见 |
断言 value: 'wasteland' 且不含 hidden: true |
保留 legacy gilded |
断言 gilded 隐藏,且 index.html 可按策略迁移到 wasteland 或 washi |
| CSS 复用 | 断言共享暗色 selector 包含 wasteland |
| 专属层泛化 | 断言 Warframe selectors 同时覆盖 gilded/wasteland 或只覆盖新 ID |
| 资产变更 | 更新 GILDED_THEME_ASSETS 或新增 WASTELAND_THEME_ASSETS |
| cache bust | 修改 index.html 的 style.css?v=... 与 app.js?v=... 后更新断言 |
新增并存主题的最小改动面
推荐:新增可见 wasteland,保留 gilded 隐藏
| 文件 | 最少改动 |
|---|---|
public/app.js |
在 THEME_OPTIONS 新增 value: 'wasteland' 可见条目;可保留 gilded hidden 作为 legacy;如复用 welcome DOM,无需改 buildWelcomeMarkup() |
public/index.html |
更新首屏迁移策略:历史 gilded 可迁移到 wasteland;设置新的 cache-busting query |
public/style.css |
新增 html[data-theme='wasteland'] 变量块;把共享暗色 selectors 纳入 wasteland;把 Warframe 专属层 selectors 从 gilded 泛化到 gilded/wasteland 或迁移为 wasteland |
public/assets/themes/* |
若复用现有四图则不变;若换图则新增本地资产,不使用远程 URL |
scripts/regression.js |
新增/调整主题契约,覆盖新 ID、可见性、迁移、CSS selector、资产 hash/尺寸、cache bust |
server.js |
仅在引入新 MIME 类型时修改;当前 PNG/JPEG 已覆盖 |
可选:直接复活 gilded
| 文件 | 最少改动 |
|---|---|
public/app.js |
删除 hidden: true,让 picker 与 normalizeTheme() 接受它 |
public/index.html |
删除或反转 gilded -> washi 迁移 |
scripts/regression.js |
删除“隐藏主题”相关断言,改成“gilded 可见且可持久化” |
public/style.css |
当前大部分 CSS 可复用,按需求微调 |
不推荐直接复活的原因:当前测试和 bootstrap 已明确把 gilded 定义为隐藏状态;复活会同时改变持久化、首屏、设置候选和回归契约,语义冲击更大。
可复用隔离模式
| 模式 | 价值 | 复用建议 |
|---|---|---|
主题 ID 只写 data-theme |
让 CSS 完全接管视觉差异 | 新主题不要创建额外 body class |
THEME_OPTIONS 单源 |
设置候选、label、swatch 同源 | 不要在设置页硬编码第二份列表 |
hidden 双重隔离 |
同时从 picker 和持久化合法性排除 | legacy/实验主题可继续用 |
| 首屏 bootstrap | CSS 前写 data-theme 避免闪烁 |
新主题必须能被 bootstrap 识别或迁移 |
| 语义变量优先 | 大部分组件吃 --bg-*、--text-*、--accent |
新主题先补变量,再补组件特例 |
| 共享暗色 completion selector | 暗色主题共用大量控件适配 | 新暗色主题必须加入 :is(...) 列表 |
| 专属组件层后置 | 高风险视觉只在主题 selector 下生效 | Warframe 视觉不要污染普通主题 |
| 默认隐藏视觉舞台 | 非目标主题不会露出 Warframe DOM | 新主题要显式打开 .welcome-visual |
真实 <img> 资产节点 |
可被回归测试验证 hash/尺寸,避免远程 wallpaper | 继续保留本地资产和 img DOM |
| 回归防远程 URL | 防止参考站点进入产品 UI | 新资产必须落本地并断言 |
潜在脏文件冲突
当前工作区已经有大量未提交内容。本次研究只应新增该文档;后续实现必须先确认这些脏文件归属,避免覆盖他人工作。
已修改的跟踪文件
| 文件 | 与主题关系 |
|---|---|
public/app.js |
高相关:主题注册、picker、welcome DOM |
public/index.html |
高相关:首屏主题 bootstrap、静态 welcome、cache bust |
public/style.css |
高相关:主题变量和 Warframe CSS |
scripts/regression.js |
高相关:主题回归契约 |
server.js |
中相关:静态 MIME |
dist-exe/cc-web-bun-linux-x64-baseline.tar.gz |
发布包脏文件,主题实现一般不应直接改 |
.planning/subagent-card-metadata/*、.trellis/tasks/07-11-subagent-card-metadata/prd.md |
其他任务痕迹,避免误动 |
未跟踪目录/文件
| 路径 | 说明 |
|---|---|
.trellis/tasks/07-17-gilded-wasteland-theme/ |
当前任务目录未跟踪,本研究文档会落在这里 |
.trellis/tasks/07-17-warframe-gallery-redesign/ |
相关但独立任务 |
public/assets/themes/ |
Warframe 资产目录未跟踪 |
.planning/dark-gilded-theme/ 等多个 .planning/* |
历史/并行规划产物 |
新增暗金荒野主题 TO DO list.csv、重做 Warframe 饱满主题 TO DO list.csv |
临时 TODO CSV,后续实现前需确认是否可清理 |
验证命令
建议按从轻到重执行:
# 1. 只跑主题契约
npm run regression -- --target gilded-theme
# 2. 跑全量回归
npm run regression
# 3. 检查补丁空白错误
git diff --check
# 4. 检查主题相关文件变更面
git diff -- public/app.js public/index.html public/style.css scripts/regression.js server.js
如果要做浏览器视觉验证:
# 启动服务
npm start
# 本地确认服务可用
curl http://127.0.0.1:<port>/
# Playwright 远程环境访问时,优先使用 11.144.144.* 网段地址
hostname -I
注意:本次研究未运行回归测试;以上命令是后续实现/验收阶段应执行的验证路径。