206 lines
7.2 KiB
TypeScript
206 lines
7.2 KiB
TypeScript
|
|
"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;
|