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;
const MAX_CHARACTERS = 5;
const MAX_PROPS = 5;
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 | 蒙太奇快切 | 固定机位快速切出
画面定格在角色刚转头的瞬间,背景虚化为暖色光斑
......@@ -281,11 +284,11 @@ export function StoryboardWorkspace() {
const promptPreviewText = freeformPrompt.trim();
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 ─── */}
<div className="flex-shrink-0 border-r border-border bg-background flex flex-col items-center overflow-hidden py-5" style={{ width: 80 }}>
<span className="font-semibold mb-5" style={{ color: "#9ba0b1", fontSize: 16, letterSpacing: 1 }}>
<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: "#7c8598", fontSize: 16, letterSpacing: 1 }}>
集数
</span>
<div
......@@ -308,18 +311,18 @@ export function StoryboardWorkspace() {
key={ep.id}
onClick={() => handleEpisodeSelect(ep)}
title={ep.title ? `第${ep.episodeNumber}集 · ${ep.title}` : `第${ep.episodeNumber}集`}
className={`flex items-center justify-center font-bold tabular-nums transition-all flex-shrink-0 ${
active ? "" : "hover:bg-[#eceef4]"
className={`flex items-center justify-center font-bold tabular-nums transition-all duration-200 flex-shrink-0 ${
active ? "" : "hover:bg-[#f0f4fb] hover:text-[#475569] hover:shadow-sm"
}`}
style={{
width: 50,
height: 56,
borderRadius: 16,
fontSize: 22,
backgroundColor: active ? "#1c64ff" : "#f6f7fb",
color: active ? "#ffffff" : "#a0a5b5",
boxShadow: active ? "0 6px 14px rgba(28, 100, 255, 0.35)" : "none",
border: "none",
backgroundColor: active ? "#1c64ff" : "#ffffff",
color: active ? "#ffffff" : "#8b95a7",
boxShadow: active ? "0 12px 24px rgba(28, 100, 255, 0.28)" : "0 1px 2px rgba(15, 23, 42, 0.05)",
border: active ? "1px solid rgba(28, 100, 255, 0.35)" : "1px solid rgba(148, 163, 184, 0.22)",
}}
>
{ep.episodeNumber}
......@@ -331,16 +334,16 @@ export function StoryboardWorkspace() {
</div>
{/* ─── 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: 分镜表 / 备注描述 */}
<div className="px-4 py-3 border-b border-border bg-[#fafbfd] flex-shrink-0 relative">
<span className="absolute left-0 top-3 bottom-3 w-[2px] bg-[#1c64ff]/40 rounded-r-full" />
<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-[3px] bg-[#1c64ff]/55 rounded-r-full" />
<div className="flex items-center justify-between mb-1.5">
<span className="text-[11px] font-semibold tracking-[0.12em] uppercase text-muted-foreground">分镜备注</span>
{promptPreviewText && (
<button
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="查看完整内容"
>
<Info className="w-3 h-3" />
......@@ -358,11 +361,11 @@ export function StoryboardWorkspace() {
</div>
{/* 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
onClick={handleCreate}
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" />}
新建分镜
......@@ -371,14 +374,14 @@ export function StoryboardWorkspace() {
onClick={handleGenerateAll}
disabled={generateSbs.isPending || !eid}
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" />}
</button>
</div>
{/* 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 ? (
<div className="px-3 py-8 text-center">
<p className="text-xs text-muted-foreground">请从左侧选择集数</p>
......@@ -425,8 +428,8 @@ export function StoryboardWorkspace() {
</div>
{/* ─── 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="h-14 px-4 border-b border-border flex items-center flex-shrink-0 bg-background">
<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-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>
{expanded && (
<span className="ml-2 text-[11px] text-muted-foreground tabular-nums">
......@@ -440,7 +443,7 @@ export function StoryboardWorkspace() {
<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" />
<p className="text-[11px] text-[#1c64ff] leading-relaxed">
角色、场景、道具预设已默认参考,无需再次添加引用
......@@ -450,13 +453,13 @@ export function StoryboardWorkspace() {
value={freeformPrompt}
onChange={(e) => setFreeformPrompt(e.target.value)}
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 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="relative flex-1">
<select
......@@ -464,7 +467,7 @@ export function StoryboardWorkspace() {
onChange={(e) => setSelectedModel(e.target.value)}
disabled={!expanded}
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) => (
<option key={m.value} value={m.value}>{m.label}</option>
......@@ -477,7 +480,7 @@ export function StoryboardWorkspace() {
value={selectedRatio}
onChange={(e) => setSelectedRatio(e.target.value)}
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) => (
<option key={r.value} value={r.value}>{r.label}</option>
......@@ -490,7 +493,7 @@ export function StoryboardWorkspace() {
value={selectedDuration}
onChange={(e) => setSelectedDuration(Number(e.target.value))}
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) => (
<option key={d} value={d}>{d}</option>
......@@ -504,8 +507,8 @@ export function StoryboardWorkspace() {
title={audioOn ? "已开启配音" : "已关闭配音"}
className={`p-2 rounded-lg border text-xs transition-colors disabled:opacity-50 ${
audioOn
? "border-[#1c64ff] bg-[#1c64ff]/10 text-[#1c64ff]"
: "border-border bg-[#fafbfd] text-muted-foreground hover:bg-[#eceef4]"
? "border-[#1c64ff] bg-[#1c64ff]/10 text-[#1c64ff] shadow-[0_4px_12px_rgba(28,100,255,0.12)]"
: "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" />}
......@@ -516,7 +519,7 @@ export function StoryboardWorkspace() {
onClick={handleGenerate}
disabled={!canGenerate || generatingVideoId === expanded?.id}
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 ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
......@@ -529,8 +532,8 @@ export function StoryboardWorkspace() {
</div>
{/* ─── Column 4: Big video preview + thumbnail timeline ─── */}
<div className="flex-1 flex flex-col overflow-hidden min-w-0 bg-background">
<div className="h-14 px-5 border-b border-border flex items-center justify-between flex-shrink-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-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">
视频预览
{previewSb && (
......@@ -545,7 +548,7 @@ export function StoryboardWorkspace() {
href={assemblyTask.downloadUrl}
target="_blank"
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" />
下载合集
......@@ -559,7 +562,7 @@ export function StoryboardWorkspace() {
assemblyTask?.status === "pending" ||
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" ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
......@@ -570,7 +573,7 @@ export function StoryboardWorkspace() {
</button>
<button
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" />
......@@ -578,14 +581,14 @@ export function StoryboardWorkspace() {
</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 ? (
<div className="h-full flex items-center justify-center">
<p className="text-sm text-muted-foreground">展开任意分镜后查看视频</p>
</div>
) : previewTask?.status === "succeeded" && previewTask.resultVideoUrl ? (
<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
key={previewTask.resultVideoUrl}
src={previewTask.resultVideoUrl}
......@@ -605,16 +608,16 @@ export function StoryboardWorkspace() {
</a>
</div>
) : 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">
<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" />
</div>
<p className="text-sm font-semibold text-foreground tracking-tight">视频生成中…</p>
<p className="text-xs text-muted-foreground">通常需要 30-60 秒</p>
</div>
) : 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">
<X className="w-4 h-4 text-red-500" />
<span className="text-sm font-medium text-red-700">生成失败</span>
......@@ -623,7 +626,7 @@ export function StoryboardWorkspace() {
</div>
) : (
<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" />
<p className="text-sm text-muted-foreground">尚未生成视频</p>
<p className="text-xs text-muted-foreground/70">在左侧填好素材+提示词后生成</p>
......@@ -634,8 +637,8 @@ export function StoryboardWorkspace() {
{/* Thumbnail timeline */}
{storyboards.length > 0 && (
<div className="border-t border-border bg-background flex-shrink-0">
<div className="px-3 py-2.5 flex items-center gap-2 overflow-x-auto">
<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-3 flex items-center gap-2 overflow-x-auto">
{storyboards.map((sb) => {
const t = getTaskForSb(sb);
const isActive = activeVideoSbId === sb.id;
......@@ -650,8 +653,8 @@ export function StoryboardWorkspace() {
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 ${
isActive
? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25 shadow-[0_4px_12px_rgba(28,100,255,0.2)]"
: "border-border hover:border-[#1c64ff]/40 hover:-translate-y-px"
? "border-[#1c64ff] ring-2 ring-[#1c64ff]/25 shadow-[0_8px_18px_rgba(28,100,255,0.22)]"
: "border-slate-200 hover:border-[#1c64ff]/40 hover:-translate-y-px hover:shadow-sm"
}`}
>
{url ? (
......@@ -662,7 +665,7 @@ export function StoryboardWorkspace() {
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" />
</div>
)}
......@@ -732,24 +735,28 @@ function StoryboardCard(props: StoryboardCardProps) {
const [descCollapsed, setDescCollapsed] = useState(false);
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
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">
分镜信息{String(sb.sequenceNum).padStart(3, "0")}
</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>
{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>
<button
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>
{descCollapsed
......@@ -762,7 +769,7 @@ function StoryboardCard(props: StoryboardCardProps) {
value={props.shortDescription}
onChange={(e) => props.onShortDescriptionChange(e.target.value)}
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>
......@@ -796,7 +803,7 @@ function StoryboardCard(props: StoryboardCardProps) {
<div className="pt-1 flex justify-end">
<button
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" />
删除该分镜
......@@ -825,13 +832,13 @@ function CharacterRowList({
characterKeys, keyToCharacter, characters, onRemove, onSwap, onAdd,
}: CharacterRowListProps) {
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">
<span className="text-xs font-medium text-foreground">出镜角色</span>
<button
onClick={onAdd}
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" />
添加
......@@ -889,8 +896,8 @@ function CharacterRow({
});
return (
<div className="flex items-center gap-1.5">
<div className="w-8 h-8 rounded-md overflow-hidden border border-border bg-muted flex-shrink-0">
<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-lg overflow-hidden border border-slate-200 bg-slate-100 flex-shrink-0">
{url ? (
<img src={url} alt={character?.name ?? ""} className="w-full h-full object-cover" />
) : (
......@@ -901,7 +908,7 @@ function CharacterRow({
<select
value={tosKey}
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>}
{options.map((c) => {
......@@ -914,7 +921,7 @@ function CharacterRow({
<div className="relative flex-1 min-w-0">
<select
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>
</select>
......@@ -922,14 +929,14 @@ function CharacterRow({
</div>
<button
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
onClick={handleRegen}
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" />}
重绘
......@@ -953,13 +960,13 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
const selected = usable.find((s) => s.imageTosKey === selectedKey) ?? null;
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">
<span className="text-xs font-medium text-foreground">分镜场景</span>
{usable.length > 0 && (
<button
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" />
{selected ? "更换" : "选择"}
......@@ -972,7 +979,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
type="button"
onClick={() => usable.length > 0 && setShowPicker((v) => !v)}
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 ? (
<img src={selected.imageUrl} alt={selected.name} className="w-full h-full object-cover" />
......@@ -988,17 +995,17 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
{/* Bottom row: scene name + role label + actions menu */}
<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>}
</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 className="relative flex-shrink-0">
<button
onClick={() => setMenuOpen((v) => !v)}
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" />
</button>
......@@ -1008,16 +1015,16 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
className="fixed inset-0 z-10 cursor-default"
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
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
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>
......@@ -1028,7 +1035,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
</div>
{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 ? (
<p className="text-[11px] text-muted-foreground italic text-center py-2">先到「场景」页创建场景</p>
) : (
......@@ -1044,7 +1051,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
}}
title={s.name}
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 ? (
......@@ -1056,7 +1063,7 @@ function SceneSection({ scenes, selectedKey, onSelect }: SceneSectionProps) {
<span className="text-[10px] text-white truncate block">{s.name}</span>
</div>
{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" />
</div>
)}
......@@ -1090,13 +1097,13 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
const canAddMore = selectedKeys.length < MAX_PROPS;
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">
<span className="text-xs font-medium text-foreground">场景道具</span>
<button
onClick={() => setShowPicker((v) => !v)}
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" />
添加
......@@ -1112,7 +1119,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
key={s.id}
onClick={() => onToggle(s.imageTosKey!)}
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 ? (
<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) {
)}
{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 ? (
<p className="text-[11px] text-muted-foreground italic text-center py-2">
{allCandidates.length === 0 ? "先到「场景」页创建场景" : "已添加全部可选道具"}
......@@ -1147,7 +1154,7 @@ function PropGrid({ scenes, sceneKey, selectedKeys, onToggle }: PropGridProps) {
}}
disabled={!canAddMore}
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 ? (
<img src={s.imageUrl} alt={s.name} className="w-full h-full object-cover" />
......@@ -1200,14 +1207,14 @@ function CharacterPickerModal({
const usable = characters.filter((c) => !!characterFrontKey(c));
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
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()}
>
<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>
<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" />
</button>
</div>
......@@ -1231,8 +1238,8 @@ function CharacterPickerModal({
onClick={() => { onPick(k); onClose(); }}
className={`relative rounded-lg overflow-hidden border-2 transition ${
isAdded
? "border-primary opacity-50 cursor-not-allowed"
: "border-border hover:border-primary/60"
? "border-[#1c64ff] opacity-50 cursor-not-allowed"
: "border-slate-200 hover:border-[#1c64ff]/60 hover:shadow-sm"
}`}
>
<div className="aspect-square bg-muted">
......@@ -1246,7 +1253,7 @@ function CharacterPickerModal({
<p className="text-xs font-medium text-foreground truncate">{c.name}</p>
</div>
{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" />
</div>
)}
......@@ -1257,17 +1264,17 @@ function CharacterPickerModal({
)}
</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
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" />
创建新角色变体
</button>
<button
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>
......@@ -1322,11 +1329,11 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
};
return (
<div className="fixed inset-0 bg-black/50 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="flex items-center justify-between px-5 py-3 border-b border-border">
<div className="fixed inset-0 bg-slate-950/55 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<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-slate-200/80 bg-[#f8fafc]">
<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" />
</button>
</div>
......@@ -1342,7 +1349,7 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
value={name}
onChange={(e) => setName(e.target.value)}
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>
......@@ -1352,24 +1359,24 @@ function AddCharacterModal({ projectId, onClose, onAdded }: AddCharacterModalPro
value={imagePrompt}
onChange={(e) => setImagePrompt(e.target.value)}
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>
{error && <p className="text-xs text-red-500">{error}</p>}
</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
onClick={onClose}
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
onClick={handleSubmit}
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" />}
创建并生成
......@@ -1390,20 +1397,20 @@ interface PromptDetailsModalProps {
function PromptDetailsModal({ text, onClose }: PromptDetailsModalProps) {
const trimmed = text.trim();
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
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()}
>
<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>
<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" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-5">
{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}
</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