Files
2026-06-16 00:38:58 +08:00

1584 lines
98 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" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>CapCut · Confidence Before Investment</title>
<style>
/* ============================================================
CEC Prototype — CapCut 创作-导出链路重设计
设计令牌 / Design Tokens
============================================================ */
:root{
--bg:#0a0a0d; /* 画框外背景 */
--app:#0d0d12; /* App 背景 */
--surface:#16161d; /* 卡片 */
--surface-2:#1f1f29; /* 次级卡片 */
--surface-3:#2a2a37; /* 高亮卡片 */
--line:rgba(255,255,255,.08);
--line-2:rgba(255,255,255,.14);
--text:#f6f6f8;
--text-2:#9b9ba6;
--text-3:#6a6a76;
/* 品牌渐变 */
--brand-1:#4f7bff;
--brand-2:#19d3c5;
--grad:linear-gradient(135deg,#4f7bff 0%,#19d3c5 100%);
--grad-soft:linear-gradient(135deg,rgba(79,123,255,.18),rgba(25,211,197,.18));
/* 计费语义色 */
--free:#2ad17e; /* 免费=绿 */
--free-bg:rgba(42,209,126,.14);
--pro:#ffcf5c; /* Pro=金 */
--pro-2:#ffac4a;
--pro-bg:rgba(255,196,80,.14);
--credit:#ff9d54; /* credits=琥珀 */
--credit-bg:rgba(255,157,84,.14);
--danger:#ff5a6a;
--danger-bg:rgba(255,90,106,.14);
--r-xl:26px;
--r-lg:20px;
--r:15px;
--r-sm:11px;
--shadow:0 18px 50px -12px rgba(0,0,0,.6);
--shadow-sm:0 6px 20px -6px rgba(0,0,0,.5);
--spring:cubic-bezier(.34,1.56,.64,1);
--ease:cubic-bezier(.16,1,.3,1);
--frame-w:393px;
--frame-h:852px;
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;}
html,body{height:100%;}
body{
font-family:-apple-system,"SF Pro Display",system-ui,"PingFang SC","Microsoft YaHei",sans-serif;
background:
radial-gradient(1200px 700px at 20% -10%,rgba(79,123,255,.18),transparent 60%),
radial-gradient(1000px 700px at 90% 110%,rgba(25,211,197,.14),transparent 55%),
var(--bg);
color:var(--text);
display:flex;align-items:center;justify-content:center;
min-height:100vh;padding:24px;
-webkit-font-smoothing:antialiased;
overflow:hidden;
}
/* ============================================================ 画框 ============ */
.stage{display:flex;flex-direction:column;align-items:center;gap:18px;}
.topbar{display:flex;align-items:center;gap:14px;max-width:var(--frame-w);}
.tagline{text-align:center;color:var(--text-2);font-size:13px;letter-spacing:.3px;flex:1;}
.tagline b{color:var(--text);font-weight:600;}
.lang-toggle{
position:fixed;top:18px;right:18px;z-index:1000;
cursor:pointer;font-family:inherit;font-weight:700;font-size:13px;color:var(--text);
background:rgba(31,31,41,.92);backdrop-filter:blur(10px);border:1px solid var(--line-2);border-radius:999px;padding:9px 16px;
transition:transform .25s var(--spring),background .2s;white-space:nowrap;box-shadow:var(--shadow-sm);
}
.lang-toggle:hover{background:var(--surface-3);}
.lang-toggle:active{transform:scale(.92);}
.phone{
position:relative;width:var(--frame-w);height:var(--frame-h);
background:var(--app);border-radius:54px;
box-shadow:var(--shadow),0 0 0 11px #1a1a1f,0 0 0 13px #2c2c33;
overflow:hidden;flex:none;
}
/* 灵动岛 */
.dynamic-island{
position:absolute;top:11px;left:50%;transform:translateX(-50%);
width:122px;height:34px;background:#000;border-radius:20px;z-index:600;
}
/* 状态栏 */
.statusbar{
position:absolute;top:0;left:0;right:0;height:54px;z-index:500;
display:flex;align-items:center;justify-content:space-between;
padding:18px 30px 0;font-size:14px;font-weight:600;color:#fff;pointer-events:none;
}
.statusbar .right{display:flex;gap:6px;align-items:center;}
/* App 视口 */
.viewport{position:absolute;inset:0;overflow:hidden;border-radius:54px;}
/* 屏幕容器 */
.screen{
position:absolute;inset:0;padding-top:54px;
display:flex;flex-direction:column;
opacity:0;visibility:hidden;transform:translateX(24px);
transition:opacity .4s var(--ease),transform .5s var(--ease),visibility .4s;
background:var(--app);
}
.screen.active{opacity:1;visibility:visible;transform:none;z-index:10;}
.screen.exit-left{transform:translateX(-22px);opacity:0;}
.scroll{flex:1;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;}
.scroll::-webkit-scrollbar{width:0;}
.pad{padding:14px 18px 120px;}
/* ============================================================ 通用 UI ============ */
.appbar{
display:flex;align-items:center;gap:12px;padding:6px 16px 12px;min-height:48px;
}
.appbar .title{font-size:19px;font-weight:700;letter-spacing:-.2px;}
.appbar .sub{font-size:12px;color:var(--text-2);margin-top:2px;}
.iconbtn{
width:38px;height:38px;flex:none;border-radius:12px;border:none;cursor:pointer;
background:var(--surface-2);color:var(--text);
display:flex;align-items:center;justify-content:center;
transition:transform .25s var(--spring),background .2s;
}
.iconbtn:active{transform:scale(.88);background:var(--surface-3);}
.spacer{flex:1;}
.btn{
border:none;cursor:pointer;font-family:inherit;font-weight:600;color:#fff;
border-radius:14px;padding:14px 20px;font-size:15px;
background:var(--surface-2);
transition:transform .25s var(--spring),filter .2s,opacity .2s;
display:inline-flex;align-items:center;justify-content:center;gap:8px;
}
.btn:active{transform:scale(.96);}
.btn[disabled]{opacity:.4;pointer-events:none;}
.btn-primary{background:var(--grad);box-shadow:0 8px 24px -8px rgba(79,123,255,.7);}
.btn-primary:active{filter:brightness(1.08);}
.btn-block{display:flex;width:100%;}
.btn-ghost{background:transparent;border:1px solid var(--line-2);color:var(--text);}
/* 计费徽标 */
.cost{
display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;
padding:3px 8px;border-radius:999px;line-height:1;white-space:nowrap;letter-spacing:.2px;
}
.cost svg{width:11px;height:11px;}
.cost.free{color:var(--free);background:var(--free-bg);}
.cost.pro{color:var(--pro);background:var(--pro-bg);}
.cost.credit{color:var(--credit);background:var(--credit-bg);}
.badge-ai{
display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:700;
padding:3px 8px;border-radius:999px;line-height:1;
color:#cfe0ff;background:var(--grad-soft);border:1px solid rgba(79,123,255,.3);
}
.badge-ai svg{width:11px;height:11px;}
.badge-edited{color:#ffd9a8;background:rgba(255,157,84,.12);border:1px solid rgba(255,157,84,.3);}
.chip{
display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:999px;
background:var(--surface-2);color:var(--text-2);font-size:13px;font-weight:600;cursor:pointer;
border:1px solid transparent;transition:.25s var(--spring);white-space:nowrap;
}
.chip:active{transform:scale(.94);}
.chip.on{color:#fff;background:var(--grad-soft);border-color:rgba(79,123,255,.4);}
/* ============================================================ 意图选择 ============ */
.hero{padding:28px 22px 14px;}
.hero .kicker{font-size:12px;font-weight:700;letter-spacing:1.5px;color:var(--brand-2);text-transform:uppercase;}
.hero h1{font-size:27px;font-weight:800;letter-spacing:-.5px;margin-top:8px;line-height:1.2;}
.hero h1 .grad{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
.hero p{color:var(--text-2);font-size:14px;margin-top:10px;line-height:1.5;}
.intent-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:8px 18px;}
.intent-card{
position:relative;background:var(--surface);border:1px solid var(--line);
border-radius:var(--r-lg);padding:16px 14px;cursor:pointer;overflow:hidden;
transition:transform .3s var(--spring),border-color .3s,background .3s;
opacity:0;transform:translateY(16px);
}
.intent-card.in{opacity:1;transform:none;}
.intent-card:active{transform:scale(.95);}
.intent-card::before{
content:"";position:absolute;inset:0;opacity:0;transition:opacity .3s;
background:var(--grad-soft);
}
.intent-card:hover::before{opacity:1;}
.intent-ic{
width:46px;height:46px;border-radius:14px;display:flex;align-items:center;justify-content:center;
background:var(--grad);color:#fff;margin-bottom:12px;position:relative;
}
.intent-ic svg{width:24px;height:24px;}
.intent-card h3{font-size:16px;font-weight:700;position:relative;}
.intent-card .d{font-size:11.5px;color:var(--text-2);margin-top:5px;line-height:1.45;position:relative;}
.intent-card .go{
position:absolute;top:16px;right:14px;color:var(--text-3);
transition:transform .3s var(--spring),color .3s;
}
.intent-card:hover .go{transform:translateX(3px);color:var(--brand-2);}
.intent-tag{
position:absolute;top:0;right:0;font-size:9.5px;font-weight:800;letter-spacing:.5px;
padding:4px 9px;border-bottom-left-radius:12px;background:var(--grad);color:#fff;
}
.skip{
margin:16px 18px 0;text-align:center;color:var(--text-2);font-size:13.5px;cursor:pointer;
padding:14px;border-radius:14px;border:1px dashed var(--line-2);transition:.25s;
}
.skip:active{background:var(--surface-2);transform:scale(.98);}
.skip b{color:var(--text);}
/* loading 态 */
.loader{position:absolute;inset:0;background:rgba(13,13,18,.86);backdrop-filter:blur(8px);
display:none;flex-direction:column;align-items:center;justify-content:center;gap:18px;z-index:400;}
.loader.show{display:flex;}
.spinner{width:46px;height:46px;border-radius:50%;border:3px solid rgba(255,255,255,.12);
border-top-color:var(--brand-2);animation:spin .8s linear infinite;}
.loader .lt{font-size:14px;color:var(--text-2);}
@keyframes spin{to{transform:rotate(360deg);}}
/* ============================================================ 工作区 ============ */
.preview{
margin:6px 18px 0;border-radius:var(--r-lg);overflow:hidden;position:relative;
aspect-ratio:9/13;background:linear-gradient(160deg,#26303f,#161a22);
border:1px solid var(--line);
}
.preview .ph{
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:10px;
}
.preview .ph .play{width:58px;height:58px;border-radius:50%;background:rgba(255,255,255,.16);
backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;}
.preview .pv-caption{
position:absolute;left:0;right:0;bottom:0;padding:40px 16px 16px;
background:linear-gradient(transparent,rgba(0,0,0,.7));
}
.preview .pv-caption .cap{font-size:15px;font-weight:700;text-shadow:0 1px 6px rgba(0,0,0,.5);}
.preview .pv-caption .sub{font-size:11px;color:#d8d8e0;margin-top:4px;}
.preview .pv-badge{position:absolute;top:12px;left:12px;}
.preview .pv-music{position:absolute;top:12px;right:12px;display:flex;align-items:center;gap:6px;
font-size:11px;font-weight:600;padding:6px 10px;border-radius:999px;background:rgba(0,0,0,.45);backdrop-filter:blur(6px);}
.section-h{display:flex;align-items:center;gap:8px;margin:20px 18px 10px;}
.section-h .t{font-size:15px;font-weight:700;}
.section-h .c{font-size:12px;color:var(--text-3);}
.section-h .spacer{flex:1;}
.tool-row{display:flex;gap:10px;padding:2px 18px;overflow-x:auto;}
.tool-row::-webkit-scrollbar{height:0;}
.tool{
flex:none;width:84px;background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
padding:12px 8px 10px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;
transition:transform .25s var(--spring),border-color .25s;position:relative;
}
.tool:active{transform:scale(.93);}
.tool .ti{width:34px;height:34px;border-radius:11px;background:var(--surface-3);display:flex;align-items:center;justify-content:center;color:var(--brand-2);}
.tool .tn{font-size:11.5px;font-weight:600;color:var(--text);}
.tool .cost{position:absolute;top:-6px;right:-4px;transform:scale(.86);}
/* 工作区空状态引导 */
.ws-empty{margin:14px 18px 6px;padding:24px 20px;border-radius:var(--r-lg);text-align:center;
background:var(--grad-soft);border:1px solid rgba(79,123,255,.22);}
.ws-empty .we-ic{width:52px;height:52px;border-radius:16px;background:var(--grad);color:#fff;
display:flex;align-items:center;justify-content:center;margin:0 auto 12px;box-shadow:0 10px 24px -10px rgba(79,123,255,.8);}
.ws-empty .we-t{font-size:16px;font-weight:700;}
.ws-empty .we-d{font-size:12.5px;color:var(--text-2);margin-top:7px;line-height:1.5;}
/* 成片结果卡(AI 生成后出现) */
.result-card{margin:14px 18px 0;display:flex;gap:13px;padding:12px;border-radius:var(--r-lg);
background:var(--surface);border:1px solid var(--line);animation:popResult .5s var(--spring);}
@keyframes popResult{0%{opacity:0;transform:translateY(12px) scale(.97);}100%{opacity:1;transform:none;}}
.result-card .rc-thumb{position:relative;width:92px;height:122px;flex:none;border-radius:14px;overflow:hidden;
background:linear-gradient(160deg,#2a3445,#161a22);display:flex;align-items:center;justify-content:center;}
.result-card .play-sm{width:40px;height:40px;border-radius:50%;background:rgba(255,255,255,.16);backdrop-filter:blur(4px);
display:flex;align-items:center;justify-content:center;}
.result-card .play-sm svg{width:18px;height:18px;}
.result-card .rc-music{position:absolute;bottom:8px;left:8px;color:#fff;opacity:.85;}
.result-card .rc-info{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:7px;}
.result-card .rc-cap{font-size:14.5px;font-weight:700;line-height:1.35;}
.result-card .rc-sub{font-size:12px;color:var(--text-2);}
.result-card .badge-ai{align-self:flex-start;}
/* 魔法按钮 */
.magic-wrap{margin:14px 18px 0;position:relative;}
.magic{
width:100%;border:none;cursor:pointer;border-radius:var(--r-lg);padding:16px 18px;
background:var(--grad);color:#fff;font-family:inherit;text-align:left;position:relative;overflow:hidden;
box-shadow:0 12px 30px -10px rgba(79,123,255,.7);
transition:transform .25s var(--spring);
}
.magic:active{transform:scale(.97);}
.magic::after{
content:"";position:absolute;top:0;left:-60%;width:50%;height:100%;
background:linear-gradient(100deg,transparent,rgba(255,255,255,.35),transparent);
animation:sheen 3.4s ease-in-out infinite;
}
@keyframes sheen{0%{left:-60%;}55%,100%{left:130%;}}
.magic .mt{display:flex;align-items:center;gap:10px;font-size:16px;font-weight:800;}
.magic .md{font-size:12px;opacity:.9;margin-top:5px;font-weight:500;}
.magic .spark{width:26px;height:26px;}
/* ============================================================ 底部导航 ============ */
.tabbar{
position:absolute;left:0;right:0;bottom:0;height:84px;z-index:300;
background:rgba(13,13,18,.82);backdrop-filter:blur(22px);
border-top:1px solid var(--line);
display:flex;align-items:flex-start;padding:10px 8px 0;
}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;color:var(--text-3);
transition:color .25s;padding-top:4px;}
.tab svg{width:24px;height:24px;}
.tab .lb{font-size:10px;font-weight:600;}
.tab.on{color:var(--text);}
.tab.on svg{color:var(--brand-2);}
.tab.create-tab .plus{
width:46px;height:34px;border-radius:12px;background:var(--grad);display:flex;align-items:center;justify-content:center;
color:#fff;box-shadow:0 8px 20px -8px rgba(79,123,255,.8);margin-top:-2px;
}
/* ============================================================ 底部抽屉 / Bottom Sheet ============ */
.scrim{
position:absolute;inset:0;background:rgba(0,0,0,.5);backdrop-filter:blur(3px);
opacity:0;visibility:hidden;transition:opacity .35s var(--ease),visibility .35s;z-index:700;
}
.scrim.show{opacity:1;visibility:visible;}
.sheet{
position:absolute;left:0;right:0;bottom:0;z-index:800;
background:var(--surface);border-radius:26px 26px 0 0;
border-top:1px solid var(--line-2);
transform:translateY(102%);transition:transform .45s var(--spring);
max-height:86%;display:flex;flex-direction:column;
box-shadow:0 -20px 50px -12px rgba(0,0,0,.6);
}
.sheet.show{transform:translateY(0);}
.sheet .grip{width:38px;height:4px;border-radius:999px;background:var(--line-2);margin:10px auto 4px;flex:none;}
.sheet .sh-head{padding:8px 20px 6px;display:flex;align-items:center;gap:12px;flex:none;}
.sheet .sh-head .t{font-size:18px;font-weight:700;}
.sheet .sh-head .s{font-size:12.5px;color:var(--text-2);margin-top:3px;}
.sheet .sh-body{overflow-y:auto;padding:8px 20px 26px;}
.sheet .sh-body::-webkit-scrollbar{width:0;}
/* AI 输入 */
.ai-input{
width:100%;background:var(--surface-2);border:1px solid var(--line-2);border-radius:var(--r);
color:var(--text);font-family:inherit;font-size:15px;padding:14px;resize:none;min-height:92px;line-height:1.5;
transition:border-color .25s;
}
.ai-input:focus{outline:none;border-color:var(--brand-1);}
.ai-input::placeholder{color:var(--text-3);}
.input-foot{display:flex;align-items:center;justify-content:space-between;margin-top:8px;}
.input-foot .cnt{font-size:11.5px;color:var(--text-3);}
.input-foot .cnt.err{color:var(--danger);}
.suggest{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;}
.suggest .sg{font-size:12px;padding:7px 12px;border-radius:999px;background:var(--surface-2);
color:var(--text-2);cursor:pointer;border:1px solid var(--line);transition:.2s;}
.suggest .sg:active{transform:scale(.94);color:#fff;}
/* AI 步骤卡 */
.step{
background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);
padding:13px 14px;margin-bottom:10px;position:relative;overflow:hidden;
opacity:0;transform:translateY(10px);transition:opacity .4s var(--ease),transform .4s var(--ease),border-color .3s;
}
.step.in{opacity:1;transform:none;}
.step.running{border-color:rgba(79,123,255,.5);}
.step.done{border-color:rgba(42,209,126,.35);}
.step .st-top{display:flex;align-items:center;gap:9px;}
.step .st-num{width:24px;height:24px;border-radius:8px;background:var(--surface-3);color:var(--text-2);
font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex:none;transition:.3s;}
.step.done .st-num{background:var(--free-bg);color:var(--free);}
.step .st-name{font-size:14.5px;font-weight:700;flex:1;}
.step .st-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:9px;}
.step .st-result{
margin-top:11px;padding:11px 12px;background:var(--app);border-radius:var(--r-sm);
border:1px solid var(--line);font-size:13px;color:var(--text-2);line-height:1.5;
display:none;
}
.step.done .st-result{display:block;}
.step .st-result .rv{color:var(--text);font-weight:600;}
.st-actions{display:none;gap:8px;margin-top:11px;}
.step.done .st-actions{display:flex;}
.st-actions .mini{
font-size:12px;font-weight:600;padding:7px 12px;border-radius:10px;cursor:pointer;
background:var(--surface-3);color:var(--text);border:none;display:inline-flex;align-items:center;gap:5px;transition:.2s var(--spring);
}
.st-actions .mini:active{transform:scale(.92);}
.st-actions .mini svg{width:13px;height:13px;}
/* 进度条 */
.st-prog{height:3px;border-radius:999px;background:var(--surface-3);margin-top:11px;overflow:hidden;display:none;}
.step.running .st-prog{display:block;}
.st-prog .fill{height:100%;width:0;background:var(--grad);border-radius:999px;transition:width .2s linear;}
.step.running .st-name::after{
content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--brand-2);margin-left:7px;
animation:pulse 1s ease-in-out infinite;vertical-align:middle;}
@keyframes pulse{0%,100%{opacity:.3;}50%{opacity:1;}}
/* 计费明细行(Pro 说明 / 导出预检) */
.li{display:flex;align-items:center;gap:12px;padding:13px 0;border-bottom:1px solid var(--line);}
.li:last-child{border-bottom:none;}
.li .li-ic{width:38px;height:38px;border-radius:11px;background:var(--surface-2);display:flex;align-items:center;justify-content:center;color:var(--text-2);flex:none;}
.li .li-main{flex:1;min-width:0;}
.li .li-t{font-size:14.5px;font-weight:600;}
.li .li-s{font-size:12px;color:var(--text-2);margin-top:3px;line-height:1.4;}
.li .li-r{flex:none;display:flex;align-items:center;gap:8px;}
.swap{font-size:12px;font-weight:700;color:var(--free);background:var(--free-bg);border:none;cursor:pointer;
padding:8px 12px;border-radius:10px;display:inline-flex;align-items:center;gap:5px;transition:.2s var(--spring);}
.swap:active{transform:scale(.92);}
.swap svg{width:13px;height:13px;}
/* 免费替代推荐块 */
.alt{background:var(--free-bg);border:1px solid rgba(42,209,126,.3);border-radius:var(--r);padding:13px;margin-top:14px;}
.alt .at{font-size:13px;font-weight:700;color:var(--free);display:flex;align-items:center;gap:6px;}
.alt .ad{font-size:12.5px;color:var(--text-2);margin-top:6px;line-height:1.5;}
/* 价值点 */
.value-list{margin:6px 0 4px;}
.value-list .vrow{display:flex;align-items:flex-start;gap:9px;padding:7px 0;font-size:13.5px;color:var(--text);}
.value-list .vrow svg{width:17px;height:17px;color:var(--pro);flex:none;margin-top:1px;}
/* 导出预检汇总 */
.preflight-card{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);padding:4px 14px;margin-bottom:14px;}
.summary-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px;}
.sm{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);padding:13px;}
.sm .smk{font-size:11.5px;color:var(--text-3);display:flex;align-items:center;gap:5px;}
.sm .smv{font-size:17px;font-weight:800;margin-top:6px;letter-spacing:-.3px;}
.sm .smv.ok{color:var(--free);}
.sm .smv.warn{color:var(--credit);}
.sm.alert{border-color:rgba(255,90,106,.4);background:var(--danger-bg);}
.credit-warn{background:var(--danger-bg);border:1px solid rgba(255,90,106,.35);border-radius:var(--r);
padding:12px 14px;margin-bottom:14px;font-size:13px;color:#ffc4cb;line-height:1.5;display:flex;gap:9px;}
.credit-warn svg{width:18px;height:18px;color:var(--danger);flex:none;margin-top:1px;}
/* ============================================================ Toast ============ */
.toast-wrap{position:absolute;left:0;right:0;bottom:100px;z-index:900;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;padding:0 18px;}
.toast{
background:rgba(40,40,52,.96);backdrop-filter:blur(14px);border:1px solid var(--line-2);
border-radius:14px;padding:12px 16px;font-size:13.5px;font-weight:600;color:#fff;
display:flex;align-items:center;gap:10px;box-shadow:var(--shadow-sm);max-width:100%;
transform:translateY(20px) scale(.96);opacity:0;transition:transform .4s var(--spring),opacity .3s;
}
.toast.show{transform:none;opacity:1;}
.toast .tdot{width:22px;height:22px;border-radius:50%;background:var(--free-bg);color:var(--free);display:flex;align-items:center;justify-content:center;flex:none;}
.toast .tdot svg{width:13px;height:13px;}
.toast .link{color:var(--brand-2);font-weight:700;margin-left:2px;}
/* ============================================================ 对话框 ============ */
.dialog{
position:absolute;left:24px;right:24px;top:50%;z-index:950;
background:var(--surface);border:1px solid var(--line-2);border-radius:22px;padding:22px 20px 18px;
transform:translateY(-50%) scale(.9);opacity:0;visibility:hidden;
transition:transform .4s var(--spring),opacity .3s,visibility .3s;box-shadow:var(--shadow);
}
.dialog.show{transform:translateY(-50%) scale(1);opacity:1;visibility:visible;}
.dialog .dg-ic{width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;margin-bottom:14px;
background:var(--credit-bg);color:var(--credit);}
.dialog .dg-ic.danger{background:var(--danger-bg);color:var(--danger);}
.dialog h3{font-size:18px;font-weight:700;}
.dialog p{font-size:13.5px;color:var(--text-2);margin-top:8px;line-height:1.55;}
.dialog .dg-actions{display:flex;gap:10px;margin-top:20px;}
.dialog .dg-actions .btn{flex:1;padding:13px;}
/* ============================================================ 完成态 ============ */
.done-screen{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:28px 26px 110px;min-height:100%;}
.done-ring{width:96px;height:96px;border-radius:50%;background:var(--free-bg);display:flex;align-items:center;justify-content:center;margin-bottom:22px;
animation:popIn .6s var(--spring);}
.done-ring .inner{width:66px;height:66px;border-radius:50%;background:var(--free);display:flex;align-items:center;justify-content:center;color:#06281a;}
.done-ring svg{width:34px;height:34px;}
@keyframes popIn{0%{transform:scale(0);}60%{transform:scale(1.12);}100%{transform:scale(1);}}
.done-screen h2{font-size:23px;font-weight:800;letter-spacing:-.4px;}
.done-screen .dp{font-size:14px;color:var(--text-2);margin-top:10px;line-height:1.5;}
.ready-card{width:100%;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);padding:16px;margin-top:24px;text-align:left;}
.ready-card .rc-row{display:flex;align-items:center;justify-content:space-between;padding:9px 0;font-size:13.5px;border-bottom:1px solid var(--line);}
.ready-card .rc-row:last-child{border-bottom:none;}
.ready-card .rc-row .k{color:var(--text-2);}
.ready-card .rc-row .v{font-weight:700;}
/* ============================================================ 进度指示器(环节) ============ */
.stepper{display:flex;align-items:center;gap:6px;padding:4px 18px 10px;}
.stepper .sp{flex:1;height:4px;border-radius:999px;background:var(--surface-3);overflow:hidden;position:relative;}
.stepper .sp.done{background:var(--free);}
.stepper .sp.cur{background:var(--surface-3);}
.stepper .sp.cur::after{content:"";position:absolute;inset:0;background:var(--grad);animation:fillbar .5s var(--ease) forwards;}
@keyframes fillbar{from{transform:translateX(-100%);}to{transform:translateX(0);}}
.stepper-lbl{display:flex;justify-content:space-between;padding:0 18px 4px;font-size:10.5px;color:var(--text-3);}
.stepper-lbl span.on{color:var(--brand-2);font-weight:700;}
/* ============================================================ 对比页 ============ */
.cmp-toggle{display:flex;gap:8px;background:var(--surface-2);border-radius:14px;padding:4px;margin:4px 18px 14px;}
.cmp-toggle .ct{flex:1;text-align:center;padding:10px;border-radius:10px;font-size:13.5px;font-weight:700;cursor:pointer;color:var(--text-2);transition:.3s var(--spring);}
.cmp-toggle .ct.on{background:var(--surface);color:#fff;box-shadow:var(--shadow-sm);}
.cmp-toggle .ct.on.after{color:var(--free);}
.metric-cmp{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;padding:0 18px 6px;}
.mc{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:14px 10px;text-align:center;}
.mc .mcv{font-size:24px;font-weight:800;letter-spacing:-1px;}
.mc .mcl{font-size:11px;color:var(--text-2);margin-top:5px;}
.mc .mcd{font-size:11px;font-weight:700;margin-top:8px;display:inline-flex;align-items:center;gap:3px;padding:3px 7px;border-radius:999px;}
.mc .mcd.good{color:var(--free);background:var(--free-bg);}
.mc .mcd.bad{color:var(--danger);background:var(--danger-bg);}
.flow{padding:8px 18px 6px;}
.flow-node{display:flex;gap:13px;position:relative;padding-bottom:4px;}
.flow-node .dotcol{display:flex;flex-direction:column;align-items:center;flex:none;}
.flow-node .nd{width:30px;height:30px;border-radius:50%;background:var(--surface-2);border:2px solid var(--line-2);
display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:var(--text-2);z-index:2;flex:none;}
.flow-node.decision .nd{border-color:var(--credit);color:var(--credit);}
.flow-node.block .nd{border-color:var(--danger);color:var(--danger);background:var(--danger-bg);}
.flow-node.ok .nd{border-color:var(--free);color:var(--free);}
.flow-node .ln{width:2px;flex:1;background:var(--line);min-height:14px;}
.flow-node:last-child .ln{display:none;}
.flow-node .nbody{padding-bottom:16px;flex:1;}
.flow-node .ntitle{font-size:14px;font-weight:600;}
.flow-node .ntag{font-size:11px;margin-top:3px;display:inline-block;padding:2px 8px;border-radius:999px;font-weight:600;}
.flow-node.decision .ntag{color:var(--credit);background:var(--credit-bg);}
.flow-node.block .ntag{color:var(--danger);background:var(--danger-bg);}
/* ============================================================ 指标页 ============ */
.metric-group{margin:0 18px 18px;}
.mg-title{font-size:14px;font-weight:700;margin:18px 0 10px;display:flex;align-items:center;gap:8px;}
.mg-title .gi{width:26px;height:26px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:#fff;}
.mrow{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:14px;margin-bottom:10px;position:relative;}
.mrow .mr-top{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;}
.mrow .mr-left{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0;}
.mrow .mr-name{font-size:13.5px;font-weight:600;color:var(--text);}
.mrow .mr-valwrap{flex:none;text-align:right;white-space:nowrap;}
.mrow .mr-val{font-size:20px;font-weight:800;letter-spacing:-.5px;}
.mrow .mr-unit{font-size:12px;color:var(--text-2);font-weight:600;margin-left:2px;}
.mrow .mr-bar{height:6px;border-radius:999px;background:var(--surface-3);margin-top:12px;overflow:hidden;position:relative;}
.mrow .mr-base{position:absolute;top:-3px;width:2px;height:12px;background:var(--text-3);border-radius:2px;}
.mrow .mr-fill{height:100%;border-radius:999px;width:0;transition:width 1s var(--ease);}
.mrow .mr-foot{display:flex;justify-content:space-between;margin-top:8px;font-size:11px;color:var(--text-3);}
.mrow .mr-status{font-size:10.5px;font-weight:700;padding:3px 8px;border-radius:999px;flex:none;}
.mrow .mr-status.hit{color:var(--free);background:var(--free-bg);}
.mrow .mr-status.miss{color:var(--credit);background:var(--credit-bg);}
.mrow.missing{opacity:.6;}
.mr-missing{font-size:13px;color:var(--text-3);font-style:italic;}
/* 杂项 */
.fade-up{opacity:0;transform:translateY(14px);animation:fadeUp .5s var(--ease) forwards;}
@keyframes fadeUp{to{opacity:1;transform:none;}}
.divider{height:1px;background:var(--line);margin:16px 18px;}
.note{font-size:11.5px;color:var(--text-3);text-align:center;padding:14px 24px;line-height:1.5;}
</style>
</head>
<body>
<div class="stage">
<div class="topbar">
<p class="tagline" data-i18n-html="tagline"></p>
<button class="lang-toggle" id="langToggle">中文</button>
</div>
<div class="phone">
<div class="dynamic-island"></div>
<div class="statusbar">
<span>9:41</span>
<span class="right">
<svg width="17" height="12" viewBox="0 0 17 12" fill="none"><rect x="0" y="7" width="3" height="5" rx="1" fill="white"/><rect x="4.5" y="4.5" width="3" height="7.5" rx="1" fill="white"/><rect x="9" y="2" width="3" height="10" rx="1" fill="white"/><rect x="13.5" y="0" width="3" height="12" rx="1" fill="white" opacity=".5"/></svg>
<svg width="22" height="12" viewBox="0 0 24 12" fill="none"><rect x="1" y="1" width="20" height="10" rx="3" stroke="white" stroke-opacity=".5" fill="none"/><rect x="2.5" y="2.5" width="15" height="7" rx="1.5" fill="white"/><rect x="22" y="4" width="1.5" height="4" rx="1" fill="white" opacity=".5"/></svg>
</span>
</div>
<div class="viewport" id="viewport">
<!-- ===================== Screen 1: Intent ===================== -->
<section class="screen" id="screen-intent" data-tab="create">
<div class="scroll">
<div class="hero">
<div class="kicker" data-i18n="heroKicker">AI · Smart Creation</div>
<h1 data-i18n-html="heroTitle"></h1>
<p data-i18n="heroDesc"></p>
</div>
<div class="intent-grid" id="intentGrid"></div>
<div class="skip" id="skipIntent" data-i18n-html="skip"></div>
<div class="note" data-i18n="disclaimer"></div>
</div>
<div class="loader" id="intentLoader">
<div class="spinner"></div>
<div class="lt" id="loaderText"></div>
</div>
</section>
<!-- ===================== Screen 2: Workspace ===================== -->
<section class="screen" id="screen-workspace" data-tab="create">
<div class="appbar">
<button class="iconbtn" id="wsBack">${'svg-back'}</button>
<div><div class="title" style="font-size:17px" id="wsTitle">Workspace</div><div class="sub" id="wsSub"></div></div>
<div class="spacer"></div>
<div class="cost free" style="transform:scale(1.05)" data-i18n="phaseBadge">Step 1/4</div>
</div>
<div class="stepper" id="stepper1"></div>
<div class="stepper-lbl" id="stepperLbl1"></div>
<div class="scroll">
<div class="ws-empty" id="wsEmpty">
<div class="we-ic">${'svg-spark'}</div>
<div class="we-t" data-i18n="emptyTitle"></div>
<div class="we-d" data-i18n="emptyDesc"></div>
</div>
<div class="magic-wrap">
<button class="magic" id="magicBtn">
<div class="mt">${'svg-spark'} <span data-i18n="magicTitle"></span></div>
<div class="md" data-i18n="magicDesc"></div>
</button>
</div>
<div class="result-card" id="wsPreview" style="display:none">
<div class="rc-thumb">
<div class="play-sm">${'svg-play'}</div>
<span class="rc-music" id="pvMusic">${'svg-music'}</span>
</div>
<div class="rc-info">
<span class="badge-ai" id="pvBadge">${'svg-spark'}<span data-i18n="aiGenerated"></span></span>
<div class="rc-cap" id="pvCap"></div>
<div class="rc-sub" id="pvSub"></div>
</div>
</div>
<div class="section-h"><span class="t" data-i18n="recoTemplates"></span><span class="c" data-i18n="recoTemplatesSub"></span></div>
<div class="tool-row" id="templateRow"></div>
<div class="section-h"><span class="t" data-i18n="tools"></span></div>
<div class="tool-row" id="toolRow"></div>
<div style="padding:22px 18px 110px">
<button class="btn btn-primary btn-block" id="toExport">${'svg-export'} <span data-i18n="previewExport"></span></button>
</div>
</div>
</section>
<!-- ===================== Screen 3: Done ===================== -->
<section class="screen" id="screen-done" data-tab="create">
<div class="scroll"><div class="done-screen">
<div class="done-ring"><div class="inner">${'svg-check'}</div></div>
<h2 data-i18n="doneTitle"></h2>
<p class="dp" data-i18n="doneDesc"></p>
<div class="ready-card" id="readyCard"></div>
<div style="display:flex;gap:10px;width:100%;margin-top:20px">
<button class="btn btn-ghost" style="flex:1" id="doneCompare" data-i18n="seeCompare"></button>
<button class="btn btn-primary" style="flex:1" id="doneRestart" data-i18n="makeAnother"></button>
</div>
</div></div>
</section>
<!-- ===================== Screen 4: Compare ===================== -->
<section class="screen" id="screen-compare" data-tab="compare">
<div class="appbar"><div><div class="title" data-i18n="cmpTitle"></div><div class="sub" data-i18n="cmpSub"></div></div></div>
<div class="scroll"><div style="padding-bottom:120px">
<div class="metric-cmp" id="cmpMetrics"></div>
<div class="cmp-toggle">
<div class="ct on" id="ctBefore" data-v="before" data-i18n="beforePath"></div>
<div class="ct after" id="ctAfter" data-v="after" data-i18n="afterPath"></div>
</div>
<div class="flow" id="cmpFlow"></div>
</div></div>
</section>
<!-- ===================== Screen 5: Metrics ===================== -->
<section class="screen" id="screen-metrics" data-tab="metrics">
<div class="appbar"><div><div class="title" data-i18n="metricsTitle"></div><div class="sub" data-i18n="metricsSub"></div></div></div>
<div class="scroll"><div id="metricsBody" style="padding-bottom:120px"></div></div>
</section>
</div><!-- /viewport -->
<!-- Overlays -->
<div class="scrim" id="scrim"></div>
<div class="sheet" id="sheet"><div class="grip"></div><div class="sh-head" id="sheetHead"></div><div class="sh-body" id="sheetBody"></div></div>
<div class="dialog" id="dialog"></div>
<div class="toast-wrap" id="toastWrap"></div>
<!-- Bottom navigation -->
<nav class="tabbar" id="tabbar">
<div class="tab on" data-tab="create" data-screen="screen-intent"><div class="plus" style="display:none"></div>${'tab-create'}<span class="lb" data-i18n="tabCreate"></span></div>
<div class="tab" data-tab="compare" data-screen="screen-compare">${'tab-compare'}<span class="lb" data-i18n="tabCompare"></span></div>
<div class="tab" data-tab="metrics" data-screen="screen-metrics">${'tab-metrics'}<span class="lb" data-i18n="tabMetrics"></span></div>
</nav>
</div>
</div>
<script>
/* ============================================================
图标系统:内联 SVG,离线可用
============================================================ */
const I = {
'svg-back':'<svg viewBox="0 0 24 24" fill="none" width="20" height="20"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-play':'<svg viewBox="0 0 24 24" width="22" height="22"><path d="M8 5v14l11-7z" fill="white"/></svg>',
'svg-music':'<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M9 18V6l10-2v12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6" cy="18" r="3" stroke="currentColor" stroke-width="2"/><circle cx="16" cy="16" r="3" stroke="currentColor" stroke-width="2"/></svg>',
'svg-spark':'<svg viewBox="0 0 24 24" fill="none" width="22" height="22"><path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3z" fill="currentColor"/><path d="M19 14l.9 2.6L22 17.5l-2.1.9L19 21l-.9-2.6L16 17.5l2.1-.9L19 14z" fill="currentColor" opacity=".8"/></svg>',
'svg-export':'<svg viewBox="0 0 24 24" fill="none" width="19" height="19"><path d="M12 16V4m0 0L8 8m4-4l4 4" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',
'svg-check':'<svg viewBox="0 0 24 24" fill="none" width="30" height="30"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-check-sm':'<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-edit':'<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M16 4l4 4-11 11H5v-4L16 4z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-undo':'<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M9 14L4 9l5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 9h11a5 5 0 010 10h-3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-swap':'<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M7 4L3 8l4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 8h13M17 20l4-4-4-4M21 16H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-crown':'<svg viewBox="0 0 24 24" width="11" height="11"><path d="M3 7l4 4 5-7 5 7 4-4-1.5 12h-15z" fill="currentColor"/></svg>',
'svg-coin':'<svg viewBox="0 0 24 24" width="11" height="11" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/><path d="M12 7v10M9.5 9.5h4a1.5 1.5 0 010 3h-3a1.5 1.5 0 000 3h4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',
'svg-gift':'<svg viewBox="0 0 24 24" width="11" height="11" fill="none"><path d="M4 11h16v9H4z" stroke="currentColor" stroke-width="2"/><path d="M2 7h20v4H2zM12 7v13M12 7S10 3 7.5 4 9 7 12 7zM12 7s2-4 4.5-3S15 7 12 7z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',
'svg-alert':'<svg viewBox="0 0 24 24" fill="none" width="18" height="18"><path d="M12 8v5M12 16.5v.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M10.3 3.8L2.6 17a2 2 0 001.7 3h15.4a2 2 0 001.7-3L13.7 3.8a2 2 0 00-3.4 0z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',
'svg-x':'<svg viewBox="0 0 24 24" fill="none" width="18" height="18"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',
'svg-zap':'<svg viewBox="0 0 24 24" width="16" height="16"><path d="M13 2L4 14h6l-1 8 9-12h-6l1-8z" fill="currentColor"/></svg>',
'svg-shield':'<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M12 3l8 3v5c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V6l8-3z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M9 12l2 2 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'svg-rocket':'<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M5 15c-1 1-2 5-2 5s4-1 5-2m4-9a3 3 0 11-4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M14 4c4 0 6 2 6 6-1 5-6 8-9 9l-3-3c1-3 4-8 9-12 0 0-2 0-3 0z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',
// 意图图标
'ic-shop':'<svg viewBox="0 0 24 24" fill="none" width="24" height="24"><path d="M6 7h12l-1 13H7L6 7z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M9 7a3 3 0 016 0" stroke="currentColor" stroke-width="2"/></svg>',
'ic-mic':'<svg viewBox="0 0 24 24" fill="none" width="24" height="24"><rect x="9" y="3" width="6" height="11" rx="3" stroke="currentColor" stroke-width="2"/><path d="M5 11a7 7 0 0014 0M12 18v3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
'ic-beat':'<svg viewBox="0 0 24 24" fill="none" width="24" height="24"><path d="M3 12h3l2-6 4 14 3-9 2 4h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'ic-vlog':'<svg viewBox="0 0 24 24" fill="none" width="24" height="24"><rect x="3" y="6" width="13" height="12" rx="2" stroke="currentColor" stroke-width="2"/><path d="M16 10l5-3v10l-5-3" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',
// 工具图标
'ic-text':'<svg viewBox="0 0 24 24" fill="none" width="20" height="20"><path d="M5 6h14M12 6v13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
'ic-sticker':'<svg viewBox="0 0 24 24" fill="none" width="20" height="20"><path d="M4 4h16v10l-6 6H4z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="M14 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',
'ic-filter':'<svg viewBox="0 0 24 24" fill="none" width="20" height="20"><circle cx="9" cy="9" r="5" stroke="currentColor" stroke-width="2"/><circle cx="15" cy="15" r="5" stroke="currentColor" stroke-width="2"/></svg>',
'ic-cut':'<svg viewBox="0 0 24 24" fill="none" width="20" height="20"><circle cx="6" cy="6" r="3" stroke="currentColor" stroke-width="2"/><circle cx="6" cy="18" r="3" stroke="currentColor" stroke-width="2"/><path d="M8.5 7.5L20 18M8.5 16.5L20 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
'ic-speed':'<svg viewBox="0 0 24 24" fill="none" width="20" height="20"><path d="M12 12l5-3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M4 18a9 9 0 1116 0" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
'ic-res':'<svg viewBox="0 0 24 24" fill="none" width="18" height="18"><rect x="3" y="5" width="18" height="14" rx="2" stroke="currentColor" stroke-width="2"/><path d="M7 15v-4l2 2 2-2v4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'ic-wm':'<svg viewBox="0 0 24 24" fill="none" width="18" height="18"><path d="M4 4l16 16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M7 7l10 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity=".4"/></svg>',
'ic-clock':'<svg viewBox="0 0 24 24" fill="none" width="18" height="18"><circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="2"/><path d="M12 8v4l3 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
'tab-create':'<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="5" stroke="currentColor" stroke-width="2"/><path d="M12 8v8M8 12h8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
'tab-compare':'<svg viewBox="0 0 24 24" fill="none"><path d="M12 3v18" stroke="currentColor" stroke-width="2"/><path d="M5 8l-3 3 3 3M19 8l3 3-3 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
'tab-metrics':'<svg viewBox="0 0 24 24" fill="none"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
};
function costHTML(c){
if(!c) return '';
if(c.type==='free') return '<span class="cost free">'+I['svg-gift']+T('free')+'</span>';
if(c.type==='pro') return '<span class="cost pro">'+I['svg-crown']+'Pro</span>';
if(c.type==='credit') return '<span class="cost credit">'+I['svg-coin']+c.n+' credits</span>';
return '';
}
/* ============================================================
APP_DATA — 唯一数据源(演示用模拟数据)
============================================================ */
const APP_DATA = {
creditBalance: 10,
intents:[
{id:'shop', name:{zh:'带货',en:'Shopping'}, icon:'ic-shop', desc:{zh:'商品展示 + 卖点口播,30 秒成交',en:'Product demo + pitch, sell in 30s'}, tag:{zh:'主线',en:'Main'}},
{id:'talk', name:{zh:'口播',en:'Talking'}, icon:'ic-mic', desc:{zh:'对着镜头讲,自动配字幕',en:'Talk to camera, auto captions'}},
{id:'beat', name:{zh:'卡点',en:'Beat Sync'}, icon:'ic-beat', desc:{zh:'踩节奏混剪,音乐自动对齐',en:'Beat-synced cuts, auto-aligned'}},
{id:'vlog', name:{zh:'Vlog',en:'Vlog'}, icon:'ic-vlog', desc:{zh:'生活记录,自然转场叙事',en:'Life log, natural transitions'}},
],
// 按意图分场景:每个意图有各自的模板/工具/AI 编排/成片预览
scenes:{
shop:{
line:{zh:'30 秒带货/口播视频',en:'30s shopping / talking video'},
templates:[
{id:'t1', name:{zh:'好物开箱',en:'Unboxing'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'t2', name:{zh:'爆款种草',en:'Viral Promo'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'极简种草(免费)',en:'Minimal Promo (Free)'}},
{id:'t3', name:{zh:'限时秒杀',en:'Flash Sale'}, icon:'ic-text', cost:{type:'credit',n:3}, free:{zh:'基础促销(免费)',en:'Basic Sale (Free)'}},
{id:'t4', name:{zh:'测评对比',en:'Compare & Review'}, icon:'ic-cut', cost:{type:'free'}},
],
tools:[
{id:'x1', name:{zh:'文字',en:'Text'}, icon:'ic-text', cost:{type:'free'}},
{id:'x2', name:{zh:'贴纸',en:'Stickers'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'x3', name:{zh:'AI 调色',en:'AI Color'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'基础滤镜(免费)',en:'Basic Filter (Free)'}},
{id:'x4', name:{zh:'智能剪辑',en:'Smart Cut'}, icon:'ic-cut', cost:{type:'credit',n:5}, free:{zh:'手动分割(免费)',en:'Manual Split (Free)'}},
{id:'x5', name:{zh:'变速',en:'Speed'}, icon:'ic-speed', cost:{type:'free'}},
],
aiPrompt:{zh:'帮我做个 30 秒带货视频,加亲切女声配音和卡点音乐',en:'Make a 30s shopping video with a warm female voiceover and beat-synced music'},
aiSuggest:{zh:['30 秒带货视频,加字幕和卡点音乐','口播讲解产品卖点,自动配字幕','开箱视频,要电商质感调色'],
en:['30s shopping video with captions & beat music','Pitch product highlights with auto captions','Unboxing video with e-commerce color grade']},
aiSteps:[
{id:'s1', name:{zh:'生成带货文案 + 字幕',en:'Generate pitch script + captions'}, cost:{type:'free'},
result:{zh:'<span class="rv">「这件外套我穿了一整个秋天…」</span> 已生成 3 段口播文案 + 逐句字幕。',en:'<span class="rv">"I wore this coat all autumn…"</span> Generated 3 pitch lines + line-by-line captions.'}},
{id:'s2', name:{zh:'AI 智能配音(女声 · 亲切)',en:'AI voiceover (warm female)'}, cost:{type:'credit',n:8},
result:{zh:'已用 <span class="rv">亲切女声</span> 朗读全文,时长 28s,自动对齐字幕。',en:'Narrated by a <span class="rv">warm female voice</span>, 28s, auto-synced to captions.'}},
{id:'s3', name:{zh:'匹配卡点背景音乐',en:'Match beat-synced music'}, cost:{type:'free'},
result:{zh:'已匹配 <span class="rv">轻快电子卡点</span>,鼓点对齐 6 个画面切换。',en:'Matched <span class="rv">upbeat electronic beats</span>, aligned to 6 cuts.'}},
{id:'s4', name:{zh:'自动卡点剪辑 + 转场',en:'Auto beat cut + transitions'}, cost:{type:'credit',n:5},
result:{zh:'按节奏切了 <span class="rv">6 个镜头</span>,加入推拉与闪黑转场。',en:'Cut <span class="rv">6 shots</span> to the beat, added push & flash transitions.'}},
{id:'s5', name:{zh:'AI 智能调色(电商质感)',en:'AI color grade (e-commerce)'}, cost:{type:'pro'},
result:{zh:'套用 <span class="rv">高级电商色调</span>,提亮商品、统一肤色。',en:'Applied a <span class="rv">premium e-commerce grade</span>, brightened product, unified skin tone.'}},
],
previewCap:{zh:'这件外套我穿了一整个秋天…',en:'I wore this coat all autumn…'}, previewSub:{zh:'AI 配音 · 卡点音乐 · 6 镜头',en:'AI voice · beat music · 6 shots'},
},
talk:{
line:{zh:'口播讲解 · 自动字幕',en:'Talking head · auto captions'},
templates:[
{id:'t1', name:{zh:'知识口播',en:'Knowledge Talk'}, icon:'ic-text', cost:{type:'free'}},
{id:'t2', name:{zh:'干货清单',en:'Tips Listicle'}, icon:'ic-sticker', cost:{type:'pro'}, free:{zh:'基础清单(免费)',en:'Basic List (Free)'}},
{id:'t3', name:{zh:'观点输出',en:'Opinion'}, icon:'ic-filter', cost:{type:'free'}},
{id:'t4', name:{zh:'教程讲解',en:'Tutorial'}, icon:'ic-cut', cost:{type:'credit',n:3}, free:{zh:'基础模板(免费)',en:'Basic Template (Free)'}},
],
tools:[
{id:'x1', name:{zh:'文字',en:'Text'}, icon:'ic-text', cost:{type:'free'}},
{id:'x2', name:{zh:'提词器',en:'Teleprompter'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'x3', name:{zh:'AI 字幕',en:'AI Captions'}, icon:'ic-text', cost:{type:'free'}},
{id:'x4', name:{zh:'人声降噪',en:'Voice Denoise'}, icon:'ic-speed', cost:{type:'credit',n:5}, free:{zh:'基础降噪(免费)',en:'Basic Denoise (Free)'}},
{id:'x5', name:{zh:'AI 调色',en:'AI Color'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'基础滤镜(免费)',en:'Basic Filter (Free)'}},
],
aiPrompt:{zh:'帮我把这段口播配上逐句字幕和合适的背景音乐',en:'Add line-by-line captions and fitting background music to this talking-head clip'},
aiSuggest:{zh:['口播视频,自动断句配字幕','讲解类视频,重点词高亮','提词 + 字幕一键生成'],
en:['Talking video with auto-segmented captions','Explainer with key-word highlights','Teleprompter + captions in one tap']},
aiSteps:[
{id:'s1', name:{zh:'自动断句 + 逐句字幕',en:'Auto-segment + line captions'}, cost:{type:'free'},
result:{zh:'已为整段口播自动断句,生成 <span class="rv">逐句字幕</span>并对齐时间轴。',en:'Auto-segmented the talk and generated <span class="rv">line-by-line captions</span> aligned to the timeline.'}},
{id:'s2', name:{zh:'人声降噪 + 增强',en:'Voice denoise + enhance'}, cost:{type:'credit',n:5},
result:{zh:'已去除环境噪声,<span class="rv">人声更清晰</span>,音量统一。',en:'Removed background noise, <span class="rv">clearer voice</span>, leveled volume.'}},
{id:'s3', name:{zh:'关键词高亮',en:'Keyword highlights'}, cost:{type:'free'},
result:{zh:'自动识别并高亮 <span class="rv">7 个关键词</span>,强化重点。',en:'Auto-detected and highlighted <span class="rv">7 keywords</span> for emphasis.'}},
{id:'s4', name:{zh:'匹配轻背景音乐',en:'Match light background music'}, cost:{type:'free'},
result:{zh:'配了一段 <span class="rv">不抢话的轻音乐</span>,音量自动避让人声。',en:'Added <span class="rv">unobtrusive light music</span>, auto-ducked under the voice.'}},
{id:'s5', name:{zh:'AI 清爽调色',en:'AI clean color grade'}, cost:{type:'pro'},
result:{zh:'套用 <span class="rv">清爽口播色调</span>,提亮面部、背景柔化。',en:'Applied a <span class="rv">clean talking-head grade</span>, brightened face, softened background.'}},
],
previewCap:{zh:'今天聊聊三个让效率翻倍的习惯…',en:"Let's talk about 3 habits that double your output…"}, previewSub:{zh:'逐句字幕 · 人声增强 · 轻音乐',en:'Line captions · voice boost · light music'},
},
beat:{
line:{zh:'卡点混剪 · 音乐对齐',en:'Beat montage · music-synced'},
templates:[
{id:'t1', name:{zh:'节奏踩点',en:'Beat Hits'}, icon:'ic-cut', cost:{type:'free'}},
{id:'t2', name:{zh:'炫酷转场',en:'Flashy Transitions'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'基础转场(免费)',en:'Basic Transition (Free)'}},
{id:'t3', name:{zh:'旅行混剪',en:'Travel Montage'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'t4', name:{zh:'电音卡点',en:'EDM Beat'}, icon:'ic-speed', cost:{type:'credit',n:3}, free:{zh:'基础卡点(免费)',en:'Basic Beat (Free)'}},
],
tools:[
{id:'x1', name:{zh:'音乐库',en:'Music Library'}, icon:'ic-text', cost:{type:'free'}},
{id:'x2', name:{zh:'AI 卡点',en:'AI Beat Sync'}, icon:'ic-cut', cost:{type:'credit',n:5}, free:{zh:'手动打点(免费)',en:'Manual Markers (Free)'}},
{id:'x3', name:{zh:'转场',en:'Transitions'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'基础转场(免费)',en:'Basic Transition (Free)'}},
{id:'x4', name:{zh:'滤镜',en:'Filters'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'x5', name:{zh:'变速',en:'Speed'}, icon:'ic-speed', cost:{type:'free'}},
],
aiPrompt:{zh:'把这些素材按音乐节奏卡点混剪,加炫酷转场',en:'Cut these clips to the music beat and add flashy transitions'},
aiSuggest:{zh:['素材按节奏自动卡点','旅行视频卡点混剪','加电音和闪切转场'],
en:['Auto-cut clips to the beat','Travel video beat montage','Add EDM and flash-cut transitions']},
aiSteps:[
{id:'s1', name:{zh:'分析音乐节拍',en:'Analyze music beat'}, cost:{type:'free'},
result:{zh:'已识别 <span class="rv">BPM 128</span> 与 12 个重拍点。',en:'Detected <span class="rv">BPM 128</span> and 12 downbeats.'}},
{id:'s2', name:{zh:'素材自动卡点对齐',en:'Auto-align clips to beat'}, cost:{type:'credit',n:5},
result:{zh:'已把 <span class="rv">镜头切换对齐到重拍</span>,节奏紧凑。',en:'Aligned <span class="rv">cuts to the downbeats</span>, tight rhythm.'}},
{id:'s3', name:{zh:'AI 炫酷转场',en:'AI flashy transitions'}, cost:{type:'pro'},
result:{zh:'在切换处加入 <span class="rv">闪切 / 缩放转场</span>,更带感。',en:'Added <span class="rv">flash / zoom transitions</span> at the cuts.'}},
{id:'s4', name:{zh:'节奏律动滤镜',en:'Beat-reactive filter'}, cost:{type:'free'},
result:{zh:'画面亮度随 <span class="rv">鼓点轻微律动</span>。',en:'Brightness <span class="rv">pulses with the drums</span>.'}},
{id:'s5', name:{zh:'统一冲击力调色',en:'Unified punchy grade'}, cost:{type:'pro'},
result:{zh:'统一整体色调,<span class="rv">高对比电音风</span>。',en:'Unified color, <span class="rv">high-contrast EDM look</span>.'}},
],
previewCap:{zh:'跟着节奏,一镜到底的旅行混剪',en:'A beat-driven travel montage'}, previewSub:{zh:'12 重拍 · 自动卡点 · 炫酷转场',en:'12 beats · auto-sync · flashy transitions'},
},
vlog:{
line:{zh:'Vlog 叙事 · 自然转场',en:'Vlog story · natural transitions'},
templates:[
{id:'t1', name:{zh:'生活日常',en:'Daily Life'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'t2', name:{zh:'旅行日记',en:'Travel Diary'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'简约日记(免费)',en:'Simple Diary (Free)'}},
{id:'t3', name:{zh:'美食记录',en:'Food Log'}, icon:'ic-text', cost:{type:'free'}},
{id:'t4', name:{zh:'故事感',en:'Cinematic Story'}, icon:'ic-cut', cost:{type:'credit',n:3}, free:{zh:'基础叙事(免费)',en:'Basic Story (Free)'}},
],
tools:[
{id:'x1', name:{zh:'文字',en:'Text'}, icon:'ic-text', cost:{type:'free'}},
{id:'x2', name:{zh:'贴纸',en:'Stickers'}, icon:'ic-sticker', cost:{type:'free'}},
{id:'x3', name:{zh:'AI 字幕',en:'AI Captions'}, icon:'ic-text', cost:{type:'free'}},
{id:'x4', name:{zh:'电影调色',en:'Cinematic Color'}, icon:'ic-filter', cost:{type:'pro'}, free:{zh:'基础滤镜(免费)',en:'Basic Filter (Free)'}},
{id:'x5', name:{zh:'变速',en:'Speed'}, icon:'ic-speed', cost:{type:'free'}},
],
aiPrompt:{zh:'把今天的素材剪成有故事感的 Vlog,加旁白字幕和柔和音乐',en:"Cut today's clips into a story-driven Vlog with narration captions and soft music"},
aiSuggest:{zh:['生活素材剪成 Vlog','旅行记录,自然转场叙事','加旁白字幕和轻音乐'],
en:['Turn daily clips into a Vlog','Travel log with natural transitions','Add narration captions and light music']},
aiSteps:[
{id:'s1', name:{zh:'智能挑选精彩片段',en:'Smart-pick highlights'}, cost:{type:'free'},
result:{zh:'已从素材里挑出 <span class="rv">8 个高光片段</span>。',en:'Picked <span class="rv">8 highlight clips</span> from the footage.'}},
{id:'s2', name:{zh:'生成旁白文案 + 字幕',en:'Generate narration + captions'}, cost:{type:'free'},
result:{zh:'写好一段 <span class="rv">第一人称旁白</span>并配逐句字幕。',en:'Wrote a <span class="rv">first-person narration</span> with line captions.'}},
{id:'s3', name:{zh:'AI 旁白配音(温柔)',en:'AI narration (gentle)'}, cost:{type:'credit',n:8},
result:{zh:'用 <span class="rv">温柔嗓音</span>朗读旁白,停顿自然。',en:'Read by a <span class="rv">gentle voice</span> with natural pacing.'}},
{id:'s4', name:{zh:'自然转场串接',en:'Natural transitions'}, cost:{type:'free'},
result:{zh:'用 <span class="rv">淡入淡出</span>把片段串成连贯叙事。',en:'Stitched clips with <span class="rv">fades</span> into a coherent story.'}},
{id:'s5', name:{zh:'柔和电影调色',en:'Soft cinematic grade'}, cost:{type:'pro'},
result:{zh:'套用 <span class="rv">温暖电影感色调</span>。',en:'Applied a <span class="rv">warm cinematic grade</span>.'}},
],
previewCap:{zh:'周末的一天,从一杯咖啡开始…',en:'A weekend day, starting with coffee…'}, previewSub:{zh:'旁白配音 · 8 片段 · 电影调色',en:'Narration · 8 clips · cinematic grade'},
},
},
// 导出预检项(可一键换免费)
exportItems:[
{id:'e1', name:{zh:'输出清晰度',en:'Output resolution'}, icon:'ic-res', pro:{label:{zh:'1080p 高清',en:'1080p HD'}, kind:'pro'}, freeAlt:{label:{zh:'720p(免费)',en:'720p (Free)'}}, isPro:true, key:'res'},
{id:'e2', name:{zh:'去除水印',en:'Remove watermark'}, icon:'ic-wm', pro:{label:{zh:'无水印',en:'No watermark'}, kind:'pro'}, freeAlt:{label:{zh:'保留水印(免费)',en:'Keep watermark (Free)'}}, isPro:true, key:'wm'},
{id:'e3', name:{zh:'AI 智能配音',en:'AI voiceover'}, icon:'svg-music', pro:{label:{zh:'亲切女声',en:'Warm female voice'}, kind:'credit', n:8}, freeAlt:{label:{zh:'系统配音(免费)',en:'System voice (Free)'}}, isPro:true, key:'voice'},
{id:'e4', name:{zh:'自动卡点剪辑',en:'Auto beat cut'}, icon:'ic-cut', pro:{label:{zh:'AI 卡点',en:'AI beat sync'}, kind:'credit', n:5}, freeAlt:{label:{zh:'套用模板节奏(免费)',en:'Template rhythm (Free)'}}, isPro:true, key:'cut'},
],
exportTimeBase: 18, // 秒
// Before / After 对比
comparison:{
metrics:[
{key:'steps', label:{zh:'操作步数',en:'Steps'}, before:14, after:6, unit:{zh:'步',en:''}, betterLower:true},
{key:'decisions', label:{zh:'决策点',en:'Decisions'}, before:9, after:3, unit:{zh:'个',en:''}, betterLower:true},
{key:'blocks', label:{zh:'被付费墙拦截',en:'Paywall blocks'}, before:4, after:0, unit:{zh:'次',en:''}, betterLower:true},
],
before:[
{t:{zh:'打开 App,面对空白时间线',en:'Open the app, face a blank timeline'}, type:'normal'},
{t:{zh:'自己找模板 —— 不知免费还是 Pro',en:'Hunt for a template — unsure if free or Pro'}, type:'decision'},
{t:{zh:'套用模板做到一半',en:'Apply a template, get halfway'}, type:'normal'},
{t:{zh:'提示「该模板为 Pro」被拦截',en:'Blocked: "this template is Pro"'}, type:'block'},
{t:{zh:'回头找散落的 AI 字幕工具',en:'Go back to find scattered AI caption tools'}, type:'decision'},
{t:{zh:'加配音 —— 不知会不会扣费',en:'Add voiceover — unsure if it charges'}, type:'decision'},
{t:{zh:'手动找音乐对卡点',en:'Manually find music and sync beats'}, type:'normal'},
{t:{zh:'点导出',en:'Tap export'}, type:'normal'},
{t:{zh:'提示「1080p 需要 Pro」',en:'Blocked: "1080p needs Pro"'}, type:'block'},
{t:{zh:'导出后发现有水印',en:'After export, find a watermark'}, type:'block'},
{t:{zh:'重新调整再导出',en:'Adjust and export again'}, type:'normal'},
],
after:[
{t:{zh:'选意图「带货」,直达备好的工作区',en:'Pick intent "Shopping", land in a ready workspace'}, type:'ok'},
{t:{zh:'一句话需求,AI 编排好所有步骤',en:'One sentence, AI orchestrates all steps'}, type:'ok'},
{t:{zh:'每步价格当场可见,按需替换免费项',en:'Price shown per step, swap to free as needed'}, type:'ok'},
{t:{zh:'导出预检:清晰度/水印/credit 一目了然',en:'Export pre-flight: resolution / watermark / credits at a glance'}, type:'ok'},
{t:{zh:'credit 不足?一键换免费方案',en:'Low on credits? Swap to free in one tap'}, type:'decision'},
{t:{zh:'确认导出 —— 结果和清单完全一致',en:'Confirm export — result matches the checklist exactly'}, type:'ok'},
],
},
// 成功指标
metrics:{
efficiency:{ title:{zh:'用户效率',en:'User efficiency'}, icon:'svg-zap', color:'linear-gradient(135deg,#4f7bff,#19d3c5)', items:[
{name:{zh:'任务完成时间',en:'Time to complete'}, base:8.5, cur:3.6, target:4, unit:'min', betterLower:true},
{name:{zh:'任务完成率',en:'Completion rate'}, base:62, cur:88, target:85, unit:'%', betterLower:false},
]},
confidence:{ title:{zh:'用户信心',en:'User confidence'}, icon:'svg-shield', color:'linear-gradient(135deg,#2ad17e,#19d3c5)', items:[
{name:{zh:'自评信心(15',en:'Self-rated confidence (15)'}, base:2.8, cur:4.3, target:4.2, unit:'', betterLower:false},
{name:{zh:'中途流失率',en:'Mid-task drop-off'}, base:38, cur:14, target:15, unit:'%', betterLower:true},
]},
health:{ title:{zh:'产品健康',en:'Product health'}, icon:'svg-rocket', color:'linear-gradient(135deg,#ffac4a,#ff9d54)', items:[
{name:{zh:'AI 入口采用率',en:'AI entry adoption'}, base:0, cur:64, target:60, unit:'%', betterLower:false},
{name:{zh:'7 日重复使用率',en:'7-day repeat use'}, base:41, cur:57, target:55, unit:'%', betterLower:false},
{name:{zh:'免费→Pro 转化率',en:'Free→Pro conversion'}, base:3.1, cur:5.4, target:5, unit:'%', betterLower:false},
]},
},
};
/* ============================================================
运行时状态
============================================================ */
const state = {
screen:'screen-intent',
intentId:'shop', // 当前意图
reachedStep:0, // 主线进度 0=意图 1=工作区 2=编排 3=计费/导出 4=完成
ai:{generated:false, steps:[]}, // 每步:{...def, status:'idle|running|done', edited:false, value}
exportSel:{}, // key -> 'pro'|'free'
};
// 当前意图对应的场景数据
const curScene = () => APP_DATA.scenes[state.intentId] || APP_DATA.scenes.shop;
// 初始化导出选择:全 pro
APP_DATA.exportItems.forEach(it=>state.exportSel[it.key]='pro');
/* ============================================================
i18n — 双语支持(默认英文,可切中文)
============================================================ */
let LANG = 'en';
const L = v => (v && typeof v === 'object' && 'zh' in v) ? (v[LANG] ?? v.zh) : v;
const UI = {
tagline:{zh:'CapCut · 创作—导出链路重设计 — 主题:<b>让创作者在投入前就心里有数(Confidence Before Investment</b>',
en:'CapCut · Create-to-Export Redesign — Theme: <b>Confidence Before Investment</b>'},
heroKicker:{zh:'AI · 智能创作',en:'AI · Smart Creation'},
heroTitle:{zh:'你想做<span class="grad">什么</span>',en:'What do you want to <span class="grad">make</span>?'},
heroDesc:{zh:'先告诉我目标,我直接给你备好模板和工具 —— 不用面对空白的时间线。',en:'Tell me the goal first — I\'ll prep the templates and tools, so you never face a blank timeline.'},
skip:{zh:'想自己来?<b>直接进入空白时间线 →</b>',en:'Prefer to start fresh? <b>Go to a blank timeline →</b>'},
disclaimer:{zh:'所有数据、计费与 credit 均为演示用模拟值,不代表 CapCut 真实定价。',en:'All data, pricing and credits are mock values for demo only — not CapCut\'s real pricing.'},
phaseBadge:{zh:'环节 1/4',en:'Step 1/4'},
emptyTitle:{zh:'画布还是空的',en:'The canvas is still empty'},
emptyDesc:{zh:'不用从空白时间线开始 —— 用一句话,让 AI 帮你把字幕、配音、卡点一次编排好。',en:'No need to start from a blank timeline — in one sentence, let AI orchestrate captions, voiceover and beat-sync at once.'},
magicTitle:{zh:'用一句话生成视频',en:'Generate a video in one sentence'},
magicDesc:{zh:'描述你的需求,AI 帮你编排好字幕、配乐、卡点……每步都标好价格',en:'Describe what you want; AI orchestrates captions, music, beat-sync… each step priced'},
aiGenerated:{zh:'AI 生成',en:'AI generated'},
recoTemplates:{zh:'推荐模板',en:'Recommended templates'},
recoTemplatesSub:{zh:'选择前就标好免费 / Pro',en:'Free / Pro labeled before you pick'},
tools:{zh:'工具',en:'Tools'},
previewExport:{zh:'预览并导出',en:'Preview & export'},
doneTitle:{zh:'视频已就绪 🎉',en:'Your video is ready 🎉'},
doneDesc:{zh:'全程没有意外收费,导出前你已经看过完整清单。',en:'No surprise charges — you saw the full checklist before exporting.'},
seeCompare:{zh:'看 Before/After',en:'See Before/After'},
makeAnother:{zh:'再做一个',en:'Make another'},
cmpTitle:{zh:'改了什么?',en:'What changed?'},
cmpSub:{zh:'同一个「30 秒带货视频」,两条路径对比',en:'Same 30s shopping video, two paths compared'},
beforePath:{zh:'现状路径',en:'Current path'},
afterPath:{zh:'新路径',en:'New path'},
metricsTitle:{zh:'成功指标',en:'Success metrics'},
metricsSub:{zh:'衡量「信心前置」是否奏效',en:'Measuring whether "confidence up front" works'},
tabCreate:{zh:'创作',en:'Create'},
tabCompare:{zh:'对比',en:'Compare'},
tabMetrics:{zh:'指标',en:'Metrics'},
// 动态文案
workspace:{zh:'工作区',en:'Workspace'},
free:{zh:'免费',en:'Free'},
loaderPrep:{zh:n=>`正在为「${n}」准备模板与工具…`,en:n=>`Preparing templates & tools for "${n}"…`},
toastBlank:{zh:'已进入空白时间线(演示)',en:'Entered a blank timeline (demo)'},
toastApplied:{zh:n=>`已应用「${n}`,en:n=>`Applied "${n}"`},
// Pro sheet
proSubCredit:{zh:'消耗 credits 的',en:'a credit-based'}, proSubPro:{zh:'Pro',en:'Pro'},
proSub:{zh:t=>`这是${t}功能 —— 先看清价值,再决定要不要用。`,en:t=>`This is ${t} feature — see the value first, then decide.`},
proVal1:{zh:'更高级的视觉效果,适合追求质感的商品展示',en:'Higher-end visuals, great for premium product shots'},
proValCredit:{zh:n=>`按次计费,仅在使用时扣除 ${n} credits`,en:n=>`Pay per use — only ${n} credits when used`},
proValPro:{zh:'订阅期内无限次使用',en:'Unlimited use during subscription'},
altTitle:{zh:'也有免费替代',en:'There\'s a free alternative too'},
altDesc:{zh:f=>`不想花钱?用 <b style="color:#fff">${f}</b> 也能达到不错的效果,随时可在导出前切换。`,en:f=>`Don\'t want to pay? <b style="color:#fff">${f}</b> gets a good result too — switch any time before export.`},
useFree:{zh:'用免费替代',en:'Use free alternative'},
usePro:{zh:n=>n?`${n} credits`:'用 Pro 版',en:n=>n?`Use ${n} credits`:'Use Pro'},
proNote:{zh:'关闭此面板会回到工作区,你的编辑不会丢失。',en:'Closing this panel returns to the workspace; your edits are kept.'},
freeAltName:{zh:'免费版本',en:'free version'},
toastSwapFree:{zh:f=>`已用免费替代「${f}`,en:f=>`Switched to free "${f}"`},
// AI sheet
aiHead:{zh:'一句话生成视频',en:'Generate a video in one sentence'},
aiHeadSub:{zh:'描述你的需求,AI 会拆解成有序步骤 —— 每步标好价格,做完都能改。',en:'Describe your need; AI breaks it into ordered steps — each priced, all editable.'},
aiPlaceholder:{zh:p=>`例如:${p}`,en:p=>`e.g. ${p}`},
balance:{zh:n=>`余额 ${n} credits`,en:n=>`Balance ${n} credits`},
aiGoBtn:{zh:'生成编排',en:'Generate plan'},
aiEmptyErr:{zh:'请先描述你的需求',en:'Describe your need first'},
orchHead:{zh:'AI 正在编排…',en:'AI is orchestrating…'},
orchSub:{zh:n=>`${n} 步 · 每步标注计费与「AI 完成」标记`,en:n=>`${n} steps · each shows price & an "AI" tag`},
orchDoneBtn:{zh:'完成编排,去校对成本',en:'Finish — review costs'},
orchNote:{zh:'每个结果都能 ✏️ 编辑或 ↩︎ 撤销 —— AI 是起点,不是终点。',en:'Edit ✏️ or undo ↩︎ any result — AI is the start, not the end.'},
orchResultHead:{zh:'AI 编排结果',en:'AI plan'},
orchResultSub:{zh:n=>`${n} 步 · 可继续编辑`,en:n=>`${n} steps · keep editing`},
toastPlanApplied:{zh:'编排已应用到时间线 ✦',en:'Plan applied to the timeline ✦'},
viewLink:{zh:'查看',en:'View'},
badgeEdited:{zh:'已由创作者编辑',en:'Edited by you'},
badgeAI:{zh:'由 AI 完成',en:'Made by AI'},
edit:{zh:'编辑',en:'Edit'}, undo:{zh:'撤销',en:'Undo'},
editHead:{zh:n=>`编辑:${n}`,en:n=>`Edit: ${n}`},
editSub:{zh:'改完后这一步会标记为「已由创作者编辑」',en:'After editing, this step is marked "Edited by you"'},
editSave:{zh:'保存修改',en:'Save changes'},
editEmptyErr:{zh:'内容不能为空',en:'Content cannot be empty'},
toastEditSaved:{zh:'已保存你的修改',en:'Your changes are saved'},
undoTitle:{zh:'撤销这一步?',en:'Undo this step?'},
undoBody:{zh:n=>`将丢弃「${n}」当前的结果,恢复到 AI 生成前的状态。确认后可重新生成。`,en:n=>`This discards the current result of "${n}" and restores the pre-AI state. You can regenerate afterwards.`},
cancel:{zh:'取消',en:'Cancel'}, undoConfirm:{zh:'确认撤销',en:'Undo'},
toastUndid:{zh:n=>`已撤销「${n}`,en:n=>`Undid "${n}"`},
// export
exportHead:{zh:'导出预检',en:'Export pre-flight'},
exportHeadSub:{zh:'生成文件前,先把清晰度、水印、花费看清楚 —— 不再「导完才发现」。',en:'Before generating the file, see resolution, watermark and cost clearly — no more "surprise after export".'},
kRes:{zh:'清晰度',en:'Resolution'}, kWm:{zh:'水印',en:'Watermark'}, kCost:{zh:'预计花费',en:'Est. cost'}, kTime:{zh:'预计时长',en:'Est. time'},
wmYes:{zh:'有水印',en:'Watermark'}, wmNo:{zh:'无水印',en:'No watermark'},
creditWarn:{zh:(x,y)=>`<b>credits 不足</b>(需 ${x},仅剩 ${y})。把下面任一计费项换成免费替代即可继续导出。`,en:(x,y)=>`<b>Not enough credits</b> (need ${x}, only ${y} left). Swap any paid item to free to continue.`},
proItemsUsed:{zh:'用到的 Pro / 计费项',en:'Pro / paid items used'},
noProItems:{zh:'当前没有使用任何 Pro 项,导出为免费的 720p(含水印)。',en:'No Pro items used — export is free 720p (with watermark).'},
swapFreeBtn:{zh:'换免费',en:'Swap free'},
confirmExportBtn:{zh:c=>`确认导出(${c} credits`,en:c=>`Confirm export (${c} credits)`},
confirmExportDisabled:{zh:'credits 不足 · 请先换免费项',en:'Not enough credits · swap first'},
backEditBtn:{zh:'返回继续编辑',en:'Back to editing'},
toastExportOk:{zh:'导出成功 · 与预检清单完全一致 ✓',en:'Export complete · matches the pre-flight exactly ✓'},
// done card
dRes:{zh:'清晰度',en:'Resolution'}, dWm:{zh:'水印',en:'Watermark'}, dCost:{zh:'本次花费',en:'Cost'}, dTime:{zh:'时长',en:'Duration'},
// compare
pctNA:{zh:'不适用',en:'N/A'},
wasVal:{zh:(v,u)=>`${v}${u}`,en:(v,u)=>`was ${v}${u}`},
tagDecision:{zh:'决策点',en:'Decision'}, tagBlock:{zh:'被拦截',en:'Blocked'},
// metrics
statHit:{zh:'已达成',en:'Met'}, statMiss:{zh:'未达成',en:'Not met'},
baseline:{zh:(v,u)=>`基线 ${v}${u}`,en:(v,u)=>`Baseline ${v}${u}`},
target:{zh:(v,u)=>`目标 ${v}${u}`,en:(v,u)=>`Target ${v}${u}`},
dataMissing:{zh:'数据缺失',en:'Data missing'},
metricsNote:{zh:'对比「现状基线」与「重设计目标」,绿色=已达成。数据为演示模拟值。',en:'Baseline vs redesign target; green = met. Mock data for demo.'},
};
const T = (k,...a)=>{ const v=UI[k]?.[LANG]; return typeof v==='function'?v(...a):(v??k); };
const PHASES_I18N = {zh:['意图','AI 编排','透明计费','导出预检'],en:['Intent','AI Plan','Pricing','Export Check']};
// 应用静态文案(data-i18n / data-i18n-html
function applyStaticI18n(){
document.documentElement.lang = LANG==='zh'?'zh-CN':'en';
$$('[data-i18n]').forEach(el=>{ el.textContent = T(el.dataset.i18n); });
$$('[data-i18n-html]').forEach(el=>{ el.innerHTML = T(el.dataset.i18nHtml); });
$('#langToggle').textContent = LANG==='en'?'中文':'EN';
}
// 切换语言并重渲染
function toggleLang(){
LANG = LANG==='en'?'zh':'en';
closeSheet(); closeDialog();
applyStaticI18n();
renderIntents();
// 工作区文案
if(state.intentId){
const sc=curScene(); const intent=APP_DATA.intents.find(i=>i.id===state.intentId);
if(intent){ $('#wsTitle').textContent = L(intent.name)+' · '+T('workspace'); $('#wsSub').textContent = L(sc.line); }
$('#templateRow').innerHTML = sc.templates.map(t=>toolCard(t)).join('');
$('#toolRow').innerHTML = sc.tools.map(t=>toolCard(t)).join('');
bindToolCards();
renderStepper(state.phaseIdx ?? 1);
if(state.ai.generated){ $('#pvCap').textContent=L(sc.previewCap); $('#pvSub').textContent=L(sc.previewSub); }
}
if(state.screen==='screen-compare') renderCompare();
if(state.screen==='screen-metrics') renderMetrics();
}
/* ============================================================
引擎:图标注入 + 基础工具
============================================================ */
const phone = document.querySelector('.phone');
// 把 HTML 中的 ${'icon-key'} 占位符替换为内联 SVG(绑定事件前执行一次)
phone.innerHTML = phone.innerHTML.replace(/\$\{'([\w-]+)'\}/g,(m,k)=>I[k]||'');
const $ = (s,r=document)=>r.querySelector(s);
const $$ = (s,r=document)=>[...r.querySelectorAll(s)];
const viewport = $('#viewport');
const scrim = $('#scrim'), sheet = $('#sheet'), dialog = $('#dialog');
/* ---------- 屏幕切换 ---------- */
function showScreen(id, tab){
const cur = $('#'+state.screen);
const next = $('#'+id);
if(cur && cur!==next){ cur.classList.add('exit-left'); cur.classList.remove('active'); setTimeout(()=>cur.classList.remove('exit-left'),420); }
next.classList.add('active');
state.screen = id;
const t = tab || next.dataset.tab;
$$('.tab').forEach(el=>el.classList.toggle('on', el.dataset.tab===t));
next.querySelector('.scroll')?.scrollTo(0,0);
}
/* ---------- Bottom Sheet ---------- */
let sheetOnClose=null;
function openSheet(headHTML, bodyHTML, onClose){
$('#sheetHead').innerHTML = headHTML;
$('#sheetBody').innerHTML = bodyHTML;
sheetOnClose = onClose||null;
scrim.classList.add('show'); sheet.classList.add('show');
}
function closeSheet(){
sheet.classList.remove('show'); scrim.classList.remove('show');
if(sheetOnClose){const f=sheetOnClose;sheetOnClose=null;setTimeout(f,200);}
}
scrim.addEventListener('click',()=>{ if(sheet.classList.contains('show')) closeSheet(); });
// 下滑关闭
let sy=0;
sheet.addEventListener('touchstart',e=>sy=e.touches[0].clientY,{passive:true});
sheet.addEventListener('touchmove',e=>{ if(e.touches[0].clientY-sy>70){closeSheet();sy=1e9;} },{passive:true});
/* ---------- 对话框 ---------- */
function openDialog(html){ dialog.innerHTML=html; scrim.classList.add('show'); dialog.classList.add('show'); }
function closeDialog(){ dialog.classList.remove('show'); if(!sheet.classList.contains('show')) scrim.classList.remove('show'); }
/* ---------- Toast ---------- */
function toast(msg, link){
const wrap=$('#toastWrap');
const el=document.createElement('div');el.className='toast';
el.innerHTML='<span class="tdot">'+I['svg-check-sm']+'</span><span>'+msg+(link?' <span class="link">'+link+'</span>':'')+'</span>';
wrap.appendChild(el);
requestAnimationFrame(()=>el.classList.add('show'));
setTimeout(()=>{el.classList.remove('show');setTimeout(()=>el.remove(),400);},2600);
}
/* ============================================================
屏 1:意图选择
============================================================ */
function renderIntents(){
const g=$('#intentGrid');
g.innerHTML = APP_DATA.intents.map(it=>`
<div class="intent-card" data-id="${it.id}">
${it.tag?`<span class="intent-tag">${L(it.tag)}</span>`:''}
<div class="intent-ic">${I[it.icon]}</div>
<h3>${L(it.name)}</h3>
<div class="d">${L(it.desc)}</div>
<span class="go"><svg viewBox="0 0 24 24" width="18" height="18" fill="none"><path d="M9 6l6 6-6 6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>`).join('');
// 入场动画
$$('.intent-card',g).forEach((c,i)=>setTimeout(()=>c.classList.add('in'),80+i*70));
$$('.intent-card',g).forEach(c=>c.addEventListener('click',()=>selectIntent(c.dataset.id)));
}
function selectIntent(id){
const intent = APP_DATA.intents.find(x=>x.id===id);
const loader=$('#intentLoader');
$('#loaderText').textContent = T('loaderPrep', L(intent.name));
loader.classList.add('show');
// 模拟预载(成功路径)
setTimeout(()=>{
loader.classList.remove('show');
state.reachedStep = Math.max(state.reachedStep,1);
enterWorkspace(intent);
},950);
}
$('#skipIntent').addEventListener('click',()=>{
toast(T('toastBlank'));
});
/* ============================================================
屏 2:工作区
============================================================ */
const PHASES=['意图','AI 编排','透明计费','导出预检'];
function renderStepper(curIdx){
state.phaseIdx = curIdx;
const labels = PHASES_I18N[LANG];
const sp=$('#stepper1'), lb=$('#stepperLbl1');
sp.innerHTML = labels.map((p,i)=>`<div class="sp ${i<curIdx?'done':i===curIdx?'cur':''}"></div>`).join('');
lb.innerHTML = labels.map((p,i)=>`<span class="${i===curIdx?'on':''}">${p}</span>`).join('');
}
function enterWorkspace(intent){
state.intentId = intent.id;
const scene = curScene();
$('#wsTitle').textContent = L(intent.name) + ' · ' + T('workspace');
$('#wsSub').textContent = L(scene.line);
renderStepper(1);
// 重置:显示空状态引导,隐藏成片结果卡
$('#wsEmpty').style.display='block';
$('#wsPreview').style.display='none';
// 按意图渲染模板与工具
$('#templateRow').innerHTML = scene.templates.map(t=>toolCard(t)).join('');
$('#toolRow').innerHTML = scene.tools.map(t=>toolCard(t)).join('');
bindToolCards();
showScreen('screen-workspace','create');
}
function toolCard(t){
return `<div class="tool" data-id="${t.id}">
<div class="cost ${t.cost.type}">${t.cost.type==='free'?I['svg-gift']+T('free'):t.cost.type==='pro'?I['svg-crown']+'Pro':I['svg-coin']+t.cost.n}</div>
<div class="ti">${I[t.icon]}</div><div class="tn">${L(t.name)}</div></div>`;
}
function bindToolCards(){
const scene=curScene();
$$('#templateRow .tool, #toolRow .tool').forEach(el=>{
el.addEventListener('click',()=>{
const id=el.dataset.id;
const item=[...scene.templates,...scene.tools].find(x=>x.id===id);
if(item.cost.type==='free'){ toast(T('toastApplied', L(item.name))); }
else { openProSheet(item); } // R3:Pro/计费项点选 → 抽屉说明,不打断
});
});
}
$('#wsBack').addEventListener('click',()=>showScreen('screen-intent','create'));
/* ---------- R3Pro 项说明抽屉(善意设计:说明价值 + 给免费替代) ---------- */
function openProSheet(item){
const isCredit=item.cost.type==='credit';
const freeName = L(item.free) || T('freeAltName');
const head=`<div style="flex:1"><div class="t">${L(item.name)} ${costHTML(item.cost)}</div>
<div class="s">${T('proSub', isCredit?T('proSubCredit'):T('proSubPro'))}</div></div>
<button class="iconbtn" onclick="closeSheet()">${I['svg-x']}</button>`;
const body=`
<div class="value-list">
<div class="vrow">${I['svg-crown']}<span>${T('proVal1')}</span></div>
<div class="vrow">${I['svg-crown']}<span>${isCredit?T('proValCredit',item.cost.n):T('proValPro')}</span></div>
</div>
<div class="alt">
<div class="at">${I['svg-gift']} ${T('altTitle')}</div>
<div class="ad">${T('altDesc', freeName)}</div>
</div>
<div style="display:flex;gap:10px;margin-top:16px">
<button class="btn btn-ghost" style="flex:1" onclick="applyFree(this.dataset.f)" data-f="${freeName}">${T('useFree')}</button>
<button class="btn btn-primary" style="flex:1" onclick="applyPro()">${T('usePro', isCredit?item.cost.n:0)}</button>
</div>
<div class="note" style="padding:14px 0 0">${T('proNote')}</div>`;
openSheet(head,body);
}
function applyFree(freeName){ closeSheet(); toast(T('toastSwapFree', freeName)); }
function applyPro(){ closeSheet(); toast(LANG==='en'?'Applied to your project':'已应用到项目'); }
$('#magicBtn').addEventListener('click',openAISheet);
$('#toExport').addEventListener('click',openExportSheet);
/* ============================================================
R2 + R8AI 魔法按钮编排
============================================================ */
function openAISheet(){
const scene=curScene();
const head=`<div style="flex:1"><div class="t">${I['svg-spark']} ${T('aiHead')}</div>
<div class="s">${T('aiHeadSub')}</div></div>
<button class="iconbtn" onclick="closeSheet()">${I['svg-x']}</button>`;
const body=`
<textarea class="ai-input" id="aiInput" maxlength="500" placeholder="${T('aiPlaceholder', L(scene.aiPrompt))}">${L(scene.aiPrompt)}</textarea>
<div class="input-foot"><span class="cnt" id="aiCnt">0/500</span>
<span style="font-size:11.5px;color:var(--text-3)">${T('balance', APP_DATA.creditBalance)}</span></div>
<div class="suggest" id="aiSuggest">${L(scene.aiSuggest).map(s=>`<span class="sg">${s}</span>`).join('')}</div>
<button class="btn btn-primary btn-block" id="aiGo" style="margin-top:18px">${I['svg-spark']} ${T('aiGoBtn')}</button>`;
openSheet(head,body);
const ta=$('#aiInput'), cnt=$('#aiCnt');
const upd=()=>{ const n=ta.value.trim().length; cnt.textContent=n+'/500'; cnt.classList.toggle('err',n===0||n>500); };
ta.addEventListener('input',upd); upd();
$$('#aiSuggest .sg').forEach(s=>s.addEventListener('click',()=>{ta.value=s.textContent;upd();ta.focus();}));
$('#aiGo').addEventListener('click',()=>{
const n=ta.value.trim().length;
if(n===0){ cnt.classList.add('err'); cnt.textContent=T('aiEmptyErr'); return; } // R2.4 校验
if(n>500){ cnt.classList.add('err'); return; }
startOrchestration();
});
}
function startOrchestration(){
state.ai.generated=true; state.reachedStep=Math.max(state.reachedStep,2);
renderStepper(1);
// 重置步骤
state.ai.steps = curScene().aiSteps.map(s=>({...s, status:'idle', edited:false, value:L(s.result)}));
const head=`<div style="flex:1"><div class="t">${T('orchHead')}</div>
<div class="s">${T('orchSub', state.ai.steps.length)}</div></div>`;
const body=`<div id="stepList"></div>
<div id="orchFoot" style="display:none">
<div class="divider" style="margin:8px 0"></div>
<button class="btn btn-primary btn-block" id="orchDone">${I['svg-check-sm']} ${T('orchDoneBtn')}</button>
<div class="note" style="padding:10px 0 0">${T('orchNote')}</div>
</div>`;
openSheet(head,body);
renderSteps(true);
// 逐条入场后开始执行(R2.3 拆解 → 执行)
setTimeout(runAllSteps, 150+state.ai.steps.length*140+300);
}
function renderSteps(stagger){
$('#stepList').innerHTML = state.ai.steps.map((st,i)=>`
<div class="step ${st.status}" id="step-${st.id}">
<div class="st-top">
<div class="st-num">${st.status==='done'?I['svg-check-sm']:(i+1)}</div>
<div class="st-name">${L(st.name)}</div>
</div>
<div class="st-meta">${costHTML(st.cost)}
<span class="badge-ai ${st.edited?'badge-edited':''}">${st.edited?I['svg-edit']+T('badgeEdited'):I['svg-spark']+T('badgeAI')}</span>
</div>
<div class="st-prog"><div class="fill" id="fill-${st.id}"></div></div>
<div class="st-result" id="res-${st.id}">${st.value}</div>
<div class="st-actions">
<button class="mini" onclick="editStep('${st.id}')">${I['svg-edit']}${T('edit')}</button>
<button class="mini" onclick="undoStep('${st.id}')">${I['svg-undo']}${T('undo')}</button>
</div>
</div>`).join('');
// 确保步骤始终可见:首次逐条入场,其余即时显示
const els=$$('#stepList .step');
if(stagger){ els.forEach((s,i)=>setTimeout(()=>s.classList.add('in'), 120+i*140)); }
else { els.forEach(s=>s.classList.add('in')); }
}
function runAllSteps(){
let i=0;
const next=()=>{
if(i>=state.ai.steps.length){ $('#orchFoot').style.display='block'; updatePreviewAfterAI(); return; }
const st=state.ai.steps[i];
// R2.12credit 不足则阻止
if(st.cost.type==='credit' && st.cost.n>APP_DATA.creditBalance){
st.status='idle'; i++; next(); return;
}
st.status='running'; renderSteps();
const fill=$('#fill-'+st.id); let p=0;
const iv=setInterval(()=>{
p+=Math.random()*22+10; if(p>=100){p=100;clearInterval(iv);
st.status='done'; renderSteps();
setTimeout(()=>{ i++; next(); },220);
}
if(fill) fill.style.width=Math.min(p,100)+'%';
},120);
};
next();
}
function updatePreviewAfterAI(){
const scene=curScene();
$('#wsEmpty').style.display='none';
$('#wsPreview').style.display='flex';
$('#pvCap').textContent=scene.previewCap;
$('#pvSub').textContent=scene.previewSub;
}
/* R8:编辑 / 撤销 */
function editStep(id){
const st=state.ai.steps.find(s=>s.id===id);
const head=`<div style="flex:1"><div class="t">${T('editHead', L(st.name))}</div><div class="s">${T('editSub')}</div></div><button class="iconbtn" onclick="reopenOrch()">${I['svg-x']}</button>`;
const body=`<textarea class="ai-input" id="editTa" maxlength="200">${st.value.replace(/<[^>]+>/g,'')}</textarea>
<div class="input-foot"><span class="cnt" id="editCnt"></span></div>
<button class="btn btn-primary btn-block" id="editSave" style="margin-top:14px">${T('editSave')}</button>`;
openSheet(head,body);
const ta=$('#editTa');
$('#editSave').addEventListener('click',()=>{
const v=ta.value.trim();
if(!v){ $('#editCnt').classList.add('err'); $('#editCnt').textContent=T('editEmptyErr'); return; } // R8.4
st.value=v; st.edited=true;
reopenOrch(); toast(T('toastEditSaved'));
});
}
function undoStep(id){
const st=state.ai.steps.find(s=>s.id===id);
openDialog(`<div class="dg-ic">${I['svg-undo']}</div>
<h3>${T('undoTitle')}</h3>
<p>${T('undoBody', L(st.name))}</p>
<div class="dg-actions">
<button class="btn btn-ghost" onclick="closeDialog()">${T('cancel')}</button>
<button class="btn btn-primary" onclick="doUndo('${id}')">${T('undoConfirm')}</button>
</div>`);
}
function doUndo(id){
const st=state.ai.steps.find(s=>s.id===id);
st.status='idle'; st.edited=false; st.value=L(st.result);
closeDialog(); renderSteps(); toast(T('toastUndid', L(st.name)));
}
// 编辑/撤销后回到编排面板
function reopenOrch(){
const head=`<div style="flex:1"><div class="t">${T('orchResultHead')}</div><div class="s">${T('orchResultSub', state.ai.steps.length)}</div></div>`;
const body=`<div id="stepList"></div>
<div id="orchFoot"><div class="divider" style="margin:8px 0"></div>
<button class="btn btn-primary btn-block" id="orchDone">${I['svg-check-sm']} ${T('orchDoneBtn')}</button>
<div class="note" style="padding:10px 0 0">${T('orchNote')}</div></div>`;
openSheet(head,body);
renderSteps();
}
// 事件委托:完成编排
document.addEventListener('click',e=>{ if(e.target.closest('#orchDone')){ closeSheet(); toast(T('toastPlanApplied'),T('viewLink')); } });
/* ============================================================
R4:导出预检(Pre-flight
============================================================ */
function exportCompute(){
let credit=0, proCount=0, res='720p', noWm=false;
APP_DATA.exportItems.forEach(it=>{
const sel=state.exportSel[it.key];
if(sel==='pro'){
proCount++;
if(it.pro.kind==='credit') credit+=it.pro.n;
if(it.key==='res') res='1080p';
if(it.key==='wm') noWm=true;
}
});
const time = APP_DATA.exportTimeBase + (res==='1080p'?9:0) + proCount*2;
const enough = credit<=APP_DATA.creditBalance;
return {credit,proCount,res,noWm,time,enough};
}
function openExportSheet(){
state.reachedStep=Math.max(state.reachedStep,3);
renderStepper(3);
renderExportSheet();
}
function renderExportSheet(){
const c=exportCompute();
const head=`<div style="flex:1"><div class="t">${I['svg-export']} ${T('exportHead')}</div>
<div class="s">${T('exportHeadSub')}</div></div>
<button class="iconbtn" onclick="cancelExport()">${I['svg-x']}</button>`;
const summary=`<div class="summary-grid">
<div class="sm"><div class="smk">${I['ic-res']} ${T('kRes')}</div><div class="smv ${c.res==='1080p'?'':'warn'}">${c.res}</div></div>
<div class="sm"><div class="smk">${I['ic-wm']} ${T('kWm')}</div><div class="smv ${c.noWm?'ok':'warn'}">${c.noWm?T('wmNo'):T('wmYes')}</div></div>
<div class="sm ${!c.enough?'alert':''}"><div class="smk">${I['svg-coin']} ${T('kCost')}</div><div class="smv ${!c.enough?'':'ok'}">${c.credit} <span style="font-size:12px;color:var(--text-2)">/ ${APP_DATA.creditBalance} credits</span></div></div>
<div class="sm"><div class="smk">${I['ic-clock']} ${T('kTime')}</div><div class="smv">${c.time}s</div></div>
</div>`;
const warn = !c.enough ? `<div class="credit-warn">${I['svg-alert']}<div>${T('creditWarn', c.credit, APP_DATA.creditBalance)}</div></div>`:'';
const proItems = APP_DATA.exportItems.filter(it=>state.exportSel[it.key]==='pro');
const list = proItems.length ? proItems.map(it=>`
<div class="li">
<div class="li-ic">${I[it.icon]||I['svg-crown']}</div>
<div class="li-main"><div class="li-t">${L(it.name)}</div>
<div class="li-s">${L(it.pro.label)} · ${it.pro.kind==='credit'?'<span style="color:var(--credit)">'+it.pro.n+' credits</span>':'<span style="color:var(--pro)">Pro</span>'}</div></div>
<div class="li-r"><button class="swap" onclick="swapFree('${it.key}')">${I['svg-swap']}${T('swapFreeBtn')}</button></div>
</div>`).join('') : `<div class="note" style="padding:8px 0">${T('noProItems')}</div>`;
const body = summary + warn +
`<div class="section-h" style="margin:6px 0 4px"><span class="t" style="font-size:14px">${T('proItemsUsed')}</span></div>
<div class="preflight-card">${list}</div>
<button class="btn btn-primary btn-block" id="confirmExport" ${c.enough?'':'disabled'}>
${c.enough?I['svg-check-sm']+' '+T('confirmExportBtn',c.credit):T('confirmExportDisabled')}</button>
<button class="btn btn-ghost btn-block" style="margin-top:10px" onclick="cancelExport()">${T('backEditBtn')}</button>`;
openSheet(head,body,null);
$('#confirmExport').addEventListener('click',()=>{ if(exportCompute().enough) confirmExport(); });
}
function swapFree(key){
const it=APP_DATA.exportItems.find(x=>x.key===key);
state.exportSel[key]='free';
toast(T('toastSwapFree', L(it.freeAlt.label)));
renderExportSheet(); // R4.5:同步更新清单
}
function cancelExport(){ closeSheet(); } // R4.9:保留编辑返回
function confirmExport(){
const c=exportCompute();
closeSheet();
state.reachedStep=4;
renderDone(c);
showScreen('screen-done','create');
setTimeout(()=>toast(T('toastExportOk')),500);
}
/* ============================================================
屏 3:完成态
============================================================ */
function renderDone(c){
$('#readyCard').innerHTML=`
<div class="rc-row"><span class="k">${T('dRes')}</span><span class="v">${c.res}</span></div>
<div class="rc-row"><span class="k">${T('dWm')}</span><span class="v" style="color:${c.noWm?'var(--free)':'var(--credit)'}">${c.noWm?T('wmNo'):T('wmYes')}</span></div>
<div class="rc-row"><span class="k">${T('dCost')}</span><span class="v">${c.credit} credits</span></div>
<div class="rc-row"><span class="k">${T('dTime')}</span><span class="v">${c.time}s</span></div>`;
}
$('#doneRestart').addEventListener('click',()=>{
// 重置
APP_DATA.exportItems.forEach(it=>state.exportSel[it.key]='pro');
state.ai.generated=false; state.reachedStep=0;
showScreen('screen-intent','create');
});
$('#doneCompare').addEventListener('click',()=>{ renderCompare(); showScreen('screen-compare','compare'); });
/* ============================================================
屏 4Before / After 对比(R5
============================================================ */
function renderCompare(){
const cm=APP_DATA.comparison;
// 三项量化指标
$('#cmpMetrics').innerHTML = cm.metrics.map(m=>{
const diff=m.before-m.after;
const pct = m.before===0 ? T('pctNA') : Math.round(diff/m.before*100)+'%';
const good = m.betterLower ? (m.after<m.before) : (m.after>m.before);
const arrow = m.after<m.before?'↓':(m.after>m.before?'↑':'·');
const u=L(m.unit);
return `<div class="mc">
<div class="mcv" style="color:var(--free)">${m.after}<span style="font-size:13px;color:var(--text-3)">${u}</span></div>
<div class="mcl">${L(m.label)}</div>
<div class="mcd ${good?'good':'bad'}">${arrow} ${pct===T('pctNA')?pct:('-'+pct)}</div>
<div style="font-size:10px;color:var(--text-3);margin-top:6px">${T('wasVal', m.before, u)}</div>
</div>`;
}).join('');
drawFlow('before');
$('#ctBefore').classList.add('on'); $('#ctAfter').classList.remove('on');
}
function drawFlow(which){
const list=APP_DATA.comparison[which];
$('#cmpFlow').innerHTML = list.map((n,i)=>{
const tag = n.type==='decision'?`<span class="ntag">${T('tagDecision')}</span>`:n.type==='block'?`<span class="ntag">${T('tagBlock')}</span>`:'';
const num = n.type==='block'?'!':(n.type==='ok'?'✓':i+1);
return `<div class="flow-node ${n.type} fade-up" style="animation-delay:${i*45}ms">
<div class="dotcol"><div class="nd">${num}</div><div class="ln"></div></div>
<div class="nbody"><div class="ntitle">${L(n.t)}</div>${tag}</div>
</div>`;
}).join('');
}
$('#ctBefore').addEventListener('click',()=>{$('#ctBefore').classList.add('on');$('#ctAfter').classList.remove('on');drawFlow('before');});
$('#ctAfter').addEventListener('click',()=>{$('#ctAfter').classList.add('on');$('#ctBefore').classList.remove('on');drawFlow('after');});
/* ============================================================
屏 5:成功指标(R9
============================================================ */
function renderMetrics(){
const M=APP_DATA.metrics;
let html='';
Object.values(M).forEach(g=>{
html+=`<div class="mg-title"><span class="gi" style="background:${g.color}">${I[g.icon]}</span>${L(g.title)}</div>`;
g.items.forEach(it=>{
// 字段完整性校验(R9.6
const ok = it.name!=null && it.cur!=null && it.unit!=null && it.base!=null && it.target!=null;
if(!ok){ html+=`<div class="mrow missing"><div class="mr-name">${it.name?L(it.name):'—'}</div><div class="mr-missing">${T('dataMissing')}</div></div>`; return; }
const hit = it.betterLower ? (it.cur<=it.target) : (it.cur>=it.target);
// 进度条:当前值相对 base→target 的推进
const span=Math.abs(it.target-it.base)||1;
let prog=Math.abs(it.cur-it.base)/span*100; prog=Math.max(6,Math.min(prog,100));
html+=`<div class="mrow">
<div class="mr-top">
<div class="mr-left"><span class="mr-name">${L(it.name)}</span><span class="mr-status ${hit?'hit':'miss'}">${hit?T('statHit'):T('statMiss')}</span></div>
<div class="mr-valwrap"><span class="mr-val">${it.cur}</span><span class="mr-unit">${it.unit}</span></div>
</div>
<div class="mr-bar"><div class="mr-fill" style="background:${hit?'var(--free)':'var(--credit)'}" data-w="${prog}"></div></div>
<div class="mr-foot"><span>${T('baseline', it.base, it.unit)}</span><span>${T('target', it.target, it.unit)}</span></div>
</div>`;
});
});
$('#metricsBody').innerHTML=
`<div class="note" style="text-align:left;padding:4px 18px 6px">${T('metricsNote')}</div>`+
`<div style="padding:0 18px">${html}</div>`;
// 动画填充
requestAnimationFrame(()=>setTimeout(()=>$$('#metricsBody .mr-fill').forEach(f=>f.style.width=f.dataset.w+'%'),100));
}
/* ============================================================
底部导航 + 初始化
============================================================ */
$$('.tab').forEach(t=>t.addEventListener('click',()=>{
const tab=t.dataset.tab;
if(tab==='create'){
// 回到创作:若已完成停在完成态,否则意图/工作区
showScreen(state.reachedStep>=1 && state.ai.generated? 'screen-workspace' : 'screen-intent','create');
}else if(tab==='compare'){ renderCompare(); showScreen('screen-compare','compare'); }
else if(tab==='metrics'){ renderMetrics(); showScreen('screen-metrics','metrics'); }
}));
$('#langToggle').addEventListener('click',toggleLang);
applyStaticI18n();
renderIntents();
showScreen('screen-intent','create');
</script>
</body>
</html>