313 lines
16 KiB
Markdown
313 lines
16 KiB
Markdown
# 主题架构研究:新增并存 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') || 'washi'` | `public/index.html:17` |
|
||
| 首屏 DOM 绑定 | `document.documentElement.dataset.theme = theme` | `public/index.html:23` |
|
||
| 运行时初始状态 | `document.documentElement.dataset.theme || localStorage.getItem(...) || 'washi'` | `public/app.js:200` |
|
||
| 合法性过滤 | `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` |
|
||
|
||
当前有一个特别重要的迁移逻辑:
|
||
|
||
```js
|
||
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:2029`
|
||
- `html: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 定位裁切,不用 CSS `url(...)` 壁纸:`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: true`
|
||
- `normalizeTheme()` 会排除 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.js` cache-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,后续实现前需确认是否可清理 |
|
||
|
||
## 验证命令
|
||
|
||
建议按从轻到重执行:
|
||
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
如果要做浏览器视觉验证:
|
||
|
||
```bash
|
||
# 启动服务
|
||
npm start
|
||
|
||
# 本地确认服务可用
|
||
curl http://127.0.0.1:<port>/
|
||
|
||
# Playwright 远程环境访问时,优先使用 11.144.144.* 网段地址
|
||
hostname -I
|
||
```
|
||
|
||
注意:本次研究未运行回归测试;以上命令是后续实现/验收阶段应执行的验证路径。
|