"use client"; import { useState, useEffect, useRef } from 'react'; import { ArrowUp } from 'lucide-react'; import Image from 'next/image'; import gsap from 'gsap'; import { SplitText } from 'gsap/SplitText'; // 注册 SplitText 插件 if (typeof window !== 'undefined') { gsap.registerPlugin(SplitText); } const ideaText = 'a cute capybara with an orange on its head, staring into the distance and walking forward'; const AnimatedText = ({ text, onComplete, shouldStart }: { text: string; onComplete: () => void; shouldStart: boolean }) => { const containerRef = useRef(null); const titleRef = useRef(null); const inputContainerRef = useRef(null); const inputRef = useRef(null); const cursorRef = useRef(null); const mouseRef = useRef(null); const buttonRef = useRef(null); const animationRef = useRef(null); const [displayText, setDisplayText] = useState(''); const demoText = "a cute capybara with an orange on its head"; useEffect(() => { if (containerRef.current && typeof window !== 'undefined' && shouldStart) { // 清理之前的动画 if (animationRef.current) { animationRef.current.kill(); } // 重置状态 setDisplayText(''); if (containerRef.current) { containerRef.current.style.opacity = '1'; } // 设置初始状态 gsap.set([titleRef.current, inputContainerRef.current], { opacity: 1 }); // 创建主时间轴 const mainTl = gsap.timeline(); // 1. 显示标题和输入框 mainTl.fromTo(titleRef.current, { y: -30, opacity: 0 }, { y: 0, opacity: 1, duration: 0.3, ease: "power2.out" }) .fromTo(inputContainerRef.current, { scale: 0.9, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.2, ease: "back.out(1.7)" }, "-=0.3"); // 2. 显示鼠标指针并移动到输入框 mainTl.fromTo(mouseRef.current, { opacity: 0, x: -100, y: -50 }, { opacity: 1, x: -150, y: 0, duration: 0.3, ease: "power2.out" }, "+=0.5" ); // 3. 鼠标移动到输入框中心 mainTl.to(mouseRef.current, { x: 0, y: 0, duration: 0.2, ease: "power2.inOut" }); // 4. 输入框聚焦效果 mainTl.to(inputRef.current, { scale: 1.05, rotationY: 1, rotationX: 15, transformOrigin: "center center", boxShadow: ` inset 0 3px 0 rgba(255,255,255,0.35), inset 0 -3px 0 rgba(0,0,0,0.2), inset 3px 0 0 rgba(255,255,255,0.15), inset -3px 0 0 rgba(0,0,0,0.08), 0 0 0 3px rgba(59, 130, 246, 0.4), 0 4px 8px rgba(0,0,0,0.15), 0 12px 24px rgba(0,0,0,0.2), 0 20px 40px rgba(0,0,0,0.15), 0 0 0 1px rgba(59, 130, 246, 0.6) `, borderColor: 'rgba(79, 70, 229, 0.8)', duration: 0.1, ease: "back.out(1.7)" }, "+=0.2"); // 5. 显示按钮 mainTl.fromTo(buttonRef.current, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.1, ease: "back.out(1.7)" }, "+=0.3" ); // 隐藏鼠标指针 mainTl.to(mouseRef.current, { opacity: 0, duration: 0.1 }, "+=0.2"); // 6. 打字动画 const typingDuration = demoText.length * 0.01; let currentChar = 0; mainTl.to({}, { duration: typingDuration, ease: "none", onUpdate: function() { const progress = this.progress(); const targetChar = Math.floor(progress * demoText.length); if (targetChar !== currentChar && targetChar <= demoText.length) { currentChar = targetChar; setDisplayText(demoText.slice(0, currentChar)); } } }, "+=0.3"); // 7. 隐藏光标 mainTl.to(cursorRef.current, { opacity: 0, duration: 0.1 }, "+=0.2"); // 重新显示鼠标指针并移动到按钮 mainTl.fromTo(mouseRef.current, { opacity: 0, x: 0, y: 0 }, { opacity: 1, duration: 0.1, ease: "power2.out" }, "-=0.3" ); // 鼠标移动到按钮 mainTl.to(mouseRef.current, { x: 20, y: 10, duration: 0.1, ease: "power2.inOut" }, "+=0.5"); // 8. 输入框失焦效果 mainTl.to(inputRef.current, { scale: 1, rotationY: 0, rotationX: 0, transformOrigin: "center center", boxShadow: ` inset 0 2px 0 rgba(255,255,255,0.25), inset 0 -2px 0 rgba(0,0,0,0.15), inset 2px 0 0 rgba(255,255,255,0.1), inset -2px 0 0 rgba(0,0,0,0.05), 0 2px 4px rgba(0,0,0,0.1), 0 8px 16px rgba(0,0,0,0.15), 0 16px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(255,255,255,0.1) `, duration: 0.1, ease: "power2.out" }, "+=0.2"); // 点击效果 mainTl.to(mouseRef.current, { scale: 0.8, duration: 0.1, ease: "power2.out", yoyo: true, repeat: 1 }) .to(buttonRef.current, { scale: 0.95, duration: 0.1, ease: "power2.out", yoyo: true, repeat: 1 }, "-=0.2") .to(buttonRef.current, { boxShadow: "0 0 20px rgba(79, 70, 229, 0.6)", duration: 0.1, ease: "power2.out" }, "-=0.1"); // 停留展示时间 mainTl.to({}, { duration: 0.1 }); // 退场动画 mainTl.to(containerRef.current, { opacity: 0, y: -50, duration: 0.3, ease: "power2.in", onComplete: () => { onComplete(); } }); animationRef.current = mainTl; } return () => { if (animationRef.current) { animationRef.current.kill(); } }; }, [shouldStart, onComplete]); return (
{/* 标题 */}

You can input script to generate video

{/* 输入框模拟 */}
{displayText}
{/* Create按钮 */}
Create
{/* 鼠标指针 */}
); }; // 阶段文字解释组件 const StageExplanation = ({ text, stage, shouldStart, onComplete }: { text: string; stage: 'images' | 'replacing' | 'merging'; shouldStart: boolean; onComplete: () => void; }) => { const textRef = useRef(null); const splitRef = useRef(null); const animationRef = useRef(null); useEffect(() => { if (textRef.current && typeof window !== 'undefined' && shouldStart) { // 重置显示 textRef.current.style.opacity = '1'; // 清理之前的动画 if (splitRef.current) { splitRef.current.revert(); } if (animationRef.current) { animationRef.current.kill(); } // 创建分割文本 splitRef.current = new SplitText(textRef.current, { type: "words" }); let tl = gsap.timeline({ onComplete: () => { // 延迟后开始退场动画 setTimeout(() => { const exitTl = gsap.timeline({ onComplete: () => { onComplete(); } }); // 不同阶段的退场动画 switch (stage) { case 'images': exitTl.to(splitRef.current.words, { y: -50, opacity: 0, duration: 0.2, ease: "power2.in", stagger: 0.05 }); break; case 'replacing': exitTl.to(splitRef.current.words, { scale: 0, rotation: 360, opacity: 0, duration: 0.4, ease: "back.in(2)", stagger: 0.08 }); break; case 'merging': exitTl.to(splitRef.current.words, { x: "random(-200, 200)", y: "random(-100, -200)", opacity: 0, duration: 0.7, ease: "power3.in", stagger: 0.06 }); break; } }, 1000); // 显示1秒 } }); // 不同阶段的入场动画 switch (stage) { case 'images': // 从下方弹跳进入 tl.from(splitRef.current.words, { y: 100, opacity: 0, duration: 0.5, ease: "bounce.out", stagger: 0.1 }); break; case 'replacing': // 旋转缩放进入 tl.from(splitRef.current.words, { scale: 0, rotation: -180, opacity: 0, duration: 0.4, ease: "back.out(2)", stagger: 0.08 }); break; case 'merging': // 从四周飞入 tl.from(splitRef.current.words, { x: "random(-300, 300)", y: "random(-200, 200)", opacity: 0, duration: 0.7, ease: "power3.out", stagger: 0.06 }); break; } animationRef.current = tl; } return () => { if (animationRef.current) { animationRef.current.kill(); } if (splitRef.current) { splitRef.current.revert(); } }; }, [shouldStart, stage, onComplete]); return (

{text}

); }; const ImageQueue = ({ shouldStart, onComplete }: { shouldStart: boolean; onComplete: () => void }) => { const imagesRef = useRef(null); const [currentStage, setCurrentStage] = useState<'images' | 'replacing' | 'merging'>('images'); const [replacementIndex, setReplacementIndex] = useState(0); const [showStageText, setShowStageText] = useState(false); const finalVideoContainerRef = useRef(null); const imageUrls = [ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg' ]; const videoUrls = [ 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750384661_d8e30b79-828e-48cd-9025-ab62a996717c_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750320040_4b47996e-7c70-490e-8433-80c7df990fdd_text_to_video_0.mp4' ]; const finalVideoUrl = 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4'; // 阶段文字映射 const stageTexts = { images: 'Then, these are the key frames split from the storyboard', replacing: 'Then, these are the videos corresponding to the split storyboard', merging: 'Finally, efficiently edit the perfect video' }; // 重置函数 const resetComponent = () => { // 清理最终视频容器 if (finalVideoContainerRef.current) { finalVideoContainerRef.current.remove(); finalVideoContainerRef.current = null; } // 额外清理:查找并移除所有可能的最终视频容器 const allFinalVideos = document.querySelectorAll('div[class*="fixed"][class*="top-1/2"][class*="w-[400px]"]'); allFinalVideos.forEach(container => { if (container.parentNode === document.body) { container.remove(); } }); // 重置所有状态 setCurrentStage('images'); setReplacementIndex(0); setShowStageText(false); // 重置DOM结构 if (imagesRef.current) { imagesRef.current.style.display = 'flex'; Array.from(imagesRef.current.children).forEach((container, index) => { const htmlContainer = container as HTMLElement; // 移除视频元素 const videos = htmlContainer.querySelectorAll('video'); videos.forEach(video => video.remove()); // 使用GSAP重置容器样式 gsap.set(htmlContainer, { position: 'static', x: 0, y: index % 2 ? 20 : -20, opacity: 1, scale: 1, rotation: 0 }); // 确保图片显示 const img = htmlContainer.querySelector('img'); if (img) { img.style.opacity = '1'; } }); } }; // 当shouldStart改变时重置并开始新的动画 useEffect(() => { if (shouldStart) { resetComponent(); // 延迟显示阶段文字 setTimeout(() => { setShowStageText(true); }, 500); } }, [shouldStart]); // 阶段文字完成回调 const handleStageTextComplete = () => { setShowStageText(false); // 根据当前阶段决定下一步 if (currentStage === 'images') { // 开始图片入场动画 startImagesAnimation(); } else if (currentStage === 'replacing') { // 继续图片替换视频 // replacementIndex会自动处理 } else if (currentStage === 'merging') { // 开始合并动画 startMergingAnimation(); } }; // 图片入场动画 const startImagesAnimation = () => { if (imagesRef.current) { const images = imagesRef.current.children; gsap.set(images, { x: window.innerWidth, opacity: 0, rotation: 45 }); const tl = gsap.timeline({ onComplete: () => { // 图片动画完成后,切换到替换阶段 setTimeout(() => { setCurrentStage('replacing'); setShowStageText(true); }, 1000); } }); tl.to(images, { x: 0, opacity: 1, rotation: 0, duration: 1, ease: "elastic.out(1, 0.5)", stagger: { amount: 1, from: "random" } }); } }; // 开始合并动画 const startMergingAnimation = () => { if (imagesRef.current) { const containers = Array.from(imagesRef.current.children) as HTMLElement[]; const videos = containers.map(container => container.querySelector('video')).filter(Boolean) as HTMLVideoElement[]; if (videos.length > 0) { // 创建最终的大视频容器 const finalVideoContainer = document.createElement('div'); finalVideoContainer.className = 'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] rounded-lg overflow-hidden shadow-2xl opacity-0'; const finalVideo = document.createElement('video'); finalVideo.src = finalVideoUrl; finalVideo.autoplay = true; finalVideo.loop = true; finalVideo.muted = true; finalVideo.playsInline = true; finalVideo.className = 'w-full h-full object-cover'; finalVideoContainer.appendChild(finalVideo); document.body.appendChild(finalVideoContainer); finalVideoContainerRef.current = finalVideoContainer; // 等待最终视频准备就绪 const onFinalVideoReady = () => { finalVideo.removeEventListener('canplay', onFinalVideoReady); finalVideo.play().then(() => { const tl = gsap.timeline({ onComplete: () => { // 移除原有的小视频容器 if (imagesRef.current) { imagesRef.current.style.display = 'none'; } // 延迟3秒后调用完成回调,开始下一轮循环 setTimeout(() => { onComplete(); }, 3000); } }); // 执行合并动画 tl.to(containers, { rotation: "random(-720, 720)", x: "random(-200, 200)", y: "random(-200, 200)", scale: 0.8, duration: 0.7, ease: "power2.out", stagger: 0.1 }) .to(containers, { x: 0, y: 0, rotation: 0, scale: 1, duration: 0.5, ease: "power2.inOut", stagger: 0.05 }) .to(containers, { scale: 1.2, duration: 0.1, ease: "power2.inOut" }) .to(finalVideoContainer, { opacity: 1, scale: 1, duration: 0.2, ease: "power2.out" }, "-=0.2") .to(containers, { opacity: 0, scale: 0.8, duration: 0.2, ease: "power2.in" }, "-=0.5") .to(finalVideoContainer, { scale: 1, duration: 0.1, ease: "back.out(1.7)" }); }).catch(() => { console.error('最终视频播放失败'); finalVideoContainer.remove(); onComplete(); }); }; finalVideo.addEventListener('canplay', onFinalVideoReady); finalVideo.addEventListener('error', () => { console.error('最终视频加载失败'); finalVideoContainer.remove(); onComplete(); }); finalVideo.load(); } } }; // 图片到视频的替换动画 useEffect(() => { if (currentStage === 'replacing' && imagesRef.current && replacementIndex < imageUrls.length && shouldStart && !showStageText) { const targetContainer = imagesRef.current.children[replacementIndex] as HTMLElement; if (targetContainer) { // 创建视频元素 const video = document.createElement('video'); video.src = videoUrls[replacementIndex]; video.autoplay = true; video.loop = true; video.muted = true; video.playsInline = true; video.className = 'absolute inset-0 w-full h-full object-cover opacity-0'; // 添加视频到容器中 targetContainer.style.position = 'relative'; targetContainer.appendChild(video); // 等待视频准备好播放 const onCanPlay = () => { video.removeEventListener('canplay', onCanPlay); video.removeEventListener('error', onError); // 开始播放 video.play().then(() => { // 创建无缝替换动画 const tl = gsap.timeline({ onComplete: () => { // 移除图片元素 const img = targetContainer.querySelector('img'); if (img) { img.remove(); } // 移除绝对定位,让视频正常占位 video.className = 'w-full h-full object-cover'; // 检查是否是最后一个视频 if (replacementIndex === imageUrls.length - 1) { // 最后一个视频替换完成,切换到合并阶段 setTimeout(() => { setCurrentStage('merging'); setShowStageText(true); }, 1000); } else { // 延迟后替换下一个 setTimeout(() => { setReplacementIndex(prev => prev + 1); }, 600); } } }); // 同时淡入视频和淡出图片 tl.to(video, { opacity: 1, duration: 0.1, ease: "power2.inOut" }) .to(targetContainer.querySelector('img'), { opacity: 0, duration: 0.1, ease: "power2.inOut" }, 0) .to(targetContainer, { scale: 1.02, duration: 0.1, ease: "power2.out", yoyo: true, repeat: 1 }, 0.1); }).catch(onError); }; const onError = () => { video.removeEventListener('canplay', onCanPlay); video.removeEventListener('error', onError); console.error('视频加载或播放失败:', videoUrls[replacementIndex]); // 移除失败的视频元素 video.remove(); // 跳过到下一个 if (replacementIndex === imageUrls.length - 1) { setTimeout(() => { setCurrentStage('merging'); setShowStageText(true); }, 500); } else { setTimeout(() => { setReplacementIndex(prev => prev + 1); }, 500); } }; video.addEventListener('canplay', onCanPlay); video.addEventListener('error', onError); // 加载视频 video.load(); } } }, [currentStage, replacementIndex, shouldStart, showStageText]); return ( <> {showStageText && ( )}
{imageUrls.map((url, index) => (
{`reference-${index
))}
); }; // 主要的空状态动画组件 export const EmptyStateAnimation = () => { const [showText, setShowText] = useState(false); const [showImages, setShowImages] = useState(false); const [animationCycle, setAnimationCycle] = useState(0); const [isClient, setIsClient] = useState(false); // 全局清理函数 const globalCleanup = () => { // 清理所有可能的最终视频容器 const existingFinalVideos = document.querySelectorAll('div[class*="fixed"][class*="top-1/2"][class*="w-[400px]"]'); existingFinalVideos.forEach(container => { if (container.parentNode === document.body) { container.remove(); } }); // 清理所有body下的视频相关元素 const bodyChildren = Array.from(document.body.children); bodyChildren.forEach(child => { if (child instanceof HTMLElement && (child.querySelector('video') || child.tagName === 'VIDEO')) { child.remove(); } }); }; // 循环控制 const startNextCycle = () => { // 先进行全局清理 globalCleanup(); setAnimationCycle(prev => prev + 1); setShowImages(false); // 延迟一下再显示文字,确保清理完成 setTimeout(() => { setShowText(true); }, 100); }; const handleTextComplete = () => { setShowText(false); setTimeout(() => { setShowImages(true); }, 100); }; const handleImagesComplete = () => { setShowImages(false); // 延迟后开始新的循环 setTimeout(() => { startNextCycle(); }, 1000); }; // 开始第一轮动画 useEffect(() => { if (isClient && animationCycle === 0) { startNextCycle(); } }, [isClient]); // 组件卸载时清理 useEffect(() => { return () => { globalCleanup(); }; }, []); useEffect(() => { setIsClient(true); }, []); if (!isClient) { return null; } return (
{showText && ( )} {showImages && ( )}
); };