fx
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
|
||||
import { useEffect, useMemo, useState, useRef } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { motion, AnimatePresence, useInView } from 'framer-motion'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { wrap } from 'popmotion'
|
||||
import { Button } from '@/components/Button';
|
||||
import { H2, P, H4, CT, CP } from '@/components/Texts';
|
||||
import { H2, 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 },
|
||||
@@ -33,8 +34,6 @@ const AUTOPLAY_MS = 3200
|
||||
export function ClickableGallery() {
|
||||
const [active, setActive] = useState(0)
|
||||
const [hovering, setHovering] = useState(false)
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true });
|
||||
|
||||
// autoplay
|
||||
useEffect(() => {
|
||||
@@ -52,113 +51,112 @@ export function ClickableGallery() {
|
||||
const prev = () => setActive((i) => wrap(0, galleryItems.length, i - 1))
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div className="relative isolate pt-8 pb-0 bg-transparent text-center w-full ">
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }} transition={{ duration: 0.8, delay: 0.1 }} className="mx-auto max-w-5xl">
|
||||
<H2 className="text-center">One Agent, Endless Possibilities.</H2>
|
||||
</motion.div>
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }} transition={{ duration: 0.8, delay: 0.2 }} className="mx-auto max-w-4xl mt-6">
|
||||
<P className="text-center" color="primary">
|
||||
The future isn’t about more tools. It’s about one intelligent partner that can do it all. This is your gateway to creativity, automation, and discovery.
|
||||
</P>
|
||||
</motion.div>
|
||||
</div>
|
||||
<motion.section
|
||||
initial={{ opacity: 0 }}
|
||||
animate={isInView ? { opacity: 1 } : { opacity: 0 }}
|
||||
transition={{ duration: 1, delay: 0.4 }}
|
||||
className="relative w-full flex items-center justify-center overflow-hidden bg-transparent -mt-8 pt-0 pb-0"
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
>
|
||||
|
||||
|
||||
<div className="relative w-full max-w-[1800px] 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(255, 255, 255, 0.2)' : 'none',
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
|
||||
onClick={() => setActive(idx)}
|
||||
>
|
||||
<div
|
||||
className="relative bg-black flex items-center justify-center"
|
||||
>
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.text}
|
||||
width={item.width}
|
||||
height={item.height}
|
||||
className="object-contain text-white"
|
||||
priority={i === VISIBLE}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Arrows */}
|
||||
<div className="absolute inset-y-0 left-8 flex items-center z-50">
|
||||
<button
|
||||
onClick={prev}
|
||||
className="bg-transparent rounded-full p-2 shadow-lg backdrop-blur-md"
|
||||
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 flex items-center z-50">
|
||||
<button
|
||||
onClick={next}
|
||||
className="bg-transparent rounded-full p-2 shadow-lg backdrop-blur-md"
|
||||
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 */}
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[60]">
|
||||
<div className="flex items-center justify-between w-[1040px] gap-6 rounded-2xl bg-black/30 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 backdrop-blur">
|
||||
<CT as="h4" className="max-w-[820px] h-[72px] text-white flex items-center">
|
||||
<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">
|
||||
Start
|
||||
</Button>
|
||||
<div>
|
||||
<div className="relative isolate pt-8 pb-0 bg-transparent text-center w-full">
|
||||
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<H2 className="text-center">One Agent, Endless Possibilities.</H2>
|
||||
</div>
|
||||
</div>
|
||||
</FadeIn>
|
||||
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}>
|
||||
<div className="mx-auto max-w-4xl mt-6">
|
||||
<P className="text-center" color="primary">
|
||||
The future isn’t about more tools. It’s about one intelligent partner that can do it all. This is your gateway to creativity, automation, and discovery.
|
||||
</P>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</motion.section>
|
||||
<FadeIn transition={{ duration: 1, delay: 0.4 }}>
|
||||
<section
|
||||
className="relative w-full flex items-center justify-center overflow-hidden bg-transparent -mt-8 pt-0 pb-0"
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
>
|
||||
<div className="relative w-full max-w-[1800px] 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(255, 255, 255, 0.2)' : 'none',
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
|
||||
onClick={() => setActive(idx)}
|
||||
>
|
||||
<div className="relative bg-black flex items-center justify-center">
|
||||
<Image
|
||||
src={item.image}
|
||||
alt={item.text}
|
||||
width={item.width}
|
||||
height={item.height}
|
||||
className="object-contain text-white"
|
||||
priority={i === VISIBLE}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrows */}
|
||||
<div className="absolute inset-y-0 left-8 flex items-center z-50">
|
||||
<button
|
||||
onClick={prev}
|
||||
className="bg-transparent rounded-full p-2 shadow-lg backdrop-blur-md"
|
||||
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 flex items-center z-50">
|
||||
<button
|
||||
onClick={next}
|
||||
className="bg-transparent rounded-full p-2 shadow-lg backdrop-blur-md"
|
||||
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 */}
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[60]">
|
||||
<div className="flex items-center justify-between w-[1040px] gap-6 rounded-2xl bg-black/30 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 backdrop-blur">
|
||||
<CT as="h4" className="max-w-[820px] h-[72px] text-white flex items-center">
|
||||
<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">
|
||||
Start
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</FadeIn>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user