# 亮色主题切换（dark / light theme toggle）

**状态**：待开发
**创建日期**：2026-04-28
**优先级**：P3（影视工具默认深色更专业，浅色主要服务非影视用户 / 白天强光环境 / 投资人 demo）

---

## 需求背景

当前界面是深色固定主题（参考 Linear / Vercel 风格）。部分用户在强光环境 / 白天工作 / 习惯亮色的场景下希望能切换。

不走传统"产品+设计师"流程，直接由 AI 从代码层做改造。

---

## 现状分析（关键发现）

### ✅ 基础设施已就位

- `web/src/index.css` `:root` 集中定义了 **35 个 CSS variable**（`--color-bg-page`、`--color-text-primary` 等）
- 全项目 **525 处** 引用了 `var(--color-*)`，集中可控
- **未使用 Arco Design**（CLAUDE.md 写错了，实际 `grep @arco-design = 0`）→ 不需要适配第三方 UI 库主题，少了一大块工作量

### ⚠️ 109 处 inline 颜色散落在 `.tsx`

热点文件（占 75%）：

| 文件 | inline 颜色数 |
|---|---|
| `pages/DashboardPage.tsx` | 31 |
| `pages/TeamDashboardPage.tsx` | 21 |
| `components/RecordDetailModal.tsx` | 17 |
| `components/ReferenceList.tsx` | 12 |
| 其他 | 28 |

颜色种类高度收敛（前 10 高频占 60%+）：

```
17次 #8b8ea8                       ← 已有 var: --color-text-secondary
10次 #888                          ← 同上语义
 6次 rgba(255,255,255,0.06)        ← 已有 var: --color-bg-card / --color-bg-input-bar
 5次 rgba(255,255,255,0.08)        ← 已有 var: --color-bg-hover
 5次 #f1f0ff                       ← 已有 var: --color-text-primary
 6次 #6c63ff                       ← 已有 var: --color-primary
```

→ 大部分是机械性 grep+replace，不是创造性设计。

---

## 浅色主题色板设计

直接抄 Linear / Vercel 的浅色方案（这俩就是当前深色主题模仿的对象，他们都有官方浅色版）。

### 核心规则

| 类别 | 深色（现状） | 浅色（新增） | 说明 |
|---|---|---|---|
| 页面背景 | `#07070f` | `#fafafa` | 主背景，深色近黑/浅色近白 |
| 卡片背景 | `rgba(255,255,255,0.06)` | `rgba(0,0,0,0.04)` | **半透明色反相**：白半透明 → 黑半透明 |
| Hover | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.06)` | 同上 |
| 输入框背景 | `rgba(255,255,255,0.06)` | `#ffffff` | 浅色下输入框直接给纯白更醒目 |
| 主文字 | `#f1f0ff`（亮紫白）| `#171823`（深紫黑）| 反相，保留紫调一致性 |
| 次文字 | `#8b8ea8` | `#6b6e85` | 同上 |
| 边框 | `rgba(255,255,255,0.10)` | `rgba(0,0,0,0.08)` | 半透明反相 |
| 主色（按钮）| `#6c63ff` | `#5048cc` | 浅色背景下加深 18%，对比度足 |
| 强调色 | `#00b8e6` | `#0099cc` | 同上原则 |
| 成功 | `#00b894` | `#00a37e` | 浅色下加深 |
| 危险 | `#e74c3c` | `#d63a2a` | 同上 |
| 警告 | `#f39c12` | `#d4860a` | 同上 |
| Modal 阴影 | `rgba(0,0,0,0.6)` | `rgba(0,0,0,0.15)` | 浅色下阴影减弱 |

### 切换机制

- 用 `<html data-theme="dark">` / `<html data-theme="light">` 切换
- CSS 选择器 `[data-theme="dark"] :root { ... }` / `[data-theme="light"] :root { ... }` 各定义一套
- 默认 `data-theme="dark"`（保留现有体验）
- 用户切换后 localStorage 持久化，下次访问保持

---

## 改造步骤（分阶段，每阶段一次提交，不做大 PR）

### Stage 1 — inline 颜色全部替换为 var（不破坏现有体验）

**目标**：所有 `style={{ color: '#xxx' }}` / `background: 'rgba(...)'` 改成 `style={{ color: 'var(--color-xxx)' }}`，深色继续工作不变。

**具体改动**：

1. **DashboardPage.tsx** — 31 处
   - 大部分是图表 axis / legend / tooltip 颜色，对照现有 var 替换
   - ECharts 配置里的 `axisLine.lineStyle.color` / `tooltip.backgroundColor` 等改用 `getComputedStyle(document.documentElement).getPropertyValue('--color-xxx')` 动态读取（支持主题切换实时刷新图表）
2. **TeamDashboardPage.tsx** — 21 处（类似 DashboardPage）
3. **RecordDetailModal.tsx** — 17 处（弹窗各 section 标题 / 边框 / 背景）
4. **ReferenceList.tsx** — 12 处
5. **VideoGenerationPage / VideoDetailModal / ProfilePage / AuroraCanvas** — 共 ~28 处

**新增的 var**（覆盖现有 `:root` 没有的颜色）：
- `--color-modal-overlay`（替代各处 `rgba(0,0,0,0.6)` / `rgba(0,0,0,0.7)`）
- `--color-text-tertiary`（替代各处 `#888` / `#555`）
- `--color-bg-modal`（替代各处 `#1a1a2e` / `#111118`）
- `--color-shadow`（替代各处 `rgba(0,0,0,0.4)` 之类）

**验收**：
- `git diff` 看 109 处全部 `style={{ ... '#xxx' ... }}` 变 `var(--color-xxx)`
- 浏览器跑起来，肉眼对比改前改后无变化（因为 var 值还是原来的深色值）

**预估**：1-1.5 天（人）/ 2-3 小时（AI 连续 grep+sed+verify）

### Stage 2 — `:root` 拆 dark / light 两套

**目标**：CSS 层面准备好两套值，但不切换（默认 dark，等 Stage 3 切按钮）。

**改动**：`web/src/index.css`

```css
[data-theme="dark"] {
  --color-bg-page: #07070f;
  --color-text-primary: #f1f0ff;
  /* ... 35 个 var 全部 */
}

[data-theme="light"] {
  --color-bg-page: #fafafa;
  --color-text-primary: #171823;
  /* ... 同样 35 个 var 的浅色版本 */
}
```

`<html data-theme="dark">` 默认值在 `index.html` 写死，等 Stage 3 才动。

**验收**：
- 手动改 `index.html` 的 `data-theme="light"` 看一眼整页效果
- 不要求完美，对比强烈一眼能看出"啊它确实是浅色了"就行
- 列出"看着丑"的地方，进 Stage 4 修

**预估**：0.5 天（人）/ 1 小时（AI）

### Stage 3 — themeStore + 切换按钮 + 持久化

**改动**：

1. 新建 `web/src/store/theme.ts`（Zustand 风格，保持和其他 store 一致）：
   ```typescript
   import { create } from 'zustand';

   type Theme = 'dark' | 'light';

   interface ThemeState {
     theme: Theme;
     toggleTheme: () => void;
   }

   const STORAGE_KEY = 'airdrama-theme';
   const initialTheme: Theme =
     (localStorage.getItem(STORAGE_KEY) as Theme) || 'dark';
   document.documentElement.dataset.theme = initialTheme;

   export const useThemeStore = create<ThemeState>((set, get) => ({
     theme: initialTheme,
     toggleTheme: () => {
       const next = get().theme === 'dark' ? 'light' : 'dark';
       document.documentElement.dataset.theme = next;
       localStorage.setItem(STORAGE_KEY, next);
       set({ theme: next });
     },
   }));
   ```

2. 顶部加切换按钮（建议放 `Sidebar.tsx` 底部 / 用户头像旁，月亮/太阳 SVG 图标）

3. ECharts 等动态依赖 CSS var 的图表，订阅 theme 变化重新 render（用 `useThemeStore((s) => s.theme)` 作为 key 触发重渲染）

**验收**：
- 点击按钮深↔浅切换流畅
- 刷新页面保持上次选择
- 登录页 / 错误页等所有路由都生效

**预估**：0.5 天（人）/ 1 小时（AI）

### Stage 4 — 浅色色板调试 / 边角料修复

切完之后**一定**会发现：
- 某个按钮文字不可见（对比度不足）
- 某个磁玻璃 backdrop 太透看不清
- 某个图表的 grid line 浅色下消失
- ECharts tooltip 颜色没跟着切

**做法**：每页跑一遍，列 bug 表，逐个调整 `[data-theme="light"]` 块里的具体值。

**预估**：1-1.5 天（人）/ 2-3 小时（AI 配合用户截图反馈）

### Stage 5 — 回归 vitest + 手测

- vitest 跑一遍（不会因为颜色变化挂，主要看依赖 DOM 结构的 snapshot test 没崩）
- 每个页面深 / 浅各走一遍：登录页 / 生成页 / 个人中心 / 7 个 admin 页 / 视频详情弹窗 / 任务详情弹窗 / 公告弹窗 / Toast

**预估**：0.5-1 天（人）/ 1 小时（AI 跑测试 + 用户走手测）

---

## 关键技术点（容易踩坑）

### 1. 半透明色反相不能简单替换

`rgba(255,255,255, 0.06)` 不是变成 `rgba(0,0,0, 0.06)`，**透明度也要调整**。白半透明在深色背景下肉眼看是"浅色卡片"；黑半透明在浅色背景下看是"深色卡片"，但人眼对深色对比的敏感度不同。

**经验值**：浅色透明度通常比深色 -20%~-40%。比如深色 0.06 对应浅色 0.04。

### 2. ECharts 等图表的颜色需要 JS 同步切换

CSS variable 改了，但 ECharts 已经渲染的图表不会自动重新读 var。两种方案：

- **方案 A**：图表内部颜色用 `getComputedStyle(document.documentElement).getPropertyValue('--color-xxx').trim()`，且组件内 `useEffect(theme => render)` 触发重渲染。
- **方案 B**：所有图表配置传入颜色直接读 themeStore 的 theme 值，动态返回不同 hex。

推荐 A（保持单一颜色源）。

### 3. AuroraCanvas 极光动效

登录页 `AuroraCanvas.tsx` 是 canvas 画的极光渐变，硬编码紫色调。
- **暗色**：紫蓝极光好看
- **浅色**：极光放在白底上会刺眼

方案：浅色模式下整个 AuroraCanvas 直接 `display: none`，背景换成 `#fafafa` 纯净白，反而更"高级"。

### 4. 玻璃效果 backdrop-filter

不少地方用 `backdrop-filter: blur(24px)` + 白半透明做磨砂玻璃。浅色下 backdrop-filter 仍然有效，但底层颜色要换成黑半透明（`rgba(0,0,0,0.04)`）。

### 5. 主色对比度（WCAG AA）

`#6c63ff` 紫色按钮：
- 深色背景 + 白字：对比度 ~7.1（AAA 级别）✓
- 浅色背景 + 白字：对比度 ~4.4（接近 AA 边界，但按钮上的小字可能不够）
- 解决：浅色模式 `--color-primary: #5048cc`（加深 18%），按钮上白字对比度 ~6.8（AAA）

---

## 验证清单

切换前后两种主题下都要看：

- [ ] 登录页（含 AuroraCanvas 切换）
- [ ] 生成页（卡片 / 输入框 / @ 标签 / 滚动条）
- [ ] 提示词标签（缩略图 / 文字）
- [ ] 任务卡片各种状态（生成中 / 完成 / 失败）
- [ ] 个人中心（消费图表）
- [ ] 公告弹窗（HTML 渲染）
- [ ] Toast / 各种 Modal / Dropdown
- [ ] 7 个 admin 页（Dashboard / Users / Records / Settings / Security / Logs / Assets）
- [ ] 团管 4 个页（TeamDashboard / TeamMembers / TeamRecords / TeamAssets）
- [ ] 火山 EP / 任务详情 / 录像弹窗

---

## 工作量预估

| Stage | 描述 | 人工时 | AI 工时 |
|---|---|---|---|
| 1 | inline 颜色 → var | 1-1.5 天 | 2-3 小时 |
| 2 | dark/light 两套 var | 0.5 天 | 1 小时 |
| 3 | themeStore + 切换按钮 | 0.5 天 | 1 小时 |
| 4 | 浅色色板调试 | 1-1.5 天 | 2-3 小时 |
| 5 | 回归测试 | 0.5-1 天 | 1 小时 |
| **总计** | | **3.5-5 天** | **7-9 小时（AI 连续）** |

---

## 不做的

- **跟随系统主题**（`prefers-color-scheme: light`）：以后再加，初版手动切换就够
- **多套主题**（如 sepia 米色 / 高对比度无障碍模式）：用户没要求
- **管理后台和用户端独立主题**：保持一致更简单
- **每个团队管理员可定制配色**：复杂度爆炸，不做

---

## 风险点

1. **改 `:root` 默认 hex 写法 → 改成 `[data-theme]` 选择器后，原来组件的 var 引用都还能正确解析**（CSS 优先级要确认。`[data-theme="dark"]` 选择器优先级 0,1,0；`:root` 优先级 0,0,1。前者会胜出 ✓）
2. **AuroraCanvas 在浅色下隐藏** 的产品决策需要用户确认（也可以保留，但调淡）
3. **ECharts 重渲染开销**：每次切主题所有图表 re-render 一遍，仪表盘 6 个图表加起来 ~200ms 卡顿可接受
4. **localStorage 在隐身模式 / 用户禁用时**：fallback 到 dark，不报错

---

## 参考资料

- Linear 浅色主题色板：https://linear.app（直接 toggle 看）
- Vercel 浅色主题：https://vercel.com（同上）
- WCAG 对比度计算：https://webaim.org/resources/contrastchecker/
- `prefers-color-scheme` MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme

---

## Critical Files

修改：
- `web/src/index.css` — `:root` 拆 `[data-theme="dark/light]` 两套
- `web/src/store/theme.ts` — **新建**
- `web/src/pages/DashboardPage.tsx` — 31 处颜色替换
- `web/src/pages/TeamDashboardPage.tsx` — 21 处
- `web/src/components/RecordDetailModal.tsx` — 17 处
- `web/src/components/ReferenceList.tsx` — 12 处
- `web/src/components/Sidebar.tsx` — 加切换按钮（位置待定）
- 其余 ~28 处 inline 颜色散落的组件

不动：
- 后端代码（纯前端改造）
- DB schema
- 现有路由 / API
