Commit 27725d8e authored by yaoke.yk's avatar yaoke.yk

分镜页面整体布局不变,细节优化

parent b7185a90
...@@ -39,6 +39,9 @@ const DEFAULT_VIDEO_MODEL = VIDEO_MODEL_OPTIONS[0].value; ...@@ -39,6 +39,9 @@ const DEFAULT_VIDEO_MODEL = VIDEO_MODEL_OPTIONS[0].value;
const MAX_CHARACTERS = 5; const MAX_CHARACTERS = 5;
const MAX_PROPS = 5; const MAX_PROPS = 5;
const PROMPT_PREVIEW_LIMIT = 80; const PROMPT_PREVIEW_LIMIT = 80;
const FOCUS_RING = "focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/45";
const CONTROL_SURFACE = "border border-slate-200/80 bg-white shadow-[0_1px_2px_rgba(15,23,42,0.04)]";
const SOFT_PANEL = "border border-slate-200/80 bg-white/95 shadow-[0_8px_24px_rgba(15,23,42,0.05)]";
const FREEFORM_PLACEHOLDER = `【镜头1】 时长2s | 蒙太奇快切 | 固定机位快速切出 const FREEFORM_PLACEHOLDER = `【镜头1】 时长2s | 蒙太奇快切 | 固定机位快速切出
画面定格在角色刚转头的瞬间,背景虚化为暖色光斑 画面定格在角色刚转头的瞬间,背景虚化为暖色光斑
...@@ -281,11 +284,11 @@ export function StoryboardWorkspace() { ...@@ -281,11 +284,11 @@ export function StoryboardWorkspace() {
const promptPreviewText = freeformPrompt.trim(); const promptPreviewText = freeformPrompt.trim();
return ( return (
<div className="h-full flex overflow-hidden bg-background"> <div className="h-full flex overflow-hidden bg-[#f4f7fb] text-foreground">
{/* ─── Far Left: Episode list strip ─── */} {/* ─── Far Left: Episode list strip ─── */}
<div className="flex-shrink-0 border-r border-border bg-background flex flex-col items-center overflow-hidden py-5" style={{ width: 80 }}> <div className="flex-shrink-0 border-r border-slate-200/80 bg-white/90 flex flex-col items-center overflow-hidden py-5 shadow-[1px_0_0_rgba(15,23,42,0.02)]" style={{ width: 80 }}>
<span className="font-semibold mb-5" style={{ color: "#9ba0b1", fontSize: 16, letterSpacing: 1 }}> <span className="font-semibold mb-5" style={{ color: "#7c8598", fontSize: 16, letterSpacing: 1 }}>
集数 集数
</span> </span>
<div <div
...@@ -308,18 +311,18 @@ export function StoryboardWorkspace() { ...@@ -308,18 +311,18 @@ export function StoryboardWorkspace() {
key={ep.id} key={ep.id}
onClick={() => handleEpisodeSelect(ep)} onClick={() => handleEpisodeSelect(ep)}
title={ep.title ? `第${ep.episodeNumber}集 · ${ep.title}` : `第${ep.episodeNumber}集`} title={ep.title ? `第${ep.episodeNumber}集 · ${ep.title}` : `第${ep.episodeNumber}集`}
className={`flex items-center justify-center font-bold tabular-nums transition-all flex-shrink-0 ${ className={`flex items-center justify-center font-bold tabular-nums transition-all duration-200 flex-shrink-0 ${
active ? "" : "hover:bg-[#eceef4]" active ? "" : "hover:bg-[#f0f4fb] hover:text-[#475569] hover:shadow-sm"
}`} }`}
style={{ style={{
width: 50, width: 50,
height: 56, height: 56,
borderRadius: 16, borderRadius: 16,
fontSize: 22, fontSize: 22,
backgroundColor: active ? "#1c64ff" : "#f6f7fb", backgroundColor: active ? "#1c64ff" : "#ffffff",
color: active ? "#ffffff" : "#a0a5b5", color: active ? "#ffffff" : "#8b95a7",
boxShadow: active ? "0 6px 14px rgba(28, 100, 255, 0.35)" : "none", boxShadow: active ? "0 12px 24px rgba(28, 100, 255, 0.28)" : "0 1px 2px rgba(15, 23, 42, 0.05)",
border: "none", border: active ? "1px solid rgba(28, 100, 255, 0.35)" : "1px solid rgba(148, 163, 184, 0.22)",
}} }}
> >
{ep.episodeNumber} {ep.episodeNumber}
...@@ -331,16 +334,16 @@ export function StoryboardWorkspace() { ...@@ -331,16 +334,16 @@ export function StoryboardWorkspace() {
</div> </div>
{/* ─── Column 2: Storyboard accordion list (top: prompt summary, then cards) ─── */} {/* ─── Column 2: Storyboard accordion list (top: prompt summary, then cards) ─── */}
<div className="flex flex-col flex-shrink-0 border-r border-border overflow-hidden bg-background" style={{ width: 380 }}> <div className="flex flex-col flex-shrink-0 border-r border-slate-200/80 overflow-hidden bg-white/95" style={{ width: 380 }}>
{/* Top: 分镜表 / 备注描述 */} {/* Top: 分镜表 / 备注描述 */}
<div className="px-4 py-3 border-b border-border bg-[#fafbfd] flex-shrink-0 relative"> <div className="px-4 py-3 border-b border-slate-200/80 bg-white/95 flex-shrink-0 relative shadow-[0_1px_0_rgba(15,23,42,0.03)]">
<span className="absolute left-0 top-3 bottom-3 w-[2px] bg-[#1c64ff]/40 rounded-r-full" /> <span className="absolute left-0 top-3 bottom-3 w-[3px] bg-[#1c64ff]/55 rounded-r-full" />
<div className="flex items-center justify-between mb-1.5"> <div className="flex items-center justify-between mb-1.5">
<span className="text-[11px] font-semibold tracking-[0.12em] uppercase text-muted-foreground">分镜备注</span> <span className="text-[11px] font-semibold tracking-[0.12em] uppercase text-muted-foreground">分镜备注</span>
{promptPreviewText && ( {promptPreviewText && (
<button <button
onClick={() => setShowPromptDetails(true)} onClick={() => setShowPromptDetails(true)}
className="text-[10px] text-[#1c64ff] hover:underline flex items-center gap-0.5 font-medium" className="text-[10px] text-[#1c64ff] hover:text-[#164fd0] flex items-center gap-0.5 font-semibold transition-colors"
title="查看完整内容" title="查看完整内容"
> >
<Info className="w-3 h-3" /> <Info className="w-3 h-3" />
...@@ -358,11 +361,11 @@ export function StoryboardWorkspace() { ...@@ -358,11 +361,11 @@ export function StoryboardWorkspace() {
</div> </div>
{/* Action row: new + AI generate */} {/* Action row: new + AI generate */}
<div className="h-12 px-3 border-b border-border flex items-center gap-2 flex-shrink-0 bg-background"> <div className="h-12 px-3 border-b border-slate-200/80 flex items-center gap-2 flex-shrink-0 bg-white/95">
<button <button
onClick={handleCreate} onClick={handleCreate}
disabled={createSb.isPending || !eid} disabled={createSb.isPending || !eid}
className="flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-lg bg-[#1c64ff] text-white text-xs font-semibold tracking-wide transition-all duration-150 hover:shadow-[0_4px_12px_rgba(28,100,255,0.3)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none" className="flex-1 flex items-center justify-center gap-1.5 py-1.5 rounded-lg bg-[#1c64ff] text-white text-xs font-semibold tracking-wide shadow-[0_6px_14px_rgba(28,100,255,0.22)] transition-all duration-150 hover:bg-[#1857e8] hover:shadow-[0_8px_18px_rgba(28,100,255,0.28)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none"
> >
{createSb.isPending ? <Loader2 className="w-3 h-3 animate-spin" /> : <Plus className="w-3 h-3" />} {createSb.isPending ? <Loader2 className="w-3 h-3 animate-spin" /> : <Plus className="w-3 h-3" />}
新建分镜 新建分镜
...@@ -371,14 +374,14 @@ export function StoryboardWorkspace() { ...@@ -371,14 +374,14 @@ export function StoryboardWorkspace() {
onClick={handleGenerateAll} onClick={handleGenerateAll}
disabled={generateSbs.isPending || !eid} disabled={generateSbs.isPending || !eid}
title="AI批量生成分镜" title="AI批量生成分镜"
className="p-2 rounded-lg border border-border bg-background text-muted-foreground hover:bg-[#f6f7fb] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50" className={`p-2 rounded-lg text-muted-foreground hover:bg-[#f6f9ff] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50 ${CONTROL_SURFACE}`}
> >
{generateSbs.isPending ? <Loader2 className="w-4 h-4 animate-spin" /> : <Wand2 className="w-4 h-4" />} {generateSbs.isPending ? <Loader2 className="w-4 h-4 animate-spin" /> : <Wand2 className="w-4 h-4" />}
</button> </button>
</div> </div>
{/* Cards (accordion) */} {/* Cards (accordion) */}
<div className="flex-1 overflow-y-auto py-1"> <div className="flex-1 overflow-y-auto px-2 py-2 bg-[#f7f9fd]">
{!eid ? ( {!eid ? (
<div className="px-3 py-8 text-center"> <div className="px-3 py-8 text-center">
<p className="text-xs text-muted-foreground">请从左侧选择集数</p> <p className="text-xs text-muted-foreground">请从左侧选择集数</p>
...@@ -425,8 +428,8 @@ export function StoryboardWorkspace() { ...@@ -425,8 +428,8 @@ export function StoryboardWorkspace() {
</div> </div>
{/* ─── Column 3: Video generation panel (5-section prompt + duration + button) ─── */} {/* ─── Column 3: Video generation panel (5-section prompt + duration + button) ─── */}
<div className="flex flex-col flex-shrink-0 border-r border-border overflow-hidden bg-[#fafbfd]" style={{ width: 340 }}> <div className="flex flex-col flex-shrink-0 border-r border-slate-200/80 overflow-hidden bg-[#f7f9fd]" style={{ width: 340 }}>
<div className="h-14 px-4 border-b border-border flex items-center flex-shrink-0 bg-background"> <div className="h-14 px-4 border-b border-slate-200/80 flex items-center flex-shrink-0 bg-white/95 shadow-[0_1px_0_rgba(15,23,42,0.03)]">
<span className="text-[13px] font-semibold text-foreground tracking-tight">分镜视频生成</span> <span className="text-[13px] font-semibold text-foreground tracking-tight">分镜视频生成</span>
{expanded && ( {expanded && (
<span className="ml-2 text-[11px] text-muted-foreground tabular-nums"> <span className="ml-2 text-[11px] text-muted-foreground tabular-nums">
...@@ -440,7 +443,7 @@ export function StoryboardWorkspace() { ...@@ -440,7 +443,7 @@ export function StoryboardWorkspace() {
<p className="text-xs text-muted-foreground italic mt-4">展开左侧分镜后填写提示词</p> <p className="text-xs text-muted-foreground italic mt-4">展开左侧分镜后填写提示词</p>
) : ( ) : (
<> <>
<div className="flex items-start gap-1.5 px-2.5 py-2 rounded-lg bg-[#1c64ff]/[0.06] border border-[#1c64ff]/15"> <div className="flex items-start gap-1.5 px-3 py-2.5 rounded-xl bg-white border border-[#1c64ff]/15 shadow-[0_4px_14px_rgba(28,100,255,0.06)]">
<Sparkles className="w-3.5 h-3.5 text-[#1c64ff] mt-0.5 flex-shrink-0" /> <Sparkles className="w-3.5 h-3.5 text-[#1c64ff] mt-0.5 flex-shrink-0" />
<p className="text-[11px] text-[#1c64ff] leading-relaxed"> <p className="text-[11px] text-[#1c64ff] leading-relaxed">
角色、场景、道具预设已默认参考,无需再次添加引用 角色、场景、道具预设已默认参考,无需再次添加引用
...@@ -450,13 +453,13 @@ export function StoryboardWorkspace() { ...@@ -450,13 +453,13 @@ export function StoryboardWorkspace() {
value={freeformPrompt} value={freeformPrompt}
onChange={(e) => setFreeformPrompt(e.target.value)} onChange={(e) => setFreeformPrompt(e.target.value)}
placeholder={FREEFORM_PLACEHOLDER} placeholder={FREEFORM_PLACEHOLDER}
className="flex-1 min-h-[280px] px-3 py-2.5 rounded-lg border border-border bg-background text-sm text-foreground leading-relaxed resize-none focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 font-mono" className={`flex-1 min-h-[280px] px-3 py-2.5 rounded-xl border border-slate-200/80 bg-white text-sm text-foreground leading-relaxed resize-none font-mono shadow-inner shadow-slate-100/80 ${FOCUS_RING}`}
/> />
</> </>
)} )}
</div> </div>
<div className="px-3 py-3 border-t border-border flex-shrink-0 bg-background space-y-2"> <div className="px-3 py-3 border-t border-slate-200/80 flex-shrink-0 bg-white/95 space-y-2 shadow-[0_-1px_0_rgba(15,23,42,0.03)]">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="relative flex-1"> <div className="relative flex-1">
<select <select
...@@ -464,7 +467,7 @@ export function StoryboardWorkspace() { ...@@ -464,7 +467,7 @@ export function StoryboardWorkspace() {
onChange={(e) => setSelectedModel(e.target.value)} onChange={(e) => setSelectedModel(e.target.value)}
disabled={!expanded} disabled={!expanded}
title="视频生成模型" title="视频生成模型"
className="w-full appearance-none pl-3 pr-7 py-2 rounded-lg border border-border bg-[#fafbfd] text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 cursor-pointer disabled:opacity-50 transition-colors" className={`w-full appearance-none pl-3 pr-7 py-2 rounded-lg bg-[#f8fafc] text-xs text-foreground cursor-pointer disabled:opacity-50 transition-colors ${CONTROL_SURFACE} ${FOCUS_RING}`}
> >
{VIDEO_MODEL_OPTIONS.map((m) => ( {VIDEO_MODEL_OPTIONS.map((m) => (
<option key={m.value} value={m.value}>{m.label}</option> <option key={m.value} value={m.value}>{m.label}</option>
...@@ -477,7 +480,7 @@ export function StoryboardWorkspace() { ...@@ -477,7 +480,7 @@ export function StoryboardWorkspace() {
value={selectedRatio} value={selectedRatio}
onChange={(e) => setSelectedRatio(e.target.value)} onChange={(e) => setSelectedRatio(e.target.value)}
disabled={!expanded} disabled={!expanded}
className="w-full appearance-none pl-3 pr-7 py-2 rounded-lg border border-border bg-[#fafbfd] text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 cursor-pointer disabled:opacity-50 transition-colors" className={`w-full appearance-none pl-3 pr-7 py-2 rounded-lg bg-[#f8fafc] text-xs text-foreground cursor-pointer disabled:opacity-50 transition-colors ${CONTROL_SURFACE} ${FOCUS_RING}`}
> >
{RATIO_OPTIONS.map((r) => ( {RATIO_OPTIONS.map((r) => (
<option key={r.value} value={r.value}>{r.label}</option> <option key={r.value} value={r.value}>{r.label}</option>
...@@ -490,7 +493,7 @@ export function StoryboardWorkspace() { ...@@ -490,7 +493,7 @@ export function StoryboardWorkspace() {
value={selectedDuration} value={selectedDuration}
onChange={(e) => setSelectedDuration(Number(e.target.value))} onChange={(e) => setSelectedDuration(Number(e.target.value))}
disabled={!expanded} disabled={!expanded}
className="appearance-none pl-2 pr-6 py-2 rounded-lg border border-border bg-[#fafbfd] text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-[#1c64ff]/20 focus:border-[#1c64ff]/40 cursor-pointer w-16 disabled:opacity-50 transition-colors tabular-nums" className={`appearance-none pl-2 pr-6 py-2 rounded-lg bg-[#f8fafc] text-xs text-foreground cursor-pointer w-16 disabled:opacity-50 transition-colors tabular-nums ${CONTROL_SURFACE} ${FOCUS_RING}`}
> >
{DURATION_OPTIONS.map((d) => ( {DURATION_OPTIONS.map((d) => (
<option key={d} value={d}>{d}</option> <option key={d} value={d}>{d}</option>
...@@ -504,8 +507,8 @@ export function StoryboardWorkspace() { ...@@ -504,8 +507,8 @@ export function StoryboardWorkspace() {
title={audioOn ? "已开启配音" : "已关闭配音"} title={audioOn ? "已开启配音" : "已关闭配音"}
className={`p-2 rounded-lg border text-xs transition-colors disabled:opacity-50 ${ className={`p-2 rounded-lg border text-xs transition-colors disabled:opacity-50 ${
audioOn audioOn
? "border-[#1c64ff] bg-[#1c64ff]/10 text-[#1c64ff]" ? "border-[#1c64ff] bg-[#1c64ff]/10 text-[#1c64ff] shadow-[0_4px_12px_rgba(28,100,255,0.12)]"
: "border-border bg-[#fafbfd] text-muted-foreground hover:bg-[#eceef4]" : "border-slate-200/80 bg-[#f8fafc] text-muted-foreground hover:bg-[#eef4ff] hover:text-[#1c64ff]"
}`} }`}
> >
{audioOn ? <Volume2 className="w-3.5 h-3.5" /> : <VolumeX className="w-3.5 h-3.5" />} {audioOn ? <Volume2 className="w-3.5 h-3.5" /> : <VolumeX className="w-3.5 h-3.5" />}
...@@ -516,7 +519,7 @@ export function StoryboardWorkspace() { ...@@ -516,7 +519,7 @@ export function StoryboardWorkspace() {
onClick={handleGenerate} onClick={handleGenerate}
disabled={!canGenerate || generatingVideoId === expanded?.id} disabled={!canGenerate || generatingVideoId === expanded?.id}
title={!canGenerate ? "需要:至少 1 个出镜角色 + 场景图 + 提示词" : ""} title={!canGenerate ? "需要:至少 1 个出镜角色 + 场景图 + 提示词" : ""}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg bg-[#1c64ff] text-white text-xs font-semibold tracking-wide transition-all duration-150 hover:shadow-[0_6px_14px_rgba(28,100,255,0.35)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none whitespace-nowrap" className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-[#1c64ff] text-white text-xs font-semibold tracking-wide shadow-[0_9px_20px_rgba(28,100,255,0.26)] transition-all duration-150 hover:bg-[#1857e8] hover:shadow-[0_12px_24px_rgba(28,100,255,0.32)] hover:-translate-y-px disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none whitespace-nowrap"
> >
{generatingVideoId === expanded?.id ? ( {generatingVideoId === expanded?.id ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" /> <Loader2 className="w-3.5 h-3.5 animate-spin" />
...@@ -529,8 +532,8 @@ export function StoryboardWorkspace() { ...@@ -529,8 +532,8 @@ export function StoryboardWorkspace() {
</div> </div>
{/* ─── Column 4: Big video preview + thumbnail timeline ─── */} {/* ─── Column 4: Big video preview + thumbnail timeline ─── */}
<div className="flex-1 flex flex-col overflow-hidden min-w-0 bg-background"> <div className="flex-1 flex flex-col overflow-hidden min-w-0 bg-white/90">
<div className="h-14 px-5 border-b border-border flex items-center justify-between flex-shrink-0 bg-background"> <div className="h-14 px-5 border-b border-slate-200/80 flex items-center justify-between flex-shrink-0 bg-white/95 shadow-[0_1px_0_rgba(15,23,42,0.03)]">
<span className="text-[13px] font-semibold text-foreground tracking-tight"> <span className="text-[13px] font-semibold text-foreground tracking-tight">
视频预览 视频预览
{previewSb && ( {previewSb && (
...@@ -545,7 +548,7 @@ export function StoryboardWorkspace() { ...@@ -545,7 +548,7 @@ export function StoryboardWorkspace() {
href={assemblyTask.downloadUrl} href={assemblyTask.downloadUrl}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-500 text-white text-xs font-medium hover:bg-emerald-600 hover:shadow-[0_4px_12px_rgba(16,185,129,0.3)] transition-all duration-150" className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-500 text-white text-xs font-medium shadow-[0_6px_14px_rgba(16,185,129,0.22)] hover:bg-emerald-600 hover:shadow-[0_8px_18px_rgba(16,185,129,0.28)] transition-all duration-150"
> >
<Download className="w-3.5 h-3.5" /> <Download className="w-3.5 h-3.5" />
下载合集 下载合集
...@@ -559,7 +562,7 @@ export function StoryboardWorkspace() { ...@@ -559,7 +562,7 @@ export function StoryboardWorkspace() {
assemblyTask?.status === "pending" || assemblyTask?.status === "pending" ||
storyboards.length === 0 storyboards.length === 0
} }
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border bg-background text-xs text-foreground hover:bg-[#f6f7fb] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50 disabled:hover:bg-background disabled:hover:border-border disabled:hover:text-foreground" className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs text-foreground hover:bg-[#f6f9ff] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors disabled:opacity-50 disabled:hover:bg-white disabled:hover:border-slate-200 disabled:hover:text-foreground ${CONTROL_SURFACE}`}
> >
{startAssembly.isPending || assemblyTask?.status === "running" ? ( {startAssembly.isPending || assemblyTask?.status === "running" ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" /> <Loader2 className="w-3.5 h-3.5 animate-spin" />
...@@ -570,7 +573,7 @@ export function StoryboardWorkspace() { ...@@ -570,7 +573,7 @@ export function StoryboardWorkspace() {
</button> </button>
<button <button
onClick={() => navigate(`/project/${projectId}/video`)} onClick={() => navigate(`/project/${projectId}/video`)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border bg-background text-xs text-foreground hover:bg-[#f6f7fb] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors" className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs text-foreground hover:bg-[#f6f9ff] hover:border-[#1c64ff]/30 hover:text-[#1c64ff] transition-colors ${CONTROL_SURFACE}`}
> >
视频管理 视频管理
<ArrowRight className="w-3.5 h-3.5" /> <ArrowRight className="w-3.5 h-3.5" />
...@@ -578,14 +581,14 @@ export function StoryboardWorkspace() { ...@@ -578,14 +581,14 @@ export function StoryboardWorkspace() {
</div> </div>
</div> </div>
<div className="flex-1 overflow-y-auto p-5 min-h-0 bg-[#fafbfd]"> <div className="flex-1 overflow-y-auto p-5 min-h-0 bg-[#f4f7fb]">
{!previewSb ? ( {!previewSb ? (
<div className="h-full flex items-center justify-center"> <div className="h-full flex items-center justify-center">
<p className="text-sm text-muted-foreground">展开任意分镜后查看视频</p> <p className="text-sm text-muted-foreground">展开任意分镜后查看视频</p>
</div> </div>
) : previewTask?.status === "succeeded" && previewTask.resultVideoUrl ? ( ) : previewTask?.status === "succeeded" && previewTask.resultVideoUrl ? (
<div className="space-y-3"> <div className="space-y-3">
<div className="rounded-xl overflow-hidden bg-black aspect-video shadow-[0_8px_24px_rgba(15,23,42,0.12)]"> <div className="rounded-2xl overflow-hidden bg-black aspect-video shadow-[0_18px_45px_rgba(15,23,42,0.18)] ring-1 ring-black/10">
<video <video
key={previewTask.resultVideoUrl} key={previewTask.resultVideoUrl}
src={previewTask.resultVideoUrl} src={previewTask.resultVideoUrl}
...@@ -605,16 +608,16 @@ export function StoryboardWorkspace() { ...@@ -605,16 +608,16 @@ export function StoryboardWorkspace() {
</a> </a>
</div> </div>
) : previewTask?.status === "running" || previewTask?.status === "submitted" || previewTask?.status === "pending" ? ( ) : previewTask?.status === "running" || previewTask?.status === "submitted" || previewTask?.status === "pending" ? (
<div className="h-full flex flex-col items-center justify-center gap-4"> <div className={`h-full flex flex-col items-center justify-center gap-4 rounded-2xl ${SOFT_PANEL}`}>
<div className="relative w-16 h-16 flex items-center justify-center"> <div className="relative w-16 h-16 flex items-center justify-center">
<span className="absolute inset-0 rounded-full bg-[#1c64ff]/10 animate-pulse" /> <span className="absolute inset-0 rounded-full bg-[#1c64ff]/10 animate-pulse ring-8 ring-[#1c64ff]/5" />
<Loader2 className="w-8 h-8 text-[#1c64ff] animate-spin relative" /> <Loader2 className="w-8 h-8 text-[#1c64ff] animate-spin relative" />
</div> </div>
<p className="text-sm font-semibold text-foreground tracking-tight">视频生成中…</p> <p className="text-sm font-semibold text-foreground tracking-tight">视频生成中…</p>
<p className="text-xs text-muted-foreground">通常需要 30-60 秒</p> <p className="text-xs text-muted-foreground">通常需要 30-60 秒</p>
</div> </div>
) : previewTask?.status === "failed" ? ( ) : previewTask?.status === "failed" ? (
<div className="rounded-xl border border-red-200 bg-red-50 p-6"> <div className="rounded-2xl border border-red-200 bg-red-50 p-6 shadow-[0_8px_24px_rgba(185,28,28,0.08)]">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<X className="w-4 h-4 text-red-500" /> <X className="w-4 h-4 text-red-500" />
<span className="text-sm font-medium text-red-700">生成失败</span> <span className="text-sm font-medium text-red-700">生成失败</span>
...@@ -623,7 +626,7 @@ export function StoryboardWorkspace() { ...@@ -623,7 +626,7 @@ export function StoryboardWorkspace() {
</div> </div>
) : ( ) : (
<div className="h-full flex flex-col items-center justify-center gap-4"> <div className="h-full flex flex-col items-center justify-center gap-4">
<div className="w-full max-w-sm aspect-video rounded-xl border-[1.5px] border-dashed border-border bg-background flex flex-col items-center justify-center gap-3"> <div className="w-full max-w-sm aspect-video rounded-2xl border-[1.5px] border-dashed border-slate-300/80 bg-white/90 shadow-[0_10px_30px_rgba(15,23,42,0.05)] flex flex-col items-center justify-center gap-3">
<Play className="w-12 h-12 text-muted-foreground/30" /> <Play className="w-12 h-12 text-muted-foreground/30" />
<p className="text-sm text-muted-foreground">尚未生成视频</p> <p className="text-sm text-muted-foreground">尚未生成视频</p>
<p className="text-xs text-muted-foreground/70">在左侧填好素材+提示词后生成</p> <p className="text-xs text-muted-foreground/70">在左侧填好素材+提示词后生成</p>
...@@ -634,8 +637,8 @@ export function StoryboardWorkspace() { ...@@ -634,8 +637,8 @@ export function StoryboardWorkspace() {
{/* Thumbnail timeline */} {/* Thumbnail timeline */}
{storyboards.length > 0 && ( {storyboards.length > 0 && (
<div className="border-t border-border bg-background flex-shrink-0"> <div className="border-t border-slate-200/80 bg-white/95 flex-shrink-0 shadow-[0_-1px_0_rgba(15,23,42,0.03)]">
<div className="px-3 py-2.5 flex items-center gap-2 overflow-x-auto"> <div className="px-3 py-3 flex items-center gap-2 overflow-x-auto">
{storyboards.map((sb) => { {storyboards.map((sb) => {
const t = getTaskForSb(sb); const t = getTaskForSb(sb);
const isActive = activeVideoSbId === sb.id; const isActive = activeVideoSbId === sb.id;
...@@ -650,8 +653,8 @@ export function StoryboardWorkspace() { ...@@ -650,8 +653,8 @@ export function StoryboardWorkspace() {
title={`分镜 ${String(sb.sequenceNum).padStart(3, "0")}`} title={`分镜 ${String(sb.sequenceNum).padStart(3, "0")}`}
className={`relative flex-shrink-0 w-[88px] h-[52px] rounded-lg overflow-hidden border transition-all duration-150 ${ className={`relative flex-shrink-0 w-[88px] h-[52px] rounded-lg overflow-hidden border transition-all duration-150 ${
isActive isActive
? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25 shadow-[0_4px_12px_rgba(28,100,255,0.2)]" ? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25 shadow-[0_8px_18px_rgba(28,100,255,0.22)]"
: "border-border hover:border-[#1c64ff]/40 hover:-translate-y-px" : "border-slate-200 hover:border-[#1c64ff]/40 hover:-translate-y-px hover:shadow-sm"
}`} }`}
> >
{url ? ( {url ? (
...@@ -662,7 +665,7 @@ export function StoryboardWorkspace() { ...@@ -662,7 +665,7 @@ export function StoryboardWorkspace() {
className="w-full h-full object-cover pointer-events-none" className="w-full h-full object-cover pointer-events-none"
/> />
) : ( ) : (
<div className="w-full h-full bg-[#f6f7fb] flex items-center justify-center"> <div className="w-full h-full bg-[#f8fafc] flex items-center justify-center">
<ImageIcon className="w-4 h-4 text-muted-foreground/40" /> <ImageIcon className="w-4 h-4 text-muted-foreground/40" />
</div> </div>
)} )}
...@@ -732,24 +735,28 @@ function StoryboardCard(props: StoryboardCardProps) { ...@@ -732,24 +735,28 @@ function StoryboardCard(props: StoryboardCardProps) {
const [descCollapsed, setDescCollapsed] = useState(false); const [descCollapsed, setDescCollapsed] = useState(false);
return ( return (
<div className={`mx-2 my-1 rounded-xl border ${isExpanded ? "border-primary/40 bg-card shadow-sm" : "border-border hover:bg-muted/50"} transition-colors`}> <div className={`my-2 rounded-2xl border transition-all duration-200 ${
isExpanded
? "border-[#1c64ff]/35 bg-white shadow-[0_12px_30px_rgba(15,23,42,0.08)]"
: "border-slate-200/80 bg-white/80 hover:bg-white hover:border-[#1c64ff]/25 hover:shadow-[0_8px_18px_rgba(15,23,42,0.05)]"
}`}>
<button <button
onClick={onToggle} onClick={onToggle}
className="w-full px-3.5 pt-3 pb-2 flex items-center gap-2 text-left" className="w-full px-3.5 pt-3 pb-2.5 flex items-center gap-2 text-left rounded-t-2xl"
> >
<span className="text-sm font-semibold text-foreground flex-1 truncate"> <span className="text-sm font-semibold text-foreground flex-1 truncate">
分镜信息{String(sb.sequenceNum).padStart(3, "0")} 分镜信息{String(sb.sequenceNum).padStart(3, "0")}
</span> </span>
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${dotClass}`} /> <span className={`w-2.5 h-2.5 rounded-full flex-shrink-0 ring-4 ring-white ${dotClass}`} />
</button> </button>
{isExpanded && ( {isExpanded && (
<div className="px-3.5 pb-3.5 space-y-3.5"> <div className="px-3.5 pb-3.5 space-y-3.5 border-t border-slate-100">
{/* 分镜描述 */} {/* 分镜描述 */}
<div> <div>
<button <button
onClick={() => setDescCollapsed((v) => !v)} onClick={() => setDescCollapsed((v) => !v)}
className="w-full flex items-center justify-between mb-1.5" className="w-full flex items-center justify-between mb-1.5 rounded-md text-left"
> >
<span className="text-xs font-medium text-foreground">分镜描述</span> <span className="text-xs font-medium text-foreground">分镜描述</span>
{descCollapsed {descCollapsed
...@@ -762,7 +769,7 @@ function StoryboardCard(props: StoryboardCardProps) { ...@@ -762,7 +769,7 @@ function StoryboardCard(props: StoryboardCardProps) {
value={props.shortDescription} value={props.shortDescription}
onChange={(e) => props.onShortDescriptionChange(e.target.value)} onChange={(e) => props.onShortDescriptionChange(e.target.value)}
placeholder="简要描述此分镜的剧情或画面" placeholder="简要描述此分镜的剧情或画面"
className="w-full px-3 py-2 rounded-lg border border-border bg-background text-xs text-foreground leading-relaxed resize-none focus:outline-none focus:ring-1 focus:ring-primary/30" className={`w-full px-3 py-2 rounded-xl border border-slate-200/80 bg-[#f8fafc] text-xs text-foreground leading-relaxed resize-none shadow-inner shadow-slate-100/80 ${FOCUS_RING}`}
/> />
)} )}
</div> </div>
...@@ -796,7 +803,7 @@ function StoryboardCard(props: StoryboardCardProps) { ...@@ -796,7 +803,7 @@ function StoryboardCard(props: StoryboardCardProps) {
<div className="pt-1 flex justify-end"> <div className="pt-1 flex justify-end">
<button <button
onClick={onDelete} onClick={onDelete}
className="text-[11px] text-destructive hover:underline flex items-center gap-1" className="text-[11px] text-destructive hover:text-red-700 flex items-center gap-1 rounded-md px-2 py-1 hover:bg-red-50 transition-colors"
> >
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
删除该分镜 删除该分镜
...@@ -825,13 +832,13 @@ function CharacterRowList({ ...@@ -825,13 +832,13 @@ function CharacterRowList({
characterKeys, keyToCharacter, characters, onRemove, onSwap, onAdd, characterKeys, keyToCharacter, characters, onRemove, onSwap, onAdd,
}: CharacterRowListProps) { }: CharacterRowListProps) {
return ( return (
<div> <div className="rounded-xl bg-[#f8fafc] p-3 ring-1 ring-slate-200/70">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs font-medium text-foreground">出镜角色</span> <span className="text-xs font-medium text-foreground">出镜角色</span>
<button <button
onClick={onAdd} onClick={onAdd}
disabled={characterKeys.length >= MAX_CHARACTERS} disabled={characterKeys.length >= MAX_CHARACTERS}
className="flex items-center gap-0.5 text-[11px] text-primary hover:underline disabled:opacity-40 disabled:no-underline" className="flex items-center gap-0.5 text-[11px] text-[#1c64ff] hover:text-[#164fd0] font-medium disabled:opacity-40"
> >
<Plus className="w-3 h-3" /> <Plus className="w-3 h-3" />
添加 添加
...@@ -889,8 +896,8 @@ function CharacterRow({ ...@@ -889,8 +896,8 @@ function CharacterRow({
}); });
return ( return (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5 rounded-xl bg-white p-1.5 ring-1 ring-slate-200/70">
<div className="w-8 h-8 rounded-md overflow-hidden border border-border bg-muted flex-shrink-0"> <div className="w-8 h-8 rounded-lg overflow-hidden border border-slate-200 bg-slate-100 flex-shrink-0">
{url ? ( {url ? (
<img src={url} alt={character?.name ?? ""} className="w-full h-full object-cover" /> <img src={url} alt={character?.name ?? ""} className="w-full h-full object-cover" />
) : ( ) : (
...@@ -901,7 +908,7 @@ function CharacterRow({ ...@@ -901,7 +908,7 @@ function CharacterRow({
<select <select
value={tosKey} value={tosKey}
onChange={(e) => onSwap(e.target.value)} onChange={(e) => onSwap(e.target.value)}
className="w-full appearance-none pl-2 pr-6 py-1.5 rounded border border-border bg-background text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-primary/30 cursor-pointer truncate" className={`w-full appearance-none pl-2 pr-6 py-1.5 rounded-lg border border-slate-200/80 bg-white text-xs text-foreground cursor-pointer truncate ${FOCUS_RING}`}
> >
{!character && <option value={tosKey}>(已失效)</option>} {!character && <option value={tosKey}>(已失效)</option>}
{options.map((c) => { {options.map((c) => {
...@@ -914,7 +921,7 @@ function CharacterRow({ ...@@ -914,7 +921,7 @@ function CharacterRow({
<div className="relative flex-1 min-w-0"> <div className="relative flex-1 min-w-0">
<select <select
disabled disabled
className="w-full appearance-none pl-2 pr-6 py-1.5 rounded border border-border bg-muted/40 text-xs text-muted-foreground cursor-not-allowed truncate" className="w-full appearance-none pl-2 pr-6 py-1.5 rounded-lg border border-slate-200/80 bg-slate-100/80 text-xs text-muted-foreground cursor-not-allowed truncate"
> >
<option>未配置音色</option> <option>未配置音色</option>
</select> </select>
...@@ -922,14 +929,14 @@ function CharacterRow({ ...@@ -922,14 +929,14 @@ function CharacterRow({
</div> </div>
<button <button
onClick={onRemove} onClick={onRemove}
className="px-2 py-1.5 rounded border border-border text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground" className="px-2 py-1.5 rounded-lg border border-slate-200/80 text-[11px] text-muted-foreground hover:bg-red-50 hover:text-red-600 transition-colors"
> >
删除 删除
</button> </button>
<button <button
onClick={handleRegen} onClick={handleRegen}
disabled={!character || isRegenerating} disabled={!character || isRegenerating}
className="px-2 py-1.5 rounded border border-border text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-40 flex items-center gap-1" className="px-2 py-1.5 rounded-lg border border-slate-200/80 text-[11px] text-muted-foreground hover:bg-[#f6f9ff] hover:text-[#1c64ff] disabled:opacity-40 flex items-center gap-1 transition-colors"
> >
{isRegenerating && <Loader2 className="w-3 h-3 animate-spin" />} {isRegenerating && <Loader2 className="w-3 h-3 animate-spin" />}
重绘 重绘
...@@ -953,13 +960,13 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -953,13 +960,13 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
const selected = usable.find((s) => s.imageTosKey === selectedKey) ?? null; const selected = usable.find((s) => s.imageTosKey === selectedKey) ?? null;
return ( return (
<div> <div className="rounded-xl bg-[#f8fafc] p-3 ring-1 ring-slate-200/70">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs font-medium text-foreground">分镜场景</span> <span className="text-xs font-medium text-foreground">分镜场景</span>
{usable.length > 0 && ( {usable.length > 0 && (
<button <button
onClick={() => setShowPicker((v) => !v)} onClick={() => setShowPicker((v) => !v)}
className="flex items-center gap-0.5 text-[11px] text-primary hover:underline" className="flex items-center gap-0.5 text-[11px] text-[#1c64ff] hover:text-[#164fd0] font-medium"
> >
<Plus className="w-3 h-3" /> <Plus className="w-3 h-3" />
{selected ? "更换" : "选择"} {selected ? "更换" : "选择"}
...@@ -972,7 +979,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -972,7 +979,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
type="button" type="button"
onClick={() => usable.length > 0 && setShowPicker((v) => !v)} onClick={() => usable.length > 0 && setShowPicker((v) => !v)}
disabled={usable.length === 0} disabled={usable.length === 0}
className="w-full aspect-video rounded-lg overflow-hidden border border-border bg-muted mb-2 block hover:border-primary/40 transition disabled:cursor-not-allowed" className="w-full aspect-video rounded-xl overflow-hidden border border-slate-200/80 bg-slate-100 mb-2 block hover:border-[#1c64ff]/40 hover:shadow-sm transition disabled:cursor-not-allowed"
> >
{selected?.imageUrl ? ( {selected?.imageUrl ? (
<img src={selected.imageUrl} alt={selected.name} className="w-full h-full object-cover" /> <img src={selected.imageUrl} alt={selected.name} className="w-full h-full object-cover" />
...@@ -988,17 +995,17 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -988,17 +995,17 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
{/* Bottom row: scene name + role label + actions menu */} {/* Bottom row: scene name + role label + actions menu */}
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<div className="flex-1 min-w-0 px-2.5 py-1.5 rounded-md border border-border bg-background text-xs text-foreground truncate"> <div className="flex-1 min-w-0 px-2.5 py-1.5 rounded-lg border border-slate-200/80 bg-white text-xs text-foreground truncate">
{selected?.name || <span className="text-muted-foreground">请选择场景</span>} {selected?.name || <span className="text-muted-foreground">请选择场景</span>}
</div> </div>
<div className="px-2.5 py-1.5 rounded-md border border-border bg-muted/30 text-xs text-muted-foreground flex-shrink-0"> <div className="px-2.5 py-1.5 rounded-lg border border-slate-200/80 bg-white/80 text-xs text-muted-foreground flex-shrink-0">
主场景图 主场景图
</div> </div>
<div className="relative flex-shrink-0"> <div className="relative flex-shrink-0">
<button <button
onClick={() => setMenuOpen((v) => !v)} onClick={() => setMenuOpen((v) => !v)}
disabled={!selected} disabled={!selected}
className="p-1.5 rounded-md border border-border text-muted-foreground hover:bg-muted disabled:opacity-40" className="p-1.5 rounded-lg border border-slate-200/80 bg-white text-muted-foreground hover:bg-[#f6f9ff] hover:text-[#1c64ff] disabled:opacity-40 transition-colors"
> >
<MoreHorizontal className="w-3.5 h-3.5" /> <MoreHorizontal className="w-3.5 h-3.5" />
</button> </button>
...@@ -1008,16 +1015,16 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -1008,16 +1015,16 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
className="fixed inset-0 z-10 cursor-default" className="fixed inset-0 z-10 cursor-default"
onClick={() => setMenuOpen(false)} onClick={() => setMenuOpen(false)}
/> />
<div className="absolute right-0 top-full mt-1 z-20 bg-popover border border-border rounded-md shadow-lg overflow-hidden min-w-[100px]"> <div className="absolute right-0 top-full mt-1 z-20 bg-white border border-slate-200/80 rounded-xl shadow-lg overflow-hidden min-w-[100px]">
<button <button
onClick={() => { setShowPicker(true); setMenuOpen(false); }} onClick={() => { setShowPicker(true); setMenuOpen(false); }}
className="w-full px-3 py-1.5 text-left text-xs text-foreground hover:bg-muted" className="w-full px-3 py-1.5 text-left text-xs text-foreground hover:bg-[#f6f9ff]"
> >
更换场景 更换场景
</button> </button>
<button <button
onClick={() => { onSelect(""); setMenuOpen(false); }} onClick={() => { onSelect(""); setMenuOpen(false); }}
className="w-full px-3 py-1.5 text-left text-xs text-destructive hover:bg-muted" className="w-full px-3 py-1.5 text-left text-xs text-destructive hover:bg-red-50"
> >
清除选择 清除选择
</button> </button>
...@@ -1028,7 +1035,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -1028,7 +1035,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
</div> </div>
{showPicker && ( {showPicker && (
<div className="mt-2 rounded-lg border border-border bg-muted/30 p-2"> <div className="mt-2 rounded-xl border border-slate-200/80 bg-white/80 p-2">
{usable.length === 0 ? ( {usable.length === 0 ? (
<p className="text-[11px] text-muted-foreground italic text-center py-2">先到「场景」页创建场景</p> <p className="text-[11px] text-muted-foreground italic text-center py-2">先到「场景」页创建场景</p>
) : ( ) : (
...@@ -1044,7 +1051,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -1044,7 +1051,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
}} }}
title={s.name} title={s.name}
className={`relative aspect-square rounded-md overflow-hidden border-2 ${ className={`relative aspect-square rounded-md overflow-hidden border-2 ${
active ? "border-primary ring-2 ring-primary/30" : "border-border hover:border-primary/40" active ? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25" : "border-slate-200 hover:border-[#1c64ff]/40"
}`} }`}
> >
{s.imageUrl ? ( {s.imageUrl ? (
...@@ -1056,7 +1063,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) { ...@@ -1056,7 +1063,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
<span className="text-[10px] text-white truncate block">{s.name}</span> <span className="text-[10px] text-white truncate block">{s.name}</span>
</div> </div>
{active && ( {active && (
<div className="absolute top-1 right-1 w-4 h-4 rounded-full bg-primary flex items-center justify-center"> <div className="absolute top-1 right-1 w-4 h-4 rounded-full bg-[#1c64ff] flex items-center justify-center shadow-sm">
<Check className="w-2.5 h-2.5 text-white" /> <Check className="w-2.5 h-2.5 text-white" />
</div> </div>
)} )}
...@@ -1090,13 +1097,13 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) { ...@@ -1090,13 +1097,13 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
const canAddMore = selectedKeys.length < MAX_PROPS; const canAddMore = selectedKeys.length < MAX_PROPS;
return ( return (
<div> <div className="rounded-xl bg-[#f8fafc] p-3 ring-1 ring-slate-200/70">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs font-medium text-foreground">场景道具</span> <span className="text-xs font-medium text-foreground">场景道具</span>
<button <button
onClick={() => setShowPicker((v) => !v)} onClick={() => setShowPicker((v) => !v)}
disabled={!canAddMore && !showPicker} disabled={!canAddMore && !showPicker}
className="flex items-center gap-0.5 text-[11px] text-primary hover:underline disabled:opacity-40 disabled:no-underline" className="flex items-center gap-0.5 text-[11px] text-[#1c64ff] hover:text-[#164fd0] font-medium disabled:opacity-40"
> >
<Plus className="w-3 h-3" /> <Plus className="w-3 h-3" />
添加 添加
...@@ -1112,7 +1119,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) { ...@@ -1112,7 +1119,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
key={s.id} key={s.id}
onClick={() => onToggle(s.imageTosKey!)} onClick={() => onToggle(s.imageTosKey!)}
title={`移除 ${s.name}`} title={`移除 ${s.name}`}
className="relative aspect-square rounded-lg overflow-hidden border border-border bg-muted group" className="relative aspect-square rounded-xl overflow-hidden border border-slate-200/80 bg-slate-100 group hover:shadow-sm transition-shadow"
> >
{s.imageUrl ? ( {s.imageUrl ? (
<img src={s.imageUrl} alt={s.name} className="w-full h-full object-cover" /> <img src={s.imageUrl} alt={s.name} className="w-full h-full object-cover" />
...@@ -1131,7 +1138,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) { ...@@ -1131,7 +1138,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
)} )}
{showPicker && ( {showPicker && (
<div className="rounded-lg border border-border bg-muted/30 p-2"> <div className="rounded-xl border border-slate-200/80 bg-white/80 p-2">
{available.length === 0 ? ( {available.length === 0 ? (
<p className="text-[11px] text-muted-foreground italic text-center py-2"> <p className="text-[11px] text-muted-foreground italic text-center py-2">
{allCandidates.length === 0 ? "先到「场景」页创建场景" : "已添加全部可选道具"} {allCandidates.length === 0 ? "先到「场景」页创建场景" : "已添加全部可选道具"}
...@@ -1147,7 +1154,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) { ...@@ -1147,7 +1154,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
}} }}
disabled={!canAddMore} disabled={!canAddMore}
title={s.name} title={s.name}
className="relative aspect-square rounded-md overflow-hidden border border-border bg-background hover:border-primary/40 disabled:opacity-40" className="relative aspect-square rounded-lg overflow-hidden border border-slate-200 bg-white hover:border-[#1c64ff]/40 hover:shadow-sm disabled:opacity-40 transition"
> >
{s.imageUrl ? ( {s.imageUrl ? (
<img src={s.imageUrl} alt={s.name} className="w-full h-full object-cover" /> <img src={s.imageUrl} alt={s.name} className="w-full h-full object-cover" />
...@@ -1200,14 +1207,14 @@ function CharacterPickerModal({ ...@@ -1200,14 +1207,14 @@ function CharacterPickerModal({
const usable = characters.filter((c) => !!characterFrontKey(c)); const usable = characters.filter((c) => !!characterFrontKey(c));
return ( return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" onClick={onClose}> <div className="fixed inset-0 bg-slate-950/55 backdrop-blur-sm z-50 flex items-center justify-center p-4" onClick={onClose}>
<div <div
className="bg-background rounded-xl shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col" className="bg-white rounded-2xl shadow-[0_24px_70px_rgba(15,23,42,0.28)] ring-1 ring-white/60 w-full max-w-2xl max-h-[80vh] flex flex-col overflow-hidden"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="flex items-center justify-between px-5 py-3 border-b border-border"> <div className="flex items-center justify-between px-5 py-3 border-b border-slate-200/80 bg-[#f8fafc]">
<h3 className="text-sm font-semibold text-foreground">添加出镜角色</h3> <h3 className="text-sm font-semibold text-foreground">添加出镜角色</h3>
<button onClick={onClose} className="p-1 hover:bg-muted rounded"> <button onClick={onClose} className="p-1 hover:bg-white rounded-lg transition-colors">
<X className="w-4 h-4 text-muted-foreground" /> <X className="w-4 h-4 text-muted-foreground" />
</button> </button>
</div> </div>
...@@ -1231,8 +1238,8 @@ function CharacterPickerModal({ ...@@ -1231,8 +1238,8 @@ function CharacterPickerModal({
onClick={() => { onPick(k); onClose(); }} onClick={() => { onPick(k); onClose(); }}
className={`relative rounded-lg overflow-hidden border-2 transition ${ className={`relative rounded-lg overflow-hidden border-2 transition ${
isAdded isAdded
? "border-primary opacity-50 cursor-not-allowed" ? "border-[#1c64ff] opacity-50 cursor-not-allowed"
: "border-border hover:border-primary/60" : "border-slate-200 hover:border-[#1c64ff]/60 hover:shadow-sm"
}`} }`}
> >
<div className="aspect-square bg-muted"> <div className="aspect-square bg-muted">
...@@ -1246,7 +1253,7 @@ function CharacterPickerModal({ ...@@ -1246,7 +1253,7 @@ function CharacterPickerModal({
<p className="text-xs font-medium text-foreground truncate">{c.name}</p> <p className="text-xs font-medium text-foreground truncate">{c.name}</p>
</div> </div>
{isAdded && ( {isAdded && (
<div className="absolute top-1 right-1 w-5 h-5 rounded-full bg-primary flex items-center justify-center"> <div className="absolute top-1 right-1 w-5 h-5 rounded-full bg-[#1c64ff] flex items-center justify-center shadow-sm">
<Check className="w-3 h-3 text-white" /> <Check className="w-3 h-3 text-white" />
</div> </div>
)} )}
...@@ -1257,17 +1264,17 @@ function CharacterPickerModal({ ...@@ -1257,17 +1264,17 @@ function CharacterPickerModal({
)} )}
</div> </div>
<div className="px-5 py-3 border-t border-border flex justify-between items-center gap-2"> <div className="px-5 py-3 border-t border-slate-200/80 bg-[#f8fafc] flex justify-between items-center gap-2">
<button <button
onClick={() => setCreateMode(true)} onClick={() => setCreateMode(true)}
className="px-3 py-1.5 rounded-lg border border-primary/40 text-primary text-xs hover:bg-primary/10 flex items-center gap-1.5" className="px-3 py-1.5 rounded-lg border border-[#1c64ff]/35 text-[#1c64ff] text-xs hover:bg-[#1c64ff]/10 flex items-center gap-1.5 transition-colors"
> >
<Plus className="w-3 h-3" /> <Plus className="w-3 h-3" />
创建新角色变体 创建新角色变体
</button> </button>
<button <button
onClick={onClose} onClick={onClose}
className="px-4 py-1.5 rounded-lg border border-border text-xs text-foreground hover:bg-muted" className="px-4 py-1.5 rounded-lg border border-slate-200/80 bg-white text-xs text-foreground hover:bg-[#f6f9ff] transition-colors"
> >
完成 完成
</button> </button>
...@@ -1322,11 +1329,11 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro ...@@ -1322,11 +1329,11 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
}; };
return ( return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4"> <div className="fixed inset-0 bg-slate-950/55 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-background rounded-xl shadow-xl w-full max-w-md max-h-[90vh] flex flex-col"> <div className="bg-white rounded-2xl shadow-[0_24px_70px_rgba(15,23,42,0.28)] ring-1 ring-white/60 w-full max-w-md max-h-[90vh] flex flex-col overflow-hidden">
<div className="flex items-center justify-between px-5 py-3 border-b border-border"> <div className="flex items-center justify-between px-5 py-3 border-b border-slate-200/80 bg-[#f8fafc]">
<h3 className="text-sm font-semibold text-foreground">新增出镜角色变体</h3> <h3 className="text-sm font-semibold text-foreground">新增出镜角色变体</h3>
<button onClick={onClose} className="p-1 hover:bg-muted rounded"> <button onClick={onClose} className="p-1 hover:bg-white rounded-lg transition-colors">
<X className="w-4 h-4 text-muted-foreground" /> <X className="w-4 h-4 text-muted-foreground" />
</button> </button>
</div> </div>
...@@ -1342,7 +1349,7 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro ...@@ -1342,7 +1349,7 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="例如:陆长生 - 白衣" placeholder="例如:陆长生 - 白衣"
className="w-full px-3 py-2 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20" className={`w-full px-3 py-2 rounded-xl border border-slate-200/80 bg-white text-sm ${FOCUS_RING}`}
/> />
</div> </div>
<div> <div>
...@@ -1352,24 +1359,24 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro ...@@ -1352,24 +1359,24 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
value={imagePrompt} value={imagePrompt}
onChange={(e) => setImagePrompt(e.target.value)} onChange={(e) => setImagePrompt(e.target.value)}
placeholder="例如:男性,二十岁出头,身穿白色长袍,气质温雅,黑色长发束起" placeholder="例如:男性,二十岁出头,身穿白色长袍,气质温雅,黑色长发束起"
className="w-full px-3 py-2 rounded-lg border border-border bg-background text-sm leading-relaxed resize-none focus:outline-none focus:ring-2 focus:ring-primary/20" className={`w-full px-3 py-2 rounded-xl border border-slate-200/80 bg-white text-sm leading-relaxed resize-none ${FOCUS_RING}`}
/> />
</div> </div>
{error && <p className="text-xs text-red-500">{error}</p>} {error && <p className="text-xs text-red-500">{error}</p>}
</div> </div>
<div className="px-5 py-3 border-t border-border flex justify-end gap-2"> <div className="px-5 py-3 border-t border-slate-200/80 bg-[#f8fafc] flex justify-end gap-2">
<button <button
onClick={onClose} onClick={onClose}
disabled={busy} disabled={busy}
className="px-4 py-1.5 rounded-lg border border-border text-xs text-foreground hover:bg-muted disabled:opacity-50" className="px-4 py-1.5 rounded-lg border border-slate-200/80 bg-white text-xs text-foreground hover:bg-[#f6f9ff] disabled:opacity-50 transition-colors"
> >
取消 取消
</button> </button>
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={busy} disabled={busy}
className="px-4 py-1.5 rounded-lg bg-gradient-to-r from-[#5b5ff9] to-[#8b5ff9] text-white text-xs hover:shadow-md disabled:opacity-50 flex items-center gap-1.5" className="px-4 py-1.5 rounded-lg bg-[#1c64ff] text-white text-xs shadow-[0_6px_14px_rgba(28,100,255,0.22)] hover:bg-[#1857e8] hover:shadow-[0_8px_18px_rgba(28,100,255,0.28)] disabled:opacity-50 flex items-center gap-1.5 transition-all"
> >
{busy && <Loader2 className="w-3 h-3 animate-spin" />} {busy && <Loader2 className="w-3 h-3 animate-spin" />}
创建并生成 创建并生成
...@@ -1390,20 +1397,20 @@ interface PromptDetailsModalProps { ...@@ -1390,20 +1397,20 @@ interface PromptDetailsModalProps {
function PromptDetailsModal({ text, onClose }: PromptDetailsModalProps) { function PromptDetailsModal({ text, onClose }: PromptDetailsModalProps) {
const trimmed = text.trim(); const trimmed = text.trim();
return ( return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" onClick={onClose}> <div className="fixed inset-0 bg-slate-950/55 backdrop-blur-sm z-50 flex items-center justify-center p-4" onClick={onClose}>
<div <div
className="bg-background rounded-xl shadow-xl w-full max-w-lg max-h-[80vh] flex flex-col" className="bg-white rounded-2xl shadow-[0_24px_70px_rgba(15,23,42,0.28)] ring-1 ring-white/60 w-full max-w-lg max-h-[80vh] flex flex-col overflow-hidden"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="flex items-center justify-between px-5 py-3 border-b border-border"> <div className="flex items-center justify-between px-5 py-3 border-b border-slate-200/80 bg-[#f8fafc]">
<h3 className="text-sm font-semibold text-foreground">分镜提示词全文</h3> <h3 className="text-sm font-semibold text-foreground">分镜提示词全文</h3>
<button onClick={onClose} className="p-1 hover:bg-muted rounded"> <button onClick={onClose} className="p-1 hover:bg-white rounded-lg transition-colors">
<X className="w-4 h-4 text-muted-foreground" /> <X className="w-4 h-4 text-muted-foreground" />
</button> </button>
</div> </div>
<div className="flex-1 overflow-y-auto p-5"> <div className="flex-1 overflow-y-auto p-5">
{trimmed ? ( {trimmed ? (
<pre className="text-sm text-foreground leading-relaxed whitespace-pre-wrap font-mono"> <pre className="text-sm text-foreground leading-relaxed whitespace-pre-wrap font-mono rounded-xl border border-slate-200/80 bg-[#f8fafc] p-4">
{trimmed} {trimmed}
</pre> </pre>
) : ( ) : (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment