## 🎨 前端优化：板块合并 + 序号标题 + UI 精细化 (Day 26)

### 概述

首页原有 9 个独立板块（左栏 7 个 + 右栏 2 个），每个都有自己的卡片容器和标题，视觉碎片化严重。本次将相关板块合并为 5 个主板块，添加中文序号（一~十），移除 emoji 图标，并对多个子组件的布局和交互细节进行优化。

---

## ✅ 改动内容

### 1. 板块合并方案

**左栏（4 个主板块 + 2 个独立区域）：**

| 序号 | 板块名 | 子板块 | 原组件 |
|------|--------|--------|--------|
| 一 | 文案提取与编辑 | — | ScriptEditor |
| 二 | 标题与字幕 | — | TitleSubtitlePanel |
| 三 | 配音 | 配音方式 / 配音列表 | VoiceSelector + GeneratedAudiosPanel |
| 四 | 素材编辑 | 视频素材 / 时间轴编辑 | MaterialSelector + TimelineEditor |
| 五 | 背景音乐 | — | BgmPanel |
| — | 生成按钮 | — | GenerateActionBar（不编号） |

**右栏（1 个主板块）：**

| 序号 | 板块名 | 子板块 | 原组件 |
|------|--------|--------|--------|
| 六 | 作品 | 作品列表 / 作品预览 | HistoryList + PreviewPanel |

**发布页（/publish）：**

| 序号 | 板块名 |
|------|--------|
| 七 | 平台账号 |
| 八 | 选择发布作品 |
| 九 | 发布信息 |
| 十 | 选择发布平台 |

### 2. embedded 模式

6 个组件新增 `embedded?: boolean` prop（默认 `false`）：

- `VoiceSelector` — embedded 时不渲染外层卡片和主标题
- `GeneratedAudiosPanel` — embedded 时两行布局：第 1 行（语速+生成配音右对齐）、第 2 行（配音列表+刷新）
- `MaterialSelector` — embedded 时自渲染 h3 子标题"视频素材"+ 上传/刷新按钮同行
- `TimelineEditor` — embedded 时自渲染 h3 子标题"时间轴编辑"+ 画面比例/播放控件同行
- `PreviewPanel` — embedded 时不渲染外层卡片和标题
- `HistoryList` — embedded 时不渲染外层卡片和标题（刷新按钮由 HomePage 提供）

### 3. 序号标题 + emoji 移除

所有编号板块移除 emoji 图标，使用纯中文序号：

- ScriptEditor: `✍️ 文案提取与编辑` → `一、文案提取与编辑`
- TitleSubtitlePanel: `🎬 标题与字幕` → `二、标题与字幕`
- BgmPanel: `🎵 背景音乐` → `五、背景音乐`
- HomePage 右栏: `五、作品` → `六、作品`
- PublishPage: `👤 平台账号` → `七、平台账号`、`📹 选择发布作品` → `八、选择发布作品`、`✍️ 发布信息` → `九、发布信息`、`📱 选择发布平台` → `十、选择发布平台`

### 4. 子标题与分隔样式

- **主标题**: `text-base sm:text-lg font-semibold text-white`
- **子标题**: `text-sm font-medium text-gray-400`
- **分隔线**: `<div className="border-t border-white/10 my-4" />`

### 5. 配音列表布局优化

GeneratedAudiosPanel embedded 模式下采用两行布局：
- **第 1 行**：语速下拉 + 生成配音按钮（右对齐，`flex justify-end`）
- **第 2 行**：`<h3>配音列表</h3>` + 刷新按钮（两端对齐）
- 非 embedded 模式保持原单行布局

### 6. TitleSubtitlePanel 下拉对齐

- 标题样式/副标题样式/字幕样式三行标签统一 `w-20`（固定 80px），确保下拉菜单垂直对齐
- 下拉菜单宽度 `w-1/3 min-w-[100px]`，避免过宽

### 7. RefAudioPanel 文案简化

- 原底部段落"上传任意语音样本（3-10秒）…" 移至 "我的参考音频" 标题旁，简化为 `(上传3-10秒语音样本)`

### 8. 账户下拉菜单添加手机号

- AccountSettingsDropdown 在账户有效期上方新增手机号显示区域
- 显示 `user?.phone || '未知账户'`

### 9. 标题显示模式对副标题生效

- **payload 修复**: `useHomeController.ts` 中 `title_display_mode` 的发送条件从 `videoTitle.trim()` 改为 `videoTitle.trim() || videoSecondaryTitle.trim()`，确保仅有副标题时也能发送显示模式
- **UI 调整**: 短暂显示/常驻显示下拉从片头标题输入行移至"二、标题与字幕"板块标题行（与预览样式按钮同行），明确表示该设置对标题和副标题同时生效
- Remotion 端 `Title.tsx` 已支持（标题和副标题作为整体组件渲染，`displayMode` 统一控制）

### 10. 时间轴模糊遮罩

遮罩从外层 wrapper 移入"四、素材编辑"卡片内，仅覆盖时间轴子区域（`rounded-xl`）。

### 11. 登录后用户信息立即可用

- AuthContext 新增 `setUser` 方法暴露给消费者
- 登录页成功后调用 `setUser(result.user)` 立即写入 Context，无需等页面刷新
- 修复登录后账户下拉显示"未知账户"、刷新后才显示手机号的问题

### 12. 文案与选项微调

- MaterialSelector 描述 `(可多选，最多4个)` → `(上传自拍视频，最多可选4个)`
- TitleSubtitlePanel 显示模式选项 `短暂显示/常驻显示` → `标题短暂显示/标题常驻显示`

### 13. UI/UX 体验优化（6 项）

- **操作按钮移动端可见**: 配音列表、作品列表、素材列表、参考音频、历史文案的操作按钮从 `opacity-0`（hover 才显示）改为 `opacity-40`（平时半透明可见，hover 全亮），解决触屏设备无法发现按钮的问题
- **手机号脱敏**: AccountSettingsDropdown 手机号中间四位遮掩 `138****5678`
- **标题字数计数器**: TitleSubtitlePanel 标题/副标题输入框右侧显示实时字数 `3/15`，超限变红
- **列表滚动条提示**: ~~配音列表、作品列表、素材列表、BGM 列表从 `hide-scrollbar` 改为 `custom-scrollbar`~~ → 已全部改回 `hide-scrollbar` 隐藏滚动条（滚动功能不变）
- **时间轴拖拽提示**: TimelineEditor 色块左上角新增 `GripVertical` 抓手图标，暗示可拖拽排序
- **截取滑块放大**: ClipTrimmer 手柄从 16px 放大到 20px，触控区从 32px 放大到 40px

### 14. 代码质量修复（4 项）

- **AccountSettingsDropdown**: 关闭密码弹窗补齐 `setSuccess('')` 清空
- **MaterialSelector**: `selectedSet` 加 `useMemo` 避免每次渲染重建
- **TimelineEditor**: `visibleSegments`/`overflowSegments` 加 `useMemo`
- **MaterialSelector**: 素材满 4 个时非选中项按钮加 `disabled`

### 15. 发布页平台账号响应式布局

- **单行布局**：图标+名称+状态在左，按钮在右（`flex items-center`）
- **移动端紧凑**：图标 `h-6 w-6`、按钮 `text-xs px-2 py-1 rounded-md`、间距 `space-y-2 px-3 py-2.5`
- **桌面端宽松**：`sm:h-7 sm:w-7`、`sm:text-sm sm:px-3 sm:py-1.5 sm:rounded-lg`、`sm:space-y-3 sm:px-4 sm:py-3.5`
- 两端各自美观，风格与其他板块一致

### 16. 移动端刷新回顶部修复

- **问题**: 移动端刷新页面后不回到顶部，而是滚动到背景音乐板块
- **根因**: 1) 浏览器原生滚动恢复覆盖 `scrollTo(0,0)`；2) 列表 scroll effect 有双依赖（`selectedId` + `list`），数据异步加载时第二次触发跳过了 ref 守卫，执行了 `scrollIntoView` 导致页面跳动
- **修复**: 三管齐下 — ① `history.scrollRestoration = "manual"` 禁用浏览器原生恢复；② 时间门控 `scrollEffectsEnabled` ref（1 秒内禁止所有列表自动滚动）替代单次 ref 守卫；③ 200ms 延迟兜底 `scrollTo(0,0)`

### 17. 移动端样式预览窗口缩小

- **问题**: 移动端点击"预览样式"后窗口占满整屏（宽 358px，高约 636px），遮挡样式调节控件
- **修复**: 移动端宽度从 `window.innerWidth - 32` 缩小到 **160px**；位置从左上角改为**右下角**（`right:12, bottom:12`），不遮挡上方控件；最大高度限制 `50dvh`
- 桌面端保持不变（280px，左上角）

### 18. 列表滚动条统一隐藏

- 将 Day 26 早期改为 `custom-scrollbar`（细紫色滚动条）的 7 处全部改回 `hide-scrollbar`
- 涉及：BgmPanel、GeneratedAudiosPanel、HistoryList、MaterialSelector（2处）、ScriptExtractionModal（2处）
- 滚动功能不受影响，仅视觉上不显示滚动条

### 19. 配音按钮移动端适配

- VoiceSelector "选择声音/克隆声音" 按钮：内边距 `px-4` → `px-2 sm:px-4`，字号加 `text-sm sm:text-base`，图标加 `shrink-0`
- 修复移动端窄屏下按钮被挤压导致"克隆声音"不可见的问题

### 20. 素材标题溢出修复

- MaterialSelector embedded 标题行移除 `whitespace-nowrap`
- 描述文字 `(上传自拍视频，最多可选4个)` 在移动端隐藏（`hidden sm:inline`），桌面端正常显示
- 修复移动端刷新按钮被推出容器外的问题

### 21. 生成配音按钮放大

- "生成配音" 作为核心操作按钮，从辅助尺寸升级为主操作尺寸
- 内边距 `px-2/px-3 py-1/py-1.5` → `px-4 py-2`，字号 `text-xs` → `text-sm font-medium`
- 图标 `h-3.5 w-3.5` → `h-4 w-4`，新增 `shadow-sm` + hover `shadow-md`
- embedded 与非 embedded 模式统一放大

### 22. 生成进度条位置调整

- **问题**: 生成进度条在"六、作品"卡片内部（作品预览下方），不够醒目
- **修复**: 进度条从 PreviewPanel 内部提取到 HomePage 右栏，作为独立卡片渲染在"六、作品"卡片**上方**
- 使用紫色边框（`border-purple-500/30`）区分，显示任务消息和百分比
- PreviewPanel embedded 模式下不再渲染进度条（传入 `currentTask={null}`）
- 生成完成后进度卡片自动消失

### 23. LatentSync 超时修复

- **问题**: 约 2 分钟的视频（3023 帧，190 段推理）预计推理 54 分钟，但 httpx 超时仅 20 分钟，导致 LatentSync 调用失败并回退到无口型同步
- **根因**: `lipsync_service.py` 中 `httpx.AsyncClient(timeout=1200.0)` 不足以覆盖长视频推理时间
- **修复**: 超时从 `1200s`（20 分钟）改为 `3600s`（1 小时），足以覆盖 2-3 分钟视频的推理

### 24. 字幕时间戳节奏映射（修复长视频字幕漂移）

- **问题**: 2 分钟视频字幕明显对不上语音，越到后面偏差越大
- **根因**: `whisper_service.py` 的 `original_text` 处理逻辑丢弃了 Whisper 逐词时间戳，仅保留总时间范围后做全程线性插值，每个字分配相同时长，完全忽略语速变化和停顿
- **修复**: 保留 Whisper 的逐字时间戳作为语音节奏模板，将原文字符按比例映射到 Whisper 时间节奏上（rhythm-mapping），而非线性均分。字幕文字不变，只是时间戳跟随真实语速
- **算法**: 原文第 i 个字符映射到 Whisper 时间线的 `(i/N)*M` 位置（N=原文字符数，M=Whisper字符数），在相邻 Whisper 时间点间线性插值

---

## 📁 修改文件清单

| 文件 | 改动 |
|------|------|
| `VoiceSelector.tsx` | 新增 embedded prop，移动端按钮适配（`px-2 sm:px-4`） |
| `GeneratedAudiosPanel.tsx` | 新增 embedded prop，两行布局，操作按钮可见度，"生成配音"按钮放大 |
| `MaterialSelector.tsx` | 新增 embedded prop，自渲染子标题+操作按钮，useMemo，disabled 守卫，操作按钮可见度，标题溢出修复 |
| `TimelineEditor.tsx` | 新增 embedded prop，自渲染子标题+控件，useMemo，拖拽抓手图标 |
| `PreviewPanel.tsx` | 新增 embedded prop |
| `HistoryList.tsx` | 新增 embedded prop，操作按钮可见度 |
| `ScriptEditor.tsx` | 标题加序号，移除 emoji，操作按钮可见度 |
| `TitleSubtitlePanel.tsx` | 标题加序号，移除 emoji，下拉对齐，显示模式下拉上移，字数计数器 |
| `BgmPanel.tsx` | 标题加序号 |
| `HomePage.tsx` | 核心重构：合并板块、序号标题、生成配音按钮迁入、`scrollRestoration` + 延迟兜底修复刷新回顶部、生成进度条提取到作品卡片上方 |
| `PublishPage.tsx` | 四个板块加序号（七~十），移除 emoji，平台卡片响应式单行布局 |
| `RefAudioPanel.tsx` | 简化提示文案，操作按钮可见度 |
| `AccountSettingsDropdown.tsx` | 新增手机号显示（脱敏），补齐 success 清空 |
| `AuthContext.tsx` | 新增 `setUser` 方法，登录后立即更新用户状态 |
| `login/page.tsx` | 登录成功后调用 `setUser` 写入用户数据 |
| `useHomeController.ts` | titleDisplayMode 条件修复，列表 scroll 时间门控 `scrollEffectsEnabled` |
| `FloatingStylePreview.tsx` | 移动端预览窗口缩小（160px）并移至右下角 |
| `ScriptExtractionModal.tsx` | 滚动条改回隐藏 |
| `ClipTrimmer.tsx` | 滑块手柄放大、触控区增高 |
| `lipsync_service.py` | httpx 超时从 1200s 改为 3600s |
| `whisper_service.py` | 字幕时间戳从线性插值改为 Whisper 节奏映射 |

---

## 🔍 验证

- `npm run build` — 零报错零警告
- 合并后布局：各子板块分隔清晰、主标题有序号
- 向后兼容：`embedded` 默认 `false`，组件独立使用不受影响
- 配音列表两行布局：语速+生成配音在上，配音列表+刷新在下
- 下拉菜单垂直对齐正确
- 短暂显示/常驻显示对标题和副标题同时生效
- 操作按钮在移动端（触屏）可见
- 手机号脱敏显示
- 标题字数计数器正常
- 列表滚动条全部隐藏
- 时间轴拖拽抓手图标显示
- 发布页平台卡片：移动端紧凑、桌面端宽松，风格一致
- 移动端刷新后回到顶部，不再滚动到背景音乐位置
- 移动端样式预览窗口不遮挡控件
- 移动端配音按钮（选择声音/克隆声音）均可见
- 移动端素材标题行按钮不溢出
- 生成配音按钮视觉层级高于辅助按钮
- 生成进度条在作品卡片上方独立显示
- LatentSync 长视频推理不再超时回退
- 字幕时间戳与语音节奏同步，长视频不漂移
