/* ── LANDING SECTIONS ─────────────────────────────────────────────── */
/* ── Intersection helper ─────────────────────────────────────────────
Replays the entrance animation every time the element re-enters the
viewport (scroll down AND scroll up). Two observers:
• Enter — fires when the element is comfortably in view (uses the
caller's tighter threshold/rootMargin), flipping `seen` → true.
• Leave — fires only when the element is *fully* off-screen, flipping
`seen` → false so the next entry can replay. The reset happens
out of view, so the user never sees the content disappear.
─────────────────────────────────────────────────────────────────── */
function useInView(ref, options = { threshold: 0.15, rootMargin: "-60px" }) {
const [seen, setSeen] = useState(false);
useEffect(() => {
if (!ref.current) return;
const el = ref.current;
const enterObs = new IntersectionObserver((entries) => {
entries.forEach(en => { if (en.isIntersecting) setSeen(true); });
}, options);
enterObs.observe(el);
const leaveObs = new IntersectionObserver((entries) => {
entries.forEach(en => { if (!en.isIntersecting) setSeen(false); });
}, { threshold: 0, rootMargin: "0px" });
leaveObs.observe(el);
return () => { enterObs.disconnect(); leaveObs.disconnect(); };
}, [ref]);
return seen;
}
function Reveal({ children, delay = 0, y = 24, style }) {
const ref = useRef(null);
const seen = useInView(ref);
return (
{children}
);
}
/* ── ANIMATED HEADING ─────────────────────────────────────────────
Reusable heading w/ motion variants:
word-up — words fade-up with stagger (default)
letter-up — letters fade-up with stagger
slide-right — words slide in from the right
blur-in — words fade from blur to focus
mask-reveal — sliding gold mask reveals each line
highlight — gold highlight wipes behind text as you scroll in
lines: ["plain", { text, gradient: true, underline: true }]
─────────────────────────────────────────────────────────────────── */
function AnimatedHeading({ lines, motion = "word-up", as: Tag = "h2", className, style, delay = 0 }) {
const ref = useRef(null);
const seen = useInView(ref, { threshold: 0.25, rootMargin: "-40px" });
const normLines = lines.map(l => (typeof l === "string" ? { text: l } : l));
return (
{normLines.map((line, li) => (
{line.underline && (
)}
))}
);
}
function HeadingLine({ text, motion, seen, startDelay, gradient }) {
// Split text — letter-up splits into letters w/ word groups; others split into words
if (motion === "mask-reveal") {
return (
{text}
);
}
if (motion === "highlight") {
return (
{text}
);
}
if (motion === "letter-up") {
// split into words to preserve spacing, each word into chars
const words = text.split(" ");
let idx = 0;
return (
<>
{words.map((w, wi) => (
{[...w].map((ch) => {
const i = idx++;
return (
{ch}
);
})}
{wi < words.length - 1 ? " " : ""}
))}
>
);
}
// word-based motions
const words = text.split(" ");
return (
<>
{words.map((w, i) => {
let initial = "translateY(.5em)";
if (motion === "slide-right") initial = "translateX(.5em)";
if (motion === "blur-in") initial = "translateY(.2em)";
return (
{w}
{i < words.length - 1 ? " " : ""}
);
})}
>
);
}
/* ── HOW IT WORKS ────────────────────────────────────────────────── */
function HowItWorks() {
const ref = useRef(null);
const seen = useInView(ref);
const STEPS = [
{
step: "01", color: "#3b82f6", bg: "rgba(59,130,246,.12)",
title: "Connect your sources",
desc: "Link Meta Ads, Google Ads, or add a booking form. Leads flow in automatically from the platforms you already use.",
icon: ,
},
{
step: "02", color: "#8b5cf6", bg: "rgba(139,92,246,.12)",
title: "Build your pipeline",
desc: "Create custom stages that match your sales process. Assign owners, set up automation rules, invite your team.",
icon: ,
},
{
step: "03", color: "#10b981", bg: "rgba(16,185,129,.12)",
title: "Close more deals",
desc: "Automated follow-ups go out the moment a lead is ready. Your team focuses on conversations, not admin.",
icon: ,
},
];
return (
How it works
Three steps to a running sales operation. No configuration consultants needed.
{/* connecting line */}
{/* moving dot along line */}
{STEPS.map((s, i) => (
);
}
/* ── PRODUCT PROOF ────────────────────────────────────────────────── */
function ProductProof() {
const ITEMS = [
{
title: "Capture leads the moment they arrive",
description: "Connect Meta and Google sources so new enquiries land directly in your CRM instead of getting buried in inboxes, exports, or spreadsheets.",
label: "Lead capture", color: "#3b82f6", motion: "capture",
stats: [{ val: "Seconds", l: "from form to CRM" }, { val: "Meta + Google", l: "ad sources ready" }],
icon: ,
},
{
title: "Run your pipeline without spreadsheet chaos",
description: "Move leads through custom stages, assign owners, track activity, and keep the whole team looking at the same live board.",
label: "Pipeline workspace", color: "#8b5cf6", motion: "pipeline",
stats: [{ val: "Custom", l: "stages and fields" }, { val: "Live", l: "team pipeline view" }],
icon: ,
},
{
title: "Automate the follow-up work",
description: "Use n8n and webhook flows to trigger emails, WhatsApp messages, tasks, and internal alerts as soon as a lead reaches the right stage.",
label: "Automation", color: "#10b981", motion: "automation",
stats: [{ val: "n8n", l: "workflow support" }, { val: "Rules", l: "stage-based triggers" }],
icon: ,
},
{
title: "Launch with the basics already covered",
description: "Start with booking links, imports, field mapping, team roles, and reporting foundations so early sales operations do not become messy later.",
label: "Ready to operate", color: "#C9A227", motion: "launch",
stats: [{ val: "CSV", l: "import and mapping" }, { val: "Bookings", l: "links and calendar" }],
icon: ,
},
];
return (
Built for the first sales motion
A practical breakdown of the workflows Kredoo helps you put in place: capture, pipeline, automation, and launch operations.
{ITEMS.map((it, i) => (
{/* top accent bar with traveling shimmer */}
);
}
/* Mini-visualization per card */
function PPMotion({ kind, color }) {
if (kind === "capture") {
// Lead capture: dots flying from "Meta/Google" left → CRM right
return (
);
}
if (kind === "pipeline") {
// Three stages with a card hopping between them
return (
{["New", "Qualified", "Won"].map((stage, i) => (
{stage}
))}
);
}
if (kind === "automation") {
// Trigger → Action animated waveform
return (
);
}
if (kind === "launch") {
// Progress bar filling
return (