done landing page
This commit is contained in:
159
sections/About.tsx
Normal file
159
sections/About.tsx
Normal 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
134
sections/Benefit.tsx
Normal 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
75
sections/CTA.tsx
Normal 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
117
sections/CaseStudy.tsx
Normal 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
79
sections/FAQ.tsx
Normal 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 4–8 weeks. After the first sensor installation, data appears live on your dashboard — giving you immediate insights into your urban forest’s 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
149
sections/Hero.tsx
Normal 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
102
sections/HeroCase.tsx
Normal 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
191
sections/HowWork.tsx
Normal 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
205
sections/ProductPreview.tsx
Normal 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
48
sections/SocialProff.tsx
Normal 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
186
sections/Testimonial.tsx
Normal 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 city’s green spaces helped us reduce water usage by over 30%. It’s 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. It’s 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 platform’s clarity in reports is unmatched.",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Laura Green",
|
||||
role: "Sustainability Consultant",
|
||||
avatar: "/images/png/Avatar (1).png",
|
||||
message:
|
||||
"It’s refreshing to see technology being used for nature’s 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;
|
||||
Reference in New Issue
Block a user