150 lines
5.6 KiB
TypeScript
150 lines
5.6 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import React, { useEffect, useRef } from "react";
|
|||
|
|
import gsap from "gsap";
|
|||
|
|
import { SplitText } from "gsap/SplitText";
|
|||
|
|
import Image from "next/image";
|
|||
|
|
import Button from "@/components/shared/Button";
|
|||
|
|
import SecondaryButton from "@/components/shared/Secondary";
|
|||
|
|
import Navbar from "@/components/shared/Navbar";
|
|||
|
|
import MobileNavbar from "@/components/ui/mobile-navbar";
|
|||
|
|
|
|||
|
|
// Register plugins (once)
|
|||
|
|
gsap.registerPlugin(SplitText);
|
|||
|
|
|
|||
|
|
const Hero = () => {
|
|||
|
|
// ── Refs ─────────────────────────────────────────────────────────────────────
|
|||
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|||
|
|
const headingRef = useRef<HTMLHeadingElement>(null);
|
|||
|
|
const subHeadingRef = useRef<HTMLParagraphElement>(null);
|
|||
|
|
const buttonWrapperRef = useRef<HTMLDivElement>(null);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const ctx = gsap.context(() => {
|
|||
|
|
// ── 2. HEADING – char-by-char ───────────────────────────────────
|
|||
|
|
if (!headingRef.current) return;
|
|||
|
|
const headingSplit = new SplitText(headingRef.current, { type: "words" });
|
|||
|
|
gsap.fromTo(
|
|||
|
|
headingSplit.words,
|
|||
|
|
{ yPercent: 120, opacity: 0 },
|
|||
|
|
{
|
|||
|
|
yPercent: 0,
|
|||
|
|
opacity: 1,
|
|||
|
|
duration: 1.2,
|
|||
|
|
ease: "expo.out",
|
|||
|
|
stagger: 0.09,
|
|||
|
|
delay: 0.3, // after navbar
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// ── 3. SUB-HEADING – char-by-char ───────────────────────────────
|
|||
|
|
if (subHeadingRef.current) {
|
|||
|
|
const subSplit = new SplitText(subHeadingRef.current, {
|
|||
|
|
type: "words",
|
|||
|
|
});
|
|||
|
|
gsap.fromTo(
|
|||
|
|
subSplit.words,
|
|||
|
|
{ yPercent: 100, opacity: 0 },
|
|||
|
|
{
|
|||
|
|
yPercent: 0,
|
|||
|
|
opacity: 1,
|
|||
|
|
duration: 1,
|
|||
|
|
ease: "expo.out",
|
|||
|
|
stagger: 0.04,
|
|||
|
|
delay: 0.6, // after heading
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── 4. BUTTONS – slide up ───────────────────────────────────────
|
|||
|
|
const buttons = buttonWrapperRef.current?.children;
|
|||
|
|
if (buttons?.length) {
|
|||
|
|
gsap.fromTo(
|
|||
|
|
buttons,
|
|||
|
|
{ y: 40, opacity: 0 },
|
|||
|
|
{
|
|||
|
|
y: 0,
|
|||
|
|
opacity: 1,
|
|||
|
|
duration: 0.8,
|
|||
|
|
ease: "power3.out",
|
|||
|
|
stagger: 0.15,
|
|||
|
|
delay: 0.9, // after sub-heading
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}, containerRef);
|
|||
|
|
|
|||
|
|
// ── Cleanup ────────────────────────────────────────────────────────
|
|||
|
|
return () => ctx.revert();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<section
|
|||
|
|
ref={containerRef}
|
|||
|
|
id="home"
|
|||
|
|
className="font-switzer tab:p-5 tab:h-[110vh] mx-auto max-h-[900px] w-full max-w-[1650px] overflow-hidden sm:h-screen sm:p-2.5"
|
|||
|
|
>
|
|||
|
|
{/* ── Desktop Navbar (wrapped) ───────────────────────────────────── */}
|
|||
|
|
<Navbar />
|
|||
|
|
|
|||
|
|
{/* ── Mobile Navbar (wrapped) ────────────────────────────────────── */}
|
|||
|
|
<MobileNavbar />
|
|||
|
|
|
|||
|
|
{/* ── Hero Content ───────────────────────────────────────────────── */}
|
|||
|
|
<div className="relative mx-auto flex h-full w-full items-center justify-center overflow-hidden rounded-2xl">
|
|||
|
|
<video
|
|||
|
|
autoPlay
|
|||
|
|
muted
|
|||
|
|
loop
|
|||
|
|
playsInline
|
|||
|
|
className="absolute inset-0 h-full w-full object-cover"
|
|||
|
|
>
|
|||
|
|
<source src="/video/video-1.mp4" type="video/mp4" />
|
|||
|
|
Your browser does not support the video tag.
|
|||
|
|
</video>
|
|||
|
|
|
|||
|
|
<div className="tab:p-0 tab:mt-0 tab:max-w-[600px] relative z-20 mx-auto flex flex-col gap-5 sm:mt-10 sm:px-3 md:max-w-[750px] lg:max-w-[850px]">
|
|||
|
|
{/* Heading */}
|
|||
|
|
<h1
|
|||
|
|
ref={headingRef}
|
|||
|
|
className="font-switzer sm:text-hero-mobile tab:text-h-2-60 lg:text-h-1-96 tab:leading-h2 text-center font-semibold text-white sm:leading-[54px] md:text-[75px] md:leading-[75px]"
|
|||
|
|
>
|
|||
|
|
Trees can talk. We help you{" "}
|
|||
|
|
<span className="font-playfair font-semibold italic">listen.</span>
|
|||
|
|
</h1>
|
|||
|
|
|
|||
|
|
{/* Sub-heading */}
|
|||
|
|
<p
|
|||
|
|
ref={subHeadingRef}
|
|||
|
|
className="font-mium-reg tab:text-b-3-16 lg:text-b-1-20 sm:text-b-3-16 w-full text-center leading-normal text-white sm:tracking-[-0.6px]"
|
|||
|
|
>
|
|||
|
|
Whispering Trees shows you if a tree is healthy or thirsty — in real
|
|||
|
|
time. So you water only when needed, save money, and keep trees
|
|||
|
|
alive longer.
|
|||
|
|
</p>
|
|||
|
|
|
|||
|
|
{/* Buttons */}
|
|||
|
|
<div
|
|||
|
|
ref={buttonWrapperRef}
|
|||
|
|
className="tab:gap-5 flex w-full justify-center sm:flex-wrap sm:gap-3"
|
|||
|
|
>
|
|||
|
|
<Button text="Tree for Free" />
|
|||
|
|
<SecondaryButton targetId="how-it-works" text="How It Works" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Blob overlay */}
|
|||
|
|
<Image
|
|||
|
|
src="/images/png/Blob 5.png"
|
|||
|
|
width={700}
|
|||
|
|
height={500}
|
|||
|
|
alt="overlay"
|
|||
|
|
className="tab:h-[550px] absolute bottom-0 left-0 w-full object-cover sm:h-[70vh]"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default Hero;
|