done landing page

This commit is contained in:
2025-11-10 17:10:34 +05:30
parent 3852d46661
commit 483515e163
105 changed files with 3529 additions and 104 deletions

159
sections/About.tsx Normal file
View File

@@ -0,0 +1,159 @@
"use client";
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(ScrollTrigger, SplitText);
const About = () => {
const aboutTitle = useRef<HTMLHeadingElement>(null);
const subHeadingRef = useRef<HTMLParagraphElement>(null);
const subHeading2Ref = useRef<HTMLParagraphElement>(null);
const mainHeadingRef = useRef<HTMLHeadingElement>(null);
const subSplit1 = useRef<SplitText | null>(null);
const subSplit2 = useRef<SplitText | null>(null);
const mainSplit = useRef<SplitText | null>(null);
useEffect(() => {
const ctx = gsap.context(() => {
// Split subheadings into words
const headingSplit = new SplitText(aboutTitle.current, { type: "chars" });
if (subHeadingRef.current) {
subSplit1.current = new SplitText(subHeadingRef.current, {
type: "words",
wordsClass: "word",
});
}
if (subHeading2Ref.current) {
subSplit2.current = new SplitText(subHeading2Ref.current, {
type: "words",
wordsClass: "word",
});
}
const allSubWords = [
...(subSplit1.current?.words || []),
...(subSplit2.current?.words || []),
];
gsap.from(headingSplit.chars, {
yPercent: 100,
opacity: 0,
duration: 1,
ease: "expo.out",
stagger: 0.025,
scrollTrigger: {
trigger: aboutTitle.current,
start: "top 85%",
toggleActions: "play none none none",
},
});
gsap.from(allSubWords, {
yPercent: 100,
opacity: 0,
duration: 1,
ease: "expo.out",
stagger: 0.02,
scrollTrigger: {
trigger: subHeadingRef.current,
start: "top 85%",
toggleActions: "play none none none",
// markers: true,
},
});
// MAIN HEADING fade-in per character
if (mainHeadingRef.current) {
mainSplit.current = new SplitText(mainHeadingRef.current, {
type: "chars",
charsClass: "char",
});
const chars = mainSplit.current.chars;
gsap.fromTo(
chars,
{ opacity: 0.3 },
{
opacity: 1,
ease: "expo.out",
stagger: 0.04,
scrollTrigger: {
trigger: mainHeadingRef.current,
start: "top 85%",
end: "bottom 60%",
scrub: 1,
},
}
);
}
});
return () => {
ctx.revert();
subSplit1.current?.revert();
subSplit2.current?.revert();
mainSplit.current?.revert();
};
}, []);
return (
<section
id="about"
className="tab:flex-row tab:gap-0 tab:px-6 mx-auto flex h-fit w-full max-w-[1420px] items-start justify-between py-8 sm:flex-col sm:gap-4 sm:px-2.5 lg:px-0"
>
{/* Left */}
<div className="tab:w-3/12 sm:w-full">
<h1
ref={aboutTitle}
className="text-h-5-28 font-switzer leading-h4 text-left font-semibold text-black"
>
About Us
</h1>
</div>
{/* Right */}
<div className="tab:w-8/12 flex flex-col gap-5 sm:w-full">
{/* MAIN HEADING */}
<h2
ref={mainHeadingRef}
className="font-switzer sm:text-h-4-34 tab:text-h-6-38 tab:leading-h3 overflow-hidden font-semibold text-black sm:leading-10"
>
At{" "}
<span className="text-brand font-playfair font-semibold italic">
FalktronTrees
</span>{" "}
merges IoT, AI, and environmental intelligence to connect nature and
technology, creating smart solutions that monitor and improve the
health of trees and ecosystems.
</h2>
{/* Sub-heading 1 */}
<p
ref={subHeadingRef}
className="font-mium-reg text-b-2-17 leading-b2 w-full overflow-hidden text-left tracking-[-0.8px] text-black"
>
Based in Oelde, Germany, FalktronTrees brings together innovation,
data science, and environmental care to empower cities, organizations,
and individuals to manage natural resources intelligently.
</p>
{/* Sub-heading 2 */}
<p
ref={subHeading2Ref}
className="font-mium-reg text-b-2-17 leading-b2 w-full overflow-hidden text-left tracking-[-0.8px] text-black"
>
Under the leadership of Matthias Mut, co-founder and CEO, the company
combines expertise in AI-driven analytics, sensor technology, and
prompt engineering to deliver cutting-edge environmental monitoring
systems.
</p>
</div>
</section>
);
};
export default About;

134
sections/Benefit.tsx Normal file
View File

@@ -0,0 +1,134 @@
"use client";
import Headline from "@/components/ui/headline";
import Image from "next/image";
import { useEffect, useRef } from "react";
import ScrollTrigger from "gsap/ScrollTrigger";
import gsap from "gsap";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
const Benefit = () => {
const headingRef = useRef<HTMLHeadingElement | null>(null);
const paraRef = useRef<HTMLParagraphElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
useSplitHeading(headingRef, paraRef);
const benefits = [
{
icon: "/images/svg/mdi_water-check-outline (1).svg",
title: "Save Water & Costs",
description:
"Optimize irrigation with real-time data and reduce unnecessary water usage.",
},
{
icon: "/images/svg/entypo_tree.svg",
title: "Healthier Trees",
description:
"Detect stress early and ensure optimal growth and longevity.",
},
{
icon: "/images/svg/iconoir_reports.svg",
title: "Transparent Reports",
description:
"Provide actionable insights for councils, cities, and citizens alike.",
},
{
icon: "/images/svg/lucide_monitor-cog.svg",
title: "Fast Setup",
description:
"Deploy smart monitoring from pilot to full project within weeks.",
},
];
useEffect(() => {
const cards = gsap.utils.toArray<HTMLDivElement>(".animation .card-inner");
cards.forEach((card) => {
gsap.from(card, {
x: -130,
opacity: 0,
duration: 1,
ease: "expo.out",
scrollTrigger: {
trigger: ".wrapper",
start: "top 80%",
},
});
});
return () => {
ScrollTrigger.getAll().forEach((st) => st.kill());
};
}, []);
return (
<section
ref={containerRef}
id="benefits"
className="bg-light-200 relative h-full w-full"
>
<div className="tab:px-6 mx-auto flex w-full max-w-[1420px] flex-col items-center justify-start gap-12 py-8 sm:px-2.5 lg:h-screen 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="Benefits" />
<h1
ref={headingRef}
className="font-switzer sm:text-h-mobile-30 sm:leading-h5 tab:text-[50px] tab:leading-h3 lg:text-h-2-60 lg:leading-h2 overflow-hidden text-center font-semibold text-black"
>
Smarter Trees. Greener{" "}
<span className="font-playfair text-brand font-semibold italic">
Cities.
</span>
Sustainable{" "}
<span className="font-playfair text-brand font-semibold italic">
Future.{" "}
</span>
</h1>
<p
ref={paraRef}
className="text-b-3-16 leading-b3 font-mium-reg text-center tracking-[-0.8px] text-black"
>
Experience real-time insights, efficient water use, and healthier
urban forests start your pilot or schedule a demo today.
</p>
</div>
<div className="tab:grid-cols-2 wrapper tab:gap-6 grid grid-cols-1 sm:grid-cols-1 sm:gap-3 md:grid-cols-3 lg:grid-cols-4">
{benefits.map((benefit, index) => (
<div
key={index}
className="animation tab:max-w-[320px] group h-[350px] overflow-hidden sm:w-full"
>
<div
className={`tab:max-w-[320px] card-inner relative flex h-[350px] cursor-pointer flex-col items-start justify-between overflow-hidden rounded-[20px] bg-white px-6 py-5 transition-colors duration-500 ease-in-out sm:w-full`}
>
<div className="bg-brand absolute top-5 left-6 h-20 w-20 scale-0 rounded-full transition-transform duration-500 ease-out group-hover:scale-[10]"></div>
<div className="bg-light-200 relative z-20 flex h-20 w-20 items-center justify-center rounded-full">
<Image
src={benefit.icon}
alt={benefit.title}
width={32}
height={32}
className="object-cover"
/>
</div>
<div className="relative z-20 h-[124px]">
<h3
className={`text-h-7-24 leading-h5 font-switzer mb-2.5 text-left font-semibold text-black group-hover:text-white`}
>
{benefit.title}
</h3>
<p
className={`text-b-3-16 leading-b3 font-mium-reg tracking-[-0.8px] text-black group-hover:text-white`}
>
{benefit.description}
</p>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default Benefit;

75
sections/CTA.tsx Normal file
View File

@@ -0,0 +1,75 @@
"use client";
import Button from "@/components/shared/Button";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import ScrollTrigger from "gsap/ScrollTrigger";
import Image from "next/image";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
gsap.registerPlugin(ScrollTrigger);
const CTA = () => {
const headingRef = useRef<HTMLHeadingElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const paraRef = useRef<HTMLParagraphElement>(null);
const buttonRef = useRef<HTMLDivElement>(null);
useSplitHeading(headingRef, paraRef);
useEffect(() => {
if (!containerRef.current) return;
if (buttonRef.current) {
gsap.from(buttonRef.current, {
opacity: 0,
y: 20,
duration: 0.8,
ease: "power2.out",
delay: 0.6,
scrollTrigger: {
trigger: containerRef.current,
start: "top 80%",
},
});
}
}, []);
return (
<div
ref={containerRef}
id="cta"
className="tab:mx-0 relative mx-auto h-full w-full sm:px-2.5"
>
<div className="tab:h-[450px] tab:my-6 relative mx-auto flex w-full max-w-[1420px] items-center justify-center overflow-hidden rounded-3xl bg-[url('/images/png/1-2.jpeg')] bg-cover bg-center sm:h-[490px]">
<div className="relative z-20 flex flex-col items-center justify-center gap-3 px-4">
<h1
ref={headingRef}
className="font-switzer tab:text-h-2-60 md:leading-h1 tab:leading-h2 text-center font-semibold text-white sm:text-[50px] sm:leading-[54px]"
>
Join the Future of Smart Tree Care
</h1>
<p
aria-hidden="true"
ref={paraRef}
className="text-b-3-16 leading-b3 font-mium-reg tab:w-9/12 text-center tracking-[-0.8px] text-white sm:w-full"
>
Experience real-time insights, efficient water use, and healthier
urban forests start your pilot or schedule a demo today.
</p>
<div ref={buttonRef}>
<Button text="Book a Demo" />
</div>
</div>
<Image
src="/images/png/Blob 5.png"
width={700}
height={500}
alt="overlay"
className="absolute bottom-0 left-0 h-[410px] w-full object-cover"
/>
</div>
</div>
);
};
export default CTA;

117
sections/CaseStudy.tsx Normal file
View File

@@ -0,0 +1,117 @@
"use client";
import Headline from "@/components/ui/headline";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
import Image from "next/image";
import ScrollTrigger from "gsap/ScrollTrigger";
import gsap from "gsap";
import { useEffect, useRef } from "react";
import { useCases } from "@/data/useCases";
import { useRouter } from "next/navigation";
import { usePageTransition } from "@/layout/Layout";
const CaseStudy = () => {
const headingRef = useRef<HTMLHeadingElement | null>(null);
const paraRef = useRef<HTMLParagraphElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const { startTransition } = usePageTransition();
useSplitHeading(headingRef, paraRef);
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray<HTMLDivElement>(".use-card");
gsap.from(cards, {
y: 100,
opacity: 0,
duration: 1.2,
ease: "power3.out",
stagger: 0.19,
scrollTrigger: {
trigger: ".use-wrapper",
start: "top 80%",
end: "bottom 60%",
toggleActions: "play none none none",
},
});
return () => {
ScrollTrigger.getAll().forEach((st) => st.kill());
};
}, []);
return (
<section
id="use-cases"
ref={containerRef}
className="bg-light-200 relative h-full w-full"
>
<div className="tab:px-6 mx-auto flex h-full w-full max-w-[1420px] flex-col items-center justify-start gap-12 py-8 sm:px-2.5 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="Use Case" />
<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"
>
One Technology, Many{" "}
<span className="font-playfair text-brand font-semibold italic">
Applications
</span>
</h1>
<p
ref={paraRef}
className="text-b-3-16 leading-b3 font-mium-reg text-center tracking-[-0.8px] text-black"
>
Whether for municipalities, research, or contractors FalktronTrees
brings actionable insights to every level of urban forestry.
</p>
</div>
<div className="tab:grid-cols-2 wrapper tab:gap-6 grid grid-cols-1 sm:grid-cols-1 sm:gap-3 md:grid-cols-3 lg:grid-cols-4">
{useCases.map((useCase, index) => (
<div
key={index}
className="group use-card relative overflow-hidden rounded-2xl bg-white p-3.5"
>
{/* Image */}
<div className="relative h-[190px] overflow-hidden rounded-2xl">
<Image
src={useCase.image}
alt={useCase.title}
width={400}
height={190}
className="h-full w-full transform rounded-2xl object-cover transition-transform duration-500 ease-out group-hover:scale-105"
/>
</div>
{/* Text Content */}
<div className="flex h-max flex-col items-start px-1 pt-6 pb-2 text-left">
<h3 className="text-b-1-20 font-switzer leading-b1 mb-2 text-left font-medium text-black">
{useCase.title}
</h3>
<p className="text-gray font-mium-reg mb-6 line-clamp-3 text-left text-sm leading-5 tracking-[-0.8px]">
{useCase.description}
</p>
<button
onClick={() => startTransition(`/use-cases/${useCase.slug}`)}
className="text-b-4-14 font-switzer bg-brand leading-b4 inline-flex cursor-pointer items-center gap-2 rounded-full px-4 py-3 font-normal text-white transition-all duration-300 hover:gap-3"
>
Learn more
<Image
src="/images/svg/arrow.svg"
alt="Learn more"
className="h-4.5 w-4.5 object-contain"
width={16}
height={16}
/>
</button>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default CaseStudy;

79
sections/FAQ.tsx Normal file
View File

@@ -0,0 +1,79 @@
"use client";
import { FAQ } from "@/components/ui/accordion";
import Headline from "@/components/ui/headline";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
import { useRef } from "react";
const FAQSection = () => {
const headingRef = useRef<HTMLHeadingElement | null>(null);
const paraRef = useRef<HTMLParagraphElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
useSplitHeading(headingRef, paraRef);
const faq = [
{
question: "What is Whispering Trees?",
answer:
"Whispering Trees is an IoT and AI platform that monitors the health of urban trees in real time. Our sensors measure moisture, stress, and environmental data — helping cities make smarter irrigation and maintenance decisions.",
},
{
question: "How many trees can we monitor?",
answer:
"The system is fully scalable — from a single pilot district with 10 sensors to thousands of trees across multiple cities. You can start small and expand anytime without hardware changes.",
},
{
question: "How accurate are the sensors?",
answer:
"Whispering Trees sensors are scientifically validated with precision readings for moisture and electrical resistance, ensuring early stress detection with over 95% reliability in field conditions.",
},
{
question: "Do we need Wi-Fi or mobile data in the field?",
answer:
"No fixed Wi-Fi is required. The sensors communicate via low-power wireless networks (LoRaWAN or LTE-M), optimized for long range and minimal energy use — perfect for public spaces.",
},
{
question: "What kind of support is included?",
answer:
"We offer onboarding assistance, online documentation, and email support for all Starter/Core users. Enterprise plans include dedicated account management and advanced analytics.",
},
{
question: "How soon can we start a pilot project?",
answer:
"You can begin within 48 weeks. After the first sensor installation, data appears live on your dashboard — giving you immediate insights into your urban forests health.",
},
];
return (
<section
ref={containerRef}
id="faq"
className="tab:px-0 flex h-full flex-col items-center justify-center pb-8 sm:px-2.5"
>
<div className="tab:w-9/12 flex flex-col items-center justify-center gap-4 sm:w-full md:w-6/12">
<Headline text="FAQ" />
<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"
>
Whispering Trees{" "}
<span className="font-playfair text-brand font-semibold italic">
Explained
</span>
</h1>
<p
ref={paraRef}
className="text-b-3-16 leading-b3 font-mium-reg max-w-2xl text-center tracking-[-0.8px] text-black/80"
>
Learn how Whispering Trees helps cities grow greener and smarter.
</p>
</div>
<div className="tab:w-11/12 mt-10 w-full md:w-9/12">
<FAQ faqData={faq} />
</div>
</section>
);
};
export default FAQSection;

149
sections/Hero.tsx Normal file
View File

@@ -0,0 +1,149 @@
"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;

102
sections/HeroCase.tsx Normal file
View File

@@ -0,0 +1,102 @@
"use client";
import Image from "next/image";
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(SplitText);
interface HeroClientProps {
item: {
heroImage: string;
title: string;
description: string;
};
}
export default function HeroClient({ item }: HeroClientProps) {
const containerRef = useRef<HTMLDivElement>(null);
const headingRef = useRef<HTMLHeadingElement>(null);
const subHeadingRef = useRef<HTMLParagraphElement>(null);
useEffect(() => {
const ctx = gsap.context(() => {
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,
}
);
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,
}
);
}
}, containerRef);
return () => ctx.revert();
}, []);
return (
<section
ref={containerRef}
className="font-switzer tab:p-5 tab:h-[90vh] relative mx-auto w-full max-w-[1650px] overflow-hidden sm:h-[80vh] sm:p-2.5"
>
<div className="relative mx-auto flex h-full w-full items-center justify-center overflow-hidden rounded-2xl">
<Image
src={item.heroImage}
alt={item.title}
width={1200}
height={600}
className="absolute inset-0 h-full w-full rounded-2xl object-cover"
/>
<div className="flex w-full max-w-[1420px] items-center px-5">
<div className="tab:p-0 tab:mt-0 relative z-20 flex w-full max-w-[850px] flex-col gap-5 sm:mt-10 sm:px-0">
<h1
ref={headingRef}
className="font-switzer sm:text-hero-mobile tab:text-h-1-96 tab:leading-h1 text-left font-semibold text-white sm:leading-[54px]"
>
{item.title}
</h1>
<p
ref={subHeadingRef}
className="font-mium-reg tab:text-b-1-20 sm:text-b-3-16 w-full text-left leading-normal text-white sm:tracking-[-0.6px]"
>
{item.description}
</p>
</div>
</div>
<Image
src="/images/png/Blob 5.png"
width={700}
height={500}
alt="overlay"
className="tab:h-[450px] absolute bottom-0 left-0 w-full object-cover sm:h-[40vh]"
/>
</div>
</section>
);
}

191
sections/HowWork.tsx Normal file
View File

@@ -0,0 +1,191 @@
"use client";
import Headline from "@/components/ui/headline";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
import Image from "next/image";
import { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const HowWork = () => {
const headingRef = useRef<HTMLHeadingElement>(null);
const paraRef = useRef<HTMLParagraphElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
// Refs for animation targets
const imageRef = useRef<HTMLImageElement>(null);
const leftStepsRef = useRef<(HTMLDivElement | null)[]>([]);
const rightStepsRef = useRef<(HTMLDivElement | null)[]>([]);
useSplitHeading(headingRef, paraRef);
const steps = [
{
id: "01",
text: "Deploy Whispering Trees Pulse sensor directly onto your tree.",
position: "left",
},
{
id: "02",
text: "Real-time data automatically streams to Whispering Trees Cloud.",
position: "left",
},
{
id: "03",
text: "Dashboard displays tree health, history, and irrigation requirements.",
position: "right",
},
{
id: "04",
text: "Export data or integrate seamlessly with city systems.",
position: "right",
},
];
useEffect(() => {
const ctx = gsap.context(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: imageRef.current,
start: "top 70%",
toggleActions: "play none none none",
},
});
// 1. Image: clip-path from bottom → top
if (imageRef.current) {
gsap.set(imageRef.current, { clipPath: "inset(100% 0% 0% 0%)" });
tl.to(imageRef.current, {
clipPath: "inset(0% 0% 0% 0%)",
duration: 1.2,
ease: "power3.out",
});
}
leftStepsRef.current.forEach((el, i) => {
if (el) {
gsap.set(el, { opacity: 0, x: -80 });
tl.to(
el,
{
opacity: 1,
x: 0,
duration: 0.8,
ease: "power2.out",
},
i === 0 ? "-=0.8" : "-=0.6" // stagger
);
}
});
rightStepsRef.current.forEach((el, i) => {
if (el) {
gsap.set(el, { opacity: 0, x: 80 });
tl.to(
el,
{
opacity: 1,
x: 0,
duration: 0.8,
ease: "power2.out",
},
i === 0 ? "-=0.8" : "-=0.6"
);
}
});
}, containerRef);
return () => ctx.revert();
}, []);
return (
<section
ref={containerRef}
id="how-it-works"
className="tab:px-6 relative h-full w-full sm:px-2.5 lg:px-0"
>
<div className="mx-auto flex h-full w-full max-w-[1420px] flex-col items-center justify-start gap-12 py-8 sm:pt-8">
{/* Heading */}
<div className="tab:w-9/12 flex flex-col items-center justify-center gap-4 sm:w-full md:w-7/12">
<Headline text="How It Works" />
<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"
>
The Whispering Trees{" "}
<span className="font-playfair text-brand italic">Process.</span>
</h1>
<p
ref={paraRef}
className="font-mium-reg text-b-3-16 leading-b3 text-center tracking-[-0.8px] text-black"
>
Monitor tree health in real-time and optimize irrigation schedules
with our intelligent sensor system, ensuring urban forests thrive
while conserving water.
</p>
</div>
{/* Steps + Image */}
<div className="tab:flex-row tab:h-[500px] flex w-full items-center justify-between sm:flex-col">
{/* Left Steps */}
<div className="tab:w-4/12 flex h-full flex-col items-center justify-between sm:w-full">
{steps
.filter((s) => s.position === "left")
.map((step, i) => (
<div
key={step.id}
ref={(el) => {
leftStepsRef.current[i] = el;
}}
className="tab:h-[200px] tab:w-[350px] flex h-[180px] w-full flex-col items-center gap-4 sm:h-[180px]"
>
<h2 className="font-switzer text-h-4-34 leading-h4 font-medium text-black">
{step.id}
</h2>
<p className="font-switzer text-b-1-20 leading-b1 text-center font-medium text-black">
{step.text}
</p>
</div>
))}
</div>
{/* Center Image */}
<div className="tab:mt-0 tab:mb-0 tab:w-4/12 h-full sm:-mt-14 sm:mb-14 sm:w-full">
<Image
ref={imageRef}
alt="Product"
width={600}
height={600}
src="/images/png/product.png"
className="h-full w-full object-contain"
/>
</div>
{/* Right Steps */}
<div className="tab:w-4/12 flex h-full flex-col items-center justify-between sm:w-full">
{steps
.filter((s) => s.position === "right")
.map((step, i) => (
<div
key={step.id}
ref={(el) => {
rightStepsRef.current[i] = el;
}}
className="tab:h-[200px] tab:w-[350px] flex h-[180px] w-full flex-col items-center gap-4 sm:h-[180px]"
>
<h2 className="font-switzer text-h-4-34 leading-h4 font-medium text-black">
{step.id}
</h2>
<p className="font-switzer text-b-1-20 leading-b1 text-center font-medium text-black">
{step.text}
</p>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default HowWork;

205
sections/ProductPreview.tsx Normal file
View File

@@ -0,0 +1,205 @@
"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;

48
sections/SocialProff.tsx Normal file
View File

@@ -0,0 +1,48 @@
import { Marquee } from "@/components/ui/logo-marquee";
import Image from "next/image";
const companies = [
{ url: "/images/svg/googleplus logo.svg" },
{ url: "/images/svg/linkedin-plain-wordmark logo.svg" },
{ url: "/images/svg/metallb-horizontal-white logo.svg" },
{ url: "/images/svg/microsoft logo.svg" },
// add more logopngeeded
];
const SocialProff = () => {
return (
<section
id="logos"
className="tab:px-6 relative z-20 mx-auto w-full max-w-[1420px] py-2 sm:px-2.5 lg:px-0"
>
<div className="tab:flex-row flex w-full flex-col items-center justify-between gap-2 md:gap-0">
<h3 className="font-switzer leading-b2 tab:w-3/12 tab:text-left tab:px-0 text-xl font-semibold text-black sm:w-full sm:px-7 sm:text-center lg:w-2/12">
Trusted by more than <span className="text-black">5k+</span> satisfied
clients
</h3>
<div className="tab:w-8/12 relative mt-2 overflow-hidden sm:w-full">
<Marquee gap="3rem" className="max-w-full [--duration:10s]">
{companies.map((company, idx) => (
<Image
key={idx}
width={112}
height={42}
src={company.url}
className="w-full object-contain grayscale transition-all duration-300 hover:grayscale-0 sm:h-10 md:h-10"
alt={`Company logo ${idx + 1}`}
/>
))}
</Marquee>
{/* left gradient fade */}
<div className="pointer-events-none absolute inset-y-0 left-0 h-full w-1/4 bg-gradient-to-r from-white to-transparent"></div>
{/* right gradient fade */}
<div className="pointer-events-none absolute inset-y-0 right-0 h-full w-1/4 bg-gradient-to-l from-white to-transparent"></div>
</div>
</div>
</section>
);
};
export default SocialProff;

186
sections/Testimonial.tsx Normal file
View File

@@ -0,0 +1,186 @@
"use client";
import { Marquee } from "@/components/ui/logo-marquee";
import { TestimonialCard } from "@/components/ui/testimonial-marquee";
import { useEffect, useRef } from "react";
import gsap from "gsap";
import SplitText from "gsap/SplitText";
import ScrollTrigger from "gsap/ScrollTrigger";
import Headline from "@/components/ui/headline";
import { useSplitHeading } from "@/lib/useTextRevealHeading";
gsap.registerPlugin(SplitText, ScrollTrigger);
const testimonials = [
{
id: 1,
name: "Dazzle Healer",
role: "Front End Developer",
avatar: "/images/png/Avatar.png",
message:
"I've used other kits, but this one is the best. The attention to detail and usability are truly amazing for all designers. I highly recommend it for any type of project.",
},
{
id: 2,
name: "Ammy Johnson",
role: "Scientific Advisor",
avatar: "/images/png/Avatar (2).png",
message:
"The level of accuracy and precision in the data analytics blew me away. Whispering Tree makes environmental monitoring so much easier for our teams.",
},
{
id: 3,
name: "Leo Smith",
role: "Urban Planner",
avatar: "/images/png/Avatar (1).png",
message:
"Integrating the IoT sensors into our citys green spaces helped us reduce water usage by over 30%. Its a game-changer for sustainable urban development.",
},
{
id: 4,
name: "Sofia Martins",
role: "Research Scientist",
avatar: "/images/png/Avatar (3).png",
message:
"Real-time health insights from trees have opened a whole new perspective for environmental research. Its efficient, reliable, and visually elegant.",
},
{
id: 5,
name: "Michael Chen",
role: "Environmental Engineer",
avatar: "/images/png/Avatar (4).png",
message:
"Our city parks have become smarter and greener since adopting Whispering Tree. The platforms clarity in reports is unmatched.",
},
{
id: 6,
name: "Laura Green",
role: "Sustainability Consultant",
avatar: "/images/png/Avatar (1).png",
message:
"Its refreshing to see technology being used for natures benefit. The dashboards are intuitive and provide deep insights for eco-management.",
},
{
id: 7,
name: "David Kim",
role: "IoT Solutions Architect",
avatar: "/images/png/Avatar (4).png",
message:
"The ease of integrating sensors into existing infrastructure made implementation seamless. Great work by the Whispering Tree team!",
},
{
id: 8,
name: "Priya Nair",
role: "Climate Research Analyst",
avatar: "/images/png/Avatar.png",
message:
"This platform allows us to visualize and act on real environmental data in real time. The insights are helping cities adapt sustainably.",
},
];
const Testimonial = () => {
const headingRef = useRef<HTMLHeadingElement | null>(null);
const paraRef = useRef<HTMLParagraphElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
useSplitHeading(headingRef, paraRef);
const middleIndex = Math.ceil(testimonials.length / 2);
const firstHalf = testimonials.slice(0, middleIndex);
const secondHalf = testimonials.slice(middleIndex);
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray<HTMLDivElement>(".test-card");
gsap.from(cards, {
y: 100,
opacity: 0,
duration: 1.2,
ease: "power3.out",
stagger: 0.19,
scrollTrigger: {
trigger: ".test-wrapper",
start: "top 80%",
end: "bottom 60%",
toggleActions: "play none none none",
},
});
return () => {
ScrollTrigger.getAll().forEach((st) => st.kill());
};
}, []);
return (
<section
ref={containerRef}
className="tab:px-6 mx-auto flex h-full w-full max-w-[1420px] flex-col items-center justify-start gap-12 py-8 sm:px-2.5 lg:px-0"
>
<div className="tab:w-9/12 flex flex-col items-center justify-center gap-4 sm:w-full md:w-6/12">
<Headline text="Feedback" />
<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"
>
Powered by{" "}
<span className="font-playfair text-brand font-semibold italic">
Trust.
</span>
<br />
Proven by{" "}
<span className="font-playfair text-brand font-semibold italic">
Nature.
</span>
</h1>
<p
ref={paraRef}
className="text-b-3-16 leading-b3 font-mium-reg text-center tracking-[-0.8px] text-black"
>
See how municipalities and partners use FalktronTrees to make smarter,
data-driven decisions for urban green spaces.
</p>
</div>
<div className="test-wrapper relative w-full overflow-hidden">
<Marquee gap="1rem" className="test-card max-w-full [--duration:120s]">
{firstHalf.map((test, idx) => (
<TestimonialCard
key={idx}
img={test.avatar}
id={idx + 1}
name={test.name}
title={test.role}
body={test.message}
/>
))}
</Marquee>
<Marquee
gap="1rem"
className="test-card max-w-full [--duration:120s]"
reverse
pauseOnHover
>
{secondHalf.map((test, idx) => (
<TestimonialCard
key={idx}
img={test.avatar}
id={idx + 1}
name={test.name}
title={test.role}
body={test.message}
/>
))}
</Marquee>
{/* left gradient fade */}
<div className="pointer-events-none absolute inset-y-0 left-0 h-full w-1/4 bg-gradient-to-r from-white to-transparent"></div>
{/* right gradient fade */}
<div className="pointer-events-none absolute inset-y-0 right-0 h-full w-1/4 bg-gradient-to-l from-white to-transparent"></div>
</div>
</section>
);
};
export default Testimonial;