diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..17027d6
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "plugins": ["prettier-plugin-tailwindcss"],
+
+ "trailingComma": "es5"
+}
diff --git a/app/globals.css b/app/globals.css
index a2dc41e..5c46589 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,26 +1,154 @@
@import "tailwindcss";
-
-:root {
- --background: #ffffff;
- --foreground: #171717;
+@font-face {
+ font-family: "Switzer Variable";
+ src: url("/font/Switzer-Variable.ttf") format("truetype");
+ font-weight: 100 900; /* Full variable range */
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Mium Regular";
+ src: url("/font/miumAccess-Regular.ttf") format("truetype");
+ font-weight: 300; /* Full variable range */
+ font-style: normal;
+ font-display: swap;
+}
+@font-face {
+ font-family: "Playfair Display";
+ src: url("/font/PlayfairDisplay-Italic-VariableFont_wght.ttf")
+ format("truetype");
+ font-weight: 300; /* Full variable range */
+ font-style: normal;
+ font-display: swap;
}
-
@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
+ --font-switzer: "Switzer Variable", system-ui, sans-serif;
+ --font-mium-reg: "Mium Regular", system-ui, sans-serif;
+ --font-playfair: "Playfair Display", Georgia, serif;
+
+ /* === HEADINGS === */
+ --text-h-1-96: 96px;
+ --text-h-2-60: 60px;
+ --text-h-3-40: 40px;
+ --text-h-4-34: 34px;
+ --text-h-5-28: 28px;
+ --text-h-6-38: 38px;
+ --text-h-7-24: 24px;
+ --text-h-mobile-30: 30px;
+ --text-hero-mobile: 56px;
+
+ --leading-h1: 90px;
+ --leading-h2: 63px;
+ --leading-h3: 48px;
+ --leading-h4: 42px;
+ --leading-h5: 30px;
+ --leading-mobile: 38px;
+
+ /* === BODY TEXT === */
+ --text-b-1-20: 20px;
+ --text-b-2-17: 17px;
+ --text-b-3-16: 16px;
+ --text-b-4-14: 14px;
+
+ --leading-b1: 25px;
+ --leading-b2: 26px;
+ --leading-b3: 23px;
+ --leading-b4: 16px;
+
+ --color-background: #f8f8ff;
+ --color-light-100: #d5ffdc;
+ --color-light-200: #ecf9db;
+ --color-light-300: #d5ffdc33;
+ --color-brand: #274b2d;
+ --color-hover: #2f5a36;
+ --color-btn: #274b2d;
+
+ --color-border: #3a6f43;
+
+ --color-secondary: #636369;
+ --color-gray: #423f3f;
+
+ --breakpoint-xl: 1600px;
+ --breakpoint-lg: 1400px;
+ --breakpoint-md: 1024px;
+ --breakpoint-tab: 767px;
+ --breakpoint-sm: 320px;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
+@keyframes marquee {
+ from {
+ transform: translateX(0);
+ }
+ to {
+ transform: translateX(calc(-100% - var(--gap)));
}
}
-body {
- background: var(--background);
- color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+/* Base marquee container */
+.marquee {
+ display: flex;
+ gap: var(--gap);
+ animation: marquee var(--duration) linear infinite;
+}
+
+@keyframes marquee {
+ from {
+ transform: translateX(0);
+ }
+ to {
+ transform: translateX(-50%);
+ }
+}
+
+.marquee {
+ display: flex;
+ gap: var(--gap);
+ animation: marquee var(--duration) linear infinite;
+}
+
+/* Reverse direction support */
+.marquee.reverse {
+ animation-direction: reverse;
+}
+
+/* Pause on hover */
+.group:hover .marquee.pause {
+ animation-play-state: paused;
+}
+/* === PAGE TRANSITION FIX === */
+
+.overlay {
+ grid-area: 1 / 1 / 2 / 2;
+ position: fixed; /* instead of relative — ensures full viewport coverage */
+ top: 0;
+ left: 0;
+ width: 100vw; /* use viewport units explicitly */
+ height: 100vh;
+ z-index: 1000;
+ pointer-events: none;
+}
+
+.overlay__path {
+ fill: var(--color-brand, #274b2d);
+ stroke: none;
+ vector-effect: non-scaling-stroke;
+}
+
+.view {
+ position: relative;
+ grid-area: 1 / 1 / 2 / 2;
+ display: grid;
+ place-items: center;
+}
+
+.view--2 {
+ background: var(--color-bg-view-2, #cbb37e);
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.5s ease;
+}
+
+.view--open {
+ pointer-events: auto;
+ opacity: 1;
}
diff --git a/app/layout.tsx b/app/layout.tsx
index f7fa87e..73e3096 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,20 +1,42 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Playfair_Display } from "next/font/google";
import "./globals.css";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+import SmoothScrollProvider from "@/lib/useLenisScroll";
+import { LayoutWithTransition } from "@/layout/Layout";
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title:
+ "Whispering Tree | Smart IoT & AI Solutions for Tree Health Monitoring",
+ description:
+ "Whispering Tree combines IoT sensors and AI analytics to monitor the health of trees in real time. Our smart environmental system helps cities, organizations, and individuals optimize irrigation, detect stress early, and promote sustainable growth through data-driven insights.",
+ keywords: [
+ "Whispering Tree",
+ "tree health monitoring",
+ "IoT for environment",
+ "AI tree analytics",
+ "smart irrigation",
+ "urban forestry technology",
+ "environmental intelligence",
+ "FalktronTrees",
+ "Matthias Mut",
+ "AI for sustainability",
+ "smart city solutions",
+ "real-time tree monitoring",
+ "IoT sensors for trees",
+ "green technology",
+ "water optimization system",
+ ],
+ openGraph: {
+ title:
+ "Whispering Tree | Smart IoT & AI Solutions for Tree Health Monitoring",
+ description:
+ "Whispering Tree combines IoT sensors and AI analytics to monitor the health of trees in real time. Our smart environmental system helps cities, organizations, and individuals optimize irrigation, detect stress early, and promote sustainable growth through data-driven insights.",
+ url: "https://whisperingtree.com",
+ type: "website",
+ },
+ icons: {
+ icon: "/fav.svg",
+ },
};
export default function RootLayout({
@@ -24,10 +46,10 @@ export default function RootLayout({
}>) {
return (
-
- {children}
+
+
+ {children}
+
);
diff --git a/app/page.tsx b/app/page.tsx
index 295f8fd..0fd0cbb 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,65 +1,34 @@
-import Image from "next/image";
+"use client";
+import Footer from "@/components/shared/Footer";
+import FAQ from "@/sections/FAQ";
+import About from "@/sections/About";
+import Benefit from "@/sections/Benefit";
+import CaseStudy from "@/sections/CaseStudy";
+import CTA from "@/sections/CTA";
+import Hero from "@/sections/Hero";
+import HowWork from "@/sections/HowWork";
+import ProductPreview from "@/sections/ProductPreview";
+import SocialProff from "@/sections/SocialProff";
+import Testimonial from "@/sections/Testimonial";
export default function Home() {
return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
+ <>
+
+ {/*
*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
}
diff --git a/app/sitemap.xml/route.ts b/app/sitemap.xml/route.ts
new file mode 100644
index 0000000..c4bffb1
--- /dev/null
+++ b/app/sitemap.xml/route.ts
@@ -0,0 +1,46 @@
+// app/sitemap/route.ts
+
+interface SitemapPage {
+ loc: string;
+ lastmod: string;
+}
+
+export const GET = async () => {
+ const BASE_URL = "https://trees.falktron.com";
+
+ const useCases = [
+ "forestry",
+ "research",
+ "agriculture",
+ "cities-communities",
+ ];
+
+ const pages: SitemapPage[] = [
+ { loc: `${BASE_URL}/`, lastmod: new Date().toISOString().split("T")[0] },
+ ...useCases.map((slug) => ({
+ loc: `${BASE_URL}/use-cases/${slug}`,
+ lastmod: new Date().toISOString().split("T")[0],
+ })),
+ ];
+
+ const xml = `
+
+ ${pages
+ .map(
+ (page) => `
+
+ ${page.loc}
+ ${page.lastmod}
+ `
+ )
+ .join("")}
+ `;
+
+ return new Response(xml, {
+ status: 200,
+ headers: {
+ "Content-Type": "application/xml",
+ "Cache-Control": "public, s-maxage=86400, stale-while-revalidate=3600",
+ },
+ });
+};
diff --git a/app/use-cases/[slug]/page.tsx b/app/use-cases/[slug]/page.tsx
new file mode 100644
index 0000000..51329ba
--- /dev/null
+++ b/app/use-cases/[slug]/page.tsx
@@ -0,0 +1,83 @@
+import Image from "next/image";
+import { notFound } from "next/navigation";
+import { useCases } from "@/data/useCases";
+import Footer from "@/components/shared/Footer";
+import Navbar from "@/components/shared/Navbar";
+import MobileNavbar from "@/components/ui/mobile-navbar";
+import CTA from "@/sections/CTA";
+import HeroClient from "@/sections/HeroCase";
+
+interface Props {
+ params: Promise<{ slug: string }>; // ← Promise!
+}
+
+export default async function UseCaseDetail({ params }: Props) {
+ const { slug } = await params;
+ const item = useCases.find((c) => c.slug === slug);
+ if (!item) notFound();
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Overview
+
+
+ {item.description}
+
+
+
+
+
+ Challenge
+
+
+ {item.challenge}
+
+
+
+
+
+
+ Our Solution
+
+
+ {item.solution}
+
+
+
+
+
+ Impact
+
+
+ {item.outcome}
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/components/shared/Button.tsx b/components/shared/Button.tsx
new file mode 100644
index 0000000..fa014fe
--- /dev/null
+++ b/components/shared/Button.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+interface ButtonProps {
+ text: string;
+ className?: string;
+}
+
+const Button: React.FC = ({ text, className = "" }) => {
+ return (
+
+ {/* Smooth hover text animation */}
+
+
+ {text}
+
+
+ {text}
+
+
+
+ );
+};
+
+export default Button;
diff --git a/components/shared/Footer.tsx b/components/shared/Footer.tsx
new file mode 100644
index 0000000..71128a5
--- /dev/null
+++ b/components/shared/Footer.tsx
@@ -0,0 +1,198 @@
+"use client";
+
+import Image from "next/image";
+import Link from "next/link";
+
+const Footer = () => {
+ const companyLinks = [
+ { label: "Home", href: "/#home" },
+ { label: "About Us", href: "/#about" },
+ { label: "Product", href: "/#product" },
+ { label: "Benefits", href: "/#benefits" },
+ { label: "How it works", href: "/#how-it-works" },
+ { label: "Use Cases", href: "/#use-cases" },
+ { label: "Privacy Policy", href: "/privacy" },
+ ];
+
+ const socialLinks = [
+ { label: "LinkedIn", href: "https://linkedin.com", icon: "linkedin" },
+ { label: "X", href: "https://x.com", icon: "x" },
+ { label: "Facebook", href: "https://facebook.com", icon: "facebook" },
+ { label: "Instagram", href: "https://instagram.com", icon: "instagram" },
+ ];
+
+ return (
+
+ );
+};
+
+export default Footer;
diff --git a/components/shared/Navbar.tsx b/components/shared/Navbar.tsx
new file mode 100644
index 0000000..0833303
--- /dev/null
+++ b/components/shared/Navbar.tsx
@@ -0,0 +1,112 @@
+"use client";
+
+import Image from "next/image";
+import { useEffect, useRef, useState } from "react";
+import { gsap } from "gsap";
+import Button from "./Button";
+import SmoothScrollLink from "@/components/ui/smooth-scroll-link";
+import Link from "next/link";
+import { usePageTransition } from "@/layout/Layout";
+
+const Navbar = () => {
+ const navbarRef = useRef(null);
+ const [scrolled, setScrolled] = useState(false);
+ const [showNavbar, setShowNavbar] = useState(true);
+ const { startTransition } = usePageTransition();
+
+ const [lastScrollY, setLastScrollY] = useState(0);
+
+ // ── GSAP: Initial slide-in ─────────────────────────────────────
+ useEffect(() => {
+ const el = navbarRef.current;
+ if (!el) return;
+
+ // Start off-screen
+ gsap.set(el, { yPercent: -100, opacity: 0 });
+
+ // Animate in
+ gsap.to(el, {
+ yPercent: 0,
+ opacity: 1,
+ duration: 1.2,
+ ease: "expo.out",
+ });
+ }, []);
+
+ // ── Scroll: Hide/show + background ─────────────────────────────
+ useEffect(() => {
+ const handleScroll = () => {
+ const currentScroll = window.scrollY;
+
+ // Hide on scroll down
+ if (currentScroll > lastScrollY && currentScroll > 100) {
+ setShowNavbar(false);
+ } else {
+ setShowNavbar(true);
+ }
+
+ setScrolled(currentScroll > 30);
+ setLastScrollY(currentScroll);
+ };
+
+ window.addEventListener("scroll", handleScroll, { passive: true });
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [lastScrollY]);
+
+ const navLinks = [
+ { label: "Home", href: "#home" },
+ { label: "About Us", href: "#about" },
+ { label: "Benefits", href: "#benefits" },
+ { label: "How it works", href: "/#how-it-works" },
+ { label: "FAQ", href: "#faq" },
+ ];
+
+ return (
+
+ );
+};
+
+export default Navbar;
diff --git a/components/shared/Secondary.tsx b/components/shared/Secondary.tsx
new file mode 100644
index 0000000..8af8209
--- /dev/null
+++ b/components/shared/Secondary.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+
+interface SecondaryButtonProps {
+ text: string;
+ targetId: string; // ← NEW: pass the section ID
+ className?: string;
+}
+
+const SecondaryButton: React.FC = ({
+ text,
+ targetId,
+ className = "",
+}) => {
+ const handleScroll = (e: React.MouseEvent) => {
+ e.preventDefault();
+ const section = document.getElementById(targetId);
+ if (section) {
+ const yOffset = -80;
+ const y =
+ section.getBoundingClientRect().top + window.pageYOffset + yOffset;
+ window.scrollTo({ top: y, behavior: "smooth" });
+ }
+ };
+
+ return (
+
+ {text}
+
+ );
+};
+
+export default SecondaryButton;
diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx
new file mode 100644
index 0000000..1327e15
--- /dev/null
+++ b/components/ui/accordion.tsx
@@ -0,0 +1,148 @@
+"use client";
+
+import React, { useState, useRef, useEffect } from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { Plus } from "lucide-react";
+import { cn } from "@/lib/utils";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+
+gsap.registerPlugin(ScrollTrigger);
+
+interface FAQItemType {
+ question: string;
+ answer: string;
+}
+
+interface FAQProps {
+ title?: string;
+ subtitle?: string;
+ faqData: FAQItemType[];
+ className?: string;
+}
+
+export const FAQ: React.FC = ({
+ title = "FAQs",
+ subtitle = "Frequently Asked Questions",
+ faqData,
+ className,
+ ...props
+}) => {
+ const accordionRef = useRef(null);
+
+ useEffect(() => {
+ const items = accordionRef.current?.querySelectorAll(".accordion-item");
+
+ if (items) {
+ gsap.set(items, {
+ opacity: 0,
+ y: 50,
+ });
+
+ ScrollTrigger.batch(items, {
+ onEnter: (batch) => {
+ gsap.to(batch, {
+ opacity: 1,
+ y: 0,
+ stagger: 0.12,
+ duration: 1,
+ ease: "power1.inOut",
+ overwrite: true,
+ });
+ },
+ start: "top 90%",
+ once: true,
+ });
+ }
+
+ return () => {
+ ScrollTrigger.getAll().forEach((t) => t.kill());
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+const FAQList = ({
+ faqData,
+ accordionRef,
+}: {
+ faqData: FAQItemType[];
+ accordionRef: React.RefObject;
+}) => (
+
+
+ {faqData?.map((faq, index) => (
+
+ ))}
+
+
+);
+
+const FAQItem = ({
+ question,
+ answer,
+ className,
+}: FAQItemType & { className?: string }) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+ setIsOpen(!isOpen)}
+ className="flex w-full cursor-pointer items-center justify-between gap-4 px-5 py-6 text-left"
+ >
+
+ {question}
+
+
+
+
+
+
+
+ {answer}
+
+
+
+ );
+};
diff --git a/components/ui/headline.tsx b/components/ui/headline.tsx
new file mode 100644
index 0000000..fa44b92
--- /dev/null
+++ b/components/ui/headline.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import React, { useEffect, useRef } from "react";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+
+gsap.registerPlugin(ScrollTrigger);
+
+type HeadlineProps = {
+ text: string;
+};
+
+const Headline: React.FC = ({ text }) => {
+ const headlineRef = useRef(null);
+
+ useEffect(() => {
+ if (!headlineRef.current) return;
+
+ gsap.from(headlineRef.current, {
+ y: 80,
+ opacity: 0,
+ duration: 1,
+ ease: "expo.out",
+ scrollTrigger: {
+ trigger: headlineRef.current,
+ start: "top 90%",
+ toggleActions: "play none none none",
+ },
+ });
+ }, []);
+
+ return (
+
+
+ {text}
+
+
+ );
+};
+
+export default Headline;
diff --git a/components/ui/loader.tsx b/components/ui/loader.tsx
new file mode 100644
index 0000000..6706139
--- /dev/null
+++ b/components/ui/loader.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import React, { useEffect, useRef } from "react";
+import gsap from "gsap";
+import Image from "next/image";
+
+const Loader = () => {
+ // We need a DOM node that will be the clipping container
+ const containerRef = useRef(null);
+ const imgRef = useRef(null);
+
+ useEffect(() => {
+ const container = containerRef.current;
+ const img = imgRef.current;
+ if (!container || !img) return;
+ gsap.set(container, { width: "0", clipPath: "inset(0 100% 0 0)" });
+ gsap.to(container, {
+ width: "280px", // reveal left to right
+ duration: 1.4,
+ clipPath: "inset(0 0% 0 0)",
+ ease: "expo.inout",
+ });
+ }, []);
+
+ return (
+
+ {/* Clipping container */}
+
+
+
+
+ );
+};
+
+export default Loader;
diff --git a/components/ui/logo-marquee.tsx b/components/ui/logo-marquee.tsx
new file mode 100644
index 0000000..97e4679
--- /dev/null
+++ b/components/ui/logo-marquee.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+
+interface MarqueeProps {
+ className?: string;
+ reverse?: boolean;
+ pauseOnHover?: boolean;
+ children?: React.ReactNode;
+ vertical?: boolean;
+ repeat?: number;
+ duration?: string;
+ gap?: string;
+ [key: string]: unknown;
+}
+
+export function Marquee({
+ className,
+ reverse = false,
+ pauseOnHover = false,
+ children,
+ vertical = false,
+ repeat = 4,
+ gap,
+
+ duration = "40s",
+ ...props
+}: MarqueeProps) {
+ return (
+
+ {Array.from({ length: repeat }).map((_, i) => (
+
+ {children}
+
+ ))}
+
+ );
+}
diff --git a/components/ui/mobile-navbar.tsx b/components/ui/mobile-navbar.tsx
new file mode 100644
index 0000000..d299e98
--- /dev/null
+++ b/components/ui/mobile-navbar.tsx
@@ -0,0 +1,180 @@
+"use client";
+
+import React, { useEffect, useRef, useState } from "react";
+import Image from "next/image";
+import Link from "next/link";
+import { motion, Variants } from "framer-motion";
+import { cn } from "@/lib/utils";
+import Button from "../shared/Button";
+
+const MobileNavbar = () => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [scrolled, setScrolled] = useState(false);
+ const mobileRef = useRef(null);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const currentScroll = window.scrollY;
+ setScrolled(currentScroll > 30);
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ const navLinks = [
+ { label: "Home", href: "#home" },
+ { label: "About Us", href: "#about" },
+ { label: "Features", href: "#features" },
+ { label: "Benefits", href: "#benefits" },
+ { label: "FAQ", href: "#faq" },
+ ];
+ const containerVariants: Variants = {
+ hidden: { y: -100, opacity: 0 },
+ visible: {
+ y: 0,
+ opacity: 1,
+ transition: { duration: 1.2, ease: [0.19, 1, 0.22, 1] }, // expo.out‑ish
+ },
+ };
+ // ✅ Correctly typed Framer Motion variants
+ const mobileNavVariants: Variants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ staggerChildren: 0.1,
+ duration: 0.4,
+ ease: "easeInOut", // ✅ typed correctly
+ },
+ },
+ };
+
+ const linkVariants: Variants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ duration: 0.3,
+ ease: "easeInOut", // ✅ allowed by Framer
+ },
+ },
+ };
+
+ // ✅ Safe smooth scroll handler
+ const handleClick = (
+ e: React.MouseEvent,
+ href: string
+ ) => {
+ e.preventDefault();
+ const targetId = href.replace("#", "");
+ const section = document.getElementById(targetId);
+ if (section) {
+ const yOffset = -80;
+ const y =
+ section.getBoundingClientRect().top + window.pageYOffset + yOffset;
+ window.scrollTo({ top: y, behavior: "smooth" });
+ setIsOpen(false);
+ }
+ };
+
+ return (
+
+ {/* Header Bar */}
+
+
+
+
+
+
+
+ {/* Mobile Menu Toggle */}
+
setIsOpen(!isOpen)}
+ >
+
+
+
+
+
+
+ {/* Dropdown Mobile Menu */}
+
+ {navLinks.map((link, i) => (
+
+ handleClick(e, link.href)}
+ className={cn(
+ "font-switzer text-h-4-34 group relative inline-block h-full cursor-pointer leading-9 font-medium transition-all duration-700 ease-[cubic-bezier(0.19,1,0.22,1)]"
+ )}
+ >
+ {/* Text hover animation */}
+
+
+ {link.label}
+
+
+ {link.label}
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default MobileNavbar;
diff --git a/components/ui/page-transition.tsx b/components/ui/page-transition.tsx
new file mode 100644
index 0000000..ee25c94
--- /dev/null
+++ b/components/ui/page-transition.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import { gsap } from "gsap";
+
+interface PageTransitionProps {
+ isOpen: boolean;
+ onComplete?: () => void;
+ direction?: "reveal" | "unreveal";
+}
+
+/* ------------------------------------------------------------------
+ 1. Path data – identical to the original demo
+ ------------------------------------------------------------------ */
+const paths = {
+ step1: {
+ unfilled: "M 0 100 V 100 Q 50 100 100 100 V 100 z",
+ inBetween: {
+ curve1: "M 0 100 V 50 Q 50 0 100 50 V 100 z",
+ curve2: "M 0 100 V 50 Q 50 100 100 50 V 100 z",
+ },
+ filled: "M 0 100 V 0 Q 50 0 100 0 V 100 z",
+ },
+ step2: {
+ filled: "M 0 0 V 100 Q 50 100 100 100 V 0 z",
+ inBetween: {
+ curve1: "M 0 0 V 50 Q 50 0 100 50 V 0 z",
+ curve2: "M 0 0 V 50 Q 50 100 100 50 V 0 z",
+ },
+ unfilled: "M 0 0 V 0 Q 50 0 100 0 V 0 z",
+ },
+};
+
+export const PageTransition = ({
+ isOpen,
+ onComplete,
+ direction = "reveal",
+}: PageTransitionProps) => {
+ const pathRef = useRef(null);
+ const svgRef = useRef(null); // 👈 new ref for hiding
+ const [animating, setAnimating] = useState(false);
+
+ /* --------------------------------------------------------------
+ REVEAL – open second view
+ -------------------------------------------------------------- */
+ const reveal = () => {
+ if (animating) return;
+ setAnimating(true);
+
+ // 👇 Show overlay before animation starts
+ gsap.set(svgRef.current, { display: "block" });
+
+ const tl = gsap.timeline({
+ onComplete: () => {
+ setAnimating(false);
+ onComplete?.();
+ // 👇 Hide overlay after animation completes
+ gsap.set(svgRef.current, { display: "none" });
+ },
+ });
+
+ tl.set(pathRef.current, { attr: { d: paths.step1.unfilled } })
+ .to(pathRef.current, {
+ duration: 0.8,
+ ease: "power4.in",
+ attr: { d: paths.step1.inBetween.curve1 },
+ })
+ .to(pathRef.current, {
+ duration: 0.2,
+ ease: "power1",
+ attr: { d: paths.step1.filled },
+ onComplete: () => onComplete?.(),
+ })
+ .set(pathRef.current, { attr: { d: paths.step2.filled } })
+ .to(pathRef.current, {
+ duration: 0.2,
+ ease: "sine.in",
+ attr: { d: paths.step2.inBetween.curve1 },
+ })
+ .to(pathRef.current, {
+ duration: 1,
+ ease: "power4",
+ attr: { d: paths.step2.unfilled },
+ });
+ };
+
+ /* --------------------------------------------------------------
+ UNREVEAL – back to first view
+ -------------------------------------------------------------- */
+ const unreveal = () => {
+ if (animating) return;
+ setAnimating(true);
+
+ // 👇 Show overlay before animation starts
+ gsap.set(svgRef.current, { display: "block" });
+
+ const tl = gsap.timeline({
+ onComplete: () => {
+ setAnimating(false);
+ onComplete?.();
+ // 👇 Hide overlay after animation completes
+ gsap.set(svgRef.current, { display: "none" });
+ },
+ });
+
+ tl.set(pathRef.current, { attr: { d: paths.step2.unfilled } })
+ .to(pathRef.current, {
+ duration: 0.8,
+ ease: "power4.in",
+ attr: { d: paths.step2.inBetween.curve2 },
+ })
+ .to(pathRef.current, {
+ duration: 0.2,
+ ease: "power1",
+ attr: { d: paths.step2.filled },
+ onComplete: () => onComplete?.(),
+ })
+ .set(pathRef.current, { attr: { d: paths.step1.filled } })
+ .to(pathRef.current, {
+ duration: 0.2,
+ ease: "sine.in",
+ attr: { d: paths.step1.inBetween.curve2 },
+ })
+ .to(pathRef.current, {
+ duration: 1,
+ ease: "power4",
+ attr: { d: paths.step1.unfilled },
+ });
+ };
+
+ /* --------------------------------------------------------------
+ React to prop changes
+ -------------------------------------------------------------- */
+ useEffect(() => {
+ if (!isOpen) return;
+ if (direction === "reveal") reveal();
+ else if (direction === "unreveal") unreveal();
+ }, [isOpen, direction]);
+
+ return (
+
+
+
+ );
+};
diff --git a/components/ui/smooth-scroll-link.tsx b/components/ui/smooth-scroll-link.tsx
new file mode 100644
index 0000000..eff359b
--- /dev/null
+++ b/components/ui/smooth-scroll-link.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import React from "react";
+import { cn } from "@/lib/utils";
+
+interface SmoothScrollLinkProps {
+ href: string;
+ children: React.ReactNode;
+ scrolled?: boolean;
+}
+
+const SmoothScrollLink = ({
+ href,
+ children,
+ scrolled = false,
+}: SmoothScrollLinkProps) => {
+ const handleClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ const targetId = href.replace("#", "");
+ const section = document.getElementById(targetId);
+ if (section) {
+ const yOffset = -80;
+ const y =
+ section.getBoundingClientRect().top + window.pageYOffset + yOffset;
+ window.scrollTo({ top: y, behavior: "smooth" });
+ }
+ };
+
+ return (
+
+ {/* Smooth hover text animation */}
+
+
+ {children}
+
+
+ {children}
+
+
+
+ );
+};
+
+export default SmoothScrollLink;
diff --git a/components/ui/testimonial-marquee.tsx b/components/ui/testimonial-marquee.tsx
new file mode 100644
index 0000000..6eca837
--- /dev/null
+++ b/components/ui/testimonial-marquee.tsx
@@ -0,0 +1,45 @@
+import Image from "next/image";
+
+export const TestimonialCard = ({
+ id,
+ name,
+ title,
+ body,
+ img,
+}: {
+ id: number;
+ name: string;
+ title: string;
+ img: string;
+ body: string;
+}) => {
+ return (
+
+
+
+
+
+ {name}
+
+
+ {title}
+
+
+
+
+
+ );
+};
diff --git a/data/useCases.ts b/data/useCases.ts
new file mode 100644
index 0000000..89e5f40
--- /dev/null
+++ b/data/useCases.ts
@@ -0,0 +1,79 @@
+export type UseCase = {
+ slug: string;
+ heroImage: string;
+ image: string;
+ image1: string;
+ image2: string;
+ title: string;
+ description: string;
+ challenge: string;
+ solution: string;
+ outcome: string;
+};
+
+export const useCases: UseCase[] = [
+ {
+ slug: "cities-communities",
+ heroImage: "/images/png/challenge-2.png",
+ image: "/images/png/citiy-1.png",
+ title: "Cities & Communities",
+ image1: "/images/png/challenge-1.png",
+ image2: "/images/png/impact.png",
+ description:
+ "Smart irrigation systems empower city managers to optimize water use across urban landscapes, balancing green sustainability with infrastructure efficiency.",
+ challenge:
+ "City park departments often overwater trees due to fixed irrigation schedules, wasting 40–50% of water annually. Drought conditions and budget constraints make this approach unsustainable.",
+ solution:
+ "Whispering Tree sensors monitor soil moisture and weather data in real-time, allowing AI-driven, demand-based irrigation that ensures trees receive just the right amount of water.",
+ outcome:
+ "Cities achieve up to 45% water savings, reduce maintenance costs, and meet sustainability goals—while keeping parks greener, healthier, and more resilient.",
+ },
+ {
+ slug: "agriculture",
+ heroImage: "/images/png/hero-3.png",
+ image: "/images/png/thimbnail-3.png",
+ image1: "/images/png/challenge-3.png",
+ image2: "/images/png/impact-3.png",
+ title: "Agriculture",
+ description:
+ "Farmers and agronomists use data-driven irrigation control to improve crop yields, save resources, and ensure resilience against climate variability.",
+ challenge:
+ "Traditional irrigation methods often lead to water stress or over-irrigation, reducing crop quality and yield—especially in areas facing unpredictable rainfall.",
+ solution:
+ "IoT sensors continuously monitor soil and climate conditions, providing precise irrigation timing and volume recommendations. This reduces water usage while improving root-zone health.",
+ outcome:
+ "Farmers see up to 20% higher yields, 30% water savings, and healthier soils—creating a more sustainable agricultural model.",
+ },
+ {
+ slug: "research",
+ heroImage: "/images/png/hero-2.png",
+ image: "/images/png/thumbnail-2.png",
+ image1: "/images/png/impact-5.png",
+ image2: "/images/png/impact-6.png",
+ title: "Research",
+ description:
+ "Researchers and environmental scientists use continuous, high-frequency data to gain deep insights into tree physiology and ecosystem patterns.",
+ challenge:
+ "Field research often lacks consistent, real-time data on soil moisture and tree health—making long-term ecological studies difficult and data accuracy inconsistent.",
+ solution:
+ "Whispering Tree’s sensor network collects continuous measurements, allowing automated data logging and cross-site comparison of environmental variables.",
+ outcome:
+ "Research teams save time on manual sampling and gain access to reliable datasets for modeling tree-water relationships and predicting ecosystem responses.",
+ },
+ {
+ slug: "forestry",
+ heroImage: "/images/png/h-f.png",
+ image: "/images/png/4-f.png",
+ image1: "/images/png/f-2.png",
+ image2: "/images/png/f-1.png",
+ title: "Forestry",
+ description:
+ "Forest managers leverage IoT-based environmental intelligence to monitor forest health and protect biodiversity through early stress detection.",
+ challenge:
+ "Large forest areas are difficult to monitor manually, leading to undetected signs of drought stress, pest outbreaks, and reduced growth.",
+ solution:
+ "Networked sensors collect microclimate and soil data across different forest zones, helping managers track water stress, tree vitality, and regeneration progress in real-time.",
+ outcome:
+ "Forestry departments reduce monitoring time by 50%, prevent forest loss through early alerts, and improve reforestation success rates using precise environmental data.",
+ },
+];
diff --git a/layout/Layout.tsx b/layout/Layout.tsx
new file mode 100644
index 0000000..372d76c
--- /dev/null
+++ b/layout/Layout.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { createContext, useContext, useState, useEffect, useRef } from "react";
+import { usePathname, useRouter } from "next/navigation";
+import { PageTransition } from "@/components/ui/page-transition";
+
+type TransitionContextType = {
+ startTransition: (url: string) => void;
+};
+const TransitionContext = createContext({
+ startTransition: () => {},
+});
+export const usePageTransition = () => useContext(TransitionContext);
+
+export const LayoutWithTransition = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [pendingUrl, setPendingUrl] = useState(null);
+ const [direction, setDirection] = useState<"reveal" | "unreveal">("unreveal");
+ const [pageReady, setPageReady] = useState(false);
+
+ const router = useRouter();
+ const pathname = usePathname();
+ const prevPath = useRef(null);
+
+ /* --------------------------------------------------------------
+ 🔹 User clicks → exit animation (reveal)
+ -------------------------------------------------------------- */
+ const startTransition = (url: string) => {
+ setDirection("reveal"); // going out
+ setPendingUrl(url);
+ setIsOpen(true); // start reveal animation
+ };
+
+ /* --------------------------------------------------------------
+ 🔹 When GSAP reveal animation finishes → navigate
+ -------------------------------------------------------------- */
+ const handleAnimationComplete = () => {
+ if (pendingUrl) {
+ router.push(pendingUrl);
+ setPendingUrl(null);
+ setPageReady(false); // reset entry state
+ }
+ };
+
+ /* --------------------------------------------------------------
+ 🔹 When route changes (new page load)
+ play "unreveal" after slight delay
+ -------------------------------------------------------------- */
+ useEffect(() => {
+ if (prevPath.current && prevPath.current !== pathname) {
+ // only play unreveal on navigation, not on first load
+ const timer = setTimeout(() => {
+ setDirection("unreveal");
+ setIsOpen(true);
+ setTimeout(() => setIsOpen(false), 1200); // animation duration
+ }, 250); // give React time to render page
+ return () => clearTimeout(timer);
+ }
+ prevPath.current = pathname;
+ }, [pathname]);
+
+ return (
+
+
+ {/* Page content */}
+ {children}
+
+ {/* Animated overlay */}
+
+
+
+ );
+};
diff --git a/lib/useLenisScroll.js b/lib/useLenisScroll.js
new file mode 100644
index 0000000..f2f40ea
--- /dev/null
+++ b/lib/useLenisScroll.js
@@ -0,0 +1,25 @@
+"use client";
+
+import { useEffect } from "react";
+import Lenis from "@studio-freight/lenis";
+
+export default function SmoothScrollProvider({ children }) {
+ useEffect(() => {
+ const lenis = new Lenis({
+ smooth: true,
+ duration: 1.2, // Adjust the scroll speed
+ });
+
+ function raf(time) {
+ lenis.raf(time);
+ requestAnimationFrame(raf);
+ }
+ requestAnimationFrame(raf);
+
+ return () => {
+ lenis.destroy();
+ };
+ }, []);
+
+ return <>{children}>;
+}
diff --git a/lib/useSectionReveal.ts b/lib/useSectionReveal.ts
new file mode 100644
index 0000000..1849f73
--- /dev/null
+++ b/lib/useSectionReveal.ts
@@ -0,0 +1,82 @@
+import { useEffect } from "react";
+import gsap from "gsap";
+import SplitText from "gsap/SplitText";
+import ScrollTrigger from "gsap/ScrollTrigger";
+
+gsap.registerPlugin(SplitText, ScrollTrigger);
+
+type UseSplitTextAnimationProps = {
+ headingRef: React.RefObject;
+ paraRef: React.RefObject;
+ shinyRef?: React.RefObject;
+ triggerId: string;
+};
+
+export function useSplitTextAnimation({
+ headingRef,
+ paraRef,
+ shinyRef,
+ triggerId,
+}: UseSplitTextAnimationProps) {
+ useEffect(() => {
+ const ctx = gsap.context(() => {
+ const tl = gsap.timeline({
+ scrollTrigger: {
+ trigger: `#${triggerId}`,
+ start: "top 70%",
+ },
+ });
+
+ const headingSplit = new SplitText(headingRef.current, {
+ type: "lines, words",
+ linesClass: "line",
+ });
+
+ const paraSplit = new SplitText(paraRef.current, {
+ type: "lines, words",
+ linesClass: "line",
+ });
+
+ const headingWords = headingSplit.words;
+ const paraWords = paraSplit.words;
+
+ if (shinyRef?.current) {
+ tl.from(shinyRef.current, {
+ opacity: 0,
+ y: 20,
+ duration: 0.6,
+ ease: "power",
+ });
+ }
+
+ tl.from(
+ headingWords,
+ {
+ duration: 0.8,
+ opacity: 0,
+ yPercent: 120,
+ ease: "power",
+ stagger: 0.015,
+ },
+ "-=0.2"
+ );
+
+ tl.from(
+ paraWords,
+ {
+ duration: 0.7,
+ opacity: 0,
+ yPercent: 120,
+ ease: "power",
+ stagger: 0.01,
+ },
+ "-=0.5"
+ );
+ });
+
+ return () => {
+ ctx.revert();
+ ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
+ };
+ }, [headingRef, paraRef, shinyRef, triggerId]);
+}
diff --git a/lib/useTextRevealHeading.ts b/lib/useTextRevealHeading.ts
new file mode 100644
index 0000000..94b66e3
--- /dev/null
+++ b/lib/useTextRevealHeading.ts
@@ -0,0 +1,57 @@
+"use client";
+import { useEffect } from "react";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+import { SplitText } from "gsap/SplitText";
+
+gsap.registerPlugin(ScrollTrigger, SplitText);
+
+export const useSplitHeading = (
+ headingRef: React.RefObject,
+ subHeadingRef?: React.RefObject
+) => {
+ useEffect(() => {
+ if (!headingRef.current) return;
+
+ const headingSplit = new SplitText(headingRef.current, { type: "words" });
+ const subSplit =
+ subHeadingRef?.current &&
+ new SplitText(subHeadingRef.current, { type: "words" });
+
+ // heading animation
+ gsap.from(headingSplit.words, {
+ yPercent: 100,
+ opacity: 0,
+ duration: 1,
+ ease: "expo.out",
+ stagger: 0.07,
+ scrollTrigger: {
+ trigger: headingRef.current,
+ start: "top 85%",
+ toggleActions: "play none none none",
+ },
+ });
+
+ // subheading animation
+ if (subSplit) {
+ gsap.from(subSplit.words, {
+ yPercent: 100,
+ opacity: 0,
+ duration: 1,
+ ease: "expo.out",
+ stagger: 0.04,
+ scrollTrigger: {
+ trigger: subHeadingRef?.current,
+ start: "top 85%",
+ toggleActions: "play none none none",
+ },
+ });
+ }
+
+ return () => {
+ headingSplit.revert();
+ subSplit?.revert();
+ ScrollTrigger.getAll().forEach((st) => st.kill());
+ };
+ }, [headingRef, subHeadingRef]);
+};
diff --git a/lib/utils.ts b/lib/utils.ts
new file mode 100644
index 0000000..a5ef193
--- /dev/null
+++ b/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/package-lock.json b/package-lock.json
index 2c449fd..bae79d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,15 @@
"name": "whispering-tree",
"version": "0.1.0",
"dependencies": {
+ "@studio-freight/lenis": "^1.0.42",
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.23.24",
+ "gsap": "^3.13.0",
+ "lucide-react": "^0.552.0",
"next": "16.0.1",
"react": "19.2.0",
- "react-dom": "19.2.0"
+ "react-dom": "19.2.0",
+ "tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -19,6 +25,8 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.0.1",
+ "prettier": "^3.6.2",
+ "prettier-plugin-tailwindcss": "^0.7.1",
"tailwindcss": "^4",
"typescript": "^5"
}
@@ -1195,6 +1203,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@studio-freight/lenis": {
+ "version": "1.0.42",
+ "resolved": "https://registry.npmjs.org/@studio-freight/lenis/-/lenis-1.0.42.tgz",
+ "integrity": "sha512-HJAGf2DeM+BTvKzHv752z6Z7zy6bA643nZM7W88Ft9tnw2GsJSp6iJ+3cekjyMIWH+cloL2U9X82dKXgdU8kPg==",
+ "deprecated": "The '@studio-freight/lenis' package has been renamed to 'lenis'. Please update your dependencies: npm install lenis and visit the documentation: https://www.npmjs.com/package/lenis",
+ "license": "MIT"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2571,6 +2586,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3580,6 +3604,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.23.24",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
+ "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.23.23",
+ "motion-utils": "^12.23.6",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3781,6 +3832,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/gsap": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz",
+ "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -4835,6 +4892,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.552.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz",
+ "integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4902,6 +4968,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.23.23",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
+ "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.23.6"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.23.6",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
+ "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5336,6 +5417,101 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.1.tgz",
+ "integrity": "sha512-Bzv1LZcuiR1Sk02iJTS1QzlFNp/o5l2p3xkopwOrbPmtMeh3fK9rVW5M3neBQzHq+kGKj/4LGQMTNcTH4NGPtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-hermes": "*",
+ "@prettier/plugin-oxc": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-hermes": {
+ "optional": true
+ },
+ "@prettier/plugin-oxc": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -6017,6 +6193,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
+ "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
diff --git a/package.json b/package.json
index 7163667..a0fdea0 100644
--- a/package.json
+++ b/package.json
@@ -9,18 +9,26 @@
"lint": "eslint"
},
"dependencies": {
+ "@studio-freight/lenis": "^1.0.42",
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.23.24",
+ "gsap": "^3.13.0",
+ "lucide-react": "^0.552.0",
+ "next": "16.0.1",
"react": "19.2.0",
"react-dom": "19.2.0",
- "next": "16.0.1"
+ "tailwind-merge": "^3.3.1"
},
"devDependencies": {
- "typescript": "^5",
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4",
"eslint": "^9",
- "eslint-config-next": "16.0.1"
+ "eslint-config-next": "16.0.1",
+ "prettier": "^3.6.2",
+ "prettier-plugin-tailwindcss": "^0.7.1",
+ "tailwindcss": "^4",
+ "typescript": "^5"
}
}
diff --git a/public/fav.svg b/public/fav.svg
new file mode 100644
index 0000000..e5dd3e3
--- /dev/null
+++ b/public/fav.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/file.svg b/public/file.svg
deleted file mode 100644
index 004145c..0000000
--- a/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/font/PlayfairDisplay-Italic-VariableFont_wght.ttf b/public/font/PlayfairDisplay-Italic-VariableFont_wght.ttf
new file mode 100644
index 0000000..ca22220
Binary files /dev/null and b/public/font/PlayfairDisplay-Italic-VariableFont_wght.ttf differ
diff --git a/public/font/Switzer-Variable.ttf b/public/font/Switzer-Variable.ttf
new file mode 100644
index 0000000..d6e5cc4
Binary files /dev/null and b/public/font/Switzer-Variable.ttf differ
diff --git a/public/font/miumAccess-Regular.ttf b/public/font/miumAccess-Regular.ttf
new file mode 100644
index 0000000..efd9a92
Binary files /dev/null and b/public/font/miumAccess-Regular.ttf differ
diff --git a/public/images/png/1-1.jpeg b/public/images/png/1-1.jpeg
new file mode 100644
index 0000000..d46cf66
Binary files /dev/null and b/public/images/png/1-1.jpeg differ
diff --git a/public/images/png/1-2.jpeg b/public/images/png/1-2.jpeg
new file mode 100644
index 0000000..031fad2
Binary files /dev/null and b/public/images/png/1-2.jpeg differ
diff --git a/public/images/png/2-1.jpeg b/public/images/png/2-1.jpeg
new file mode 100644
index 0000000..da99a65
Binary files /dev/null and b/public/images/png/2-1.jpeg differ
diff --git a/public/images/png/2-2.jpeg b/public/images/png/2-2.jpeg
new file mode 100644
index 0000000..873ae72
Binary files /dev/null and b/public/images/png/2-2.jpeg differ
diff --git a/public/images/png/4-f.png b/public/images/png/4-f.png
new file mode 100644
index 0000000..f8eb1a9
Binary files /dev/null and b/public/images/png/4-f.png differ
diff --git a/public/images/png/Aerial View of Urban Crossroad and Park 1.png b/public/images/png/Aerial View of Urban Crossroad and Park 1.png
new file mode 100644
index 0000000..6c70778
Binary files /dev/null and b/public/images/png/Aerial View of Urban Crossroad and Park 1.png differ
diff --git a/public/images/png/Avatar (1).png b/public/images/png/Avatar (1).png
new file mode 100644
index 0000000..b25da59
Binary files /dev/null and b/public/images/png/Avatar (1).png differ
diff --git a/public/images/png/Avatar (2).png b/public/images/png/Avatar (2).png
new file mode 100644
index 0000000..8994ba6
Binary files /dev/null and b/public/images/png/Avatar (2).png differ
diff --git a/public/images/png/Avatar (3).png b/public/images/png/Avatar (3).png
new file mode 100644
index 0000000..25c18b2
Binary files /dev/null and b/public/images/png/Avatar (3).png differ
diff --git a/public/images/png/Avatar (4).png b/public/images/png/Avatar (4).png
new file mode 100644
index 0000000..85d70cc
Binary files /dev/null and b/public/images/png/Avatar (4).png differ
diff --git a/public/images/png/Avatar.png b/public/images/png/Avatar.png
new file mode 100644
index 0000000..47372e3
Binary files /dev/null and b/public/images/png/Avatar.png differ
diff --git a/public/images/png/Blob 5.png b/public/images/png/Blob 5.png
new file mode 100644
index 0000000..0372804
Binary files /dev/null and b/public/images/png/Blob 5.png differ
diff --git a/public/images/png/Enchanted Verdant Canopy Road 2.png b/public/images/png/Enchanted Verdant Canopy Road 2.png
new file mode 100644
index 0000000..c9df355
Binary files /dev/null and b/public/images/png/Enchanted Verdant Canopy Road 2.png differ
diff --git a/public/images/png/Urban Park Pathway 1.png b/public/images/png/Urban Park Pathway 1.png
new file mode 100644
index 0000000..2336951
Binary files /dev/null and b/public/images/png/Urban Park Pathway 1.png differ
diff --git a/public/images/png/bg-1.png b/public/images/png/bg-1.png
new file mode 100644
index 0000000..c6398ab
Binary files /dev/null and b/public/images/png/bg-1.png differ
diff --git a/public/images/png/challenge-1.png b/public/images/png/challenge-1.png
new file mode 100644
index 0000000..88a483d
Binary files /dev/null and b/public/images/png/challenge-1.png differ
diff --git a/public/images/png/challenge-2.png b/public/images/png/challenge-2.png
new file mode 100644
index 0000000..3ae4d78
Binary files /dev/null and b/public/images/png/challenge-2.png differ
diff --git a/public/images/png/challenge-3.png b/public/images/png/challenge-3.png
new file mode 100644
index 0000000..a4b1c68
Binary files /dev/null and b/public/images/png/challenge-3.png differ
diff --git a/public/images/png/citiy-1.png b/public/images/png/citiy-1.png
new file mode 100644
index 0000000..5722748
Binary files /dev/null and b/public/images/png/citiy-1.png differ
diff --git a/public/images/png/f-1.png b/public/images/png/f-1.png
new file mode 100644
index 0000000..3da0a35
Binary files /dev/null and b/public/images/png/f-1.png differ
diff --git a/public/images/png/f-2.png b/public/images/png/f-2.png
new file mode 100644
index 0000000..7c0861c
Binary files /dev/null and b/public/images/png/f-2.png differ
diff --git a/public/images/png/forest 1.png b/public/images/png/forest 1.png
new file mode 100644
index 0000000..bab928a
Binary files /dev/null and b/public/images/png/forest 1.png differ
diff --git a/public/images/png/grainy.png b/public/images/png/grainy.png
new file mode 100644
index 0000000..3800663
Binary files /dev/null and b/public/images/png/grainy.png differ
diff --git a/public/images/png/h-f.png b/public/images/png/h-f.png
new file mode 100644
index 0000000..aba7e0b
Binary files /dev/null and b/public/images/png/h-f.png differ
diff --git a/public/images/png/hero-2.png b/public/images/png/hero-2.png
new file mode 100644
index 0000000..48c7d4c
Binary files /dev/null and b/public/images/png/hero-2.png differ
diff --git a/public/images/png/hero-3.png b/public/images/png/hero-3.png
new file mode 100644
index 0000000..c9b0ded
Binary files /dev/null and b/public/images/png/hero-3.png differ
diff --git a/public/images/png/hero-bg.png b/public/images/png/hero-bg.png
new file mode 100644
index 0000000..aec7bec
Binary files /dev/null and b/public/images/png/hero-bg.png differ
diff --git a/public/images/png/impact-2.png b/public/images/png/impact-2.png
new file mode 100644
index 0000000..3ea5952
Binary files /dev/null and b/public/images/png/impact-2.png differ
diff --git a/public/images/png/impact-3.png b/public/images/png/impact-3.png
new file mode 100644
index 0000000..c99b950
Binary files /dev/null and b/public/images/png/impact-3.png differ
diff --git a/public/images/png/impact-5.png b/public/images/png/impact-5.png
new file mode 100644
index 0000000..80bcffc
Binary files /dev/null and b/public/images/png/impact-5.png differ
diff --git a/public/images/png/impact-6.png b/public/images/png/impact-6.png
new file mode 100644
index 0000000..0828ad0
Binary files /dev/null and b/public/images/png/impact-6.png differ
diff --git a/public/images/png/impact.png b/public/images/png/impact.png
new file mode 100644
index 0000000..7c22b88
Binary files /dev/null and b/public/images/png/impact.png differ
diff --git a/public/images/png/product.png b/public/images/png/product.png
new file mode 100644
index 0000000..a8ce465
Binary files /dev/null and b/public/images/png/product.png differ
diff --git a/public/images/png/rural-landscape-with-green-trees 1.png b/public/images/png/rural-landscape-with-green-trees 1.png
new file mode 100644
index 0000000..0e139a6
Binary files /dev/null and b/public/images/png/rural-landscape-with-green-trees 1.png differ
diff --git a/public/images/png/side-view-forest-wardens-wearing-vests 1.png b/public/images/png/side-view-forest-wardens-wearing-vests 1.png
new file mode 100644
index 0000000..e0711c9
Binary files /dev/null and b/public/images/png/side-view-forest-wardens-wearing-vests 1.png differ
diff --git a/public/images/png/thimbnail-3.png b/public/images/png/thimbnail-3.png
new file mode 100644
index 0000000..d5f8ce7
Binary files /dev/null and b/public/images/png/thimbnail-3.png differ
diff --git a/public/images/png/thumbnail-2.png b/public/images/png/thumbnail-2.png
new file mode 100644
index 0000000..e6d06f9
Binary files /dev/null and b/public/images/png/thumbnail-2.png differ
diff --git a/public/images/png/tree-view (1).jpeg b/public/images/png/tree-view (1).jpeg
new file mode 100644
index 0000000..4a74208
Binary files /dev/null and b/public/images/png/tree-view (1).jpeg differ
diff --git a/public/images/png/tree-view (2).jpeg b/public/images/png/tree-view (2).jpeg
new file mode 100644
index 0000000..77572ea
Binary files /dev/null and b/public/images/png/tree-view (2).jpeg differ
diff --git a/public/images/png/tree-view (3).jpeg b/public/images/png/tree-view (3).jpeg
new file mode 100644
index 0000000..790a600
Binary files /dev/null and b/public/images/png/tree-view (3).jpeg differ
diff --git a/public/images/svg/arrow.svg b/public/images/svg/arrow.svg
new file mode 100644
index 0000000..bd4d63c
--- /dev/null
+++ b/public/images/svg/arrow.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/images/svg/dark-logo.svg b/public/images/svg/dark-logo.svg
new file mode 100644
index 0000000..636b3af
--- /dev/null
+++ b/public/images/svg/dark-logo.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/svg/entypo_tree.svg b/public/images/svg/entypo_tree.svg
new file mode 100644
index 0000000..0c4f07c
--- /dev/null
+++ b/public/images/svg/entypo_tree.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/public/globe.svg b/public/images/svg/globe.svg
similarity index 100%
rename from public/globe.svg
rename to public/images/svg/globe.svg
diff --git a/public/images/svg/googleplus logo.svg b/public/images/svg/googleplus logo.svg
new file mode 100644
index 0000000..5fcddf7
--- /dev/null
+++ b/public/images/svg/googleplus logo.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/svg/iconoir_reports.svg b/public/images/svg/iconoir_reports.svg
new file mode 100644
index 0000000..264b264
--- /dev/null
+++ b/public/images/svg/iconoir_reports.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/images/svg/leaf.svg b/public/images/svg/leaf.svg
new file mode 100644
index 0000000..06539c7
--- /dev/null
+++ b/public/images/svg/leaf.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/public/images/svg/linkedin-plain-wordmark logo.svg b/public/images/svg/linkedin-plain-wordmark logo.svg
new file mode 100644
index 0000000..47cb83d
--- /dev/null
+++ b/public/images/svg/linkedin-plain-wordmark logo.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/svg/lucide_monitor-cog.svg b/public/images/svg/lucide_monitor-cog.svg
new file mode 100644
index 0000000..d0f2700
--- /dev/null
+++ b/public/images/svg/lucide_monitor-cog.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/images/svg/mage_email.svg b/public/images/svg/mage_email.svg
new file mode 100644
index 0000000..5fe4201
--- /dev/null
+++ b/public/images/svg/mage_email.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/images/svg/mdi_water-check-outline (1).svg b/public/images/svg/mdi_water-check-outline (1).svg
new file mode 100644
index 0000000..6e4e75f
--- /dev/null
+++ b/public/images/svg/mdi_water-check-outline (1).svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/images/svg/mdi_water-check-outline.svg b/public/images/svg/mdi_water-check-outline.svg
new file mode 100644
index 0000000..6e4e75f
--- /dev/null
+++ b/public/images/svg/mdi_water-check-outline.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/images/svg/metallb-horizontal-white logo.svg b/public/images/svg/metallb-horizontal-white logo.svg
new file mode 100644
index 0000000..96dbf8d
--- /dev/null
+++ b/public/images/svg/metallb-horizontal-white logo.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/public/images/svg/microsoft logo.svg b/public/images/svg/microsoft logo.svg
new file mode 100644
index 0000000..30b8c00
--- /dev/null
+++ b/public/images/svg/microsoft logo.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/public/images/svg/proicons_call.svg b/public/images/svg/proicons_call.svg
new file mode 100644
index 0000000..3d5eabe
--- /dev/null
+++ b/public/images/svg/proicons_call.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/images/svg/whispering-logo.svg b/public/images/svg/whispering-logo.svg
new file mode 100644
index 0000000..62153cf
--- /dev/null
+++ b/public/images/svg/whispering-logo.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..46a2203
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Disallow:
+
+Sitemap: http://trees.falktron.com/sitemap.xml
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index 7705396..0000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/video/tree-sensor.mp4 b/public/video/tree-sensor.mp4
new file mode 100644
index 0000000..7cfcbbd
Binary files /dev/null and b/public/video/tree-sensor.mp4 differ
diff --git a/public/video/video-1.mp4 b/public/video/video-1.mp4
new file mode 100644
index 0000000..28165fb
Binary files /dev/null and b/public/video/video-1.mp4 differ
diff --git a/public/video/video-2.mp4 b/public/video/video-2.mp4
new file mode 100644
index 0000000..a4643c2
Binary files /dev/null and b/public/video/video-2.mp4 differ
diff --git a/public/window.svg b/public/window.svg
deleted file mode 100644
index b2b2a44..0000000
--- a/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/sections/About.tsx b/sections/About.tsx
new file mode 100644
index 0000000..a312091
--- /dev/null
+++ b/sections/About.tsx
@@ -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(null);
+ const subHeadingRef = useRef(null);
+ const subHeading2Ref = useRef(null);
+ const mainHeadingRef = useRef(null);
+
+ const subSplit1 = useRef(null);
+ const subSplit2 = useRef(null);
+ const mainSplit = useRef(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 (
+
+ {/* Left */}
+
+
+ About Us
+
+
+
+ {/* Right */}
+
+ {/* MAIN HEADING */}
+
+ At{" "}
+
+ FalktronTrees
+ {" "}
+ merges IoT, AI, and environmental intelligence to connect nature and
+ technology, creating smart solutions that monitor and improve the
+ health of trees and ecosystems.
+
+
+ {/* Sub-heading 1 */}
+
+ Based in Oelde, Germany, FalktronTrees brings together innovation,
+ data science, and environmental care to empower cities, organizations,
+ and individuals to manage natural resources intelligently.
+
+
+ {/* Sub-heading 2 */}
+
+ 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.
+
+
+
+ );
+};
+
+export default About;
diff --git a/sections/Benefit.tsx b/sections/Benefit.tsx
new file mode 100644
index 0000000..332863a
--- /dev/null
+++ b/sections/Benefit.tsx
@@ -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(null);
+ const paraRef = useRef(null);
+ const containerRef = useRef(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(".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 (
+
+
+
+
+
+ Smarter Trees. Greener{" "}
+
+ Cities.
+
+ Sustainable{" "}
+
+ Future.{" "}
+
+
+
+ Experience real-time insights, efficient water use, and healthier
+ urban forests — start your pilot or schedule a demo today.
+
+
+
+ {benefits.map((benefit, index) => (
+
+
+
+
+
+
+
+
+ {benefit.title}
+
+
+ {benefit.description}
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default Benefit;
diff --git a/sections/CTA.tsx b/sections/CTA.tsx
new file mode 100644
index 0000000..a137a12
--- /dev/null
+++ b/sections/CTA.tsx
@@ -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(null);
+ const containerRef = useRef(null);
+ const paraRef = useRef(null);
+ const buttonRef = useRef(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 (
+
+
+
+
+ Join the Future of Smart Tree Care
+
+
+
+ Experience real-time insights, efficient water use, and healthier
+ urban forests — start your pilot or schedule a demo today.
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CTA;
diff --git a/sections/CaseStudy.tsx b/sections/CaseStudy.tsx
new file mode 100644
index 0000000..0dccf2c
--- /dev/null
+++ b/sections/CaseStudy.tsx
@@ -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(null);
+ const paraRef = useRef(null);
+ const containerRef = useRef(null);
+ const { startTransition } = usePageTransition();
+ useSplitHeading(headingRef, paraRef);
+
+ useEffect(() => {
+ gsap.registerPlugin(ScrollTrigger);
+
+ const cards = gsap.utils.toArray(".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 (
+
+
+
+
+
+ One Technology, Many{" "}
+
+ Applications
+
+
+
+ Whether for municipalities, research, or contractors — FalktronTrees
+ brings actionable insights to every level of urban forestry.
+
+
+
+ {useCases.map((useCase, index) => (
+
+ {/* Image */}
+
+
+
+
+ {/* Text Content */}
+
+
+ {useCase.title}
+
+
+ {useCase.description}
+
+
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
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default CaseStudy;
diff --git a/sections/FAQ.tsx b/sections/FAQ.tsx
new file mode 100644
index 0000000..ad1175c
--- /dev/null
+++ b/sections/FAQ.tsx
@@ -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(null);
+ const paraRef = useRef(null);
+ const containerRef = useRef(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 (
+
+
+
+
+ Whispering Trees{" "}
+
+ Explained
+
+
+
+ Learn how Whispering Trees helps cities grow greener and smarter.
+
+
+
+
+
+
+ );
+};
+
+export default FAQSection;
diff --git a/sections/Hero.tsx b/sections/Hero.tsx
new file mode 100644
index 0000000..09f250d
--- /dev/null
+++ b/sections/Hero.tsx
@@ -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(null);
+ const headingRef = useRef(null);
+ const subHeadingRef = useRef(null);
+ const buttonWrapperRef = useRef(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 (
+
+ {/* ── Desktop Navbar (wrapped) ───────────────────────────────────── */}
+
+
+ {/* ── Mobile Navbar (wrapped) ────────────────────────────────────── */}
+
+
+ {/* ── Hero Content ───────────────────────────────────────────────── */}
+
+
+
+ Your browser does not support the video tag.
+
+
+
+ {/* Heading */}
+
+ Trees can talk. We help you{" "}
+ listen.
+
+
+ {/* Sub-heading */}
+
+ 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.
+
+
+ {/* Buttons */}
+
+
+
+
+
+
+ {/* Blob overlay */}
+
+
+
+ );
+};
+
+export default Hero;
diff --git a/sections/HeroCase.tsx b/sections/HeroCase.tsx
new file mode 100644
index 0000000..3cc6f5f
--- /dev/null
+++ b/sections/HeroCase.tsx
@@ -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(null);
+ const headingRef = useRef(null);
+ const subHeadingRef = useRef(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 (
+
+
+
+
+
+
+ {item.title}
+
+
+ {item.description}
+
+
+
+
+
+
+ );
+}
diff --git a/sections/HowWork.tsx b/sections/HowWork.tsx
new file mode 100644
index 0000000..89c7de5
--- /dev/null
+++ b/sections/HowWork.tsx
@@ -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(null);
+ const paraRef = useRef(null);
+ const containerRef = useRef(null);
+
+ // Refs for animation targets
+ const imageRef = useRef(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 (
+
+
+ {/* Heading */}
+
+
+
+ The Whispering Trees{" "}
+ Process.
+
+
+ Monitor tree health in real-time and optimize irrigation schedules
+ with our intelligent sensor system, ensuring urban forests thrive
+ while conserving water.
+
+
+
+ {/* Steps + Image */}
+
+ {/* Left Steps */}
+
+ {steps
+ .filter((s) => s.position === "left")
+ .map((step, i) => (
+
{
+ 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]"
+ >
+
+ {step.id}
+
+
+ {step.text}
+
+
+ ))}
+
+
+ {/* Center Image */}
+
+
+
+
+ {/* Right Steps */}
+
+ {steps
+ .filter((s) => s.position === "right")
+ .map((step, i) => (
+
{
+ 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]"
+ >
+
+ {step.id}
+
+
+ {step.text}
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default HowWork;
diff --git a/sections/ProductPreview.tsx b/sections/ProductPreview.tsx
new file mode 100644
index 0000000..8ef061b
--- /dev/null
+++ b/sections/ProductPreview.tsx
@@ -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(null);
+ const headingRef2 = useRef(null);
+ const paraRef = useRef(null);
+ const paraRef2 = useRef(null);
+ const containerRef = useRef(null);
+ const [activeIndex, setActiveIndex] = useState(0);
+ const imageRef = useRef(null);
+ const intervalRef = useRef(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 (
+
+
+
+
+
+ Manage Every Tree with{" "}
+
+ Clarity.
+
+ and{" "}
+
+ Confidence.{" "}
+
+
+
+ A powerful all-in-one dashboard to monitor tree health, sensors, and
+ data insights — built for precision forestry and sustainability.
+
+
+
+
+
+ Export for{" "}
+
+ Reports
+
+
+
+ Generate ready-to-share reports with insights for management,
+ research, or community updates.
+
+
+
+
+
+ {features.map((item, idx) => (
+ {
+ 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"
+ }`}
+ >
+
+
+
+
+ {item.feature}
+
+
+ ))}
+
+
+
+
+ {/* RIGHT SIDE: Changing Image */}
+
+
+
+
+
+
+
+ );
+};
+
+export default ProductPreview;
diff --git a/sections/SocialProff.tsx b/sections/SocialProff.tsx
new file mode 100644
index 0000000..e641588
--- /dev/null
+++ b/sections/SocialProff.tsx
@@ -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 (
+
+
+
+ Trusted by more than 5k+ satisfied
+ clients
+
+
+
+ {companies.map((company, idx) => (
+
+ ))}
+
+
+ {/* left gradient fade */}
+
+
+ {/* right gradient fade */}
+
+
+
+
+ );
+};
+
+export default SocialProff;
diff --git a/sections/Testimonial.tsx b/sections/Testimonial.tsx
new file mode 100644
index 0000000..2e6a79e
--- /dev/null
+++ b/sections/Testimonial.tsx
@@ -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(null);
+ const paraRef = useRef(null);
+ const containerRef = useRef(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(".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 (
+
+
+
+
+ Powered by{" "}
+
+ Trust.
+
+
+ Proven by{" "}
+
+ Nature.
+
+
+
+ See how municipalities and partners use FalktronTrees to make smarter,
+ data-driven decisions for urban green spaces.
+
+
+
+
+ {firstHalf.map((test, idx) => (
+
+ ))}
+
+
+ {secondHalf.map((test, idx) => (
+
+ ))}
+
+
+ {/* left gradient fade */}
+
+
+ {/* right gradient fade */}
+
+
+
+ );
+};
+
+export default Testimonial;