"use client"; import { AnimatePresence, motion } from "motion/react"; import { useCallback, useEffect, useRef, useState } from "react"; import { ExpandedGifOverlay, useExpandedGif } from "@/components/ui/expanded-gif-overlay"; const carouselItems = [ { title: "Connect & Sync", description: "Connect data sources like Notion, Drive and Gmail. Automatically sync to keep them updated.", src: "/homepage/hero_tutorial/ConnectorFlowGif.mp4", }, { title: "Upload Documents", description: "Upload documents directly, from images to massive PDFs.", src: "/homepage/hero_tutorial/DocUploadGif.mp4", }, { title: "Search & Citation", description: "Ask questions and get cited responses from your knowledge base.", src: "/homepage/hero_tutorial/BSNCGif.mp4", }, { title: "Targeted Document Q&A", description: "Mention specific documents in chat for targeted answers.", src: "/homepage/hero_tutorial/BQnaGif_compressed.mp4", }, { title: "Produce Reports Instantly", description: "Generate reports from your sources in many formats.", src: "/homepage/hero_tutorial/ReportGenGif_compressed.mp4", }, { title: "Create Podcasts", description: "Turn anything into a podcast in under 20 seconds.", src: "/homepage/hero_tutorial/PodcastGenGif.mp4", }, { title: "Image Generation", description: "Generate high-quality images easily from your conversations.", src: "/homepage/hero_tutorial/ImageGenGif.mp4", }, { title: "Collaborative AI Chat", description: "Collaborate on AI-powered conversations in realtime with your team.", src: "/homepage/hero_realtime/RealTimeChatGif.mp4", }, { title: "Realtime Comments", description: "Add comments and tag teammates on any message.", src: "/homepage/hero_realtime/RealTimeCommentsFlow.mp4", }, ]; function HeroCarouselCard({ title, description, src, isActive, onExpandedChange, }: { title: string; description: string; src: string; isActive: boolean; onExpandedChange?: (expanded: boolean) => void; }) { const { expanded, open, close } = useExpandedGif(); const videoRef = useRef(null); const [frozenFrame, setFrozenFrame] = useState(null); const [hasLoaded, setHasLoaded] = useState(false); useEffect(() => { onExpandedChange?.(expanded); }, [expanded, onExpandedChange]); const captureFrame = useCallback((video: HTMLVideoElement) => { try { const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext("2d")?.drawImage(video, 0, 0); setFrozenFrame(canvas.toDataURL("image/jpeg", 0.85)); } catch { /* tainted canvas */ } }, []); useEffect(() => { const video = videoRef.current; if (isActive) { setHasLoaded(false); if (video) { video.currentTime = 0; video.play().catch(() => {}); } } else { if (video) { if (video.readyState >= 2) captureFrame(video); video.pause(); } } }, [isActive, captureFrame]); const handleCanPlay = useCallback(() => { setHasLoaded(true); }, []); return ( <>

{title}

{description}

{isActive ? (
{expanded && } ); } function HeroCarousel() { const [activeIndex, setActiveIndex] = useState(0); const [isPaused, setIsPaused] = useState(false); const [isGifExpanded, setIsGifExpanded] = useState(false); const [containerWidth, setContainerWidth] = useState(0); const [cardHeight, setCardHeight] = useState(420); const containerRef = useRef(null); const activeCardRef = useRef(null); const directionRef = useRef<"forward" | "backward">("forward"); const goTo = useCallback( (newIndex: number) => { directionRef.current = newIndex >= activeIndex ? "forward" : "backward"; setActiveIndex(newIndex); }, [activeIndex] ); useEffect(() => { const el = containerRef.current; if (!el) return; const update = () => setContainerWidth(el.offsetWidth); update(); const observer = new ResizeObserver(update); observer.observe(el); return () => observer.disconnect(); }, []); useEffect(() => { const el = activeCardRef.current; if (!el) return; const update = () => setCardHeight(el.offsetHeight); update(); const observer = new ResizeObserver(update); observer.observe(el); return () => observer.disconnect(); }, [activeIndex, containerWidth]); useEffect(() => { if (isPaused || isGifExpanded) return; const timer = setTimeout(() => { directionRef.current = "forward"; setActiveIndex((prev) => (prev >= carouselItems.length - 1 ? 0 : prev + 1)); }, 8000); return () => clearTimeout(timer); }, [activeIndex, isPaused, isGifExpanded]); const cardWidth = containerWidth < 640 ? containerWidth * 0.85 : containerWidth < 1024 ? Math.min(containerWidth * 0.7, 680) : Math.min(containerWidth * 0.55, 900); const baseOffset = containerWidth < 640 ? containerWidth * 0.2 : containerWidth < 1024 ? containerWidth * 0.15 : 150; const stackGap = containerWidth < 640 ? 35 : containerWidth < 1024 ? 45 : 55; const perspective = containerWidth < 640 ? 800 : containerWidth < 1024 ? 1000 : 1200; const getCardStyle = useCallback( (index: number) => { const diff = index - activeIndex; if (diff === 0) { const originX = directionRef.current === "forward" ? 1 : 0; return { x: -cardWidth / 2, rotateY: 0, zIndex: 20, originX, overlayOpacity: 0, blur: 0 }; } const dist = Math.abs(diff); const isLeft = diff < 0; const offset = baseOffset + (dist - 1) * stackGap; const t = Math.min(1, dist / 3); return { x: -cardWidth / 2 + (isLeft ? -offset : offset), rotateY: isLeft ? 90 : -90, zIndex: 20 - dist, originX: isLeft ? 0 : 1, overlayOpacity: t, blur: t * 6, }; }, [activeIndex, cardWidth, baseOffset, stackGap] ); return (
setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} onTouchStart={() => setIsPaused(true)} onTouchEnd={() => setIsPaused(false)} >
{containerWidth > 0 && carouselItems.map((item, i) => { const style = getCardStyle(i); return ( goTo(i) : undefined} animate={{ x: style.x, rotateY: style.rotateY, }} transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }} > ); })}
{carouselItems.map((_, i) => (
); } export { HeroCarousel, HeroCarouselCard };