Files
www_project_mycelium/src/components/ClickableGallery.tsx
2025-09-12 22:18:00 +02:00

163 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useEffect, useMemo, useState } from 'react'
import Image from 'next/image'
import { motion, AnimatePresence } from 'framer-motion'
import { wrap } from 'popmotion'
import { Button } from '@/components/Button'
const galleryItems = [
{ text: 'Navigate and interact with any web interface', image: '/images/interface.png' },
{ text: 'Process documents across all formats', image: '/images/docs.png' },
{ text: 'Execute multi-step workflows autonomously', image: '/images/flow.png' },
{ text: 'Manage calendars, emails, and tasks', image: '/images/calendar.png' },
{ text: 'Perform deep semantic search across all data sources', image: '/images/data.png' },
{ text: 'Identify patterns in complex datasets', image: '/images/datasets.png' },
{ text: 'Provide real-time market intelligence', image: '/images/market.png' },
{ text: 'Generate and debug code in multiple languages', image: '/images/code.png' },
{ text: 'Create consistent branded content', image: '/images/branding.png' },
{ text: 'Translate and localize materials', image: '/images/translate.png' },
{ text: 'Transform and migrate data structures', image: '/images/structure.png' },
]
// 🔧 Carousel Config
const VISIBLE = 4
const CARD_SIZE = 450 // square size on desktop
const GAP = 380 // spacing for larger cards
const ROT_Y = 18
const DEPTH = 260
const SCALE_DROP = 0.12
const AUTOPLAY_MS = 3200
export function ClickableGallery() {
const [active, setActive] = useState(0)
const [hovering, setHovering] = useState(false)
// 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="pt-24 pb-12 text-center">
<motion.h1
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 1 }}
className="mx-auto max-w-5xl text-4xl font-medium text-pretty text-[#2F3178] lg:text-6xl"
>
One Agent, Endless Possibilities.
</motion.h1>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 1.5 }}
className="mx-auto max-w-3xl mt-8 text-2xl font-medium text-pretty text-[#2F3178] lg:text-2xl"
>
The future isnt about more tools its about one intelligent partner that can do it all. This is your gateway to creativity, automation, and discovery.
</motion.p>
</div>
<section
className="relative w-full h-[900px] flex items-center justify-center overflow-hidden bg-background -mt-48"
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
{/* Soft edge fades */}
<div className="pointer-events-none absolute inset-y-0 left-0 w-32 bg-gradient-to-r from-background to-transparent" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-32 bg-gradient-to-l from-background to-transparent" />
<div className="relative w-full max-w-[1800px] h-full" 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.90
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"
initial={{ opacity: 0 }}
animate={{
transform: `translateX(${x}px) translateZ(${z}px) rotateY(${r}deg) scale(${s})`,
zIndex,
opacity: o,
}}
exit={{ opacity: 0 }}
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
onClick={() => setActive(idx)}
>
{/* Square container, keeps image ratio inside */}
<div
className="relative rounded-2xl overflow-hidden bg-white flex items-center justify-center"
style={{ width: CARD_SIZE, height: CARD_SIZE }}
>
<Image
src={item.image}
alt={item.text}
fill
className="object-contain rounded-2xl"
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-white/70 hover:bg-white rounded-full p-3 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-white/70 hover:bg-white rounded-full p-3 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 gap-6 rounded-3xl bg-white/95 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 py-6 backdrop-blur">
<h2 className="text-2xl lg:text-3xl font-semibold leading-tight text-[#2F3178] max-w-[820px]">
{galleryItems[active].text}
</h2>
<Button href="#" color="cyan" className="text-sm px-4 py-2 lg:text-base">
Start
</Button>
</div>
</div>
</div>
</section>
</>
)
}