"use client"; import { useEffect, useRef, useState } from "react"; import { gsap } from "gsap"; import { useGSAP } from "@gsap/react"; import { ArrowRight } from "@/components/ui/icons"; import AnimatedButton from "@/components/ui/AnimatedButton"; /** * Cinematic GSAP hero. * - Slide changes wipe in from the right (clip-path) while the old image * gently scales — film-style cut. * - Headline words reveal through overflow masks, per slide. * - Subtle mouse parallax on the background. * - Vertical slide menu (right), ghost index numeral + progress bar (left). * - Auto-advances; any interaction restarts the timer. * * Swap slide images in /public/images (HeroBanner*.jpg). */ const SLIDES = [ { id: "web", image: "/images/HeroBanner1.jpg", tag: "Web Platforms", title: "Software that ships and scales.", desc: "Production-grade web platforms engineered for speed, stability and growth.", }, { id: "mobile", image: "/images/HeroBanner2.jpg", tag: "Mobile Apps", title: "Apps people love to open.", desc: "Native and cross-platform experiences crafted down to the last interaction.", }, { id: "ai", image: "/images/HeroBanner3.jpg", tag: "Artificial Intelligence", title: "Intelligence, built into product.", desc: "Generative AI, ML and automation woven into the heart of your software.", }, ]; const HOLD = 6; // seconds per slide export default function HeroShowcase() { const root = useRef(null); const [active, setActive] = useState(0); const len = SLIDES.length; const slide = SLIDES[active]; const activeRef = useRef(0); const transitioning = useRef(false); const imageRefs = useRef([]); const progressTween = useRef(null); const goRef = useRef(() => {}); /** Transition to slide idx: wipe the new image in, swap text mid-wipe. */ const go = (idx) => { if (idx === activeRef.current || transitioning.current) return; transitioning.current = true; progressTween.current?.kill(); const incoming = imageRefs.current[idx]; const outgoing = imageRefs.current[activeRef.current]; activeRef.current = idx; const tl = gsap.timeline({ onComplete: () => { transitioning.current = false; }, }); tl.to(".hx-swap", { y: -30, opacity: 0, duration: 0.4, ease: "power2.in" }, 0) .set(incoming, { zIndex: 2, opacity: 1, clipPath: "inset(0 0 0 100%)", scale: 1.12 }, 0) .to(incoming, { clipPath: "inset(0 0 0 0%)", duration: 1.1, ease: "power3.inOut" }, 0.1) .to(incoming, { scale: 1, duration: 1.8, ease: "power2.out" }, 0.1) .to(outgoing, { scale: 1.07, duration: 1.1, ease: "power3.inOut" }, 0.1) .call(() => setActive(idx), null, 0.62) .set(outgoing, { opacity: 0, zIndex: 1, scale: 1 }); }; goRef.current = go; /* Intro — chrome + first image (runs once). */ useGSAP( () => { const tl = gsap.timeline({ defaults: { ease: "power3.out" } }); tl.from(imageRefs.current[0], { scale: 1.18, duration: 1.6, ease: "power2.out" }, 0) .from(".hx-eyebrow", { y: 18, opacity: 0, duration: 0.6 }, 0.3) .from(".hx-cta", { y: 22, opacity: 0, stagger: 0.1, duration: 0.6 }, 0.7) .from(".hx-menu-item", { x: 30, opacity: 0, stagger: 0.08, duration: 0.5 }, 0.8) .from(".hx-bottom", { y: 16, opacity: 0, duration: 0.6 }, 0.9); }, { scope: root } ); /* Per-slide text reveal + progress (runs on mount and every change). */ useGSAP( () => { gsap.fromTo( ".hx-word-inner", { yPercent: 120 }, { yPercent: 0, duration: 0.85, stagger: 0.07, ease: "power3.out" } ); gsap.fromTo( ".hx-fade", { y: 22, opacity: 0 }, { y: 0, opacity: 1, duration: 0.6, stagger: 0.1, ease: "power2.out", delay: 0.15 } ); progressTween.current?.kill(); progressTween.current = gsap.fromTo( ".hx-progress", { scaleX: 0 }, { scaleX: 1, duration: HOLD, ease: "none", onComplete: () => goRef.current((activeRef.current + 1) % len), } ); }, { scope: root, dependencies: [active] } ); /* Mouse parallax on the background. */ useGSAP( () => { const el = root.current; const xTo = gsap.quickTo(".hx-bg", "x", { duration: 0.9, ease: "power3" }); const yTo = gsap.quickTo(".hx-bg", "y", { duration: 0.9, ease: "power3" }); const move = (e) => { const r = el.getBoundingClientRect(); xTo(((e.clientX - r.left) / r.width - 0.5) * -22); yTo(((e.clientY - r.top) / r.height - 0.5) * -14); }; el.addEventListener("mousemove", move); return () => el.removeEventListener("mousemove", move); }, { scope: root } ); useEffect(() => () => progressTween.current?.kill(), []); return (
{/* Background stack (slightly oversized for parallax) */}
{SLIDES.map((s, i) => (
(imageRefs.current[i] = el)} className="absolute inset-0 will-change-transform" style={{ backgroundImage: `url(${s.image})`, backgroundSize: "cover", backgroundPosition: "center", opacity: i === 0 ? 1 : 0, zIndex: i === 0 ? 2 : 1, }} /> ))}
{/* Legibility overlays */}
{/* Content */}
A technology studio for ambitious teams {/* Swappable per-slide block */}
{slide.tag}

{slide.title.split(" ").map((w, i) => ( {w} {i < slide.title.split(" ").length - 1 &&  } ))}

{slide.desc}

Start a project See our work
{/* Right — vertical slide menu */}
{SLIDES.map((s, i) => ( ))}
{/* Bottom-left — ghost numeral + progress */}
0{active + 1}
0{active + 1} / 0{len}
); }