From f8a39d55c76000b691344667a8e858bc6df07ab3 Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Mon, 11 May 2026 19:46:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=E6=B5=85=E8=89=B2=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=20V2=20=E2=80=94=20=E7=8E=BB=E7=92=83=E8=B4=A8?= =?UTF-8?q?=E6=84=9F=E9=87=8D=E5=81=9A=20+=20LandingPage=20=E6=B5=85?= =?UTF-8?q?=E8=89=B2=E5=8C=96=20+=20=E5=8F=8C=E8=AF=AD=E8=A8=80=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase A: index.css 完全重写 [data-theme="light"] block - 玻璃方向修正: bg-card 从 rgba(0,0,0,0.05) 黑透明 → 双 token 拆分 --color-bg-card: #ffffff 实体白 (admin 卡) --color-bg-glass: rgba(255,255,255,0.65) 透明白玻璃 (sidebar/modal/banner) - Aurora 浅色不再 display:none, 改 pastel 紫蓝桃 0.20-0.32 alpha - Inset highlight 方向反转: 浅色用 rgba(255,255,255,0.50) 白高光 (玻璃顶边标志) - Backdrop-filter 五档标准: --bf-glass-sm/md/lg/xl (12-40px + saturate 140-180%) - Multi-layer shadow: --shadow-card-light (2 stops) + --shadow-glass-light (3 stops + inset) - 暖调 chip: --color-chip-warm-* GitBook 公告风格 - 文字主色: #171823 微紫 → #171717 Vercel Black Phase B: LandingPage + AuroraCanvas 浅色化 - 移除 LandingPage 的 data-theme="dark" 强制 (V1 的回避) - LandingPage.module.css 21 处颜色全 var 化 - AuroraCanvas: 订阅 useThemeStore, 新 LIGHT_ORBS 数组 pastel 紫蓝桃, vignette 浅色用白色, grain opacity 减半 Phase C: 13 个玻璃面升级 (3 sub-agent 并行) - Modal 类 (Login/ForceChange/VideoDetail.infoPanel/RecordDetail/AssetLibrary/ Announcement/Confirm/TeamsPage.detailModal): 接入 bg-modal-glass + bf-glass-lg/xl + shadow-glass-light (含 inset highlight) - Bar/Dropdown/Toast (AnnouncementBanner/Toast/Dropdown/Select/DatePicker): bg-glass-strong + bf-glass-md + inset-highlight - Sidebar + 生成页 (Sidebar/PromptInput/GenerationCard): glass + 顶边白高光 - AnnouncementBanner 写双套独立 [data-theme] 规则 (CSS gradient 内不能 var alpha) Phase D: admin 实体卡 multi-layer shadow (13 处, 1 sub-agent) - DashboardPage / TeamsPage / UsersPage / RecordsPage / AdminAssetsPage / LoginRecordsPage / AuditLogsPage / ProfilePage / SettingsPage 的 .statCard / .tableWrapper / .chartWrapper / .accordionItem 等 加 var(--shadow-card-light) 双层柔阴影 AdminLayout 修复 (V1 漏的): - .layout 改 transparent, 让 AmbientBackground pastel aurora 在主区透出 - .sidebar 加 bf-glass-md + inset highlight + 立体阴影 LoginModal / ForceChangePassword 残留 mint 清理: - submitBtn bg/border/color 用 mint-accent var, 字重 500→600 + 字距 0.04em - input:focus border 用 var(--color-mint-accent) - 加 bf-glass-sm + inset highlight 验证: - TS 编译过 - vitest 71 fail / 162 pass 与 V1 基线完全一致, 无新增回归 - 24 张 V2 截图位于 docs/screenshots/v2/ (本地, .gitignore 排除 png) 完成报告: docs/todo/亮色主题切换V2-完成报告.md V2 plan: docs/todo/亮色主题切换V2.md 视觉对齐稿: docs/todo/showcase.html 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/todo/showcase.html | 1360 +++++++++++++++++ docs/todo/亮色主题切换V2-完成报告.md | 262 ++++ docs/todo/亮色主题切换V2.md | 516 +++++++ .../components/AnnouncementBanner.module.css | 16 + .../components/AnnouncementModal.module.css | 5 +- .../components/AssetLibraryModal.module.css | 5 +- web/src/components/AuroraCanvas.tsx | 76 +- web/src/components/ConfirmModal.module.css | 2 +- web/src/components/DatePicker.module.css | 9 +- web/src/components/Dropdown.module.css | 9 +- .../ForceChangePasswordModal.module.css | 28 +- web/src/components/GenerationCard.module.css | 31 +- web/src/components/LoginModal.module.css | 29 +- web/src/components/PromptInput.module.css | 12 +- web/src/components/RecordDetailModal.tsx | 7 +- web/src/components/Select.module.css | 9 +- web/src/components/Sidebar.module.css | 8 +- web/src/components/Toast.module.css | 10 +- .../components/VideoDetailModal.module.css | 7 +- web/src/index.css | 152 +- web/src/pages/AdminAssetsPage.module.css | 2 + web/src/pages/AdminLayout.module.css | 11 +- web/src/pages/AuditLogsPage.module.css | 1 + web/src/pages/DashboardPage.module.css | 2 + web/src/pages/LandingPage.module.css | 59 +- web/src/pages/LandingPage.tsx | 7 +- web/src/pages/LoginRecordsPage.module.css | 1 + web/src/pages/ProfilePage.module.css | 3 + web/src/pages/RecordsPage.module.css | 1 + web/src/pages/SettingsPage.module.css | 1 + web/src/pages/TeamsPage.module.css | 7 +- web/src/pages/UsersPage.module.css | 1 + web/test/theme-screenshots-v2.mjs | 141 ++ 33 files changed, 2612 insertions(+), 178 deletions(-) create mode 100644 docs/todo/showcase.html create mode 100644 docs/todo/亮色主题切换V2-完成报告.md create mode 100644 docs/todo/亮色主题切换V2.md create mode 100644 web/test/theme-screenshots-v2.mjs 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/亮色主题切换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/src/components/AnnouncementBanner.module.css b/web/src/components/AnnouncementBanner.module.css index 0d2c894..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 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); diff --git a/web/src/components/AnnouncementModal.module.css b/web/src/components/AnnouncementModal.module.css index 742a3ba..bc8170a 100644 --- a/web/src/components/AnnouncementModal.module.css +++ b/web/src/components/AnnouncementModal.module.css @@ -9,7 +9,9 @@ } .modal { - background: var(--color-bg-modal-elevated); + 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 { diff --git a/web/src/components/AssetLibraryModal.module.css b/web/src/components/AssetLibraryModal.module.css index 9053d5c..871e032 100644 --- a/web/src/components/AssetLibraryModal.module.css +++ b/web/src/components/AssetLibraryModal.module.css @@ -12,12 +12,15 @@ width: 90vw; max-width: 1400px; height: 85vh; - background: var(--color-bg-modal-elevated); + 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 { diff --git a/web/src/components/AuroraCanvas.tsx b/web/src/components/AuroraCanvas.tsx index 26ddd00..30fb7b5 100644 --- a/web/src/components/AuroraCanvas.tsx +++ b/web/src/components/AuroraCanvas.tsx @@ -1,53 +1,64 @@ import { useRef, useEffect, useCallback } from 'react'; +import { useThemeStore } from '../store/theme'; /** * Aurora background — 5 large diffuse orbs with additive blending. - * Orbs are deliberately offset from center to avoid uniform "blue blob" look. - * Mouse gently pushes nearby orbs (10-20px, very subtle). + * V2: 接入 themeStore,浅色态用 pastel orb + 白色 vignette/gradient,深色保持原品牌色。 */ interface Orb { cx: number; // base center X ratio (0-1) cy: number; // base center Y ratio (0-1) color: [number, number, number]; - alpha: number; // peak alpha — varies per orb for brightness contrast + alpha: number; phase: number; freqX: number; freqY: number; ampX: number; ampY: number; - radius: number; // ratio of max(w,h) - // breathing + radius: number; breathFreq: number; breathAmp: number; } -// Orbs deliberately NOT centered — some in corners, some offset, -// creating uneven light distribution with dark pockets. -const ORBS: Orb[] = [ - // Cyan — upper-left area, large and bright +// Dark theme orbs — vivid 品牌色(青/紫/蓝) +const DARK_ORBS: Orb[] = [ { cx: 0.25, cy: 0.30, color: [126, 220, 200], alpha: 0.28, phase: 0, freqX: 0.00012, freqY: 0.00010, ampX: 0.12, ampY: 0.10, radius: 0.50, breathFreq: 0.0004, breathAmp: 0.06 }, - // Purple — lower-right, medium { cx: 0.72, cy: 0.65, color: [108, 99, 255], alpha: 0.22, phase: 1.8, freqX: 0.00010, freqY: 0.00014, ampX: 0.14, ampY: 0.12, radius: 0.45, breathFreq: 0.0003, breathAmp: 0.08 }, - // Blue — center-right, smaller and dimmer (fills gap) { cx: 0.58, cy: 0.38, color: [59, 130, 246], alpha: 0.18, phase: 3.2, freqX: 0.00015, freqY: 0.00008, ampX: 0.10, ampY: 0.15, radius: 0.38, breathFreq: 0.0005, breathAmp: 0.07 }, - // Light purple — bottom-left corner, dim accent { cx: 0.20, cy: 0.70, color: [167, 139, 250], alpha: 0.15, phase: 4.5, freqX: 0.00008, freqY: 0.00012, ampX: 0.16, ampY: 0.08, radius: 0.40, breathFreq: 0.00035, breathAmp: 0.10 }, - // Bright cyan — upper-right, small bright accent { cx: 0.78, cy: 0.25, color: [34, 211, 238], alpha: 0.20, phase: 5.8, freqX: 0.00014, freqY: 0.00016, ampX: 0.08, ampY: 0.12, radius: 0.35, breathFreq: 0.00045, breathAmp: 0.09 }, ]; -// Mouse influence radius (px) and max push distance (px) +// Light theme orbs — pastel 浅色,alpha 减半左右,给玻璃面提供穿透色源 +const LIGHT_ORBS: Orb[] = [ + { cx: 0.25, cy: 0.30, color: [180, 167, 255], alpha: 0.32, // pastel lavender + phase: 0, freqX: 0.00012, freqY: 0.00010, ampX: 0.12, ampY: 0.10, + radius: 0.55, breathFreq: 0.0004, breathAmp: 0.06 }, + { cx: 0.72, cy: 0.65, color: [167, 200, 255], alpha: 0.28, // pastel sky + phase: 1.8, freqX: 0.00010, freqY: 0.00014, ampX: 0.14, ampY: 0.12, + radius: 0.50, breathFreq: 0.0003, breathAmp: 0.08 }, + { cx: 0.58, cy: 0.38, color: [255, 180, 130, ], alpha: 0.18, // pastel peach + phase: 3.2, freqX: 0.00015, freqY: 0.00008, ampX: 0.10, ampY: 0.15, + radius: 0.42, breathFreq: 0.0005, breathAmp: 0.07 }, + { cx: 0.20, cy: 0.70, color: [220, 167, 255], alpha: 0.20, // pastel pink-violet + phase: 4.5, freqX: 0.00008, freqY: 0.00012, ampX: 0.16, ampY: 0.08, + radius: 0.45, breathFreq: 0.00035, breathAmp: 0.10 }, + { cx: 0.78, cy: 0.25, color: [180, 220, 255], alpha: 0.22, // pastel blue + phase: 5.8, freqX: 0.00014, freqY: 0.00016, ampX: 0.08, ampY: 0.12, + radius: 0.40, breathFreq: 0.00045, breathAmp: 0.09 }, +]; + const MOUSE_RADIUS = 400; const MOUSE_PUSH = 28; @@ -55,6 +66,8 @@ export function AuroraCanvas() { const canvasRef = useRef(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 90b3502..7334ed7 100644 --- a/web/src/components/ConfirmModal.module.css +++ b/web/src/components/ConfirmModal.module.css @@ -1,5 +1,5 @@ .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-elevated); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 24px; width: 400px; max-width: 90vw; } +.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; } diff --git a/web/src/components/DatePicker.module.css b/web/src/components/DatePicker.module.css index 423f5d0..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: var(--color-bg-modal-elevated); + background: var(--color-bg-dropdown); border: 1px solid var(--color-border-card); border-radius: 12px; - box-shadow: 0 8px 32px var(--color-shadow-dropdown); - 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; } diff --git a/web/src/components/Dropdown.module.css b/web/src/components/Dropdown.module.css index 5a33dff..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 var(--color-shadow-dropdown); + box-shadow: + 0 8px 32px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); } .open { diff --git a/web/src/components/ForceChangePasswordModal.module.css b/web/src/components/ForceChangePasswordModal.module.css index c7485a2..64087ee 100644 --- a/web/src/components/ForceChangePasswordModal.module.css +++ b/web/src/components/ForceChangePasswordModal.module.css @@ -21,13 +21,13 @@ width: 100%; max-width: 420px; margin: 0 20px; - background: var(--color-bg-card); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); + 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 var(--color-shadow-dropdown), 0 0 0 1px rgba(255, 255, 255, 0.05) inset; + box-shadow: var(--shadow-glass-light); animation: panelIn 0.3s ease-out; } @@ -106,7 +106,7 @@ } .input:focus { - border-color: rgba(126, 220, 200, 0.5); + border-color: var(--color-mint-accent); } .error { @@ -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 3e1a05a..3a1704a 100644 --- a/web/src/components/GenerationCard.module.css +++ b/web/src/components/GenerationCard.module.css @@ -81,7 +81,7 @@ overflow: hidden; } -/* hover 展开黑底:基于 .header 定位,左边距图片 4px */ +/* hover 展开 prompt 面板 — V2 玻璃面 */ .promptExpanded { position: absolute; top: 0; @@ -91,12 +91,15 @@ color: var(--color-text-primary); line-height: 1.6; word-break: break-word; - background: var(--color-bg-dropdown); - backdrop-filter: blur(12px); + 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 var(--color-shadow-dropdown); + box-shadow: + 0 8px 24px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); } .mentionTag { @@ -173,13 +176,17 @@ .detailTooltip { position: fixed; z-index: 1000; - background: var(--color-bg-dropdown); - backdrop-filter: blur(12px); + 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 var(--color-shadow-dropdown); + /* V2 玻璃面 — 阴影 + 顶边白高光 */ + box-shadow: + 0 8px 24px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); animation: detailTooltipFadeIn 0.15s ease-out; } @@ -417,14 +424,18 @@ position: absolute; bottom: calc(100% + 6px); right: 0; - background: var(--color-bg-dropdown); - backdrop-filter: blur(20px) saturate(180%); + 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 var(--color-shadow-dropdown); + /* 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; } diff --git a/web/src/components/LoginModal.module.css b/web/src/components/LoginModal.module.css index c34feae..b9f9c98 100644 --- a/web/src/components/LoginModal.module.css +++ b/web/src/components/LoginModal.module.css @@ -21,13 +21,13 @@ width: 100%; max-width: 400px; margin: 0 20px; - background: var(--color-bg-card); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); + 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 var(--color-shadow-dropdown), 0 0 0 1px rgba(255, 255, 255, 0.05) inset; + box-shadow: var(--shadow-glass-light); animation: panelIn 0.3s ease-out; } @@ -120,7 +120,7 @@ } .input:focus { - border-color: rgba(126, 220, 200, 0.5); + border-color: var(--color-mint-accent); } .error { @@ -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 { diff --git a/web/src/components/PromptInput.module.css b/web/src/components/PromptInput.module.css index 2fe125b..72c0a35 100644 --- a/web/src/components/PromptInput.module.css +++ b/web/src/components/PromptInput.module.css @@ -78,18 +78,22 @@ color: var(--color-mention-text-hover); } -/* Mention popup — appears above cursor */ +/* Mention popup — appears above cursor (V2 玻璃面) */ .mentionPopup { position: absolute; z-index: 100; - background: var(--color-bg-dropdown); + 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 var(--color-shadow-dropdown); - 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; } diff --git a/web/src/components/RecordDetailModal.tsx b/web/src/components/RecordDetailModal.tsx index 6343ac9..c9882a6 100644 --- a/web/src/components/RecordDetailModal.tsx +++ b/web/src/components/RecordDetailModal.tsx @@ -113,8 +113,13 @@ const overlay: React.CSSProperties = { alignItems: 'center', justifyContent: 'center', zIndex: 10000, }; const modal: React.CSSProperties = { - background: 'var(--color-bg-modal)', border: '1px solid var(--color-border-modal)', borderRadius: 12, + background: 'var(--color-bg-modal-glass)', + backdropFilter: 'blur(24px) saturate(180%)', + WebkitBackdropFilter: 'blur(24px) saturate(180%)', + border: '1px solid var(--color-border-modal-soft)', + borderRadius: 12, width: 560, maxHeight: '80vh', display: 'flex', flexDirection: 'column', + boxShadow: 'var(--shadow-glass-light)', }; const header: React.CSSProperties = { display: 'flex', justifyContent: 'space-between', alignItems: 'center', diff --git a/web/src/components/Select.module.css b/web/src/components/Select.module.css index 335b03f..3534e93 100644 --- a/web/src/components/Select.module.css +++ b/web/src/components/Select.module.css @@ -48,13 +48,16 @@ position: absolute; top: calc(100% + 4px); left: 0; - background: var(--color-bg-modal-elevated); + background: var(--color-bg-dropdown); border: 1px solid var(--color-border-card); border-radius: 8px; padding: 4px; z-index: 1000; - backdrop-filter: blur(20px) saturate(180%); - box-shadow: 0 8px 32px var(--color-shadow-dropdown); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); + box-shadow: + 0 8px 32px var(--color-shadow-dropdown), + inset 0 1px 0 var(--color-inset-highlight); opacity: 0; transform: translateY(-4px); pointer-events: none; diff --git a/web/src/components/Sidebar.module.css b/web/src/components/Sidebar.module.css index ac66b4e..2d089c6 100644 --- a/web/src/components/Sidebar.module.css +++ b/web/src/components/Sidebar.module.css @@ -2,8 +2,14 @@ width: 76px; height: 100%; background: var(--color-sidebar-bg); - backdrop-filter: blur(16px) saturate(160%); + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); border-right: 1px solid var(--color-border-modal-soft); + /* V2 玻璃顶边白高光 + 右侧立体阴影 */ + box-shadow: + inset 0 1px 0 var(--color-inset-highlight), + 1px 0 0 var(--color-border-soft), + 2px 0 12px rgba(0, 0, 0, 0.04); display: flex; flex-direction: column; align-items: center; diff --git a/web/src/components/Toast.module.css b/web/src/components/Toast.module.css index 787ecf8..f39ea78 100644 --- a/web/src/components/Toast.module.css +++ b/web/src/components/Toast.module.css @@ -3,15 +3,15 @@ top: 20px; left: 50%; transform: translateX(-50%) translateY(-20px); - background: var(--color-bg-card); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); + 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); box-shadow: 0 0 0 1px var(--color-inset-highlight) inset, 0 8px 32px var(--color-shadow-dropdown), - 0 1px 0 var(--color-inset-highlight-strong) inset; - color: var(--color-on-overlay); + inset 0 1px 0 var(--color-inset-highlight-strong); + color: var(--color-text-primary); padding: 10px 24px; border-radius: 10px; font-size: 13px; diff --git a/web/src/components/VideoDetailModal.module.css b/web/src/components/VideoDetailModal.module.css index 049f1c1..e9b6053 100644 --- a/web/src/components/VideoDetailModal.module.css +++ b/web/src/components/VideoDetailModal.module.css @@ -278,9 +278,10 @@ display: flex; flex-direction: column; border-left: 1px solid var(--color-border-modal-soft); - background: var(--color-bg-upload); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); + background: var(--color-bg-glass); + backdrop-filter: var(--bf-glass-lg); + -webkit-backdrop-filter: var(--bf-glass-lg); + box-shadow: inset 0 1px 0 var(--color-inset-highlight); } /* Header with download + icons */ diff --git a/web/src/index.css b/web/src/index.css index 667854b..6ecf76f 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -178,51 +178,91 @@ --color-aurora-1: rgba(108, 99, 255, 0.6); --color-aurora-2: rgba(59, 130, 246, 0.5); --color-aurora-3: rgba(139, 92, 246, 0.35); + --color-aurora-peach: rgba(255, 130, 100, 0.20); --color-cursor-glow: rgba(108, 99, 255, 0.06); --color-grid-line: rgba(255, 255, 255, 0.02); + + /* ─── V2 新增 tokens (2026-05-11) ─── */ + /* GLASS 类(透明白 + blur,用于 sidebar/banner/modal overlay/dropdown) */ + --color-bg-glass: rgba(255, 255, 255, 0.06); + --color-bg-glass-strong: rgba(255, 255, 255, 0.10); + --color-border-glass-edge: rgba(255, 255, 255, 0.12); + + /* 行 hover(实体卡内表格 hover;与 --color-bg-hover 区分) */ + --color-bg-row-hover: rgba(255, 255, 255, 0.02); + + /* Multi-layer shadows — Vercel/GitBook 风格 */ + --shadow-card-light: 0 1px 2px rgba(0, 0, 0, 0.30), + 0 4px 16px rgba(0, 0, 0, 0.40); + --shadow-glass-light: 0 8px 32px rgba(0, 0, 0, 0.30), + 0 1px 2px rgba(0, 0, 0, 0.40), + inset 0 1px 0 rgba(255, 255, 255, 0.08); + + /* 暖调 chip (公告/Trial/"新版上线") */ + --color-chip-warm-bg: rgba(255, 200, 130, 0.10); + --color-chip-warm-border: rgba(255, 200, 130, 0.25); + --color-chip-warm-text: #f1f0ff; + --color-chip-warm-badge-bg: rgba(255, 150, 100, 0.20); + --color-chip-warm-badge-text: #ffb589; + + /* Backdrop-filter 标准五档 */ + --bf-glass-sm: blur(12px) saturate(140%); + --bf-glass-md: blur(16px) saturate(160%); + --bf-glass-lg: blur(24px) saturate(180%); + --bf-glass-xl: blur(40px) saturate(180%); } /* ══════════════════════════════════════════════ - LIGHT THEME OVERRIDES - 规范来源: Vercel Geist (#fafafa / #171717 / 阴影边框) + Linear (#f3f4f5 surface) - 主色加深 18% 满足 WCAG AA 对比度 + LIGHT THEME OVERRIDES — V2 + 规范:Vercel Geist 灰阶 + GitBook/Framer 玻璃 + Linear pastel aurora + 关键变化: + - 玻璃面用 _白透明_ rgba(255,255,255,0.65) — V1 是黑透明,方向反了 + - aurora 不再 display:none,保留 pastel 紫蓝桃给玻璃穿透色源 + - 实体卡 bg = #ffffff 纯白 + multi-layer shadow + 1px shadow-border + - LandingPage 不再强制 dark,跟随主题切换 + - 玻璃顶边 inset highlight 白高光是 frosted glass 视觉标志 + 主色加深 18% 满足 WCAG AA ══════════════════════════════════════════════ */ [data-theme="light"] { - /* Page surfaces — Vercel Gray 50 + 纯白 modal */ + /* ── Page surfaces ── */ --color-bg-page: #fafafa; - --color-bg-input-bar: #ffffff; - --color-bg-dropdown: rgba(255, 255, 255, 0.96); - /* 卡片背景加深至 0.05,配合更强的 border 在 #fafafa 上有清晰轮廓 */ - --color-bg-upload: rgba(0, 0, 0, 0.03); - --color-bg-card: rgba(0, 0, 0, 0.05); - --color-bg-hover: rgba(0, 0, 0, 0.07); - /* Sidebar 加深一档,避免在浅色 page bg 上完全融入消失 */ - --color-sidebar-bg: rgba(243, 244, 246, 0.92); - --color-bg-sidebar: rgba(243, 244, 246, 0.92); - --color-sidebar-active: rgba(0, 0, 0, 0.08); - --color-sidebar-hover: rgba(0, 0, 0, 0.05); + --color-bg-input-bar: rgba(255, 255, 255, 0.85); /* 玻璃输入条 */ + --color-bg-dropdown: rgba(255, 255, 255, 0.85); /* 玻璃下拉 */ + --color-bg-upload: #ffffff; /* 实体上传区 */ + --color-bg-card: #ffffff; /* ★ V2 核心:实体白卡,不是黑透明 */ + --color-bg-hover: rgba(0, 0, 0, 0.04); /* 行 hover 黑透明保留(不是玻璃) */ + --color-bg-row-hover: rgba(0, 0, 0, 0.03); - /* Borders — Vercel shadow-border 风格,整体加深 0.02 提升浅色下卡片轮廓 */ - --color-border-input-bar: rgba(0, 0, 0, 0.12); - --color-border-card: rgba(0, 0, 0, 0.10); - --color-border-upload: rgba(0, 0, 0, 0.08); - --color-border-modal: #e5e7eb; - --color-border-modal-soft: rgba(0, 0, 0, 0.08); + /* ── GLASS surfaces (sidebar / banner / modal overlay) ── */ + --color-bg-glass: rgba(255, 255, 255, 0.65); /* ★ V2 核心:玻璃透白 */ + --color-bg-glass-strong: rgba(255, 255, 255, 0.85); + --color-sidebar-bg: rgba(255, 255, 255, 0.70); /* 真正玻璃 */ + --color-bg-sidebar: rgba(255, 255, 255, 0.70); + --color-sidebar-active: rgba(0, 0, 0, 0.06); + --color-sidebar-hover: rgba(0, 0, 0, 0.04); + + /* ── Borders — Vercel shadow-border 风格 0.08 ── */ + --color-border-input-bar: rgba(0, 0, 0, 0.10); + --color-border-card: rgba(0, 0, 0, 0.08); + --color-border-upload: rgba(0, 0, 0, 0.06); + --color-border-modal: rgba(0, 0, 0, 0.06); + --color-border-modal-soft: rgba(0, 0, 0, 0.05); --color-border-modal-hover: #9ca3af; - --color-border-soft: rgba(0, 0, 0, 0.06); - --color-border-row: rgba(0, 0, 0, 0.05); + --color-border-soft: rgba(0, 0, 0, 0.05); + --color-border-row: rgba(0, 0, 0, 0.06); + --color-border-glass-edge: rgba(255, 255, 255, 0.70); /* 玻璃外边白高光 */ - /* Text — Vercel 灰阶 #171717 / #4d4d4d / #888 / #cbd5e1 */ - --color-text-primary: #171823; - --color-text-secondary: #6b6e85; - --color-text-tertiary: #9ca3af; - --color-text-quaternary: #cbd5e1; - --color-text-disabled: #cbd5e1; + /* ── Text — Vercel 黑白灰严格灰阶 ── */ + --color-text-primary: #171717; /* Vercel Black 纯近黑,去紫调 */ + --color-text-secondary: #525252; /* Gray 600 */ + --color-text-tertiary: #888888; /* Gray 500 */ + --color-text-quaternary: #a3a3a3; /* Gray 400 */ + --color-text-disabled: #a3a3a3; --color-text-light: #374151; --color-text-monochrome: #4b5563; - --color-text-on-glass: rgba(0, 0, 0, 0.75); - --color-text-on-glass-soft: rgba(0, 0, 0, 0.55); - --color-text-on-glass-faint: rgba(0, 0, 0, 0.40); + --color-text-on-glass: rgba(23, 23, 23, 0.85); + --color-text-on-glass-soft: rgba(23, 23, 23, 0.55); + --color-text-on-glass-faint: rgba(23, 23, 23, 0.40); /* Brand — 主色加深 18% (#6c63ff → #5048cc) */ --color-primary: #5048cc; @@ -256,7 +296,7 @@ --color-purple-bg: rgba(124, 58, 237, 0.10); --color-purple-bg-hover: rgba(124, 58, 237, 0.06); - /* Modal & overlay — 浅色下整体减弱 */ + /* ── Modal & overlay — 浅色下整体减弱 ── */ --color-modal-overlay: rgba(0, 0, 0, 0.20); --color-overlay-strong: rgba(0, 0, 0, 0.30); --color-overlay-soft: rgba(0, 0, 0, 0.18); @@ -265,14 +305,21 @@ --color-overlay-deep: rgba(0, 0, 0, 0.45); --color-bg-modal: #ffffff; --color-bg-modal-elevated: #ffffff; - --color-bg-modal-glass: rgba(255, 255, 255, 0.92); + --color-bg-modal-glass: rgba(255, 255, 255, 0.85); /* 略透气 */ --color-bg-modal-hover: #f5f5f5; --color-bg-elevated: #f3f4f5; --color-bg-placeholder: #ebebeb; - --color-bg-dropdown-elevated: #ffffff; + --color-bg-dropdown-elevated: rgba(255, 255, 255, 0.95); --color-bg-video: #000; - --color-shadow-modal: rgba(0, 0, 0, 0.10); - --color-shadow-dropdown: rgba(0, 0, 0, 0.08); + --color-shadow-modal: rgba(0, 0, 0, 0.08); + --color-shadow-dropdown: rgba(0, 0, 0, 0.10); + + /* ── V2 multi-layer shadows ── */ + --shadow-card-light: 0 1px 2px rgba(0, 0, 0, 0.04), + 0 4px 16px rgba(0, 0, 0, 0.06); + --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); /* Charts — 浅色 tooltip 用白底 */ --color-tooltip-bg: rgba(255, 255, 255, 0.98); @@ -295,9 +342,16 @@ --color-mint-accent-border: rgba(13, 148, 136, 0.30); --color-mint-accent-glow: rgba(13, 148, 136, 0.18); - /* Inset highlight (浅色下用淡黑半透明做 inset) */ - --color-inset-highlight: rgba(0, 0, 0, 0.04); - --color-inset-highlight-strong: rgba(0, 0, 0, 0.06); + /* Inset highlight — V2 改为白高光,玻璃顶边视觉标志 */ + --color-inset-highlight: rgba(255, 255, 255, 0.50); + --color-inset-highlight-strong: rgba(255, 255, 255, 0.70); + + /* 暖调 chip — GitBook 风格 */ + --color-chip-warm-bg: #fff5eb; + --color-chip-warm-border: rgba(255, 180, 130, 0.40); + --color-chip-warm-text: #1a1a1a; + --color-chip-warm-badge-bg: rgba(255, 100, 50, 0.12); + --color-chip-warm-badge-text: #c2410c; /* Mention pill */ --color-mention-bg: rgba(80, 72, 204, 0.10); @@ -331,19 +385,17 @@ --color-scrollbar-thumb: rgba(0, 0, 0, 0.15); --color-scrollbar-thumb-hover: rgba(0, 0, 0, 0.30); - /* Aurora 在浅色下隐藏(下面有规则),但 var 也置弱以防万一 */ - --color-aurora-1: transparent; - --color-aurora-2: transparent; - --color-aurora-3: transparent; - --color-cursor-glow: rgba(80, 72, 204, 0.04); + /* Aurora — V2 保留 pastel 紫蓝桃,给玻璃面提供穿透色源(V1 错点:display:none) */ + --color-aurora-1: rgba(180, 167, 255, 0.30); /* pastel 紫 */ + --color-aurora-2: rgba(167, 200, 255, 0.28); /* pastel 蓝青 */ + --color-aurora-3: rgba(220, 167, 255, 0.22); /* pastel 粉紫 */ + --color-aurora-peach: rgba(255, 180, 130, 0.25); /* pastel 桃 */ + --color-cursor-glow: rgba(80, 72, 204, 0.06); --color-grid-line: rgba(0, 0, 0, 0.025); } -/* 浅色下隐藏 aurora 极光层(白底 + 极光会刺眼,纯净白更"高级") */ -[data-theme="light"] .aurora-bg, -[data-theme="light"] .aurora-blob-3 { - display: none; -} +/* V2 删除原 [data-theme="light"] .aurora-bg { display: none } — + 极光在浅色下保留 pastel,给玻璃面 backdrop-filter 提供穿透色源 */ /* ═══════════════════════════════════════════ Reset / globals diff --git a/web/src/pages/AdminAssetsPage.module.css b/web/src/pages/AdminAssetsPage.module.css index 68b2d1f..1ec778d 100644 --- a/web/src/pages/AdminAssetsPage.module.css +++ b/web/src/pages/AdminAssetsPage.module.css @@ -10,6 +10,7 @@ flex: 1; padding: 16px 20px; background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); + box-shadow: var(--shadow-card-light); } .statLabel { font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px; } @@ -21,6 +22,7 @@ .accordionItem { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); overflow: hidden; + box-shadow: var(--shadow-card-light); } .accordionHeader { diff --git a/web/src/pages/AdminLayout.module.css b/web/src/pages/AdminLayout.module.css index 42f0356..781d7e0 100644 --- a/web/src/pages/AdminLayout.module.css +++ b/web/src/pages/AdminLayout.module.css @@ -2,14 +2,23 @@ display: flex; height: 100vh; overflow: hidden; - background: var(--color-bg-page); + /* V2: transparent 让全局 AmbientBackground pastel aurora 在主区也能隐约透出 */ + background: transparent; + position: relative; + z-index: 2; } .sidebar { width: var(--sidebar-width); min-width: var(--sidebar-width); background: var(--color-bg-sidebar); + /* V2 玻璃 */ + backdrop-filter: var(--bf-glass-md); + -webkit-backdrop-filter: var(--bf-glass-md); border-right: 1px solid var(--color-border-card); + /* V2 玻璃顶边白高光 + 右侧立体感 */ + box-shadow: inset 0 1px 0 var(--color-inset-highlight), + 2px 0 12px rgba(0, 0, 0, 0.04); display: flex; flex-direction: column; transition: width 0.2s ease, min-width 0.2s ease; diff --git a/web/src/pages/AuditLogsPage.module.css b/web/src/pages/AuditLogsPage.module.css index 3f451cd..f1932cb 100644 --- a/web/src/pages/AuditLogsPage.module.css +++ b/web/src/pages/AuditLogsPage.module.css @@ -19,6 +19,7 @@ .tableWrapper { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); overflow-x: auto; + box-shadow: var(--shadow-card-light); } .table { width: 100%; border-collapse: collapse; font-size: 13px; } .table th { padding: 12px 16px; text-align: left; color: var(--color-text-secondary); font-weight: 500; border-bottom: 1px solid var(--color-border-card); white-space: nowrap; } diff --git a/web/src/pages/DashboardPage.module.css b/web/src/pages/DashboardPage.module.css index baf17ee..2c86ba5 100644 --- a/web/src/pages/DashboardPage.module.css +++ b/web/src/pages/DashboardPage.module.css @@ -21,6 +21,7 @@ border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 20px; + box-shadow: var(--shadow-card-light); } .statLabel { @@ -72,6 +73,7 @@ border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 16px; + box-shadow: var(--shadow-card-light); } /* Skeleton loading */ diff --git a/web/src/pages/LandingPage.module.css b/web/src/pages/LandingPage.module.css index a84d830..af46151 100644 --- a/web/src/pages/LandingPage.module.css +++ b/web/src/pages/LandingPage.module.css @@ -6,7 +6,7 @@ align-items: center; justify-content: center; overflow: hidden; - background: #000; + background: var(--color-bg-page); z-index: 2; } @@ -36,7 +36,7 @@ width: 80px; height: 80px; margin-bottom: 28px; - filter: drop-shadow(0 0 40px rgba(126, 220, 200, 0.25)); + filter: drop-shadow(0 0 40px var(--color-mint-accent-glow)); animation: fadeUp 1.2s ease-out 0.1s both; } @@ -44,7 +44,7 @@ font-family: 'Space Grotesk', sans-serif; font-size: 48px; font-weight: 300; - color: #f1f0ff; + color: var(--color-text-primary); letter-spacing: 0.1em; margin-bottom: 12px; line-height: 1.1; @@ -55,7 +55,7 @@ font-family: 'Space Grotesk', sans-serif; font-size: 14px; font-weight: 300; - color: rgba(255, 255, 255, 0.5); + color: var(--color-text-on-glass-soft); letter-spacing: 0.15em; margin-bottom: 48px; animation: fadeUp 1.2s ease-out 0.3s both; @@ -85,45 +85,47 @@ border-radius: 10px; cursor: pointer; transition: all 0.3s; - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + backdrop-filter: var(--bf-glass-sm); + -webkit-backdrop-filter: var(--bf-glass-sm); } .btnPrimary { - background: rgba(120, 220, 200, 0.12); - border: 1px solid rgba(120, 220, 200, 0.3); + background: var(--color-mint-accent-bg); + border: 1px solid var(--color-mint-accent-border); } .btnPrimary .btnName { font-family: 'Space Grotesk', sans-serif; font-size: 15px; font-weight: 500; - color: #7edcc8; + color: var(--color-mint-accent); } .btnPrimary:hover { - background: rgba(120, 220, 200, 0.22); - box-shadow: 0 0 30px rgba(120, 220, 200, 0.15); + background: var(--color-mint-accent-bg-hover); + box-shadow: 0 0 30px var(--color-mint-accent-glow); } .btnGhost { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); + background: var(--color-bg-glass); + border: 1px solid var(--color-border-card); + /* V2: 玻璃顶边白高光 */ + box-shadow: inset 0 1px 0 var(--color-inset-highlight); } .btnGhost .btnName { font-family: 'Space Grotesk', sans-serif; font-size: 15px; font-weight: 500; - color: rgba(255, 255, 255, 0.7); + color: var(--color-text-on-glass); } .btnGhost:hover { - background: rgba(255, 255, 255, 0.1); + background: var(--color-bg-glass-strong); } .btnGhost:hover .btnName { - color: rgba(255, 255, 255, 0.9); + color: var(--color-text-primary); } /* Sub-text below buttons */ @@ -131,14 +133,15 @@ font-family: 'Space Grotesk', sans-serif; font-size: 12px; font-weight: 300; - color: rgba(120, 220, 200, 0.5); + color: var(--color-mint-accent); + opacity: 0.65; } .btnSubGhost { font-family: 'Space Grotesk', sans-serif; font-size: 12px; font-weight: 300; - color: rgba(255, 255, 255, 0.35); + color: var(--color-text-tertiary); } /* ── Easter egg ── */ @@ -152,17 +155,19 @@ font-size: 13px; font-weight: 300; font-style: italic; - color: rgba(255, 255, 255, 0.06); + color: var(--color-text-quaternary); + opacity: 0.45; letter-spacing: 0.05em; cursor: default; - transition: color 2s ease; + transition: color 2s ease, opacity 2s ease; white-space: nowrap; text-align: center; animation: fadeUp 1.2s ease-out 0.8s both; } .easter:hover { - color: rgba(255, 255, 255, 0.25); + color: var(--color-text-tertiary); + opacity: 1; } /* ── Air Spark full-screen overlay ── */ @@ -174,7 +179,7 @@ align-items: center; justify-content: center; cursor: pointer; - background: rgba(0, 0, 0, 0.5); + background: var(--color-overlay-soft); backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px); animation: sparkBgIn 0.5s ease-out both; @@ -189,7 +194,7 @@ to { backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px); - background: rgba(0, 0, 0, 0.5); + background: var(--color-overlay-soft); } } @@ -215,7 +220,7 @@ font-family: 'Space Grotesk', sans-serif; font-size: clamp(40px, 5vw, 64px); font-weight: 300; - color: #ffffff; + color: var(--color-text-primary); line-height: 1.2; text-align: center; } @@ -229,7 +234,7 @@ font-family: 'Space Grotesk', sans-serif; font-size: 16px; font-weight: 300; - color: rgba(255, 255, 255, 0.5); + color: var(--color-text-secondary); margin-top: 20px; } @@ -253,14 +258,14 @@ -webkit-tap-highlight-color: transparent; user-select: none; -webkit-user-select: none; - color: rgba(255, 255, 255, 0.2); + color: var(--color-text-quaternary); cursor: pointer; transition: color 0.3s; animation: fadeUp 1.2s ease-out 0.8s both; } .musicBtn:hover { - color: rgba(255, 255, 255, 0.5); + color: var(--color-text-tertiary); } diff --git a/web/src/pages/LandingPage.tsx b/web/src/pages/LandingPage.tsx index e13a7d5..cdd0758 100644 --- a/web/src/pages/LandingPage.tsx +++ b/web/src/pages/LandingPage.tsx @@ -111,10 +111,9 @@ export function LandingPage({ autoLogin }: Props) { }, [playing]); return ( - // 强制深色:LandingPage 是品牌专属 Air Spark 体验页, - // 黑底 + 极光 + 薄荷绿是核心调性,浅色化会破坏品牌识别。 - // 整个登录流程(含 LoginModal / ForceChangePasswordModal)都继承这个 dark 子树。 -
+ // V2: 跟随全局主题切换。LandingPage 浅色化 = AuroraCanvas + LoginModal 都跟随。 + // 薄荷绿在浅色下加深为 teal (#0d9488),保证对比度。 +
{/* Layer 1-4: Aurora background */} diff --git a/web/src/pages/LoginRecordsPage.module.css b/web/src/pages/LoginRecordsPage.module.css index f9dec1a..aa84b99 100644 --- a/web/src/pages/LoginRecordsPage.module.css +++ b/web/src/pages/LoginRecordsPage.module.css @@ -19,6 +19,7 @@ .tableWrapper { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); overflow-x: auto; + box-shadow: var(--shadow-card-light); } .table { width: 100%; border-collapse: collapse; font-size: 13px; max-width: none; } .table th { padding: 12px 16px; text-align: left; color: var(--color-text-secondary); font-weight: 500; border-bottom: 1px solid var(--color-border-card); white-space: nowrap; } diff --git a/web/src/pages/ProfilePage.module.css b/web/src/pages/ProfilePage.module.css index d1787d4..e969421 100644 --- a/web/src/pages/ProfilePage.module.css +++ b/web/src/pages/ProfilePage.module.css @@ -135,6 +135,7 @@ display: flex; flex-direction: column; justify-content: center; + box-shadow: var(--shadow-card-light); } .quotaLabel { @@ -220,6 +221,7 @@ border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 16px; + box-shadow: var(--shadow-card-light); } /* Records */ @@ -245,6 +247,7 @@ border: 1px solid var(--color-border-card); border-radius: var(--radius-card); transition: border-color 0.2s; + box-shadow: var(--shadow-card-light); } .recordItem:hover { diff --git a/web/src/pages/RecordsPage.module.css b/web/src/pages/RecordsPage.module.css index d359d7a..a45d9d3 100644 --- a/web/src/pages/RecordsPage.module.css +++ b/web/src/pages/RecordsPage.module.css @@ -29,6 +29,7 @@ .tableWrapper { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); overflow-x: auto; + box-shadow: var(--shadow-card-light); } .table { width: 100%; border-collapse: collapse; font-size: 13px; } .table th { padding: 12px 16px; text-align: left; color: var(--color-text-secondary); font-weight: 500; border-bottom: 1px solid var(--color-border-card); white-space: nowrap; } diff --git a/web/src/pages/SettingsPage.module.css b/web/src/pages/SettingsPage.module.css index b845fb1..ceca815 100644 --- a/web/src/pages/SettingsPage.module.css +++ b/web/src/pages/SettingsPage.module.css @@ -6,6 +6,7 @@ .card { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); padding: 24px; + box-shadow: var(--shadow-card-light); } .cardHeader { display: flex; justify-content: space-between; align-items: flex-start; } .cardTitle { font-size: 16px; font-weight: 600; color: var(--color-text-primary); margin-bottom: 4px; } diff --git a/web/src/pages/TeamsPage.module.css b/web/src/pages/TeamsPage.module.css index 58e70d7..d9bf55e 100644 --- a/web/src/pages/TeamsPage.module.css +++ b/web/src/pages/TeamsPage.module.css @@ -19,6 +19,7 @@ .tableWrapper { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); overflow-x: auto; + box-shadow: var(--shadow-card-light); } .table { width: 100%; border-collapse: collapse; font-size: 13px; } .table th { padding: 12px 16px; text-align: left; color: var(--color-text-secondary); font-weight: 500; border-bottom: 1px solid var(--color-border-card); white-space: nowrap; } @@ -86,8 +87,8 @@ .detailModal { background: var(--color-bg-modal-glass); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); + backdrop-filter: var(--bf-glass-lg); + -webkit-backdrop-filter: var(--bf-glass-lg); border: 1px solid var(--color-border-modal-soft); border-radius: 16px; width: 1280px; @@ -96,7 +97,7 @@ max-height: 90vh; display: flex; flex-direction: column; - box-shadow: 0 24px 64px var(--color-shadow-modal), 0 0 0 1px var(--color-border-row) inset; + box-shadow: 0 24px 64px var(--color-shadow-modal), var(--shadow-glass-light); animation: modalIn 0.25s ease; } diff --git a/web/src/pages/UsersPage.module.css b/web/src/pages/UsersPage.module.css index d953410..2b1ee11 100644 --- a/web/src/pages/UsersPage.module.css +++ b/web/src/pages/UsersPage.module.css @@ -25,6 +25,7 @@ .tableWrapper { background: var(--color-bg-card); border: 1px solid var(--color-border-card); border-radius: var(--radius-card); overflow-x: auto; + box-shadow: var(--shadow-card-light); } .table { width: 100%; border-collapse: collapse; font-size: 13px; } .table th { padding: 12px 16px; text-align: left; color: var(--color-text-secondary); font-weight: 500; border-bottom: 1px solid var(--color-border-card); white-space: nowrap; } diff --git a/web/test/theme-screenshots-v2.mjs b/web/test/theme-screenshots-v2.mjs new file mode 100644 index 0000000..c2eba4f --- /dev/null +++ b/web/test/theme-screenshots-v2.mjs @@ -0,0 +1,141 @@ +/** + * Theme switching visual regression — captures dark + light screenshots of key pages. + * + * Run from web/ directory after starting backend (port 8000) + dev server (port 5173): + * node test/theme-screenshots.mjs + * + * Output: ../docs/screenshots/__.png + */ +import { chromium } from '@playwright/test'; +import { mkdir } from 'node:fs/promises'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OUT_DIR = resolve(__dirname, '../../docs/screenshots/v2'); +const BASE = 'http://localhost:5173'; +const API = 'http://localhost:8000'; + +const ADMIN = { username: 'admin', password: 'admin123' }; +const TEAM_USER = { username: 'screenshot_user', password: 'shotpass123' }; + +/** Set theme directly via localStorage + html attribute, no UI click needed. */ +async function setTheme(page, theme) { + await page.evaluate((t) => { + localStorage.setItem('airdrama-theme', t); + document.documentElement.dataset.theme = t; + }, theme); + // Reload to ensure ECharts and any once-mounted styles re-init + await page.reload({ waitUntil: 'domcontentloaded' }); + await page.waitForTimeout(400); +} + +/** Programmatic login: POST to API → seed tokens into localStorage → navigate. */ +async function login(page, creds) { + const res = await page.request.post(`${API}/api/v1/auth/login`, { data: creds }); + if (!res.ok()) throw new Error(`login ${creds.username} failed: ${res.status()} ${await res.text()}`); + const body = await res.json(); + const access = body?.tokens?.access; + const refresh = body?.tokens?.refresh; + const user = body?.user; + if (!access) throw new Error(`login ${creds.username}: no access token in response`); + await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded' }); + await page.evaluate(({ access, refresh, user }) => { + localStorage.setItem('access_token', access); + if (refresh) localStorage.setItem('refresh_token', refresh); + if (user) localStorage.setItem('user', JSON.stringify(user)); + }, { access, refresh, user }); +} + +async function shot(page, slug, theme) { + const file = resolve(OUT_DIR, `${slug}__${theme}.png`); + await page.screenshot({ path: file, fullPage: false }); + console.log(` ✓ ${slug}__${theme}.png`); +} + +/** Visit URL, wait for network idle + a settle timeout, then screenshot in both themes. */ +async function visitAndCapture(page, slug, url, opts = {}) { + for (const theme of ['dark', 'light']) { + await setTheme(page, theme); + await page.goto(`${BASE}${url}`, { waitUntil: 'domcontentloaded' }).catch(() => {}); + await page.waitForTimeout(opts.settle ?? 800); + if (opts.afterLoad) await opts.afterLoad(page); + await shot(page, slug, theme); + } +} + +async function main() { + await mkdir(OUT_DIR, { recursive: true }); + + const browser = await chromium.launch({ headless: true }); + const ctx = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + deviceScaleFactor: 1, + }); + const page = await ctx.newPage(); + // Mute console errors (API 4xx/5xx in empty DB are noisy but expected) + page.on('pageerror', () => {}); + page.on('console', () => {}); + + console.log(`▼ Capturing to ${OUT_DIR}`); + + // 1. Login page (no auth needed) — use a fresh context so localStorage is clean + console.log('\n[1/12] /login'); + for (const theme of ['dark', 'light']) { + await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded' }); + await setTheme(page, theme); + await page.waitForTimeout(500); + await shot(page, '01_login', theme); + } + + // 2. Login as admin → admin pages + console.log('\n[2/12] admin login'); + await login(page, ADMIN); + + await visitAndCapture(page, '02_admin_dashboard', '/admin/dashboard', { settle: 1500 }); + console.log('[3/12] /admin/dashboard'); + + await visitAndCapture(page, '03_admin_users', '/admin/users'); + console.log('[4/12] /admin/users'); + + await visitAndCapture(page, '04_admin_records', '/admin/records'); + console.log('[5/12] /admin/records'); + + await visitAndCapture(page, '05_admin_settings', '/admin/settings'); + console.log('[6/12] /admin/settings'); + + await visitAndCapture(page, '06_admin_security', '/admin/security'); + console.log('[7/12] /admin/security'); + + await visitAndCapture(page, '07_admin_logs', '/admin/logs'); + console.log('[8/12] /admin/logs'); + + await visitAndCapture(page, '08_admin_assets', '/admin/assets'); + console.log('[9/12] /admin/assets'); + + // 3. Switch to team_admin user → generation + profile + team pages + console.log('\n[10/12] team_user login'); + await ctx.clearCookies(); + await page.evaluate(() => localStorage.clear()); + await login(page, TEAM_USER); + + await visitAndCapture(page, '09_generation', '/app', { settle: 1200 }); + console.log('[10/12] /app'); + + await visitAndCapture(page, '10_profile', '/profile', { settle: 1200 }); + console.log('[11/12] /profile'); + + await visitAndCapture(page, '11_team_dashboard', '/team/dashboard', { settle: 1500 }); + console.log('[12/12] /team/dashboard'); + + await visitAndCapture(page, '12_team_members', '/team/members'); + console.log('[12/12] /team/members'); + + await browser.close(); + console.log('\n✅ done'); +} + +main().catch((err) => { + console.error('❌ screenshot run failed:', err); + process.exit(1); +});