diff --git a/.gitignore b/.gitignore index 4e5a4f6..c7ed5a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,8 @@ -# 工程文件 -node_modules/ -.next/ -.turbo/ -dist/ -build/ - -# OS / IDE +node_modules +.next +out +.env*.local .DS_Store -Thumbs.db -.vscode/ -.idea/ -*.swp - -# 日志 -*.log -npm-debug.log* -pnpm-debug.log* - -# 本地环境 -.env -.env.local -.env.*.local - -# 临时 -*.tmp -*.bak -screenshots/ +*.tsbuildinfo +next-env.d.ts.bak +_design_src diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..d4d135a --- /dev/null +++ b/PRD.md @@ -0,0 +1,1150 @@ +# 带货流水线(暂名) — 产品 PRD v0.3 + +> 2026-05-09 起草|v0.3 = v0.2 + 用户多轮对齐 + brief 视觉路线 +> 文档定位:**产品 PRD**,描述"做什么/怎么用/长什么样",不涉及技术实现 + +--- + +## 0. 文档约定 + +- 本文档"用户"指**商家端使用者**(运营、内容策划、店主、代运营成员) +- 商品 = SKU,是一等公民业务对象 +- 资产 = 视觉素材的统称(商品/人物/场景/故事板/视频片段/成片) +- 工序 = Stage = 流水线阶段(共 5 段) +- 数字(时长、阈值、积分)均为 V1 默认值,运营可后台调整 +- **视觉风格**:克制现代 SaaS,谱系参考 Linear / Stripe / Vercel / Notion(详见 §4) + +--- + +## 1. 产品定位 + +### 1.1 一句话定义 + +> **抖音白牌商家的 AI 短剧化带货视频生成平台**——商家选商品 → AI 出脚本(自动切镜+切段)→ 生成基础资产(人物/场景/商品图)→ GPT-image-2 出故事板大图 → Seedance 按段出 15s 视频片段 → 时间线组装 → 导出 ≤60 秒短剧化带货成片。 + +### 1.2 解决的核心问题 + +| 现状痛点 | 我们的方案 | +|---------|----------| +| 商家拍带货视频成本高(主播/场地/剪辑),单条 ≥ 数百元 | AI 全流程生成,单条算力成本几元到十几元 | +| 第三方代运营周期 3-7 天,沟通成本高 | 自助创建,1-2 小时出成片 | +| 现有 AI 视频工具碎片化,不能直发抖音 | 端到端流水线,输出 9:16 ≤60s 直发成片 | +| 商家怕 AI 出错浪费钱 | 5 阶段每段都有人工审核门,**失败不扣费、确认后扣** | +| 同商家做多条视频,要复用主播/场景/商品图 | **团队级跨项目资产库**,主播/场景/商品图沉淀复用 | +| 商家自己已写好口播稿,想要平台只做视觉 | 脚本入口支持"自带粘贴" | +| 商家不会写脚本 | 脚本入口支持"AI 全生" + "一句话主题" | +| MCN/代运营管多账号、需要分工 | 团队 + 角色 + 四层额度 + 共享资产库 | + +### 1.3 V1 / V2 范围 + +#### V1 In scope + +- **完整 B2B 团队体系**:超管 / 团管 / 成员 + 四层额度(用户日/月 + 团队月/总)+ 团队共享资产库 +- 商品库(独立一级模块) +- 项目管理(创建/列表/搜索/复制/归档/删除) +- 5 阶段流水线:脚本 → 基础资产 → 故事板 → 视频片段 → 拼接导出 +- 4 种脚本入口(AI 全生 / 自带粘贴 / 一句话主题 / 复刻爆款灰掉占位) +- GPT-image-2 故事板大图(一次出整张,整张重跑) +- Seedance 按 15s 段生成(60s = 4 段) +- 失败不扣费 + 确认后扣 + 团队级额度预检 +- 时间线组装(裁剪/排序/转场/字幕/BGM) +- 成片 ≤ 60 秒,9:16 竖版 +- 资产库(跨项目共享 + 项目内)+ 中间产物可下载 +- 运营后台简化版(商家管理 / 任务监控 / Skill 管理 / 财务对账) + +#### V2 Out of scope + +- 4-8 条变体批量生成 +- 复刻爆款(依赖 Gemini Vision API) +- 组装编辑器的"我的模板"保存与套用 +- 抖音直发对接 / 数据回流 +- 长视频(>60s) / 横屏视频 +- 多商品同框(一个项目带多 SKU) +- 登录风控规则(5 条规则 + 自动封号 + 飞书告警)—— 待评估接 AirDrama 已有实现 + +#### V1 明确不做 + +- 内容审核 / 合规违禁词前置 +- 直播间相关 +- 投放(DOU+/千川) +- 带货商品系统对接(淘宝/拼多多) + +--- + +## 2. 目标用户与场景 + +### 2.1 用户画像 + +| 画像 | 占比预估 | 特征 | 关键诉求 | +|------|---------|------|---------| +| **抖音白牌商家** | 50% | 抖店/淘宝小店主,SKU 5-50,自己或小团队做内容 | 便宜、快、成片直接能发 | +| **代运营机构 / MCN** | 35% | 服务多个商家,专业团队 5-20 人 | **团队协作、四层额度、批量生产** | +| **达人/带货号** | 15% | 个人 IP 或小团队,做种草/测评 | 主播形象稳定、剧情多变 | + +### 2.2 核心场景 + +#### 场景 A:MCN 老张 + 5 个运营成员 + +1. 老张作为团队超管,注册团队账号,充值 ¥5000 总额度,设月限额 ¥3000 +2. 邀请 5 个运营成员,分配每日 ¥100 / 月 ¥1500 额度 +3. 老张把"主播虚拟人小美"的人物图上传到团队共享资产库 +4. 运营 A 接到商家"补水面膜"任务,新建项目时选商品 → 资产生成阶段绑定团队已有"小美" → 节省费用 +5. 运营 B 同时做另一个项目,也用"小美" +6. 老张在「消费」页看本月已消耗、各成员明细、按项目分摊 + +#### 场景 B:店主小李第一次用平台 + +1. 注册账号自动建独立"团队"(团队=店铺,仅小李一人) +2. 进商品库建第一个商品"补水面膜",传 3 张图、写卖点+人群 +3. 进项目创建:选商品 → AI 全生 + 痛点种草模板 → 开始 +4. **Stage 1 脚本**:AI 出 60s 脚本(4 段 × 15s × 6 镜 = 24 镜),小李对话改钩子 → 确认 +5. **Stage 2 基础资产**: + - 2a 人物图:AI 自动按脚本提取"主播-小美 / 朋友 A",出图,过;用户加了一个"宠物猫"角色 + - 2b 场景图:AI 提取"卧室 / 浴室",第一张不满意重跑,过 + - 2c 商品图:从商品库引用面膜图,[AI 优化] 一键去白底+打光,过 +6. **Stage 3 故事板**:GPT-image-2 一次出 4 张故事板大图(每张 ~6 镜),第 3 张不满意 → 改提示词重跑该张 → 全过 +7. **Stage 4 视频片段**:4 段并发提交 Seedance(每段输入第 K 张故事板 + 全套基础资产 + "按故事板生成视频"),全过(其中 1 段重跑了 1 次) +8. **Stage 5 拼接**:4 段 15s 自动拼成 60s → 加抖音电商字幕样式 → 选卡点 BGM → 导出 1080P MP4 +9. 总时长 ~50 分钟,消耗 ¥22 + +--- + +## 3. 信息架构(页面地图) + +``` +登录 / 注册 + │ + ▼ +┌─────────────── 主应用(左侧 Sidebar 持久导航)─────────────────┐ +│ │ +│ 📊 工作台 ──► /dashboard 概览 + 最近项目 │ +│ │ +│ 🛍️ 商品库 ──► /products 商品卡片网格 ⭐核心 │ +│ │ │ +│ ├──────► /products/new 新建商品 │ +│ └──────► /products/:id 商品详情 │ +│ │ +│ 🎬 视频项目 ──► /projects 项目列表(按状态 Tab) │ +│ │ │ +│ ├──────► /projects/new 新建项目向导(4 步) │ +│ │ │ +│ └──────► /projects/:id 流水线主界面 ⭐核心 │ +│ ├ stage 1 脚本 │ +│ ├ stage 2 基础资产(2a/2b/2c) │ +│ ├ stage 3 故事板(K 张) │ +│ ├ stage 4 视频片段(K 段) │ +│ └ stage 5 拼接导出 │ +│ │ +│ 📦 资产库 ──► /library 跨团队共享 + 项目内 │ +│ │ +│ 👥 团队 ──► /team 成员 / 角色 / 邀请 │ +│ │ +│ 💰 消费 ──► /billing 余额 / 充值 / 额度 / 账单│ +│ │ +│ ⚙️ 设置 ──► /settings 个人 + 团队偏好 │ +│ │ +└────────────────────────────────────────────────────────────────┘ +``` + +### 3.1 页面优先级 + +| 优先级 | 页面 | 状态 | +|-------|------|------| +| **P0** | 工作台 Dashboard | 必出 | +| **P0** | 商品库(列表+详情+新建) | 必出 | +| **P0** | 项目列表 | 必出 | +| **P0** | 新建项目向导 | 必出 | +| **P0** | 流水线主界面(5 阶段) | 必出 | +| **P1** | 资产库 | 必出 | +| **P1** | 团队(成员/角色/邀请/额度) | 必出 | +| **P1** | 消费(余额/充值/账单) | 必出 | +| **P2** | 登录/注册 | 必出 | +| **P2** | 设置 | 出占位 | +| **平行** | 运营后台 | V1 独立工程,简化版 | + +--- + +## 4. 视觉风格规范 ⭐ + +> 整体定位:**克制现代 SaaS + 工业级精致 + 轻未来感** +> 谱系:Linear(极简系统感)+ Stripe(金融级精致)+ Vercel(黑白现代)+ Notion(留白克制) +> 不用:电商红/橙/亮黄、渐变铺面、磨砂玻璃拟物、霓虹色、暗色玻璃紫 +> 商家是抖音白牌商家但他们也用 Mac、看 Apple、用 Notion——给一套"看着就像专业工具"的界面,强化平台权威感。 + +### 4.1 配色系统 + +主色调:**浅色为主**(白天作业场景多),暗色模式延后 V2 + +| 用途 | 颜色 | 色值 | +|------|------|------| +| 主背景 | 纯白 | `#FFFFFF` | +| 浅暖灰背景 | 暖灰 | `#FAFAF9` | +| 表面层 | 极浅灰 | `#F5F5F4` | +| 边框/分隔 | 极轻灰 | `#E7E5E4` | +| 主文字 | 近黑 | `#0C0A09` | +| 次级文字 | 中性灰 | `#57534E` | +| 弱化文字 | 浅灰 | `#A8A29E` | +| **强调色(主 CTA)** | **深沉电致蓝** | `#1E40AF` | +| 成功 | 森林绿 | `#15803D` | +| 警告 | 沉稳琥珀 | `#B45309` | +| 错误 | 克制红 | `#B91C1C` | +| 信息 | 同强调色蓝 | `#1E40AF` | + +### 4.2 字体系统 + +| 用途 | 字体 | 字号梯度 | 字重 | +|------|------|---------|------| +| 中文 | HarmonyOS Sans(首选)/ 思源黑体 / PingFang SC | 12 / 14 / 16 / 20 / 24 / 32 / 40 | 400 主用,500 强调,600 标题 | +| 英文/数字 | Inter(首选)/ SF Pro | 同上 | 同上,避免 Bold(700+) | +| 数字(金额/计数) | Inter tabular numerals 等宽 | — | — | + +行高:1.5 正文 / 1.3 标题 + +### 4.3 形状 / 间距 / 层级 + +| 项 | 标准 | +|----|------| +| 圆角 | 6-8px 主流元素 / 12px 大卡片 / 不超过 16px | +| 阴影 | 极轻量。优先 1px border 划层级,shadow 仅用于浮层 | +| Modal | `0 8px 24px rgba(0,0,0,0.08)` | +| Card hover | `0 1px 3px rgba(0,0,0,0.04)` | +| 间距系统 | 4px 基准,常用 8 / 12 / 16 / 24 / 32 / 48 | +| 栅格 | 12 列,最大内容宽 1280px,左右 padding | + +### 4.4 动效 + +- 基调:ease-out 200-300ms,克制不张扬 +- 不要:Material Ripple、bouncy spring 弹跳、夸张 page transition +- 要:状态切换轻量过渡、hover 微反馈(亮度+5% 或 scale 1.02)、loading 用 skeleton 不用 spinner + +### 4.5 图标 + +- 风格:线性图标,2px stroke,圆角端点 +- 库:Lucide / Heroicons / Tabler Icons(统一一套,不混用) +- 大小:16 / 20 / 24 + +### 4.6 按钮层级 + +| 层级 | 样式 | +|------|------| +| **Primary(主行动)** | 实色填充强调色 `#1E40AF`,白字,hover 加深 5% | +| **Secondary(次要)** | 白底 + border + 主色文字 | +| **Tertiary(弱化)** | 无 border 无 bg,纯文字 + hover 加底色 | +| **Destructive(删除)** | 错误色 border,hover 才转实色 | + +### 4.7 关键 DO / DON'T + +**DO ✅** +- 大量留白,不怕空 +- 信息分层用字号 + 字重 + 颜色 +- 数字用 tabular numerals +- 视频缩略图保持 9:16 真实比例 +- 状态用色斑(小色块/徽章),不用大面积填充 + +**DON'T ❌** +- 不用渐变铺面(除极小面积装饰) +- 不用电商红 / 拼多多橙 / 大牌饱和色 +- 不用磨砂玻璃 / 拟物化 / Skeuomorphism +- 不用浮动操作按钮(FAB) +- 不堆叠 3 层以上阴影 +- 不用 emoji 做装饰图标 + +### 4.8 主应用布局 + +``` +┌─────────────┬─────────────────────────────────────────────────────────┐ +│ │ ┌─────────────────────────────────────────────────────┐│ +│ │ │ Breadcrumb:项目 / 补水面膜 / 流水线 ││ +│ Sidebar │ │ [操作区] ││ +│ 240px │ └─────────────────────────────────────────────────────┘│ +│ (白底) │ │ +│ ───── │ ※ 主内容区(白底,max-w 1280)※ │ +│ 📊 工作台 │ │ +│ 🛍️ 商品库 │ (根据当前页面渲染) │ +│ 🎬 项目 │ │ +│ 📦 资产 │ │ +│ 👥 团队 │ │ +│ 💰 消费 │ │ +│ ⚙️ 设置 │ │ +│ ───── │ │ +│ 👤 用户 │ │ +│ ◀ 收起 │ │ +└─────────────┴─────────────────────────────────────────────────────────┘ +``` + +--- + +## 5. 业务流程(端到端) + +### 5.1 完整旅程图 + +``` +注册账号 (创建团队,注册者自动为超管) + │ + ▼ +进入 Dashboard + │ + ▼ +[超管/团管] 邀请成员、分配额度 (可选,单人也能直接做项目) + │ + ▼ +进商品库 ──► [+ 新建商品] (传图+卖点+人群) + │ │ + │ ▼ 商品入库 + │ + ▼ +回 Dashboard 点 [+ 新建项目] + │ + ▼ +向导 4 步:① 选商品 → ② 选脚本来源 → ③ 选模板(仅 AI 全生时)→ ④ 摘要确认 + │ + ▼ +进入流水线(落到 Stage 1) + │ + ▼ +【Stage 1】脚本 ──► 用户审核 ──► [✓ 确认] + │ ↑ + ▼ │ +【Stage 2】基础资产 │ + │ ├ 2a 人物图(AI 自动出 + 用户增/删/改)│ + │ ├ 2b 场景图(同上) │ + │ └ 2c 商品图(商品库引用 + AI 优化)│ + │ 入跨项目共享库 │ + │ ↑ + ▼ │ +【Stage 3】故事板(K 张大图,1:1 对应 K 段视频)│ + │ GPT-image-2 一次出整张,可整张重跑 │ + │ 入项目内库 │ + │ ↑ + ▼ │ +【Stage 4】视频片段 ──► K 段并发跑 ──► 逐段审核 ──► [全通过] + │ 每段输入:第 K 张故事板 + 基础资产 │ + │ 入项目内库 │ + │ ↑ + ▼ │ +【Stage 5】拼接导出 ──► 字幕/BGM/转场 ──► [✓ 确认导出] │ + │ │ + ▼ │ +成片入库 + 可下载 │ + │ +任意阶段可点 [↩ 回退] 回到上游 ────────────────────────────┘ +``` + +### 5.2 阶段详细规则 + +#### 【Stage 1】脚本生成(Agent 对话) + +**入口(4 选 1,新建项目时确定)**: + +| 入口 | 输入 | 适合场景 | +|------|------|---------| +| **AI 全生** | 商品(来自商品库)+ 卖点 + 模板 | 不会写脚本的小白商家 | +| **自带脚本** | 商家粘贴自己写的口播稿 | 已有文案的商家、达人 | +| **一句话主题** | 商品 + 一句话方向 | 有想法但懒得写 | +| **复刻爆款** | 抖音爆款视频 URL | V1 灰掉占位(V2 接 Gemini Vision) | + +**AI 输出(全自动结构化)**: +- 总时长:30s / 45s / 60s(用户选档位 or AI 推荐) +- **自动切分为 K 段(每段 15s)**:30s→K=2,45s→K=3,60s→K=4 +- 每段约 6 镜,总 N 镜(N = K × 6 ± 浮动) +- 每镜含:镜号、时长(如"0-2s")、场景、角色行为、对白/旁白、镜头建议 + +> 镜数和切分点全由 AI 决定,用户不关心也不调整。 + +**界面**:双栏 +- 左:Agent 对话框(聊天 UI) +- 右:脚本结构化预览(按段+镜分组) + +**用户操作**: +- 对话框跟 AI 沟通调整("第二段语气改活泼") +- 行内编辑某镜文字 +- 单镜重跑 / 整体重跑 + +**审核门**:[✓ 确认脚本] —— 主蓝色 CTA + +**回退**:后续阶段可点 [↩ 回 Stage 1] 改脚本,弹窗确认"清空下游所有产出" + +--- + +#### 【Stage 2】基础资产生成(3 子步,不再有元素提取阶段) + +> AI 自动从已确认脚本中提取人物 + 场景清单,**直接进入生图,无需中间审核步**。 +> 用户在生图阶段可以增/删/改清单。 + +##### Stage 2a — 👤 人物图 + +**系统行为**:AI 提取脚本中的人物 → 并发调用 image-2 出图 + +**界面**:每个人物一张卡片 +- 人物名 + AI 生成的图片预览 +- 提示词(默认 AI 生成,用户可改) +- 操作按钮 + +**用户操作**: + +| 操作 | 行为 | +|------|------| +| [✓ 通过] | 入【跨项目共享库 - 人物图】 | +| [↻ 重跑] | 同提示词再生 | +| [📝 改词重跑] | 改提示词后重生 | +| [📤 上传替换] | 上传本地图替代,也入库 | +| [🔗 绑定已有] | 从团队共享库选已有人物图(不重生,省费) | +| [+ 添加资产] | 用户主动加一个 AI 没提取出的人物 | +| [- 删除资产] | 用户认为该人物不需要,删除 | +| [🔍 看大图] | 模态框看高清版 | + +**审核门**:所有人物都已 [✓ 通过] / [🔗 绑定] / [📤 上传] → [→ 进入场景图] + +##### Stage 2b — 🌆 场景图 + +操作完全镜像 2a,针对场景清单。入库到【跨项目共享库 - 场景图】。 + +##### Stage 2c — 📦 商品图 + +**输入**:项目创建时选定的商品(从商品库引用) + +**界面**:商品图卡片 + 操作选项 +- 显示商品库的原图 +- [📷 用原图] —— 不优化 +- [✨ AI 优化] —— **一键预制 pipeline**: + - 自动跑:去白底 → 重打光 → 清晰化锐化 + - 输出优化版与原图并排展示 + - 选 [✓ 用优化版] / [↺ 重新优化] / [↺ 用原图] +- [📤 上传新图] —— 上传新的商品图替代 +- [+ 添加商品图] —— 同一项目可用多张商品图 + +> AI 优化是**预制提示词驱动的固定操作**,**不开放菜单细分**("去背景/换光/调角度"等)。一键解决,V2 加"专家模式"。 + +**入库**:原图 + 优化版都入【跨项目共享库 - 商品图】,标签区分 + +**审核门**:所有商品图都已确认 → [→ 进入故事板生成] + +--- + +#### 【Stage 3】故事板生成 ⭐ 核心 + +**输入**:脚本 + 人物图 + 场景图 + 商品图(Stage 2 全套基础资产) + +**系统行为**: +- 脚本被切分为 K 段(K = 1/2/3/4,基于总时长 / 15) +- 对每段调用 **GPT-image-2 一次性生成一张大图** +- 这张大图包含该段所有镜头的视觉布局(如示例:6 镜表格,每行:镜号+时长 / 画面 / 机位运动 / 对白音效) +- **整张是一个不可拆分的视觉单元** + +**界面**:K 张故事板卡片并列展示 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 故事板列表(共 K=4 张) │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ 第 1 段 │ │ 第 2 段 │ │ 第 3 段 │ │ 第 4 段 │ │ +│ │ 0-15s │ │ 15-30s │ │ 30-45s │ │ 45-60s │ │ +│ │ 6 镜 │ │ 6 镜 │ │ 6 镜 │ │ 6 镜 │ │ +│ │[缩略图] │ │[缩略图] │ │[缩略图] │ │[缩略图] │ │ +│ │ ✓ 通过 │ │ ✓ 通过 │ │ ⚠ 待审 │ │ ○ 待跑 │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**单张故事板审核页**(点缩略图打开): + +``` +┌────────────────────────────────────────────────────────────┐ +│ 第 3 段 · 30-45s · 6 镜 [↩ 回 Stage 2 改基础资产]│ +│ ──────────────────────────────────────────────────────────│ +│ ┌──────────────────┬─────────────────────────────────┐ │ +│ │ │ │ │ +│ │ 故事板大图 │ 右侧元数据列(只读) │ │ +│ │ 居中展示 │ ────────────────── │ │ +│ │ (9:16 长图) │ 镜 1 (0-2s) │ │ +│ │ 保持原比例 │ 中景/固定 · 画外音"..." │ │ +│ │ │ 镜 2 (2-5s) │ │ +│ │ 工具: │ ... │ │ +│ │ [🔍 放大] │ ... │ │ +│ │ [📥 下载] │ │ │ +│ │ │ ★ 元数据由 AI 同步生成 │ │ +│ │ │ ★ V1 不支持单镜文字编辑 │ │ +│ └──────────────────┴─────────────────────────────────┘ │ +│ │ +│ 提示词(用户可改后重跑): │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ 6 镜分镜板,镜号+时长+画面+机位+对白格式... │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ +│ [↺ 整张重跑] [✓ 通过这张故事板] │ +└────────────────────────────────────────────────────────────┘ +``` + +**用户操作(每张故事板独立)**: + +| 操作 | 行为 | +|------|------| +| [↺ 整张重跑] | 用默认提示词整张重新生成 | +| [📝 改提示词后重跑] | 编辑提示词,整张重新生成 | +| [✓ 通过] | 入【项目内库 - 故事板】,进度推进 | +| [↩ 回 Stage 2] | 回退改基础资产,下游清空 | +| [↩ 回 Stage 1] | 回退改脚本,全部下游清空 | + +**关键约束**: +- ⚠ **不能局部改单张内的某镜**(GPT-image-2 输出是不可拆的整张图) +- ⚠ **Seedance 用的也是整张故事板**(下一阶段) +- 单张故事板可独立重跑,不影响其他张 + +**审核门**:所有 K 张都 [✓ 通过] → [→ 进入视频生成] + +--- + +#### 【Stage 4】视频片段生成(Seedance) + +**输入**(每段独立): +- 第 K 张故事板(整张) +- 全套基础资产(人物图 + 场景图 + 商品图) +- 提示词:默认 "按照分镜故事板生成视频",用户可改 + +**系统行为**: +- K 段并发提交 Seedance(K = 故事板张数 = 1/2/3/4) +- 每段输出 15s 视频片段 + +**界面**:K 段视频卡片 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 片段 3 30-45s [✓通过][↻重跑]│ +│ ────────────────────────────────────────────────────── │ +│ │ +│ ┌── 输入资产(只读)─────────────────────────────────────┐│ +│ │ [故事板3 缩略] [人物图×N] [场景图×N] [商品图×N] ││ +│ │ ★ 要改资产请回 Stage 3 / Stage 2 ││ +│ └─────────────────────────────────────────────────────────┘│ +│ │ +│ 提示词(可编辑): │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ 按照分镜故事板生成视频 │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ +│ ┌── 视频预览(9:16)──────────────────────────────────┐ │ +│ │ [视频播放器,15s] │ │ +│ │ 生成中… ●●● 65% 预计剩余 1:20 │ │ +│ └────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**用户操作(每段独立)**: + +| 操作 | 行为 | +|------|------| +| [✓ 通过] | 视频片段入【项目内库 - 视频片段】 | +| [↻ 重跑] | 同提示词再生 | +| [📝 改词重跑] | 改提示词后再生 | +| [↩ 回 Stage 3] | 改故事板(影响该段及下游) | + +**审核门**:所有 K 段都通过 → [→ 进入拼接] + +--- + +#### 【Stage 5】拼接 & 导出 + +**输入**:所有通过的视频片段(按段号排序) + +**系统行为**: +- 默认按段号顺序自动拼接(无转场,无字幕,无 BGM) +- 生成预览版(低码率快速版) + +**用户操作区**: + +``` +┌── 时间轴预览 ────────────────────────────────────────────┐ +│ [片段1] [片段2] [片段3] [片段4] │ ← 拖拽重排,可删 +│ 15s 15s 15s 15s 共 60 秒 │ +└──────────────────────────────────────────────────────────┘ + +┌── 字幕(5-8 套预设样式)────────────────────────────────┐ +│ ☑ 启用字幕 │ +│ 来源:从脚本台词自动生成 │ +│ 样式:[大字综艺▼] 备选:简洁电商/高级排版/弹幕轻量/强调爆款│ +│ 编辑:可逐句调整文字 │ +│ ★ 记忆:"上次选 大字综艺" │ +└─────────────────────────────────────────────────────────┘ + +┌── BGM(V1 内置 20-50 首抖音热门授权曲)─────────────────┐ +│ ☑ 启用 BGM │ +│ 选择:[抖音 Top10 卡点曲库 ▼] │ +│ 音量:原视频 80% / BGM 40% │ +│ ★ 记忆:"上次选 卡点 #3" │ +└─────────────────────────────────────────────────────────┘ + +┌── 转场 ─────────────────────────────────────────────────┐ +│ ☑ 启用转场 │ +│ 类型:[淡入淡出 / 滑动 / 缩放] │ +│ 时长:0.3 秒 │ +└─────────────────────────────────────────────────────────┘ + + [👁 预览成片] [✓ 确认导出 1080P MP4] +``` + +**审核门**:[✓ 确认导出] —— 触发高清渲染(2-5 分钟),完成弹"导出成功"卡片,提供下载链接 + +**导出后**: +- 成片入【项目内库 - 成片】 +- 项目状态 → `completed` +- 商家可基于该项目复制做下条 + +--- + +### 5.3 跨阶段通用规则 + +#### 5.3.1 审核门是阻塞的 + +- 上一阶段未确认前,下一阶段 tab 灰色不可点 +- hover 灰色 tab 显示"请先完成 Stage X" + +#### 5.3.2 任意阶段可回退 + +| 当前位置 | 可回退到 | 影响 | +|---------|---------|------| +| Stage 2-5 | Stage 1(改脚本) | 下游全清空 | +| Stage 3-5 | Stage 2(换基础资产) | Stage 3-5 清空 | +| Stage 4-5 | Stage 3(改故事板) | Stage 4-5 清空 | +| Stage 5 | Stage 4(重做视频) | Stage 5 清空 | + +回退一律弹窗二次确认。 + +#### 5.3.3 状态持久化 + +- 任何阶段中关浏览器/刷新状态不丢 +- 后台任务(生图/生视频)继续跑,回来即看结果 +- 同项目同时只允许一人编辑(其他人只读 + 提示"X 正在编辑") + +#### 5.3.4 失败处理 + +| 失败场景 | 处理 | +|---------|------| +| 单张图生成失败 | 该图标红,[↻ 重跑],其他图继续 | +| 单张故事板失败 | 该张标红,[↻ 重跑] | +| 单段视频生成失败 | 该段标红,[↻ 重跑] | +| 余额/额度不足 | 进入阶段前预检 → 弹窗"额度不足",禁止开始 | +| 模型 API 故障 | 自动重试 3 次,仍失败标红 + 错误码透传 | +| 任务超时(> 10 分钟) | 标"超时",[↻ 重跑] | + +#### 5.3.5 扣费规则 ⭐ + +> **失败不扣费,确认后扣** + +- 每次生图 / 生故事板 / 生视频任务:**仅在用户点 [✓ 通过] 时才扣费** +- 失败、超时、用户重跑(旧版本作废)一律不扣 +- 进入阶段前**预检团队额度 + 个人额度**(按预估消耗 × 1.2 安全系数),不足直接禁止 +- 余额信息在 Sidebar 底部 + Dashboard 统计条实时显示 + +--- + +## 6. 资产库模型与流转规则 + +### 6.1 资产分类 + +``` +资产库 +├─ 跨项目共享(团队级,所有项目可见可引用) +│ ├─ 📦 商品图(来自商品库 / Stage 2c 上传或优化) +│ ├─ 👤 人物图(Stage 2a 生成 / 手动上传) +│ └─ 🌆 场景图(Stage 2b 生成 / 手动上传) +│ +└─ 项目内(仅本项目可见) + ├─ 🎬 故事板(Stage 3 生成的 K 张大图) + ├─ 🎞️ 视频片段(Stage 4 生成的 K 段 15s 片段) + └─ 📺 成片(Stage 5 输出的最终视频) +``` + +### 6.2 资产入库来源 + +| 来源 | 触发 | 入库分类 | +|------|------|---------| +| 商品库新建商品 | 上传商品图 | 跨项目-商品图 | +| Stage 2a [✓ 通过] | AI 生成的人物图 | 跨项目-人物图 | +| Stage 2a [📤 上传] | 商家本地上传 | 跨项目-人物图 | +| Stage 2b 同上 | 场景图 | 跨项目-场景图 | +| Stage 2c [✨ 优化] | 优化版商品图 | 跨项目-商品图(标"优化") | +| Stage 3 单张 [✓ 通过] | 故事板大图 | 项目内-故事板 | +| Stage 4 单段 [✓ 通过] | 视频片段 | 项目内-视频片段 | +| Stage 5 [✓ 导出] | 最终成片 | 项目内-成片 | +| 资产库 [+ 上传] | 商家手动上传 | 视用户选择 | + +### 6.3 资产引用规则 + +- Stage 2a/2b 可绑定团队共享库已有资产(不重生) +- Stage 2c 必从商品库引用(或新上传到库) +- 资产被引用时仅记录关系,不复制 +- 删除资产前检查引用:被 N 个项目用 → 不允许直接删,提示先解除 +- 软删除:标记 `deleted` 隐藏,已引用项目仍可见 + +### 6.4 资产权限(团队级别) + +| 操作 | 超管 | 团管 | 成员 | +|------|:---:|:---:|:---:| +| 上传到团队共享库 | ✓ | ✓ | ✓ | +| 删除自己上传的 | ✓ | ✓ | ✓ | +| 删除别人上传的(团队共享) | ✓ | ✓ | ✗ | +| 上传/删除项目内资产 | ✓ | ✓ | ✓(仅自己项目) | + +### 6.5 中间产物可下载 + +- 跨团队共享 + 项目内(除成片外)所有资产**都可下载** +- 视频片段下载为 MP4,图片下载为 PNG/JPG + +--- + +## 7. 业务对象(产品语义层) + +### 7.1 团队 Team ⭐ 一等公民 + +| 属性 | 说明 | +|------|------| +| 名称 | 团队/公司名 | +| 类型 | 个人(自动建)/ 企业(手动建) | +| 总额度池 | 充值累加,不自动重置 | +| 月限额 | 每月可消费上限,按自然月重置 | +| 当月已消耗 | 实时统计 | +| 成员列表 | 引用 User ID + 角色 | +| 创建者 ID | 默认超管 | + +### 7.2 用户 User & 角色 + +| 属性 | 说明 | +|------|------| +| 用户 ID | 唯一 | +| 团队 ID | 归属团队(一人可多团队?V1 一人一团队) | +| 角色 | 超管 / 团管 / 成员 | +| 每日额度 | 当日可消费上限 | +| 月度额度 | 当月可消费上限 | +| 当日/月已消耗 | 实时统计 | + +### 7.3 商品 Product ⭐ 一等公民 + +| 属性 | 说明 | +|------|------| +| 名称 | 商家自填,必填 | +| 团队 ID | 归属(团队共享) | +| 商品图 | 1-5 张,第一张为主图 | +| 卖点 | 多行文本,3-5 条要点 | +| 目标人群 | 多行文本,可选 | +| 价格区间 | 可选,预留 | +| 关联项目数 | 系统统计 | + +### 7.4 项目 Project + +| 属性 | 说明 | +|------|------| +| 名称 | 自填或自动 | +| 关联商品 | 引用 Product ID | +| 团队 ID + 创建者 ID | 归属 | +| 脚本来源 | AI 全生 / 自带 / 一句话 / 复刻爆款 | +| 模板 | 仅 AI 全生时 | +| 总时长 | 30 / 45 / 60 | +| 状态 | 草稿 / 进行中 / 已完成 / 失败 / 已归档 | +| 当前阶段 | 1-5 | +| 关联资产 | 引用资产 ID 列表 | + +### 7.5 脚本 Script + +| 属性 | 说明 | +|------|------| +| 项目 ID | 归属 | +| 来源 | AI 全生 / 自带 / 一句话 / 复刻爆款 | +| 总时长 | 30 / 45 / 60 | +| **段数 K** | 总时长 / 15 | +| 镜头列表 | N 个镜头(每镜:镜号、时长、场景、角色、对白、镜头建议、所属段号) | +| 状态 | 草稿 / 已确认 | + +### 7.6 故事板 Storyboard ⭐ + +| 属性 | 说明 | +|------|------| +| 项目 ID | 归属 | +| 段号 | 1 至 K | +| 大图 URL | GPT-image-2 输出的整张图 | +| 提示词 | 生成用的提示词 | +| 状态 | 生成中 / 待确认 / 已确认 | + +> 一个项目有 K 个故事板对象(K = 1/2/3/4)。 + +### 7.7 资产 Asset + +| 属性 | 说明 | +|------|------| +| 类型 | 商品/人物/场景/故事板/视频片段/成片 | +| 范围 | 跨项目共享 / 项目内 | +| 来源 | AI 生成 / 手动上传 / AI 优化 / 官方预置 | +| URL + 缩略图 | 存储位置 | +| 提示词 | 仅 AI 生成有 | +| 模型 | image-2 / GPT-image-2 / Seedance | +| 元数据 | 尺寸、时长、文件大小 | +| 引用记录 | 被哪些项目用过 | +| 上传者 ID | 团队权限判定用 | + +### 7.8 模板 Template + +| 属性 | 说明 | +|------|------| +| 名称 | 开箱测评 / 痛点种草 / 对比展示 / 教程演示 / 剧情带货 | +| 脚本结构 | 系统提示词模板(AI 全生入口用) | +| 默认时长档 | 30 / 45 / 60 推荐值 | +| 示例视频 | 1-3 个示例 | + +--- + +## 8. 详细页面说明 + +### 8.1 工作台 Dashboard `/dashboard` + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ H1:工作台 [+ 新建项目] │ +│ │ +│ ┌── 概览卡片(4 张并排,浅色卡片 + 1px border)─────────────┐ │ +│ │ 总项目 │ 进行中 │ 本月成片 │ 团队余额 │ │ +│ │ 12 │ 3 │ 8 │ ¥4,327 [充值] │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌── 最近项目 ──────────────────────────────────────────────┐ │ +│ │ 缩略图 / 项目名 / 商品 / 5段进度条 / 时间 / [继续] │ │ +│ │ ... │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌── 快捷入口 ─────────────────────────────────────────────┐ │ +│ │ [+ 新建商品] [📦 资产库] [👥 邀请成员] [💰 充值] │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 8.2 商品库 `/products` + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ H1:商品库 [+ 新建商品] │ +│ 副标题:管理你的带货商品,团队共享 │ +│ │ +│ ┌── 工具栏 ────────────────────────────────────────────────┐ │ +│ │ [🔍 搜索...] [↕ 排序▼] │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌── 商品卡片网格(4 列)───────────────────────────────────┐ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ +│ │ │[主图] │ │[主图] │ │[主图] │ │[主图] │ │ │ +│ │ │补水面膜 │ │蓝牙耳机 │ │速食面 │ │防晒霜 │ │ │ +│ │ │关联 5 个│ │关联 3 个│ │关联 2 个│ │关联 0 个│ │ │ +│ │ │5/2 创建 │ │5/1 创建 │ │4/30 创建 │ │4/28 创建 │ │ │ +│ │ │[⋯] │ │[⋯] │ │[⋯] │ │[⋯] │ │ │ +│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +新建商品(侧滑抽屉):商品名 * + 商品图 1-5 张 * + 卖点(多行)+ 目标人群(多行)+ 价格区间(可选预留) + +### 8.3 项目列表 `/projects` + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ H1:视频项目 [+ 新建项目] │ +│ │ +│ Tab: [全部 12] [草稿 1] [进行中 3] [已完成 7] [失败 1] │ +│ │ +│ [🔍 搜索...] [↕ 最近更新▼] │ +│ │ +│ 列表行(行视图): │ +│ 缩略图 / 项目名+商品 / 5段进度条 / 时间 / [继续/打开] [⋯] │ +└─────────────────────────────────────────────────────────────────┘ +``` + +进度条 5 段:脚本 / 资产 / 故事板 / 视频 / 拼接 + +### 8.4 新建项目向导 `/projects/new` + +4 步: +1. **选商品**(横向滑动卡片选择器,最后一张是 [+ 新建商品]) +2. **选脚本来源**(4 个 Tab 切换):AI 全生 / 自带粘贴 / 一句话主题 / 复刻爆款(V1 灰) +3. **选模板 + 时长**(仅 AI 全生 / 一句话时显示) +4. **摘要确认**(项目名可改 + 预估消耗 + 当前余额) + +### 8.5 流水线主界面 `/projects/:id` + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Breadcrumb:项目 / 补水面膜 / 流水线 [设置] │ +│ │ +│ ┌── Stage 进度条(5 段,sticky)──────────────────────────────┐ │ +│ │ ① 脚本 ► ② 资产 ► ③ 故事板 ► ④ 视频 ► ⑤ 拼接 │ │ +│ │ ✓完成 ✓完成 ⚠当前 ○待机 ○待机 │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌── 主内容区(按当前 Stage 渲染,详见 §5.2)───────────────────┐│ +│ └────────────────────────────────────────────────────────────────┘│ +│ │ +│ ┌── 底部固定操作条 ────────────────────────────────────────────┐ │ +│ │ 已花费 ¥0 / 预估总额 ¥27 [↩ 回退] [↺ 整体重做] [✓ 通过] │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 8.6 资产库 `/library` + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ H1:资产库 [+ 上传资产] │ +│ │ +│ 范围:[● 团队共享] [○ 项目资产 ▼ 选项目] │ +│ │ +│ Tab: [全部] [📦 商品图] [👤 人物图] [🌆 场景图] [🎬 故事板] │ +│ [🎞️ 视频片段] [📺 成片] │ +│ │ +│ [🔍 搜索...] [↕ 排序▼] [⊞⊟ 视图] │ +│ │ +│ 网格:缩略图 + 名称 + 来源标签 + 引用次数 + […] 菜单 │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 8.7 团队 `/team` + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ H1:团队管理 [+ 邀请成员] │ +│ │ +│ 团队信息卡:[团队名][充值余额][月限额][当月消耗] + [⚙ 团队设置] │ +│ │ +│ 成员列表(表格): │ +│ 头像/姓名 / 角色 / 每日额度 / 月度额度 / 已消耗 / [编辑] [移除] │ +│ ... │ +│ │ +│ 邀请成员(弹窗):手机号 + 角色(团管/成员)+ 默认额度 │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 8.8 消费 `/billing` + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ H1:消费 │ +│ │ +│ ┌── 余额卡片 ──────────────────────────────────────────────┐ │ +│ │ 团队余额 ¥4,327 月限额 ¥3,000 当月已用 ¥1,673 [充值] │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ Tab: [总览] [按项目] [按成员] [账单流水] │ +│ │ +│ 总览:折线图(按天消耗) + 环形图(按 Stage 分类) │ +│ 按项目/成员:表格 │ +│ 账单流水:时间 / 项目 / 阶段 / 消耗 / 操作者 / 状态 │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 8.9 设置 `/settings`(V1 简版) + +个人信息 + 偏好(默认模板/默认字幕/默认 BGM 库) + +--- + +## 9. 状态机 + +### 9.1 项目状态机 + +``` +[新建]──创建向导完成──►[草稿]──进入Stage1──►[进行中]──Stage5导出──►[已完成] + │ │ │ + │ 用户归档 │ 重试3次仍失败 │ 用户归档 + ▼ ▼ ▼ + [已归档] [失败] [已归档] +``` + +### 9.2 单资产状态机 + +``` +[生成中]──成功──►[待审核]──通过──►[已入库] + │ │ │ + └─失败─►[失败] └─手动上传替换─►[已入库(替换)] + │ + └─重跑─►[生成中] +``` + +### 9.3 单故事板状态机 + +``` +[生成中]──成功──►[待审核]──通过──►[已入库] + │ │ + │ ├─整张重跑(默认提示词)─►[生成中] + │ └─改提示词后重跑──────►[生成中] + │ + └─失败──►[失败]──重跑──►[生成中] +``` + +### 9.4 单视频片段状态机 + +``` +[资产就绪]──提交Seedance──►[生成中]──成功──►[待审核] + │ │ + └─失败─►[失败] ├─通过──►[已入库] + └─重跑──►[生成中] +``` + +### 9.5 项目工序状态机(5 阶段) + +``` +[草稿]──选商品+选脚本来源──► +[Stage 1 脚本生成中]──完成──►[Stage 1 待审核]──编辑/重生──→ 回流 + │ 商家确认 + ▼ +[Stage 2 基础资产生成中](2a/2b/2c 顺序)──全通过──► +[Stage 3 故事板生成中]──完成──►[Stage 3 待审核]──单张重跑──→ 局部回流 + │ 全部确认 + ▼ +[Stage 4 视频片段生成中](K 段并行)──全通过──► +[Stage 5 拼接编辑中]──商家点导出──►[已完成] +``` + +--- + +## 10. 角色与权限 + +### 10.1 角色定义 + +| 角色 | 权限 | +|------|------| +| **超管** | 团队所有权限 + 总额度划拨 + 团管任命 + 财务查看 | +| **团管** | 成员管理(邀请/移除)+ 成员每日/月额度分配 + 团队共享资产库管理 | +| **成员** | 创建项目 + 流水线操作 + 个人项目管理 + 引用团队资产 | +| **观察者** | 仅查看团队项目和资产,不可创建、不可消费(V2) | + +### 10.2 权限矩阵 + +| 功能 | 超管 | 团管 | 成员 | +|------|:---:|:---:|:---:| +| 创建项目 | ✓ | ✓ | ✓ | +| 编辑别人的项目 | ✓ | ✓ | ✗ | +| 删除别人的项目 | ✓ | ✓ | ✗ | +| 上传到团队共享资产库 | ✓ | ✓ | ✓ | +| 删除别人上传的团队共享资产 | ✓ | ✓ | ✗ | +| 邀请成员 | ✓ | ✓ | ✗ | +| 设置成员额度 | ✓ | ✓ | ✗ | +| 划拨团队总额度(充值) | ✓ | ✗ | ✗ | +| 查看团队消费明细 | ✓ | ✓ | ✗(仅自己) | +| 团队设置(名称/限额) | ✓ | ✗ | ✗ | + +### 10.3 四层额度检查(每次任务确认前) + +``` +预检顺序(任一不通过即拒绝): +1. 当前用户当日剩余额度 ≥ 任务预估消耗 × 1.2 +2. 当前用户当月剩余额度 ≥ 同上 +3. 团队当月剩余额度 ≥ 同上 +4. 团队总余额 ≥ 同上 +``` + +--- + +## 11. 边界情况与异常处理 + +### 11.1 输入异常 + +| 场景 | 处理 | +|------|------| +| 商品名为空 | 表单校验,提示"请填商品名" | +| 商品图 0 张 | 至少 1 张,否则不能保存 | +| 上传图 > 5MB | 前端拦截 | +| 上传图格式 | 仅 jpg/png/webp | +| 自带脚本太长(> 80s 估算) | 弹窗"建议 ≤ 60s,仍要继续?" | + +### 11.2 额度异常 + +| 场景 | 处理 | +|------|------| +| 进入阶段前预检不足 | 弹窗"额度不足,预估 ¥X,剩余 ¥Y",区分用户/团队层级 + [充值/联系超管] | +| 团队月限额到 | 跨月自动重置,期间禁止新任务 | +| 团队总额度耗尽 | 所有任务暂停,仅超管可充值 | + +### 11.3 网络异常 + +| 场景 | 处理 | +|------|------| +| 提交任务时断网 | 自动重试 3 次 | +| 轮询任务状态时断网 | 静默重试,恢复后自动同步 | +| 长时间无响应(> 10 分钟) | 标"超时",[↻ 重跑] | + +### 11.4 并发异常 + +| 场景 | 处理 | +|------|------| +| 同项目两人同时编辑 | 后进入者只读 + 顶部提示"X 正在编辑" | +| 同时多个项目跑视频生成 | 排队,顶部 Toast 提示"已有 N 个任务排队" | + +### 11.5 内容合规异常 + +| 场景 | 处理 | +|------|------| +| 提示词触发审核(医疗/绝对化用语) | AI 拒绝,提示用户改 | +| 商家上传违规图片 | 入库前预审拒收(V1 简版规则) | +| 成片合规 | V1 依赖商家自检,V2 接审核 API | + +--- + +## 12. V1 验收标准 + +### 12.1 功能验收 + +- [ ] 注册账号自动建团队,注册者为超管 +- [ ] 超管可充值、设月限额、邀请成员、分配额度 +- [ ] 商品库 CRUD 跑通 +- [ ] 4 步项目向导跑通(4 种脚本入口至少前 3 种可用) +- [ ] 5 阶段流水线跑通(mock 数据下) +- [ ] 每阶段都有审核门,未确认前下一阶段不可点 +- [ ] Stage 2 三子步顺序:人物 → 场景 → 商品;用户可增/删/改资产 +- [ ] Stage 2c 商品图一键 AI 优化可用 +- [ ] Stage 3 K 张故事板独立审核 + 整张重跑(不能局部改) +- [ ] Stage 4 K 段视频并发生成,单段可重跑/改提示词 +- [ ] Stage 5 时间线编辑 + 字幕预设 + BGM + 导出 1080P MP4 +- [ ] 资产入库自动 + 资产库可查看 + 中间产物可下载 +- [ ] 跨阶段回退规则正确(清空下游 + 二次确认) +- [ ] 四层额度预检 + 失败不扣 + 确认后扣 规则正确 +- [ ] 团队权限矩阵符合 §10.2 + +### 12.2 性能验收 + +- [ ] Dashboard 首屏 < 1 秒 +- [ ] 流水线主界面切换 Stage < 200ms +- [ ] 资产库网格滚动流畅 +- [ ] 视频预览 buffer < 3 秒 + +### 12.3 视觉验收 + +- [ ] 严格遵循 §4 视觉规范(浅色 SaaS 风) +- [ ] 主色严格用 `#1E40AF`,无电商红/橙 +- [ ] 字体中文 HarmonyOS Sans,英文 Inter +- [ ] 阴影克制,多用 1px border 划分层级 +- [ ] 数字用 tabular numerals 等宽 +- [ ] 视频缩略图保持 9:16 +- [ ] 无 emoji 作 UI 图标(全 Lucide/Heroicons) +- [ ] 动效 200-300ms ease-out,克制 + +--- + +## 13. 待补充事项 + +| 项目 | 谁来定 | 备注 | +|------|--------|------| +| 5 个模板的脚本结构、示例视频、封面图 | 运营提供 | 影响 Stage 1 AI 全生 | +| 商品图 AI 优化的预制提示词 | 产品+模型工程师调试 | 去白底+重打光+清晰化 | +| BGM 库选曲(V1 内置 20-50 首) | 运营+法务 | 抖音热门授权曲 / royalty-free | +| 字幕样式预设(5-8 套) | 设计提供 | 大字综艺/简洁电商/高级排版/弹幕轻量/强调爆款 | +| 算力定价细则 | 财务 | 影响预估消耗显示 | +| 项目命名规则 | 待决 | 商家自填 vs 自动生成 | +| 平台水印 | 待决 | 默认水印 + VIP 去 | +| 复刻爆款 V2 启用时机 | 取决于 Gemini Vision API 集成 | V1 灰掉占位 | +| 登录风控(5 条规则)是否 V1 接 | 待决 | AirDrama 已有代码可复用 | +| GPT-image-2 故事板的尺寸/比例 | 模型工程师 | 影响 §5.2 Stage 3 输出规格 | + +--- + +## 14. 文档历史 + +| 版本 | 日期 | 作者 | 变更 | +|------|------|------|------| +| v0.1 | 2026-05-09 | Claude + 用户对齐 | 初稿,5 阶段,无故事板,无商品库独立 | +| v0.2 | 2026-05-09 | Claude + 用户对齐 + brief 合并 | 6 阶段(增故事板、元素提取),商品库独立,4 种脚本入口,失败不扣,V1 单商家 + 团队 V1.5 占位 | +| v0.3 | 2026-05-09 | Claude + 用户最终对齐(视觉切浅色 SaaS、团队完整 B2B、砍元素提取阶段、故事板=GPT-image-2 一张大图、Seedance 1:1 K 段) | **重大重构**:5 阶段、视觉切 Linear 风、完整 B2B、故事板大图整张重跑、Seedance 60s=4 段 | diff --git a/README.md b/README.md deleted file mode 100644 index 476bf2c..0000000 --- a/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# AirShelf · UI 设计稿货架 - -@zyc / iye 的 UI 设计稿合集。每个子目录是一个独立项目,各自有自己的设计规范和静态稿。 - -## 货架内容 - -| 项目 | 风格 | 说明 | -| --- | --- | --- | -| [电商AI平台/](电商AI平台/) | Restraint V2.1 (Firecrawl-aligned) | AI 短视频带货生成平台 · 10 个页面 · 完整 5 阶段流水线 | - ---- - -## 浏览方式 - -直接 clone + 用浏览器打开任意 `*.html`: - -```bash -git clone https://gitea.airlabs.art/zyc/AirShelf.git -cd AirShelf/电商AI平台 -# 浏览器直开 index.html · 或本地起 server -npx http-server . -p 8080 -``` - -## 添加新项目 - -新项目作为根目录下的兄弟文件夹(中文命名 OK),保持各自独立: - -``` -AirShelf/ -├── 电商AI平台/ -├── <未来项目 B>/ -└── <未来项目 C>/ -``` - ---- - -## 部署 - -CI/CD 走 Gitea Actions + 火山引擎 CR + K3s(traefik + cert-manager)。 - -| 分支 | 环境 | 域名 | Image tag | -| --- | --- | --- | --- | -| `master` | production | `airshelf.airlabs.art` | `prod-YYYYMMDD-` | -| `dev` | development | `airshelf.test.airlabs.art` | `dev-YYYYMMDD-` | - -推到对应分支会自动触发 [.gitea/workflows/deploy.yaml](.gitea/workflows/deploy.yaml): -checkout → docker build/push (`airshelf-web`,无构建阶段、纯 nginx + 静态) → kubectl apply [k8s/](k8s/) → rollout restart。 - -构建上下文是 `电商AI平台/`,Dockerfile/nginx.conf 都在该子目录。当前仅一个项目,故 image 名固定 `airshelf-web`;若未来加兄弟项目,流水线需要扩展为按项目分别构建。 - -**Gitea 仓库需要配置的 Secrets:** -- prod: `CR_PROD_PASSWORD` · `VOLCANO_PROD_KUBE_CONFIG` -- dev: `CR_SERVER` · `CR_USERNAME` · `CR_PASSWORD` · `VOLCANO_TEST_KUBE_CONFIG` diff --git a/_check.html b/_check.html new file mode 100644 index 0000000..65b310f --- /dev/null +++ b/_check.html @@ -0,0 +1,983 @@ + + + + +新建项目 · 流·Studio + + + + + +
+ +
+
+

新建项目

+
// 商品 → 脚本来源 → 配置 → 确认 · 4 步开始生成
+
+
+ 退出 +
+
+ +
+ +
+ +
+ +
+ + + + + + + diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..3b27998 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,1196 @@ +/* Tailwind not used; rely on tokens + custom CSS below */ + +/* ============================================================ + v3 · Restraint — Design Tokens + ============================================================ */ +:root { + --bg: #FAF9F5; + --bg-soft: #F4F2EC; + --bg-softer: #F7F5EE; + --card: #FFFFFF; + --border: #E9E5DB; + --border-soft: #EFEBE0; + --border-strong: #D8D3C5; + --ink: #15140F; + --ink-2: #5A584F; + --ink-3: #9C988C; + --ink-4: #C8C4B8; + --orange: #E55B26; + --orange-d: #D04E1F; + --orange-soft: #FAE8DC; + --orange-tint: #FFF4ED; + --green: #3F6B3F; + --green-soft: #EAF2EA; + --red: #B33A2A; + --red-soft: #FBEBE7; + --blue: #1B4FAA; + --blue-soft: #E8EFFB; + --topbar-h: 57px; + --aside-w: 248px; + --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + --sans: "Inter", system-ui, -apple-system, "Segoe UI", "PingFang SC", + "Hiragino Sans GB", "Microsoft YaHei", sans-serif; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } +html, body { + background: var(--bg); + color: var(--ink); + font-family: var(--sans); + font-size: 13px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +a { color: inherit; text-decoration: none; } +button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; } +input, textarea, select { font: inherit; color: inherit; } +::selection { background: var(--orange-soft); color: var(--ink); } + +.mono { font-family: var(--mono); } +.tnum { font-variant-numeric: tabular-nums; } +.muted { color: var(--ink-2); } +.muted-2 { color: var(--ink-3); } +.spacer { flex: 1; } +.hstack { display: flex; align-items: center; gap: 8px; } +.vstack { display: flex; flex-direction: column; gap: 8px; } + +/* ============================================================ + App shell + ============================================================ */ +.app { display: grid; grid-template-columns: var(--aside-w) 1fr; min-height: 100vh; } + +/* ──────── Sidebar ──────── */ +aside.sidebar { + padding: 18px 14px; + border-right: 1px solid var(--border); + background: var(--bg); + position: relative; +} +.brand { display: flex; align-items: center; gap: 10px; padding: 6px 8px 14px; } +.flame { width: 22px; height: 22px; color: var(--orange); } +.flame svg { width: 100%; height: 100%; } +.brand-name { font-weight: 700; font-size: 18px; letter-spacing: -.012em; } +.brand-ver { + font-family: var(--mono); font-size: 9.5px; color: var(--ink-3); + background: var(--bg-soft); padding: 1px 5px; border: 1px solid var(--border-soft); + border-radius: 3px; margin-left: auto; +} + +.search-box { + display: flex; align-items: center; gap: 8px; + padding: 9px 12px; + background: var(--card); + border: 1px solid var(--border); + border-radius: 9px; + color: var(--ink-3); + margin-bottom: 14px; + font-size: 13px; + cursor: text; +} +.search-box svg { width: 14px; height: 14px; } +.search-box .kbd { + margin-left: auto; font-family: var(--mono); font-size: 11px; + background: var(--bg-soft); padding: 1px 6px; border-radius: 4px; + border: 1px solid var(--border-soft); +} + +.nav-section { + font-family: var(--mono); font-size: 10px; color: var(--ink-3); + letter-spacing: .08em; padding: 12px 12px 6px; text-transform: uppercase; +} +nav.sidebar-nav { display: flex; flex-direction: column; gap: 1px; } +nav.sidebar-nav a, nav.sidebar-nav .nav-item { + display: flex; align-items: center; gap: 11px; + padding: 8px 12px; + color: var(--ink-2); + font-size: 13.5px; font-weight: 500; + border-radius: 7px; + cursor: pointer; +} +nav.sidebar-nav a:hover, nav.sidebar-nav .nav-item:hover { background: var(--bg-soft); color: var(--ink); } +nav.sidebar-nav a.active { background: var(--orange-tint); color: var(--orange); } +nav.sidebar-nav .nav-item.disabled { opacity: .55; cursor: not-allowed; } +nav.sidebar-nav .nav-item.disabled:hover { background: transparent; color: var(--ink-2); } +nav.sidebar-nav svg { width: 14px; height: 14px; opacity: .85; flex-shrink: 0; } +nav.sidebar-nav a.active svg { opacity: 1; } +nav.sidebar-nav .chev { margin-left: auto; width: 12px; height: 12px; opacity: .5; } +nav.sidebar-nav .label { flex: 1; } +nav.sidebar-nav .badge-mini { + font-family: var(--mono); font-size: 9.5px; + background: var(--bg-soft); color: var(--ink-3); + padding: 1px 5px; border: 1px solid var(--border-soft); + border-radius: 3px; +} + +.aside-foot { position: absolute; left: 14px; right: 14px; bottom: 14px; } +.aside-user { + display: flex; align-items: center; gap: 9px; + padding: 8px; border-radius: 8px; cursor: pointer; +} +.aside-user:hover { background: var(--bg-soft); } +.aside-user .av { + width: 24px; height: 24px; border-radius: 6px; + background: var(--ink); color: #FFF; + display: flex; align-items: center; justify-content: center; + font-weight: 600; font-size: 11px; +} +.aside-user .em { font-size: 12.5px; } +.aside-collapse { + display: flex; align-items: center; gap: 8px; + padding: 8px 10px; color: var(--ink-3); font-size: 12.5px; + border-top: 1px solid var(--border); margin-top: 8px; + cursor: pointer; +} +.aside-collapse svg { width: 12px; height: 12px; } + +/* ──────── Main / graph-paper bg ──────── */ +main.main { position: relative; overflow: hidden; background: var(--bg); min-width: 0; } +.grid-bg { + position: absolute; inset: 0; pointer-events: none; + background-image: + /* "+" registration marks at major (240×240) intersections */ + url("data:image/svg+xml;utf8,"), + /* tiny dots at sub-grid (60×60) intersections */ + url("data:image/svg+xml;utf8,"), + /* faint dashed grid lines (240×240) */ + url("data:image/svg+xml;utf8,"); + background-size: 240px 240px, 60px 60px, 240px 240px; + background-position: 0 0, 0 0, 0 0; + -webkit-mask-image: radial-gradient(ellipse 95% 80% at 50% 35%, #000 25%, transparent 95%); + mask-image: radial-gradient(ellipse 95% 80% at 50% 35%, #000 25%, transparent 95%); +} +.scatter { + position: absolute; font-family: var(--mono); + font-size: 8.5px; line-height: 1.05; + color: var(--ink-4); white-space: pre; + pointer-events: none; opacity: .8; letter-spacing: .04em; +} +.sq { position: absolute; width: 5px; height: 5px; background: var(--ink-3); opacity: .55; pointer-events: none; } +.corner-tag { + position: absolute; color: var(--ink-3); font-family: var(--mono); + font-size: 10.5px; letter-spacing: .06em; + pointer-events: none; opacity: .85; +} + +/* ──────── Topbar ──────── */ +.topbar { + display: flex; align-items: center; justify-content: space-between; + padding: 12px 24px; + border-bottom: 1px solid var(--border); + background: var(--bg); + position: relative; z-index: 2; + min-height: var(--topbar-h); +} +.crumbs { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-2); } +.crumbs a { color: var(--ink-2); } +.crumbs a:hover { color: var(--ink); } +.crumbs .sep { color: var(--ink-4); } +.crumbs .here { color: var(--ink); font-weight: 500; } + +.team-switcher { + display: flex; align-items: center; gap: 8px; + padding: 7px 11px 7px 8px; + background: var(--card); border: 1px solid var(--border); + border-radius: 9px; font-size: 13px; font-weight: 500; cursor: pointer; +} +.team-switcher .p { + width: 22px; height: 22px; border-radius: 6px; + background: var(--orange); color: #FFF; + display: flex; align-items: center; justify-content: center; + font-weight: 700; font-size: 11.5px; +} +.team-switcher .chev { width: 12px; height: 12px; color: var(--ink-3); } + +.top-r { display: flex; align-items: center; gap: 8px; } +.icon-btn { + width: 36px; height: 36px; + display: flex; align-items: center; justify-content: center; + background: var(--card); border: 1px solid var(--border); + border-radius: 9px; color: var(--ink-2); cursor: pointer; + position: relative; +} +.icon-btn:hover { color: var(--ink); } +.icon-btn svg { width: 15px; height: 15px; } +.icon-btn .dot { + position: absolute; top: 8px; right: 9px; + width: 7px; height: 7px; border-radius: 50%; + background: var(--orange); border: 1.5px solid var(--card); +} +.pill-btn { + display: flex; align-items: center; gap: 7px; + padding: 8px 14px; + background: var(--card); border: 1px solid var(--border); + border-radius: 9px; font-size: 13px; color: var(--ink); font-weight: 500; + cursor: pointer; +} +.pill-btn:hover { background: var(--bg-soft); } +.pill-btn svg { width: 14px; height: 14px; color: var(--ink-2); } +.upgrade { + background: var(--orange); color: #FFF; + border: 1px solid var(--orange); font-weight: 600; padding: 8px 16px; +} +.upgrade:hover { background: var(--orange-d); } +.upgrade svg { color: #FFF; } +.balance-chip { + display: inline-flex; align-items: center; gap: 6px; + padding: 7px 11px; background: var(--card); border: 1px solid var(--border); + border-radius: 9px; font-size: 12.5px; color: var(--ink-2); +} +.balance-chip strong { color: var(--ink); font-family: var(--mono); font-weight: 500; } + +/* ──────── Content shell ──────── */ +.content { + padding: 40px 56px 56px; + position: relative; z-index: 1; + max-width: 1480px; +} + +/* page head */ +.page-head { + display: flex; align-items: flex-start; justify-content: space-between; + margin-bottom: 28px; gap: 16px; +} +.page-head h1 { font-size: 26px; font-weight: 600; letter-spacing: -.018em; line-height: 1.2; } +.page-head .sub { + font-size: 13.5px; color: var(--ink-2); margin-top: 6px; + display: flex; align-items: center; gap: 8px; +} +.page-head .sub .mono-sub { + font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); + letter-spacing: .02em; +} +.page-head .actions { display: flex; align-items: center; gap: 8px; } + +/* ──────── Buttons ──────── */ +.btn { + display: inline-flex; align-items: center; gap: 6px; + padding: 8px 14px; + border-radius: 9px; + font-size: 13px; font-weight: 500; + border: 1px solid var(--border); + background: var(--card); color: var(--ink); + cursor: pointer; white-space: nowrap; + transition: background 100ms; +} +.btn:hover { background: var(--bg-soft); } +.btn svg { width: 13px; height: 13px; } +.btn.btn-primary { + background: var(--orange); color: #FFF; + border-color: var(--orange); font-weight: 600; padding: 8px 16px; +} +.btn.btn-primary:hover { background: var(--orange-d); } +.btn.btn-primary.btn-lg { padding: 10px 20px; font-size: 13.5px; } +.btn.btn-secondary { background: var(--bg-soft); color: var(--ink); } +.btn.btn-secondary:hover { background: var(--bg-softer); } +.btn.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-2); } +.btn.btn-ghost:hover { background: var(--bg-soft); color: var(--ink); } +.btn.btn-sm { padding: 5px 11px; font-size: 12px; } +.btn.btn-lg { padding: 10px 18px; font-size: 13.5px; } +.btn:disabled { opacity: .45; cursor: not-allowed; } +.btn:disabled:hover { background: var(--card); } + +/* ──────── Pills ──────── */ +.pill { + display: inline-flex; align-items: center; gap: 6px; + padding: 4px 10px; + border-radius: 999px; + font-size: 11.5px; font-weight: 500; + border: 1px solid var(--border); + white-space: nowrap; +} +.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; } +.pill-info { background: var(--orange-tint); color: var(--orange); border-color: var(--orange-soft); } +.pill-success, .pill-ok { background: var(--green-soft); color: var(--green); border-color: #D5E5D5; } +.pill-error, .pill-err { background: var(--red-soft); color: var(--red); border-color: #F2D6CE; } +.pill-neutral { background: var(--bg-soft); color: var(--ink-2); border-color: var(--border); } + +/* ──────── Mono tag ──────── */ +.tag-mono { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .06em; + padding: 2px 8px; border: 1px solid var(--border-soft); + background: var(--bg-soft); border-radius: 4px; + display: inline-block; +} + +/* ──────── Inputs ──────── */ +.input, .select, .textarea { + width: 100%; height: 36px; padding: 0 12px; + background: var(--card); border: 1px solid var(--border); + border-radius: 7px; font-size: 13px; color: var(--ink); + transition: border-color 100ms; +} +.input:focus, .select:focus, .textarea:focus { + outline: none; border-color: var(--orange); + box-shadow: 0 0 0 3px var(--orange-tint); +} +.textarea { height: auto; padding: 10px 12px; resize: vertical; } +.select { appearance: none; padding-right: 32px; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; background-position: right 10px center; } +.field { display: block; margin-bottom: 16px; } +.field-label { display: block; font-size: 12.5px; color: var(--ink-2); font-weight: 500; margin-bottom: 6px; } +.field-label .req { color: var(--orange); margin-left: 2px; } +.field-hint { font-size: 11.5px; color: var(--ink-3); margin-top: 4px; } + +/* ──────── Card (hard-corner v3) ──────── */ +.card { + background: var(--card); border: 1px solid var(--border); + padding: 20px; +} +.card.padded { padding: 24px; } +.card-hd { + display: flex; align-items: center; justify-content: space-between; + padding: 14px 18px; border-bottom: 1px solid var(--border); +} +.card-hd h3 { font-size: 14px; font-weight: 600; } + +/* corner SVG crosshair marks (V2.1 spec · 22×21 viewBox · 圆弧内凹) */ +.has-corners { position: relative; } +.has-corners::before, .has-corners::after, +.corner-tr, .corner-bl { + content: ''; position: absolute; + width: 14px; height: 14px; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%239C988C'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center; + background-size: contain; + pointer-events: none; +} +.has-corners::before { top: -7px; left: -7px; } +.has-corners::after { bottom: -7px; right: -7px; } +.corner-tr { top: -7px; right: -7px; } +.corner-bl { bottom: -7px; left: -7px; } + +/* ──────── Section heading ──────── */ +.section-h { + display: flex; align-items: baseline; justify-content: space-between; + margin-bottom: 12px; +} +.section-h h2 { font-size: 15px; font-weight: 600; letter-spacing: -.01em; } +.section-h .more { + font-family: var(--mono); font-size: 11px; + color: var(--ink-3); letter-spacing: .04em; +} +.section-h .more:hover { color: var(--orange); } + +/* ──────── Tabs ──────── */ +.tabs { + display: flex; gap: 4px; + border-bottom: 1px solid var(--border); + margin-bottom: 20px; +} +.tab { + padding: 10px 14px; + font-size: 13px; color: var(--ink-2); font-weight: 500; + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + display: inline-flex; align-items: center; gap: 6px; +} +.tab:hover { color: var(--ink); } +.tab.active { color: var(--ink); border-bottom-color: var(--orange); } +.tab .count { + font-family: var(--mono); font-size: 10.5px; + background: var(--bg-soft); border: 1px solid var(--border-soft); + color: var(--ink-3); padding: 1px 6px; border-radius: 3px; +} +.tab.active .count { color: var(--orange); background: var(--orange-tint); border-color: var(--orange-soft); } + +/* ──────── Filter chips ──────── */ +.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; flex-wrap: wrap; } +.toolbar-search { position: relative; flex: 1; max-width: 320px; min-width: 200px; } +.toolbar-search svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--ink-3); width: 14px; height: 14px; } +.toolbar-search input { padding-left: 34px; } +.filter-chip { + height: 32px; padding: 0 12px; + border: 1px solid var(--border); + background: var(--card); + border-radius: 8px; + font-size: 12.5px; color: var(--ink-2); font-weight: 500; + display: inline-flex; align-items: center; gap: 6px; + cursor: pointer; +} +.filter-chip:hover { background: var(--bg-soft); } +.filter-chip.active { border-color: var(--orange); color: var(--orange); background: var(--orange-tint); } +.filter-chip svg { width: 12px; height: 12px; } +.filter-chip .count-mini { color: var(--ink-3); font-family: var(--mono); font-size: 10.5px; } + +/* ──────── Progress bar (5 segs) ──────── */ +.prog { display: flex; gap: 3px; } +.prog span { width: 18px; height: 5px; border-radius: 2px; background: var(--bg-soft); } +.prog span.done { background: var(--ink-2); } +.prog span.cur { background: var(--orange); } +.prog span.fail { background: var(--red); } + +/* ──────── Placeholder thumb / image ──────── */ +.placeholder { + background: var(--bg-soft); + border: 1px solid var(--border-soft); + display: flex; align-items: center; justify-content: center; + font-family: var(--mono); font-size: 10px; color: var(--ink-3); + letter-spacing: .04em; text-align: center; padding: 4px; + position: relative; overflow: hidden; +} +.placeholder::before { + content: ''; position: absolute; inset: 0; + background-image: + linear-gradient(45deg, var(--border-soft) 25%, transparent 25%), + linear-gradient(-45deg, var(--border-soft) 25%, transparent 25%); + background-size: 14px 14px; + background-position: 0 0, 7px 0; + opacity: .35; pointer-events: none; +} +.placeholder .ph-frame { position: relative; z-index: 1; padding: 2px 6px; + background: var(--bg); border: 1px solid var(--border-soft); } + +/* ──────── Divider ──────── */ +.divider { height: 1px; background: var(--border); margin: 14px 0; } + +/* ──────── Spinner ──────── */ +.spinner { + width: 18px; height: 18px; + border: 2px solid var(--bg-soft); + border-top-color: var(--orange); + border-radius: 50%; + animation: spin 800ms linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + +/* ============================================================ + Workspace page + ============================================================ */ +.welcome { margin-bottom: 36px; } + +.stats { + display: grid; grid-template-columns: repeat(4, 1fr); + gap: 0; + background: var(--card); border: 1px solid var(--border); + position: relative; +} +.stat { + padding: 22px 24px; + border-right: 1px solid var(--border); + position: relative; +} +.stat:last-child { border-right: 0; } +.stat .lbl { + font-size: 12.5px; color: var(--ink-3); font-weight: 500; + letter-spacing: .01em; + display: flex; align-items: center; gap: 6px; +} +.stat .lbl .badge { + font-family: var(--mono); font-size: 9.5px; color: var(--ink-3); + background: var(--bg-soft); padding: 1px 6px; border-radius: 4px; + border: 1px solid var(--border-soft); +} +.stat .v { + font-size: 30px; font-weight: 600; letter-spacing: -.02em; + line-height: 1; margin-top: 14px; color: var(--ink); + font-variant-numeric: tabular-nums; +} +.stat .v small { + font-size: 14px; color: var(--ink-3); + font-weight: 500; margin-left: 2px; +} +.stat .delta { + font-family: var(--mono); font-size: 11px; + margin-top: 8px; color: var(--ink-3); letter-spacing: .02em; +} +.stat .delta.up { color: var(--green); } +.stat .usage-bar { + height: 5px; background: var(--bg-soft); + border-radius: 3px; margin-top: 12px; overflow: hidden; +} +.stat .usage-bar span { + display: block; height: 100%; + background: var(--orange); border-radius: 3px; + width: 33%; +} +.stat .sub-mono { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); margin-top: 8px; letter-spacing: .02em; +} + +.grid2 { + display: grid; grid-template-columns: 1.7fr 1fr; + gap: 24px; align-items: start; +} + +.list-card { + background: var(--card); border: 1px solid var(--border); + overflow: hidden; +} +.recent-row { + display: grid; + grid-template-columns: 54px 1fr auto auto auto; + align-items: center; gap: 16px; + padding: 14px 18px; + border-bottom: 1px solid var(--border); + text-decoration: none; +} +.recent-row:last-child { border-bottom: 0; } +.recent-row:hover { background: var(--bg-soft); } +.recent-row .thumb { + width: 54px; height: 70px; + background: var(--bg-soft); + border: 1px solid var(--border-soft); + display: flex; align-items: center; justify-content: center; + font-family: var(--mono); font-size: 10px; + color: var(--ink-3); letter-spacing: .04em; +} +.r-meta .name { font-weight: 600; font-size: 13.5px; color: var(--ink); } +.r-meta .sub { + font-size: 12px; color: var(--ink-3); + margin-top: 3px; font-family: var(--mono); + letter-spacing: .01em; +} + +.shortcuts { + display: grid; grid-template-columns: 1fr 1fr; gap: 12px; +} +.shortcut { + background: var(--card); border: 1px solid var(--border); + padding: 14px; + display: flex; align-items: flex-start; gap: 11px; + color: var(--ink); cursor: pointer; position: relative; +} +.shortcut:hover { background: var(--bg-soft); } +.shortcut .ic { + width: 30px; height: 30px; + background: var(--orange-tint); color: var(--orange); + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; border: 1px solid var(--orange-soft); +} +.shortcut .ic svg { width: 15px; height: 15px; } +.shortcut .t { font-size: 13px; font-weight: 600; } +.shortcut .d { + font-size: 11.5px; color: var(--ink-3); + margin-top: 2px; font-family: var(--mono); letter-spacing: .01em; +} + +.tip { + background: var(--card); + border: 1px dashed var(--border); + padding: 14px 16px; + font-size: 12.5px; color: var(--ink-2); line-height: 1.6; +} +.tip strong { + color: var(--ink); font-weight: 600; + display: block; margin-bottom: 4px; +} +.tip .mono-pill { + font-family: var(--mono); color: var(--orange); + background: var(--orange-tint); + padding: 1px 5px; border-radius: 3px; font-size: 11.5px; +} + +/* ============================================================ + Wizard (New Project) + ============================================================ */ +.wizard-content { max-width: 1400px; } + +.wizard-shell { + display: grid; + grid-template-columns: 200px minmax(0, 1fr) 300px; + gap: 36px; + align-items: start; +} +@media (max-width: 1180px) { + .wizard-shell { grid-template-columns: 200px minmax(0, 1fr); } + .wiz-preview { display: none; } +} + +/* ──────── Steps rail ──────── */ +.steps { position: sticky; top: 28px; align-self: start; } +.step { display: flex; gap: 12px; padding: 12px 0; position: relative; } +.step:not(.last)::after { + content: ''; position: absolute; left: 13px; top: 38px; + width: 1px; height: calc(100% - 22px); background: var(--border); +} +.step .num { + width: 26px; height: 26px; + border: 1px solid var(--border); + background: var(--card); + font-family: var(--mono); font-size: 11.5px; + color: var(--ink-3); + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.step.done .num { background: var(--ink); border-color: var(--ink); color: #FFF; } +.step.active .num { background: var(--orange); border-color: var(--orange); color: #FFF; } +.step .step-label { font-size: 13px; font-weight: 600; color: var(--ink); line-height: 1.3; } +.step.pending .step-label { color: var(--ink-3); font-weight: 500; } +.step .step-desc { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); margin-top: 3px; letter-spacing: .02em; +} + +/* ──────── Wiz main ──────── */ +.wiz-main { min-width: 0; } +.wiz-main > .card { margin-bottom: 14px; } +.collapsed-step { padding: 16px 20px; } +.collapsed-step h3 { font-size: 13.5px; font-weight: 600; } +.active-step { padding: 26px 28px; position: relative; } +.active-step::before, .active-step::after { + content: ''; position: absolute; + width: 14px; height: 14px; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%239C988C'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center; + background-size: contain; + pointer-events: none; +} +.active-step::before { top: -7px; left: -7px; } +.active-step::after { bottom: -7px; right: -7px; } + +.step-h { margin-bottom: 20px; } +.step-h h2 { font-size: 19px; font-weight: 600; letter-spacing: -.015em; } +.step-h p { font-size: 13px; color: var(--ink-2); margin-top: 6px; line-height: 1.5; } + +/* ──────── Option cards (radio-grid) ──────── */ +.option-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } +.option-row.cols-4 { grid-template-columns: repeat(4, 1fr); } +.option-row.cols-6 { grid-template-columns: repeat(3, 1fr); } +@media (min-width: 1280px) { + .option-row.cols-6 { grid-template-columns: repeat(6, 1fr); } +} + +.option-card { + border: 1px solid var(--border); + background: var(--card); + padding: 14px; + cursor: pointer; + position: relative; + display: flex; flex-direction: column; + transition: border-color .12s, background .12s; + min-width: 0; +} +.option-card:hover { background: var(--bg-soft); } +.option-card.selected { + border-color: var(--orange); + background: var(--orange-tint); +} +.option-card.selected::after { + content: ''; position: absolute; top: 8px; right: 10px; + width: 16px; height: 16px; + background-color: var(--orange); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 10px 10px; +} +.option-card h4 { font-size: 13px; font-weight: 600; letter-spacing: -.005em; } +.option-card .sub { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); margin-top: 3px; letter-spacing: .02em; +} +.option-card .note { + font-size: 11.5px; color: var(--ink-2); + margin-top: 6px; line-height: 1.5; +} +.option-card .metric { + margin-top: auto; padding-top: 10px; + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .02em; + display: flex; align-items: center; gap: 4px; +} +.option-card .metric .val { color: var(--ink); font-weight: 500; } +.option-card.selected .metric .val { color: var(--orange); } +.option-card .badge, +.option-card .tag-mono { + display: inline-block; + font-family: var(--mono); font-size: 9.5px; + padding: 1px 6px; + background: var(--card); + border: 1px solid var(--border); + color: var(--ink-3); + margin-top: 8px; + letter-spacing: .04em; + align-self: flex-start; +} +.option-card.selected .badge { color: var(--orange); border-color: var(--orange-soft); } + +/* ──────── Theme pill (key-points) ──────── */ +.theme-pill { + display: inline-flex; align-items: center; gap: 4px; + height: 28px; padding: 0 12px; + border: 1px solid var(--border); + background: var(--card); + font-size: 12.5px; color: var(--ink-2); + border-radius: 999px; + cursor: pointer; + white-space: nowrap; +} +.theme-pill:hover { background: var(--bg-soft); } +.theme-pill.on, .theme-pill.active { + background: var(--orange-tint); + color: var(--orange); + border-color: var(--orange-soft); + font-weight: 600; +} + +/* ──────── Wiz foot ──────── */ +.wiz-foot { + display: flex; justify-content: space-between; align-items: center; + padding-top: 18px; margin-top: 18px; + border-top: 1px solid var(--border); +} + +/* ──────── Preview panel (right rail) ──────── */ +.wiz-preview { + position: sticky; top: 28px; align-self: start; + background: var(--card); + border: 1px solid var(--border); + padding: 18px 18px 16px; + position: sticky; +} +.wiz-preview::before, .wiz-preview::after { + content: ''; position: absolute; + width: 14px; height: 14px; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%239C988C'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center; + background-size: contain; + pointer-events: none; +} +.wiz-preview::before { top: -7px; left: -7px; } +.wiz-preview::after { bottom: -7px; right: -7px; } +.wiz-preview .pv-h { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .08em; + margin-bottom: 12px; text-transform: uppercase; + display: flex; align-items: center; justify-content: space-between; +} +.wiz-preview .pv-h .live-dot { + display: inline-flex; align-items: center; gap: 5px; + color: var(--orange); +} +.wiz-preview .pv-h .live-dot::before { + content: ''; width: 6px; height: 6px; border-radius: 50%; + background: var(--orange); + animation: pv-pulse 1.6s ease-in-out infinite; +} +@keyframes pv-pulse { + 0%, 100% { opacity: 1; } 50% { opacity: .35; } +} + +.pv-title { + font-size: 14px; font-weight: 600; + line-height: 1.3; margin-bottom: 14px; + word-break: break-all; +} + +.pv-metrics { + display: grid; grid-template-columns: 1fr 1fr; + gap: 1px; + background: var(--border); + border: 1px solid var(--border); + margin-bottom: 14px; +} +.pv-metric { + padding: 10px 12px; + background: var(--card); +} +.pv-metric .l { + font-family: var(--mono); font-size: 9.5px; + color: var(--ink-3); letter-spacing: .04em; + text-transform: uppercase; +} +.pv-metric .v { + font-size: 18px; font-weight: 600; + letter-spacing: -.01em; margin-top: 3px; + font-variant-numeric: tabular-nums; + color: var(--ink); +} +.pv-metric .v small { + font-size: 11px; font-weight: 500; + color: var(--ink-3); margin-left: 1px; +} +.pv-metric.accent .v { color: var(--orange); } + +.pv-section { margin-top: 14px; } +.pv-section .lbl { + font-family: var(--mono); font-size: 9.5px; + color: var(--ink-3); letter-spacing: .06em; + text-transform: uppercase; margin-bottom: 8px; +} +.pv-flow { + display: flex; flex-wrap: wrap; align-items: center; + gap: 4px 0; font-size: 11.5px; + color: var(--ink-2); line-height: 1.7; +} +.pv-flow .node { + padding: 2px 7px; + background: var(--bg-soft); + border: 1px solid var(--border-soft); + color: var(--ink); font-weight: 500; +} +.pv-flow .arrow { color: var(--orange); margin: 0 5px; font-family: var(--mono); } + +.pv-list { + list-style: none; padding: 0; margin: 0; +} +.pv-list li { + font-size: 11.5px; + color: var(--ink-2); + padding: 4px 0; + display: flex; align-items: center; gap: 6px; +} +.pv-list li::before { + content: ''; + width: 11px; height: 11px; + flex-shrink: 0; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23E55B26' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12l5 5L20 6'/%3E%3C/svg%3E") no-repeat center; + background-size: contain; +} + +.pv-foot { + margin-top: 14px; + padding-top: 12px; + border-top: 1px dashed var(--border); + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .02em; + display: flex; justify-content: space-between; +} +.pv-foot strong { color: var(--ink); font-weight: 500; } + +/* ──────── Recommendation bubble ──────── */ +.reco-bubble { + position: relative; + margin-top: 10px; + padding: 10px 14px; + background: var(--orange-tint); + border: 1px solid var(--orange-soft); + display: flex; align-items: center; gap: 12px; + font-size: 12.5px; + color: var(--ink); + animation: reco-in .25s ease-out; +} +@keyframes reco-in { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +} +.reco-bubble::before { + content: ''; position: absolute; top: -5px; left: 28px; + width: 9px; height: 9px; + background: var(--orange-tint); + border-left: 1px solid var(--orange-soft); + border-top: 1px solid var(--orange-soft); + transform: rotate(45deg); +} +.reco-bubble .ic { + color: var(--orange); flex-shrink: 0; + display: inline-flex; align-items: center; justify-content: center; + width: 18px; height: 18px; +} +.reco-bubble .ic svg { display: block; } +.reco-bubble .txt { flex: 1; line-height: 1.5; } +.reco-bubble .txt strong { color: var(--orange); font-weight: 600; } +.reco-bubble .txt .meta { + display: block; font-family: var(--mono); + font-size: 10.5px; color: var(--ink-3); + margin-top: 2px; letter-spacing: .02em; +} +.reco-bubble button { + flex-shrink: 0; + height: 26px; padding: 0 12px; + background: var(--orange); color: #FFF; + border: 1px solid var(--orange); + font-size: 11.5px; font-weight: 600; + font-family: inherit; + cursor: pointer; letter-spacing: .01em; +} +.reco-bubble button:hover { background: var(--orange-d); } +.reco-bubble .dismiss { + background: transparent; color: var(--ink-3); + border: 0; width: 24px; height: 24px; padding: 0; + display: inline-flex; align-items: center; justify-content: center; + cursor: pointer; +} +.reco-bubble .dismiss:hover { color: var(--ink); background: transparent; } +.reco-bubble .dismiss svg { display: block; } + +/* ============================================================ + Wizard — Step 1 / 2 / 4 extras + ============================================================ */ + +/* ──────── Step rail · clickable ──────── */ +.step.clickable { cursor: pointer; } +.step.clickable:hover .step-label { color: var(--orange); } +.step.clickable:hover .num { border-color: var(--orange); color: var(--orange); } +.step.done.clickable:hover .num { background: var(--orange); border-color: var(--orange); color: #FFF; } + +/* ──────── Step 1 · Product picker ──────── */ +.pick-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; } +.pick-section-h { + display: flex; align-items: baseline; gap: 8px; + margin: 14px 0 8px; + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .08em; text-transform: uppercase; +} +.pick-section-h .count { + background: var(--bg-soft); border: 1px solid var(--border-soft); + padding: 1px 6px; color: var(--ink-3); font-size: 10px; +} + +.product-pick-grid { + display: grid; grid-template-columns: repeat(3, 1fr); + gap: 10px; +} +@media (max-width: 1100px) { .product-pick-grid { grid-template-columns: repeat(2, 1fr); } } + +.product-pick { + display: flex; gap: 12px; padding: 12px; + border: 1px solid var(--border); + background: var(--card); + cursor: pointer; position: relative; + transition: background .12s, border-color .12s; + min-width: 0; +} +.product-pick:hover { background: var(--bg-soft); } +.product-pick.selected { border-color: var(--orange); background: var(--orange-tint); } +.product-pick.selected::after { + content: ''; position: absolute; top: 8px; right: 10px; + width: 16px; height: 16px; + background-color: var(--orange); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; background-position: center; + background-size: 10px 10px; +} +.product-pick .thumb { + width: 56px; height: 72px; flex-shrink: 0; +} +.product-pick .body { flex: 1; min-width: 0; } +.product-pick .name { + font-weight: 600; font-size: 13px; + line-height: 1.3; + display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; + overflow: hidden; +} +.product-pick .meta { + margin-top: 4px; + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .02em; +} +.product-pick .meta b { color: var(--ink); font-weight: 500; } +.product-pick .tags { margin-top: 6px; display: flex; gap: 4px; flex-wrap: wrap; } +.product-pick .tag-s { + font-size: 10.5px; color: var(--ink-2); + background: var(--bg-soft); padding: 1px 6px; + border: 1px solid var(--border-soft); +} +.product-pick.selected .tag-s { background: var(--card); border-color: var(--orange-soft); color: var(--orange); } + +.product-pick.add { + display: flex; align-items: center; justify-content: center; + flex-direction: column; gap: 8px; + border-style: dashed; color: var(--ink-3); + min-height: 96px; +} +.product-pick.add:hover { color: var(--orange); border-color: var(--orange); background: var(--orange-tint); } +.product-pick.add .pc { width: 32px; height: 32px; border: 1px solid currentColor; display: grid; place-items: center; } + +/* ──────── Step 2 · Source type cards ──────── */ +.source-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } +.source-card { + display: flex; flex-direction: column; gap: 8px; + padding: 16px 16px 14px; + border: 1px solid var(--border); + background: var(--card); + cursor: pointer; position: relative; + transition: background .12s, border-color .12s; + min-height: 132px; +} +.source-card:hover { background: var(--bg-soft); } +.source-card.selected { border-color: var(--orange); background: var(--orange-tint); } +.source-card.selected::after { + content: ''; position: absolute; top: 10px; right: 12px; + width: 16px; height: 16px; + background-color: var(--orange); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; background-position: center; background-size: 10px 10px; +} +.source-card .src-ic { + width: 32px; height: 32px; + background: var(--bg-soft); color: var(--ink-2); + border: 1px solid var(--border-soft); + display: grid; place-items: center; +} +.source-card.selected .src-ic { + background: var(--card); color: var(--orange); + border-color: var(--orange-soft); +} +.source-card h4 { font-size: 14px; font-weight: 600; letter-spacing: -.005em; } +.source-card .src-tag { + font-family: var(--mono); font-size: 9.5px; + color: var(--ink-3); letter-spacing: .06em; + background: var(--card); border: 1px solid var(--border-soft); + padding: 1px 6px; align-self: flex-start; +} +.source-card.selected .src-tag { color: var(--orange); border-color: var(--orange-soft); } +.source-card .src-desc { font-size: 12px; color: var(--ink-2); line-height: 1.55; margin-top: auto; } + +.source-detail { + margin-top: 16px; + padding: 18px 20px; + background: var(--bg-soft); + border: 1px solid var(--border-soft); +} +.source-detail .sd-h { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .08em; + text-transform: uppercase; margin-bottom: 10px; +} +.source-detail .sd-h b { color: var(--ink); font-weight: 500; } + +/* ──────── Step 4 · Summary + Billing ──────── */ +.confirm-grid { + display: grid; grid-template-columns: 1fr 1fr; + gap: 10px; margin-bottom: 18px; +} +.confirm-card { + position: relative; + border: 1px solid var(--border); + background: var(--card); + padding: 14px 16px; +} +.confirm-card .cc-h { + display: flex; align-items: center; justify-content: space-between; + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .08em; + text-transform: uppercase; margin-bottom: 10px; +} +.confirm-card .cc-edit { + font-size: 11.5px; color: var(--ink-2); letter-spacing: 0; + font-family: var(--sans); text-transform: none; + padding: 2px 8px; border: 1px solid var(--border-soft); + background: var(--card); cursor: pointer; border-radius: 4px; +} +.confirm-card .cc-edit:hover { color: var(--orange); border-color: var(--orange-soft); } +.confirm-card .cc-body { font-size: 13px; color: var(--ink); } +.confirm-card .cc-body .ln { + display: flex; align-items: center; gap: 8px; padding: 2px 0; + font-size: 12.5px; color: var(--ink-2); +} +.confirm-card .cc-body .ln b { color: var(--ink); font-weight: 500; } + +.section-sub { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .08em; + text-transform: uppercase; margin: 18px 0 10px; +} + +.bill-list { + border: 1px solid var(--border); + background: var(--card); +} +.bill-row { + display: grid; grid-template-columns: 1fr auto 80px; + align-items: baseline; gap: 12px; + padding: 11px 16px; + border-bottom: 1px solid var(--border); +} +.bill-row:last-child { border-bottom: 0; } +.bill-row .l { font-size: 12.5px; color: var(--ink); } +.bill-row .l .l-sub { color: var(--ink-3); font-size: 11.5px; margin-left: 6px; } +.bill-row .qty { + font-family: var(--mono); font-size: 11px; + color: var(--ink-3); letter-spacing: .02em; + text-align: right; +} +.bill-row .amt { + font-family: var(--mono); font-size: 12.5px; + color: var(--ink); font-variant-numeric: tabular-nums; + text-align: right; +} +.bill-row.subtotal { background: var(--bg-soft); } +.bill-row.subtotal .l { color: var(--ink-2); font-size: 12px; } +.bill-row.total { + background: var(--bg-soft); + border-top: 1px solid var(--border-strong); +} +.bill-row.total .l { font-weight: 600; font-size: 13px; } +.bill-row.total .amt { + font-size: 16px; font-weight: 600; color: var(--ink); +} +.bill-row.total .amt small { font-size: 11px; color: var(--ink-3); font-weight: 500; margin-left: 2px; } + +.balance-row { + display: flex; align-items: center; gap: 14px; + padding: 14px 16px; + background: var(--card); + border: 1px solid var(--border); + margin-top: 10px; +} +.balance-row .bl { display: flex; align-items: center; gap: 8px; flex: 1; } +.balance-row .bl .lbl { font-size: 12.5px; color: var(--ink-2); } +.balance-row .bl .val { + font-family: var(--mono); font-size: 14px; + color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 500; +} +.balance-row .bl .arrow { color: var(--ink-3); font-family: var(--mono); } +.balance-row .bl .val.after { color: var(--ink); } +.balance-row.low .bl .val.after { color: var(--red); } +.balance-row .pill { margin-left: auto; } + +.eta-block { + display: grid; grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 10px; +} +.eta-tile { + padding: 14px 16px; + border: 1px solid var(--border); + background: var(--card); +} +.eta-tile .lbl { + font-family: var(--mono); font-size: 10px; + color: var(--ink-3); letter-spacing: .08em; + text-transform: uppercase; margin-bottom: 6px; +} +.eta-tile .v { + font-size: 15px; font-weight: 600; + font-variant-numeric: tabular-nums; letter-spacing: -.01em; +} +.eta-tile .v small { font-size: 11px; color: var(--ink-3); font-weight: 500; margin-left: 2px; } +.eta-tile .desc { + font-family: var(--mono); font-size: 10.5px; + color: var(--ink-3); letter-spacing: .02em; + margin-top: 6px; +} + +/* ──────── SVG checkbox ──────── */ +.check-row { + display: flex; align-items: center; gap: 10px; + padding: 9px 0; + font-size: 12.5px; color: var(--ink-2); + cursor: pointer; +} +.check-row:hover .check-box { border-color: var(--ink-2); } +.check-box { + width: 16px; height: 16px; + background: var(--card); + border: 1px solid var(--border-strong); + flex-shrink: 0; + border-radius: 3px; + display: grid; place-items: center; +} +.check-row.on .check-box { + background: var(--orange); border-color: var(--orange); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; background-position: center; + background-size: 11px 11px; +} +.check-row.on .lab { color: var(--ink); } +.check-row .lab b { color: var(--ink); font-weight: 500; } +.check-row .lab .mono { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); margin-left: 6px; letter-spacing: .02em; } + +.tos-row { + display: flex; align-items: center; gap: 10px; + padding: 14px 16px; + background: var(--bg-soft); + border: 1px solid var(--border-soft); + margin-top: 14px; + cursor: pointer; + font-size: 12.5px; color: var(--ink-2); +} +.tos-row:hover .check-box { border-color: var(--ink-2); } +.tos-row.on { background: var(--orange-tint); border-color: var(--orange-soft); } +.tos-row.on .lab { color: var(--ink); } +.tos-row .lab a { color: var(--orange); text-decoration: underline; } diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..cf327ab --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,43 @@ +import type { Metadata } from "next"; +import { Inter, JetBrains_Mono } from "next/font/google"; +import "./globals.css"; +import Sidebar from "@/components/Sidebar"; +import GridBg from "@/components/GridBg"; + +const inter = Inter({ + subsets: ["latin"], + variable: "--font-inter", + display: "swap", +}); + +const mono = JetBrains_Mono({ + subsets: ["latin"], + variable: "--font-mono", + display: "swap", +}); + +export const metadata: Metadata = { + title: "流·Studio — AI 短视频生产平台", + description: + "为抖音 / TikTok 商户打造的 AI 短视频生产流水线 · 脚本 → 基础资产 → 故事板 → 视频片段 → 拼接导出", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
+ +
+ + {children} +
+
+ + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..254dd66 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,196 @@ +import Link from "next/link"; +import Topbar from "@/components/Topbar"; +import Icon from "@/components/Icon"; + +interface Recent { + name: string; + meta: string; + prog: ("done" | "cur" | "fail" | "")[]; + pill: { kind: "info" | "ok" | "err"; label: string }; + action: { label: string; href: string }; +} + +const RECENT: Recent[] = [ + { + name: "补水面膜 · 痛点种草", + meta: "补水面膜 / AI 全生 / 6 镜", + prog: ["done", "done", "cur", "", ""], + pill: { kind: "info", label: "故事板 待确认" }, + action: { label: "继续", href: "/pipeline?stage=3" }, + }, + { + name: "蓝牙耳机 · 开箱测评", + meta: "南卡 Lite Pro / 自带脚本 / 5 镜", + prog: ["done", "done", "done", "done", "done"], + pill: { kind: "ok", label: "已完成" }, + action: { label: "打开", href: "/pipeline?stage=5" }, + }, + { + name: "速食牛肉面 · 一句话主题", + meta: "滋啦速食 / 一句话 / 4 镜", + prog: ["done", "cur", "", "", ""], + pill: { kind: "info", label: "资产生成中" }, + action: { label: "继续", href: "/pipeline?stage=2" }, + }, + { + name: "防晒霜 · 对比展示", + meta: "透真防晒 / AI 全生 / 6 镜", + prog: ["done", "done", "done", "cur", ""], + pill: { kind: "info", label: "视频生成 4/6" }, + action: { label: "继续", href: "/pipeline?stage=4" }, + }, + { + name: "咖啡冻干粉 · 剧情带货", + meta: "三顿半同款 / 一句话 / 5 镜", + prog: ["done", "done", "fail", "", ""], + pill: { kind: "err", label: "故事板失败" }, + action: { label: "查看", href: "/pipeline?stage=3" }, + }, +]; + +export default function WorkspacePage() { + return ( + <> + +
+
+
+

欢迎回来,小李

+
+ // 05.13 · 周三 + · + 你有 3 个项目 正在进行中 +
+
+
+ + + 新建商品 + + + + 新建项目 + +
+
+ +
+ + +
+
总项目 ALL
+
12
+
↑ 本月 +3
+
+
+
进行中 WIP
+
3
+
2 个待审核
+
+
+
本月成片 DONE
+
8
+
↑ 较上月 +33%
+
+
+
余额 ¥
+
¥327.40
+
+
已用 ¥162.60 / ¥500
+
+
+ +
+
+
+

最近项目

+ [ ALL · 12 ] → +
+
+ {RECENT.map((r) => ( +
+
9:16
+
+
{r.name}
+
{r.meta}
+
+
+ {r.prog.map((p, i) => )} +
+ {r.pill.label} + {r.action.label} +
+ ))} +
+
+ +
+
+
+

快捷入口

+ [ /shortcuts ] +
+
+ +
+ + + + +
+
+
商品库
+
12 SKU
+
+ + +
+
+
资产库
+
人 8 · 景 14 · 片 8
+
+ + +
+ + + + +
+
+
充值
+
¥327.40
+
+ + +
+ + + + +
+
+
所有项目
+
12 个
+
+ +
+
+ +
+
+

提示

+ [ FAQ ] +
+
+ 扣费规则 + 生成失败、超时、用户重跑 — 均不扣费。仅在你点{" "} + [ 确认通过 ] 时按 token 实际结算。 +
+
+
+
+
+ + ); +} diff --git a/app/products/page.tsx b/app/products/page.tsx new file mode 100644 index 0000000..b098092 --- /dev/null +++ b/app/products/page.tsx @@ -0,0 +1,93 @@ +import Topbar from "@/components/Topbar"; +import Icon from "@/components/Icon"; + +interface Product { + name: string; + cat: string; + imgs: number; + tags: string[]; + thumb: string; +} + +const PRODUCTS: Product[] = [ + { name: "透真玻尿酸补水面膜", cat: "美妆个护", imgs: 3, tags: ["熬夜党", "敏感肌", "¥39.9/盒"], thumb: "补水面膜 · 1200×800" }, + { name: "南卡 Lite Pro 蓝牙耳机", cat: "数码 3C", imgs: 5, tags: ["通勤", "运动", "¥199"], thumb: "蓝牙耳机 · 1200×800" }, + { name: "滋啦速食牛肉面 · 6 桶装", cat: "食品饮料", imgs: 4, tags: ["加班", "独居", "¥49.9"], thumb: "速食牛肉面 · 1200×800" }, + { name: "透真清透物理防晒霜", cat: "美妆个护", imgs: 4, tags: ["SPF50", "通勤", "¥69"], thumb: "防晒霜 · 1200×800" }, + { name: "三顿半同款冻干咖啡粉", cat: "食品饮料", imgs: 6, tags: ["提神", "早八", "¥89/24 颗"], thumb: "咖啡冻干粉 · 1200×800" }, + { name: "小熊 4L 可视空气炸锅", cat: "家电", imgs: 5, tags: ["小户型", "健康", "¥159"], thumb: "空气炸锅 · 1200×800" }, + { name: "露露同款裸感瑜伽裤", cat: "服饰", imgs: 8, tags: ["健身房", "通勤", "¥119"], thumb: "瑜伽裤 · 1200×800" }, +]; + +const FILTERS = ["全部", "美妆个护", "数码 3C", "食品饮料", "服饰", "家电"]; + +export default function ProductsPage() { + return ( + <> + +
+
+
+

商品库

+
+ 12 个商品 + · SKU + · 商品信息会作为脚本和资产生成的素材 +
+
+
+ +
+
+ +
+
+ + +
+ {FILTERS.map((f, i) => ( + + ))} + + +
+ +
+ {PRODUCTS.map((p) => ( +
+
+ {p.thumb} +
+
+
{p.name}
+
+ {p.cat} + · + {p.imgs} 张图 +
+
+ {p.tags.map((t) => ( + {t} + ))} +
+
+
+ ))} + +
+
+ +
+
新建商品
+
+
+
+ + ); +} diff --git a/app/projects/new/page.tsx b/app/projects/new/page.tsx new file mode 100644 index 0000000..a425356 --- /dev/null +++ b/app/projects/new/page.tsx @@ -0,0 +1,877 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import Topbar from "@/components/Topbar"; +import Icon from "@/components/Icon"; + +/* ============================================================ + Data + ============================================================ */ + +interface Product { + id: string; + name: string; + cat: string; + price: number; + imgs: number; + points: string[]; + tags: string[]; + thumb: string; +} + +const PRODUCTS: Product[] = [ + { id: "mask", name: "透真玻尿酸补水面膜", cat: "美妆个护", price: 39.9, imgs: 3, points: ["透明质酸 + B5", "30g 大容量精华", "0 香精 0 酒精"], tags: ["熬夜党", "敏感肌"], thumb: "补水面膜" }, + { id: "earphone",name: "南卡 Lite Pro 蓝牙耳机", cat: "数码 3C", price: 199, imgs: 5, points: ["主动降噪", "32 小时续航", "IP55 防水"], tags: ["通勤", "运动"], thumb: "蓝牙耳机" }, + { id: "noodle", name: "滋啦速食牛肉面 · 6 桶装", cat: "食品饮料", price: 49.9, imgs: 4, points: ["3 分钟出餐", "真材实料牛肉", "0 防腐剂"], tags: ["加班", "独居"], thumb: "速食牛肉面" }, + { id: "sun", name: "透真清透物理防晒霜", cat: "美妆个护", price: 69, imgs: 4, points: ["SPF50 PA+++", "纯物理防晒", "不泛白不假面"], tags: ["SPF50", "通勤"], thumb: "防晒霜" }, + { id: "coffee", name: "三顿半同款冻干咖啡粉", cat: "食品饮料", price: 89, imgs: 6, points: ["冷热水秒溶", "意式深烘", "24 颗轻便装"], tags: ["提神", "早八"], thumb: "咖啡冻干粉" }, + { id: "fryer", name: "小熊 4L 可视空气炸锅", cat: "家电", price: 159, imgs: 5, points: ["可视化窗口", "4L 大容量", "低脂少油"], tags: ["小户型", "健康"], thumb: "空气炸锅" }, + { id: "yoga", name: "露露同款裸感瑜伽裤", cat: "服饰", price: 119, imgs: 8, points: ["裸感面料", "高弹回弹", "随心动随心穿"], tags: ["健身房", "通勤"], thumb: "瑜伽裤" }, +]; + +const RECENT_IDS = ["mask", "sun", "coffee", "earphone"]; +const CATS = ["全部", "美妆个护", "数码 3C", "食品饮料", "服饰", "家电"]; + +type SourceId = "ai" | "theme" | "manual"; +const SOURCES: Array<{ id: SourceId; name: string; icon: "sparkles" | "lightbulb" | "doc"; tag: string; desc: string }> = [ + { id: "ai", name: "AI 全生", icon: "sparkles", tag: "最常用", desc: "LLM 全权决定脚本走向,最省事。后续仍可在故事板阶段微调。" }, + { id: "theme", name: "一句话主题", icon: "lightbulb", tag: "轻引导", desc: "你给一句切入主题,AI 按此扩写。推荐 5–30 字。" }, + { id: "manual", name: "自带脚本", icon: "doc", tag: "我已有稿", desc: "粘贴或上传完整脚本,系统按镜头自动切分并适配商品。" }, +]; + +type DurationId = "0-10" | "0-15" | "0-30" | "0-60"; +const DURATIONS: Array<{ id: DurationId; label: string; shotsRange: [number, number]; tag: string; completion: number; conversion: number; }> = [ + { id: "0-10", label: "0-10 秒", shotsRange: [3, 4], tag: "黄金完播", completion: 52, conversion: 1.6 }, + { id: "0-15", label: "0-15 秒", shotsRange: [4, 5], tag: "完播率最佳", completion: 42, conversion: 1.8 }, + { id: "0-30", label: "0-30 秒", shotsRange: [6, 8], tag: "卖点详解", completion: 32, conversion: 2.1 }, + { id: "0-60", label: "0-60 秒", shotsRange: [10, 12], tag: "故事化", completion: 26, conversion: 2.4 }, +]; + +type StyleId = "pain" | "review" | "compare"; +const STYLES: Array<{ id: StyleId; name: string; note: string; tag?: string; flow: string[] }> = [ + { id: "pain", name: "痛点种草", note: "用户痛点切入,以「我懂你」的口吻引出产品。", tag: "最常用", flow: ["痛点", "共鸣", "产品", "效果", "引导"] }, + { id: "review", name: "开箱测评", note: "朋友式分享,从开箱到使用感受娓娓道来。", flow: ["开箱", "首印象", "试用", "对比", "结论"] }, + { id: "compare", name: "对比展示", note: "「用前 vs 用后 / 同类 vs 本品」直观呈现。", flow: ["对照", "差距", "本品", "数据", "购买"] }, +]; + +type PersonaId = "urban" | "bestie" | "ceo" | "reviewer" | "mom" | "genz"; +const PERSONAS: Array<{ id: PersonaId; name: string; sub: string; metric: string; defaults: { duration: DurationId; style: StyleId } }> = [ + { id: "urban", name: "都市白领女性", sub: "25-30 岁", metric: "大盘消费力", defaults: { duration: "0-15", style: "pain" } }, + { id: "bestie", name: "闺蜜种草", sub: "邻家女孩", metric: "复购最高", defaults: { duration: "0-15", style: "pain" } }, + { id: "ceo", name: "总裁亲选", sub: "创始人 IP", metric: "30 万销额案例", defaults: { duration: "0-30", style: "pain" } }, + { id: "reviewer", name: "专业测评师", sub: "垂类达人", metric: "互动 +30%", defaults: { duration: "0-30", style: "review" } }, + { id: "mom", name: "实用宝妈", sub: "家庭决策者", metric: "母婴/家清稳", defaults: { duration: "0-30", style: "pain" } }, + { id: "genz", name: "学生党", sub: "Z 世代 18-24", metric: "平价快消", defaults: { duration: "0-10", style: "compare" } }, +]; + +/* ============================================================ + Helpers + ============================================================ */ + +const USER_EMAIL = "airlabsv001@gmail.com"; +const ACCOUNT_BALANCE = 327.4; + +function avg([a, b]: [number, number]) { return (a + b) / 2; } + +/* ============================================================ + Component + ============================================================ */ + +type StepNum = 1 | 2 | 3 | 4; + +export default function NewProjectPage() { + const router = useRouter(); + const [step, setStep] = useState(1); + + // Step 1 + const [productId, setProductId] = useState(null); + const [pickSearch, setPickSearch] = useState(""); + const [pickCat, setPickCat] = useState("全部"); + + // Step 2 + const [sourceId, setSourceId] = useState(null); + const [themeText, setThemeText] = useState(""); + const [manualScript, setManualScript] = useState(""); + + // Step 3 + const [projectName, setProjectName] = useState(""); + const [duration, setDuration] = useState("0-15"); + const [scriptStyle, setScriptStyle] = useState("pain"); + const [persona, setPersona] = useState("urban"); + const [recoDismissed, setRecoDismissed] = useState(false); + const [points, setPoints] = useState>({}); + + // Step 4 + const [notifyEmail, setNotifyEmail] = useState(true); + const [notifyWeChat, setNotifyWeChat] = useState(false); + const [agreed, setAgreed] = useState(false); + + /* ---- derived ---- */ + const product = useMemo(() => PRODUCTS.find((p) => p.id === productId) ?? null, [productId]); + const source = useMemo(() => SOURCES.find((s) => s.id === sourceId) ?? null, [sourceId]); + const personaObj = useMemo(() => PERSONAS.find((p) => p.id === persona)!, [persona]); + const durationObj = useMemo(() => DURATIONS.find((d) => d.id === duration)!, [duration]); + const styleObj = useMemo(() => STYLES.find((s) => s.id === scriptStyle)!, [scriptStyle]); + + const shots = avg(durationObj.shotsRange); + const completion = durationObj.completion; + const conversion = durationObj.conversion; + + // Live cost: roughly 4 line items + const cost = useMemo(() => { + const script = 0.20; + const storyboard = 0.40; + const assets = product ? product.imgs * 0.30 : 0; + const render = shots * 0.30; + const subtotal = script + storyboard + assets + render; + const fee = +(subtotal * 0.05).toFixed(2); + return { script, storyboard, assets, render, subtotal: +subtotal.toFixed(2), fee, total: +(subtotal + fee).toFixed(2) }; + }, [product, shots]); + + const balanceAfter = +(ACCOUNT_BALANCE - cost.total).toFixed(2); + const lowBalance = balanceAfter < 5; + + const etaMinutes = Math.max(3, Math.round(2 + shots * 0.4 + (product?.imgs ?? 0) * 0.2)); + + // Reco bubble (Step 3) + const recoMismatch = + personaObj.defaults.duration !== duration || personaObj.defaults.style !== scriptStyle; + const showReco = step === 3 && recoMismatch && !recoDismissed; + const recoDuration = DURATIONS.find((d) => d.id === personaObj.defaults.duration)!; + const recoStyle = STYLES.find((s) => s.id === personaObj.defaults.style)!; + + /* ---- validation gates ---- */ + const canPass1 = !!product; + const canPass2 = + !!source && + (source.id !== "theme" || themeText.trim().length >= 4) && + (source.id !== "manual" || manualScript.trim().length >= 20); + const canPass3 = projectName.trim().length >= 2; + const canFinish = agreed && !lowBalance; + + /* ---- actions ---- */ + function selectProduct(p: Product) { + setProductId(p.id); + // seed defaults derived from product + if (!projectName) setProjectName(`${p.name.split(" ")[0]} · 痛点种草 · v1`); + const seeded: Record = {}; + p.points.forEach((pt, i) => { seeded[pt] = i < 2; }); + setPoints(seeded); + } + + function applyPreset() { + setDuration(personaObj.defaults.duration); + setScriptStyle(personaObj.defaults.style); + setRecoDismissed(false); + } + function pickPersona(id: PersonaId) { + setPersona(id); + setRecoDismissed(false); + } + function togglePoint(k: string) { setPoints((p) => ({ ...p, [k]: !p[k] })); } + + function goPrev() { setStep((s) => (s > 1 ? ((s - 1) as StepNum) : s)); } + function goNext() { + if (step === 1 && !canPass1) return; + if (step === 2 && !canPass2) return; + if (step === 3 && !canPass3) return; + setStep((s) => (s < 4 ? ((s + 1) as StepNum) : s)); + } + function startGenerate() { + if (!canFinish) return; + router.push("/pipeline?stage=1"); + } + function jumpTo(target: StepNum) { + // only allow going to a completed step or current + if (target < step) setStep(target); + } + + /* ---- step rail config ---- */ + const stepConfig: Array<{ n: StepNum; label: string; desc: string }> = [ + { n: 1, label: "选择商品", desc: product ? product.name : "未选择" }, + { n: 2, label: "脚本来源", desc: source ? source.name + (source.id === "theme" && themeText ? " · 有主题" : "") : "未选择" }, + { n: 3, label: "项目配置", desc: step >= 3 ? `${durationObj.label} · ${styleObj.name}` : "时长 · 风格 · 人设" }, + { n: 4, label: "确认与计费", desc: `预估 ¥${cost.total.toFixed(2)}` }, + ]; + + /* ---- filtered products for Step 1 ---- */ + const filteredProducts = useMemo(() => { + return PRODUCTS.filter((p) => { + if (pickCat !== "全部" && p.cat !== pickCat) return false; + if (pickSearch && !p.name.includes(pickSearch)) return false; + return true; + }); + }, [pickCat, pickSearch]); + + const recentProducts = useMemo( + () => RECENT_IDS.map((id) => PRODUCTS.find((p) => p.id === id)!).filter(Boolean), + [] + ); + + return ( + <> + +
+
+
+

新建项目

+
+ // 商品 → 脚本来源 → 配置 → 确认 · 4 步开始生成 +
+
+
+ 退出 +
+
+ +
+ {/* ── Steps rail ─────────────────────────── */} + + + {/* ── Wiz main ───────────────────────────── */} +
+ {/* Step 1 · 选择商品 ───────────────── */} + {step === 1 && ( +
+
+

第 1 步 · 选择商品

+

从商品库选一个 SKU。它的主图与卖点会被 LLM 作为脚本/资产生成的素材。

+
+ +
+
+ + setPickSearch(e.target.value)} + /> +
+ {CATS.map((c) => ( + + ))} +
+ + {pickCat === "全部" && !pickSearch && ( + <> +
+ 最近使用 + {recentProducts.length} +
+
+ {recentProducts.map((p) => ( + selectProduct(p)} /> + ))} +
+ + )} + +
+ {pickCat === "全部" && !pickSearch ? "全部商品" : "搜索结果"} + {filteredProducts.length} +
+
+ {filteredProducts.map((p) => ( + selectProduct(p)} /> + ))} +
+
+
新建商品
+
+
+
+ )} + + {/* Step 2 · 脚本来源 ───────────────── */} + {step === 2 && ( + <> + setStep(1)} + body={product && } + /> +
+
+

第 2 步 · 脚本来源

+

决定 LLM 如何获得初稿脚本。三种方式由「最省事」到「最保真原意」。

+
+ +
+ {SOURCES.map((s) => ( +
setSourceId(s.id)} + > + +

{s.name}

+ [ {s.tag} ] +

{s.desc}

+
+ ))} +
+ + {source && ( +
+
+ // 已选 · {source.name} +
+ {source.id === "ai" && ( +
+ AI 全生模式无需额外输入。下一步选定时长 / 风格 / 人设后,LLM 会自动决定切入点和卖点权重。 +
+ )} + {source.id === "theme" && ( +
+ + setThemeText(e.target.value)} + /> +
推荐 5–30 字。这句话会作为 LLM 扩写的锚点,越具体越聚焦。
+
+ )} + {source.id === "manual" && ( +
+ + +
+ + + +
+
+
+ +
+
+ 镜头脚本 + · 6 镜 · 0-15s + + +
+
+
+
1
0-2s
+
画面
深夜的办公桌,电脑屏幕亮着,女主对着镜子叹气,皮肤干燥起皮特写。
+
对白
(叹气)"加班三天,脸已经不能看了……"
+
+
+
2
2-5s
+
画面
女主从抽屉拿出补水面膜,包装特写,光线柔和。
+
对白
"还好我有这个 —— 透真玻尿酸面膜。"
+
+
+
3
5-8s
+
画面
面膜布展开,30g 精华液从布上滴落特写,慢镜头。
+
对白
"30g 精华液,一片顶三片的量。"
+
+
+
4
8-11s
刚改
+
画面
女主敷面膜,闭眼平躺,灯光暖。床头闹钟显示 23:41。
+
对白
"真的,敷完第二天起来脸是软的,不是绷着的。"
+
+
+
5
11-13s
+
画面
第二天早上,女主对镜化妆,皮肤透亮,状态饱满。
+
对白
"早上化妆都能看出来,不假吹。"
+
+
+
6
13-15s
+
画面
面膜产品大图,价格标签 "5 片装 ¥39.9",购物车浮动按钮。
+
对白
"618 五片才 39.9,囤起来。"
+
+
+
+
+ +
+
[ LLM 用量 ~2.4k tokens · ¥0.04 ]
+
+ + +
+
+
+ + +
+
+
+
人物2/2
+
场景3/3
+
商品3 张
+
+ 基础资产是后续故事板的素材。生成后可以单独修改提示词重跑,或上传你已有的图替换。 +

+ // 人物 +¥0.20/张 + // 场景 +¥0.15/张 + 商品图无成本(直接复用商品库) +
+
+ +
+
+

人物 · 2 个

+ + + +
+ +
+
+
林夕 · 都市白领
+
+
主角 · 林夕已确认
+
25-30 岁都市白领,长发,穿宽松米色家居服,温柔但带点疲倦感,肤色偏黄/略干。
+
+ + + + +
+
+
+ +
+
+
+
+ 生成中 · 约 8s +
+
+
+
朋友/同事 · 阿楠生成中
+
25-30 岁同龄女性,短发,穿白色衬衫,妆容精致皮肤好,作为对比。
+
+ + + + +
+
+
+
+ +
+

场景 · 3 个

+ + + +
+ +
+
+
深夜办公桌
+
+
深夜办公桌已确认
+
深夜居家办公环境,木质书桌,台灯暖光,电脑屏幕亮着,背景虚化。
+
+
+
+
床头特写
+
+
卧室床头已确认
+
米白色床品,木质床头柜,闹钟显示晚间时间,氛围温柔安静。
+
+
+
+
+
+
!
+ 生成失败 +
+
+
+
通勤地铁失败
+
早高峰地铁车厢,光线偏冷,年轻通勤族,氛围紧张。
+
⚠ 提示词被安全审核拦截,请调整后重试(不扣费)
+
+ + +
+
+
+
+
+
+ +
+
[ 已确认 ¥0.85 · 待生成 ¥0.20 · 失败 ¥0(不扣) ]
+
+ + +
+
+
+ + +
+
+
+
+
镜号
画面
机位 / 对白 / 音效
+
+
+
10-2s
+
深夜办公桌 · 女主对镜叹气 · 暖光
+
+
中景 / 固定机位
+
"加班三天,脸已经不能看了……"
+
SFX:键盘声 + 远处空调嗡鸣
+
+
+
+
22-5s
+
面膜包装特写 · 抽屉光线柔和
+
+
特写 / 缓推
+
"还好我有这个 —— 透真玻尿酸面膜。"
+
SFX:抽屉滑动声
+
+
+
+
35-8s
+
面膜布展开 · 30g 精华滴落 · 慢动作
+
+
微距 / 慢镜头
+
"30g 精华液,一片顶三片的量。"
+
SFX:水滴慢速回弹
+
+
+
+
48-11s
+
女主敷面膜平躺 · 闹钟 23:41
+
+
中近景 / 固定
+
"敷完第二天起来脸是软的,不是绷着的。"
+
SFX:呼吸声 + 窗外风声
+
+
+
+
511-13s
+
早晨化妆台 · 女主对镜上妆 · 透亮
+
+
中景 / 固定
+
"早上化妆都能看出来,不假吹。"
+
SFX:化妆刷轻扫声
+
+
+
+
613-15s
+
产品大图 · 价格 5片¥39.9 · 购物车
+
+
产品定格 / 静止
+
"618 五片才 39.9,囤起来。"
+
SFX:清脆叮咚音效
+
+
+
+ +
+
+
+ 故事板 + + 已生成 +
+
+ 整张故事板由 image-2 一次性输出,包含画面 + 镜头说明。如需修改请编辑下方提示词整张重跑(不能局部改)。 +
+ +
// 视觉提示词
+
风格:日系小清新短视频,暖色调,午夜→清晨光线变化。 +镜头列表(6 镜,0-15s): +1. 中景 · 深夜办公桌女主对镜叹气 +2. 特写 · 面膜包装从抽屉拿出 +3. 微距 · 面膜布展开 + 精华液滴落慢镜 +4. 中近景 · 女主敷面膜平躺,床头闹钟 23:41 +5. 中景 · 早晨化妆台 + 女主透亮上妆 +6. 产品定格 · 面膜盒 + 价格标签 ¥39.9 +人物:林夕(参考 R1) +场景:深夜办公桌(S1) + 卧室床头(S2) +要求:表格化布局,每镜含画面 + 文字说明
+ +
+ + + ~¥0.45 +
+ +
+ +
// 绑定的资产
+
+ 林夕(人物) + 深夜办公桌(场景) + 卧室床头(场景) + 面膜盒(商品) +
+
+
+
+ +
+
[ image-2 一次 ¥0.45 · 累计 ¥1.50 ]
+
+ + +
+
+
+ + +
+
+
+
视频生成中 · 4 / 6 完成
+
// 每镜 Seedance 调用 ~30s · 预计还需 1 分钟
+
+
+ 67% + +
+ +
+
+
+ 镜 1 · 0-2s +
+
+
+
镜 1 · 深夜办公桌完成
+
2.0s · 1080×1920 · ¥0.18
+
+ + +
+
+
+
+
+ 镜 2 · 2-5s +
+
+
+
镜 2 · 面膜包装完成
+
3.0s · 1080×1920 · ¥0.22
+
+ + +
+
+
+
+
+ 镜 3 · 5-8s +
+
+
+
镜 3 · 精华液微距完成
+
3.0s · 1080×1920 · ¥0.22
+
+ + +
+
+
+
+
+ 镜 4 · 8-11s +
+
+
+
镜 4 · 敷面膜平躺完成
+
3.0s · 1080×1920 · ¥0.22
+
+ + +
+
+
+
+
+
+
+ 镜 5 · 生成中 18s +
+
+
+
镜 5 · 化妆台生成中
+
2.0s · 排队中 · ~¥0.18
+
+ + +
+
+
+
+
镜 6 · 排队
+
+
镜 6 · 产品定格排队中
+
2.0s · 等待中 · ~¥0.18
+
+
+
+ +
+
[ 已确认 ¥0.84 · 待生成 ¥0.36 · 累计 ¥2.34 ]
+
+ + +
+
+
+ + +
+
+
+
9:16 预览 · 1080×1920
+
+ + + + 00:08.42 / 00:15.00 +
+
+ +
+
+
字幕
+
转场
+
BGM
+
+
// 字幕样式
+
+
真实分享
朴素白底
+
真实分享
影视黑底
+
真实分享
手写描边
+
真实分享
综艺暖黄
+
+ +
+ +
// 当前选中(镜 4)
+
起始
+
时长
+
音量
+
速度
+
入场交叉淡化
+ +
+ +
// BGM
+
+ 温柔治愈钢琴 · 0:42 + +
+
+ +
+
+ + + | + + + + + 缩放 + +
+ +
+
// time
+
+ 0s2s5s8s11s13s15s +
+
+ +
+
视频
+
+
1 深夜办公桌
+
2 面膜包装
+
3 精华液微距
+
4 敷面膜平躺
+
5 化妆台
+
6 产品定格
+
+
+ +
+
字幕
+
+
加班三天 脸已经不能看了…
+
还好我有这个 透真玻尿酸面膜
+
30g 精华 一片顶三片
+
敷完起来脸是软的
+
化妆都能看出来
+
5 片 ¥39.9 囤起来
+
+
+
+ +
+
BGM
+
+
温柔治愈钢琴 · 0:42(循环 1 次,淡入淡出)
+
+
+
+
+ +
+
[ 合成预估 ~30s · 不消耗 token ]
+
+ + + +
+
+
+ + + + + + diff --git a/v1/products.html b/v1/products.html new file mode 100644 index 0000000..d24af6f --- /dev/null +++ b/v1/products.html @@ -0,0 +1,191 @@ + + + + +商品库 · 流·Studio + + + + + +
+ +
+
+

商品库

+
// 12 SKU · 商品信息会作为脚本和资产生成的素材
+
+
+ +
+
+ +
+
+ + +
+ + + + + + + +
+ +
+
+
补水面膜 · 1200×800
+
+
透真玻尿酸补水面膜
+
美妆个护 · 3 张图
+
熬夜党敏感肌¥39.9
+
+
+
+
蓝牙耳机 · 1200×800
+
+
南卡 Lite Pro 蓝牙耳机
+
数码 3C · 5 张图
+
通勤运动¥199
+
+
+
+
速食牛肉面 · 1200×800
+
+
滋啦速食牛肉面 · 6 桶装
+
食品饮料 · 4 张图
+
加班独居¥49.9
+
+
+
+
防晒霜 · 1200×800
+
+
透真清透物理防晒霜
+
美妆个护 · 4 张图
+
SPF50通勤¥69
+
+
+
+
咖啡冻干粉 · 1200×800
+
+
三顿半同款冻干咖啡粉
+
食品饮料 · 6 张图
+
提神早八¥89/24 颗
+
+
+
+
空气炸锅 · 1200×800
+
+
小熊 4L 可视空气炸锅
+
家电 · 5 张图
+
小户型健康¥159
+
+
+
+
瑜伽裤 · 1200×800
+
+
露露同款裸感瑜伽裤
+
服饰 · 8 张图
+
健身房通勤¥119
+
+
+
+
+
新建商品
+
+
+ +
+ + +
+ + + + + + diff --git a/v1/projects-new.html b/v1/projects-new.html new file mode 100644 index 0000000..7cbe1ca --- /dev/null +++ b/v1/projects-new.html @@ -0,0 +1,286 @@ + + + + +新建项目 · 流·Studio + + + + + +
+ +
+
+

新建项目

+
// 商品 → 脚本来源 → 配置 → 确认 · 4 步开始生成
+
+
+ 退出 +
+
+ +
+ + +
+ + + + +
+
+

第 3 步 · 项目配置

+

这些设置会影响 LLM 生成脚本的方向,确认后会进入流水线第 1 步(脚本生成)。

+
+ +
+ + +
+ +
+ +
+

0-10 秒

3-4 镜
黄金完播
完播 52%
+

0-15 秒

4-5 镜
完播率最佳
完播 42%
+

0-30 秒

6-8 镜
卖点详解
完播 32%
+

0-60 秒

10-12 镜
故事化
完播 26%
+
+
数据来源:抖音同品类 TOP 视频均值 · 实际镜头数由 LLM 决定
+
+ +
+ +
+
+

痛点种草

+
用户痛点切入,以「我懂你」的口吻引出产品。
+ 最常用 +
+
+

开箱测评

+
朋友式分享,从开箱到使用感受娓娓道来。
+
+
+

对比展示

+
「用前 vs 用后 / 同类 vs 本品」直观呈现。
+
+
+
+ +
+ +
+

都市白领女性

25-30 岁
大盘消费力
+

闺蜜种草

邻家女孩
复购最高
+

总裁亲选

创始人 IP
30 万销额
+

专业测评师

垂类达人
互动 +30%
+

实用宝妈

家庭决策者
母婴/家清
+

学生党

Z 世代 18-24
平价快消
+
+ +
+ + + +
+ 抖音同人设 TOP 视频更常用 0-10 秒 + 对比展示 + 当前 0-15 秒 · 痛点种草 → 推荐换为学生党最优组合 +
+ + +
+
+ +
+ +
+ ✓ 透明质酸 + B5 + ✓ 30g 大容量精华 + + 0 香精 0 酒精 +
+
+
+ +
+ +
+ // 下一步:确认与计费 + 下一步 → +
+
+
+ + + +
+ +
+ + + + diff --git a/v1/projects.html b/v1/projects.html new file mode 100644 index 0000000..ec3d796 --- /dev/null +++ b/v1/projects.html @@ -0,0 +1,521 @@ + + + + +视频项目 · 流·Studio + + + + + +
+ +
+
+

视频项目

+
// 12 个 · 3 进行中 · 8 完成 · 1 失败
+
+ +
+ +
+
全部 12
+
进行中 3
+
已完成 8
+
失败 1
+
+ +
+
+ + +
+ + + + +
+ + +
+
+ +
// 显示 12 / 12 个项目
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
项目商品脚本来源进度状态更新于
+
+
9:16
+
补水面膜 · 痛点种草 · v3
6 镜 · 0-15s
+
+
透真补水面膜AI 全生 +
+
+ 3/5 +
+
故事板 待确认12 分钟前 +
+ + +
+
+
+
9:16
+
速食牛肉面 · 加班治愈
4 镜 · 0-12s
+
+
滋啦速食 · 6 桶装一句话主题 +
+
+ 2/5 +
+
资产生成中37 分钟前
+
+
9:16
+
透真防晒 · 通勤对比
6 镜 · 0-18s
+
+
透真清透防晒霜AI 全生 +
+
+ 4/5 +
+
视频生成 4/62 小时前
+
+
9:16
+
咖啡冻干 · 早八剧情
5 镜 · 0-15s
+
+
三顿半同款冻干一句话主题 +
+
+ 3/5 +
+
故事板生成失败昨天 18:42
+
+
9:16
+
蓝牙耳机 · 开箱测评
5 镜 · 0-15s
+
+
南卡 Lite Pro自带脚本 +
+
+ 5/5 +
+
已完成5 月 7 日
+
+
9:16
+
瑜伽裤 · 通勤穿搭
5 镜 · 0-15s
+
+
露露同款瑜伽裤AI 全生 +
+
+ 5/5 +
+
已完成5 月 6 日
+
+
9:16
+
空气炸锅 · 小户型
4 镜 · 0-12s
+
+
小熊 4L 空气炸锅一句话主题 +
+
+ 5/5 +
+
已完成5 月 4 日
+
+
9:16
+
补水面膜 · 痛点种草 · v1
6 镜 · 0-15s
+
+
透真补水面膜AI 全生 +
+
+ 5/5 +
+
已归档4 月 28 日
+
+ + + + + +
+
+ +
+

没有匹配的项目

+

// 试试切换 tab 或修改搜索词

+
+ +
+ + + + diff --git a/电商AI平台/_ARCHIVE.md b/电商AI平台/_ARCHIVE.md new file mode 100644 index 0000000..35e75c0 --- /dev/null +++ b/电商AI平台/_ARCHIVE.md @@ -0,0 +1,59 @@ +# 待归档/清理的 HTML 文件 + +> 2026-05-21 · 设计稿对接前清理清单 + +下列文件是早期版本或独立实验页,**不在主流程上**,对接给团队前建议归档/删除。 +保留它们不会影响主流程渲染(没有 sidebar / 内部 link 指向),但会让 repo 文件目录显得混乱。 + +## ✅ 可以直接删除(无任何入口) + +| 文件 | 原用途 | 现状 | +|------|--------|------| +| `product-create.legacy.html` | 旧版「新建商品」全屏页(3883 行 drawer) | 已被 `product-create.html`(stub 重定向)+ drawer 模式替代,无任何链接 | +| `product-create-v2.html` | 备选「新建商品」实验版 | 仅 `product-studio.html` 单向关联,product-studio 本身已无入口 | +| `studio.html` | 早期工作室页占位 | 已被 `pipeline.html` 替代,sidebar 已切换,无 link | +| `studio-v2.html` | 早期工作室 V2 实验 | 同上 | +| `product-studio.html` | 商品 + 工作室合并实验 | 主流程已拆为 `product-detail.html` + `pipeline.html` | + +## ⚠️ 保留但要清理 sidebar 入口判断 + +| 文件 | 决定 | +|------|------| +| `product-create.html` | **保留** · stub 重定向,处理外部书签/直接访问 → 弹 drawer | +| `product-create-upload.html` | **保留** · 商品图上传专用辅助页,通过 drawer 内部调用 | +| `model-photo.html` / `platform-cover.html` | **保留** · 「AI 生成素材」二级页(从商品库 gen-choice 跳入) | +| `asset-factory.html` | **保留** · sidebar 入口「图片生成」 | +| `design-system.html` | **保留** · 设计系统参考,非用户路径 | + +## 🚀 清理命令(对接前) + +如果决定执行,可在 `电商AI平台/` 目录下: + +```powershell +# Windows PowerShell +Remove-Item product-create.legacy.html, product-create-v2.html, studio.html, studio-v2.html, product-studio.html +``` + +清理后总文件数:**21 → 16**(含 design-system),核心 15 个。 + +## 📌 sidebar NAV 当前入口对照(assets/shell.js:10-43) + +``` +工作台 → index.html ✓ +商品库 → products.html ✓ +视频项目 → projects.html ✓ +图片生成 → asset-factory.html ✓ +资产库 → library.html ✓ +团队 → team.html ✓ +消费 → account.html ✓ +设置 → settings.html ✓ +``` + +未在 sidebar 的"白名单"页面(从其他页面 link 进入): +- login.html / register.html(auth flow · 顶级入口) +- projects-new.html(项目向导 · 工作台「+新建项目」入口) +- pipeline.html(流水线 · 项目列表「打开」入口) +- product-detail.html(商品详情 · 商品库卡片入口) +- product-create.html stub / product-create-upload.html(drawer 流程辅助) +- model-photo.html / platform-cover.html(AI 素材生成入口) +- design-system.html(设计参考 · 非用户路径) diff --git a/电商AI平台/account.html b/电商AI平台/account.html index 00b4bdd..c465025 100644 --- a/电商AI平台/account.html +++ b/电商AI平台/account.html @@ -2,63 +2,186 @@ -账户 · 流·Studio +消费 · 流·Studio @@ -66,105 +189,424 @@
-

账户

-
// 余额 · 充值 · 消费明细
+

消费

+
// 余额 · 充值 · 4 维消费视图 + 账单流水
-
-
-
- -
[ CURRENT BALANCE ]
+ +
+ +
+ +
+
团队余额
¥327.40
-
// 本月已消费 ¥162.60 · 可使用约 32 个项目
-
- - +
// 充值累加 · 不重置
+
+
+
+
本月限额
+
¥3,000.00
+
// 按自然月重置
+
+
+
当月已用
+
¥162.60
+
// 占比 5.4% · 健康
- -
-

快速充值

-
// 充值后立刻到账,可开发票
-
-
¥100
无赠送
-
推荐
¥500
+ ¥30 赠送
-
¥1000
+ ¥80 赠送
-
¥3000
+ ¥300 赠送
-
-
- - - -
+
+ +
- -
-

消费明细

- -
- - -
-
- - - - - - - - - - - - - - - -
时间项目 / 类型详情金额
05.09 14:08补水面膜 · v3故事板 image-2 · 1 次-¥0.45
05.09 14:02补水面膜 · v3脚本 LLM · 2.4k tokens-¥0.04
05.09 13:38补水面膜 · v3基础资产 · 5 张图-¥1.05
05.08 18:21透真防晒 · 通勤对比视频片段 · 6 镜-¥1.20
05.08 11:02充值微信支付 · TX2024050811021Z+¥500.00
05.07 20:14蓝牙耳机 · 开箱视频片段 · 5 镜(1 镜重跑不扣)-¥0.94
05.07 15:48咖啡冻干 · 早八故事板生成失败 · 不扣费¥0.00
05.06 10:30瑜伽裤 · 通勤穿搭项目导出 · 1 次-¥3.20
-
-
-

本月消费分布

-
视频片段(Seedance)¥98.40
-
- -
故事板(image-2)¥36.00
-
- -
基础资产¥21.00
-
- -
脚本 LLM¥7.20
-
- -
-
合计¥162.60
+ +
+

快速充值

+
// 充值后立刻到账,可开发票 · 仅超管可操作
+
+
¥100
无赠送
+
推荐
¥500
+ ¥30 赠送
+
¥1000
+ ¥80 赠送
+
¥3000
+ ¥300 赠送
+
+ +
+ + +
+
+
+
-
-

扣费规则

-
- ① 失败不扣:模型超时、内容审核拦截、生成异常一律不扣费。
- ② 用户重跑不扣首次:第一次重跑保留原扣费,第二次起按次结算。
- ③ 仅在你点击 [ 确认通过 ] 时入账
- ④ 导出不再扣费,所有 token 已在过程中结算。 + +
+ + + + +
+ + +
+
+
+
+

消费趋势

+ // 近 14 天 · 单位 ¥ + + + + +
+
+
+ +
+
+ +
+
+
+
14 天合计¥0.00
+
日均¥0.00
+
峰值¥0.00
-
-

开发票

-
本月可开发票额度:¥162.60
- +
+

本月按阶段分布

+
// PRD §5.3.5 扣费规则 · 仅确认后扣
+
视频片段(Seedance)¥98.40
+
+
故事板(image-2)¥36.00
+
+
基础资产¥21.00
+
+
脚本 LLM¥7.20
+
+
合计¥162.60
+
+
+ +
+

扣费 + 四层额度预检规则

+
// PRD §5.3.5 + §10.3 · 对接团队请以此页为准
+
+ ① 失败不扣:模型超时 / 内容审核拦截 / 生成异常一律不扣费。
+ ② 用户重跑不扣首次:第一次重跑保留原扣费,第二次起按次结算。
+ ③ 仅在你点击 [ 确认通过 ] 时入账
+ ④ 导出不再扣费,所有 token 已在过程中结算。 +
+
+
// 任务确认前 · 四层额度预检(任一不通过即拦截)
+
1个人日剩余 ≥ 任务预估 × 1.2
+
2个人月剩余 ≥ 同上
+
3团队月剩余 ≥ 同上
+
4团队总余额 ≥ 同上
+
+
+
+ + +
+
+ + + + 8 个项目 · 当月消耗 ¥162.60 +
+ + + + + + + + + + + + + + +
项目商品所属成员当前阶段状态当月消耗
+
+ + +
+
+ + + + 5 人 · 当月合计 ¥319.00 +
+ + + + + + + + + + + + + +
成员角色已完成项目当月已用 / 月度额度最近活跃
+
+ + +
+
+ + + + + 28 条 · +
+ + + + + + + + + + + + + + +
时间项目 / 类型详情成员状态金额
+
+ + +
- + diff --git a/电商AI平台/assets/restraint.css b/电商AI平台/assets/restraint.css index 22a1acd..885845b 100644 --- a/电商AI平台/assets/restraint.css +++ b/电商AI平台/assets/restraint.css @@ -1440,3 +1440,32 @@ table.t tbody tr:hover { background: var(--black-alpha-4); } animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } + +/* ─── 缺三视图 badge · 商品 / 人物 卡片 thumb 左上角提示 ─── */ +.tri-missing-badge { + position: absolute; + top: 8px; left: 8px; + z-index: 2; + display: inline-flex; align-items: center; gap: 5px; + padding: 3px 8px 3px 6px; + background: var(--heat); + color: var(--accent-white); + font-family: var(--font-mono); + font-size: 10.5px; + font-weight: 500; + letter-spacing: .03em; + border-radius: var(--r-sm); + box-shadow: 0 1px 2px rgba(0,0,0,.12); + pointer-events: none; + user-select: none; +} +.tri-missing-badge::before { + content: '!'; + width: 12px; height: 12px; + border: 1px solid currentColor; + border-radius: 50%; + display: grid; place-items: center; + font-size: 9px; font-weight: 700; + line-height: 1; +} +.tri-missing-badge .lbl-mono { font-family: var(--font-mono); letter-spacing: .04em; } diff --git a/电商AI平台/index.html b/电商AI平台/index.html index b4d0cfd..294b4ac 100644 --- a/电商AI平台/index.html +++ b/电商AI平台/index.html @@ -82,51 +82,51 @@ [ ALL · 8 ]
- +
9:16
-
补水面膜 · 痛点种草
-
补水面膜 / AI 全生 / 6 镜
+
补水面膜 · 痛点种草 · v3
+
透真补水面膜 / AI 全生 / 7 镜
故事板 待确认 继续
- +
9:16
-
蓝牙耳机 · 开箱测评
-
南卡 Lite Pro / 自带脚本 / 5 镜
+
透真防晒 · 通勤对比
+
透真防晒 / AI 全生 / 6 镜
已完成 打开
- +
9:16
-
速食牛肉面 · 一句话主题
-
滋啦速食 / 一句话 / 4 镜
-
-
- 资产生成中 - 继续 -
- -
9:16
-
-
防晒霜 · 对比展示
-
透真防晒 / AI 全生 / 6 镜
+
蓝牙耳机 · 开箱测评
+
Pro 4 蓝牙耳机 / 自带脚本 / 6 镜
视频生成 4/6 继续
- +
9:16
-
咖啡冻干粉 · 剧情带货
-
三顿半同款 / 一句话 / 5 镜
+
春日新品 · 立体口红
+
凝彩立体口红 / 一句话 / 5 镜
+
+
+ 资产生成中 + 继续 +
+ +
9:16
+
+
咖啡冻干 · 早八
+
冷萃咖啡冻干 / 一句话 / 5 镜
故事板失败 diff --git a/电商AI平台/login.html b/电商AI平台/login.html new file mode 100644 index 0000000..5c5562f --- /dev/null +++ b/电商AI平台/login.html @@ -0,0 +1,207 @@ + + + + +登录 · 流·Studio + + + + + +
← 返回工作台 + +
+ + + + + + +
+
+

登录

+ // /auth/login +
+

使用团队邀请邮箱登录,接受邀请后自动加入对应团队。

+ +
+
+ +
+ + +
+
+ +
+ +
+ + + +
+
+ +
+ + 忘记密码? +
+ + + +
OR
+ +
+ + +
+ +
+ 还没账号? 注册团队 → +
+
+
+
+ + + + diff --git a/电商AI平台/products.html b/电商AI平台/products.html index 459abfe..4613e65 100644 --- a/电商AI平台/products.html +++ b/电商AI平台/products.html @@ -4,7 +4,7 @@ 商品库 · 流·Studio - + + + +← 返回登录 + +
+ + + + +
+
+

注册团队

+ // /auth/register +
+

填写团队信息开通账户,默认成为团队超管。

+ +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+ +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ +
+ + +
+
+ + + + + +
+ 已有账号? 登录 → +
+
+
+
+ + + + diff --git a/电商AI平台/settings.html b/电商AI平台/settings.html index bf7d28c..0dfe51c 100644 --- a/电商AI平台/settings.html +++ b/电商AI平台/settings.html @@ -11,10 +11,16 @@ .settings-nav { position: sticky; top: 16px; } .settings-nav .nav-h { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .06em; text-transform: uppercase; padding: 0 12px 8px; } - .settings-nav a { display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13px; color: var(--accent-black); border-radius: var(--r-md); border: 1px solid transparent; cursor: pointer; text-decoration: none; transition: background var(--t-base), border-color var(--t-base); } + .settings-nav a { display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13px; color: var(--accent-black); border-radius: var(--r-md); border: 1px solid transparent; cursor: pointer; text-decoration: none; transition: background var(--t-base), border-color var(--t-base); position: relative; } .settings-nav a:hover { background: var(--background-lighter); } + .settings-nav a:focus-visible { outline: 2px solid var(--heat); outline-offset: 2px; } .settings-nav a.active { background: var(--heat-12); color: var(--heat); border-color: var(--heat-20); font-weight: 600; } .settings-nav a svg { width: 16px; height: 16px; stroke-width: 1.5; } + .settings-nav a .nav-badge { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-48); padding: 1px 6px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-pill); letter-spacing: .02em; line-height: 14px; } + .settings-nav a.active .nav-badge { color: var(--heat); background: var(--accent-white); border-color: var(--heat-20); } + .settings-nav a .nav-dot { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 6px; height: 6px; border-radius: 50%; background: var(--heat); display: none; } + .settings-nav a.has-changes .nav-dot { display: block; } + .settings-nav a.active .nav-dot { right: -4px; } /* ─── pane ─── */ .pane { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 24px; margin-bottom: 16px; } @@ -73,6 +79,23 @@ .device-row .meta { font-size: 11.5px; color: var(--black-alpha-48); font-family: var(--font-mono); margin-top: 2px; letter-spacing: .02em; } .device-row .tag-cur { font-family: var(--font-mono); font-size: 10.5px; padding: 1px 6px; background: var(--accent-forest); color: var(--accent-white); border-radius: var(--r-sm); margin-left: 8px; letter-spacing: .04em; font-weight: 600; } .device-row .spacer { margin-left: auto; } + + /* ─── 头像上传 modal · V2.1 Restraint ─── */ + .av-up-modal { width: min(440px, 92vw); max-width: min(440px, 92vw); } + + /* 预览区:左圆头像 + 右 mono 元数据 · 装订线点划线分隔 */ + .av-up-preview-row { display: flex; align-items: center; gap: 14px; padding-bottom: 14px; margin-bottom: 14px; position: relative; } + .av-up-preview-row::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: repeating-linear-gradient(to right, var(--border-faint) 0, var(--border-faint) 4px, transparent 4px, transparent 8px); } + .av-up-preview { width: 64px; height: 64px; border-radius: 50%; background: var(--background-lighter); border: 1px solid var(--border-faint); display: grid; place-items: center; font-size: 22px; font-weight: 600; color: var(--accent-black); overflow: hidden; flex: 0 0 64px; } + .av-up-preview img { width: 100%; height: 100%; object-fit: cover; display: block; } + .av-up-preview-meta { min-width: 0; } + .av-up-preview-meta .t { font-size: 12.5px; font-weight: 600; color: var(--accent-black); margin-bottom: 3px; letter-spacing: .01em; } + .av-up-preview-meta .d { font-size: 11px; color: var(--black-alpha-48); font-family: var(--font-mono); letter-spacing: .02em; line-height: 1.55; } + + /* 规则文本 · 纯 mono, 装订线分隔 */ + .av-up-rules { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--border-faint); font-size: 11px; color: var(--black-alpha-56); font-family: var(--font-mono); letter-spacing: .02em; line-height: 1.7; } + .av-up-rules .li { display: flex; gap: 8px; } + .av-up-rules .li::before { content: '//'; color: var(--black-alpha-32); flex: 0 0 auto; } @@ -94,33 +117,40 @@
-
+ + +
diff --git a/电商AI平台/team.html b/电商AI平台/team.html index eb35072..6e2f391 100644 --- a/电商AI平台/team.html +++ b/电商AI平台/team.html @@ -110,6 +110,125 @@ .members-table tr.pending td { opacity: .65; } .members-table tr.pending .nm::after { content: '· 待激活'; font-size: 11px; color: var(--black-alpha-48); margin-left: 6px; font-weight: 400; font-family: var(--font-mono); } + /* ─── 成员详情 modal ─── */ + .member-detail-modal { width: min(760px, 92vw); max-width: min(760px, 92vw); } + /* 给 modal-h 留出右侧 X 的位置 */ + .member-detail-modal .modal-h { padding-right: 56px; } + /* 模态右上角关闭 X */ + .md-x { position: absolute; top: 14px; right: 16px; width: 28px; height: 28px; border-radius: var(--r-sm); border: 1px solid var(--border-faint); background: var(--surface); color: var(--black-alpha-56); display: grid; place-items: center; cursor: pointer; transition: all var(--t-base); z-index: 2; } + .md-x:hover { color: var(--accent-black); border-color: var(--black-alpha-32); } + .md-x svg { width: 13px; height: 13px; } + + .md-header { display: flex; align-items: center; gap: 14px; padding: 4px 0 16px; border-bottom: 1px solid var(--border-faint); margin-bottom: 18px; } + .md-header .av-big { width: 52px; height: 52px; border-radius: 50%; background: var(--background-lighter); border: 1px solid var(--border-faint); display: grid; place-items: center; font-size: 20px; font-weight: 600; color: var(--accent-black); flex-shrink: 0; } + .md-header .who-main { min-width: 0; flex: 1; } + .md-header .nm-big { font-size: 16px; font-weight: 600; color: var(--accent-black); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } + .md-header .em-big { font-size: 12px; color: var(--black-alpha-48); font-family: var(--font-mono); margin-top: 4px; letter-spacing: .02em; } + .md-header .role-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: var(--r-pill); font-size: 11px; font-weight: 500; } + .md-header .role-pill .dot { width: 6px; height: 6px; border-radius: 50%; } + .md-header .role-pill.role-super { background: var(--heat-12); color: var(--heat); } + .md-header .role-pill.role-super .dot { background: var(--heat); } + .md-header .role-pill.role-admin { background: rgba(30,64,175,.1); color: #1E40AF; } + .md-header .role-pill.role-admin .dot { background: #1E40AF; } + .md-header .role-pill.role-member { background: var(--background-lighter); color: var(--black-alpha-56); } + .md-header .role-pill.role-member .dot { background: var(--black-alpha-56); } + .md-header .tag-pending { font-family: var(--font-mono); font-size: 10.5px; padding: 1px 6px; background: rgba(180,83,9,.12); color: #B45309; border-radius: var(--r-sm); letter-spacing: .04em; } + /* 创建者:mono 小 tag,不再用 "·" 字符串拼接 */ + .md-header .tag-creator { font-family: var(--font-mono); font-size: 10.5px; padding: 1px 6px; background: var(--background-lighter); color: var(--black-alpha-56); border: 1px solid var(--border-faint); border-radius: var(--r-sm); letter-spacing: .04em; font-weight: 500; } + + .md-section { margin-bottom: 18px; } + .md-section:last-child { margin-bottom: 0; } + .md-section-h { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .06em; text-transform: uppercase; margin-bottom: 10px; } + .md-section-h .hint { margin-left: 6px; font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-32); font-weight: 400; letter-spacing: .02em; text-transform: none; } + + /* 角色权限速览 */ + .md-perm-list { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; } + .md-perm-list .pi { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--accent-black); line-height: 1.4; } + .md-perm-list .pi .ck { flex: 0 0 12px; color: var(--accent-forest); font-family: var(--font-mono); font-size: 11px; font-weight: 600; } + .md-perm-list .pi.deny .ck { color: var(--black-alpha-32); } + .md-perm-list .pi.deny { color: var(--black-alpha-48); } + .md-perm-list .pi .lb { flex: 1; min-width: 0; } + .md-perm-list .pi .lb em { font-style: normal; color: var(--black-alpha-48); font-family: var(--font-mono); font-size: 11px; margin-left: 4px; } + + /* 额度日消耗子行 */ + .md-daily-row { display: flex; gap: 16px; align-items: center; margin-top: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-56); letter-spacing: .02em; } + .md-daily-row .k { color: var(--black-alpha-48); } + .md-daily-row .v { color: var(--accent-black); font-weight: 600; font-variant-numeric: tabular-nums; } + .md-daily-row .v.warn { color: #B45309; } + .md-daily-row .sep { color: var(--black-alpha-32); } + + .md-quota-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; } + .md-quota-box { background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; } + .md-quota-box .lbl { font-size: 11px; color: var(--black-alpha-48); font-family: var(--font-mono); letter-spacing: .02em; } + .md-quota-box .v { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent-black); margin-top: 4px; } + .md-quota-box .v.warn { color: #B45309; } + .md-progress { margin-top: 10px; } + .md-progress .label { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--black-alpha-56); font-family: var(--font-mono); margin-bottom: 4px; } + .md-progress .bar { height: 6px; background: var(--background-lighter); border-radius: 3px; overflow: hidden; border: 1px solid var(--border-faint); } + .md-progress .bar > span { display: block; height: 100%; background: var(--heat); } + .md-progress .bar > span.ok { background: var(--accent-forest); } + .md-progress .bar > span.warn { background: #B45309; } + + .md-stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; } + .md-stat { background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 10px 12px; text-align: center; } + .md-stat .v { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent-black); } + .md-stat .lbl { font-size: 11px; color: var(--black-alpha-48); font-family: var(--font-mono); margin-top: 2px; letter-spacing: .02em; } + + .md-activity-list { display: flex; flex-direction: column; gap: 8px; max-height: 180px; overflow-y: auto; padding-right: 4px; } + .md-activity-list::-webkit-scrollbar { width: 4px; } + .md-activity-list::-webkit-scrollbar-thumb { background: var(--border-faint); border-radius: 2px; } + .md-activity-item { display: grid; grid-template-columns: 70px 1fr; gap: 10px; align-items: baseline; font-size: 12.5px; line-height: 1.4; } + .md-activity-item .ts { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; } + .md-activity-item .act { color: var(--black-alpha-56); } + .md-activity-item .obj { color: var(--heat); } + + /* ─── 消费明细 section ─── */ + .md-section-h .right { margin-left: auto; font-family: var(--font-mono); font-size: 11.5px; color: var(--accent-black); font-weight: 600; letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; } + .md-section-h { display: flex; align-items: baseline; } + + .md-stage-list { display: flex; flex-direction: column; gap: 10px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 14px 16px; } + .md-stage-row { display: grid; grid-template-columns: 12px 1fr; column-gap: 10px; row-gap: 5px; align-items: center; } + .md-stage-row .swatch { width: 8px; height: 8px; border-radius: 2px; align-self: center; } + .md-stage-row .swatch.s-video { background: var(--heat); } + .md-stage-row .swatch.s-storyboard { background: var(--accent-forest); } + .md-stage-row .swatch.s-asset { background: var(--black-alpha-56); } + .md-stage-row .swatch.s-script { background: var(--black-alpha-32); } + .md-stage-row .line { display: flex; align-items: baseline; font-size: 12.5px; min-width: 0; } + .md-stage-row .nm { color: var(--accent-black); } + .md-stage-row .pct { margin-left: 8px; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); font-variant-numeric: tabular-nums; } + .md-stage-row .amt { margin-left: auto; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--accent-black); font-size: 12.5px; } + .md-stage-row .bar { grid-column: 2; height: 4px; background: var(--surface); border-radius: 2px; overflow: hidden; } + .md-stage-row .bar > span { display: block; height: 100%; transition: width .3s ease; } + .md-stage-row .bar > span.s-video { background: var(--heat); } + .md-stage-row .bar > span.s-storyboard { background: var(--accent-forest); } + .md-stage-row .bar > span.s-asset { background: var(--black-alpha-56); } + .md-stage-row .bar > span.s-script { background: var(--black-alpha-32); } + + /* 流水明细切换:按钮化 */ + .md-tx-bar { display: flex; align-items: center; margin-top: 14px; padding: 8px 10px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-sm); } + .md-tx-bar .ct { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-56); letter-spacing: .02em; } + .md-tx-bar .ct b { color: var(--accent-black); font-weight: 600; } + .md-tx-toggle { display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-mono); font-size: 11px; color: var(--heat); cursor: pointer; margin-left: auto; user-select: none; padding: 4px 10px; border-radius: var(--r-sm); transition: background var(--t-base); } + .md-tx-toggle:hover { background: var(--heat-12); } + .md-tx-toggle svg { width: 11px; height: 11px; transition: transform var(--t-base); } + .md-tx-toggle.expanded svg { transform: rotate(180deg); } + + .md-tx-table { display: none; flex-direction: column; max-height: 220px; overflow-y: auto; margin-top: 10px; border: 1px solid var(--border-faint); border-radius: var(--r-sm); background: var(--surface); } + .md-tx-table.show { display: flex; } + .md-tx-table::-webkit-scrollbar { width: 4px; } + .md-tx-table::-webkit-scrollbar-thumb { background: var(--border-faint); border-radius: 2px; } + .md-tx-row { display: grid; grid-template-columns: 96px 1fr 110px 78px; gap: 10px; padding: 9px 12px; font-size: 12px; border-bottom: 1px solid var(--border-faint); align-items: center; } + .md-tx-row:last-child { border-bottom: 0; } + .md-tx-row.head { background: var(--black-alpha-3); font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; padding-top: 8px; padding-bottom: 8px; } + .md-tx-row .ts { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); } + .md-tx-row .proj { color: var(--accent-black); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .md-tx-row .type { color: var(--black-alpha-56); font-size: 11.5px; } + .md-tx-row .amt { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--accent-black); } + .md-tx-row .amt.pos { color: var(--accent-forest); } + .md-tx-row .amt.zero { color: var(--black-alpha-32); font-weight: 500; } + + .md-meta { font-size: 12px; color: var(--black-alpha-56); font-family: var(--font-mono); letter-spacing: .02em; padding-top: 14px; margin-top: 14px; border-top: 1px solid var(--border-faint); } + /* ─── 角色权限矩阵 ─── */ .perm-table { width: 100%; border-collapse: collapse; font-size: 12.5px; } .perm-table th, .perm-table td { padding: 8px 4px; border-bottom: 1px solid var(--border-faint); } @@ -320,26 +439,6 @@
-
-

额度预检规则

-
// 任一不通过即拦截
-
-
1个人日剩余 ≥ 任务预估 × 1.2
-
2个人月剩余 ≥ 同上
-
3团队月剩余 ≥ 同上
-
4团队总余额 ≥ 同上
-
-
- -
-

- - 失败不扣费 -

-
- 所有生成任务仅在用户 [ 通过 ] 时才扣费。失败 / 超时 / 重跑(旧版本作废)一律不扣。 -
-
@@ -351,12 +450,12 @@
-
邀请成员// 短信邀请 · 接受后自动加入团队
+
邀请成员// 邮件邀请 · 接受后自动加入团队
+ + +