Files
whispering-tree/sections/ProductPreview.tsx

206 lines
7.2 KiB
TypeScript
Raw Normal View History

2025-11-10 17:10:34 +05:30
"use client";
import Headline from "@/components/ui/headline";
import Image from "next/image";
import gsap from "gsap";
import { useEffect, useRef, useState } from "react";
import Button from "@/components/shared/Button";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
const ProductPreview = () => {
const headingRef = useRef<HTMLHeadingElement | null>(null);
const headingRef2 = useRef<HTMLHeadingElement | null>(null);
const paraRef = useRef<HTMLParagraphElement | null>(null);
const paraRef2 = useRef<HTMLParagraphElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const [activeIndex, setActiveIndex] = useState(0);
const imageRef = useRef<HTMLDivElement | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const listItemsRef = useRef<(HTMLLIElement | null)[]>([]);
useSplitHeading(headingRef, paraRef);
useSplitHeading(headingRef2, paraRef2);
const features = [
{
feature: "Tree Health View with Vitality Gauge",
Image: "/images/png/hero-bg.png",
},
{
feature: "Sensor Management System",
Image: "/images/png/hero-bg.png",
},
{
feature: "Data Export & Analytics Tools",
Image: "/images/png/hero-bg.png",
},
{
feature: "Multi-user Access and Permissions",
Image: "/images/png/hero-bg.png",
},
];
useEffect(() => {
if (!imageRef.current) return;
gsap.fromTo(
imageRef.current,
{ opacity: 0, scale: 0.97 },
{ opacity: 1, scale: 1, duration: 0.8, ease: "power2.out" }
);
}, [activeIndex]);
useEffect(() => {
intervalRef.current = setInterval(() => {
setActiveIndex((prev) => (prev + 1) % features.length);
}, 4000);
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, []);
/* --------------------------------------------------------------- */
/* 3. Scroll-triggered: Image clip-path + List stagger */
/* --------------------------------------------------------------- */
useEffect(() => {
const ctx = gsap.context(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".report-wrapper",
start: "top 85%",
toggleActions: "play none none none",
},
});
/* ---- Image: bottom → top clip-path ---- */
if (imageRef.current) {
gsap.set(imageRef.current, {
clipPath: "inset(100% 0% 0% 0%)",
opacity: 0,
});
tl.to(imageRef.current, {
clipPath: "inset(0% 0% 0% 0%)",
opacity: 1,
duration: 1.2,
ease: "power3.out",
});
}
const items = listItemsRef.current.filter(Boolean);
gsap.set(items, { y: 60 });
tl.to(
items,
{
y: 0,
duration: 0.8,
ease: "power2.out",
stagger: 0.15,
},
"-=0.8"
); // overlap slightly with image reveal
}, containerRef);
return () => ctx.revert();
}, []);
return (
<section ref={containerRef} className="relative h-full w-full">
<div className="tab:px-6 tab:py-8 mx-auto flex h-full w-full max-w-[1420px] flex-col items-center justify-start gap-12 sm:px-2.5 sm:pb-8 lg:px-0">
<div className="tab:w-9/12 flex flex-col items-center justify-center gap-4 sm:w-full md:w-7/12">
<Headline text="Product Preview" />
<h1
ref={headingRef}
className="font-switzer text-h-mobile-30 leading-h5 tab:text-h-2-60 tab:leading-h2 text-center font-semibold text-black"
>
Manage Every Tree with{" "}
<span className="font-playfair text-brand font-semibold italic">
Clarity.
</span>
and{" "}
<span className="font-playfair text-brand font-semibold italic">
Confidence.{" "}
</span>
</h1>
<p
ref={paraRef}
className="text-b-3-16 leading-b3 font-mium-reg text-center tracking-[-0.8px] text-black"
>
A powerful all-in-one dashboard to monitor tree health, sensors, and
data insights built for precision forestry and sustainability.
</p>
</div>
<div className="w-full lg:px-8">
<div className="tab:w-5/12 flex flex-col gap-2 sm:w-full">
<h1
ref={headingRef2}
className="font-switzer text-h-mobile-30 leading-h5 tab:text-h-2-60 tab:leading-h2 text-left font-semibold text-black"
>
Export for{" "}
<span className="font-playfair text-brand font-semibold italic">
Reports
</span>
</h1>
<p
ref={paraRef2}
className="text-b-3-16 leading-b3 font-mium-reg text-left tracking-[-0.8px] text-black"
>
Generate ready-to-share reports with insights for management,
research, or community updates.
</p>
</div>
<div className="tab:flex-row report-wrapper tab:mt-0 tab:gap-0 flex w-full items-center justify-between sm:mt-5 sm:flex-col-reverse sm:gap-4">
<div className="tab:w-6/12 w-full sm:w-full">
<ul className="mb-10 flex flex-col gap-3 overflow-hidden">
{features.map((item, idx) => (
<li
key={idx}
ref={(el) => {
listItemsRef.current[idx] = el;
}}
className={`flex items-center gap-3 transition-all duration-500 ${
activeIndex === idx
? "scale-[1.02] opacity-100"
: "scale-100 opacity-30"
}`}
>
<div
className={`flex h-10 w-10 items-center justify-center rounded-full transition-all duration-500 ${
activeIndex === idx ? "bg-[#A3C9A8]" : "bg-[#C2D2C5]"
}`}
>
<Image
src="/images/svg/leaf.svg"
alt="Leaf icon"
width={20}
height={20}
className="h-5 w-5 object-contain"
/>
</div>
<p className="font-switzer text-b-1-20 leading-b1 font-medium text-black">
{item.feature}
</p>
</li>
))}
</ul>
<Button text="Try for free" />
</div>
{/* RIGHT SIDE: Changing Image */}
<div
ref={imageRef}
key={features[activeIndex].Image}
className="tab:w-6/12 relative h-[400px] w-full overflow-hidden rounded-xl sm:w-full"
>
<Image
src={features[activeIndex].Image}
alt={features[activeIndex].feature}
width={600}
height={400}
className="h-full w-full rounded-xl object-cover"
/>
</div>
</div>
</div>
</div>
</section>
);
};
export default ProductPreview;