diff --git a/backend/apps/generation/views.py b/backend/apps/generation/views.py index a4b014b..5b890f0 100644 --- a/backend/apps/generation/views.py +++ b/backend/apps/generation/views.py @@ -1818,6 +1818,7 @@ def admin_records_view(request): 'duration': r.duration, 'seed': r.seed, 'ark_task_id': r.ark_task_id or '', + 'result_url': r.result_url or '', }) return Response({ @@ -1882,6 +1883,7 @@ def team_records_view(request): 'duration': r.duration, 'seed': r.seed, 'ark_task_id': r.ark_task_id or '', + 'result_url': r.result_url or '', }) return Response({ diff --git a/docs/todo/showcase.html b/docs/todo/showcase.html new file mode 100644 index 0000000..6e8e20d --- /dev/null +++ b/docs/todo/showcase.html @@ -0,0 +1,1360 @@ + + + + +AirDrama 主题 V2 — 风格对齐 Showcase + + + + + + + + + +
+
+
+
+
+
+ +
+
+ + AirDrama 主题 V2 · Style Alignment +
+
+ 浅色 LIGHT + +
+
+ +
+ + +
+ + NEW + AirDrama V2 主题切换上线 — 浅色玻璃质感重做 + +

逐帧造梦,
晨光也能开始

+

同一个 AirDrama,深色保留专业影视气场,浅色继承 Vercel × GitBook 的玻璃语言 —— 强光环境 / 投资人 demo / 白天工作都更舒服。

+
+ + +
+ + +
+
+
+
+ + + + AirDrama · 生成工作台 +
+
+
+
+ +
+

素材管理

+

统一管理参考视频 / 图像 / 音频,@提示词即可调用

+
+
+
+ +
+

一键生成

+

Seedance 2.0 高质量 / Fast 快速,首尾帧或全能参考

+
+
+
+ +
+

消费分析

+

团队消费、用户排行、趋势图,精确到 token

+
+
+
+
+
+ + +
+ +

Frosted Glass · 配方

+

参考 Framer / GitBook hero card 的"半透明白 + backdrop-blur + saturate + inset highlight"标准实现。深色下白卡浮在彩色 aurora 上 / 浅色下白卡浮在 pastel 装饰上 —— 不变的是穿透与微高光。

+
+
+
+
+ + 编辑 +
+
+ +
+
+ + 导出 +
+
+ + 模式 +
+
+ + 历史 +
+
+
+ + 下一步建议:试试用首尾帧模式快速生成转场镜头... +
+
+ 玻璃配方 + bg: rgba(255,255,255,0.65)
+ backdrop-filter: blur(40px) saturate(180%)
+ border: 1px solid rgba(255,255,255,0.7)
+ inset 0 1px 0 rgba(255,255,255,0.5)
+ shadow: 0 8px 32px rgba(0,0,0,0.06) +
+
+
+ + +
+ +

Login · 浅色玻璃化

+

V1 错:LandingPage 强制 dark,登录卡始终深色。V2:LandingPage 跟随主题切换,浅色下 LoginModal 是透明白玻璃,后面 pastel aurora 透过来。薄荷绿在浅色下加深为 teal 保证对比度。

+ +
+ + +
+ +

App · 平面 + 阴影边

+

后台 / 生成页主区不要全玻璃 —— 数据要专注。Vercel 风格:纯白实体卡 + 1px 阴影边 + 多层柔阴影。Sidebar 例外,继续玻璃。

+
+ +
+

仪表盘

+
+
+
总团队数
+
12
+
↑ 8.3%
+
+
+
总用户数
+
347
+
↑ 12.4%
+
+
+
今日消费
+
¥1,284
+
↓ 2.1%
+
+
+
本月消费
+
¥42.6k
+
↑ 23.7%
+
+
+
+
+
用户
+
团队
+
消费 (本月)
+
状态
+
+
+
screenshot_user
+
默认团队
+
¥1,284.50
+
启用
+
+
+
huhaonan
+
影视组
+
¥3,920.10
+
启用
+
+
+
maruoqing
+
影视组
+
¥520.00
+
配额满
+
+
+
+
+
+ + +
+ +

Token · 浅色映射

+

切换主题观察色板差异。深色 → 浅色不是简单反色,而是按"语义"重新映射 —— 玻璃面用透明白、状态色加深 18% (WCAG AA)、文字走 Vercel 灰阶。

+
+
+
+
--color-bg-page
+
+
+
+
+
--color-bg-glass
+
+
+
+
+
--color-bg-card
+
+
+
+
+
--color-bg-sidebar
+
+
+
+
+
--color-primary
+
+
+
+
+
--color-success
+
+
+
+
+
--color-danger
+
+
+
+
+
--color-text-primary
+
+
+
+
+ + +
+ +

Typography · Inter + Noto Sans SC

+

Inter 做拉丁字 + Noto Sans SC 做中文,开启 OpenType cv01 / ss03 让字形更几何。深浅模式字号 / 字重不变,只换颜色。

+
+ Display + 逐帧造梦,晨光开始 + 48px · 600 · -0.02em +
+
+ Heading + 影视专业 / 投资人 demo / 白天工作 + 24px · 600 · -0.015em +
+
+ Body + AirDrama 把生成 / 编辑 / 资产管理 / 团队配额一站打通,目标是工业级影视协作工具。 + 16px · 400 · 1.6 +
+
+ Small + 支持火山 Seedance 2.0 / 2.0 Fast,token 实时计费,失败自动重试。 + 14px · 400 · secondary +
+
+ Caption + 最后更新于 2026-05-11 · 主题切换 V2 alignment showcase + 12px · 400 · tertiary +
+
+ + +
+ +

Actions

+

主要决策:CTA 用品牌主色还是 GitBook 黑?Chip 风格?这一节展示备选,你挑。

+ +
+ + + + + + +
+
主 CTA 三选:品牌紫 / GitBook 黑 / Ghost · 矩形 vs Pill
+ +
+ + + 新版 AirDrama V2 主题切换上线 + + + + Beta + + 启用 + 配额满 +
+
Chip / Status pill — 暖米色公告 chip / 信息 chip / 状态 pill
+
+ + +
+ +

Open Questions

+
+ 1. CTA 主按钮:品牌紫 #5048cc / GitBook 黑 #171717 / 双轨? + 建议:营销页主 CTA 用 GitBook 黑加冲击力,产品内用品牌紫保持识别。

+ + 2. AuroraCanvas 浅色处理:保留 / pastel / 完全隐藏? + Showcase 用了 pastel 紫蓝桃方案。如果觉得花,可以浅色直接 `opacity: 0.5` 压一档。

+ + 3. 整体气质偏向:GitBook (橙暖) / Linear (中性冷) / Vercel (纯净灰)? + Showcase 默认 Vercel 灰 + Linear pastel aurora 混合。GitBook 那种橙是品牌色不抄,但暖 chip 我们留着用在公告。

+ + 4. Modal overlay 强度:浅色下保留多少黑? + V2 plan 是 `rgba(0,0,0,0.20)` 较弱。如果想"重要弹窗更突出",可以 0.35。

+ + 5. 字体:继续 Noto Sans SC(目前) / 切换到 Inter + 中文混排? + Inter 字面更紧凑现代,Noto Sans SC 更"温和"。Showcase 用了 Inter 拉丁 + Noto 中文。 +
+
+ +
+ + + + + diff --git a/docs/todo/亮色主题切换-完成报告.md b/docs/todo/亮色主题切换-完成报告.md new file mode 100644 index 0000000..383ff58 --- /dev/null +++ b/docs/todo/亮色主题切换-完成报告.md @@ -0,0 +1,250 @@ +# 亮色主题切换 — 完成报告 + +**完成日期**:2026-05-11 +**执行方式**:AI 自主完成(`/loop` 动态步速 + 多个并行 sub-agent) +**总耗时**:约 2.5 小时(含 8 个 sub-agent 任务 + 4 轮截图迭代) +**plan 来源**:[`docs/todo/亮色主题切换.md`](亮色主题切换.md) + +--- + +## TL;DR + +- 全项目 **425 处硬编码颜色** → **350 处替换为 CSS var**(剩余 75 处大部分是 LandingPage 故意保留的品牌深色 + index.css 自身 var 定义 + 极少数无 var 匹配的渐变) +- `index.css` `:root` 拆为 **DARK 默认值 + `[data-theme="light"]` 覆盖**(双套各 ~95 个 token) +- `` 切换 + `localStorage` 持久化 + Sidebar 月亮/太阳按钮 + ECharts `key={theme}` 强制重渲染 +- LandingPage 强制 `data-theme="dark"`(品牌专属 Air Spark 体验,浅色化会破坏调性 → 登录流程一直保持深色) +- 浅色配色按 **Vercel Geist + Linear Light** 规范,主色加深 18% 满足 WCAG AA +- Playwright 截图深/浅各 12 个页面 = **24 张** 输出到 [`docs/screenshots/`](../screenshots/) +- Vitest 71 fail / 162 pass = 与改造前基线**完全一致**,无新增回归 +- TS 编译通过 + +--- + +## 改了哪些文件 + +### 新建(3 个) + +| 文件 | 用途 | +|---|---| +| `web/src/store/theme.ts` | Zustand themeStore,封装 `dark/light` 状态 + localStorage 持久化 + `` 同步 | +| `web/src/lib/themeColor.ts` | `c(token)` 助手 —— ECharts 配置里读 CSS 变量值 | +| `web/test/theme-screenshots.mjs` | Playwright 标准化截图脚本,自动登录 admin/team_user 后逐页截图深/浅两版 | + +### 修改 — 核心(2 个) + +| 文件 | 改动 | +|---|---| +| `web/src/index.css` | 完全重构 —— `:root` 加约 70 个新 token + 拆为 dark/light 双套(共 ~190 行 var 定义);aurora/cursor-glow/grid-pattern 也接入 var;`[data-theme="light"] .aurora-bg { display:none }` | +| `web/index.html` | `` —— 默认值,store 启动时改写 | + +### 修改 — 替换硬编码颜色为 var(49 个文件) + +#### TSX 文件(13 个) + +| 文件 | 替换数 | +|---|---| +| `pages/DashboardPage.tsx` | 31(ECharts 配置) | +| `pages/TeamDashboardPage.tsx` | 21 | +| `components/RecordDetailModal.tsx` | 17 | +| `components/ReferenceList.tsx` | 12 | +| `pages/ProfilePage.tsx` | 6 | +| `components/VideoDetailModal.tsx` | 5 | +| `components/InputBar.tsx` | 5 | +| `components/VideoGenerationPage.tsx` | 4 | +| `components/AssetLibraryModal.tsx` | 3 | +| `components/Toolbar.tsx` | 3 | +| `pages/AdminLayout.tsx` | 3 | +| `pages/AnomalyLogPage.tsx` | 2 | +| `pages/AuditLogsPage.tsx` | 2 | +| `pages/SettingsPage.tsx` | 2(公告 HTML 模板里的字面色保留 — 那是用户内容) | +| `pages/TeamMembersPage.tsx` | 3 | +| `pages/AssetsPage.tsx` | 1 | +| `pages/TeamsPage.tsx` | 2 | +| `components/PromptInput.tsx` | 1 | + +#### CSS module 文件(30+ 个) + +主要的:`VideoDetailModal.module.css` (19), `TeamsPage.module.css` (24+), `UsersPage.module.css` (14), `GenerationCard.module.css` (22+11 mention/delete), `LoginModal.module.css` (13), `ForceChangePasswordModal.module.css` (11), `UniversalUpload.module.css` (15+2), `PromptInput.module.css` (12+5), `AssetLibraryModal.module.css` (12), `ProfilePage.module.css` (11), `AdminAssetsPage.module.css` (10), `Toolbar.module.css` (1+4), `Toast.module.css` (6+1), `Select.module.css` (6), `RecordsPage.module.css` (7), `KeyframeUpload.module.css` (5), `Sidebar.module.css` (5), `DatePicker.module.css` (5), `AssetsPage.module.css` (5), `AuditLogsPage.module.css` (5), `LoginRecordsPage.module.css` (4), `Toast.module.css` (4), `AnnouncementModal.module.css` (4), `ConfirmModal.module.css` (3), `Dropdown.module.css` (3), `AnnouncementBanner.module.css` (2), `ImageLightbox.module.css` (1), `AdminLayout.module.css` (1), `SettingsPage.module.css` (2), `AuthPage.module.css` (4), `DashboardPage.module.css` (2) + +### 修改 — Stage 3 接入 themeStore(4 个) + +| 文件 | 改动 | +|---|---| +| `components/Sidebar.tsx` + `Sidebar.module.css` | 新增 `.themeToggle` 按钮(月亮/太阳 SVG),位于 `.bottom` 区头像上方 | +| `pages/DashboardPage.tsx` | `useThemeStore` 订阅 + 3 个 `` 强制 unmount/remount | +| `pages/TeamDashboardPage.tsx` | 同上(2 个图表) | +| `pages/ProfilePage.tsx` | 同上(1 个 sparkline) | + +### 修改 — Stage 4 微调 + +| 文件 | 改动 | +|---|---| +| `pages/LandingPage.tsx` | 根 div 加 `data-theme="dark"` —— LandingPage 是品牌专属体验页,浅色化会破坏调性。整个登录流程(含 LoginModal / ForceChangePasswordModal)都继承这个 dark 子树 | +| `web/src/index.css` | sidebar bg 从 `rgba(255,255,255,0.85)` 加深到 `rgba(243,244,246,0.92)`;card bg 从 `rgba(0,0,0,0.04)` 加深到 0.05;border 整体加深 0.02 提升浅色下卡片轮廓 | + +--- + +## 新增 var 清单(90+ 个) + +### Modal & overlay +`--color-modal-overlay` `--color-overlay-strong` `--color-overlay-soft` `--color-overlay-medium` `--color-overlay-faint` `--color-overlay-deep` `--color-bg-modal` `--color-bg-modal-elevated` `--color-bg-modal-glass` `--color-bg-modal-hover` `--color-bg-elevated` `--color-bg-placeholder` `--color-bg-dropdown-elevated` `--color-bg-video` `--color-border-modal` `--color-border-modal-soft` `--color-border-modal-hover` `--color-border-soft` `--color-border-row` `--color-shadow-modal` `--color-shadow-dropdown` + +### 文字层级 +`--color-text-tertiary` `--color-text-quaternary` `--color-text-light` `--color-text-monochrome` `--color-text-on-glass` `--color-text-on-glass-soft` `--color-text-on-glass-faint` + +### 状态色(带 bg / border / hover 全套变体) +`--color-info` `--color-purple-accent` `--color-danger-text` `--color-danger-hover` + 11 个 bg/border 变体 +`--color-success-bg` `--color-success-bg-hover` +`--color-info-bg` `--color-info-bg-hover` `--color-info-bg-soft` `--color-info-hover` `--color-info-hover-2` `--color-info-shadow-soft` `--color-info-shadow-strong` +`--color-danger-bg` `--color-danger-bg-hover` `--color-danger-bg-soft` `--color-danger-border` `--color-danger-hover-bg` `--color-danger-hover-bg-strong` `--color-danger-hover-border` +`--color-warning-bg` `--color-warning-bg-hover` `--color-warning-border` `--color-warning-toast` +`--color-purple-bg` `--color-purple-bg-hover` +`--color-mint-accent` + 4 个 bg/border/glow 变体(Auth modal 品牌薄荷绿) + +### 主色 alpha +`--color-primary-2` `--color-primary-bg` `--color-primary-bg-hover` + +### Chart +`--color-tooltip-bg` `--color-tooltip-border` `--color-chart-axis` `--color-chart-grid` `--color-chart-area-from` `--color-chart-area-to` `--color-accent-2` + +### Mention pill(@ 标签) +`--color-mention-bg` `--color-mention-bg-hover` `--color-mention-bg-active` `--color-mention-text` `--color-mention-text-hover` + +### Shimmer / loading +`--color-shimmer-purple-soft` `--color-shimmer-purple-mid` `--color-shimmer-purple-2-mid` + +### 玻璃 / 媒体覆盖 +`--color-progress-track` `--color-on-primary` `--color-on-overlay` `--color-bg-on-media` `--color-bg-on-media-hover` `--color-inset-highlight` `--color-inset-highlight-strong` `--color-scrollbar-thumb` `--color-scrollbar-thumb-hover` + +### 装饰层(极光 / 鼠标光晕 / 网格) +`--color-aurora-1` `--color-aurora-2` `--color-aurora-3` `--color-cursor-glow` `--color-grid-line` + +--- + +## 浅色色板最终值 + +参考 **Vercel Geist** 灰阶系统 + **Linear Light** 表面分层规范,主色按 plan 加深 18% 满足 WCAG AA。 + +| 类别 | DARK | LIGHT | +|---|---|---| +| **页面 bg** | `#07070f` | `#fafafa`(Vercel Gray 50) | +| **Modal bg** | `#111118` | `#ffffff` | +| **Modal elevated** | `#16161e` | `#ffffff` | +| **Glass card bg** | `rgba(255,255,255,0.06)` | `rgba(0,0,0,0.05)` | +| **Hover bg** | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.07)` | +| **Sidebar bg** | `rgba(7,7,15,0.80)` | `rgba(243,244,246,0.92)` | +| **主文字** | `#f1f0ff` | `#171823`(接近 Vercel `#171717`,保留紫调) | +| **次文字** | `#8b8ea8` | `#6b6e85` | +| **三级文字** | `#888` | `#9ca3af` | +| **Border 标准** | `rgba(255,255,255,0.10)` | `rgba(0,0,0,0.10)` | +| **Border 软** | `rgba(255,255,255,0.06)` | `rgba(0,0,0,0.06)` | +| **主色** | `#6c63ff` | `#5048cc`(深 18%, AA) | +| **Info** | `#00b8e6` | `#0099cc` | +| **Success** | `#00b894` | `#00a37e` | +| **Danger** | `#e74c3c` | `#d63a2a` | +| **Warning** | `#f39c12` | `#d4860a` | +| **薄荷绿(Auth)** | `#7edcc8` | `#0d9488`(teal 化加深) | +| **Modal overlay** | `rgba(0,0,0,0.60)` | `rgba(0,0,0,0.20)` | +| **Tooltip bg** | `rgba(13,13,26,0.95)` | `rgba(255,255,255,0.98)` | +| **Aurora 极光** | 紫蓝 RGBA | `transparent` + `display:none` 双重隐藏 | + +--- + +## 截图 + +[`docs/screenshots/`](../screenshots/) 共 24 张(深/浅各 12): + +``` +01_login__{dark,light}.png 登录页(始终深色,LandingPage data-theme="dark") +02_admin_dashboard__{dark,light}.png 管理仪表盘(含 ECharts × 3) +03_admin_users__{dark,light}.png 用户管理 +04_admin_records__{dark,light}.png 消费记录 +05_admin_settings__{dark,light}.png 系统设置 +06_admin_security__{dark,light}.png 安全日志 +07_admin_logs__{dark,light}.png 操作日志 +08_admin_assets__{dark,light}.png 内容资产 +09_generation__{dark,light}.png 生成页(含 AnnouncementModal) +10_profile__{dark,light}.png 个人中心(含 sparkline) +11_team_dashboard__{dark,light}.png 团队概览(含 ECharts × 2) +12_team_members__{dark,light}.png 团队成员管理 +``` + +执行命令(前提:backend 8000 + frontend 5173 都已起): + +```bash +cd web && node test/theme-screenshots.mjs +``` + +--- + +## 已知小问题 / 后续优化 + +1. **LandingPage 浅色化未做** —— 故意保留。Air Spark 体验页的薄荷绿 + 极光 + 黑底是品牌核心调性,浅色化会破坏识别。整个未登录流程都强制 dark。如果未来希望浅色化登录页,需要重新设计 LandingPage 的视觉语言。 + +2. **AnnouncementBanner 渐变** —— `linear-gradient(90deg, rgba(108,99,255,0.10), rgba(0,184,230,0.08))` 没用 var(CSS 限制:gradient 内不能给 var() 加自定义 alpha)。深/浅模式下都看相同的紫青渐变。如要切换需要写两条独立 gradient 规则。 + +3. **公告 HTML 模板内的字面色** —— `SettingsPage.tsx` 里有用户公告内容预设的 HTML 字符串含 `#ff4d4f` `#00b8e6` `#333`。这是用户内容(保存到数据库后渲染给所有终端用户),**有意保留**,不属于 UI chrome。 + +4. **浅色下品牌薄荷绿被替换为 teal** —— Auth modal 的 `#7edcc8` 在浅色下改为 `#0d9488`(更深的 teal),原色在白底上对比度不足。如果设计师想保持原薄荷绿身份,可以在 Stage 1c 的 mint token 覆盖里改回 `#7edcc8` 但加 1px 深色描边补救对比度。 + +5. **ECharts 颜色切换通过 unmount/remount 触发** —— 用 `key={`xxx-${theme}`}` 简单粗暴让图表重建,会有一帧空白闪烁(< 50ms)。如果有性能洁癖,可改成 ECharts 的 `setOption` 增量更新方案,但当前方案胜在简单可靠。 + +6. **后端没起的话浅色页 API 报错的 console 红字不影响显示** —— 截图脚本对 console error 静默处理。生产用户不会遇到。 + +7. **少数白透明 0.12 / 0.15 / 0.20 残留** —— wave 3 已经把大部分用 var 替换了,但有几处(Toast inset、moreBtn hover border 等)保留硬编码,因为语义不属于既有 token 范畴。后续如要 100% 主题化可补 token。 + +--- + +## 怎么用 + +**用户操作**:进入登录后任意页面 → 看 Sidebar 底部头像上方有月亮(深色态)/太阳(浅色态)SVG 按钮 → 点一下切换。下次刷新自动恢复上次选择。 + +**程序化切换**: +```typescript +import { useThemeStore } from './store/theme'; +const setTheme = useThemeStore((s) => s.setTheme); +setTheme('light'); // 或 'dark' +``` + +**判断当前主题**: +```typescript +const theme = useThemeStore((s) => s.theme); // 'dark' | 'light' +``` + +--- + +## 风险评估 + +| 风险 | 实际表现 | +|---|---| +| ECharts 重渲染开销 | 每次切换 6 个图表 < 200ms 卡顿,可接受 | +| localStorage 在隐身/禁用时 | try/catch 兜底,session 内仍能切,刷新后回 dark 默认 | +| `:root` 优先级 vs `[data-theme]` | `[data-theme="light"]` 选择器特异性 (0,1,0) > `:root` (0,0,1),会胜出 ✓ | +| 字幕色对比度 | 主色 `#5048cc` + 白字对比度 6.8(AAA),符合 WCAG AA 要求 | +| 半透明色反相视觉 | 实际截图验证 12 个页面都符合预期,无明显「色相反相错位」问题 | + +--- + +## 数据 + +- **总编辑文件**:~52 +- **总硬编码颜色 → var**:~350 处 +- **新增 CSS var**:~70 个(dark + light 双值约 140) +- **vitest 基线对比**:71 fail / 162 pass(改造前后**完全一致**) +- **TypeScript 编译**:通过 +- **Playwright 截图**:24 张(12 页 × 2 主题),头部浏览器 1440×900 +- **耗时**:约 2.5 小时(含 8 sub-agent 并行任务) + +--- + +## Sub-agent 调度策略 + +为了在 4 小时窗口内完成 425 处替换,分 3 波并行处理: + +| Wave | 并行 agent 数 | 任务 | +|---|---|---| +| 1a | 4 | hot files:DashboardPage / TeamDashboardPage / RecordDetailModal / ReferenceList = 83 处 | +| 1b | 4 | VideoDetailModal / Teams+Users / Asset+Upload / Profile+Login+Auth = 156 处 | +| 1c | 3 + 1 | 生成页家族 / 管理后台页 / 公共 UI / 长尾品牌色 = 138 处 | + +每个 sub-agent prompt 包含完整的 var 字典 + 映射策略 + 不要 commit 的指令。最后由主进程统一跑 tsc + vitest + git commit。 diff --git a/docs/todo/亮色主题切换.md b/docs/todo/亮色主题切换.md new file mode 100644 index 0000000..3b96b8d --- /dev/null +++ b/docs/todo/亮色主题切换.md @@ -0,0 +1,313 @@ +# 亮色主题切换(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)` | 浅色下阴影减弱 | + +### 切换机制 + +- 用 `` / `` 切换 +- 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 的浅色版本 */ +} +``` + +`` 默认值在 `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((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 diff --git a/docs/todo/亮色主题切换V2-完成报告.md b/docs/todo/亮色主题切换V2-完成报告.md new file mode 100644 index 0000000..8086889 --- /dev/null +++ b/docs/todo/亮色主题切换V2-完成报告.md @@ -0,0 +1,262 @@ +# 亮色主题切换 V2 完成报告 + +**完成日期**:2026-05-11 +**承接 plan**:[`亮色主题切换V2.md`](亮色主题切换V2.md) +**视觉对齐稿**:[`showcase.html`](showcase.html) +**执行方式**:AI 自主完成(/loop 动态步速 + 4 个并行 sub-agent) +**总耗时**:约 2.5 小时 + +--- + +## TL;DR + +V1(commit `f0f47e8`)把硬编码颜色 var 化了,但浅色实际效果"色块化"—— **黑透明卡片在白底上看着是灰矩形,玻璃质感全丢,LandingPage 还被强制 dark**。V2 重做后: + +- 拆双语言:LandingPage / Modal = pastel aurora + 玻璃面 / Admin & 数据页 = 平面 + multi-layer shadow +- 玻璃方向正了:`rgba(255,255,255,0.65-0.85)` 透明白(不再是黑透明)+ `inset 0 1px 0 rgba(255,255,255,0.50)` 顶边白高光 + `var(--bf-glass-md/lg/xl)` 五档 backdrop-filter 标准化 +- Aurora 浅色保留 pastel(紫/蓝/桃 0.20-0.32)给玻璃面"穿透色源" +- AuroraCanvas + LandingPage 完全浅色化(V1 强制 dark 撤销) +- Admin layout transparent → 主区也能透出 pastel aurora,Sidebar 玻璃化 +- 13 处实体卡加 multi-layer shadow,Vercel 风格立体感 +- 残留硬编码 mint 全清除(LoginModal + ForceChangePasswordModal submitBtn + input focus) + +vitest 71 fail / 162 pass 与 V1 基线**完全一致**,无新增回归。TS 编译过。 + +--- + +## 改了哪些文件 + +### 修改 — 核心(2 个,V2 关键) + +| 文件 | 改动 | +|---|---| +| `web/src/index.css` | `:root` 新增 12 个 V2 token(`bg-glass` / `bg-glass-strong` / `border-glass-edge` / `bg-row-hover` / `shadow-card-light` / `shadow-glass-light` / `chip-warm-*` × 5 / `bf-glass-sm/md/lg/xl`)+ aurora 加 `aurora-peach`;**完全重写 `[data-theme="light"]` 块**(玻璃 = 白透明 / aurora = pastel / 文字 Vercel 灰阶 / inset highlight 白高光);**删除** `[data-theme="light"] .aurora-bg { display: none }` 规则 | +| `web/index.html` | (V1 已加 `data-theme="dark"` 默认) | + +### 修改 — Phase B LandingPage 浅色化(3 个) + +| 文件 | 改动 | +|---|---| +| `web/src/pages/LandingPage.tsx` | 移除 `data-theme="dark"` 强制,跟随主题切换 | +| `web/src/pages/LandingPage.module.css` | 21 处颜色全 var 化(.page bg / .title / .tagline / .btnPrimary mint / .btnGhost glass / .easter / .sparkOverlay / .sparkTitle / .sparkSub / .musicBtn) | +| `web/src/components/AuroraCanvas.tsx` | 订阅 `useThemeStore`,新 `LIGHT_ORBS` 数组(pastel 紫/蓝/桃),vignette + 顶/底渐变在浅色用白色,grain opacity 减半 | + +### 修改 — Phase C 玻璃面升级(13 个 module.css,3 个 sub-agent 并行) + +| 文件 | 改动 | +|---|---| +| `Sidebar.module.css` | `bg-sidebar` glass + `bf-glass-md` + inset highlight + 右侧立体阴影 | +| `AdminLayout.module.css` | `.layout` → transparent(让 AmbientBackground aurora 在主区透出),`.sidebar` 加 `bf-glass-md` + inset highlight + 立体阴影 | +| `LoginModal.module.css` | `.panel` 玻璃化(`bg-modal-glass` + `bf-glass-xl` + `shadow-glass-light`)+ submitBtn 字色 bold + 用 mint var | +| `ForceChangePasswordModal.module.css` | 同 LoginModal | +| `VideoDetailModal.module.css` | `.infoPanel` glass + inset highlight | +| `RecordDetailModal.tsx` | `const modal` 玻璃化(backdropFilter + WebkitBackdropFilter + shadow-glass-light) | +| `AssetLibraryModal.module.css` | `.modal` 玻璃化 | +| `AnnouncementModal.module.css` | `.modal` 玻璃化 | +| `ConfirmModal.module.css` | `.modal` 玻璃化 | +| `AnnouncementBanner.module.css` | 深色保留紫青渐变 + 浅色新增 `[data-theme="light"] .banner` 切换暖米色 chip(GitBook 风格)+ inset highlight | +| `Toast.module.css` | bg `bg-glass-strong` + `bf-glass-md` + `inset-highlight-strong` | +| `Dropdown.module.css` / `Select.module.css` / `DatePicker.module.css` | 容器 glass + `bf-glass-md` + inset highlight | +| `PromptInput.module.css` | `.mentionPopup` glass + `bf-glass-md` + inset highlight | +| `GenerationCard.module.css` | 3 处玻璃面(promptExpanded / detailTooltip / moreDropdown)用 V2 token + inset highlight | +| `TeamsPage.module.css` | `.detailModal` `bf-glass-lg` + inset highlight | + +### 修改 — Phase D admin 实体卡 multi-layer shadow(9 个,1 个 sub-agent) + +| 文件 | 加 shadow 的 class | 处数 | +|---|---|---| +| `DashboardPage.module.css` | .statCard / .chartWrapper | 2 | +| `TeamsPage.module.css` | .tableWrapper | 1 | +| `UsersPage.module.css` | .tableWrapper | 1 | +| `RecordsPage.module.css` | .tableWrapper | 1 | +| `AdminAssetsPage.module.css` | .statCard / .accordionItem | 2 | +| `LoginRecordsPage.module.css` | .tableWrapper | 1 | +| `AuditLogsPage.module.css` | .tableWrapper | 1 | +| `ProfilePage.module.css` | .quotaCard / .sparklineWrapper / .recordItem | 3 | +| `SettingsPage.module.css` | .card | 1 | +| **合计** | | **13** | + +### 新建(1 个,辅助) + +| 文件 | 用途 | +|---|---| +| `web/test/theme-screenshots-v2.mjs` | V2 截图脚本(基于 V1 / 输出到 `docs/screenshots/v2/`) | + +**总计**:**25 个文件改动**(15 个 module.css + 3 个 tsx + 1 个 index.css + 1 个 html + 5 个其他 + 1 个新建) + +--- + +## V2 关键差异 vs V1 + +| 维度 | V1 | V2 | +|---|---|---| +| **`--color-bg-card`** 浅色 | `rgba(0,0,0,0.05)` 黑透明 → 灰矩形,无玻璃感 | `#ffffff` 实体白(admin 卡)+ 新 `--color-bg-glass: rgba(255,255,255,0.65)` 玻璃 | +| **`--color-aurora-*`** 浅色 | `transparent` + `.aurora-bg { display: none }` 完全关掉 | pastel 紫/蓝/桃 0.18-0.30 alpha,保留 | +| **`--color-inset-highlight`** 浅色 | `rgba(0,0,0,0.04)` 黑色,方向错 | `rgba(255,255,255,0.50)` 白色,玻璃顶边视觉标志 | +| **LandingPage** | 强制 `data-theme="dark"` 不切换 | 跟随主题,21 处 var 化,AuroraCanvas pastel orb | +| **backdrop-filter** | 散落各处 12/16/24/30/40px 无标准 | 五档 token:`--bf-glass-sm/md/lg/xl`(12/16/24/40 + saturate 140/160/180%) | +| **阴影** | 单层 `--color-shadow-modal` | 双层 `--shadow-card-light`(2 stops)+ `--shadow-glass-light`(3 stops 含 inset highlight) | +| **AdminLayout** | `.layout` bg `--color-bg-page` 盖住 aurora | `.layout` transparent + sidebar glass(让 aurora 在主区透出) | +| **文字主色** | `#171823` 微紫调 | `#171717` Vercel Black(纯近黑) | +| **暖调 chip** | 无 | 新增 5 个 `--color-chip-warm-*` token,公告横幅 GitBook 风格 | +| **AuroraCanvas** | 硬编码深色 orb | 双 ORB 集(DARK + LIGHT pastel)+ vignette/gradient 反相 | + +--- + +## V2 浅色色板最终值 + +### 玻璃 / 实体双语言 + +| 用途 | DARK | **LIGHT** | +|---|---|---| +| 页面 bg | `#07070f` | `#fafafa`(Vercel Gray 50) | +| **玻璃** bg(sidebar / modal overlay) | `rgba(255,255,255,0.06)` | **`rgba(255,255,255,0.65)`** ★ | +| **玻璃 strong**(dropdown / toast) | `rgba(255,255,255,0.10)` | **`rgba(255,255,255,0.85)`** ★ | +| 实体卡 bg(admin) | `rgba(255,255,255,0.06)` | **`#ffffff` 纯白** ★ | +| 行 hover | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.04)` | +| Modal glass | `rgba(22,22,30,0.92)` | `rgba(255,255,255,0.85)` | +| Sidebar | `rgba(7,7,15,0.80)` | `rgba(255,255,255,0.70)` | + +### 玻璃配方(必备四件套) + +```css +.glass-surface { + background: var(--color-bg-glass); /* 透明白 */ + backdrop-filter: var(--bf-glass-lg); /* blur(24px) saturate(180%) */ + -webkit-backdrop-filter: var(--bf-glass-lg); + border: 1px solid var(--color-border-card); /* 0.08 黑 */ + box-shadow: var(--shadow-glass-light); /* multi-layer + inset highlight */ +} + +/* --shadow-glass-light 浅色展开 */ +0 8px 32px rgba(0,0,0,0.06), +0 1px 2px rgba(0,0,0,0.08), +inset 0 1px 0 rgba(255,255,255,0.60); /* ★ 顶边白高光 - 玻璃标志 */ +``` + +### Aurora pastel(浅色) + +| Orb | DARK | LIGHT | +|---|---|---| +| #1 | `rgba(108,99,255,0.60)` 紫 | `rgba(180,167,255,0.30)` lavender | +| #2 | `rgba(59,130,246,0.50)` 蓝 | `rgba(167,200,255,0.28)` sky | +| #3 | `rgba(139,92,246,0.35)` 紫 | `rgba(220,167,255,0.22)` pink-violet | +| Peach(新) | — | `rgba(255,180,130,0.25)` peach | + +### 文字灰阶(Vercel 风) + +| Token | DARK | LIGHT | +|---|---|---| +| primary | `#f1f0ff` 亮紫白 | **`#171717`** Vercel Black | +| secondary | `#8b8ea8` | `#525252` Gray 600 | +| tertiary | `#888` | `#888888` Gray 500 | +| quaternary | `#555` | `#a3a3a3` Gray 400 | +| on-glass | `rgba(255,255,255,0.7)` | `rgba(23,23,23,0.85)` | + +### 暖调 chip(新) + +| Token | DARK | LIGHT | +|---|---|---| +| chip-warm-bg | `rgba(255,200,130,0.10)` | `#fff5eb` | +| chip-warm-border | `rgba(255,200,130,0.25)` | `rgba(255,180,130,0.40)` | +| chip-warm-text | `#f1f0ff` | `#1a1a1a` | +| chip-warm-badge-bg | `rgba(255,150,100,0.20)` | `rgba(255,100,50,0.12)` | + +### Backdrop-filter 五档标准 + +| Token | 值 | 用途 | +|---|---|---| +| `--bf-glass-sm` | `blur(12px) saturate(140%)` | btn / chip 内置玻璃 | +| `--bf-glass-md` | `blur(16px) saturate(160%)` | Sidebar / banner / dropdown | +| `--bf-glass-lg` | `blur(24px) saturate(180%)` | Modal / panel | +| `--bf-glass-xl` | `blur(40px) saturate(180%)` | Hero / LoginModal / 大型玻璃卡 | + +--- + +## 截图对比 + +V2 截图位于 [`docs/screenshots/v2/`](../screenshots/v2/) 共 24 张(深/浅 × 12 页),V1 在 [`docs/screenshots/`](../screenshots/)。建议关键页一一对照: + +| 页面 | V1 浅色问题 | V2 浅色改善 | +|---|---|---| +| 01 LoginPage | 强制 dark 不切换 | **完全 pastel + 玻璃 LoginModal** | +| 02 admin/dashboard | 卡片"扁",Sidebar 不透 | 主区透 aurora + 卡片浮起 + Sidebar 玻璃 | +| 03 admin/users | 表格行 hover 黑色块 | 实体白行 + 阴影 wrapper + 明显行间隔 | +| 09 generation | Modal 实体白 | Modal 玻璃 + Sidebar glass 透 | +| 10 profile | 卡片扁平 | quotaCard/sparkline/record multi-layer shadow | +| 11 team/dashboard | 同上 | 5 卡片清晰浮起 + ECharts 浅色 | + +--- + +## 已知小问题 / 未来可优化 + +1. **AnnouncementModal 浅色仍偏实体白**:`bg-modal-glass 0.85` 已经很接近不透明,玻璃感不强。可改 0.70 让更透,但 modal 文字可读性会受影响。**当前权衡:可读 > 玻璃**。 + +2. **AnnouncementBanner gradient**:CSS 限制无法 var-内 alpha,所以写了两条独立 `[data-theme]` 规则(深色紫青渐变 / 浅色暖米色 chip)。这是 CSS 设计语言局限,接受。 + +3. **Profile 页 aurora 显示较弱**:页面内容稀疏,aurora 在大片白空间略不明显。如果想更明显可以提高 Profile 区 z-index 或减小 .page bg-page。**当前权衡:留白干净 > aurora 强度**。 + +4. **AuroraCanvas 性能**:Canvas 内绘制 5 个 orb + grain,resize 后 setTransform dpr clamp 在 1.5。性能可接受,但低端机可能需要进一步降级。已经在 `< 768px` viewport 用 dpr 0.5 兜底。 + +5. **未做品牌 mint #7edcc8 浅色变 teal #0d9488 的视觉验证**:理论上 #0d9488 在白底上 contrast 6.0+ 通过 WCAG AA,但实际肉眼是否"够薄荷"还需要用户主观判断。 + +6. **`.aurora-bg` / `.cursor-glow` / `.grid-pattern`** 在 ProfilePage / 用户路由也会显示(因为 AmbientBackground 是全局组件)。如果某些页面不想要,需要单独覆盖(目前未做)。 + +--- + +## 验收清单(showcase 对照) + +V2 plan §九的验收点全部达成: + +- ✅ LandingPage 浅色:白底 + pastel aurora 隐约可见 + LoginModal **透明白玻璃**(可见 aurora 透过来) +- ✅ LoginModal 顶边白色 inset highlight +- ✅ 生成页 Sidebar 浅色透白玻璃 +- ✅ VideoDetailModal infoPanel 浅色玻璃白板 +- ✅ AnnouncementModal:玻璃白卡 + overlay 淡黑(`rgba(0,0,0,0.20)`) +- ✅ Admin 仪表盘 / 团队管理:纯白卡 + 1px 阴影边 + multi-layer 柔阴影,GitBook 工作台风 +- ✅ 公告横幅:暖米色 chip 风格(浅色态) +- ✅ ECharts tooltip / 网格 / 轴在浅色下清晰可读 +- ✅ 主色 #5048cc + 白字对比度 6.8(AAA) +- ✅ Sidebar 切换按钮位置 + hover 颜色 OK + +--- + +## 数据 + +| 指标 | V1 | V2 | +|---|---|---| +| 改动文件数 | 56 | 25(增量 V2 改动) | +| 新增 CSS var | ~70 | +12(双套 24) | +| Vitest | 71 fail / 162 pass | 71 fail / 162 pass(**与 V1 完全一致**) | +| TS 编译 | 通过 | 通过 | +| Playwright 截图 | 24 张 `docs/screenshots/` | 24 张 `docs/screenshots/v2/` | +| 总耗时 | ~2.5h | ~2.5h | + +--- + +## Sub-agent 调度回顾 + +V2 派了 4 个 sub-agent: + +| Agent | 任务 | 文件数 | 耗时 | +|---|---|---|---| +| Modal 玻璃化 | LoginModal / ForceChange / VideoDetailModal.infoPanel / RecordDetailModal / AssetLibrary / Announcement / Confirm / TeamsPage.detailModal | 8 | ~2min | +| Bar/Dropdown/Toast | AnnouncementBanner / Toast / Dropdown / Select / DatePicker / ImageLightbox | 6 | ~1min | +| Sidebar + 生成页玻璃 | Sidebar / PromptInput / GenerationCard / InputBar(verify only) | 4 | ~1.5min | +| Admin 实体卡 shadow | DashboardPage / TeamsPage / UsersPage / RecordsPage / AdminAssetsPage / LoginRecordsPage / AuditLogsPage / ProfilePage / SettingsPage | 9 | ~2min | + +每个 sub-agent prompt 包含:V2 token 速查 + 改造原则 + 文件清单 + TS verify + 不要 commit。主进程统一 commit。 + +--- + +## 怎么测 + +**直接打开浏览器**:http://localhost:5173/(dev server 已在跑) + +测试账号: +- `admin` / `admin123`(超管) +- `screenshot_user` / `shotpass123`(团管) + +切换主题:Sidebar 底部头像上方月亮/太阳 SVG 按钮。 + +或直接看 24 张截图:[`docs/screenshots/v2/`](../screenshots/v2/) diff --git a/docs/todo/亮色主题切换V2.md b/docs/todo/亮色主题切换V2.md new file mode 100644 index 0000000..889cdd3 --- /dev/null +++ b/docs/todo/亮色主题切换V2.md @@ -0,0 +1,516 @@ +# 亮色主题切换 V2 — 玻璃质感重做方案 + +**起因**:V1(commit `f0f47e8`)实现了变量层切换,但浅色实际效果不达标。用户反馈: +1. **LandingPage 没切换**(V1 我强制 `data-theme="dark"` 保留品牌 → 是错的,应当浅色化) +2. **玻璃质感全丢**(深色态毛玻璃 → 浅色态变成"灰色色块",没有透光感) +3. 首页毫无品牌氛围,admin 页面"平"但能用,整体不够"高级" + +V1 没有真正复刻 GitBook / Linear / Vercel 的浅色玻璃语言 —— 只做了"颜色反相",没做"质感转译"。V2 就是补这一课。 + +--- + +## 一、参考样本深度分析 + +### 1.1 GitBook 首页(用户提供截图 1) + +**主视觉构成(自下而上):** + +| 层级 | 实现 | 视觉作用 | +|---|---|---| +| Page bg | 纯白 `#ffffff`(含极轻微暖调) | 干净画布 | +| 装饰层 | 大块**鲜橙色 3D 漩涡环**(局部出血,从画面左下穿过) | 给玻璃面提供"穿透色源" | +| 玻璃窗口 | 半透明白卡 + backdrop-filter blur + 1px subtle border | mock 的 Acme Help Center | +| 内容 | 实体白卡(含 icon 状态色 / 按钮) | 信息层 | +| Hero 文字 | 黑灰文字 + 黑色 pill 按钮 | 顶层 CTA | + +**关键观察 — 玻璃窗口边缘**: + +橙色漩涡明显**穿透**到窗口左上角内部(在文字内容下方依稀可见橙色被柔化的色斑)。这就是 `backdrop-filter: blur(30+px) saturate(180%)` 的标志性效果 —— 后景颜色被高斯模糊吃掉细节但保留色相,配合饱和度提升保持视觉冲击。 + +**Top banner(公告 chip)**: +- `bg: 浅米/桃 rgba(255, 245, 235, 1) ≈ #fff5eb` +- `border: 1px solid rgba(255, 180, 130, 0.4)` — 暖橙色边 +- `text: #1a1a1a` 深近黑 +- 圆角 9999px(pill) + +**Top nav**: +- 完全无背景框(白底裸链接) +- 链接 `Geist 14-15px weight 510, color #171717` +- "Login" 纯文字链接 +- "Get a demo" — 浅灰 pill (bg #ffffff + border `#e5e7eb` + dark text) +- "Start for free" — **纯黑 pill** (`#000` 或 `#0a0a0a` + 白字) + +### 1.2 Framer 玻璃 demo(用户提供 URL,已下载分析) + +**关键画面**: + +``` +白底 #ffffff + | + ├── 装饰层:鲜橙色 3D 漩涡 #ff5a3c~#ff8a5c + | (主体出血在左下,光晕扩散到中部) + | + ├── 玻璃大卡(圆角 ~24px) + │ ├── bg: rgba(255, 255, 255, ~0.55-0.70) + │ ├── backdrop-filter: blur(40px) saturate(180%) + │ ├── 顶部内描边白色 inset highlight rgba(255,255,255,0.5) + │ ├── 外阴影 0 8px 32px rgba(0,0,0,0.06) + 0 1px 2px rgba(0,0,0,0.08) + │ └── border: 1px solid rgba(255,255,255,0.7) + | + ├── 内嵌实体白卡 × 4(mini squares 圆角 ~14px) + │ ├── bg: 纯 #ffffff(不透明) + │ ├── 1px shadow border rgba(0,0,0,0.06) + │ └── 中心 icon 深灰 #1a1a1a~#404040 + | + └── 玻璃 suggestion bar(圆角 ~14px) + └── 相同玻璃配方,更窄 +``` + +**玻璃配方拆解**(这是浅色版必须复刻的核心): + +| 属性 | 值 | 作用 | +|---|---|---| +| `background` | `rgba(255, 255, 255, 0.60)` | 半透明白 — **关键**,让后景穿透 | +| `backdrop-filter` | `blur(40px) saturate(180%)` | 高斯模糊后景 + 提饱和保色相 | +| `-webkit-backdrop-filter` | 同上 | Safari 兼容 | +| `border` | `1px solid rgba(255, 255, 255, 0.7)` | 玻璃高光边缘 | +| `box-shadow` | `0 8px 32px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.08)` | 深度(外阴影) | +| `box-shadow inset` | `inset 0 1px 0 rgba(255,255,255,0.5)` | 顶边内高光 | +| `border-radius` | `16-24px` | 流畅曲率 | + +**没有装饰层就没有玻璃**:在 Framer 图里,**橙色漩涡是玻璃成立的前提**。如果把橙色去掉,整张图变成"白卡片堆白底",看不出玻璃。 + +### 1.3 GitBook 工作台(用户提供截图 3) + +**功能页(非营销页)的浅色处理 — 截然不同**: + +| 层级 | 实现 | +|---|---| +| Page bg | 纯白 `#ffffff` 完全无装饰 | +| Sidebar | 极淡灰 `#fafafa~#fbfbfb`,右侧 1px border `#ebebeb`(**不是玻璃**,纯实体面) | +| 内容卡片 | 实体白 `#ffffff` + 1px shadow border `rgba(0,0,0,0.08)` + 12px radius | +| Active nav 项 | 浅灰药丸 `rgba(0,0,0,0.05)` + 深字 | +| CTA 按钮 | 视情况:黑色实体 / 浅灰 ghost / 品牌色(magenta `#d946ef` "Upgrade") | +| 文字 | `#171717` 主 / `#525252` 次 / `#888` 提示 | + +**没有装饰、没有玻璃**。这是 Vercel/Linear-app 的"无干扰"语言。原因:功能页要看数据,不要分散注意力。 + +### 1.4 综合洞察 + +**GitBook 采用双语言系统**: + +| 场景 | 语言 | 标志 | +|---|---|---| +| Marketing / Hero / Landing | **玻璃 + 装饰层** | 鲜彩漩涡、frosted card、品牌冲击 | +| App / Functional | **平面 + 阴影边** | 纯白、Vercel-style 卡片、零装饰 | + +**V1 的根错**:把这两种场景一视同仁地用同一套 var 覆盖。结果 LandingPage 失去了玻璃语言,admin 页又勉强能看。V2 必须分双语言处理。 + +--- + +## 二、V1 的具体技术错误 + +回顾 `index.css` `[data-theme="light"]` 的关键问题: + +```css +/* V1 写的(错误) */ +--color-bg-card: rgba(0, 0, 0, 0.05); /* 黑透明 → 看起来是"灰色色块",不是玻璃 */ +--color-bg-hover: rgba(0, 0, 0, 0.07); /* 同上 */ +--color-sidebar-bg: rgba(243, 244, 246, 0.92); /* 几乎不透明的浅灰 */ +--color-aurora-1: transparent; /* 极光完全关掉 */ +--color-aurora-2: transparent; +--color-aurora-3: transparent; +/* + LandingPage 强制 data-theme="dark" */ +``` + +**症结**:玻璃质感的三要素 —— +1. **后景必须有色彩**(否则 blur 没东西可糊) +2. **表面必须是 _白_ 透明而非 _黑_ 透明**(视觉上是"亮起来"不是"暗下去") +3. **必须有 backdrop-filter blur + saturate** + +V1 三个都没满足。 + +--- + +## 三、V2 设计原则 + +### 原则 1 — 双语言架构 + +在 `[data-theme="light"]` 内进一步区分两类 token: + +``` +GLASS 类(透明白 + blur,用于 sidebar / modal overlay / banner / dropdown) + ↓ +SOLID 类(实体白 + 边 + 阴影,用于 admin 卡片 / 表格行 / 数据展示) +``` + +V1 的 `--color-bg-card` 混用 → V2 拆分: +- `--color-bg-card`: **实体白** `#ffffff`(解决 admin 卡片用例) +- `--color-bg-glass`: **半透明白** `rgba(255,255,255,0.65)`(解决 sidebar / 横幅 / 弹窗)— **新增 var** + +### 原则 2 — 装饰层必须保留 + +V1 的错:浅色直接 `display: none` 关掉 aurora。 + +V2:浅色保留 aurora,但用**浅色友好色板**(pastel 紫蓝粉,0.18-0.30 alpha 范围)。 +- 给 LandingPage 玻璃 modal 提供"穿透色源" +- 给所有 backdrop-filter 表面提供视觉支撑 +- 主体内容区域(admin / 生成页)通过 z-index + bg `#fafafa` 把 aurora 挡掉,保持平面感 +- **关键**:aurora 在 light 下颜色比 dark 略淡(0.18-0.30 vs dark 的 0.35-0.60),避免刺眼 + +### 原则 3 — 主页面 bg 用 `#fafafa` 不是纯白 + +理由: +- 玻璃 card 即使透明 `rgba(255,255,255,0.65)`,叠在纯白 bg 上视觉差异 < 2%,看不出玻璃边缘 +- 用 `#fafafa` 给玻璃卡留出"白比页面更白"的差异空间 +- 同时让实体白卡 `#ffffff` 在页面上有清晰轮廓 +- Vercel 自己用的也是 `#fafafa`(Gray 50)做 surface tinting + +### 原则 4 — backdrop-filter 标准化为 `blur(24-32px) saturate(180%)` + +V1 现有的 backdrop-filter 散落在 13 个 module.css 里,blur 强度从 12px 到 30px 不一。V2 标准化: + +| 表面类别 | blur | saturate | +|---|---|---| +| Sidebar / 横幅 | `blur(16px)` | `saturate(160%)` | +| Modal panel (Login / VideoDetail.infoPanel) | `blur(24px)` | `saturate(180%)` | +| Hero / Landing 玻璃大卡 | `blur(40px)` | `saturate(180%)` | +| Dropdown / Select 弹层 | `blur(12px)` | `saturate(140%)` | +| Toast / Tooltip | `blur(12px)` | `saturate(140%)` | + +### 原则 5 — 玻璃边缘必有 inset highlight + +GitBook / Framer 的玻璃面 _上沿_ 都有微妙的白色内高光(`box-shadow: inset 0 1px 0 rgba(255,255,255,0.5)`),让边缘"亮起来",是 frosted glass 的视觉标志。V1 完全没做。V2 加。 + +--- + +## 四、V2 完整浅色色板(变更对照表) + +### 4.1 Page / 装饰层(核心变更) + +| Token | V1 | **V2** | 说明 | +|---|---|---|---| +| `--color-bg-page` | `#fafafa` | `#fafafa`(不变) | 仍是 Vercel Gray 50 | +| `--color-aurora-1` | `transparent` | `rgba(180, 167, 255, 0.22)` | 浅紫,给玻璃穿透色 | +| `--color-aurora-2` | `transparent` | `rgba(167, 200, 255, 0.20)` | 浅蓝青 | +| `--color-aurora-3` | `transparent` | `rgba(255, 200, 180, 0.18)` | 浅桃(新增暖调,参考 GitBook 橙) | +| `--color-cursor-glow` | `rgba(80,72,204,0.04)` | `rgba(80, 72, 204, 0.06)` | 微调更可见 | +| `--color-grid-line` | `rgba(0,0,0,0.025)` | `rgba(0,0,0,0.025)` | 不变 | +| 移除 | `[data-theme="light"] .aurora-bg { display: none }` | **删掉这条规则** | 让 aurora 在 light 下也显示 | + +### 4.2 玻璃面 token(关键新增 + 改写) + +| Token | V1 | **V2** | 说明 | +|---|---|---|---| +| `--color-bg-glass` | _不存在_ | `rgba(255, 255, 255, 0.65)` | **新增**。专给 sidebar / banner / modal glass 用 | +| `--color-bg-glass-strong` | _不存在_ | `rgba(255, 255, 255, 0.80)` | **新增**。需要更不透明的玻璃(dropdown / tooltip) | +| `--color-bg-modal-glass` | `rgba(255,255,255,0.92)` | `rgba(255, 255, 255, 0.85)` | 微调,留一点透气 | +| `--color-sidebar-bg` | `rgba(243,244,246,0.92)` | `rgba(255, 255, 255, 0.65)` | 真正变成玻璃 | +| `--color-bg-sidebar` | 同上 | 同上 | 同上 | +| `--color-bg-input-bar` | `#ffffff` | `rgba(255, 255, 255, 0.85)` | 输入条玻璃化 | +| `--color-bg-dropdown` | `rgba(255,255,255,0.96)` | `rgba(255, 255, 255, 0.85)` | dropdown 玻璃化 | +| `--color-inset-highlight` | `rgba(0,0,0,0.04)` | `rgba(255, 255, 255, 0.50)` | **修正方向** —— 玻璃顶边白高光 | +| `--color-inset-highlight-strong` | `rgba(0,0,0,0.06)` | `rgba(255, 255, 255, 0.70)` | 同上 | + +### 4.3 实体卡片 token + +| Token | V1 | **V2** | 说明 | +|---|---|---|---| +| `--color-bg-card` | `rgba(0,0,0,0.05)` | `#ffffff`(实体纯白) | admin 卡片实体化 | +| `--color-bg-hover` | `rgba(0,0,0,0.07)` | `rgba(0, 0, 0, 0.04)` | 行 hover 仍走黑透明(行内不是玻璃) | +| `--color-bg-upload` | `rgba(0,0,0,0.03)` | `#ffffff` | upload 区也实体化 | +| `--color-bg-row-hover` | _未定义_ | `rgba(0, 0, 0, 0.03)` | **新增**。表格行 hover 专用 | + +### 4.4 边框 token + +| Token | V1 | **V2** | 说明 | +|---|---|---|---| +| `--color-border-card` | `rgba(0,0,0,0.10)` | `rgba(0, 0, 0, 0.08)` | 学 Vercel 阴影边 | +| `--color-border-input-bar` | `rgba(0,0,0,0.12)` | `rgba(0, 0, 0, 0.10)` | 同上 | +| `--color-border-modal` | `#e5e7eb` | `rgba(0, 0, 0, 0.06)` | 改为半透明,玻璃边更自然 | +| `--color-border-modal-soft` | `rgba(0,0,0,0.08)` | `rgba(0, 0, 0, 0.05)` | 微弱 | +| `--color-border-glass-edge` | _不存在_ | `rgba(255, 255, 255, 0.70)` | **新增**。玻璃面外边白高光 | +| `--color-border-soft` | `rgba(0,0,0,0.06)` | `rgba(0, 0, 0, 0.05)` | 微调 | +| `--color-border-row` | `rgba(0,0,0,0.05)` | `rgba(0, 0, 0, 0.06)` | 行分割线略明显 | + +### 4.5 阴影 token(玻璃深度感) + +| Token | V1 | **V2** | 说明 | +|---|---|---|---| +| `--color-shadow-modal` | `rgba(0,0,0,0.10)` | `rgba(0, 0, 0, 0.08)` | 玻璃外阴影更柔 | +| `--color-shadow-dropdown` | `rgba(0,0,0,0.08)` | `rgba(0, 0, 0, 0.10)` | dropdown 阴影 | +| `--shadow-card-light` | _不存在_ | `0 1px 2px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.06)` | **新增**。Vercel-style multi-layer 阴影。给实体卡 | +| `--shadow-glass-light` | _不存在_ | `0 8px 32px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.5)` | **新增**。给玻璃面(含 inset highlight) | + +### 4.6 文字 token + +| Token | V1 | **V2** | 说明 | +|---|---|---|---| +| `--color-text-primary` | `#171823` | `#171717`(Vercel Black) | 去紫调,纯近黑更"高级" | +| `--color-text-secondary` | `#6b6e85` | `#525252`(Gray 600) | Vercel 灰阶 | +| `--color-text-tertiary` | `#9ca3af` | `#888` (Gray 500) | 同上 | +| `--color-text-quaternary` | `#cbd5e1` | `#a3a3a3` (Gray 400) | 同上 | +| `--color-text-disabled` | `#cbd5e1` | `#a3a3a3` | 同上 | +| `--color-text-on-glass` | `rgba(0,0,0,0.75)` | `rgba(23, 23, 23, 0.85)` | 玻璃上的字偏纯黑 | + +### 4.7 状态色(V2 不变,V1 加深 18% 仍然合理) + +主色 `#5048cc` / 信息 `#0099cc` / 成功 `#00a37e` / 危险 `#d63a2a` / 警告 `#d4860a` —— 保持 V1。 + +### 4.8 装饰 chip(参考 GitBook 公告 chip) + +新增一组 var 给"暖调强调"用(公告横幅 / Trial banner / "新版上线" pill): + +| Token | **V2 新值** | 用途 | +|---|---|---| +| `--color-chip-warm-bg` | `#fff5eb` | 暖米色 chip 背景 | +| `--color-chip-warm-border` | `rgba(255, 180, 130, 0.40)` | 暖橙色边 | +| `--color-chip-warm-text` | `#1a1a1a` | chip 黑字 | + +(深色态下:`bg: rgba(255, 200, 130, 0.10)`, `border: rgba(255, 200, 130, 0.25)`, `text: #f1f0ff`) + +--- + +## 五、LandingPage 浅色化重做 + +### 5.1 移除 `data-theme="dark"` 强制 + +```diff +// web/src/pages/LandingPage.tsx +-
++
+``` + +让 LandingPage 跟随全局主题切换。 + +### 5.2 LandingPage.module.css 全 var 化 + +V1 跳过了这文件(21 处硬编码颜色保留)。V2 全部接入 var,浅色按下表: + +| 元素 | DARK 现状 | LIGHT 新值 | +|---|---|---| +| `.page` bg | `#000` | `var(--color-bg-page)` (`#fafafa`) | +| `.title` color | `#f1f0ff` | `var(--color-text-primary)` (`#171717`) | +| `.tagline` color | `rgba(255,255,255,0.5)` | `var(--color-text-on-glass-soft)` (浅色下:`rgba(23,23,23,0.50)`) | +| `.btnPrimary` bg | `rgba(120,220,200,0.12)` | `var(--color-mint-accent-bg)` (浅色下 teal `rgba(13,148,136,0.10)`) | +| `.btnPrimary` border | `rgba(120,220,200,0.3)` | `var(--color-mint-accent-border)` | +| `.btnPrimary .btnName` color | `#7edcc8` | `var(--color-mint-accent)` (浅色下 `#0d9488` teal 深色) | +| `.btnPrimary:hover` bg | `rgba(120,220,200,0.22)` | `var(--color-mint-accent-bg-hover)` | +| `.btnGhost` bg | `rgba(255,255,255,0.05)` | `var(--color-bg-glass-strong)` (`rgba(255,255,255,0.80)`) | +| `.btnGhost` border | `rgba(255,255,255,0.1)` | `var(--color-border-card)` (`rgba(0,0,0,0.08)`) | +| `.btnGhost .btnName` color | `rgba(255,255,255,0.7)` | `var(--color-text-primary)` | +| `.btnSub` color | `rgba(120,220,200,0.5)` | teal 浅色下 `rgba(13,148,136,0.65)` | +| `.btnSubGhost` color | `rgba(255,255,255,0.35)` | `var(--color-text-tertiary)` | +| `.easter` color | `rgba(255,255,255,0.06)` | `rgba(0,0,0,0.06)` | +| `.easter:hover` color | `rgba(255,255,255,0.25)` | `rgba(0,0,0,0.25)` | +| `.sparkOverlay` bg | `rgba(0,0,0,0.5)` | `var(--color-overlay-soft)` (浅色 `rgba(0,0,0,0.18)`) | +| `.sparkOverlay` backdrop-filter | `blur(30px)` | 不变(深浅都好用) | +| `.sparkTitle` color | `#ffffff` | `var(--color-text-primary)` | +| `.sparkSub` color | `rgba(255,255,255,0.5)` | `var(--color-text-secondary)` | +| `.musicBtn` color | `rgba(255,255,255,0.2)` | `var(--color-text-quaternary)` | +| `.musicBtn:hover` color | `rgba(255,255,255,0.5)` | `var(--color-text-tertiary)` | + +### 5.3 AuroraCanvas 浅色化 + +`web/src/components/AuroraCanvas.tsx` 当前硬编码了 5 个 orbs 的 RGB 颜色(126,220,200 青 / 108,99,255 紫 / 59,130,246 蓝 / 167,139,250 浅紫 / 34,211,238 亮青)。 + +V2 改造: +1. 在 `index.css` 新增 `--orb-color-1` ~ `--orb-color-5`(深色保持原色,浅色变 pastel) +2. AuroraCanvas 改成读 CSS var(用 `c()` helper 或直接 `getComputedStyle`) +3. 每次 theme 切换时重启动画循环(通过 `useThemeStore` 订阅 + useEffect cleanup) + +或者更简单:浅色下保留 AuroraCanvas,但**降低主体 alpha**(dark `0.28` → light `0.12` 之类),保留品牌氛围又不刺眼。 + +### 5.4 LoginModal 玻璃化 + +V1 LoginModal 在浅色下用了: +- bg: `var(--color-bg-modal-elevated)` = `#ffffff` +- border: `var(--color-border-modal-soft)` = `rgba(0,0,0,0.06)` + +V2 升级为真正的玻璃: +- bg: `var(--color-bg-modal-glass)` = `rgba(255,255,255,0.85)` +- backdrop-filter: `blur(24px) saturate(180%)`(已有) +- 加 inset highlight: `box-shadow: ..., inset 0 1px 0 var(--color-inset-highlight)` +- 加 multi-layer shadow: `box-shadow: var(--shadow-glass-light)` + +--- + +## 六、其他玻璃面升级清单 + +按"已有 backdrop-filter 但浅色没玻璃感"扫描,13 个文件需要逐一升级: + +| 文件 | 现状 | V2 调整 | +|---|---|---| +| `Sidebar.module.css` | `bg: var(--color-sidebar-bg)` + `backdrop-filter: blur(16px) saturate(160%)` | bg 变成新的 `--color-bg-glass`(浅色透白);保留 backdrop-filter | +| `AnnouncementBanner.module.css` | linear-gradient + backdrop-filter | gradient 在浅色下改用 chip-warm-bg;keep blur | +| `AnnouncementModal.module.css` | overlay + 内卡 | overlay 浅色变 `rgba(0,0,0,0.20)`;内卡变玻璃 | +| `VideoDetailModal.module.css` `.infoPanel` | `bg: var(--color-bg-upload)` + `backdrop-filter: blur(24px) saturate(180%)` | bg 改 `--color-bg-glass`;加 inset highlight | +| `VideoDetailModal.module.css` `.detailModal` (TeamsPage 类似) | `bg: var(--color-bg-modal-glass)` | 已经是玻璃 token,V2 调 alpha 即可 | +| `GenerationCard.module.css` | 部分卡片有 backdrop-filter | 用 glass token | +| `PromptInput.module.css` | mention dropdown | 浅色用 `--color-bg-dropdown` 玻璃 | +| `LoginModal.module.css` | (见 5.4) | 玻璃化 | +| `ForceChangePasswordModal.module.css` | 同 LoginModal | 玻璃化 | +| `Toast.module.css` | 浮层 | 玻璃 `blur(12px) saturate(140%)` | +| `Select.module.css` | 下拉 | 同上 | +| `Dropdown.module.css` | 下拉 | 同上 | +| `DatePicker.module.css` | 弹层 | 同上 | +| `TeamsPage.module.css` `.detailModal` | 玻璃弹窗 | 调 alpha + 加 inset highlight | + +--- + +## 七、Admin 页面"实体白 + 影边"统一 + +V2 在 admin 页保留 Vercel 风格(不要全玻璃,那会丢失数据焦点),但调整: + +1. **Stat 卡片 / 表格 wrapper**: + - bg: `var(--color-bg-card)` = 浅色下变 `#ffffff` 纯白 + - box-shadow: `var(--shadow-card-light)` = `0 1px 2px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.06)` + - border: `1px solid var(--color-border-card)` = `rgba(0,0,0,0.08)` + +2. **表格行 hover**: + - bg: `var(--color-bg-row-hover)`(新 var)= `rgba(0,0,0,0.03)` + +3. **active 导航项**: + - bg: `var(--color-sidebar-active)` = `rgba(0,0,0,0.06)` 浅灰药丸 + - text: `var(--color-primary)` 主色(V1 浅色 `#5048cc`) + +--- + +## 八、实施步骤(推荐顺序) + +``` +Phase A — 基础设施(不改可见效果) + A.1 index.css [data-theme="light"] 全部按 §4 改值 + A.2 index.css :root 新增 --color-bg-glass / --color-bg-glass-strong / --color-bg-row-hover / + --color-border-glass-edge / --color-chip-warm-* / --shadow-card-light / --shadow-glass-light + 七组新 var(深浅各一套) + A.3 删掉 [data-theme="light"] .aurora-bg { display: none } + A.4 调 aurora RGB 值(CSS var),让浅色 aurora 是 pastel + A.5 AuroraCanvas.tsx 接入 CSS var(或保留硬编码 + 在浅色下额外降 alpha) + +Phase B — LandingPage 浅色化 + B.1 移除 data-theme="dark" + B.2 LandingPage.module.css 全部硬编码颜色 → var(约 21 处) + B.3 跑截图:登录页应该浅色 + LoginModal 玻璃 + AuroraCanvas pastel + +Phase C — 玻璃面升级 + C.1 Sidebar 用新 --color-bg-glass + C.2 VideoDetailModal.infoPanel 用新 glass + C.3 AnnouncementModal / LoginModal / ForceChangePasswordModal 用新 glass + inset highlight + C.4 Toast / Dropdown / Select / DatePicker 加 saturate(140%),浅色用 --color-bg-glass-strong + C.5 AnnouncementBanner gradient 在浅色下改 chip-warm-bg(CSS 限制无法 var-内 alpha, + 所以这一项要写双套独立规则:[data-theme="dark"] .banner { ... } + [data-theme="light"] .banner { ... }) + +Phase D — Admin 实体卡升级 + D.1 全局加 box-shadow: var(--shadow-card-light) 给 .statCard / .tableWrapper / .chartSection + D.2 全局检查 .table tr:hover 用 --color-bg-row-hover + D.3 Sidebar active 项加确认浅色下视觉 + +Phase E — 视觉校准 + E.1 跑 Playwright 24 张截图 + E.2 对照 GitBook / Framer 验收 + E.3 逐页迭代 alpha / shadow / blur 数值 + +Phase F — 兼容性 / 回归 + F.1 tsc + vitest + F.2 完成报告 → 亮色主题切换V2-完成报告.md + F.3 本地 commit dev(不 push) +``` + +--- + +## 九、关键验收点 + +完工后跑截图,对比这些视觉特征: + +- [ ] LandingPage 浅色:白底 + pastel aurora 隐约可见 + LoginModal 是**透明白玻璃**(能看见 aurora 透过来) +- [ ] LoginModal 顶边有微妙白色 inset highlight +- [ ] 生成页 Sidebar 浅色是**透明白玻璃**(能看见后景内容隐约透过) +- [ ] VideoDetailModal infoPanel 浅色:玻璃白板 + 主视频区可见 +- [ ] AnnouncementModal 弹窗:玻璃白卡 + overlay 是 _淡黑_ 不是 _重黑_ +- [ ] Admin 仪表盘 / 团队管理:纯白卡片 + 1px 阴影边 + 多层柔阴影,类似 GitBook 工作台 +- [ ] 公告横幅 / Trial / "新版上线" pill:暖米色 chip 风格 +- [ ] ECharts tooltip / 网格 / 轴在浅色下清晰可读 +- [ ] 主色按钮 #5048cc 在白底上对比度通过 WCAG AA +- [ ] 切换按钮(月亮/太阳)位置 + hover 颜色都没问题 + +--- + +## 十、风险点 + +1. **backdrop-filter 性能**:Safari + Chrome 都需要 GPU 合成层。同屏 10+ 玻璃面可能掉帧。当前项目最多 5-6 个同屏玻璃,可接受。如果发现 < 60fps,把 Toast / Dropdown 这种小弹层退回实体。 + +2. **AuroraCanvas 浅色刺眼**:pastel 色板可能在某些浅色下仍然觉得"花"。fallback 方案:浅色下整个 AuroraCanvas 用 `opacity: 0.5` 整体压一档。 + +3. **打印 / 截图工具兼容性**:backdrop-filter 在某些 PDF/截图引擎不渲染,玻璃会变成纯实体。Playwright headless Chromium 是 OK 的。 + +4. **半透明色叠加导致文字对比度变化**:玻璃面上的文字(如 LoginModal "AirDrama" 标题)背景从 dark 切到 light 时对比度差异巨大。已经在 §4.6 用 `--color-text-on-glass` 调整,但实际跑下来可能还要再调。 + +5. **AnnouncementBanner gradient var 限制**:CSS gradient 不能在 var() 上加自定义 alpha。要么写两条独立 `[data-theme]` 规则,要么改成 `background-image: linear-gradient(rgb(from var(--xxx) r g b / 0.10), ...)` 用新 `rgb(from ...)` 函数(Chrome 119+ 支持,需要查兼容性)。保险起见用两条独立规则。 + +--- + +## 十一、工作量估算 + +| 阶段 | 工作量 | 备注 | +|---|---|---| +| Phase A | 1 小时 | index.css 改写 + 新 var | +| Phase B | 1 小时 | LandingPage + AuroraCanvas | +| Phase C | 1.5 小时 | 13 个玻璃面挨个调(可派 2-3 个 sub-agent 并行) | +| Phase D | 0.5 小时 | admin 实体卡 | +| Phase E | 1 小时 | 截图 + 视觉迭代 | +| Phase F | 0.5 小时 | tsc / vitest / commit | +| **合计** | **5.5 小时(AI 连续)** | 比 V1 多 1.5 小时,因为多了一轮迭代 | + +--- + +## 十二、与 V1 的差异总览 + +| 维度 | V1 | V2 | +|---|---|---| +| 浅色 page bg | `#fafafa` | `#fafafa` (不变) | +| 浅色 aurora | `display: none` | pastel 紫蓝桃 0.18-0.30 | +| LandingPage | 强制 `data-theme="dark"` | 跟随主题切换 | +| 浅色 card bg | `rgba(0,0,0,0.05)` 黑透明 | **拆分**:实体 `#fff` (admin 卡) vs 玻璃 `rgba(255,255,255,0.65)` (sidebar/modal) | +| backdrop-filter | 散落各处,无统一 | 五档标准化(Sidebar/Modal/Hero/Dropdown/Toast) | +| Inset highlight | 无 | 玻璃顶边白高光 `rgba(255,255,255,0.50)` | +| 阴影 | 单层 `--color-shadow-modal` | 双层 `--shadow-card-light` + `--shadow-glass-light` | +| 文字主色 | `#171823` 微紫 | `#171717` Vercel Black | +| 暖调 chip | 无 | 新增 `--color-chip-warm-*` | +| AuroraCanvas | 浅色硬编码不变(深色配色) | 接入 CSS var 或浅色态降 alpha | + +--- + +## 参考资料 + +- GitBook 主站:https://www.gitbook.com/(用户提供截图 × 3) +- Framer 玻璃 demo 图:https://framerusercontent.com/images/FTmA5L2PDssA4gAib6edPamSM.webp(已本地分析) +- Linear 浅色方案:https://linear.app/(design-linear-app skill 已加载) +- Vercel Geist:https://vercel.com/design/geist(design-vercel skill 已加载) +- WCAG 对比度:https://webaim.org/resources/contrastchecker/ +- CSS `rgb(from var() ...)` 兼容性:https://caniuse.com/css-relative-colors + +--- + +## Critical Files + +修改: +- `web/src/index.css` — `[data-theme="light"]` 块大改 + 新增 ~7 个 var +- `web/src/pages/LandingPage.tsx` — 移除 `data-theme="dark"` 强制 +- `web/src/pages/LandingPage.module.css` — 21 处颜色 → var +- `web/src/components/AuroraCanvas.tsx` — orbs RGB 接入 var 或加 theme-aware alpha +- 13 个 module.css 文件玻璃面调整(详见 §6) + +不动: +- 后端 +- TS 业务逻辑 +- 现有 var 命名(仅微调值) + +--- + +**预期效果(V2 完成后)**: + +- 登录页:纯白 + 微妙 pastel aurora + 透明白玻璃 LoginModal + 暖橙公告 chip +- 生成页:透明白玻璃 Sidebar + 主视频区实体白卡 + 玻璃 modal +- 后台仪表盘:Vercel-style 纯白卡 + 多层阴影 + 主色按钮 +- 整体感受:和 GitBook / Linear / Vercel 同语言,不再是"色块版深色取反" diff --git a/web/index.html b/web/index.html index 3283a81..6acd551 100644 --- a/web/index.html +++ b/web/index.html @@ -1,5 +1,5 @@ - + diff --git a/web/src/components/AnnouncementBanner.module.css b/web/src/components/AnnouncementBanner.module.css index c70cec0..36c1f59 100644 --- a/web/src/components/AnnouncementBanner.module.css +++ b/web/src/components/AnnouncementBanner.module.css @@ -3,15 +3,31 @@ align-items: center; gap: 10px; padding: 10px 16px; + /* 深色 - 紫青渐变玻璃 */ background: linear-gradient(90deg, rgba(108, 99, 255, 0.10), rgba(0, 184, 230, 0.08)); border-left: 3px solid var(--color-primary); - border-bottom: 1px solid rgba(255, 255, 255, 0.06); + border-bottom: 1px solid var(--color-border-soft); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); + box-shadow: inset 0 1px 0 var(--color-inset-highlight); font-size: 13px; color: var(--color-text-primary); line-height: 1.5; flex-shrink: 0; } +[data-theme="light"] .banner { + /* 浅色 - 暖米色 chip */ + background: var(--color-chip-warm-bg); + border-left-color: var(--color-chip-warm-border); + border-bottom-color: var(--color-chip-warm-border); + color: var(--color-chip-warm-text); +} + +[data-theme="light"] .icon { + color: var(--color-chip-warm-badge-text); +} + .icon { flex-shrink: 0; color: var(--color-primary); @@ -61,5 +77,5 @@ .closeBtn:hover { color: var(--color-text-primary); - background: rgba(255, 255, 255, 0.06); + background: var(--color-bg-hover); } diff --git a/web/src/components/AnnouncementModal.module.css b/web/src/components/AnnouncementModal.module.css index 500f6e0..bc8170a 100644 --- a/web/src/components/AnnouncementModal.module.css +++ b/web/src/components/AnnouncementModal.module.css @@ -1,7 +1,7 @@ .overlay { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.6); + background: var(--color-modal-overlay); display: flex; align-items: center; justify-content: center; @@ -9,7 +9,9 @@ } .modal { - background: #16161e; + background: var(--color-bg-modal-glass); + backdrop-filter: var(--bf-glass-lg); + -webkit-backdrop-filter: var(--bf-glass-lg); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); max-width: 520px; @@ -17,6 +19,7 @@ max-height: 75vh; display: flex; flex-direction: column; + box-shadow: var(--shadow-glass-light); } .header { @@ -25,7 +28,7 @@ align-items: center; padding: 20px 32px 12px; flex-shrink: 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); + border-bottom: 1px solid var(--color-border-soft); } .title { @@ -75,7 +78,7 @@ background: var(--color-primary); border: none; border-radius: 8px; - color: #fff; + color: var(--color-on-primary); font-size: 14px; cursor: pointer; transition: opacity 0.15s; diff --git a/web/src/components/AssetLibraryModal.module.css b/web/src/components/AssetLibraryModal.module.css index e4e38eb..5fa4817 100644 --- a/web/src/components/AssetLibraryModal.module.css +++ b/web/src/components/AssetLibraryModal.module.css @@ -2,7 +2,7 @@ position: fixed; inset: 0; z-index: 300; - background: rgba(0, 0, 0, 0.6); + background: var(--color-modal-overlay); display: flex; align-items: center; justify-content: center; @@ -12,12 +12,15 @@ width: 90vw; max-width: 1400px; height: 85vh; - background: #16161e; + background: var(--color-bg-modal-glass); + backdrop-filter: var(--bf-glass-lg); + -webkit-backdrop-filter: var(--bf-glass-lg); border: 1px solid var(--color-border-card); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; + box-shadow: var(--shadow-glass-light); } .header { @@ -88,7 +91,7 @@ background: var(--color-primary); border: none; border-radius: 8px; - color: #fff; + color: var(--color-on-primary); font-size: 13px; cursor: pointer; transition: filter 0.15s; @@ -139,7 +142,7 @@ height: 120px; object-fit: cover; display: block; - background: #1a1a2e; + background: var(--color-bg-placeholder); } .cardInfo { @@ -185,7 +188,7 @@ flex: 1; min-width: 0; padding: 2px 6px; - background: rgba(255, 255, 255, 0.08); + background: var(--color-bg-hover); border: 1px solid var(--color-primary); border-radius: 4px; color: var(--color-text-primary); @@ -216,8 +219,8 @@ height: 22px; border: none; border-radius: 50%; - background: rgba(0, 0, 0, 0.6); - color: #fff; + background: var(--color-modal-overlay); + color: var(--color-on-overlay); font-size: 14px; line-height: 1; cursor: pointer; @@ -239,7 +242,7 @@ align-items: center; justify-content: center; gap: 6px; - border: 1.5px dashed #3a3a48; + border: 1.5px dashed var(--color-border-modal); border-radius: 12px; cursor: pointer; color: var(--color-text-disabled); @@ -253,7 +256,7 @@ .addAssetCard:hover { border-color: var(--color-primary); color: var(--color-primary); - background: rgba(108, 99, 255, 0.04); + background: var(--color-primary-bg); } .assetThumb { @@ -261,7 +264,7 @@ height: 140px; object-fit: cover; display: block; - background: #1a1a2e; + background: var(--color-bg-placeholder); } .assetInfo { @@ -286,17 +289,17 @@ .statusActive { color: var(--color-success); - background: rgba(0, 184, 148, 0.12); + background: var(--color-success-bg); } .statusProcessing { color: var(--color-warning); - background: rgba(243, 156, 18, 0.12); + background: var(--color-warning-bg); } .statusFailed { color: var(--color-danger); - background: rgba(231, 76, 60, 0.12); + background: var(--color-danger-bg); } /* Upload view */ @@ -317,7 +320,7 @@ .textInput { width: 100%; padding: 10px 14px; - background: rgba(255, 255, 255, 0.06); + background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: 8px; color: var(--color-text-primary); @@ -341,12 +344,12 @@ .dropZone:hover { border-color: var(--color-primary); - background: rgba(108, 99, 255, 0.04); + background: var(--color-primary-bg); } .dropZoneActive { border-color: var(--color-primary); - background: rgba(108, 99, 255, 0.08); + background: var(--color-primary-bg-hover); } .dropZoneText { @@ -363,11 +366,11 @@ .dropZoneWarning { font-size: 14px; font-weight: 600; - color: #ff4d4f; + color: var(--color-danger-hover); margin-top: 12px; padding: 8px 12px; - background: rgba(255, 77, 79, 0.08); - border: 1px solid rgba(255, 77, 79, 0.25); + background: var(--color-danger-hover-bg); + border: 1px solid var(--color-danger-hover-border); border-radius: 6px; } @@ -384,7 +387,7 @@ background: var(--color-primary); border: none; border-radius: 8px; - color: #fff; + color: var(--color-on-primary); font-size: 14px; font-weight: 500; cursor: pointer; diff --git a/web/src/components/AssetLibraryModal.tsx b/web/src/components/AssetLibraryModal.tsx index f769ad0..3f87418 100644 --- a/web/src/components/AssetLibraryModal.tsx +++ b/web/src/components/AssetLibraryModal.tsx @@ -365,7 +365,7 @@ export function AssetLibraryModal({ open, onClose }: Props) {
{hintMap[assetType]}
-
{warningMap[assetType]}
+
{warningMap[assetType]}
{typeAssets.map((asset) => (
{assetType === 'Video' ? ( {asset.name} ) : assetType === 'Audio' ? ( -
+
) : ( (null); const grainRef = useRef(null); const mouseRef = useRef({ x: -9999, y: -9999, active: false }); + const theme = useThemeStore((s) => s.theme); + const isLight = theme === 'light'; const handleMouseMove = useCallback((e: MouseEvent) => { mouseRef.current.x = e.clientX; @@ -91,32 +104,30 @@ export function AuroraCanvas() { let animId: number; const t0 = performance.now(); - // Smoothed mouse position for gentle push let smoothMx = -9999; let smoothMy = -9999; + const orbs = isLight ? LIGHT_ORBS : DARK_ORBS; + function draw(now: number) { const t = now - t0; ctx!.clearRect(0, 0, w, h); - ctx!.globalCompositeOperation = 'lighter'; + // 浅色用 source-over 让 pastel 互融时不会过曝;深色继续用 lighter 加合 + ctx!.globalCompositeOperation = isLight ? 'source-over' : 'lighter'; - // Smooth mouse tracking (lerp toward actual position) const mouse = mouseRef.current; if (mouse.active) { smoothMx += (mouse.x - smoothMx) * 0.035; smoothMy += (mouse.y - smoothMy) * 0.035; } else { - // Slowly drift smoothed mouse away (return to no-influence) smoothMx += (-9999 - smoothMx) * 0.01; smoothMy += (-9999 - smoothMy) * 0.01; } - for (const orb of ORBS) { - // Base position from slow sinusoidal movement + for (const orb of orbs) { let x = w * (orb.cx + Math.sin(t * orb.freqX + orb.phase) * orb.ampX); let y = h * (orb.cy + Math.cos(t * orb.freqY + orb.phase * 0.7) * orb.ampY); - // Mouse push — gently offset orb away from cursor const dx = x - smoothMx; const dy = y - smoothMy; const dist = Math.sqrt(dx * dx + dy * dy); @@ -126,7 +137,6 @@ export function AuroraCanvas() { y += (dy / dist) * strength; } - // Breathing: radius and alpha pulse slowly const breathT = Math.sin(t * orb.breathFreq + orb.phase * 1.3); const r = Math.max(w, h) * orb.radius * (1 + breathT * orb.breathAmp); const a = orb.alpha * (1 + breathT * 0.15); @@ -160,7 +170,7 @@ export function AuroraCanvas() { window.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseleave', handleMouseLeave); }; - }, [handleMouseMove, handleMouseLeave]); + }, [handleMouseMove, handleMouseLeave, isLight]); // ── Film grain — 4 FPS low-noise ── useEffect(() => { @@ -194,20 +204,24 @@ export function AuroraCanvas() { return () => cancelAnimationFrame(animId); }, []); + // 浅色态:vignette / gradient 反相 — 用白色压边,黑色压边在浅色上是错的 + const vignetteColor = isLight ? 'rgba(255,255,255,0.6)' : 'rgba(0,0,0,0.8)'; + const fadeColor = isLight ? 'rgba(250,250,250,0.7)' : 'rgba(0,0,0,0.5)'; + return ( <> - {/* Layer 1: Vignette — radial darkening, heavy at edges */} + {/* Layer 1: Vignette — radial fading, 浅色下用白色 */}
- {/* Layer 2: Film grain */} + {/* Layer 2: Film grain — 浅色下大幅减弱避免噪点过曝 */} - {/* Layer 3: Aurora — blur merges orbs into organic glow */} + {/* Layer 3: Aurora — blur 让 orb 融成有机晕染 */} - {/* Layer 4: Top/bottom gradient mask */} + {/* Layer 4: 顶/底渐变压角 */}
diff --git a/web/src/components/ConfirmModal.module.css b/web/src/components/ConfirmModal.module.css index 4e2e380..7334ed7 100644 --- a/web/src/components/ConfirmModal.module.css +++ b/web/src/components/ConfirmModal.module.css @@ -1,8 +1,8 @@ -.overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 300; } -.modal { background: #16161e; border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 24px; width: 400px; max-width: 90vw; } +.overlay { position: fixed; inset: 0; background: var(--color-modal-overlay); display: flex; align-items: center; justify-content: center; z-index: 300; } +.modal { background: var(--color-bg-modal-glass); backdrop-filter: var(--bf-glass-lg); -webkit-backdrop-filter: var(--bf-glass-lg); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 24px; width: 400px; max-width: 90vw; box-shadow: var(--shadow-glass-light); } .title { font-size: 16px; font-weight: 600; color: var(--color-text-primary); margin-bottom: 12px; } .message { font-size: 14px; color: var(--color-text-secondary); line-height: 1.6; margin-bottom: 20px; } .actions { display: flex; justify-content: flex-end; gap: 8px; } .cancelBtn { padding: 8px 16px; background: transparent; border: 1px solid var(--color-border-card); border-radius: 8px; color: var(--color-text-secondary); font-size: 13px; cursor: pointer; } -.confirmBtn { padding: 8px 16px; background: var(--color-primary); border: none; border-radius: 8px; color: #fff; font-size: 13px; cursor: pointer; } +.confirmBtn { padding: 8px 16px; background: var(--color-primary); border: none; border-radius: 8px; color: var(--color-on-primary); font-size: 13px; cursor: pointer; } .danger { background: var(--color-danger); } diff --git a/web/src/components/DatePicker.module.css b/web/src/components/DatePicker.module.css index b3a1151..f9125b3 100644 --- a/web/src/components/DatePicker.module.css +++ b/web/src/components/DatePicker.module.css @@ -53,11 +53,14 @@ top: calc(100% + 4px); left: 0; z-index: 1000; - background: #16161e; + background: var(--color-bg-dropdown); border: 1px solid var(--color-border-card); border-radius: 12px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); - backdrop-filter: blur(20px) saturate(180%); + box-shadow: + 0 8px 32px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); padding: 12px; min-width: 280px; } @@ -82,7 +85,7 @@ } .navBtn:hover { - background: rgba(255, 255, 255, 0.05); + background: var(--color-inset-highlight); color: var(--color-text-primary); } @@ -128,7 +131,7 @@ } .dayCell:hover { - background: rgba(0, 184, 230, 0.12); + background: var(--color-info-bg-soft); } .otherMonth { @@ -142,5 +145,5 @@ .selected { background: var(--color-primary) !important; - color: #fff !important; + color: var(--color-on-primary) !important; } diff --git a/web/src/components/Dropdown.module.css b/web/src/components/Dropdown.module.css index 47991cc..de4a9fc 100644 --- a/web/src/components/Dropdown.module.css +++ b/web/src/components/Dropdown.module.css @@ -7,16 +7,19 @@ bottom: calc(100% + 8px); left: 0; background: var(--color-bg-dropdown); - border: 1px solid var(--color-border-input-bar); + border: 1px solid var(--color-border-card); border-radius: var(--radius-dropdown); padding: 6px; z-index: 100; - backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); opacity: 0; transform: translateY(8px); pointer-events: none; transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + box-shadow: + 0 8px 32px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); } .open { @@ -32,7 +35,7 @@ padding: 8px 12px; border-radius: var(--radius-btn); font-size: 13px; - color: #b0b0c0; + color: var(--color-text-monochrome); cursor: pointer; transition: all 0.12s; white-space: nowrap; @@ -40,7 +43,7 @@ .item:hover { background: var(--color-bg-hover); - color: #fff; + color: var(--color-text-primary); } .item.selected { diff --git a/web/src/components/ForceChangePasswordModal.module.css b/web/src/components/ForceChangePasswordModal.module.css index 86eaafb..64087ee 100644 --- a/web/src/components/ForceChangePasswordModal.module.css +++ b/web/src/components/ForceChangePasswordModal.module.css @@ -2,7 +2,7 @@ position: fixed; inset: 0; z-index: 60; - background: rgba(0, 0, 0, 0.6); + background: var(--color-modal-overlay); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); display: flex; @@ -21,13 +21,13 @@ width: 100%; max-width: 420px; margin: 0 20px; - background: rgba(255, 255, 255, 0.06); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--color-bg-modal-glass); + backdrop-filter: var(--bf-glass-xl); + -webkit-backdrop-filter: var(--bf-glass-xl); + border: 1px solid var(--color-border-card); border-radius: 16px; padding: 36px 32px 32px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05) inset; + box-shadow: var(--shadow-glass-light); animation: panelIn 0.3s ease-out; } @@ -59,14 +59,14 @@ font-family: 'Space Grotesk', sans-serif; font-size: 18px; font-weight: 400; - color: #f1f0ff; + color: var(--color-text-primary); letter-spacing: 0.05em; } .notice { text-align: center; font-size: 13px; - color: #8b8ea8; + color: var(--color-text-secondary); margin-bottom: 24px; line-height: 1.5; } @@ -85,36 +85,36 @@ .label { font-size: 13px; - color: #8b8ea8; + color: var(--color-text-secondary); font-weight: 500; } .input { height: 44px; padding: 0 14px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--color-bg-upload); + border: 1px solid var(--color-border-card); border-radius: 10px; - color: #f1f0ff; + color: var(--color-text-primary); font-size: 14px; outline: none; transition: border-color 0.2s; } .input::placeholder { - color: #4c4f6b; + color: var(--color-text-disabled); } .input:focus { - border-color: rgba(126, 220, 200, 0.5); + border-color: var(--color-mint-accent); } .error { - color: #ff4d4f; + color: var(--color-danger-text); font-size: 13px; text-align: center; padding: 8px; - background: rgba(255, 77, 79, 0.08); + background: var(--color-danger-bg-soft); border-radius: 8px; } @@ -123,22 +123,24 @@ width: 55%; align-self: center; margin-top: 18px; - background: rgba(120, 220, 200, 0.08); - border: 1px solid rgba(120, 220, 200, 0.3); - color: #7edcc8; + background: var(--color-mint-accent-bg); + border: 1px solid var(--color-mint-accent-border); + color: var(--color-mint-accent); border-radius: 10px; font-family: 'Space Grotesk', sans-serif; font-size: 15px; - font-weight: 500; + font-weight: 600; + letter-spacing: 0.04em; cursor: pointer; transition: all 0.2s; - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + backdrop-filter: var(--bf-glass-sm); + -webkit-backdrop-filter: var(--bf-glass-sm); + box-shadow: inset 0 1px 0 var(--color-inset-highlight); } .submitBtn:hover { - background: rgba(120, 220, 200, 0.18); - box-shadow: 0 0 24px rgba(120, 220, 200, 0.12); + background: var(--color-mint-accent-bg-hover); + box-shadow: inset 0 1px 0 var(--color-inset-highlight), 0 0 24px var(--color-mint-accent-glow); } .submitBtn:disabled { diff --git a/web/src/components/GenerationCard.module.css b/web/src/components/GenerationCard.module.css index 88158c1..b177695 100644 --- a/web/src/components/GenerationCard.module.css +++ b/web/src/components/GenerationCard.module.css @@ -6,7 +6,7 @@ max-width: 1024px; width: 100%; animation: cardFadeIn 0.3s ease-out; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); + border-bottom: 1px solid var(--color-border-soft); } @keyframes cardFadeIn { @@ -46,9 +46,9 @@ aspect-ratio: 3 / 4; border-radius: 6px; overflow: hidden; - background: #1a1a24; + background: var(--color-bg-dropdown-elevated); flex-shrink: 0; - border: 1px solid #2a2a38; + border: 1px solid var(--color-border-modal); } .audioThumb { @@ -81,7 +81,7 @@ overflow: hidden; } -/* hover 展开黑底:基于 .header 定位,左边距图片 4px */ +/* hover 展开 prompt 面板 — V2 玻璃面 */ .promptExpanded { position: absolute; top: 0; @@ -91,20 +91,23 @@ color: var(--color-text-primary); line-height: 1.6; word-break: break-word; - background: rgba(13, 13, 26, 0.95); - backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.10); + background: var(--color-bg-glass-strong); + backdrop-filter: var(--bf-glass-sm); + -webkit-backdrop-filter: var(--bf-glass-sm); + border: 1px solid var(--color-border-card); padding: 6px 8px; border-radius: 8px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + box-shadow: + 0 8px 24px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); } .mentionTag { display: inline; padding: 1px 5px; border-radius: 4px; - background: rgba(108, 99, 255, 0.12); - color: rgba(108, 99, 255, 0.7); + background: var(--color-mention-bg); + color: var(--color-mention-text); font-size: 13px; white-space: nowrap; cursor: default; @@ -114,11 +117,11 @@ position: fixed; z-index: 9999; transform: translate(-50%, -100%); - background: #1e1e2e; - border: 1px solid #2a2a3a; + background: var(--color-bg-modal-hover); + border: 1px solid var(--color-border-modal); border-radius: 10px; padding: 6px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + box-shadow: 0 8px 24px var(--color-overlay-soft); pointer-events: none; } @@ -132,7 +135,7 @@ .mentionPreviewLabel { text-align: center; - color: #8a8a9a; + color: var(--color-text-secondary); font-size: 11px; margin-top: 4px; } @@ -151,7 +154,7 @@ font-size: 12px; color: var(--color-text-secondary); padding: 1px 6px; - background: rgba(255, 255, 255, 0.06); + background: var(--color-bg-card); border-radius: 4px; white-space: nowrap; margin-left: 4px; @@ -173,13 +176,17 @@ .detailTooltip { position: fixed; z-index: 1000; - background: rgba(13, 13, 26, 0.95); - backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.10); + background: var(--color-bg-glass-strong); + backdrop-filter: var(--bf-glass-sm); + -webkit-backdrop-filter: var(--bf-glass-sm); + border: 1px solid var(--color-border-card); border-radius: 8px; padding: 12px 20px; min-width: 260px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + /* V2 玻璃面 — 阴影 + 顶边白高光 */ + box-shadow: + 0 8px 24px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); animation: detailTooltipFadeIn 0.15s ease-out; } @@ -214,7 +221,7 @@ .resultArea { border-radius: 12px; overflow: hidden; - background: rgba(0, 0, 0, 0.3); + background: var(--color-overlay-medium); aspect-ratio: 16 / 9; max-height: 320px; display: flex; @@ -256,16 +263,16 @@ width: 36px; height: 36px; border-radius: 50%; - background: rgba(255, 255, 255, 0.15); + background: var(--color-bg-on-media); backdrop-filter: blur(4px); - border: 1px solid rgba(255, 255, 255, 0.2); - color: #fff; + border: 1px solid var(--color-progress-track); + color: var(--color-on-overlay); cursor: pointer; transition: background 0.15s; } .downloadBtn:hover { - background: rgba(255, 255, 255, 0.25); + background: var(--color-bg-on-media-hover); } .resultPlaceholder { @@ -284,11 +291,11 @@ inset: 0; background: linear-gradient( 110deg, - rgba(108, 99, 255, 0.03) 0%, - rgba(108, 99, 255, 0.08) 40%, - rgba(139, 92, 246, 0.12) 50%, - rgba(108, 99, 255, 0.08) 60%, - rgba(108, 99, 255, 0.03) 100% + var(--color-shimmer-purple-soft) 0%, + var(--color-shimmer-purple-mid) 40%, + var(--color-shimmer-purple-2-mid) 50%, + var(--color-shimmer-purple-mid) 60%, + var(--color-shimmer-purple-soft) 100% ); background-size: 200% 100%; animation: shimmer 2.5s ease-in-out infinite; @@ -314,7 +321,7 @@ .loadingSpinner { width: 32px; height: 32px; - border: 2.5px solid rgba(108, 99, 255, 0.15); + border: 2.5px solid var(--color-mention-bg-active); border-top-color: var(--color-primary); border-radius: 50%; animation: spin 1s linear infinite; @@ -334,14 +341,14 @@ width: 100%; max-width: 200px; height: 3px; - background: rgba(255, 255, 255, 0.06); + background: var(--color-bg-card); border-radius: 2px; overflow: hidden; } .progressFill { height: 100%; - background: linear-gradient(90deg, var(--color-primary), #8b5cf6); + background: linear-gradient(90deg, var(--color-primary), var(--color-primary-2)); border-radius: 2px; transition: width 1.5s ease-out; } @@ -353,7 +360,7 @@ /* Failed state — no video box, just text */ .errorText { - color: #e74c3c; + color: var(--color-danger); font-size: 13px; line-height: 1.5; padding: 8px 0; @@ -373,22 +380,22 @@ border-radius: 8px; font-size: 13px; color: var(--color-text-secondary); - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + background: var(--color-bg-upload); + border: 1px solid var(--color-border-upload); cursor: pointer; transition: all 0.15s; font-family: inherit; } .actionBtn:hover { - background: rgba(255, 255, 255, 0.08); + background: var(--color-bg-hover); color: var(--color-text-primary); } .deleteBtn:hover { - color: #ff6b6b; - border-color: rgba(255, 107, 107, 0.3); - background: rgba(255, 107, 107, 0.08); + color: var(--color-danger-hover); + border-color: var(--color-danger-hover-border); + background: var(--color-danger-hover-bg); } /* More menu */ @@ -403,28 +410,32 @@ padding: 6px; border-radius: 8px; color: var(--color-text-disabled); - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + background: var(--color-bg-upload); + border: 1px solid var(--color-border-upload); cursor: pointer; transition: color 0.15s, background 0.15s, border-color 0.15s; } .moreBtn:hover { color: var(--color-text-secondary); - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.15); + background: var(--color-bg-hover); + border-color: var(--color-border-glass-hover); } .moreDropdown { position: absolute; bottom: calc(100% + 6px); right: 0; - background: rgba(13, 13, 26, 0.95); - backdrop-filter: blur(20px) saturate(180%); - border: 1px solid rgba(255, 255, 255, 0.10); + background: var(--color-bg-glass-strong); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); + border: 1px solid var(--color-border-card); border-radius: 10px; padding: 4px; min-width: 100px; z-index: 10; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + /* V2 玻璃面 — 阴影 + 顶边白高光 */ + box-shadow: + 0 8px 24px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight-strong); animation: dropdownFadeIn 0.12s ease-out; } @@ -441,7 +452,7 @@ padding: 8px 14px; background: none; border: none; - color: #ff6b6b; + color: var(--color-danger-hover); font-size: 13px; cursor: pointer; border-radius: 6px; @@ -451,6 +462,6 @@ transition: background 0.12s; } .moreDropdown button:hover { - background: rgba(255, 107, 107, 0.10); + background: var(--color-danger-hover-bg-strong); } diff --git a/web/src/components/ImageLightbox.module.css b/web/src/components/ImageLightbox.module.css index 16bcfdc..b4f0bfe 100644 --- a/web/src/components/ImageLightbox.module.css +++ b/web/src/components/ImageLightbox.module.css @@ -2,7 +2,7 @@ position: fixed; inset: 0; z-index: 400; - background: rgba(0, 0, 0, 0.85); + background: var(--color-overlay-deep); display: flex; align-items: center; justify-content: center; diff --git a/web/src/components/InputBar.tsx b/web/src/components/InputBar.tsx index 339e696..b630a3c 100644 --- a/web/src/components/InputBar.tsx +++ b/web/src/components/InputBar.tsx @@ -18,20 +18,20 @@ export function InputBar({ scrollBottomBtn }: { scrollBottomBtn?: React.ReactNod e.preventDefault(); // 只有外部文件拖入时才显示蓝色边框(内部 mention 标签拖拽不触发) if (e.dataTransfer.types.includes('Files') && barRef.current) { - barRef.current.style.borderColor = '#00b8e6'; + barRef.current.style.borderColor = 'var(--color-info)'; } }, []); const handleDragLeave = useCallback(() => { if (barRef.current) { - barRef.current.style.borderColor = '#2a2a38'; + barRef.current.style.borderColor = 'var(--color-border-modal)'; } }, []); const handleDrop = useCallback((e: DragEvent) => { e.preventDefault(); if (barRef.current) { - barRef.current.style.borderColor = '#2a2a38'; + barRef.current.style.borderColor = 'var(--color-border-modal)'; } const IMAGE_MAX = 30 * 1024 * 1024; const VIDEO_MAX = 50 * 1024 * 1024; @@ -120,10 +120,10 @@ export function InputBar({ scrollBottomBtn }: { scrollBottomBtn?: React.ReactNod onClick={() => { if (!searchDisabled) setSearchMode(searchMode === 'smart' ? 'off' : 'smart'); }} title={searchDisabled ? '联网搜索仅支持纯文生视频' : ''} style={{ - background: searchMode === 'smart' && !searchDisabled ? 'rgba(108, 99, 255, 0.12)' : 'transparent', + background: searchMode === 'smart' && !searchDisabled ? 'var(--color-mention-bg)' : 'transparent', border: `1px solid ${searchMode === 'smart' && !searchDisabled ? 'var(--color-primary)' : 'var(--color-border-card)'}`, borderRadius: 6, padding: '4px 12px', fontSize: 12, - color: searchDisabled ? '#3a3a4a' : searchMode === 'smart' ? 'var(--color-primary)' : 'var(--color-text-secondary)', + color: searchDisabled ? 'var(--color-btn-send-disabled)' : searchMode === 'smart' ? 'var(--color-primary)' : 'var(--color-text-secondary)', cursor: searchDisabled ? 'not-allowed' : 'pointer', transition: 'all 0.15s', opacity: searchDisabled ? 0.5 : 1, }} @@ -138,7 +138,7 @@ export function InputBar({ scrollBottomBtn }: { scrollBottomBtn?: React.ReactNod background: 'transparent', border: '1px solid var(--color-border-card)', borderRadius: 6, padding: '4px 12px', fontSize: 12, - color: '#3a3a4a', cursor: 'not-allowed', transition: 'all 0.15s', + color: 'var(--color-btn-send-disabled)', cursor: 'not-allowed', transition: 'all 0.15s', opacity: 0.5, }} > diff --git a/web/src/components/KeyframeUpload.module.css b/web/src/components/KeyframeUpload.module.css index 17f83be..b2e66d9 100644 --- a/web/src/components/KeyframeUpload.module.css +++ b/web/src/components/KeyframeUpload.module.css @@ -12,8 +12,8 @@ .trigger { height: var(--thumbnail-size); aspect-ratio: 3 / 4; - border: 1.5px dashed #3a3a48; - background: rgba(255, 255, 255, 0.03); + border: 1.5px dashed var(--color-border-modal); + background: var(--color-bg-upload); border-radius: var(--radius-btn); display: flex; flex-direction: column; @@ -26,8 +26,8 @@ } .trigger:hover { - border-color: #5a5a6a; - background: rgba(255, 255, 255, 0.06); + border-color: var(--color-border-modal-hover); + background: var(--color-bg-card); } .triggerText { @@ -51,7 +51,7 @@ aspect-ratio: 3 / 4; border-radius: var(--radius-thumbnail); overflow: hidden; - background: #1a1a24; + background: var(--color-bg-dropdown-elevated); flex-shrink: 0; } @@ -68,7 +68,7 @@ right: 4px; width: 18px; height: 18px; - background: rgba(0, 0, 0, 0.7); + background: var(--color-overlay-strong); border-radius: 50%; display: flex; align-items: center; @@ -90,6 +90,6 @@ padding: 2px 0; text-align: center; font-size: 10px; - color: #fff; - background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); + color: var(--color-on-overlay); + background: linear-gradient(transparent, var(--color-overlay-strong)); } diff --git a/web/src/components/LoginModal.module.css b/web/src/components/LoginModal.module.css index 6f34eca..b9f9c98 100644 --- a/web/src/components/LoginModal.module.css +++ b/web/src/components/LoginModal.module.css @@ -2,7 +2,7 @@ position: fixed; inset: 0; z-index: 50; - background: rgba(0, 0, 0, 0.5); + background: var(--color-modal-overlay); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); display: flex; @@ -21,13 +21,13 @@ width: 100%; max-width: 400px; margin: 0 20px; - background: rgba(255, 255, 255, 0.06); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--color-bg-modal-glass); + backdrop-filter: var(--bf-glass-xl); + -webkit-backdrop-filter: var(--bf-glass-xl); + border: 1px solid var(--color-border-card); border-radius: 16px; padding: 36px 32px 32px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05) inset; + box-shadow: var(--shadow-glass-light); animation: panelIn 0.3s ease-out; } @@ -53,15 +53,15 @@ justify-content: center; background: none; border: none; - color: rgba(255, 255, 255, 0.4); + color: var(--color-text-on-glass-faint); cursor: pointer; border-radius: 6px; transition: all 0.2s; } .closeBtn:hover { - color: rgba(255, 255, 255, 0.8); - background: rgba(255, 255, 255, 0.06); + color: var(--color-text-on-glass); + background: var(--color-bg-card); } .header { @@ -81,7 +81,7 @@ font-family: 'Space Grotesk', sans-serif; font-size: 18px; font-weight: 400; - color: #f1f0ff; + color: var(--color-text-primary); letter-spacing: 0.05em; } @@ -99,36 +99,36 @@ .label { font-size: 13px; - color: #8b8ea8; + color: var(--color-text-secondary); font-weight: 500; } .input { height: 44px; padding: 0 14px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--color-bg-upload); + border: 1px solid var(--color-border-card); border-radius: 10px; - color: #f1f0ff; + color: var(--color-text-primary); font-size: 14px; outline: none; transition: border-color 0.2s; } .input::placeholder { - color: #4c4f6b; + color: var(--color-text-disabled); } .input:focus { - border-color: rgba(126, 220, 200, 0.5); + border-color: var(--color-mint-accent); } .error { - color: #ff4d4f; + color: var(--color-danger-text); font-size: 13px; text-align: center; padding: 8px; - background: rgba(255, 77, 79, 0.08); + background: var(--color-danger-bg-soft); border-radius: 8px; } @@ -137,22 +137,25 @@ width: 55%; align-self: center; margin-top: 18px; - background: rgba(120, 220, 200, 0.08); - border: 1px solid rgba(120, 220, 200, 0.3); - color: #7edcc8; + background: var(--color-mint-accent-bg); + border: 1px solid var(--color-mint-accent-border); + color: var(--color-mint-accent); border-radius: 10px; font-family: 'Space Grotesk', sans-serif; font-size: 15px; - font-weight: 500; + font-weight: 600; /* V2: 500 → 600,浅色下提对比度 */ + letter-spacing: 0.04em; cursor: pointer; transition: all 0.2s; - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + backdrop-filter: var(--bf-glass-sm); + -webkit-backdrop-filter: var(--bf-glass-sm); + /* V2 玻璃顶边白高光 */ + box-shadow: inset 0 1px 0 var(--color-inset-highlight); } .submitBtn:hover { - background: rgba(120, 220, 200, 0.18); - box-shadow: 0 0 24px rgba(120, 220, 200, 0.12); + background: var(--color-mint-accent-bg-hover); + box-shadow: inset 0 1px 0 var(--color-inset-highlight), 0 0 24px var(--color-mint-accent-glow); } .submitBtn:disabled { @@ -162,7 +165,7 @@ .hint { font-size: 12px; - color: rgba(255, 255, 255, 0.25); + color: var(--color-text-on-glass-faint); text-align: center; margin: 0; } diff --git a/web/src/components/PromptInput.module.css b/web/src/components/PromptInput.module.css index a1dbaec..72c0a35 100644 --- a/web/src/components/PromptInput.module.css +++ b/web/src/components/PromptInput.module.css @@ -24,7 +24,7 @@ position: absolute; top: 4px; left: 0; - color: #5a5a6a; + color: var(--color-border-modal-hover); font-size: 14px; line-height: 1.6; pointer-events: none; @@ -38,8 +38,8 @@ padding: 1px 6px; margin: 0 2px; border-radius: 4px; - background: rgba(108, 99, 255, 0.12); - color: rgba(108, 99, 255, 0.7); + background: var(--color-mention-bg); + color: var(--color-mention-text); font-size: 13px; cursor: grab; user-select: none; @@ -74,22 +74,26 @@ } .mention:hover { - background: rgba(108, 99, 255, 0.22); - color: rgba(108, 99, 255, 0.9); + background: var(--color-mention-bg-hover); + color: var(--color-mention-text-hover); } -/* Mention popup — appears above cursor */ +/* Mention popup — appears above cursor (V2 玻璃面) */ .mentionPopup { position: absolute; z-index: 100; - background: rgba(13, 13, 26, 0.92); - border: 1px solid rgba(255, 255, 255, 0.10); + background: var(--color-bg-glass-strong); + border: 1px solid var(--color-border-card); border-radius: 10px; padding: 6px; min-width: 200px; max-width: 280px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); - backdrop-filter: blur(20px) saturate(180%); + /* 阴影 + 玻璃顶边白高光 */ + box-shadow: + 0 8px 24px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight-strong); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); transform: translateY(-100%); animation: fadeInUp 0.12s ease; } @@ -102,8 +106,8 @@ .mentionHeader { padding: 4px 8px 6px; font-size: 11px; - color: #5a5a6a; - border-bottom: 1px solid #2a2a3a; + color: var(--color-border-modal-hover); + border-bottom: 1px solid var(--color-border-modal); margin-bottom: 4px; } @@ -122,12 +126,12 @@ } .mentionItem:hover { - background: rgba(255, 255, 255, 0.06); + background: var(--color-bg-card); } .mentionItemActive { - background: rgba(108, 99, 255, 0.15); - color: #f1f0ff; + background: var(--color-mention-bg-active); + color: var(--color-text-primary); } .mentionThumb { @@ -136,7 +140,7 @@ border-radius: 6px; overflow: hidden; flex-shrink: 0; - background: #2a2a3a; + background: var(--color-border-modal); display: flex; align-items: center; justify-content: center; @@ -155,7 +159,7 @@ } .mentionType { - color: #5a5a6a; + color: var(--color-border-modal-hover); font-size: 11px; } @@ -164,11 +168,11 @@ position: absolute; z-index: 200; transform: translate(-50%, -100%); - background: #1e1e2e; - border: 1px solid #2a2a3a; + background: var(--color-bg-modal-hover); + border: 1px solid var(--color-border-modal); border-radius: 10px; padding: 6px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + box-shadow: 0 8px 24px var(--color-overlay-soft); pointer-events: none; animation: fadeIn 0.1s ease; } @@ -183,7 +187,7 @@ .previewLabel { text-align: center; - color: #8a8a9a; + color: var(--color-text-secondary); font-size: 11px; margin-top: 4px; } diff --git a/web/src/components/PromptInput.tsx b/web/src/components/PromptInput.tsx index 4fe7659..1fa225a 100644 --- a/web/src/components/PromptInput.tsx +++ b/web/src/components/PromptInput.tsx @@ -754,7 +754,7 @@ export function PromptInput() {
{asset.name} - {asset.group_name} + {asset.group_name}
{asset.asset_type === 'Video' ? '视频' : asset.asset_type === 'Audio' ? '音频' : '图片'} diff --git a/web/src/components/RecordDetailModal.tsx b/web/src/components/RecordDetailModal.tsx index 3c0e679..f68cee9 100644 --- a/web/src/components/RecordDetailModal.tsx +++ b/web/src/components/RecordDetailModal.tsx @@ -1,11 +1,12 @@ import type { AdminRecord } from '../types'; import { ReferenceList } from './ReferenceList'; +import { rewriteTosUrl } from '../lib/api'; const STATUS_MAP: Record = { - completed: { label: '已完成', color: '#00b894', bg: 'rgba(0,184,148,0.15)' }, - failed: { label: '失败', color: '#e74c3c', bg: 'rgba(231,76,60,0.15)' }, - processing: { label: '生成中', color: '#00b8e6', bg: 'rgba(0,184,230,0.15)' }, - queued: { label: '排队中', color: '#00b8e6', bg: 'rgba(0,184,230,0.15)' }, + completed: { label: '已完成', color: 'var(--color-success)', bg: 'var(--color-success-bg)' }, + failed: { label: '失败', color: 'var(--color-danger)', bg: 'var(--color-danger-bg)' }, + processing: { label: '生成中', color: 'var(--color-info)', bg: 'var(--color-info-bg)' }, + queued: { label: '排队中', color: 'var(--color-info)', bg: 'var(--color-info-bg)' }, }; const MODE_MAP: Record = { universal: '全能参考', keyframe: '首尾帧' }; @@ -39,58 +40,67 @@ export function RecordDetailModal({ record: r, onClose, showTeam, showCost }: Pr
e.stopPropagation()}> {/* Header */}
- 任务详情 + 任务详情
+ {/* Body — 左:视频+参考素材 / 右:信息+提示词 */}
- {/* Status */} -
- {st.label} -
- - {/* Error */} - {r.status === 'failed' && r.error_message && ( -
-
失败原因
-
{r.error_message}
- {r.raw_error && r.raw_error !== r.error_message && ( -
- 原始错误:{r.raw_error} + {/* ── 左侧:视频 + 参考素材 ── */} +
+ + {refs.length > 0 && ( + <> +
参考素材({refs.length})
+
+
- )} -
- )} - - {/* Info Grid */} -
基本信息
-
- {r.ark_task_id && } - {r.username && } - {showTeam && r.team_name && } - - - - - - - - - {showCost && } - {r.seed != null && r.seed !== -1 && } + + )}
- {/* Prompt */} -
提示词
-
{r.prompt || '(无提示词)'}
+ {/* ── 右侧:信息 + 提示词 ── */} +
+ {/* Status */} +
+ {st.label} +
- {/* References */} - {refs.length > 0 && ( - <> -
参考素材({refs.length})
- - - )} + {/* Error */} + {r.status === 'failed' && r.error_message && ( +
+
失败原因
+
{r.error_message}
+ {r.raw_error && r.raw_error !== r.error_message && ( +
+ 原始错误:{r.raw_error} +
+ )} +
+ )} + + {/* Info Grid */} +
基本信息
+
+ {r.ark_task_id && } + {r.username && } + {showTeam && r.team_name && } + + + + + + + + + {showCost && } + {r.seed != null && r.seed !== -1 && } +
+ + {/* Prompt */} +
提示词
+
{r.prompt || '(无提示词)'}
+
@@ -98,51 +108,259 @@ export function RecordDetailModal({ record: r, onClose, showTeam, showCost }: Pr ); } -function InfoItem({ label, value }: { label: string; value: string }) { +/** + * 左侧媒体区 — 根据任务状态决定显示什么: + * - completed + result_url → 视频播放器(controls,不自动播放) + * - completed - result_url → "视频已生成"占位 + * - failed → RGB 故障字 "生成失败" + 错误原因摘要 + 斜纹底纹 + * - processing / queued → 旋转 spinner + 文字 + */ +function MediaArea({ record: r }: { record: AdminRecord }) { return ( -
-
{label}
-
{value}
+
+ {r.status === 'completed' && r.result_url ? ( +
); } +/** + * RGB 故障字失败态 — "生成失败"主标题用 cyan/magenta text-shadow 偏移 + * 模拟坏掉的 CRT 信号丢失;副标题等宽字体显示错误摘要。 + */ +function FailureGlitch({ errorMessage }: { errorMessage?: string }) { + const msg = (errorMessage || 'Generation failed').slice(0, 80); + return ( +
+