-
Notifications
You must be signed in to change notification settings - Fork 391
Description
import React, { useState } from 'react';
import {
Play, Pause, SkipBack, SkipForward, Scissors, Trash2,
Undo, Redo, Download, Settings, ZoomIn, ZoomOut,
Image as ImageIcon, Type, Music, Smile, Sparkles,
Layout, SplitSquareHorizontal, Sliders, MousePointer2,
ChevronRight, ChevronDown, Plus
} from 'lucide-react';
// 模拟素材数据
const MEDIA_ASSETS = [
{ id: 1, type: 'video', name: 'Travel_Vlog_01.mp4', duration: '00:15', color: 'bg-blue-500' },
{ id: 2, type: 'video', name: 'Drone_Shot_City.mov', duration: '00:08', color: 'bg-teal-500' },
{ id: 3, type: 'image', name: 'Thumbnail.jpg', duration: '00:05', color: 'bg-purple-500' },
{ id: 4, type: 'audio', name: 'LoFi_Chill_Beat.mp3', duration: '02:30', color: 'bg-yellow-500' },
];
const SIDEBAR_TABS = [
{ id: 'media', icon: ImageIcon, label: '媒体' },
{ id: 'audio', icon: Music, label: '音频' },
{ id: 'text', icon: Type, label: '文字' },
{ id: 'stickers', icon: Smile, label: '贴纸' },
{ id: 'effects', icon: Sparkles, label: '特效' },
{ id: 'transitions', icon: SplitSquareHorizontal, label: '转场' },
{ id: 'filters', icon: Sliders, label: '滤镜' },
];
export default function CapCutClone() {
const [activeTab, setActiveTab] = useState('media');
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState('00:00:04');
const [zoomLevel, setZoomLevel] = useState(50);
return (
{/* 1. 顶部标题栏 (Header) */}
<header className="h-12 border-b border-gray-800 bg-[#181818] flex items-center justify-between px-4 z-20">
<div className="flex items-center gap-4">
<div className="w-8 h-8 bg-gradient-to-br from-cyan-400 to-blue-600 rounded-lg flex items-center justify-center text-white font-bold italic">
C
</div>
<div className="flex items-center gap-2 text-xs text-gray-400">
<span className="hover:text-white cursor-pointer">Menu</span>
<span>/</span>
<span className="text-white font-medium truncate max-w-[150px]">Untitled Project *</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-xs bg-gray-800 px-3 py-1.5 rounded hover:bg-gray-700 cursor-pointer transition-colors">
1080p
</div>
<button className="bg-cyan-500 hover:bg-cyan-400 text-black font-semibold text-xs px-4 py-1.5 rounded-full flex items-center gap-2 transition-colors">
<Download size={14} />
Export
</button>
</div>
</header>
{/* 中间主要工作区 (Main Workspace) */}
<div className="flex-1 flex min-h-0">
{/* 2. 左侧资源区 (Sidebar & Assets) */}
<div className="flex w-[360px] border-r border-gray-800 bg-[#181818]">
{/* 一级导航图标栏 */}
<div className="w-16 flex flex-col items-center py-4 gap-6 border-r border-gray-800">
{SIDEBAR_TABS.map((tab) => (
<div
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex flex-col items-center gap-1 cursor-pointer transition-colors ${activeTab === tab.id ? 'text-cyan-400' : 'text-gray-500 hover:text-gray-300'}`}
>
<tab.icon size={20} strokeWidth={1.5} />
<span className="text-[10px]">{tab.label}</span>
</div>
))}
</div>
{/* 二级资源面板 */}
<div className="flex-1 flex flex-col min-w-0">
<div className="h-10 border-b border-gray-800 flex items-center px-4 font-medium text-sm text-white">
本地素材
</div>
<div className="flex-1 overflow-y-auto p-3">
<div className="grid grid-cols-2 gap-3">
<div className="aspect-square border-2 border-dashed border-gray-700 rounded-md flex flex-col items-center justify-center text-gray-500 hover:border-gray-500 cursor-pointer transition-colors">
<Plus size={24} />
<span className="text-xs mt-1">导入</span>
</div>
{MEDIA_ASSETS.map(item => (
<div key={item.id} className="group relative aspect-square bg-gray-800 rounded-md overflow-hidden cursor-grab active:cursor-grabbing border border-transparent hover:border-cyan-500/50">
<div className={`w-full h-full ${item.color} opacity-50`}></div>
<div className="absolute bottom-0 w-full bg-black/60 p-1.5">
<div className="text-[10px] text-white truncate">{item.name}</div>
<div className="text-[9px] text-gray-400">{item.duration}</div>
</div>
<div className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="bg-cyan-500 p-1 rounded cursor-pointer hover:bg-cyan-400">
<Plus size={12} className="text-black" />
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
{/* 3. 中间播放器 (Player) */}
<div className="flex-1 flex flex-col bg-[#0d0d0d] relative min-w-[400px]">
{/* 播放器工具栏 */}
<div className="h-10 flex items-center justify-between px-4">
<div className="text-xs text-gray-500">{currentTime}</div>
<div className="text-xs text-gray-500 flex gap-2">
<span>100%</span>
</div>
</div>
{/* 视频画面 */}
<div className="flex-1 flex items-center justify-center p-4">
<div className="aspect-video h-full max-h-[60%] bg-black shadow-2xl relative group border border-gray-800">
{/* 模拟视频内容 */}
<img
src="https://images.unsplash.com/photo-1492691527719-9d1e07e534b4?w=800&q=80"
alt="Preview"
className="w-full h-full object-cover opacity-80"
/>
{/* 变换控件覆盖层 */}
<div className="absolute inset-0 border-2 border-cyan-500/50 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
<div className="absolute top-0 left-0 w-2 h-2 bg-cyan-500 -translate-x-1 -translate-y-1"></div>
<div className="absolute top-0 right-0 w-2 h-2 bg-cyan-500 translate-x-1 -translate-y-1"></div>
<div className="absolute bottom-0 left-0 w-2 h-2 bg-cyan-500 -translate-x-1 translate-y-1"></div>
<div className="absolute bottom-0 right-0 w-2 h-2 bg-cyan-500 translate-x-1 translate-y-1"></div>
</div>
</div>
</div>
{/* 播放控制栏 */}
<div className="h-12 flex items-center justify-center gap-6 text-gray-400 mb-2">
<SkipBack size={20} className="hover:text-white cursor-pointer" />
<div
className="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center text-black hover:scale-110 transition-transform cursor-pointer"
onClick={() => setIsPlaying(!isPlaying)}
>
{isPlaying ? <Pause size={16} fill="black" /> : <Play size={16} fill="black" className="ml-0.5" />}
</div>
<SkipForward size={20} className="hover:text-white cursor-pointer" />
</div>
</div>
{/* 4. 右侧属性面板 (Properties) */}
<div className="w-[280px] bg-[#181818] border-l border-gray-800 flex flex-col">
<div className="flex h-10 border-b border-gray-800">
<div className="flex-1 flex items-center justify-center text-xs font-medium text-cyan-400 border-b-2 border-cyan-400 bg-gray-800/50 cursor-pointer">
画面
</div>
<div className="flex-1 flex items-center justify-center text-xs font-medium text-gray-500 hover:text-gray-300 cursor-pointer">
音频
</div>
<div className="flex-1 flex items-center justify-center text-xs font-medium text-gray-500 hover:text-gray-300 cursor-pointer">
变速
</div>
<div className="flex-1 flex items-center justify-center text-xs font-medium text-gray-500 hover:text-gray-300 cursor-pointer">
动画
</div>
</div>
<div className="flex-1 overflow-y-auto p-4 text-xs">
{/* 基础属性组 */}
<div className="mb-6">
<div className="flex items-center gap-2 mb-4 font-bold text-gray-300">
<ChevronDown size={12} /> 基础
</div>
<div className="space-y-4 pl-2">
<div className="flex items-center justify-between">
<span className="text-gray-400">缩放</span>
<div className="flex items-center gap-2">
<input type="range" className="w-20 h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer" />
<span className="w-8 text-right text-gray-300">100%</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">位置 X</span>
<span className="bg-[#0a0a0a] px-2 py-1 rounded border border-gray-700 w-16 text-center text-gray-300">0</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">位置 Y</span>
<span className="bg-[#0a0a0a] px-2 py-1 rounded border border-gray-700 w-16 text-center text-gray-300">0</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">旋转</span>
<div className="flex items-center gap-2">
<div className="w-5 h-5 rounded-full border border-gray-500 flex items-center justify-center">
<div className="w-0.5 h-2 bg-gray-400"></div>
</div>
<span className="w-8 text-right text-gray-300">0°</span>
</div>
</div>
</div>
</div>
{/* 不透明度组 */}
<div className="mb-6">
<div className="flex items-center gap-2 mb-4 font-bold text-gray-300">
<ChevronRight size={12} /> 不透明度
</div>
</div>
</div>
</div>
</div>
{/* 5. 底部时间轴 (Timeline) */}
<div className="h-[300px] bg-[#121212] border-t border-gray-800 flex flex-col relative z-10">
{/* 时间轴工具栏 */}
<div className="h-10 border-b border-gray-800 flex items-center justify-between px-4 bg-[#181818]">
<div className="flex items-center gap-4">
<div className="flex gap-2 text-gray-400">
<Undo size={16} className="hover:text-white cursor-pointer" />
<Redo size={16} className="hover:text-white cursor-pointer" />
</div>
<div className="w-px h-4 bg-gray-700"></div>
<div className="flex gap-2 text-gray-400">
<SplitSquareHorizontal size={16} className="hover:text-white cursor-pointer" />
<Trash2 size={16} className="hover:text-white cursor-pointer" />
</div>
</div>
<div className="flex items-center gap-2">
<ZoomOut size={14} className="text-gray-500 cursor-pointer" onClick={() => setZoomLevel(Math.max(10, zoomLevel - 10))} />
<input
type="range"
value={zoomLevel}
onChange={(e) => setZoomLevel(e.target.value)}
className="w-24 h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer"
/>
<ZoomIn size={14} className="text-gray-500 cursor-pointer" onClick={() => setZoomLevel(Math.min(100, zoomLevel + 10))} />
</div>
</div>
{/* 时间轴内容区 */}
<div className="flex-1 overflow-x-scroll overflow-y-hidden relative scrollbar-thin scrollbar-thumb-gray-700 scrollbar-track-transparent">
{/* 时间刻度尺 */}
<div className="h-6 border-b border-gray-800 flex items-end sticky top-0 bg-[#121212] z-10 min-w-[1000px]">
{[...Array(20)].map((_, i) => (
<div key={i} className="flex-1 border-l border-gray-700 h-2 text-[9px] text-gray-500 pl-1 select-none">
00:0{i}
</div>
))}
</div>
{/* 轨道区域 */}
<div className="py-4 min-w-[1000px] relative">
{/* 播放头 (Playhead) */}
<div className="absolute top-0 left-[300px] bottom-0 w-px bg-white z-30 flex flex-col items-center pointer-events-none">
<div className="w-3 h-3 bg-cyan-500 rotate-45 -translate-y-1.5 shadow-sm"></div>
</div>
{/* 主视频轨 */}
<div className="h-16 bg-[#1a1a1a] mb-1 relative flex items-center border-y border-gray-800/50 group">
<div className="absolute left-2 text-[10px] text-gray-500 z-10 bg-[#1a1a1a] px-1">Main Track</div>
{/* 片段 1 */}
<div className="absolute left-[50px] w-[250px] h-14 bg-blue-900/40 border border-blue-500/50 rounded overflow-hidden cursor-pointer hover:bg-blue-800/50 flex items-center justify-center">
<div className="flex w-full h-full opacity-30 gap-0.5">
{[...Array(10)].map((_,i) => <div key={i} className="flex-1 bg-blue-400"></div>)}
</div>
<span className="absolute text-xs text-blue-100 drop-shadow-md">Travel_Vlog_01.mp4</span>
</div>
{/* 片段 2 */}
<div className="absolute left-[305px] w-[180px] h-14 bg-teal-900/40 border border-teal-500/50 rounded overflow-hidden cursor-pointer hover:bg-teal-800/50 flex items-center justify-center">
<span className="absolute text-xs text-teal-100 drop-shadow-md">Drone_Shot.mov</span>
</div>
</div>
{/* 音频轨 */}
<div className="h-10 bg-[#1a1a1a] relative flex items-center border-b border-gray-800/50">
<div className="absolute left-[50px] w-[435px] h-8 bg-yellow-900/30 border border-yellow-500/30 rounded-full overflow-hidden cursor-pointer flex items-center px-2">
{/* 模拟波形 */}
<div className="flex items-center gap-0.5 w-full h-4 opacity-60">
{[...Array(60)].map((_, i) => (
<div key={i} className="w-1 bg-yellow-500 rounded-full" style={{height: `${Math.random() * 100}%`}}></div>
))}
</div>
<span className="absolute left-2 text-[9px] text-yellow-200">LoFi_Chill_Beat.mp3</span>
</div>
</div>
{/* 文本轨 */}
<div className="h-8 mt-1 bg-[#1a1a1a] relative flex items-center">
<div className="absolute left-[150px] w-[100px] h-6 bg-purple-900/40 border border-purple-500/50 rounded text-[10px] flex items-center justify-center text-purple-200 cursor-pointer">
Text: TRAVEL
</div>
</div>
</div>
</div>
</div>
</div>
);
}