AirShelf/电商AI平台/pipeline.html
iye cae935588b init: 电商AI平台 v2.1 静态设计稿
- 10 个页面 (工作台/项目/商品/流水线/资产/账户/创建向导)
- V2.1 Restraint 设计规范 (冷灰底 + #FA5D19 + 8px 圆角)
- 完整 design-system.html 组件库参考
- SVG line icon · stroke 1.5 全合规

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 17:55:11 +08:00

785 lines
52 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>补水面膜 · v3 · 流水线 · 流·Studio</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/restraint.css">
<style>
/* ─── Project header ─── */
.proj-head { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 22px; align-items: flex-start; }
.proj-head h1 { font-size: 20px; font-weight: 700; letter-spacing: -.012em; }
/* ─── Stepper ─── */
.stepper { display: flex; align-items: center; gap: 0; margin-bottom: 28px; padding: 14px 18px; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); position: relative; }
.stepper::before, .stepper::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='%23e8e8e8'%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; }
.stepper::before { top: -7px; left: -7px; }
.stepper::after { bottom: -7px; right: -7px; }
.stepper .corner-tr, .stepper .corner-bl { 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='%23e8e8e8'%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; }
.stepper .corner-tr { top: -7px; right: -7px; }
.stepper .corner-bl { bottom: -7px; left: -7px; }
.stage-step { display: flex; align-items: center; gap: 10px; padding: 6px 0; cursor: pointer; user-select: none; }
.stage-step .num { width: 26px; height: 26px; display: grid; place-items: center; font-family: var(--font-mono); font-size: 12px; font-weight: 600; border: 1px solid var(--border-faint); border-radius: var(--r-sm); background: var(--surface); color: var(--black-alpha-48); flex-shrink: 0; }
.stage-step.done .num { background: var(--accent-black); border-color: var(--accent-black); color: var(--accent-white); }
.stage-step.active .num { background: var(--heat); border-color: var(--heat); color: var(--accent-white); }
.stage-step.locked { opacity: .5; cursor: not-allowed; }
.stage-step .lbl { font-size: 13px; font-weight: 500; color: var(--black-alpha-56); }
.stage-step.active .lbl { color: var(--accent-black); font-weight: 600; }
.stage-step .st { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); margin-left: 4px; padding: 1px 6px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-sm); letter-spacing: .04em; }
.stage-step.active .st { background: var(--heat-12); color: var(--heat); border-color: var(--heat-20); }
.stage-line { flex: 1; height: 1px; background: var(--border-faint); margin: 0 14px; min-width: 30px; }
.stage-line.done { background: var(--accent-black); }
/* ─── Stage panes ─── */
.stage { display: none; }
.stage.active { display: block; }
/* Common pane */
.pane { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); }
.pane-h { display: flex; align-items: center; gap: 8px; padding: 14px 18px; border-bottom: 1px solid var(--border-faint); }
.pane-h strong { font-size: 14px; font-weight: 600; }
/* Stage foot */
.stage-foot { display: flex; justify-content: space-between; align-items: center; padding: 18px 0 0; margin-top: 18px; border-top: 1px solid var(--border-faint); }
.stage-foot .info { font-size: 12.5px; color: var(--black-alpha-56); }
.stage-foot .info .mono { font-family: var(--font-mono); color: var(--black-alpha-48); font-size: 11.5px; letter-spacing: .02em; }
/* === STAGE 1 · 脚本 === */
.stage-script { display: grid; grid-template-columns: 1fr 1.2fr; gap: 16px; min-height: 560px; }
.chat-pane { display: flex; flex-direction: column; }
.chat-body { padding: 16px 18px; flex: 1; overflow-y: auto; max-height: 460px; display: flex; flex-direction: column; gap: 14px; }
.msg .bubble { max-width: 90%; padding: 10px 14px; font-size: 13px; line-height: 1.6; border: 1px solid var(--border-faint); border-radius: var(--r-md); }
.msg.ai .bubble { background: var(--surface); }
.msg.user { display: flex; flex-direction: column; align-items: flex-end; }
.msg.user .bubble { background: var(--heat-12); color: var(--accent-black); border-color: var(--heat-20); }
.msg .time { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); margin-top: 4px; letter-spacing: .02em; }
.msg .actions { display: flex; gap: 6px; margin-top: 6px; }
.ai-avatar { width: 26px; height: 26px; background: var(--heat); color: var(--accent-white); display: grid; place-items: center; font-size: 11px; font-weight: 700; border: 1px solid var(--heat); border-radius: 50%; }
.del { text-decoration: line-through; color: var(--black-alpha-48); }
.ins { background: var(--forest-bg); color: var(--accent-forest); padding: 0 3px; }
.chat-input { padding: 14px 18px; border-top: 1px solid var(--border-faint); }
.shot-list { display: flex; flex-direction: column; }
.shots-body { padding: 12px 16px; flex: 1; overflow-y: auto; max-height: 540px; display: flex; flex-direction: column; gap: 10px; }
.shot-card { background: var(--background-base); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; }
.shot-card.highlight { border-color: var(--heat); background: var(--heat-12); }
.shot-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.shot-num { width: 22px; height: 22px; background: var(--accent-black); color: var(--accent-white); display: grid; place-items: center; font-family: var(--font-mono); font-size: 11px; font-weight: 700; border-radius: var(--r-sm); }
.shot-time { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); padding: 2px 6px; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-sm); }
.shot-row { display: grid; grid-template-columns: 36px 1fr; gap: 8px; padding: 4px 0; }
.shot-k { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); padding-top: 2px; letter-spacing: .04em; }
.shot-v { font-size: 12.5px; color: var(--accent-black); line-height: 1.55; }
.icon-mini-btn { width: 24px; height: 24px; display: grid; place-items: center; color: var(--black-alpha-48); background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-sm); cursor: pointer; font-size: 14px; }
.icon-mini-btn:hover { color: var(--heat); border-color: var(--heat); }
/* === STAGE 2 · 基础资产 === */
.stage-assets { display: grid; grid-template-columns: 200px 1fr; gap: 24px; }
.asset-side .ttab { padding: 10px 12px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 8px; border: 1px solid transparent; border-radius: var(--r-md); }
.asset-side .ttab:hover { background: var(--background-lighter); }
.asset-side .ttab.active { background: var(--heat-12); color: var(--heat); border-color: var(--heat-20); font-weight: 600; }
.asset-side .ttab .num { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); margin-left: auto; }
.asset-side .ttab.active .num { color: var(--heat); }
.asset-side .info { font-size: 12px; color: var(--black-alpha-48); padding: 14px 12px; line-height: 1.6; margin-top: 14px; border-top: 1px solid var(--border-faint); }
.asset-side .info strong { color: var(--black-alpha-56); display: block; }
.asset-side .info .mono { font-family: var(--font-mono); }
.asset-grid-2 { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.asset-card-2 { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); }
.asset-card-2 .thumb-2 { aspect-ratio: 1; }
.asset-card-2 .body-2 { padding: 12px 14px; }
.prompt-box { background: var(--background-base); border: 1px solid var(--border-faint); border-radius: var(--r-sm); padding: 10px 12px; font-size: 12px; color: var(--black-alpha-56); margin-top: 8px; line-height: 1.55; font-family: var(--font-mono); letter-spacing: .01em; }
.fail-icon { width: 28px; height: 28px; background: var(--accent-crimson); color: var(--accent-white); display: grid; place-items: center; font-weight: 700; font-size: 16px; border-radius: 50%; }
/* === STAGE 3 · 故事板 === */
.stage-storyboard { display: grid; grid-template-columns: 1.7fr 1fr; gap: 16px; align-items: start; }
.sb-canvas { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); }
.sb-row { display: grid; grid-template-columns: 60px 1fr 1fr; gap: 0; border-bottom: 1px solid var(--border-faint); }
.sb-row:last-child { border-bottom: 0; }
.sb-row.head { background: var(--background-lighter); font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; }
.sb-row.head > div { padding: 10px 14px; }
.sb-num { padding: 14px; font-family: var(--font-mono); font-size: 14px; font-weight: 700; color: var(--accent-black); border-right: 1px solid var(--border-faint); }
.sb-num .t { display: block; font-size: 10.5px; color: var(--black-alpha-48); font-weight: 400; margin-top: 4px; letter-spacing: .02em; }
.sb-img { padding: 10px; border-right: 1px solid var(--border-faint); }
.sb-img .placeholder { aspect-ratio: 16/9; }
.sb-text { padding: 14px; display: flex; flex-direction: column; gap: 6px; }
.sb-text .meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; }
.sb-text .dialog { font-size: 12.5px; color: var(--accent-black); line-height: 1.55; }
.sb-text .sfx { font-size: 11.5px; color: var(--black-alpha-48); }
.sb-side .pane { padding: 18px; }
.prompt-edit { background: var(--background-base); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; font-family: var(--font-mono); font-size: 11.5px; line-height: 1.7; color: var(--black-alpha-56); white-space: pre-wrap; min-height: 200px; outline: none; letter-spacing: .01em; }
.prompt-edit:focus { border-color: var(--heat); box-shadow: 0 0 0 3px var(--heat-12); }
.asset-tag { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-pill); font-size: 11.5px; }
.asset-tag .dotc { width: 14px; height: 14px; background: var(--surface); border: 1px solid var(--border-faint); border-radius: 50%; }
/* === STAGE 4 · 视频片段 === */
.queue-bar { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); margin-bottom: 18px; }
.queue-bar .bar-wrap { flex: 1; height: 6px; background: var(--background-lighter); overflow: hidden; }
.queue-bar .bar-wrap > span { display: block; height: 100%; background: var(--heat); }
.video-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.video-card { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); }
.video-thumb { aspect-ratio: 9/16; max-height: 320px; position: relative; border-radius: var(--r-md) var(--r-md) 0 0; }
.video-thumb .play { position: absolute; inset: 0; display: grid; place-items: center; background: rgba(0,0,0,0.05); cursor: pointer; opacity: 0; transition: opacity .15s; }
.video-thumb:hover .play { opacity: 1; }
.video-thumb .btn-play { width: 36px; height: 36px; background: rgba(0,0,0,.7); color: var(--accent-white); border-radius: 50%; display: grid; place-items: center; }
.video-card .body { padding: 10px 12px; }
/* === STAGE 5 · 编辑器 === */
.editor { display: grid; grid-template-columns: 1fr 280px; grid-template-rows: 1fr auto; gap: 0; height: 580px; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); }
.editor-preview { padding: 16px; border-right: 1px solid var(--border-faint); border-bottom: 1px solid var(--border-faint); display: flex; flex-direction: column; gap: 12px; }
.editor-preview .canvas { flex: 1; aspect-ratio: 9/16; max-height: 380px; margin: 0 auto; background:
repeating-linear-gradient(135deg, rgba(0,0,0,0.03) 0 1px, transparent 1px 12px),
var(--background-lighter);
border: 1px solid var(--border-faint);
border-radius: var(--r-md);
display: grid; place-items: center;
color: var(--black-alpha-48);
font-family: var(--font-mono);
font-size: 12px; }
.editor-preview .controls { display: flex; align-items: center; gap: 8px; justify-content: center; }
.ctl-btn { width: 36px; height: 36px; border: 1px solid var(--border-faint); background: var(--surface); color: var(--black-alpha-56); border-radius: var(--r-md); display: grid; place-items: center; cursor: pointer; transition: background var(--t-base), border-color var(--t-base), color var(--t-base); }
.ctl-btn:hover { color: var(--heat); border-color: var(--heat-40); background: var(--heat-12); }
.editor-props { padding: 16px; border-bottom: 1px solid var(--border-faint); overflow-y: auto; }
.props-tabs { display: flex; gap: 0; margin-bottom: 14px; border-bottom: 1px solid var(--border-faint); }
.props-tabs > div { padding: 8px 12px; font-size: 12.5px; color: var(--black-alpha-56); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.props-tabs > div.active { color: var(--heat); border-bottom-color: var(--heat); font-weight: 600; }
.style-swatch { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.swatch-card { padding: 10px; border: 1px solid var(--border-faint); border-radius: var(--r-md); cursor: pointer; }
.swatch-card:hover { background: var(--background-lighter); }
.swatch-card.selected { border-color: var(--heat); background: var(--heat-12); }
.swatch-card .demo { font-size: 12px; padding: 6px 8px; background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-sm); margin-bottom: 4px; text-align: center; }
.swatch-card .demo.b { background: var(--accent-black); color: var(--accent-white); font-family: serif; }
.swatch-card .demo.c { color: var(--heat); -webkit-text-stroke: 0.5px var(--accent-black); }
.swatch-card .demo.d { background: var(--accent-honey); color: var(--accent-black); font-weight: 700; }
.swatch-card .nm { font-size: 11px; color: var(--black-alpha-48); font-family: var(--font-mono); letter-spacing: .02em; }
.props-row { display: flex; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border-faint); font-size: 12.5px; }
.props-row:last-child { border-bottom: 0; }
.props-row .k { color: var(--black-alpha-48); flex: 1; font-family: var(--font-mono); font-size: 11px; letter-spacing: .02em; }
.input-mini { width: 90px; padding: 0 10px; height: 28px; font-size: 12px; border-radius: var(--r-md); background: var(--surface); border: 1px solid var(--black-alpha-12); }
.timeline { grid-column: 1 / -1; padding: 14px 16px; background: var(--background-base); }
.tl-toolbar { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--border-faint); }
.tl-ruler { display: grid; grid-template-columns: 80px 1fr; align-items: center; padding: 4px 0; font-size: 10.5px; }
.tl-ruler .l { font-family: var(--font-mono); color: var(--black-alpha-48); padding-left: 4px; }
.tl-ruler .ticks { display: flex; justify-content: space-between; font-family: var(--font-mono); color: var(--black-alpha-48); padding: 0 4px; letter-spacing: .04em; }
.tl-track { display: grid; grid-template-columns: 80px 1fr; align-items: center; gap: 0; padding: 6px 0; }
.tl-track .label { font-size: 11.5px; color: var(--black-alpha-56); display: flex; align-items: center; gap: 6px; padding-left: 4px; }
.tl-track .label .dot { width: 8px; height: 8px; }
.tl-track .lane { display: flex; gap: 2px; height: 30px; position: relative; }
.clip { padding: 0 8px; font-size: 11px; display: flex; align-items: center; cursor: pointer; overflow: hidden; white-space: nowrap; user-select: none; }
.clip.video { background: var(--heat-12); border: 1px solid var(--heat-40); color: var(--heat); }
.clip.video.selected { background: var(--heat); color: var(--accent-white); border-color: var(--heat); }
.clip.subtitle { background: var(--forest-bg); border: 1px solid var(--forest-bd); color: var(--accent-forest); }
.clip.bgm { background: rgba(144, 97, 255, 0.10); border: 1px solid rgba(144, 97, 255, 0.30); color: var(--accent-amethyst); }
.clip .num { font-family: var(--font-mono); font-weight: 700; margin-right: 6px; opacity: .7; }
.playhead { position: absolute; top: -16px; bottom: -54px; width: 1px; background: var(--heat); pointer-events: none; }
.playhead::before { content: ''; position: absolute; top: -2px; left: -4px; width: 9px; height: 9px; background: var(--heat); transform: rotate(45deg); }
</style>
</head>
<body>
<div id="page">
<!-- Project header -->
<div class="proj-head">
<div style="display:flex; gap:14px; align-items:center;">
<div class="placeholder" style="width:42px;height:54px;"><span class="ph-frame">9:16</span></div>
<div>
<div style="display:flex; gap:8px; align-items:center;">
<h1>补水面膜 · 痛点种草 · v3</h1>
<span class="pill info"><span class="dot"></span>进行中</span>
</div>
<div class="muted-2 mono" style="font-size:11.5px; margin-top:4px; letter-spacing:.02em;">// 透真补水面膜 · AI 全生 · 6 镜 · 0-15s · 9:16</div>
</div>
</div>
<div class="hstack">
<button class="btn btn-ghost btn-sm" onclick="Shell.toast('分享', '/projects/p3/share')">分享</button>
<button class="btn btn-sm" onclick="Shell.toast('已复制项目', '补水面膜 · v4')">复制项目</button>
<button class="btn btn-sm" onclick="Shell.toast('归档项目', '/projects/p3/archive')">归档</button>
</div>
</div>
<!-- Stage stepper -->
<div class="stepper">
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
<a class="stage-step done" data-stage="1" href="#stage-1"><div class="num">1</div><div class="lbl">脚本</div><div class="st">已确认</div></a>
<div class="stage-line done"></div>
<a class="stage-step done" data-stage="2" href="#stage-2"><div class="num">2</div><div class="lbl">基础资产</div><div class="st">已确认</div></a>
<div class="stage-line done"></div>
<a class="stage-step active" data-stage="3" href="#stage-3"><div class="num">3</div><div class="lbl">故事板</div><div class="st">待确认</div></a>
<div class="stage-line"></div>
<a class="stage-step" data-stage="4" href="#stage-4"><div class="num">4</div><div class="lbl">视频片段</div><div class="st">未开始</div></a>
<div class="stage-line"></div>
<a class="stage-step" data-stage="5" href="#stage-5"><div class="num">5</div><div class="lbl">拼接导出</div><div class="st">未开始</div></a>
</div>
<!-- ============= STAGE 1 · 脚本 ============= -->
<section class="stage" data-stage-pane="1">
<div class="stage-script">
<div class="pane chat-pane">
<div class="pane-h">
<div class="ai-avatar">AI</div>
<strong>脚本助手</strong>
<span class="muted-2 mono" style="font-size:11px;">· GPT-4o</span>
<span class="spacer"></span>
<button class="btn btn-ghost btn-sm" onclick="Shell.toast('已清空对话')">清空对话</button>
</div>
<div class="chat-body">
<div class="msg ai">
<div style="display:flex; gap:10px; align-items:flex-start;">
<div class="ai-avatar" style="margin-top:2px;">AI</div>
<div class="bubble">根据 <strong>透真补水面膜</strong> 和"痛点种草"风格,我先生成了一版 6 镜的脚本,主线是"加班党的深夜急救"。你可以直接编辑右侧的镜头,或者让我重写某一镜。</div>
</div>
<div class="time" style="margin-left:36px;">14:02</div>
</div>
<div class="msg user">
<div class="bubble">第 4 镜对白太硬了,能不能更口语化?</div>
<div class="time">14:05</div>
</div>
<div class="msg ai">
<div style="display:flex; gap:10px; align-items:flex-start;">
<div class="ai-avatar" style="margin-top:2px;">AI</div>
<div>
<div class="bubble">把 "<span class="del">补水力极强,锁水持久</span>" 改成 "<span class="ins">真的,敷完第二天起来脸是软的,不是绷着的</span>",更像真实分享。已替换右侧第 4 镜,确认要的话点 [接受]。</div>
<div class="actions">
<button class="btn btn-sm" onclick="Shell.toast('已接受改动', 'shot-4')">接受</button>
<button class="btn btn-ghost btn-sm" onclick="Shell.toast('再来一版')">再来一版</button>
</div>
</div>
</div>
<div class="time" style="margin-left:36px;">14:05</div>
</div>
</div>
<div class="chat-input">
<textarea class="textarea" placeholder="对脚本的修改诉求 · 比如:让第 5 镜更夸张一点、整体加 1 镜结尾……" rows="2"></textarea>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm">↻ 整体重写</button>
<span class="spacer"></span>
<button class="btn btn-primary" onclick="Shell.toast('已发送', 'POST /chat')">发送 ⌘↵</button>
</div>
</div>
</div>
<div class="pane shot-list">
<div class="pane-h">
<strong>镜头脚本</strong>
<span class="muted-2 mono" style="font-size:11px;">· 6 镜 · 0-15s</span>
<span class="spacer"></span>
<button class="btn btn-ghost btn-sm" onclick="Shell.toast('已加一镜')">+ 加一镜</button>
</div>
<div class="shots-body">
<div class="shot-card">
<div class="shot-head"><div class="shot-num">1</div><div class="shot-time">0-2s</div><span class="spacer"></span><button class="icon-mini-btn" title="重写"></button></div>
<div class="shot-row"><span class="shot-k">画面</span><div class="shot-v">深夜的办公桌,电脑屏幕亮着,女主对着镜子叹气,皮肤干燥起皮特写。</div></div>
<div class="shot-row"><span class="shot-k">对白</span><div class="shot-v">(叹气)"加班三天,脸已经不能看了……"</div></div>
</div>
<div class="shot-card">
<div class="shot-head"><div class="shot-num">2</div><div class="shot-time">2-5s</div><span class="spacer"></span><button class="icon-mini-btn"></button></div>
<div class="shot-row"><span class="shot-k">画面</span><div class="shot-v">女主从抽屉拿出补水面膜,包装特写,光线柔和。</div></div>
<div class="shot-row"><span class="shot-k">对白</span><div class="shot-v">"还好我有这个 —— 透真玻尿酸面膜。"</div></div>
</div>
<div class="shot-card">
<div class="shot-head"><div class="shot-num">3</div><div class="shot-time">5-8s</div><span class="spacer"></span><button class="icon-mini-btn"></button></div>
<div class="shot-row"><span class="shot-k">画面</span><div class="shot-v">面膜布展开,30g 精华液从布上滴落特写,慢镜头。</div></div>
<div class="shot-row"><span class="shot-k">对白</span><div class="shot-v">"30g 精华液,一片顶三片的量。"</div></div>
</div>
<div class="shot-card highlight">
<div class="shot-head"><div class="shot-num">4</div><div class="shot-time">8-11s</div><span class="pill info" style="margin-left:6px;"><span class="dot"></span>刚改</span><span class="spacer"></span><button class="icon-mini-btn"></button></div>
<div class="shot-row"><span class="shot-k">画面</span><div class="shot-v">女主敷面膜,闭眼平躺,灯光暖。床头闹钟显示 23:41。</div></div>
<div class="shot-row"><span class="shot-k">对白</span><div class="shot-v">"真的,敷完第二天起来脸是软的,不是绷着的。"</div></div>
</div>
<div class="shot-card">
<div class="shot-head"><div class="shot-num">5</div><div class="shot-time">11-13s</div><span class="spacer"></span><button class="icon-mini-btn"></button></div>
<div class="shot-row"><span class="shot-k">画面</span><div class="shot-v">第二天早上,女主对镜化妆,皮肤透亮,状态饱满。</div></div>
<div class="shot-row"><span class="shot-k">对白</span><div class="shot-v">"早上化妆都能看出来,不假吹。"</div></div>
</div>
<div class="shot-card">
<div class="shot-head"><div class="shot-num">6</div><div class="shot-time">13-15s</div><span class="spacer"></span><button class="icon-mini-btn"></button></div>
<div class="shot-row"><span class="shot-k">画面</span><div class="shot-v">面膜产品大图,价格标签 "5 片装 ¥39.9",购物车浮动按钮。</div></div>
<div class="shot-row"><span class="shot-k">对白</span><div class="shot-v">"618 五片才 39.9,囤起来。"</div></div>
</div>
</div>
</div>
</div>
<div class="stage-foot">
<div class="info"><span class="mono">[ LLM 用量 ~2.4k tokens · ¥0.04 ]</span></div>
<div class="hstack">
<button class="btn" onclick="Shell.toast('重新生成', 'POST /script/regen')"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12a8 8 0 0 1 14-5.5L21 9"/><path d="M21 4v5h-5"/><path d="M20 12a8 8 0 0 1-14 5.5L3 15"/><path d="M3 20v-5h5"/></svg> 重新生成全部</button>
<button class="btn btn-primary btn-lg" onclick="location.hash='#stage-2'">确认脚本,进入下一步 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg></button>
</div>
</div>
</section>
<!-- ============= STAGE 2 · 基础资产 ============= -->
<section class="stage" data-stage-pane="2">
<div class="stage-assets">
<div class="asset-side">
<div class="ttab active"><span>人物</span><span class="num">2/2</span></div>
<div class="ttab"><span>场景</span><span class="num">3/3</span></div>
<div class="ttab"><span>商品</span><span class="num">3 张</span></div>
<div class="info">
基础资产是后续故事板的素材。生成后可以单独修改提示词重跑,或上传你已有的图替换。
<br><br>
<strong class="mono">// 人物 +¥0.20/张</strong>
<strong class="mono">// 场景 +¥0.15/张</strong>
<span style="color:var(--black-alpha-48);">商品图无成本(直接复用商品库)</span>
</div>
</div>
<div>
<div class="hstack" style="margin-bottom:14px;">
<h3 style="font-size:15px; font-weight:600;">人物 · 2 个</h3>
<span class="spacer"></span>
<button class="btn btn-ghost btn-sm">从我的资产库选</button>
<button class="btn btn-sm" onclick="Shell.toast('+ 新增人物')">+ 新增人物</button>
</div>
<div class="asset-grid-2">
<div class="asset-card-2">
<div class="placeholder thumb-2"><span class="ph-frame">林夕 · 都市白领</span></div>
<div class="body-2">
<div class="hstack"><strong style="font-size:13.5px;">主角 · 林夕</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>已确认</span></div>
<div class="prompt-box">25-30 岁都市白领,长发,穿宽松米色家居服,温柔但带点疲倦感,肤色偏黄/略干。</div>
<div class="hstack" style="margin-top:10px;">
<button class="btn btn-ghost btn-sm">改提示词</button>
<button class="btn btn-ghost btn-sm">↻ 重跑</button>
<span class="spacer"></span>
<button class="btn btn-ghost btn-sm">⤓ 上传替换</button>
</div>
</div>
</div>
<div class="asset-card-2">
<div class="placeholder thumb-2">
<div style="display:flex; flex-direction:column; gap:8px; align-items:center;">
<div class="spinner"></div>
<span class="ph-frame">生成中 · 约 8s</span>
</div>
</div>
<div class="body-2">
<div class="hstack"><strong style="font-size:13.5px;">朋友/同事 · 阿楠</strong><span class="spacer"></span><span class="pill info"><span class="dot"></span>生成中</span></div>
<div class="prompt-box">25-30 岁同龄女性,短发,穿白色衬衫,妆容精致皮肤好,作为对比。</div>
<div class="hstack" style="margin-top:10px;">
<button class="btn btn-ghost btn-sm" disabled>改提示词</button>
<button class="btn btn-ghost btn-sm" disabled>↻ 重跑</button>
<span class="spacer"></span>
<button class="btn btn-ghost btn-sm" disabled>⤓ 上传替换</button>
</div>
</div>
</div>
</div>
<div class="hstack" style="margin:28px 0 14px;">
<h3 style="font-size:15px; font-weight:600;">场景 · 3 个</h3>
<span class="spacer"></span>
<button class="btn btn-ghost btn-sm">从我的资产库选</button>
<button class="btn btn-sm">+ 新增场景</button>
</div>
<div class="asset-grid-2">
<div class="asset-card-2">
<div class="placeholder thumb-2"><span class="ph-frame">深夜办公桌</span></div>
<div class="body-2">
<div class="hstack"><strong style="font-size:13.5px;">深夜办公桌</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>已确认</span></div>
<div class="prompt-box">深夜居家办公环境,木质书桌,台灯暖光,电脑屏幕亮着,背景虚化。</div>
</div>
</div>
<div class="asset-card-2">
<div class="placeholder thumb-2"><span class="ph-frame">床头特写</span></div>
<div class="body-2">
<div class="hstack"><strong style="font-size:13.5px;">卧室床头</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>已确认</span></div>
<div class="prompt-box">米白色床品,木质床头柜,闹钟显示晚间时间,氛围温柔安静。</div>
</div>
</div>
<div class="asset-card-2">
<div class="placeholder thumb-2">
<div style="display:flex; flex-direction:column; gap:6px; align-items:center;">
<div class="fail-icon">!</div>
<span class="ph-frame">生成失败</span>
</div>
</div>
<div class="body-2">
<div class="hstack"><strong style="font-size:13.5px;">通勤地铁</strong><span class="spacer"></span><span class="pill err"><span class="dot"></span>失败</span></div>
<div class="prompt-box">早高峰地铁车厢,光线偏冷,年轻通勤族,氛围紧张。</div>
<div class="muted-2" style="font-size:12px; margin-top:6px; display:flex; align-items:center; gap:6px;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent-crimson); flex-shrink:0;"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>提示词被安全审核拦截,请调整后重试(不扣费)</div>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-sm">改提示词</button>
<button class="btn btn-ghost btn-sm">↻ 重跑</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="stage-foot">
<div class="info"><span class="mono">[ 已确认 ¥0.85 · 待生成 ¥0.20 · 失败 ¥0(不扣) ]</span></div>
<div class="hstack">
<button class="btn" onclick="location.hash='#stage-1'">← 返回脚本</button>
<button class="btn btn-primary btn-lg" disabled>1 个资产生成中 · 等待</button>
</div>
</div>
</section>
<!-- ============= STAGE 3 · 故事板 ============= -->
<section class="stage active" data-stage-pane="3">
<div class="stage-storyboard">
<div class="sb-canvas">
<div class="sb-row head">
<div>镜号</div><div>画面</div><div>机位 / 对白 / 音效</div>
</div>
<div class="sb-row">
<div class="sb-num">1<span class="t">0-2s</span></div>
<div class="sb-img"><div class="placeholder"><span class="ph-frame">深夜办公桌 · 女主对镜叹气 · 暖光</span></div></div>
<div class="sb-text">
<div class="meta">中景 / 固定机位</div>
<div class="dialog">"加班三天,脸已经不能看了……"</div>
<div class="sfx">SFX:键盘声 + 远处空调嗡鸣</div>
</div>
</div>
<div class="sb-row">
<div class="sb-num">2<span class="t">2-5s</span></div>
<div class="sb-img"><div class="placeholder"><span class="ph-frame">面膜包装特写 · 抽屉光线柔和</span></div></div>
<div class="sb-text">
<div class="meta">特写 / 缓推</div>
<div class="dialog">"还好我有这个 —— 透真玻尿酸面膜。"</div>
<div class="sfx">SFX:抽屉滑动声</div>
</div>
</div>
<div class="sb-row">
<div class="sb-num">3<span class="t">5-8s</span></div>
<div class="sb-img"><div class="placeholder"><span class="ph-frame">面膜布展开 · 30g 精华滴落 · 慢动作</span></div></div>
<div class="sb-text">
<div class="meta">微距 / 慢镜头</div>
<div class="dialog">"30g 精华液,一片顶三片的量。"</div>
<div class="sfx">SFX:水滴慢速回弹</div>
</div>
</div>
<div class="sb-row">
<div class="sb-num">4<span class="t">8-11s</span></div>
<div class="sb-img"><div class="placeholder"><span class="ph-frame">女主敷面膜平躺 · 闹钟 23:41</span></div></div>
<div class="sb-text">
<div class="meta">中近景 / 固定</div>
<div class="dialog">"敷完第二天起来脸是软的,不是绷着的。"</div>
<div class="sfx">SFX:呼吸声 + 窗外风声</div>
</div>
</div>
<div class="sb-row">
<div class="sb-num">5<span class="t">11-13s</span></div>
<div class="sb-img"><div class="placeholder"><span class="ph-frame">早晨化妆台 · 女主对镜上妆 · 透亮</span></div></div>
<div class="sb-text">
<div class="meta">中景 / 固定</div>
<div class="dialog">"早上化妆都能看出来,不假吹。"</div>
<div class="sfx">SFX:化妆刷轻扫声</div>
</div>
</div>
<div class="sb-row">
<div class="sb-num">6<span class="t">13-15s</span></div>
<div class="sb-img"><div class="placeholder"><span class="ph-frame">产品大图 · 价格 5片¥39.9 · 购物车</span></div></div>
<div class="sb-text">
<div class="meta">产品定格 / 静止</div>
<div class="dialog">"618 五片才 39.9,囤起来。"</div>
<div class="sfx">SFX:清脆叮咚音效</div>
</div>
</div>
</div>
<div class="sb-side">
<div class="pane">
<div class="hstack" style="margin-bottom:10px;">
<strong style="font-size:14px;">故事板</strong>
<span class="spacer"></span>
<span class="pill ok"><span class="dot"></span>已生成</span>
</div>
<div class="muted-2" style="font-size:12px; line-height:1.55; margin-bottom:14px;">
整张故事板由 image-2 一次性输出,包含画面 + 镜头说明。如需修改请编辑下方提示词整张重跑(不能局部改)。
</div>
<div class="muted mono" style="font-size:11px; font-weight:500; margin-bottom:6px; letter-spacing:.04em;">// 视觉提示词</div>
<div class="prompt-edit" contenteditable="true">风格:日系小清新短视频,暖色调,午夜→清晨光线变化。
镜头列表(6 镜,0-15s):
1. 中景 · 深夜办公桌女主对镜叹气
2. 特写 · 面膜包装从抽屉拿出
3. 微距 · 面膜布展开 + 精华液滴落慢镜
4. 中近景 · 女主敷面膜平躺,床头闹钟 23:41
5. 中景 · 早晨化妆台 + 女主透亮上妆
6. 产品定格 · 面膜盒 + 价格标签 ¥39.9
人物:林夕(参考 R1)
场景:深夜办公桌(S1) + 卧室床头(S2)
要求:表格化布局,每镜含画面 + 文字说明</div>
<div class="hstack" style="margin-top:12px;">
<button class="btn btn-sm" onclick="Shell.toast('整张重跑', 'POST /storyboard/regen ¥0.45')">↻ 整张重跑</button>
<span class="spacer"></span>
<span class="muted-2 mono" style="font-size:11px;">~¥0.45</span>
</div>
<div class="divider"></div>
<div class="muted mono" style="font-size:11px; font-weight:500; margin-bottom:8px; letter-spacing:.04em;">// 绑定的资产</div>
<div style="display:flex; gap:6px; flex-wrap:wrap;">
<span class="asset-tag"><span class="dotc"></span>林夕(人物)</span>
<span class="asset-tag"><span class="dotc"></span>深夜办公桌(场景)</span>
<span class="asset-tag"><span class="dotc"></span>卧室床头(场景)</span>
<span class="asset-tag"><span class="dotc"></span>面膜盒(商品)</span>
</div>
</div>
</div>
</div>
<div class="stage-foot">
<div class="info"><span class="mono">[ image-2 一次 ¥0.45 · 累计 ¥1.50 ]</span></div>
<div class="hstack">
<button class="btn" onclick="location.hash='#stage-2'"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> 返回资产</button>
<button class="btn btn-primary btn-lg" onclick="location.hash='#stage-4'">确认故事板,开始生成视频片段 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg></button>
</div>
</div>
</section>
<!-- ============= STAGE 4 · 视频片段 ============= -->
<section class="stage" data-stage-pane="4">
<div class="queue-bar">
<div>
<div style="font-size:14px; font-weight:600;">视频生成中 · 4 / 6 完成</div>
<div class="muted-2 mono" style="font-size:11px; margin-top:3px; letter-spacing:.02em;">// 每镜 Seedance 调用 ~30s · 预计还需 1 分钟</div>
</div>
<div class="bar-wrap"><span style="width:67%"></span></div>
<span class="muted mono" style="font-size:12px;">67%</span>
<button class="btn btn-sm" onclick="Shell.toast('全部重跑', 'POST /video/regen-all')">↻ 全部重跑</button>
</div>
<div class="video-grid">
<div class="video-card">
<div class="placeholder video-thumb">
<span class="ph-frame">镜 1 · 0-2s</span>
<div class="play"><div class="btn-play"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M5 4l6 4-6 4z" fill="currentColor"/></svg></div></div>
</div>
<div class="body">
<div class="hstack"><strong style="font-size:13px;">镜 1 · 深夜办公桌</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>完成</span></div>
<div class="muted-2 mono" style="font-size:11px; margin-top:4px;">2.0s · 1080×1920 · ¥0.18</div>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm">↻ 重跑</button>
<button class="btn btn-ghost btn-sm">⤓ 下载</button>
</div>
</div>
</div>
<div class="video-card">
<div class="placeholder video-thumb">
<span class="ph-frame">镜 2 · 2-5s</span>
<div class="play"><div class="btn-play"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M5 4l6 4-6 4z" fill="currentColor"/></svg></div></div>
</div>
<div class="body">
<div class="hstack"><strong style="font-size:13px;">镜 2 · 面膜包装</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>完成</span></div>
<div class="muted-2 mono" style="font-size:11px; margin-top:4px;">3.0s · 1080×1920 · ¥0.22</div>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm">↻ 重跑</button>
<button class="btn btn-ghost btn-sm">⤓ 下载</button>
</div>
</div>
</div>
<div class="video-card">
<div class="placeholder video-thumb">
<span class="ph-frame">镜 3 · 5-8s</span>
<div class="play"><div class="btn-play"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M5 4l6 4-6 4z" fill="currentColor"/></svg></div></div>
</div>
<div class="body">
<div class="hstack"><strong style="font-size:13px;">镜 3 · 精华液微距</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>完成</span></div>
<div class="muted-2 mono" style="font-size:11px; margin-top:4px;">3.0s · 1080×1920 · ¥0.22</div>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm">↻ 重跑</button>
<button class="btn btn-ghost btn-sm">⤓ 下载</button>
</div>
</div>
</div>
<div class="video-card">
<div class="placeholder video-thumb">
<span class="ph-frame">镜 4 · 8-11s</span>
<div class="play"><div class="btn-play"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M5 4l6 4-6 4z" fill="currentColor"/></svg></div></div>
</div>
<div class="body">
<div class="hstack"><strong style="font-size:13px;">镜 4 · 敷面膜平躺</strong><span class="spacer"></span><span class="pill ok"><span class="dot"></span>完成</span></div>
<div class="muted-2 mono" style="font-size:11px; margin-top:4px;">3.0s · 1080×1920 · ¥0.22</div>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm">↻ 重跑</button>
<button class="btn btn-ghost btn-sm">⤓ 下载</button>
</div>
</div>
</div>
<div class="video-card">
<div class="placeholder video-thumb">
<div style="display:flex; flex-direction:column; gap:8px; align-items:center;">
<div class="spinner"></div>
<span class="ph-frame">镜 5 · 生成中 18s</span>
</div>
</div>
<div class="body">
<div class="hstack"><strong style="font-size:13px;">镜 5 · 化妆台</strong><span class="spacer"></span><span class="pill info"><span class="dot"></span>生成中</span></div>
<div class="muted-2 mono" style="font-size:11px; margin-top:4px;">2.0s · 排队中 · ~¥0.18</div>
<div class="hstack" style="margin-top:8px;">
<button class="btn btn-ghost btn-sm" disabled>↻ 重跑</button>
<button class="btn btn-ghost btn-sm" disabled>⤓ 下载</button>
</div>
</div>
</div>
<div class="video-card">
<div class="placeholder video-thumb"><span class="ph-frame">镜 6 · 排队</span></div>
<div class="body">
<div class="hstack"><strong style="font-size:13px;">镜 6 · 产品定格</strong><span class="spacer"></span><span class="pill neutral"><span class="dot"></span>排队中</span></div>
<div class="muted-2 mono" style="font-size:11px; margin-top:4px;">2.0s · 等待中 · ~¥0.18</div>
</div>
</div>
</div>
<div class="stage-foot">
<div class="info"><span class="mono">[ 已确认 ¥0.84 · 待生成 ¥0.36 · 累计 ¥2.34 ]</span></div>
<div class="hstack">
<button class="btn" onclick="location.hash='#stage-3'">← 返回故事板</button>
<button class="btn btn-primary btn-lg" disabled>2 镜生成中 · 等待</button>
</div>
</div>
</section>
<!-- ============= STAGE 5 · 拼接编辑器 ============= -->
<section class="stage" data-stage-pane="5">
<div class="editor">
<div class="editor-preview">
<div class="canvas">9:16 预览 · 1080×1920</div>
<div class="controls">
<button class="ctl-btn" title="上一帧"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M3 3v10l4-5zM9 3v10l4-5z" fill="currentColor"/></svg></button>
<button class="ctl-btn" title="播放" onclick="Shell.toast('播放', '00:08.42 / 00:15.00')"><svg width="16" height="16" viewBox="0 0 16 16"><path d="M5 4l7 4-7 4z" fill="currentColor"/></svg></button>
<button class="ctl-btn" title="下一帧"><svg width="14" height="14" viewBox="0 0 16 16"><path d="M13 3v10l-4-5zM7 3v10l-4-5z" fill="currentColor"/></svg></button>
<span class="muted mono" style="font-size:12px; margin-left:8px;">00:08.42 / 00:15.00</span>
</div>
</div>
<div class="editor-props">
<div class="props-tabs">
<div class="active">字幕</div>
<div>转场</div>
<div>BGM</div>
</div>
<div class="muted mono" style="font-size:11px; font-weight:500; margin-bottom:8px; letter-spacing:.04em;">// 字幕样式</div>
<div class="style-swatch">
<div class="swatch-card selected"><div class="demo">真实分享</div><div class="nm">朴素白底</div></div>
<div class="swatch-card"><div class="demo b">真实分享</div><div class="nm">影视黑底</div></div>
<div class="swatch-card"><div class="demo c">真实分享</div><div class="nm">手写描边</div></div>
<div class="swatch-card"><div class="demo d">真实分享</div><div class="nm">综艺暖黄</div></div>
</div>
<div class="divider"></div>
<div class="muted mono" style="font-size:11px; font-weight:500; margin-bottom:8px; letter-spacing:.04em;">// 当前选中(镜 4)</div>
<div class="props-row"><span class="k">起始</span><input class="input-mini" value="00:08.00"></div>
<div class="props-row"><span class="k">时长</span><input class="input-mini" value="3.00s"></div>
<div class="props-row"><span class="k">音量</span><input class="input-mini" value="100"></div>
<div class="props-row"><span class="k">速度</span><input class="input-mini" value="1.0x"></div>
<div class="props-row"><span class="k">入场</span><span class="mono" style="font-size:11.5px;">交叉淡化</span></div>
<div class="divider"></div>
<div class="muted mono" style="font-size:11px; font-weight:500; margin-bottom:8px; letter-spacing:.04em;">// BGM</div>
<div class="props-row" style="border-bottom:0;">
<span style="font-size:12px; flex:1;">温柔治愈钢琴 · 0:42</span>
<button class="btn btn-ghost btn-sm">替换</button>
</div>
</div>
<div class="timeline">
<div class="tl-toolbar">
<button class="btn btn-ghost btn-sm"></button>
<button class="btn btn-ghost btn-sm"></button>
<span class="muted-2" style="font-size:12px;">|</span>
<button class="btn btn-ghost btn-sm">分割</button>
<button class="btn btn-ghost btn-sm">复制</button>
<button class="btn btn-ghost btn-sm">删除</button>
<span class="spacer"></span>
<span class="muted mono" style="font-size:11px;">缩放</span>
<input type="range" min="50" max="200" value="100" style="width:120px;">
</div>
<div class="tl-ruler">
<div class="l">// time</div>
<div class="ticks">
<span>0s</span><span>2s</span><span>5s</span><span>8s</span><span>11s</span><span>13s</span><span>15s</span>
</div>
</div>
<div class="tl-track">
<div class="label"><span class="dot" style="background:var(--heat);"></span>视频</div>
<div class="lane">
<div class="clip video" style="flex:2;"><span class="num">1</span> 深夜办公桌</div>
<div class="clip video" style="flex:3;"><span class="num">2</span> 面膜包装</div>
<div class="clip video" style="flex:3;"><span class="num">3</span> 精华液微距</div>
<div class="clip video selected" style="flex:3;"><span class="num">4</span> 敷面膜平躺</div>
<div class="clip video" style="flex:2;"><span class="num">5</span> 化妆台</div>
<div class="clip video" style="flex:2;"><span class="num">6</span> 产品定格</div>
</div>
</div>
<div class="tl-track">
<div class="label"><span class="dot" style="background:var(--accent-forest);"></span>字幕</div>
<div class="lane" style="position:relative;">
<div class="clip subtitle" style="flex:2;">加班三天 脸已经不能看了…</div>
<div class="clip subtitle" style="flex:3;">还好我有这个 透真玻尿酸面膜</div>
<div class="clip subtitle" style="flex:3;">30g 精华 一片顶三片</div>
<div class="clip subtitle" style="flex:3;">敷完起来脸是软的</div>
<div class="clip subtitle" style="flex:2;">化妆都能看出来</div>
<div class="clip subtitle" style="flex:2;">5 片 ¥39.9 囤起来</div>
<div class="playhead" style="left:56%;"></div>
</div>
</div>
<div class="tl-track">
<div class="label"><span class="dot" style="background:var(--accent-amethyst);"></span>BGM</div>
<div class="lane">
<div class="clip bgm" style="flex:15;">温柔治愈钢琴 · 0:42(循环 1 次,淡入淡出)</div>
</div>
</div>
</div>
</div>
<div class="stage-foot">
<div class="info"><span class="mono">[ 合成预估 ~30s · 不消耗 token ]</span></div>
<div class="hstack">
<button class="btn" onclick="location.hash='#stage-4'"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> 返回片段</button>
<button class="btn" onclick="Shell.toast('已保存草稿', '/projects/p3/draft')">保存草稿</button>
<button class="btn btn-primary btn-lg" onclick="Shell.toast('开始导出', 'POST /export · 1080P 9:16')">导出 MP4 · 1080P 9:16 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v12m0 0l-5-5m5 5l5-5M4 20h16"/></svg></button>
</div>
</div>
</section>
</div>
<script src="assets/shell.js"></script>
<script>
Shell.render({
active: 'projects',
crumbs: [{ label: '工作台', href: 'index.html' }, { label: '视频项目', href: 'projects.html' }, { label: '补水面膜 · v3' }]
});
// hash routing
function activateStage(n) {
document.querySelectorAll('.stage').forEach(s => s.classList.remove('active'));
document.querySelector(`[data-stage-pane="${n}"]`)?.classList.add('active');
document.querySelectorAll('.stage-step').forEach(s => {
s.classList.remove('active');
if (+s.dataset.stage === +n) s.classList.add('active');
});
// update top breadcrumb fragment indicator with shell.toast
const stageNames = { 1:'脚本', 2:'基础资产', 3:'故事板', 4:'视频片段', 5:'拼接导出' };
Shell.toast('进入 Stage ' + n + ' · ' + stageNames[n], 'pipeline#stage-' + n);
}
function readHash() {
const m = location.hash.match(/stage-(\d)/);
if (m) activateStage(+m[1]);
}
window.addEventListener('hashchange', readHash);
readHash();
</script>
</body>
</html>