1584 lines
98 KiB
HTML
1584 lines
98 KiB
HTML
<!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:'自评信心(1–5)',en:'Self-rated confidence (1–5)'}, 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'));
|
||
|
||
/* ---------- R3:Pro 项说明抽屉(善意设计:说明价值 + 给免费替代) ---------- */
|
||
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 + R8:AI 魔法按钮编排
|
||
============================================================ */
|
||
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.12:credit 不足则阻止
|
||
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'); });
|
||
|
||
/* ============================================================
|
||
屏 4:Before / 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>
|