180 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
'use client'
 | 
						|
 | 
						|
import { useEffect, useMemo, useState } from 'react'
 | 
						|
import { useResponsiveCarousel } from '@/hooks/useResponsiveCarousel';
 | 
						|
import Image from 'next/image'
 | 
						|
import { motion, AnimatePresence } from 'framer-motion'
 | 
						|
import { wrap } from 'popmotion'
 | 
						|
import { Button } from '@/components/Button';
 | 
						|
import { SectionHeader, P, CT } from '@/components/Texts';
 | 
						|
import { TypeAnimation } from 'react-type-animation'
 | 
						|
import { FadeIn } from './FadeIn';
 | 
						|
 | 
						|
const galleryItems = [
 | 
						|
  { text: 'Navigate and interact with any web interface', image: '/images/gallery/interface.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Process documents across all formats', image: '/images/gallery/docs.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Execute multi-step workflows autonomously', image: '/images/gallery/flow.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Manage calendars, emails, and tasks', image: '/images/gallery/calendar.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Perform deep semantic search across all data sources', image: '/images/gallery/data.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Identify patterns in complex datasets', image: '/images/gallery/datasets.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Provide real-time market intelligence', image: '/images/gallery/market.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Generate and debug code in multiple languages', image: '/images/gallery/code.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Create consistent branded content', image: '/images/gallery/branding.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Translate and localize materials', image: '/images/gallery/translate.jpg', width: 448, height: 277 },
 | 
						|
  { text: 'Transform and migrate data structures', image: '/images/gallery/structure.jpg', width: 448, height: 277 },
 | 
						|
]
 | 
						|
 | 
						|
// 🔧 Carousel Config
 | 
						|
const VISIBLE = 4;
 | 
						|
const AUTOPLAY_MS = 3200;
 | 
						|
 | 
						|
export function ClickableGalleryLight() {
 | 
						|
  const [active, setActive] = useState(0);
 | 
						|
  const [hovering, setHovering] = useState(false);
 | 
						|
  const { GAP, ROT_Y, DEPTH, SCALE_DROP } = useResponsiveCarousel();
 | 
						|
 | 
						|
  // autoplay
 | 
						|
  useEffect(() => {
 | 
						|
    if (hovering) return
 | 
						|
    const id = setInterval(() => setActive((i) => wrap(0, galleryItems.length, i + 1)), AUTOPLAY_MS)
 | 
						|
    return () => clearInterval(id)
 | 
						|
  }, [hovering])
 | 
						|
 | 
						|
  const indices = useMemo(
 | 
						|
    () => [...Array(VISIBLE * 2 + 1)].map((_, i) => wrap(0, galleryItems.length, active + i - VISIBLE)),
 | 
						|
    [active]
 | 
						|
  )
 | 
						|
 | 
						|
  const next = () => setActive((i) => wrap(0, galleryItems.length, i + 1))
 | 
						|
  const prev = () => setActive((i) => wrap(0, galleryItems.length, i - 1))
 | 
						|
 | 
						|
  return (
 | 
						|
    <div className="bg-[#FAFAFA]">
 | 
						|
      <div className="relative isolate pt-8 pb-0 text-center w-full">
 | 
						|
        <FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
 | 
						|
          <div className="mx-auto max-w-5xl lg:mt-12">
 | 
						|
            <SectionHeader className="text-center" color="dark">Agents with Endless Possibilities.</SectionHeader>
 | 
						|
          </div>
 | 
						|
        </FadeIn>
 | 
						|
        <FadeIn transition={{ duration: 0.8, delay: 0.2 }}>
 | 
						|
          <div className="mx-auto max-w-4xl mt-6 lg:px-0 px-4">
 | 
						|
            <P className="text-center" color="dark">
 | 
						|
            Your private agent coordinates a team of specialists that spin up on demand, collaborate across your world, and deliver end-to-end results.
 | 
						|
            Many agents, one intelligence—yours.
 | 
						|
            </P>
 | 
						|
          </div>
 | 
						|
        </FadeIn>
 | 
						|
      </div>
 | 
						|
      <FadeIn transition={{ duration: 1, delay: 0.4 }}>
 | 
						|
        <section
 | 
						|
          className="relative w-full flex items-center justify-center overflow-hidden -mt-8 pt-0 pb-0"
 | 
						|
          onMouseEnter={() => setHovering(true)}
 | 
						|
          onMouseLeave={() => setHovering(false)}
 | 
						|
        >
 | 
						|
                    <div className="relative w-full max-w-[1800px] h-[300px] md:h-[500px]" style={{ perspective: '1600px' }}>
 | 
						|
            <div className="absolute inset-0" style={{ transformStyle: 'preserve-3d' }}>
 | 
						|
              <AnimatePresence initial={false}>
 | 
						|
                {indices.map((idx, i) => {
 | 
						|
                  const distance = i - VISIBLE;
 | 
						|
                  const item = galleryItems[idx];
 | 
						|
 | 
						|
                  const x = distance * GAP;
 | 
						|
                  const z = -Math.abs(distance) * DEPTH;
 | 
						|
                  const r = distance * ROT_Y;
 | 
						|
                  const s = 1 - Math.abs(distance) * SCALE_DROP;
 | 
						|
                  const o = distance === 0 ? 1 : 0.80;
 | 
						|
                  const zIndex = 100 - Math.abs(distance);
 | 
						|
 | 
						|
                  return (
 | 
						|
                    <motion.div
 | 
						|
                      key={`${idx}-${i}`}
 | 
						|
                      className={`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 will-change-transform overflow-hidden ${distance === 0 ? 'rounded-xl' : ''}`}
 | 
						|
                      initial={{ opacity: 0 }}
 | 
						|
                      animate={{
 | 
						|
                        transform: `translateX(${x}px) translateZ(${z}px) rotateY(${r}deg) scale(${s})`,
 | 
						|
                        zIndex,
 | 
						|
                        opacity: o,
 | 
						|
                        boxShadow: distance === 0 ? '0 0 20px 5px rgba(0, 0, 0, 0.1)' : 'none',
 | 
						|
                      }}
 | 
						|
                      exit={{ opacity: 0 }}
 | 
						|
                      transition={{ type: 'spring', stiffness: 220, damping: 26 }}
 | 
						|
                      onClick={() => setActive(idx)}
 | 
						|
                    >
 | 
						|
                      <div className="relative bg-gray-100 flex items-center justify-center">
 | 
						|
                        <Image
 | 
						|
                          src={item.image}
 | 
						|
                          alt={item.text}
 | 
						|
                          width={item.width}
 | 
						|
                          height={item.height}
 | 
						|
                          className="object-contain"
 | 
						|
                          priority={i === VISIBLE}
 | 
						|
                        />
 | 
						|
                      </div>
 | 
						|
                    </motion.div>
 | 
						|
                  );
 | 
						|
                })}
 | 
						|
              </AnimatePresence>
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
 | 
						|
          {/* Arrows */}
 | 
						|
          <div className="absolute inset-y-0 left-8 hidden md:flex items-center z-50">
 | 
						|
            <button
 | 
						|
              onClick={prev}
 | 
						|
              className="bg-white/50 rounded-full p-2 shadow-lg backdrop-blur-md text-black"
 | 
						|
              aria-label="Previous"
 | 
						|
            >
 | 
						|
              <svg className="size-8" viewBox="0 0 24 24" fill="none" dangerouslySetInnerHTML={{ __html: '<path d="M15 19L8 12l7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>' }} />
 | 
						|
            </button>
 | 
						|
          </div>
 | 
						|
          <div className="absolute inset-y-0 right-8 hidden md:flex items-center z-50">
 | 
						|
            <button
 | 
						|
              onClick={next}
 | 
						|
              className="bg-white/50 rounded-full p-2 shadow-lg backdrop-blur-md text-black"
 | 
						|
              aria-label="Next"
 | 
						|
            >
 | 
						|
              <svg className="size-8" viewBox="0 0 24 24" fill="none" dangerouslySetInnerHTML={{ __html: '<path d="M9 5l7 7-7 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>' }} />
 | 
						|
            </button>
 | 
						|
          </div>
 | 
						|
 | 
						|
          {/* Foreground pill (Desktop) */}
 | 
						|
          <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[60] hidden md:block">
 | 
						|
            <div className="flex items-center justify-between w-[1040px] gap-6 rounded-2xl bg-gray-100/80 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 backdrop-blur">
 | 
						|
              <CT as="h4" className="max-w-[820px] h-[72px] flex items-center" color="dark">
 | 
						|
                <TypeAnimation
 | 
						|
                  key={active}
 | 
						|
                  sequence={[galleryItems[active].text]}
 | 
						|
                  wrapper="span"
 | 
						|
                  speed={50}
 | 
						|
                  repeat={0}
 | 
						|
                />
 | 
						|
              </CT>
 | 
						|
              <Button href="#" color="cyan" className="text-sm px-4 py-2 lg:text-base whitespace-nowrap">
 | 
						|
                Start
 | 
						|
              </Button>
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
        </section>
 | 
						|
 | 
						|
        {/* Text box (Mobile) */}
 | 
						|
        <div className="md:hidden w-full px-4 -mt-12 mb-16">
 | 
						|
            <div className="flex flex-row items-center justify-between w-full gap-x-4 rounded-2xl bg-gray-100/80 p-4 backdrop-blur-md">
 | 
						|
              <CT as="h4" className="w-full text-left h-[72px] leading-tight flex items-center" color="dark">
 | 
						|
                <TypeAnimation
 | 
						|
                  key={active}
 | 
						|
                  sequence={[galleryItems[active].text]}
 | 
						|
                  wrapper="span"
 | 
						|
                  speed={50}
 | 
						|
                  repeat={0}
 | 
						|
                />
 | 
						|
              </CT>
 | 
						|
              <Button href="#" color="cyan" className="text-xs px-3 py-1.5 whitespace-nowrap">
 | 
						|
                 Start
 | 
						|
              </Button>
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
      </FadeIn>
 | 
						|
    </div>
 | 
						|
  );
 | 
						|
}
 |