import React, { useEffect, useRef, useState, useLayoutEffect, useMemo, memo } from 'react';
import {
motion,
useScroll,
useTransform,
AnimatePresence,
useAnimation,
useMotionValue,
useVelocity,
useSpring,
useAnimationFrame
} from 'framer-motion';
import {
ArrowUpRight,
Monitor,
RefreshCw,
Star,
FileText,
Calendar,
Rocket,
Globe,
Smartphone,
Zap,
Shield,
ChevronRight,
X,
Grape
} from 'lucide-react';
import Hls from 'hls.js';
// --- Global Styles ---
const globalStyles = `
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&family=Instrument+Serif:ital@1&display=swap');
:root {
--background: 0 0% 0%;
--foreground: 0 0% 100%;
--font-heading: 'Instrument Serif', serif;
--font-body: 'Barlow', sans-serif;
}
html {
scroll-behavior: smooth;
}
body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
font-family: var(--font-body);
-webkit-font-smoothing: antialiased;
margin: 0;
overflow-x: hidden;
}
.font-heading { font-family: var(--font-heading); }
.font-body { font-family: var(--font-body); }
.liquid-glass {
background: rgba(255, 255, 255, 0.01);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.1);
position: relative;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.liquid-glass-strong {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(50px);
-webkit-backdrop-filter: blur(50px);
box-shadow: 4px 4px 4px rgba(0,0,0,0.05), inset 0 1px 1px rgba(255, 255, 255, 0.15);
position: relative;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.fade-bottom { background: linear-gradient(to bottom, transparent, black); }
.fade-top { background: linear-gradient(to top, transparent, black); }
input, textarea, select {
background: rgba(255,255,255,0.03) !important;
border: 1px solid rgba(255,255,255,0.1) !important;
color: white !important;
outline: none !important;
}
`;
// --- Utility: Wrap Math for Infinite Scrolling ---
const wrap = (min, max, v) => {
const rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};
// --- Shared Component: Badge ---
const Badge = ({ children }) => (
{children}
);
// --- Components: Booking Form Modal ---
const BookingForm = ({ isOpen, onClose, calDataProps }) => {
const [step, setStep] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({
need: '',
name: '',
email: '',
company: '',
url: '',
competitor: '',
offer: '',
budget: '',
});
const encode = (data) => {
return Object.keys(data)
.map(key => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
.join("&");
};
const handleSubmit = async () => {
setIsSubmitting(true);
try {
const isSandbox = window.location.href.startsWith('blob:');
if (!isSandbox) {
await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: encode({ "form-name": "booking-intel", ...formData })
});
} else {
console.log("Sandbox mode detected: Skipped Netlify form submission. Form Data:", formData);
}
} catch (error) {
console.warn("Netlify Form submission error:", error);
} finally {
setIsSubmitting(false);
setStep(5);
}
};
const handleNext = () => {
if (step === 4) handleSubmit();
else setStep(step + 1);
};
if (!isOpen) return null;
return (
{/* Hidden form for Netlify */}
{step === 1 && (
What are we building?
{["A brand new website", "A high-converting rework"].map((opt) => (
))}
)}
{step === 2 && (
The Basics
)}
{step === 3 && (
The Strategy Intel
)}
{step === 4 && (
Project Investment
{["$1.5k - $3k", "$3k - $5k", "$5k - $10k+", "Under $1k"].map((opt) => (
))}
{isSubmitting && Syncing strategy intel...
}
)}
{step === 5 && (
Intel Received.
Choose a time for your strategy call below. We'll have your mockup insights ready.
)}
);
};
// --- Components: Media & Animation ---
const LoadingScreen = ({ onComplete }) => {
const [progress, setProgress] = useState(0);
const [wordIndex, setWordIndex] = useState(0);
const words = ["Vision", "Preview", "Fruitful"];
const onCompleteRef = useRef(onComplete);
useEffect(() => { onCompleteRef.current = onComplete; }, [onComplete]);
useEffect(() => {
const wordInterval = setInterval(() => { setWordIndex((prev) => (prev < words.length - 1 ? prev + 1 : prev)); }, 900);
let startTime;
let animationFrame;
const animateProgress = (timestamp) => {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const currentProgress = Math.min((elapsed / 2700) * 100, 100);
setProgress(currentProgress);
if (currentProgress < 100) animationFrame = requestAnimationFrame(animateProgress);
else setTimeout(() => { if (onCompleteRef.current) onCompleteRef.current(); }, 400);
};
animationFrame = requestAnimationFrame(animateProgress);
return () => { clearInterval(wordInterval); cancelAnimationFrame(animationFrame); };
}, [words.length]);
return (
Grapeful
{Math.round(progress).toString().padStart(3, '0')}
);
};
const HlsVideo = ({ src, className }) => {
const videoRef = useRef(null);
useEffect(() => {
if (videoRef.current) {
if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(src); hls.attachMedia(videoRef.current); }
else if (videoRef.current.canPlayType('application/vnd.apple.mpegurl')) { videoRef.current.src = src; }
}
}, [src]);
return ;
};
const BlurText = ({ text, className, delayOffset = 0 }) => (
{text.split(" ").map((word, i) => (
{word}
))}
);
// --- Components: Scroll Velocity Parallax Gallery ---
const ParallaxGalleryRow = ({ images, baseVelocity = 2 }) => {
const baseX = useMotionValue(0);
const { scrollY } = useScroll();
const scrollVelocity = useVelocity(scrollY);
const smoothVelocity = useSpring(scrollVelocity, { damping: 50, stiffness: 400 });
const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], { clamp: false });
// We duplicate images 4 times to guarantee a seamless loop regardless of screen size.
const x = useTransform(baseX, (v) => `${wrap(-25, 0, v)}%`);
const directionFactor = useRef(1);
useAnimationFrame((t, delta) => {
let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
if (velocityFactor.get() < 0) {
directionFactor.current = -1;
} else if (velocityFactor.get() > 0) {
directionFactor.current = 1;
}
moveBy += directionFactor.current * moveBy * velocityFactor.get();
baseX.set(baseX.get() + moveBy);
});
const duplicatedImages = [...images, ...images, ...images, ...images];
return (
{duplicatedImages.map((src, i) => (
))}
);
};
const InfiniteMarqueeGallery = ({ images }) => {
const half = Math.ceil(images.length / 2);
const row1 = images.slice(0, half);
const row2 = images.slice(half);
return (
);
};
const TextRevealByWord = ({ text }) => {
const targetRef = useRef(null);
const { scrollYProgress } = useScroll({ target: targetRef });
return (
{text.split(" ").map((word, i) => {
const start = i / text.split(" ").length;
const end = start + 1 / text.split(" ").length;
const opacity = useTransform(scrollYProgress, [start, end], [0, 1]);
return (
{word}
{word}
);
})}
);
};
// --- Portfolio Assets ---
const portfolioImages = [
"https://motionsites.ai/assets/hero-hr-saas-preview-Cf365Y1O.gif",
"https://motionsites.ai/assets/hero-neuralyn-preview-Br4FRDQA.gif",
"https://motionsites.ai/assets/hero-logoisum-preview-yhpSc7Yy.gif",
"https://motionsites.ai/assets/hero-web3-eos-poster-DF0_WdVS.png",
"https://motionsites.ai/assets/hero-new-era-auto-preview-W56vp0xD.gif",
"https://motionsites.ai/assets/hero-wealth-preview-B70idl_u.gif",
"https://motionsites.ai/assets/hero-taskora-preview-BlRBv8IU.gif",
"https://motionsites.ai/assets/hero-synapse-ai-preview-BjBuH68i.gif",
"https://motionsites.ai/assets/hero-bloom-ai-preview-g6RcYLTl.gif",
"https://motionsites.ai/assets/hero-taskly-preview-Dq2MKaI0.gif"
];
// --- Main Application ---
export default function App() {
const [isLoading, setIsLoading] = useState(true);
const [isFormOpen, setIsFormOpen] = useState(false);
const journeyRef = useRef(null);
const { scrollYProgress: journeyProgress } = useScroll({ target: journeyRef, offset: ["start 75%", "end 80%"] });
const lineHeight = useTransform(journeyProgress, [0, 1], ["0%", "100%"]);
useEffect(() => {
(function (C, A, L) {
let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () {
let cal = C.Cal; let ar = arguments;
if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; }
if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; if(typeof namespace === "string"){cal.ns[namespace] = cal.ns[namespace] || api;p(cal.ns[namespace], ar);p(cal, ["initNamespace", namespace]);} else p(cal, ar); return;} p(cal, ar);
};
})(window, "https://app.cal.com/embed/embed.js", "init");
window.Cal("init", "15min", {origin:"https://app.cal.com"});
window.Cal.ns["15min"]("ui", {"hideEventTypeDetails":false,"layout":"month_view"});
}, []);
const calDataProps = { "data-cal-link": "jimmy-nguyen-tippa0/15min", "data-cal-namespace": "15min", "data-cal-config": '{"layout":"month_view","useSlotsViewOnSmallScreen":"true"}' };
return (
<>
{isLoading && setIsLoading(false)} />}
{isFormOpen && setIsFormOpen(false)} calDataProps={calDataProps} />}
{/* NAV */}
{/* HERO */}
Risk-Free
Get a free preview before you pay.
We design a custom, high-converting landing page for your business—100% free. If you love it, we partner to launch. If not, walk away risk-free.
{/* PARTNERS BAR */}
Built for growing brands
{["Startups", "Local Services", "Consultants", "Agencies"].map((partner) => (
{partner}
))}
{/* MARQUEE GALLERY WITH SCROLL VELOCITY */}
{/* JOURNEY SECTION */}
The Journey
From vision to fruitful reality.
A frictionless, zero-risk process designed to get your brand online faster and better.
{[
{ title: "Fill the Form", desc: "Share your brand goals so we can build your 'Vibe Code' mockup.", icon: FileText },
{ title: "Strategy Call", desc: "A 15-minute chat to align our visions and verify the fit.", icon: Calendar },
{ title: "Free Preview", desc: "Review your functional landing page. $0 upfront cost.", icon: Monitor },
{ title: "Launch & Grow", desc: "Partner with us for ongoing growth, updates, and results.", icon: Rocket },
].map((step, i) => (
0{i+1}
{step.title}
{step.desc}
))}
{/* CAPABILITIES SECTION */}
Capabilities
Grateful clients. Fruitful results.
Results first.
Charge later.
We believe in proving our value upfront. We build a functional preview before you ever pay.
Beyond design.
Ongoing growth.
A fruitful business needs more than just a website. Our plan includes maintenance and reputation growth.
{/* THE PARTNERSHIP SECTION */}
The Partnership
Everything you need.
Nothing you don't.
{/* Card 1: Setup */}
Setup
The Foundation
High-Converting Website
A clean, modern site built to earn trust and bring in leads.
Mobile First Design
Looks sharp and works well on every screen size.
Fast and Search Ready
Built for speed, structure, and better local visibility.
{/* Card 2: Monthly */}
Monthly
Growth Partner
Review Growth System
Make it easier for customers to leave reviews and boost your online reputation.
Ongoing Website Updates
We handle text changes, image swaps, and small layout edits.
Hosting and Protection
Your site stays live, secure, and up to date.
{/* TEXT REVEAL */}
{/* FOOTER CTA */}
Ready to launch?
© 2026 Grapeful Web Agency.
>
);
}