feat: updated homepage and pricing

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2025-10-02 18:10:07 -07:00
parent 0fc241e5fa
commit 6415d4fd57
27 changed files with 857 additions and 998 deletions

View file

@ -0,0 +1,12 @@
import React from "react";
import { ContactFormGridWithDetails } from "@/components/contact/contact-form";
const page = () => {
return (
<div>
<ContactFormGridWithDetails />
</div>
);
};
export default page;

View file

@ -0,0 +1,14 @@
"use client";
import { Footer } from "@/components/homepage/footer";
import { Navbar } from "@/components/homepage/navbar";
export default function HomePageLayout({ children }: { children: React.ReactNode }) {
return (
<main className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white">
<Navbar />
{children}
<Footer />
</main>
);
}

View file

@ -1,7 +1,7 @@
"use client";
import { AnimatePresence, motion } from "motion/react";
import { Loader2 } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useSearchParams } from "next/navigation";
import { Suspense, useEffect, useState } from "react";
import { toast } from "sonner";

View file

@ -1,19 +1,21 @@
"use client";
import { Footer } from "@/components/Footer";
import { CTAHomepage } from "@/components/homepage/cta";
import { FeaturesBentoGrid } from "@/components/homepage/features-bento-grid";
import { ModernHeroWithGradients } from "@/components/homepage/ModernHeroWithGradients";
import { Navbar } from "@/components/Navbar";
import { FeaturesCards } from "@/components/homepage/features-card";
import { Footer } from "@/components/homepage/footer";
import { HeroSection } from "@/components/homepage/hero-section";
import ExternalIntegrations from "@/components/homepage/integrations";
import { Navbar } from "@/components/homepage/navbar";
export default function HomePage() {
return (
<main className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white">
<Navbar />
<ModernHeroWithGradients />
<HeroSection />
<FeaturesCards />
<FeaturesBentoGrid />
<ExternalIntegrations />
<CTAHomepage />
<Footer />
</main>
);
}

View file

@ -0,0 +1,12 @@
import React from "react";
import PricingBasic from "@/components/pricing/pricing-section";
const page = () => {
return (
<div>
<PricingBasic />
</div>
);
};
export default page;

View file

@ -1,12 +0,0 @@
import React from 'react'
import { ContactFormGridWithDetails } from '@/components/contact/contact-form'
const page = () => {
return (
<div>
<ContactFormGridWithDetails />
</div>
)
}
export default page

View file

@ -1,12 +0,0 @@
import React from 'react'
import PricingBasic from '@/components/pricing/pricing-section'
const page = () => {
return (
<div>
<PricingBasic />
</div>
)
}
export default page

View file

@ -1,305 +0,0 @@
"use client";
import { IconMail, IconMenu2, IconUser, IconUserPlus, IconX } from "@tabler/icons-react";
import { AnimatePresence, motion, useMotionValueEvent, useScroll } from "motion/react";
import Link from "next/link";
import { useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { Logo } from "./Logo";
import { ThemeTogglerComponent } from "./theme/theme-toggle";
import { Button } from "./ui/button";
interface NavbarProps {
navItems: {
name: string;
link: string;
}[];
visible: boolean;
}
export const Navbar = () => {
const navItems = [
{
name: "Docs",
link: "/docs",
},
// {
// name: "Product",
// link: "/#product",
// },
// {
// name: "Pricing",
// link: "/#pricing",
// },
];
const ref = useRef<HTMLDivElement>(null);
const { scrollY } = useScroll({
target: ref,
offset: ["start start", "end start"],
});
const [visible, setVisible] = useState<boolean>(false);
useMotionValueEvent(scrollY, "change", (latest) => {
if (latest > 100) {
setVisible(true);
} else {
setVisible(false);
}
});
return (
<motion.div ref={ref} className="w-full fixed top-2 inset-x-0 z-50">
<DesktopNav visible={visible} navItems={navItems} />
<MobileNav visible={visible} navItems={navItems} />
</motion.div>
);
};
const DesktopNav = ({ navItems, visible }: NavbarProps) => {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const handleGoogleLogin = () => {
// Redirect to the login page
window.location.href = "/login";
};
return (
<motion.div
onMouseLeave={() => setHoveredIndex(null)}
animate={{
backdropFilter: "blur(16px)",
background: visible
? "rgba(var(--background-rgb), 0.8)"
: "rgba(var(--background-rgb), 0.6)",
width: visible ? "38%" : "80%",
height: visible ? "48px" : "64px",
y: visible ? 8 : 0,
}}
initial={{
width: "80%",
height: "64px",
background: "rgba(var(--background-rgb), 0.6)",
}}
transition={{
type: "spring",
stiffness: 400,
damping: 30,
}}
className={cn(
"hidden lg:flex flex-row self-center items-center justify-between py-2 mx-auto px-6 rounded-full relative z-[60] backdrop-saturate-[1.8]",
visible ? "border dark:border-white/10 border-gray-300/30" : "border-0"
)}
style={
{
"--background-rgb": "var(--tw-dark) ? '0, 0, 0' : '255, 255, 255'",
} as React.CSSProperties
}
>
<div className="flex flex-row items-center gap-2">
<Logo className="h-8 w-8 rounded-md" />
<span className="dark:text-white/90 text-gray-800 text-lg font-bold">SurfSense</span>
</div>
<div className="flex items-center gap-4">
<motion.div
className="lg:flex flex-row items-center justify-end space-x-1 text-sm"
animate={{
scale: visible ? 0.9 : 1,
}}
>
{navItems.map((navItem, idx) => (
<motion.div
key={`nav-item-${navItem.name}`}
onHoverStart={() => setHoveredIndex(idx)}
className="relative"
>
<Link
className="dark:text-white/90 text-gray-800 relative px-3 py-1.5 transition-colors"
href={navItem.link}
>
<span className="relative z-10">{navItem.name}</span>
{hoveredIndex === idx && (
<motion.div
layoutId="menu-hover"
className="absolute inset-0 rounded-full dark:bg-gradient-to-r dark:from-white/10 dark:to-white/20 bg-gradient-to-r from-gray-200 to-gray-300"
initial={{ opacity: 0, scale: 0.8 }}
animate={{
opacity: 1,
scale: 1.1,
background:
"var(--tw-dark) ? radial-gradient(circle at center, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 50%, transparent 100%) : radial-gradient(circle at center, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.03) 50%, transparent 100%)",
}}
exit={{
opacity: 0,
scale: 0.8,
transition: {
duration: 0.2,
},
}}
transition={{
type: "spring",
bounce: 0.4,
duration: 0.4,
}}
/>
)}
</Link>
</motion.div>
))}
</motion.div>
<ThemeTogglerComponent />
<AnimatePresence mode="popLayout" initial={false}>
{!visible && (
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
animate={{
scale: 1,
opacity: 1,
transition: {
type: "spring",
stiffness: 400,
damping: 25,
},
}}
exit={{
scale: 0.8,
opacity: 0,
transition: {
duration: 0.2,
},
}}
className="flex items-center gap-2"
>
<Link href="/contact">
<Button
variant="outline"
className="hidden cursor-pointer md:flex items-center gap-2 rounded-full bg-white/10 dark:bg-white/10 backdrop-blur-lg hover:bg-white/20 dark:hover:bg-white/20 text-gray-900 dark:text-white border-white/30 transition-all"
>
<IconUserPlus className="h-4 w-4" />
<span>Sign Up</span>
</Button>
</Link>
<Button
onClick={handleGoogleLogin}
variant="outline"
className="hidden cursor-pointer md:flex items-center gap-2 rounded-full dark:bg-white/20 dark:hover:bg-white/30 dark:text-white bg-gray-100 hover:bg-gray-200 text-gray-800 border-0"
>
<IconUser className="h-4 w-4" />
<span>Sign in</span>
</Button>
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
);
};
const MobileNav = ({ navItems, visible }: NavbarProps) => {
const [open, setOpen] = useState(false);
const handleGoogleLogin = () => {
// Redirect to the login page
window.location.href = "./login";
};
return (
<motion.div
animate={{
backdropFilter: "blur(16px)",
background: visible
? "rgba(var(--background-rgb), 0.8)"
: "rgba(var(--background-rgb), 0.6)",
width: visible ? "80%" : "90%",
y: visible ? 0 : 8,
borderRadius: open ? "24px" : "full",
padding: "8px 16px",
}}
initial={{
width: "80%",
background: "rgba(var(--background-rgb), 0.6)",
}}
transition={{
type: "spring",
stiffness: 400,
damping: 30,
}}
className={cn(
"flex relative flex-col lg:hidden w-full justify-between items-center max-w-[calc(100vw-2rem)] mx-auto z-50 backdrop-saturate-[1.8] rounded-full",
visible ? "border border-solid dark:border-white/40 border-gray-300/30" : "border-0"
)}
style={
{
"--background-rgb": "var(--tw-dark) ? '0, 0, 0' : '255, 255, 255'",
} as React.CSSProperties
}
>
<div className="flex flex-row justify-between items-center w-full">
<Logo className="h-8 w-8 rounded-md" />
<div className="flex items-center gap-2">
<ThemeTogglerComponent />
{open ? (
<IconX className="dark:text-white/90 text-gray-800" onClick={() => setOpen(!open)} />
) : (
<IconMenu2
className="dark:text-white/90 text-gray-800"
onClick={() => setOpen(!open)}
/>
)}
</div>
</div>
<AnimatePresence>
{open && (
<motion.div
initial={{
opacity: 0,
y: -20,
}}
animate={{
opacity: 1,
y: 0,
}}
exit={{
opacity: 0,
y: -20,
}}
transition={{
type: "spring",
stiffness: 400,
damping: 30,
}}
className="flex rounded-3xl absolute top-16 dark:bg-black/80 bg-white/90 backdrop-blur-xl backdrop-saturate-[1.8] inset-x-0 z-50 flex-col items-start justify-start gap-4 w-full px-6 py-8"
>
{navItems.map((navItem: { link: string; name: string }) => (
<Link
key={`link-${navItem.name}`}
href={navItem.link}
onClick={() => setOpen(false)}
className="relative dark:text-white/90 text-gray-800 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<motion.span className="block">{navItem.name}</motion.span>
</Link>
))}
<Link href="/contact" className="w-full" onClick={() => setOpen(false)}>
<Button
variant="outline"
className="flex cursor-pointer items-center gap-2 mt-4 w-full justify-center rounded-full bg-white/10 dark:bg-white/10 backdrop-blur-lg hover:bg-white/20 dark:hover:bg-white/20 text-gray-900 dark:text-white border-white/30 transition-all"
>
<IconUserPlus className="h-4 w-4" />
<span>Sign Up</span>
</Button>
</Link>
<Button
onClick={handleGoogleLogin}
variant="outline"
className="flex cursor-pointer items-center gap-2 mt-4 w-full justify-center rounded-full dark:bg-white/20 dark:hover:bg-white/30 dark:text-white bg-gray-100 hover:bg-gray-200 text-gray-800 border-0"
>
<IconUser className="h-4 w-4" />
<span>Sign in</span>
</Button>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
};

View file

@ -1,549 +0,0 @@
"use client";
import { IconBrandDiscord, IconBrandGithub, IconFileTypeDoc, IconMail, IconUserPlus } from "@tabler/icons-react";
import Image from "next/image";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { Logo } from "../Logo";
import { Features } from "./features-card";
export function ModernHeroWithGradients() {
return (
<div className="relative h-full min-h-[50rem] w-full">
<div className="relative z-20 mx-auto w-full px-4 py-6 md:px-8 lg:px-4">
<div className="relative my-12 overflow-hidden rounded-3xl bg-white py-16 shadow-sm dark:bg-gray-900/80 dark:shadow-lg dark:shadow-purple-900/10 md:py-48 mx-auto w-full max-w-[95%] xl:max-w-[98%]">
<TopLines />
<BottomLines />
<SideLines />
<TopGradient />
<BottomGradient />
<DarkModeGradient />
<div className="relative z-20 flex flex-col items-center justify-center overflow-hidden rounded-3xl p-4 md:p-12 lg:p-16">
<div className="flex justify-center w-full mb-4">
<Link
href="https://github.com/MODSetter/SurfSense"
target="_blank"
rel="noopener noreferrer"
>
<img
src="https://trendshift.io/api/badge/repositories/13606"
alt="MODSetter%2FSurfSense | Trendshift"
style={{ width: "250px", height: "55px" }}
width={250}
height={55}
/>
</Link>
</div>
<Link
href="/docs"
className="flex items-center mt-8 gap-1 rounded-full border border-gray-200 bg-gradient-to-b from-gray-50 to-gray-100 px-4 py-1 text-center text-sm text-gray-800 shadow-sm dark:border-[#404040] dark:bg-gradient-to-b dark:from-[#5B5B5D] dark:to-[#262627] dark:text-white dark:shadow-inner dark:shadow-purple-500/10"
>
<IconFileTypeDoc className="h-4 w-4 text-gray-800 dark:text-white" />
<span>Documentation</span>
</Link>
{/* Import the Logo component or define it in this file */}
<div className="flex items-center justify-center gap-4 mt-2">
<div className="h-16 w-16">
<Logo className="rounded-md" />
</div>
<h1 className="bg-gradient-to-b from-gray-800 to-gray-600 bg-clip-text py-4 text-center text-3xl text-transparent dark:from-white dark:to-purple-300 md:text-5xl lg:text-8xl">
SurfSense
</h1>
</div>
<p className="mx-auto max-w-3xl pb-10 text-center text-base text-gray-600 dark:text-neutral-300 md:text-lg lg:text-xl">
Your all-in-one AI research workspace.
</p>
<div className="flex flex-col items-center gap-6 py-6 sm:flex-row">
<Link
href="/contact"
className="w-48 gap-1 rounded-full border border-white/30 bg-white/10 backdrop-blur-lg px-5 py-3 text-center text-sm font-medium text-gray-900 dark:text-white shadow-lg hover:bg-white/20 dark:hover:bg-white/20 transition-all flex items-center justify-center"
>
<IconUserPlus className="h-5 w-5 mr-2" />
<span>Sign Up</span>
</Link>
<Link
href="https://discord.gg/ejRNvftDp9"
className="w-48 gap-1 rounded-full border border-gray-200 bg-gradient-to-b from-gray-50 to-gray-100 px-5 py-3 text-center text-sm font-medium text-gray-800 shadow-sm dark:border-[#404040] dark:bg-gradient-to-b dark:from-[#5B5B5D] dark:to-[#262627] dark:text-white dark:shadow-inner dark:shadow-purple-500/10 flex items-center justify-center"
>
<IconBrandDiscord className="h-5 w-5 mr-2" />
<span>Discord</span>
</Link>
<Link
href="https://github.com/MODSetter/SurfSense"
className="w-48 gap-1 rounded-full border border-transparent bg-gray-800 px-5 py-3 text-center text-sm font-medium text-white shadow-sm hover:bg-gray-700 dark:bg-gradient-to-r dark:from-purple-700 dark:to-indigo-800 dark:text-white dark:hover:from-purple-600 dark:hover:to-indigo-700 flex items-center justify-center"
>
<IconBrandGithub className="h-5 w-5 mr-2" />
<span>GitHub</span>
</Link>
</div>
</div>
<Features />
</div>
</div>
</div>
);
}
const TopLines = () => {
return (
<svg
width="166"
height="298"
viewBox="0 0 166 298"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="aspect-square pointer-events-none absolute inset-x-0 top-0 h-[100px] w-full md:h-[200px]"
>
<title>Top Lines</title>
<line
y1="-0.5"
x2="406"
y2="-0.5"
transform="matrix(0 1 1 0 1 -108)"
stroke="url(#paint0_linear_254_143)"
/>
<line
y1="-0.5"
x2="406"
y2="-0.5"
transform="matrix(0 1 1 0 34 -108)"
stroke="url(#paint1_linear_254_143)"
/>
<line
y1="-0.5"
x2="406"
y2="-0.5"
transform="matrix(0 1 1 0 67 -108)"
stroke="url(#paint2_linear_254_143)"
/>
<line
y1="-0.5"
x2="406"
y2="-0.5"
transform="matrix(0 1 1 0 100 -108)"
stroke="url(#paint3_linear_254_143)"
/>
<line
y1="-0.5"
x2="406"
y2="-0.5"
transform="matrix(0 1 1 0 133 -108)"
stroke="url(#paint4_linear_254_143)"
/>
<line
y1="-0.5"
x2="406"
y2="-0.5"
transform="matrix(0 1 1 0 166 -108)"
stroke="url(#paint5_linear_254_143)"
/>
<defs>
<linearGradient
id="paint0_linear_254_143"
x1="-7.42412e-06"
y1="0.500009"
x2="405"
y2="0.500009"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_254_143"
x1="-7.42412e-06"
y1="0.500009"
x2="405"
y2="0.500009"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint2_linear_254_143"
x1="-7.42412e-06"
y1="0.500009"
x2="405"
y2="0.500009"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint3_linear_254_143"
x1="-7.42412e-06"
y1="0.500009"
x2="405"
y2="0.500009"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint4_linear_254_143"
x1="-7.42412e-06"
y1="0.500009"
x2="405"
y2="0.500009"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint5_linear_254_143"
x1="-7.42412e-06"
y1="0.500009"
x2="405"
y2="0.500009"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
);
};
const BottomLines = () => {
return (
<svg
width="445"
height="418"
viewBox="0 0 445 418"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="aspect-square pointer-events-none absolute inset-x-0 -bottom-20 z-20 h-[150px] w-full md:h-[300px]"
>
<title>Bottom Lines</title>
<line x1="139.5" y1="418" x2="139.5" y2="12" stroke="url(#paint0_linear_0_1)" />
<line x1="172.5" y1="418" x2="172.5" y2="12" stroke="url(#paint1_linear_0_1)" />
<line x1="205.5" y1="418" x2="205.5" y2="12" stroke="url(#paint2_linear_0_1)" />
<line x1="238.5" y1="418" x2="238.5" y2="12" stroke="url(#paint3_linear_0_1)" />
<line x1="271.5" y1="418" x2="271.5" y2="12" stroke="url(#paint4_linear_0_1)" />
<line x1="304.5" y1="418" x2="304.5" y2="12" stroke="url(#paint5_linear_0_1)" />
<path
d="M1 149L109.028 235.894C112.804 238.931 115 243.515 115 248.361V417"
stroke="url(#paint6_linear_0_1)"
strokeOpacity="0.1"
strokeWidth="1.5"
/>
<path
d="M444 149L335.972 235.894C332.196 238.931 330 243.515 330 248.361V417"
stroke="url(#paint7_linear_0_1)"
strokeOpacity="0.1"
strokeWidth="1.5"
/>
<defs>
<linearGradient
id="paint0_linear_0_1"
x1="140.5"
y1="418"
x2="140.5"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_0_1"
x1="173.5"
y1="418"
x2="173.5"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint2_linear_0_1"
x1="206.5"
y1="418"
x2="206.5"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint3_linear_0_1"
x1="239.5"
y1="418"
x2="239.5"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint4_linear_0_1"
x1="272.5"
y1="418"
x2="272.5"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint5_linear_0_1"
x1="305.5"
y1="418"
x2="305.5"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="gray" className="dark:stop-color-white" />
<stop offset="1" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint6_linear_0_1"
x1="115"
y1="390.591"
x2="-59.1703"
y2="205.673"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.481613" stopColor="#E8E8E8" className="dark:stop-color-[#F8F8F8]" />
<stop
offset="1"
stopColor="#E8E8E8"
stopOpacity="0"
className="dark:stop-color-[#F8F8F8]"
/>
</linearGradient>
<linearGradient
id="paint7_linear_0_1"
x1="330"
y1="390.591"
x2="504.17"
y2="205.673"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.481613" stopColor="#E8E8E8" className="dark:stop-color-[#F8F8F8]" />
<stop
offset="1"
stopColor="#E8E8E8"
stopOpacity="0"
className="dark:stop-color-[#F8F8F8]"
/>
</linearGradient>
</defs>
</svg>
);
};
const SideLines = () => {
return (
<svg
width="1382"
height="370"
viewBox="0 0 1382 370"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="pointer-events-none absolute inset-0 z-30 h-full w-full"
>
<title>Side Lines</title>
<path
d="M268 115L181.106 6.97176C178.069 3.19599 173.485 1 168.639 1H0"
stroke="url(#paint0_linear_337_46)"
strokeOpacity="0.1"
strokeWidth="1.5"
/>
<path
d="M1114 115L1200.89 6.97176C1203.93 3.19599 1208.52 1 1213.36 1H1382"
stroke="url(#paint1_linear_337_46)"
strokeOpacity="0.1"
strokeWidth="1.5"
/>
<path
d="M268 255L181.106 363.028C178.069 366.804 173.485 369 168.639 369H0"
stroke="url(#paint2_linear_337_46)"
strokeOpacity="0.1"
strokeWidth="1.5"
/>
<path
d="M1114 255L1200.89 363.028C1203.93 366.804 1208.52 369 1213.36 369H1382"
stroke="url(#paint3_linear_337_46)"
strokeOpacity="0.1"
strokeWidth="1.5"
/>
<defs>
<linearGradient
id="paint0_linear_337_46"
x1="26.4087"
y1="1.00001"
x2="211.327"
y2="175.17"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.481613" stopColor="#E8E8E8" className="dark:stop-color-[#F8F8F8]" />
<stop
offset="1"
stopColor="#E8E8E8"
stopOpacity="0"
className="dark:stop-color-[#F8F8F8]"
/>
</linearGradient>
<linearGradient
id="paint1_linear_337_46"
x1="1355.59"
y1="1.00001"
x2="1170.67"
y2="175.17"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.481613" stopColor="#E8E8E8" className="dark:stop-color-[#F8F8F8]" />
<stop
offset="1"
stopColor="#E8E8E8"
stopOpacity="0"
className="dark:stop-color-[#F8F8F8]"
/>
</linearGradient>
<linearGradient
id="paint2_linear_337_46"
x1="26.4087"
y1="369"
x2="211.327"
y2="194.83"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.481613" stopColor="#E8E8E8" className="dark:stop-color-[#F8F8F8]" />
<stop
offset="1"
stopColor="#E8E8E8"
stopOpacity="0"
className="dark:stop-color-[#F8F8F8]"
/>
</linearGradient>
<linearGradient
id="paint3_linear_337_46"
x1="1355.59"
y1="369"
x2="1170.67"
y2="194.83"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.481613" stopColor="#E8E8E8" className="dark:stop-color-[#F8F8F8]" />
<stop
offset="1"
stopColor="#E8E8E8"
stopOpacity="0"
className="dark:stop-color-[#F8F8F8]"
/>
</linearGradient>
</defs>
</svg>
);
};
const BottomGradient = ({ className }: { className?: string }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="851"
height="595"
viewBox="0 0 851 595"
fill="none"
className={cn(
"pointer-events-none absolute -right-80 bottom-0 h-full w-full opacity-30 dark:opacity-100 dark:hidden",
className
)}
>
<title>Bottom Gradient</title>
<path
d="M118.499 0H532.468L635.375 38.6161L665 194.625L562.093 346H0L24.9473 121.254L118.499 0Z"
fill="url(#paint0_radial_254_132)"
/>
<defs>
<radialGradient
id="paint0_radial_254_132"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(412.5 346) rotate(-91.153) scale(397.581 423.744)"
>
<stop stopColor="#AAD3E9" />
<stop offset="0.25" stopColor="#7FB8D4" />
<stop offset="0.573634" stopColor="#5A9BB8" />
<stop offset="1" stopOpacity="0" />
</radialGradient>
</defs>
</svg>
);
};
const TopGradient = ({ className }: { className?: string }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1007"
height="997"
viewBox="0 0 1007 997"
fill="none"
className={cn(
"pointer-events-none absolute -left-96 top-0 h-full w-full opacity-30 dark:opacity-100 dark:hidden",
className
)}
>
<title>Top Gradient</title>
<path
d="M807 110.119L699.5 -117.546L8.5 -154L-141 246.994L-7 952L127 782.111L279 652.114L513 453.337L807 110.119Z"
fill="url(#paint0_radial_254_135)"
/>
<path
d="M807 110.119L699.5 -117.546L8.5 -154L-141 246.994L-7 952L127 782.111L279 652.114L513 453.337L807 110.119Z"
fill="url(#paint1_radial_254_135)"
/>
<defs>
<radialGradient
id="paint0_radial_254_135"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(77.0001 15.8894) rotate(90.3625) scale(869.41 413.353)"
>
<stop stopColor="#AAD3E9" />
<stop offset="0.25" stopColor="#7FB8D4" />
<stop offset="0.573634" stopColor="#5A9BB8" />
<stop offset="1" stopOpacity="0" />
</radialGradient>
<radialGradient
id="paint1_radial_254_135"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(127.5 -31) rotate(1.98106) scale(679.906 715.987)"
>
<stop stopColor="#AAD3E9" />
<stop offset="0.283363" stopColor="#7FB8D4" />
<stop offset="0.573634" stopColor="#5A9BB8" />
<stop offset="1" stopOpacity="0" />
</radialGradient>
</defs>
</svg>
);
};
const DarkModeGradient = () => {
return (
<div className="hidden dark:block">
<div className="absolute -left-48 -top-48 h-[800px] w-[800px] rounded-full bg-purple-900/20 blur-[180px]"></div>
<div className="absolute -right-48 -bottom-48 h-[800px] w-[800px] rounded-full bg-indigo-900/20 blur-[180px]"></div>
<div className="absolute left-1/2 top-1/2 h-[400px] w-[400px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-purple-800/10 blur-[120px]"></div>
</div>
);
};

View file

@ -6,7 +6,7 @@ import { cn } from "@/lib/utils";
export function CTAHomepage() {
return (
<section className="w-full grid grid-cols-1 md:grid-cols-3 my-20 md:my-40 justify-start relative z-20 max-w-7xl mx-auto bg-gradient-to-br from-gray-100 to-white dark:from-neutral-900 dark:to-neutral-950">
<section className="w-full grid grid-cols-1 md:grid-cols-3 my-20 md:my-20 justify-start relative z-20 max-w-7xl mx-auto bg-gradient-to-br from-gray-100 to-white dark:from-neutral-900 dark:to-neutral-950">
<GridLineHorizontal className="top-0" offset="200px" />
<GridLineHorizontal className="bottom-0 top-auto" offset="200px" />
<GridLineVertical className="left-0" offset="80px" />

View file

@ -2,42 +2,33 @@ import { Sliders, Users, Workflow } from "lucide-react";
import type { ReactNode } from "react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
export function Features() {
export function FeaturesCards() {
return (
<section className="py-2 md:py-8 dark:bg-transparent">
<div className="@container mx-auto max-w-5xl">
{/* <div className="text-center">
<h2 className="text-balance text-4xl font-semibold lg:text-5xl">Built to cover your needs</h2>
<p className="mt-4">Libero sapiente aliquam quibusdam aspernatur, praesentium iusto repellendus.</p>
</div> */}
<div className="@container mx-auto max-w-7xl">
<div className="text-center">
<h2 className="text-balance text-4xl font-semibold lg:text-5xl">
Everything Your Team Needs to Succeed
</h2>
<p className="mt-4">
Powerful features designed to enhance collaboration, boost productivity, and streamline
your workflow.
</p>
</div>
<div className="@min-4xl:max-w-full @min-4xl:grid-cols-3 mx-auto mt-8 grid max-w-sm gap-6 *:text-center md:mt-16">
<Card className="group shadow-black-950/5">
<CardHeader className="pb-3">
<CardDecorator>
<Sliders className="size-6" aria-hidden />
</CardDecorator>
<h3 className="mt-6 font-medium">Customizable</h3>
</CardHeader>
<CardContent>
<p className="text-sm">Customize your research agent to your specific needs.</p>
</CardContent>
</Card>
<Card className="group shadow-black-950/5">
<CardHeader className="pb-3">
<CardDecorator>
<Workflow className="size-6" aria-hidden />
</CardDecorator>
<h3 className="mt-6 font-medium">Streamline your workflow</h3>
<h3 className="mt-6 font-medium">Streamlined Workflow</h3>
</CardHeader>
<CardContent>
<p className="text-sm">
Pull all your knowledge into one place, so you can find what matters and get things
done faster.
Centralize all your knowledge and resources in one intelligent workspace. Find what
you need instantly and accelerate decision-making.
</p>
</CardContent>
</Card>
@ -52,7 +43,26 @@ export function Features() {
</CardHeader>
<CardContent>
<p className="text-sm">Make your company and personal content collaborative.</p>
<p className="text-sm">
Work together effortlessly with real-time collaboration tools that keep your entire
team aligned.
</p>
</CardContent>
</Card>
<Card className="group shadow-black-950/5">
<CardHeader className="pb-3">
<CardDecorator>
<Sliders className="size-6" aria-hidden />
</CardDecorator>
<h3 className="mt-6 font-medium">Fully Customizable</h3>
</CardHeader>
<CardContent>
<p className="text-sm">
Choose from 100+ leading LLMs and seamlessly call any model on demand.
</p>
</CardContent>
</Card>
</div>

View file

@ -22,7 +22,7 @@ export function Footer() {
];
return (
<div className="border-t border-neutral-100 dark:border-white/[0.1] px-8 py-20 bg-white dark:bg-neutral-950 w-full relative overflow-hidden">
<div className="border-t border-neutral-100 dark:border-white/[0.1] px-8 py-20 w-full relative overflow-hidden">
<div className="max-w-7xl mx-auto text-sm text-neutral-500 justify-between items-start md:px-8">
<div className="flex flex-col items-center justify-center w-full relative">
<div className="mr-0 md:mr-4 md:flex mb-4">

View file

@ -0,0 +1,318 @@
"use client";
import { AnimatePresence, motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
import React, { useEffect, useRef, useState } from "react";
import Balancer from "react-wrap-balancer";
import { cn } from "@/lib/utils";
export function HeroSection() {
const containerRef = useRef<HTMLDivElement>(null);
const parentRef = useRef<HTMLDivElement>(null);
return (
<div
ref={parentRef}
className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-4 py-20 md:px-8 md:py-40"
>
<BackgroundGrids />
<CollisionMechanism
beamOptions={{
initialX: -400,
translateX: 600,
duration: 7,
repeatDelay: 3,
}}
containerRef={containerRef}
parentRef={parentRef}
/>
<CollisionMechanism
beamOptions={{
initialX: -200,
translateX: 800,
duration: 4,
repeatDelay: 3,
}}
containerRef={containerRef}
parentRef={parentRef}
/>
<CollisionMechanism
beamOptions={{
initialX: 200,
translateX: 1200,
duration: 5,
repeatDelay: 3,
}}
containerRef={containerRef}
parentRef={parentRef}
/>
<CollisionMechanism
containerRef={containerRef}
parentRef={parentRef}
beamOptions={{
initialX: 400,
translateX: 1400,
duration: 6,
repeatDelay: 3,
}}
/>
<h2 className="relative z-50 mx-auto mb-4 mt-4 max-w-4xl text-balance text-center text-3xl font-semibold tracking-tight text-gray-700 md:text-7xl dark:text-neutral-300">
<Balancer>
The AI Workspace{" "}
<div className="relative mx-auto inline-block w-max [filter:drop-shadow(0px_1px_3px_rgba(27,_37,_80,_0.14))]">
<div className="text-black [text-shadow:0_0_rgba(0,0,0,0.1)] dark:text-white">
<span className="">Built for Teams</span>
</div>
</div>
</Balancer>
</h2>
{/* // TODO:aCTUAL DESCRITION */}
<p className="relative z-50 mx-auto mt-4 max-w-lg px-4 text-center text-base/6 text-gray-600 dark:text-gray-200">
Your multi-collaborative AI workspace, connected to external sources.
</p>
<div className="mb-10 mt-8 flex w-full flex-col items-center justify-center gap-4 px-8 sm:flex-row md:mb-20">
<Link
href="/contact"
className="group relative z-20 flex h-10 w-full cursor-pointer items-center justify-center space-x-2 rounded-lg bg-black p-px px-4 py-2 text-center text-sm font-semibold leading-6 text-white no-underline transition duration-200 sm:w-52 dark:bg-white dark:text-black"
>
Start Free Trial
</Link>
<Link
href="/pricing"
className="shadow-input group relative z-20 flex h-10 w-full cursor-pointer items-center justify-center space-x-2 rounded-lg bg-white p-px px-4 py-2 text-sm font-semibold leading-6 text-black no-underline transition duration-200 hover:-translate-y-0.5 sm:w-52 dark:bg-neutral-800 dark:text-white"
>
Explore
</Link>
</div>
<div
ref={containerRef}
className="relative mx-auto max-w-7xl rounded-[32px] border border-neutral-200/50 bg-neutral-100 p-2 backdrop-blur-lg md:p-4 dark:border-neutral-700 dark:bg-neutral-800/50"
>
<div className="rounded-[24px] border border-neutral-200 bg-white p-2 dark:border-neutral-700 dark:bg-black">
{/* Light mode image */}
<Image
src="/homepage/temp_hero_light.png"
alt="header"
width={1920}
height={1080}
className="rounded-[20px] block dark:hidden"
/>
{/* Dark mode image */}
<Image
src="/homepage/temp_hero_dark.png"
alt="header"
width={1920}
height={1080}
className="rounded-[20px] hidden dark:block"
/>
</div>
</div>
</div>
);
}
const BackgroundGrids = () => {
return (
<div className="pointer-events-none absolute inset-0 z-0 grid h-full w-full -rotate-45 transform select-none grid-cols-2 gap-10 md:grid-cols-4">
<div className="relative h-full w-full">
<GridLineVertical className="left-0" />
<GridLineVertical className="left-auto right-0" />
</div>
<div className="relative h-full w-full">
<GridLineVertical className="left-0" />
<GridLineVertical className="left-auto right-0" />
</div>
<div className="relative h-full w-full bg-gradient-to-b from-transparent via-neutral-100 to-transparent dark:via-neutral-800">
<GridLineVertical className="left-0" />
<GridLineVertical className="left-auto right-0" />
</div>
<div className="relative h-full w-full">
<GridLineVertical className="left-0" />
<GridLineVertical className="left-auto right-0" />
</div>
</div>
);
};
const CollisionMechanism = React.forwardRef<
HTMLDivElement,
{
containerRef: React.RefObject<HTMLDivElement | null>;
parentRef: React.RefObject<HTMLDivElement | null>;
beamOptions?: {
initialX?: number;
translateX?: number;
initialY?: number;
translateY?: number;
rotate?: number;
className?: string;
duration?: number;
delay?: number;
repeatDelay?: number;
};
}
>(({ parentRef, containerRef, beamOptions = {} }, ref) => {
const beamRef = useRef<HTMLDivElement>(null);
const [collision, setCollision] = useState<{
detected: boolean;
coordinates: { x: number; y: number } | null;
}>({ detected: false, coordinates: null });
const [beamKey, setBeamKey] = useState(0);
const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false);
useEffect(() => {
const checkCollision = () => {
if (beamRef.current && containerRef.current && parentRef.current && !cycleCollisionDetected) {
const beamRect = beamRef.current.getBoundingClientRect();
const containerRect = containerRef.current.getBoundingClientRect();
const parentRect = parentRef.current.getBoundingClientRect();
if (beamRect.bottom >= containerRect.top) {
const relativeX = beamRect.left - parentRect.left + beamRect.width / 2;
const relativeY = beamRect.bottom - parentRect.top;
setCollision({
detected: true,
coordinates: { x: relativeX, y: relativeY },
});
setCycleCollisionDetected(true);
if (beamRef.current) {
beamRef.current.style.opacity = "0";
}
}
}
};
const animationInterval = setInterval(checkCollision, 50);
return () => clearInterval(animationInterval);
}, [cycleCollisionDetected, containerRef]);
useEffect(() => {
if (collision.detected && collision.coordinates) {
setTimeout(() => {
setCollision({ detected: false, coordinates: null });
setCycleCollisionDetected(false);
// Set beam opacity to 0
if (beamRef.current) {
beamRef.current.style.opacity = "1";
}
}, 2000);
// Reset the beam animation after a delay
setTimeout(() => {
setBeamKey((prevKey) => prevKey + 1);
}, 2000);
}
}, [collision]);
return (
<>
<motion.div
key={beamKey}
ref={beamRef}
animate="animate"
initial={{
translateY: beamOptions.initialY || "-200px",
translateX: beamOptions.initialX || "0px",
rotate: beamOptions.rotate || -45,
}}
variants={{
animate: {
translateY: beamOptions.translateY || "800px",
translateX: beamOptions.translateX || "700px",
rotate: beamOptions.rotate || -45,
},
}}
transition={{
duration: beamOptions.duration || 8,
repeat: Infinity,
repeatType: "loop",
ease: "linear",
delay: beamOptions.delay || 0,
repeatDelay: beamOptions.repeatDelay || 0,
}}
className={cn(
"absolute left-96 top-20 m-auto h-14 w-px rounded-full bg-gradient-to-t from-orange-500 via-yellow-500 to-transparent",
beamOptions.className
)}
/>
<AnimatePresence>
{collision.detected && collision.coordinates && (
<Explosion
key={`${collision.coordinates.x}-${collision.coordinates.y}`}
className=""
style={{
left: `${collision.coordinates.x + 20}px`,
top: `${collision.coordinates.y}px`,
transform: "translate(-50%, -50%)",
}}
/>
)}
</AnimatePresence>
</>
);
});
CollisionMechanism.displayName = "CollisionMechanism";
const Explosion = ({ ...props }: React.HTMLProps<HTMLDivElement>) => {
const spans = Array.from({ length: 20 }, (_, index) => ({
id: index,
initialX: 0,
initialY: 0,
directionX: Math.floor(Math.random() * 80 - 40),
directionY: Math.floor(Math.random() * -50 - 10),
}));
return (
<div {...props} className={cn("absolute z-50 h-2 w-2", props.className)}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: [0, 1, 0] }}
exit={{ opacity: 0 }}
transition={{ duration: 1, ease: "easeOut" }}
className="absolute -inset-x-10 top-0 m-auto h-[4px] w-10 rounded-full bg-gradient-to-r from-transparent via-orange-500 to-transparent blur-sm"
></motion.div>
{spans.map((span) => (
<motion.span
key={span.id}
initial={{ x: span.initialX, y: span.initialY, opacity: 1 }}
animate={{ x: span.directionX, y: span.directionY, opacity: 0 }}
transition={{ duration: Math.random() * 1.5 + 0.5, ease: "easeOut" }}
className="absolute h-1 w-1 rounded-full bg-gradient-to-b from-orange-500 to-yellow-500"
/>
))}
</div>
);
};
const GridLineVertical = ({ className, offset }: { className?: string; offset?: string }) => {
return (
<div
style={
{
"--background": "#ffffff",
"--color": "rgba(0, 0, 0, 0.2)",
"--height": "5px",
"--width": "1px",
"--fade-stop": "90%",
"--offset": offset || "150px", //-100px if you want to keep the line inside
"--color-dark": "rgba(255, 255, 255, 0.3)",
maskComposite: "exclude",
} as React.CSSProperties
}
className={cn(
"absolute top-[calc(var(--offset)/2*-1)] h-[calc(100%+var(--offset))] w-[var(--width)]",
"bg-[linear-gradient(to_bottom,var(--color),var(--color)_50%,transparent_0,transparent)]",
"[background-size:var(--width)_var(--height)]",
"[mask:linear-gradient(to_top,var(--background)_var(--fade-stop),transparent),_linear-gradient(to_bottom,var(--background)_var(--fade-stop),transparent),_linear-gradient(black,black)]",
"[mask-composite:exclude]",
"z-30",
"dark:bg-[linear-gradient(to_bottom,var(--color-dark),var(--color-dark)_50%,transparent_0,transparent)]",
className
)}
></div>
);
};

View file

@ -0,0 +1,185 @@
"use client";
import React, { useEffect, useState } from "react";
interface Integration {
name: string;
icon: string;
}
const INTEGRATIONS: Integration[] = [
// Search
{ name: "Tavily", icon: "https://www.tavily.com/images/logo.svg" },
{
name: "LinkUp",
icon: "https://framerusercontent.com/images/7zeIm6t3f1HaSltkw8upEvsD80.png?scale-down-to=512",
},
// Communication
{ name: "Slack", icon: "https://cdn.simpleicons.org/slack/4A154B" },
{ name: "Discord", icon: "https://cdn.simpleicons.org/discord/5865F2" },
{ name: "Gmail", icon: "https://cdn.simpleicons.org/gmail/EA4335" },
// Project Management
{ name: "Linear", icon: "https://cdn.simpleicons.org/linear/5E6AD2" },
{ name: "Jira", icon: "https://cdn.simpleicons.org/jira/0052CC" },
{ name: "ClickUp", icon: "https://cdn.simpleicons.org/clickup/7B68EE" },
{ name: "Airtable", icon: "https://cdn.simpleicons.org/airtable/18BFFF" },
// Documentation & Knowledge
{ name: "Confluence", icon: "https://cdn.simpleicons.org/confluence/172B4D" },
{ name: "Notion", icon: "https://cdn.simpleicons.org/notion/000000/ffffff" },
// Cloud Storage
{ name: "Google Drive", icon: "https://cdn.simpleicons.org/googledrive/4285F4" },
{ name: "Dropbox", icon: "https://cdn.simpleicons.org/dropbox/0061FF" },
{
name: "Amazon S3",
icon: "https://upload.wikimedia.org/wikipedia/commons/b/bc/Amazon-S3-Logo.svg",
},
// Development
{ name: "GitHub", icon: "https://cdn.simpleicons.org/github/181717/ffffff" },
// Productivity
{ name: "Google Calendar", icon: "https://cdn.simpleicons.org/googlecalendar/4285F4" },
{ name: "Luma", icon: "https://images.lumacdn.com/social-images/default-social-202407.png" },
// Media
{ name: "YouTube", icon: "https://cdn.simpleicons.org/youtube/FF0000" },
];
function SemiCircleOrbit({ radius, centerX, centerY, count, iconSize, startIndex }: any) {
return (
<>
{/* Semi-circle glow background */}
<div className="absolute inset-0 flex justify-center items-start overflow-visible">
<div
className="
w-[800px] h-[800px] rounded-full
bg-[radial-gradient(circle_at_center,rgba(0,0,0,0.15),transparent_70%)]
dark:bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.15),transparent_70%)]
blur-3xl
pointer-events-none
"
style={{
zIndex: 0,
transform: "translateY(-20%)",
}}
/>
</div>
{/* Orbit icons */}
{Array.from({ length: count }).map((_, index) => {
const actualIndex = startIndex + index;
// Skip if we've run out of integrations
if (actualIndex >= INTEGRATIONS.length) return null;
const angle = (index / (count - 1)) * 180;
const x = radius * Math.cos((angle * Math.PI) / 180);
const y = radius * Math.sin((angle * Math.PI) / 180);
const integration = INTEGRATIONS[actualIndex];
// Tooltip positioning — above or below based on angle
const tooltipAbove = angle > 90;
return (
<div
key={index}
className="absolute flex flex-col items-center group"
style={{
left: `${centerX + x - iconSize / 2}px`,
top: `${centerY - y - iconSize / 2}px`,
zIndex: 5,
}}
>
<img
src={integration.icon}
alt={integration.name}
width={iconSize}
height={iconSize}
className="object-contain cursor-pointer transition-transform hover:scale-110"
style={{ minWidth: iconSize, minHeight: iconSize }} // fix accidental shrink
/>
{/* Tooltip */}
<div
className={`absolute ${
tooltipAbove ? "bottom-[calc(100%+8px)]" : "top-[calc(100%+8px)]"
} hidden group-hover:block w-auto min-w-max rounded-lg bg-black px-3 py-1.5 text-xs text-white shadow-lg text-center whitespace-nowrap`}
>
{integration.name}
<div
className={`absolute left-1/2 -translate-x-1/2 w-3 h-3 rotate-45 bg-black ${
tooltipAbove ? "top-full" : "bottom-full"
}`}
></div>
</div>
</div>
);
})}
</>
);
}
export default function ExternalIntegrations() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const updateSize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
updateSize();
window.addEventListener("resize", updateSize);
return () => window.removeEventListener("resize", updateSize);
}, []);
const baseWidth = Math.min(size.width * 0.8, 700);
const centerX = baseWidth / 2;
const centerY = baseWidth * 0.5;
const iconSize =
size.width < 480
? Math.max(24, baseWidth * 0.05)
: size.width < 768
? Math.max(28, baseWidth * 0.06)
: Math.max(32, baseWidth * 0.07);
return (
<section className="py-12 relative min-h-screen w-full overflow-visible">
<div className="relative flex flex-col items-center text-center z-10">
<h1 className="my-6 text-4xl font-bold lg:text-7xl">Integrations</h1>
<p className="mb-12 max-w-2xl text-gray-600 dark:text-gray-400 lg:text-xl">
Connect your favourite apps to your workflow.
</p>
<div
className="relative overflow-visible"
style={{ width: baseWidth, height: baseWidth * 0.7, paddingBottom: "100px" }}
>
<SemiCircleOrbit
radius={baseWidth * 0.22}
centerX={centerX}
centerY={centerY}
count={5}
iconSize={iconSize}
startIndex={0}
/>
<SemiCircleOrbit
radius={baseWidth * 0.36}
centerX={centerX}
centerY={centerY}
count={6}
iconSize={iconSize}
startIndex={5}
/>
<SemiCircleOrbit
radius={baseWidth * 0.5}
centerX={centerX}
centerY={centerY}
count={8}
iconSize={iconSize}
startIndex={11}
/>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,177 @@
"use client";
import { IconBrandDiscord, IconBrandGithub, IconMenu2, IconX } from "@tabler/icons-react";
import { AnimatePresence, motion } from "motion/react";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import { Logo } from "@/components/Logo";
import { ThemeTogglerComponent } from "@/components/theme/theme-toggle";
import { cn } from "@/lib/utils";
export const Navbar = () => {
const [isScrolled, setIsScrolled] = useState(false);
const navItems = [
{ name: "Home", link: "/" },
{ name: "Pricing", link: "/pricing" },
{ name: "Sign In", link: "/login" },
{ name: "Docs", link: "/docs" },
];
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div className="fixed top-1 left-0 right-0 z-[60] w-full">
<DesktopNav navItems={navItems} isScrolled={isScrolled} />
<MobileNav navItems={navItems} isScrolled={isScrolled} />
</div>
);
};
const DesktopNav = ({ navItems, isScrolled }: any) => {
const [hovered, setHovered] = useState<number | null>(null);
return (
<motion.div
onMouseLeave={() => {
setHovered(null);
}}
className={cn(
"mx-auto hidden w-full max-w-7xl flex-row items-center justify-between self-start rounded-full px-4 py-2 lg:flex transition-all duration-300",
isScrolled
? "bg-white/80 backdrop-blur-md border border-white/20 shadow-lg dark:bg-neutral-950/80 dark:border-neutral-800/50"
: "bg-transparent border border-transparent"
)}
>
<div className="flex flex-row items-center gap-2">
<Logo className="h-8 w-8 rounded-md" />
<span className="dark:text-white/90 text-gray-800 text-lg font-bold">SurfSense</span>
</div>
<div className="hidden flex-1 flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-2">
{navItems.map((navItem: any, idx: number) => (
<Link
onMouseEnter={() => setHovered(idx)}
className="relative px-4 py-2 text-neutral-600 dark:text-neutral-300"
key={`link=${idx}`}
href={navItem.link}
>
{hovered === idx && (
<motion.div
layoutId="hovered"
className="absolute inset-0 h-full w-full rounded-full bg-gray-100 dark:bg-neutral-800"
/>
)}
<span className="relative z-20">{navItem.name}</span>
</Link>
))}
</div>
<div className="flex items-center gap-2">
<Link
href="https://discord.gg/ejRNvftDp9"
target="_blank"
rel="noopener noreferrer"
className="hidden rounded-full p-2 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors md:flex items-center justify-center"
>
<IconBrandDiscord className="h-5 w-5 text-neutral-600 dark:text-neutral-300" />
</Link>
<Link
href="https://github.com/MODSetter/SurfSense"
target="_blank"
rel="noopener noreferrer"
className="hidden rounded-full px-3 py-2 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors md:flex items-center gap-1.5"
>
<IconBrandGithub className="h-5 w-5 text-neutral-600 dark:text-neutral-300" />
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-300">8.3k</span>
</Link>
<ThemeTogglerComponent />
<Link
href="/contact"
className="hidden rounded-full bg-black px-8 py-2 text-sm font-bold text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] md:block dark:bg-white dark:text-black"
>
Book a call
</Link>
</div>
</motion.div>
);
};
const MobileNav = ({ navItems, isScrolled }: any) => {
const [open, setOpen] = useState(false);
return (
<>
<motion.div
animate={{ borderRadius: open ? "4px" : "2rem" }}
key={String(open)}
className={cn(
"mx-auto flex w-full max-w-[calc(100vw-2rem)] flex-col items-center justify-between px-4 py-2 lg:hidden transition-all duration-300",
isScrolled
? "bg-white/80 backdrop-blur-md border border-white/20 shadow-lg dark:bg-neutral-950/80 dark:border-neutral-800/50"
: "bg-transparent border border-transparent"
)}
>
<div className="flex w-full flex-row items-center justify-between">
<div className="flex flex-row items-center gap-2">
<Logo className="h-8 w-8 rounded-md" />
<span className="dark:text-white/90 text-gray-800 text-lg font-bold">SurfSense</span>
</div>
{open ? (
<IconX className="text-black dark:text-white" onClick={() => setOpen(!open)} />
) : (
<IconMenu2 className="text-black dark:text-white" onClick={() => setOpen(!open)} />
)}
</div>
<AnimatePresence>
{open && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-x-0 top-16 z-20 flex w-full flex-col items-start justify-start gap-4 rounded-lg bg-white/80 backdrop-blur-md border border-white/20 shadow-lg px-4 py-8 dark:bg-neutral-950/80 dark:border-neutral-800/50"
>
{navItems.map((navItem: any, idx: number) => (
<Link
key={`link=${idx}`}
href={navItem.link}
className="relative text-neutral-600 dark:text-neutral-300"
>
<motion.span className="block">{navItem.name} </motion.span>
</Link>
))}
<div className="flex w-full items-center gap-2 pt-2">
<Link
href="https://discord.gg/your-server"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center rounded-lg p-2 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
>
<IconBrandDiscord className="h-5 w-5 text-neutral-600 dark:text-neutral-300" />
</Link>
<Link
href="https://github.com/MODSetter/SurfSense"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 rounded-lg px-3 py-2 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
>
<IconBrandGithub className="h-5 w-5 text-neutral-600 dark:text-neutral-300" />
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-300">
8.3k
</span>
</Link>
</div>
<button className="w-full rounded-lg bg-black px-8 py-2 font-medium text-white shadow-[0px_-2px_0px_0px_rgba(255,255,255,0.4)_inset] dark:bg-white dark:text-black">
Book a call
</button>
</motion.div>
)}
</AnimatePresence>
</motion.div>
</>
);
};

View file

@ -91,42 +91,53 @@ export function Pricing({
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 sm:2 gap-4">
{plans.map((plan, index) => (
<motion.div
key={index}
initial={{ y: 50, opacity: 1 }}
whileInView={
isDesktop
<div className={cn(
"grid grid-cols-1 gap-4",
plans.length === 2 ? "md:grid-cols-2 max-w-5xl mx-auto" : "md:grid-cols-3"
)}>
{plans.map((plan, index) => (
<motion.div
key={index}
initial={{ y: 50, opacity: 1 }}
whileInView={
isDesktop
? plans.length === 2
? {
y: plan.isPopular ? -20 : 0,
opacity: 1,
scale: plan.isPopular ? 1.0 : 0.96,
}
: {
y: plan.isPopular ? -20 : 0,
opacity: 1,
x: index === 2 ? -30 : index === 0 ? 30 : 0,
scale: index === 0 || index === 2 ? 0.94 : 1.0,
}
: {}
}
viewport={{ once: true }}
transition={{
duration: 1.6,
type: "spring",
stiffness: 100,
damping: 30,
delay: 0.4,
opacity: { duration: 0.5 },
}}
className={cn(
`rounded-2xl border-[1px] p-6 bg-background text-center lg:flex lg:flex-col lg:justify-center relative`,
plan.isPopular ? "border-primary border-2" : "border-border",
"flex flex-col",
!plan.isPopular && "mt-5",
index === 0 || index === 2
? "z-0 transform translate-x-0 translate-y-0 -translate-z-[50px] rotate-y-[10deg]"
: {}
}
viewport={{ once: true }}
transition={{
duration: 1.6,
type: "spring",
stiffness: 100,
damping: 30,
delay: 0.4,
opacity: { duration: 0.5 },
}}
className={cn(
`rounded-2xl border-[1px] p-6 bg-background text-center lg:flex lg:flex-col lg:justify-center relative`,
plan.isPopular ? "border-primary border-2" : "border-border",
"flex flex-col",
!plan.isPopular && "mt-5",
plans.length === 3 && (index === 0 || index === 2)
? "z-0 transform translate-x-0 translate-y-0 -translate-z-[50px] rotate-y-[10deg]"
: plans.length === 2 && !plan.isPopular
? "z-0"
: "z-10",
index === 0 && "origin-right",
index === 2 && "origin-left"
)}
>
plans.length === 3 && index === 0 && "origin-right",
plans.length === 3 && index === 2 && "origin-left"
)}
>
{plan.isPopular && (
<div className="absolute top-0 right-0 bg-primary py-0.5 px-2 rounded-bl-xl rounded-tr-xl flex items-center">
<Star className="text-primary-foreground h-4 w-4 fill-current" />
@ -136,9 +147,12 @@ export function Pricing({
</div>
)}
<div className="flex-1 flex flex-col">
<p className="text-base font-semibold text-muted-foreground">{plan.name}</p>
<div className="mt-6 flex items-center justify-center gap-x-2">
<span className="text-5xl font-bold tracking-tight text-foreground">
<p className="text-base font-semibold text-muted-foreground">{plan.name}</p>
<div className="mt-6 flex items-center justify-center gap-x-2">
<span className="text-5xl font-bold tracking-tight text-foreground">
{isNaN(Number(plan.price)) ? (
<span>{isMonthly ? plan.price : plan.yearlyPrice}</span>
) : (
<NumberFlow
value={isMonthly ? Number(plan.price) : Number(plan.yearlyPrice)}
format={{
@ -154,17 +168,18 @@ export function Pricing({
willChange
className="font-variant-numeric: tabular-nums"
/>
</span>
{plan.period !== "Next 3 months" && (
<span className="text-sm font-semibold leading-6 tracking-wide text-muted-foreground">
/ {plan.period}
</span>
)}
</div>
</span>
{plan.period && plan.period !== "Next 3 months" && (
<span className="text-sm font-semibold leading-6 tracking-wide text-muted-foreground">
/ {plan.period}
</span>
)}
</div>
<p className="text-xs leading-5 text-muted-foreground">
{isMonthly ? "billed monthly" : "billed annually"}
</p>
<p className="text-xs leading-5 text-muted-foreground">
{isNaN(Number(plan.price)) ? "" : isMonthly ? "billed monthly" : "billed annually"}
</p>
<ul className="mt-5 gap-2 flex flex-col">
{plan.features.map((feature, idx) => (

View file

@ -4,55 +4,38 @@ import { Pricing } from "@/components/pricing";
const demoPlans = [
{
name: "STARTER",
price: "50",
yearlyPrice: "40",
period: "per month",
name: "COMMUNITY",
price: "0",
yearlyPrice: "0",
period: "forever",
features: [
"Up to 10 projects",
"Basic analytics",
"48-hour support response time",
"Limited API access",
"Community support",
"Supports 100+ LLMs",
"Supports local Ollama or vLLM setups",
"6000+ Embedding Models",
"50+ File extensions supported.",
"Podcasts support with local TTS providers.",
"Connects with 15+ external sources.",
"Cross-Browser Extension for dynamic webpages including authenticated content",
"Upcoming: Mergeable MindMaps",
"Upcoming: Note Management",
],
description: "Perfect for individuals and small projects",
buttonText: "Start Free Trial",
href: "/sign-up",
isPopular: false,
},
{
name: "PROFESSIONAL",
price: "99",
yearlyPrice: "79",
period: "per month",
features: [
"Unlimited projects",
"Advanced analytics",
"24-hour support response time",
"Full API access",
"Priority support",
"Team collaboration",
"Custom integrations",
],
description: "Ideal for growing teams and businesses",
description: "Open source version with powerful features",
buttonText: "Get Started",
href: "/sign-up",
href: "/docs",
isPopular: true,
},
{
name: "ENTERPRISE",
price: "299",
yearlyPrice: "239",
period: "per month",
price: "Contact Us",
yearlyPrice: "Contact Us",
period: "",
features: [
"Everything in Professional",
"Custom solutions",
"Dedicated account manager",
"1-hour support response time",
"SSO Authentication",
"Advanced security",
"Custom contracts",
"SLA agreement",
"Everything in Community",
"Priority Support",
"Access Controls",
"Multicollaborative and multiplayer features",
"Video generation",
"Advanced security features",
],
description: "For large organizations with specific needs",
buttonText: "Contact Sales",
@ -63,11 +46,7 @@ const demoPlans = [
function PricingBasic() {
return (
<Pricing
plans={demoPlans}
title="Simple, Transparent Pricing"
description="Choose the plan that works for you"
/>
<Pricing plans={demoPlans} title="SurfSense Pricing" description="Choose that works for you" />
);
}

View file

@ -74,6 +74,7 @@
"react-markdown": "^10.1.0",
"react-rough-notation": "^1.0.5",
"react-syntax-highlighter": "^15.6.1",
"react-wrap-balancer": "^1.1.1",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",

View file

@ -173,6 +173,9 @@ importers:
react-syntax-highlighter:
specifier: ^15.6.1
version: 15.6.1(react@19.1.0)
react-wrap-balancer:
specifier: ^1.1.1
version: 1.1.1(react@19.1.0)
rehype-raw:
specifier: ^7.0.0
version: 7.0.0
@ -4797,6 +4800,11 @@ packages:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-wrap-balancer@1.1.1:
resolution: {integrity: sha512-AB+l7FPRWl6uZ28VcJ8skkwLn2+UC62bjiw8tQUrZPlEWDVnR9MG0lghyn7EyxuJSsFEpht4G+yh2WikEqQ/5Q==}
peerDependencies:
react: '>=16.8.0 || ^17.0.0 || ^18'
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
@ -11108,6 +11116,10 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-wrap-balancer@1.1.1(react@19.1.0):
dependencies:
react: 19.1.0
react@19.1.0: {}
readable-stream@3.6.2:

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB