diff --git a/apps/web/package.json b/apps/web/package.json index c433c716c0..89cf034b40 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,6 +12,7 @@ "dependencies": { "@next/third-parties": "^15.1.7", "@t3-oss/env-nextjs": "^0.11.1", + "@tailwindcss/postcss": "^4.0.13", "@trigger.dev/sdk": "^3.3.15", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -37,8 +38,8 @@ "@types/node": "^22.13.2", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", - "postcss": "^8.5.2", - "tailwindcss": "^3.4.17", + "postcss": "^8.5.3", + "tailwindcss": "^4.0.13", "typescript": "^5.7.3" } } \ No newline at end of file diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs index 1a69fd2a45..2a0059f879 100644 --- a/apps/web/postcss.config.mjs +++ b/apps/web/postcss.config.mjs @@ -1,8 +1,6 @@ -/** @type {import('postcss-load-config').Config} */ const config = { - plugins: { - tailwindcss: {}, - }, + plugins: { + "@tailwindcss/postcss": {}, + }, }; - export default config; diff --git a/apps/web/public/bento-1.png b/apps/web/public/bento-1.png new file mode 100644 index 0000000000..4ff56916a9 Binary files /dev/null and b/apps/web/public/bento-1.png differ diff --git a/apps/web/public/bento-2.png b/apps/web/public/bento-2.png new file mode 100644 index 0000000000..21c8efcc15 Binary files /dev/null and b/apps/web/public/bento-2.png differ diff --git a/apps/web/public/bento-3.png b/apps/web/public/bento-3.png new file mode 100644 index 0000000000..94c351936d Binary files /dev/null and b/apps/web/public/bento-3.png differ diff --git a/apps/web/public/bento-4.png b/apps/web/public/bento-4.png new file mode 100644 index 0000000000..812ce80ded Binary files /dev/null and b/apps/web/public/bento-4.png differ diff --git a/apps/web/public/bento-5.png b/apps/web/public/bento-5.png new file mode 100644 index 0000000000..2e7b9ec4d0 Binary files /dev/null and b/apps/web/public/bento-5.png differ diff --git a/apps/web/public/comp-step-1.png b/apps/web/public/comp-step-1.png new file mode 100644 index 0000000000..7d514a73c0 Binary files /dev/null and b/apps/web/public/comp-step-1.png differ diff --git a/apps/web/public/comp-step-2.png b/apps/web/public/comp-step-2.png new file mode 100644 index 0000000000..2e4691ac8f Binary files /dev/null and b/apps/web/public/comp-step-2.png differ diff --git a/apps/web/public/comp-step-3.png b/apps/web/public/comp-step-3.png new file mode 100644 index 0000000000..189f36e0d1 Binary files /dev/null and b/apps/web/public/comp-step-3.png differ diff --git a/apps/web/public/comp-step-4.png b/apps/web/public/comp-step-4.png new file mode 100644 index 0000000000..8cef025681 Binary files /dev/null and b/apps/web/public/comp-step-4.png differ diff --git a/apps/web/public/file.svg b/apps/web/public/file.svg new file mode 100644 index 0000000000..004145cddf --- /dev/null +++ b/apps/web/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/footer-big-logo.png b/apps/web/public/footer-big-logo.png new file mode 100644 index 0000000000..9cb9fe761d Binary files /dev/null and b/apps/web/public/footer-big-logo.png differ diff --git a/apps/web/public/globe.svg b/apps/web/public/globe.svg new file mode 100644 index 0000000000..567f17b0d7 --- /dev/null +++ b/apps/web/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/hero-shadow-eclipse.png b/apps/web/public/hero-shadow-eclipse.png new file mode 100644 index 0000000000..4540fe8c5a Binary files /dev/null and b/apps/web/public/hero-shadow-eclipse.png differ diff --git a/apps/web/public/hero-showcase.png b/apps/web/public/hero-showcase.png new file mode 100644 index 0000000000..ee999845be Binary files /dev/null and b/apps/web/public/hero-showcase.png differ diff --git a/apps/web/public/next.svg b/apps/web/public/next.svg new file mode 100644 index 0000000000..5174b28c56 --- /dev/null +++ b/apps/web/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/vercel.svg b/apps/web/public/vercel.svg new file mode 100644 index 0000000000..7705396033 --- /dev/null +++ b/apps/web/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/window.svg b/apps/web/public/window.svg new file mode 100644 index 0000000000..b2b2a44f6e --- /dev/null +++ b/apps/web/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/app/(home)/layout.tsx b/apps/web/src/app/(home)/layout.tsx deleted file mode 100644 index f1bee01ce9..0000000000 --- a/apps/web/src/app/(home)/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { SiteHeader } from "../components/site-header"; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
- -
{children}
-
- ); -} diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx index 81322d66d5..f24b645c06 100644 --- a/apps/web/src/app/(home)/page.tsx +++ b/apps/web/src/app/(home)/page.tsx @@ -1,41 +1,20 @@ -import { WaitlistForm } from "@/app/components/waitlist-form"; -import Balancer from "react-wrap-balancer"; -import WhyUs from "../components/why-us"; -import OurSolution from "../components/our-solution"; -import HowItWorks from "../components/how-it-works"; -import CTA from "../components/cta"; +import { HeroSection } from "@/app/components/(homepage)/home-hero"; +import { Section2 } from "@/app/components/(homepage)/section-2"; +import { Section3 } from "@/app/components/(homepage)/section-3"; +import { Section4 } from "@/app/components/(homepage)/section-4"; +import { Section5 } from "@/app/components/(homepage)/section-5"; +import { Section6 } from "@/app/components/(homepage)/section-6"; +import { Section7 } from "@/app/components/(homepage)/section-7"; export default function Home() { return ( -
-
-
-

- Open Source Compliance Automation Platform -

-

- - Get audit ready, fast. Automate compliance with frameworks like - SOC 2, ISO 27001, and GDPR - in weeks, not months. Open source - competitor to Drata & Vanta. - -

-
-
- -
- -
- - - -
- -
- - - - -
+
+ + + + + + +
); -} +} \ No newline at end of file diff --git a/apps/web/src/app/(home)/privacy/page.tsx b/apps/web/src/app/(home)/privacy/page.tsx deleted file mode 100644 index c5611ea129..0000000000 --- a/apps/web/src/app/(home)/privacy/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { PrivacyContent } from "@/app/components/legal/privacy-content"; -import type { Metadata } from "next"; -import Balancer from "react-wrap-balancer"; - -export const metadata: Metadata = { - title: "Privacy Policy - Comp AI", - description: "Privacy policy and data handling practices for Comp AI", -}; - -export default function PrivacyPage() { - return ( -
-
-
-

- Privacy Policy -

- -
- -
-
-
-
- ); -} diff --git a/apps/web/src/app/(home)/success/page.tsx b/apps/web/src/app/(home)/success/page.tsx index 793500fb80..c9e055420a 100644 --- a/apps/web/src/app/(home)/success/page.tsx +++ b/apps/web/src/app/(home)/success/page.tsx @@ -1,4 +1,4 @@ -import Logo from "@/app/components/logo"; + import { env } from "@/env.mjs"; import Link from "next/link"; import Balancer from "react-wrap-balancer"; @@ -9,8 +9,6 @@ export default function SuccessPage() {
- -

Thank you 🎉

diff --git a/apps/web/src/app/(home)/terms/page.tsx b/apps/web/src/app/(home)/terms/page.tsx deleted file mode 100644 index 0f8badbf72..0000000000 --- a/apps/web/src/app/(home)/terms/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { TermsContent } from "@/app/components/legal/terms-content"; -import type { Metadata } from "next"; -import Balancer from "react-wrap-balancer"; - -export const metadata: Metadata = { - title: "Terms of Service - Comp AI", - description: "Terms of service and usage agreement for Comp AI", -}; - -export default function TermsPage() { - return ( -
-
-
-

- Terms of Service -

- -
- -
-
-
-
- ); -} diff --git a/apps/web/src/app/components/(homepage)/home-hero.tsx b/apps/web/src/app/components/(homepage)/home-hero.tsx new file mode 100644 index 0000000000..e4997d739e --- /dev/null +++ b/apps/web/src/app/components/(homepage)/home-hero.tsx @@ -0,0 +1,124 @@ +/* eslint-disable @next/next/no-img-element */ + +"use client"; + +import { FlickeringGrid } from "@/app/components/flickering-grid"; +import { motion } from "motion/react"; +import { WaitlistForm } from "@/app/components/(homepage)/waitlist-form"; + + +export function HeroSection() { + return ( +
+
+
+
+ +
+ +
+
+
+ +
+

+ Open Source Compliance Automation Platform +

+

+ Get audit ready, fast. Automate compliance with frameworks like + SOC 2, ISO 27001, and GDPR - in weeks, not months. +

+
+
+ +
+
+ +
+
+ + + +
+ +
+
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/section-2.tsx b/apps/web/src/app/components/(homepage)/section-2.tsx new file mode 100644 index 0000000000..309c79efe1 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/section-2.tsx @@ -0,0 +1,59 @@ +export function Section2() { + const features = [ + { + title: "Automated Continuous Monitoring", + description: "Detect security risks and compliance gaps in minutes.", + }, + { + title: "Automated Evidence Collection", + description: + "Powerful integrations automatically collect evidence for most frameworks.", + }, + { + title: "Pre-Mapped Controls", + description: + "Frameworks like SOC 2, ISO 27001, and GDPR supported out of the box.", + }, + { + title: "Risk & Vendor Management", + description: "Manage your risks, manage vendors & protect your company.", + }, + ]; + + return ( +
+
+ {features.map((feature, index) => ( +
+
+
+
+ + + + +

{feature.title}

+
+

{feature.description}

+
+ ))} +
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/section-3.tsx b/apps/web/src/app/components/(homepage)/section-3.tsx new file mode 100644 index 0000000000..349bddf194 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/section-3.tsx @@ -0,0 +1,93 @@ +/* eslint-disable @next/next/no-img-element */ +interface BentoItem { + image: string; + title: string; + description: string; + imgClassName: string; + bentoClassName: string; +} + +const bentoItems: BentoItem[] = [ + { + image: "/bento-1.png", + title: "Compliance made easy", + description: + "Access your information security program and controls, track, monitor and access risks, and manage your vendors, all from one platform.", + bentoClassName: "col-span-2 lg:border-r border-border/5 border-b", + imgClassName: "top-0", + }, + { + image: "/bento-2.png", + title: "One platform, every framework", + description: + "One platform for frameworks like SOC 2, ISO 27001, and GDPR—streamlined compliance, automated with AI & integrations.", + bentoClassName: "col-span-2 lg:border-r border-border/5 border-b", + imgClassName: "top-0", + }, + { + image: "/bento-3.png", + title: "Evidence on autopilot", + description: + "Automated evidence gathering keeps you compliant, while real-time monitoring catches gaps before they become problems.", + bentoClassName: "col-span-2 border-border/5 border-b", + imgClassName: "top-0", + }, + { + image: "/bento-4.png", + title: "Enterprise platform, low barrier to entry", + description: + "Get started with Compa in 4 minutes—simply create your account & integrate your tech stack to get an instant overview, all without frustrating sales calls and upfront annual contracts.", + bentoClassName: + "col-span-3 lg:border-r border-border/5 border-b lg:border-b-0", + imgClassName: "-top-10", + }, + { + image: "/bento-5.png", + title: "Security & Compliance", + description: + "Compa AI automates your journey with frameworks like SOC 2, ISO 27001, and GDPR—from start to finish, and beyond—backed by our open-source community, get help, advice & support through the process.", + bentoClassName: "col-span-3", + imgClassName: "-top-10", + }, +]; + +export function Section3() { + return ( +
+
+

+ Why Us +

+
+

+ Everything you need to get compliant, fast. +

+

+ Transparent, automated, and cost-effective compliance powered by + open-source and community-driven innovation. +

+
+
+ +
+ {bentoItems.map((item) => ( +
+
+ {item.title} +
+

{item.title}

+

+ {item.description} +

+
+
+
+ ))} +
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/section-4.tsx b/apps/web/src/app/components/(homepage)/section-4.tsx new file mode 100644 index 0000000000..bff9f7ae6b --- /dev/null +++ b/apps/web/src/app/components/(homepage)/section-4.tsx @@ -0,0 +1,134 @@ +const problems = [ + { + id: 1, + text: "Compliance processes are fragmented and require multiple tools.", + }, + { + id: 2, + text: "Traditional solutions are costly and lack transparency.", + }, + { + id: 3, + text: "Audits are time-consuming and stressful for teams.", + }, +]; + +const solutions = [ + { + id: 1, + text: "Compliance processes are fragmented and require multiple tools.", + }, + { + id: 2, + text: "Traditional solutions are costly and lack transparency.", + }, + { + id: 3, + text: "Audits are time-consuming and stressful for teams.", + }, +]; + +export function Section4() { + return ( +
+
+
+

+ Our Solution +

+
+
+

+ Every framework you need to win deals +

+

+ Most compliance solutions are closed, costly, and complex. We're + building an open-source platform to make SOC 2, ISO 27001, and GDPR + accessible and affordable. +

+
+ +
+
+
+
+
+
+
+
+
+
+

+ The problems +

+
+ {problems.map((problem) => ( +

+ + + + + + {problem.text} + +

+ ))} +
+
+
+

+ Our solution +

+
+ {solutions.map((solution) => ( +

+ + + + + {solution.text} +

+ ))} +
+
+
+
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/section-5.tsx b/apps/web/src/app/components/(homepage)/section-5.tsx new file mode 100644 index 0000000000..378a178a53 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/section-5.tsx @@ -0,0 +1,24 @@ +import { ComplianceRoadmap } from "@/app/components/roadmap"; + +export function Section5() { + return ( +
+
+
+

+ Framework +

+
+

+ Flexible compliance for you and your customers +

+

+ Compliance solutions and schedules tailored to fit your business + and customers' needs. +

+
+
+
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/section-6.tsx b/apps/web/src/app/components/(homepage)/section-6.tsx new file mode 100644 index 0000000000..c81a359032 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/section-6.tsx @@ -0,0 +1,84 @@ +interface BentoItem { + id: number; + title: string; + description: string; + bentoClassName: string; +} + +const bentoItems: BentoItem[] = [ + { + id: 1, + title: "Automated evidence collection and cloud tests", + description: + "Access your information security program and controls, track, monitor and access risks, and manage your vendors, all from one platform.", + bentoClassName: "col-span-2 lg:border-r border-border/5 border-b", + }, + { + id: 2, + title: "Support for every framework you need", + description: + "One platform for frameworks like SOC 2, ISO 27001, and GDPR—streamlined compliance, automated with AI & integrations.", + bentoClassName: "col-span-2 lg:border-r border-border/5 border-b", + }, + { + id: 3, + title: "Make compliance simple and straightforward", + description: + "Automated evidence gathering keeps you compliant, while real-time monitoring catches gaps before they become problems.", + bentoClassName: "col-span-2 border-border/5 border-b", + }, +]; + +export function Section6() { + return ( +
+
+

+ Built for scale +

+
+

+ Scalable compliance for startups and enterprises +

+

+ Whether you're a startup preparing for your first SOC 2 audit + or an enterprise managing multiple frameworks across subsidiaries, + our platform grows with you +

+
+
+ +
+ {bentoItems.map((item) => ( +
+
+
+ + + + +

+ {item.title} +

+
+
+
+ ))} +
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/section-7.tsx b/apps/web/src/app/components/(homepage)/section-7.tsx new file mode 100644 index 0000000000..795d6d1802 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/section-7.tsx @@ -0,0 +1,109 @@ +/* eslint-disable @next/next/no-img-element */ + +import { cn } from "@/lib/utils"; + +import React from "react"; + +interface BentoItem { + id: number; + image: string; + title: string; + description: string; + imgClassName: string; + bentoClassName: string; + contentAlign: string; +} + +const bentoItems: BentoItem[] = [ + { + id: 1, + image: "/comp-step-1.png", + title: "Connect your tech stack", + description: + "Easily integrate with your existing tools like AWS, GCP, Azure, GitHub, Slack and more.", + bentoClassName: "row-span-4 border-b border-border/5 md:border-b-0", + contentAlign: "grid items-start", + imgClassName: "order-0 md:order-1", + }, + { + id: 2, + image: "/comp-step-2.png", + title: "Get an instant overview", + description: + "Get real-time insights into security gaps, misconfigurations, and compliance deviations.", + bentoClassName: "col-span-2 row-span-2 border-b border-border/5", + contentAlign: "grid items-start", + imgClassName: "order-0", + }, + { + id: 3, + image: "/comp-step-3.png", + title: "Immediate action", + description: + "Receive immediate actions your company can take to help get you compliant.", + bentoClassName: + "col-span-2 row-span-2 border-b border-border/5 md:border-b-0", + contentAlign: "grid items-start", + imgClassName: "order-0", + }, + { + id: 4, + image: "/comp-step-4.png", + title: "Effortless audit preparation", + description: + "Generate comprehensive reports and evidence logs for auditors with one click.", + bentoClassName: "row-span-4", + contentAlign: "grid items-start", + imgClassName: "order-0 md:order-1", + }, +]; + +export function Section7() { + return ( +
+
+
+

+ How it works +

+ +

+ Get compliant in weeks, not months +

+
+ +
+ {bentoItems.map((item, index) => ( +
+
+ {item.title} +
+

+ STEP {item.id} +

+
+

+ {item.title} +

+

+ {item.description} +

+
+
+
+
+ ))} +
+
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/testimonial-section.tsx b/apps/web/src/app/components/(homepage)/testimonial-section.tsx new file mode 100644 index 0000000000..f190369e57 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/testimonial-section.tsx @@ -0,0 +1,527 @@ +"use client"; + +import { useState } from "react"; +import { motion } from "motion/react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; + +// Testimonial data structure +const testimonials = [ + { + id: 1, + quote: + "Comp AI has completely streamlined our compliance process. Automated audits and real-time monitoring have saved us countless hours, ensuring we stay ahead of regulatory requirements effortlessly.", + author: "Sarah L.", + role: "Compliance Manager", + company: "Epicurious", + }, + { + id: 2, + quote: + "The intuitive interface of Comp AI has made compliance management a breeze. I can easily track our progress and ensure we meet all necessary standards.", + author: "John D.", + role: "Operations Director", + company: "Tech Innovations", + }, + { + id: 3, + quote: + "Thanks to Comp AI, our team can focus on strategic initiatives rather than getting bogged down in compliance paperwork. It's a game changer!", + author: "Emily R.", + role: "Chief Financial Officer", + company: "Finance Corp", + }, + { + id: 4, + quote: + "Comp AI's automated reporting features have drastically reduced the time we spend on audits. I highly recommend it to any compliance team.", + author: "Michael T.", + role: "Audit Manager", + company: "Global Solutions", + }, + { + id: 5, + quote: + "With Comp AI, we have achieved a level of compliance that we never thought possible. The support team is also incredibly responsive and helpful.", + author: "Jessica K.", + role: "Legal Advisor", + company: "Law & Compliance Group", + }, +]; + +export function Testimonials() { + const [currentIndex, setCurrentIndex] = useState(0); + + const handleNext = () => { + setCurrentIndex((prev) => (prev + 1) % testimonials.length); + }; + + const handlePrev = () => { + setCurrentIndex( + (prev) => (prev - 1 + testimonials.length) % testimonials.length + ); + }; + + return ( +
+
+

+ Customer stories +

+
+

+ See how others excel at compliance with Comp AI +

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Vertical lines */} +
+ {Array.from({ length: 50 }, (_, index) => ( +
+ ))} +
+
+ + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Vertical lines */} +
+ {Array.from({ length: 50 }, (_, index) => ( +
+ ))} +
+
+ + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Vertical lines */} +
+ {Array.from({ length: 50 }, (_, index) => ( +
+ ))} +
+
+ + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + +
+ {testimonials[currentIndex].quote + .split(" ") + .map((word, idx, arr) => ( + + {word} + {idx !== arr.length - 1 && " "} + + ))} +
+ +
+ {[ + testimonials[currentIndex].author, + `${testimonials[currentIndex].role} at ${testimonials[currentIndex].company}`, + ].map((text, idx) => ( + + {text} + + ))} +
+
+
+
+
+
+
+
+
+
+
+
+ {/* Vertical lines */} +
+ {Array.from({ length: 50 }, (_, index) => ( +
+ ))} +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ +
+ + +
+
+ ); +} diff --git a/apps/web/src/app/components/(homepage)/waitlist-form.tsx b/apps/web/src/app/components/(homepage)/waitlist-form.tsx new file mode 100644 index 0000000000..329acca773 --- /dev/null +++ b/apps/web/src/app/components/(homepage)/waitlist-form.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { waitlistSchema } from "@/app/actions/schema"; +import { joinWaitlist } from "@/app/actions/waitlist"; +import { Button } from "@bubba/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@bubba/ui/form"; +import { Input } from "@bubba/ui/input"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ArrowRight, Loader2 } from "lucide-react"; +import { useAction } from "next-safe-action/hooks"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import type { z } from "zod"; + +export function WaitlistForm() { + const router = useRouter(); + + const waitlistAction = useAction(joinWaitlist, { + onSuccess: async () => { + toast.success("Thanks, you're on the list!"); + await router.push("/success"); + }, + onError: () => { + toast.error( + "Your email is already on the waitlist, or something went wrong.", + ); + }, + }); + + const form = useForm>({ + resolver: zodResolver(waitlistSchema), + defaultValues: { + email: "", + }, + }); + + const onSubmit = (data: z.infer) => { + waitlistAction.execute({ + email: data.email, + }); + }; + + return ( +
+ + ( + + + + + + + )} + /> + + + + +

+ Join our waitlist to help make 100,000 companies compliant by 2032. +

+ + ); +} \ No newline at end of file diff --git a/apps/web/src/app/components/border-text.tsx b/apps/web/src/app/components/border-text.tsx new file mode 100644 index 0000000000..bcde3a5409 --- /dev/null +++ b/apps/web/src/app/components/border-text.tsx @@ -0,0 +1,27 @@ +import { cn } from "@/lib/utils"; +import type React from "react"; +import { forwardRef } from "react"; + +interface BorderTextProps extends React.HTMLAttributes { + text: string; +} + +export const BorderText = forwardRef( + ({ text, className, ...props }, ref) => { + return ( +
+ +
+ ); + } +); + +BorderText.displayName = "BorderText"; diff --git a/apps/web/src/app/components/compliance-section.tsx b/apps/web/src/app/components/compliance-section.tsx deleted file mode 100644 index 9f829cbb86..0000000000 --- a/apps/web/src/app/components/compliance-section.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Image from "next/image"; -import Balancer from "react-wrap-balancer"; - -interface ComplianceSectionProps { - title: string; - description: string; - image: string; - alt: string; - index?: number; - totalItems?: number; -} - -export default function ComplianceSection({ - title, - description, - image, - alt, - index, - totalItems, -}: ComplianceSectionProps) { - const isMiddleItem = - index !== undefined && - totalItems !== undefined && - index > 0 && - index < totalItems - 1; - - return ( -
-
- {title} -
-

- {title} -

-

- {description} -

-
- ); -} diff --git a/apps/web/src/app/components/cta-section.tsx b/apps/web/src/app/components/cta-section.tsx new file mode 100644 index 0000000000..71bb02fc18 --- /dev/null +++ b/apps/web/src/app/components/cta-section.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { motion } from "motion/react"; +import { WaitlistForm } from "./(homepage)/waitlist-form"; + +export function CTA() { + return ( +
+
+
+
+ +
+ + + +
+
+

+ Built for scale +

+
+

+ Ready to get compliant? +

+

+ Start your compliance journey today and join thousands of companies + building trust through transparent, automated compliance. +

+
+ +
+
+
+
+ ); +} diff --git a/apps/web/src/app/components/cta.tsx b/apps/web/src/app/components/cta.tsx deleted file mode 100644 index a6ee221c8a..0000000000 --- a/apps/web/src/app/components/cta.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import Balancer from "react-wrap-balancer"; -import { WaitlistForm } from "./waitlist-form"; -import { Badge } from "@bubba/ui/badge"; - -export default function CTA() { - return ( -
-
-
-
- -
-
-
- -
-
- - {/* Content layer */} -
-
-
- Built for scale -

- Ready to get compliant? -

-

- - Start your compliance journey today and join thousands of - companies building trust through transparent, automated - compliance. - -

-
- -
- -
-
-
-
- ); -} diff --git a/apps/web/src/app/components/flickering-grid.tsx b/apps/web/src/app/components/flickering-grid.tsx new file mode 100644 index 0000000000..0dcf7b8cd2 --- /dev/null +++ b/apps/web/src/app/components/flickering-grid.tsx @@ -0,0 +1,200 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import type React from "react"; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; + +interface FlickeringGridProps extends React.HTMLAttributes { + squareSize?: number; + gridGap?: number; + flickerChance?: number; + color?: string; + width?: number; + height?: number; + className?: string; + maxOpacity?: number; +} + +export const FlickeringGrid: React.FC = ({ + squareSize = 4, + gridGap = 6, + flickerChance = 0.3, + color = "rgb(0, 0, 0)", + width, + height, + className, + maxOpacity = 0.3, + ...props +}) => { + const canvasRef = useRef(null); + const containerRef = useRef(null); + const [isInView, setIsInView] = useState(false); + const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }); + + const memoizedColor = useMemo(() => { + const toRGBA = (color: string) => { + if (typeof window === "undefined") { + return "rgba(0, 0, 0, 0.3)"; + } + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = 1; + const ctx = canvas.getContext("2d"); + if (!ctx) return "rgba(255, 0, 0, 0.3)"; + ctx.fillStyle = color; + ctx.fillRect(0, 0, 1, 1); + const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data); + return `rgba(${r}, ${g}, ${b},`; + }; + return toRGBA(color); + }, [color]); + + const setupCanvas = useCallback( + (canvas: HTMLCanvasElement, width: number, height: number) => { + const dpr = window.devicePixelRatio || 1; + canvas.width = width * dpr; + canvas.height = height * dpr; + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + const cols = Math.floor(width / (squareSize + gridGap)); + const rows = Math.floor(height / (squareSize + gridGap)); + + const squares = new Float32Array(cols * rows); + for (let i = 0; i < squares.length; i++) { + squares[i] = Math.random() * maxOpacity; + } + + return { cols, rows, squares, dpr }; + }, + [squareSize, gridGap, maxOpacity] + ); + + const updateSquares = useCallback( + (squares: Float32Array, deltaTime: number) => { + for (let i = 0; i < squares.length; i++) { + if (Math.random() < flickerChance * deltaTime) { + squares[i] = Math.random() * maxOpacity; + } + } + }, + [flickerChance, maxOpacity] + ); + + const drawGrid = useCallback( + ( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + cols: number, + rows: number, + squares: Float32Array, + dpr: number + ) => { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = "transparent"; + ctx.fillRect(0, 0, width, height); + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const opacity = squares[i * rows + j]; + ctx.fillStyle = `${memoizedColor}${opacity})`; + ctx.fillRect( + i * (squareSize + gridGap) * dpr, + j * (squareSize + gridGap) * dpr, + squareSize * dpr, + squareSize * dpr + ); + } + } + }, + [memoizedColor, squareSize, gridGap] + ); + + useEffect(() => { + const canvas = canvasRef.current; + const container = containerRef.current; + if (!canvas || !container) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + let animationFrameId: number; + let gridParams: ReturnType; + + const updateCanvasSize = () => { + const newWidth = width || container.clientWidth; + const newHeight = height || container.clientHeight; + setCanvasSize({ width: newWidth, height: newHeight }); + gridParams = setupCanvas(canvas, newWidth, newHeight); + }; + + updateCanvasSize(); + + let lastTime = 0; + const animate = (time: number) => { + if (!isInView) return; + + const deltaTime = (time - lastTime) / 1000; + lastTime = time; + + updateSquares(gridParams.squares, deltaTime); + drawGrid( + ctx, + canvas.width, + canvas.height, + gridParams.cols, + gridParams.rows, + gridParams.squares, + gridParams.dpr + ); + animationFrameId = requestAnimationFrame(animate); + }; + + const resizeObserver = new ResizeObserver(() => { + updateCanvasSize(); + }); + + resizeObserver.observe(container); + + const intersectionObserver = new IntersectionObserver( + ([entry]) => { + setIsInView(entry.isIntersecting); + }, + { threshold: 0 } + ); + + intersectionObserver.observe(canvas); + + if (isInView) { + animationFrameId = requestAnimationFrame(animate); + } + + return () => { + cancelAnimationFrame(animationFrameId); + resizeObserver.disconnect(); + intersectionObserver.disconnect(); + }; + }, [setupCanvas, updateSquares, drawGrid, width, height, isInView]); + + return ( +
+ +
+ ); +}; diff --git a/apps/web/src/app/components/footer.tsx b/apps/web/src/app/components/footer.tsx new file mode 100644 index 0000000000..77ab4f478d --- /dev/null +++ b/apps/web/src/app/components/footer.tsx @@ -0,0 +1,178 @@ +/* eslint-disable @next/next/no-img-element */ + +import Link from "next/link"; + +const socialIcons = [ + { + id: 2, + name: "twitter", + href: "https://x.com/@trycompai", + icon: ( + + + + ), + }, + { + id: 4, + name: "youtube", + href: "https://www.youtube.com/@trycompai", + icon: ( + + + + ), + }, + { + id: 6, + name: "linkedin", + href: "https://www.linkedin.com/company/trycompai", + icon: ( + + + + ), + }, +]; + +const menuItems = [ + { + id: 1, + name: "Home", + href: "/", + }, +]; + +export function Footer() { + return ( +
+
+
+
+
+
+ + + +

CompAI

+
+

+ Start your compliance journey today and join companies building trust through transparent, automated compliance. +

+
+
+
    + {menuItems.map((item) => ( +
  • + {item.name} +
  • + ))} +
+
+
+ {/* second part of the footer */} +
+
+ + + + + + + + + + +

+ 2261 Market Street, San Francisco, California, 94114 +

+
+
+ {socialIcons.map((item) => ( + + {item.icon} + + ))} +
+
+ {/* Third part of the footer */} +
+
+

+ © 2025 CompAI. All rights reserved. +

+
    +
  • + Terms of Services +
  • +
  • + Privacy Policy +
  • +
+
+
+
+ +
+
+
+ ); +} diff --git a/apps/web/src/app/components/header.tsx b/apps/web/src/app/components/header.tsx new file mode 100644 index 0000000000..36a4698793 --- /dev/null +++ b/apps/web/src/app/components/header.tsx @@ -0,0 +1,286 @@ +"use client"; + +import type React from "react"; +import { useState, useEffect } from "react"; +import { Menu, X } from "lucide-react"; +import { motion, AnimatePresence } from "motion/react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +const navItems = [ + { id: 1, label: "Blog", href: "/blog" }, + { id: 2, label: "About Us", href: "/about-us" }, + { id: 3, label: "Case Studies", href: "#" }, + { id: 4, label: "Company", href: "#" }, +]; + +const menuVariants = { + hidden: { opacity: 0, y: -20 }, + visible: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -20 }, +}; + +const menuContainerVariants = { + visible: { + transition: { + staggerChildren: 0.1, + }, + }, +}; + +const overlayVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + exit: { opacity: 0 }, +}; + +const drawerVariants = { + hidden: { + opacity: 0, + y: 100, + rotate: 5, + }, + visible: { + opacity: 1, + y: 0, + rotate: 0, + transition: { + type: "spring", + damping: 15, + stiffness: 200, + staggerChildren: 0.03, + }, + }, + exit: { + opacity: 0, + y: 100, + rotate: 5, + transition: { + duration: 0.1, + }, + }, +}; + +const drawerMenuVariants = { + hidden: { + opacity: 0, + x: -10, + }, + visible: { + opacity: 1, + x: 0, + transition: { + type: "spring", + damping: 20, + stiffness: 300, + }, + }, + exit: { + opacity: 0, + x: -10, + }, +}; + +const drawerMenuContainerVariants = { + hidden: { + opacity: 0, + }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.03, + delayChildren: 0.1, + }, + }, + exit: { + opacity: 0, + }, +}; + +const springTransition = { + type: "spring", + stiffness: 200, + damping: 15, +}; + +const navVariants = { + hidden: { + y: -100, + opacity: 0, + }, + visible: { + y: 0, + opacity: 1, + transition: { + type: "spring", + damping: 12, + stiffness: 100, + }, + }, +}; + +// Add this new variant for header animation +const headerVariants = { + visible: { + y: 0, + transition: { + type: "spring", + stiffness: 200, + damping: 20, + }, + }, + hidden: { + y: "-100%", + transition: { + type: "spring", + stiffness: 200, + damping: 20, + }, + }, +}; + +export default function Header() { + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [isVisible, setIsVisible] = useState(true); + const [prevScrollPos, setPrevScrollPos] = useState(0); + const pathname = usePathname(); + + const toggleDrawer = () => { + setIsDrawerOpen(!isDrawerOpen); + }; + + const handleOverlayClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + setIsDrawerOpen(false); + } + }; + + useEffect(() => { + const handleScroll = () => { + // Close drawer if open while scrolling + if (isDrawerOpen) { + setIsDrawerOpen(false); + } + + // Handle header visibility + const currentScrollPos = window.scrollY; + const isScrollingDown = currentScrollPos > prevScrollPos; + + setIsVisible(!isScrollingDown || currentScrollPos < 10); + setPrevScrollPos(currentScrollPos); + }; + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, [isDrawerOpen, prevScrollPos]); + + const isActivePath = (href: string) => { + if (href === "#") return false; + return pathname === href; + }; + + return ( + + + + + + +

CompAI

+ +
+ + + {isDrawerOpen && ( + <> + + +
+ + + + +

CompAI

+ + + +
+ + + {navItems.map((item) => ( + + + {item.label} + + + ))} + + +
+ + )} +
+
+ ); +} diff --git a/apps/web/src/app/components/how-it-works.tsx b/apps/web/src/app/components/how-it-works.tsx deleted file mode 100644 index d212413919..0000000000 --- a/apps/web/src/app/components/how-it-works.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { Badge } from "@bubba/ui/badge"; -import Balancer from "react-wrap-balancer"; -import Image from "next/image"; -import { cn } from "@bubba/ui/cn"; - -interface WorkflowStepProps { - stepNumber: number; - title: string; - description: string; - image?: string; - imagePosition?: "top" | "bottom"; - className?: string; -} - -function WorkflowStep({ - stepNumber, - title, - description, - image, - imagePosition = "bottom", - className, -}: WorkflowStepProps) { - return ( -
- {imagePosition === "top" && image && ( -
- {title} -
- )} - -
-
- STEP {stepNumber} -
-

{title}

-

{description}

-
- - {imagePosition === "bottom" && image && ( -
- {title} -
- )} - - {/* Image at bottom for small screens only if imagePosition is "bottom" */} - {image && ( -
- {title} -
- )} -
- ); -} - -interface HowItWorksProps { - title?: string; - className?: string; - steps?: WorkflowStepProps[]; -} - -export default function HowItWorks({ - title = "Integrate & Go", - className, - steps = [ - { - stepNumber: 1, - title: "Connect your tech stack", - description: - "Easily integrate with your existing tools like AWS, GCP, Azure, GitHub, Slack and more.", - image: "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/assets/tech-stack.png", - }, - { - stepNumber: 2, - title: "Get an instant overview", - description: - "Get real-time insights into security gaps, misconfigurations, and compliance deviations.", - image: "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/assets/overview.png", - }, - { - stepNumber: 3, - title: "Immediate action", - description: - "Receive immediate actions your company can take to help get you compliant.", - image: "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/assets/upload-evidence.png", - }, - { - stepNumber: 4, - title: "Effortless audit preparation", - description: - "Generate comprehensive reports and evidence logs for auditors with one click.", - image: "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/assets/export-audit.png", - }, - ], -}: HowItWorksProps) { - return ( -
-
-
- - HOW IT WORKS - -

- {title} -

-
- -
-
-
- {steps[0] && ( - - )} -
- -
-
- {steps[1] && ( - - )} - - {steps[1] && ( - - )} -
- - {/* Step 3 - bottom middle */} -
- {steps[2] && ( - - )} - {steps[2] && ( - - )} -
-
- -
- {steps[3] && ( - - )} -
-
-
-
-
- ); -} diff --git a/apps/web/src/app/components/legal/privacy-content.tsx b/apps/web/src/app/components/legal/privacy-content.tsx deleted file mode 100644 index 30086d0f1f..0000000000 --- a/apps/web/src/app/components/legal/privacy-content.tsx +++ /dev/null @@ -1,90 +0,0 @@ -export function PrivacyContent() { - return ( -
-

- Last updated: February 1, 2025 -

- -
-

1. Introduction

-

- At Bubba AI, Inc. ("we," "our," or "us"), we take your privacy - seriously. This Privacy Policy explains how we collect, use, disclose, - and safeguard your information when you use our compliance automation - platform and related services. -

-
- -
-

- 2. Information We Collect -

-

- 2.1 Information You Provide -

-
    -
  • Account information (name, email, company details)
  • -
  • Compliance-related documentation and data
  • -
  • Communication preferences
  • -
  • Customer support interactions
  • -
- -

- 2.2 Automatically Collected Information -

-
    -
  • Usage data and analytics
  • -
  • Device and browser information
  • -
  • IP address and location data
  • -
  • Cookies and similar technologies
  • -
-
- -
-

- 3. How We Use Your Information -

-
    -
  • To provide and improve our services
  • -
  • To maintain security and prevent fraud
  • -
  • To communicate with you about our services
  • -
  • To comply with legal obligations
  • -
-
- -
-

4. Data Security

-

- We implement appropriate technical and organizational measures to - protect your information. However, no method of transmission over the - Internet or electronic storage is 100% secure. -

-
- -
-

5. Your Rights

-

You have the right to:

-
    -
  • Access your personal data
  • -
  • Correct inaccurate data
  • -
  • Request deletion of your data
  • -
  • Object to processing of your data
  • -
  • Data portability
  • -
-
- -
-

6. Contact Us

-

- If you have questions about this Privacy Policy, please contact us at{" "} - - hello@trycomp.ai - -

-
-
- ); -} diff --git a/apps/web/src/app/components/legal/terms-content.tsx b/apps/web/src/app/components/legal/terms-content.tsx deleted file mode 100644 index 8cd7c226ab..0000000000 --- a/apps/web/src/app/components/legal/terms-content.tsx +++ /dev/null @@ -1,101 +0,0 @@ -export function TermsContent() { - return ( -
-

- Last updated: February 1, 2025 -

- -
-

1. Agreement to Terms

-

- By accessing or using Comp AI's services, you agree to be bound by - these Terms of Service and all applicable laws and regulations. If you - do not agree with any of these terms, you are prohibited from using or - accessing our services. -

-
- -
-

2. Use License

-

- Subject to these Terms of Service, we grant you a limited, - non-exclusive, non-transferable license to use our services for your - organization's internal business purposes. -

-

2.1 Restrictions

-

You may not:

-
    -
  • Modify or copy our materials without explicit permission
  • -
  • Use the materials for commercial purposes
  • -
  • Remove any copyright or proprietary notations
  • -
  • Transfer the materials to another person or organization
  • -
-
- -
-

- 3. Service Availability and Support -

-

- We strive to provide uninterrupted service but do not guarantee that - our services will be available at all times. We may occasionally need - to suspend services for maintenance or updates. -

-
- -
-

4. User Obligations

-

You must:

-
    -
  • Provide accurate and complete information
  • -
  • Maintain the security of your account credentials
  • -
  • Comply with all applicable laws and regulations
  • -
  • Use the services in good faith and as intended
  • -
-
- -
-

- 5. Intellectual Property -

-

- The services and all related materials are protected by intellectual - property laws. Our open-source components are licensed under their - respective licenses. -

-
- -
-

- 6. Limitation of Liability -

-

- To the fullest extent permitted by law, Comp AI shall not be liable - for any indirect, incidental, special, consequential, or punitive - damages resulting from your use of our services. -

-
- -
-

7. Changes to Terms

-

- We reserve the right to modify these terms at any time. We will notify - users of any material changes via email or through our services. -

-
- -
-

8. Contact Information

-

- Questions about the Terms of Service should be sent to{" "} - - hello@trycomp.ai - -

-
-
- ); -} diff --git a/apps/web/src/app/components/logo-header.tsx b/apps/web/src/app/components/logo-header.tsx deleted file mode 100644 index 57a7c42e88..0000000000 --- a/apps/web/src/app/components/logo-header.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type * as React from "react"; - -const LogoHeader = (props: React.SVGProps) => ( - - - - -); - -export default LogoHeader; diff --git a/apps/web/src/app/components/logo.tsx b/apps/web/src/app/components/logo.tsx deleted file mode 100644 index 4540363b1b..0000000000 --- a/apps/web/src/app/components/logo.tsx +++ /dev/null @@ -1,23 +0,0 @@ -"use client"; - -import type * as React from "react"; - -const Logo = (props: React.SVGProps) => ( - - - -); - -export default Logo; diff --git a/apps/web/src/app/components/main-nav.tsx b/apps/web/src/app/components/main-nav.tsx deleted file mode 100644 index 326dd10db1..0000000000 --- a/apps/web/src/app/components/main-nav.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import Link from "next/link"; -import LogoHeader from "./logo-header"; - -export function MainNav() { - return ( -
- - - -
- ); -} diff --git a/apps/web/src/app/components/mode-switcher.tsx b/apps/web/src/app/components/mode-switcher.tsx deleted file mode 100644 index 3fc9642b9a..0000000000 --- a/apps/web/src/app/components/mode-switcher.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import { MoonIcon, SunIcon } from "lucide-react"; -import { useTheme } from "next-themes"; -import * as React from "react"; -import { Button } from "@bubba/ui/button"; - -export function ModeSwitcher() { - const { setTheme, resolvedTheme } = useTheme(); - const [mounted, setMounted] = React.useState(false); - - // Mount handler - React.useEffect(() => { - setMounted(true); - }, []); - - const toggleTheme = React.useCallback(() => { - setTheme(resolvedTheme === "dark" ? "light" : "dark"); - }, [resolvedTheme, setTheme]); - - // Prevent hydration mismatch by not rendering until mounted - if (!mounted) { - return ( - - ); - } - - const themeText = resolvedTheme === "dark" - ? "Switch to light theme" - : "Switch to dark theme"; - - return ( - - ); -} diff --git a/apps/web/src/app/components/our-solution.tsx b/apps/web/src/app/components/our-solution.tsx deleted file mode 100644 index 12ec9c6392..0000000000 --- a/apps/web/src/app/components/our-solution.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Badge } from "@bubba/ui/badge"; -import Balancer from "react-wrap-balancer"; -import { Check, X } from "lucide-react"; -import { cn } from "@bubba/ui/cn"; - -interface ProblemSolutionItemProps { - text: string; - isPositive?: boolean; -} - -function ProblemSolutionItem({ - text, - isPositive = false, -}: ProblemSolutionItemProps) { - return ( -
-
- {isPositive ? : } -
-

- {text} -

-
- ); -} - -interface OurSolutionProps { - title?: string; - subtitle?: string; - problems?: Array<{ id: string; text: string }>; - solutions?: Array<{ id: string; text: string }>; - className?: string; -} - -export default function OurSolution({ - title = "Every framework you need to win deals", - subtitle = "Most compliance solutions are closed, costly, and complex. We're building an open-source platform to make SOC 2, ISO 27001, and GDPR accessible and affordable.", - problems = [ - { - id: "fragmented", - text: "Compliance processes are fragmented and require multiple tools.", - }, - { - id: "costly", - text: "Traditional solutions are costly and lack transparency.", - }, - { - id: "time-consuming", - text: "Audits are time-consuming and stressful for teams.", - }, - ], - solutions = [ - { - id: "unified", - text: "Unified Platform: A single dashboard for monitoring, reporting, and compliance automation.", - }, - { - id: "open-source", - text: "Open Source: Full transparency and adaptability with any tech stack.", - }, - { - id: "ai-powered", - text: "AI-Powered Automation: Identify compliance gaps, generate reports, and stay audit-ready effortlessly.", - }, - ], - className, -}: OurSolutionProps) { - return ( -
-
-
- - OUR SOLUTION - -
-
-

- {title} -

-
-
-

{subtitle}

-
-
-
- -
-
-

- The Problem -

-
- {problems.map((problem) => ( - - ))} -
-
- -
-

- Our Solution -

-
- {solutions.map((solution) => ( - - ))} -
-
-
-
-
- ); -} diff --git a/apps/web/src/app/components/page-header.tsx b/apps/web/src/app/components/page-header.tsx deleted file mode 100644 index 4273739fd5..0000000000 --- a/apps/web/src/app/components/page-header.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { cn } from "@/lib/utils"; -import Balancer from "react-wrap-balancer"; - -function PageHeader({ - className, - children, - ...props -}: React.HTMLAttributes) { - return ( -
-
-
- {children} -
-
-
- ); -} - -function PageHeaderHeading({ - className, - ...props -}: React.HTMLAttributes) { - return ( -

- ); -} - -function PageHeaderSubheading({ - className, - ...props -}: React.HTMLAttributes) { - return ( -

- ); -} - -function PageHeaderDescription({ - className, - ...props -}: React.HTMLAttributes) { - return ( -

- ); -} - -function PageActions({ - className, - ...props -}: React.HTMLAttributes) { - return ( -

- ); -} - -export { - PageActions, - PageHeader, - PageHeaderDescription, - PageHeaderHeading, - PageHeaderSubheading, -}; diff --git a/apps/web/src/app/components/pitch/feature-card.tsx b/apps/web/src/app/components/pitch/feature-card.tsx deleted file mode 100644 index 62c874b148..0000000000 --- a/apps/web/src/app/components/pitch/feature-card.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@bubba/ui/card"; -import { motion } from "framer-motion"; -import { Brain, Code, Link, SquareSlash, Store, Workflow } from "lucide-react"; - -const icons = { - Code, - Link, - Brain, - Workflow, - Store, - SquareSlash, -}; - -export function FeatureCard({ - title, - description, - icon, -}: { - title: string; - description: string; - icon: keyof typeof icons; -}) { - const Icon = icons[icon]; - - return ( - - - - - {title} - - -

- {description} -

-
-
-
- ); -} diff --git a/apps/web/src/app/components/pitch/pitch-carousel.tsx b/apps/web/src/app/components/pitch/pitch-carousel.tsx deleted file mode 100644 index a42f414892..0000000000 --- a/apps/web/src/app/components/pitch/pitch-carousel.tsx +++ /dev/null @@ -1,81 +0,0 @@ -"use client"; - -import { - Carousel, - type CarouselApi, - CarouselContent, - CarouselItem, -} from "@bubba/ui/carousel"; -import * as React from "react"; -import { SectionMarket } from "./pitch-market"; -import { SectionMission } from "./pitch-mission"; -import { PitchNavigation } from "./pitch-navigation"; -import { SectionProblem } from "./pitch-problem"; -import { SectionSolution } from "./pitch-solution"; -import { SectionStart } from "./pitch-start"; -import { SectionTeam } from "./pitch-team"; -import { SectionTraction } from "./pitch-traction"; - -const slides = [ - { component: SectionStart, title: "Start" }, - { component: SectionMission, title: "Mission" }, - { component: SectionProblem, title: "The Problem" }, - { component: SectionSolution, title: "The Solution" }, - { component: SectionMarket, title: "Market" }, - { component: SectionTraction, title: "Traction" }, - { component: SectionTeam, title: "Team" }, -]; - -export function PitchCarousel() { - const [api, setApi] = React.useState(); - const [current, setCurrent] = React.useState(0); - - React.useEffect(() => { - if (!api) { - return; - } - - setCurrent(api.selectedScrollSnap()); - - api.on("select", () => { - setCurrent(api.selectedScrollSnap()); - }); - }, [api]); - - return ( - Loading...
}> -
- - - {slides.map(({ component: SlideComponent }, index) => ( - - - - ))} - - - - {/* Navigation Overlay */} -
-
- api?.scrollTo(index)} - /> -
-
-
- - ); -} diff --git a/apps/web/src/app/components/pitch/pitch-market.tsx b/apps/web/src/app/components/pitch/pitch-market.tsx deleted file mode 100644 index f6d86d765c..0000000000 --- a/apps/web/src/app/components/pitch/pitch-market.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@bubba/ui/card"; -import { motion } from "framer-motion"; -import Image from "next/image"; -import Link from "next/link"; - -const marketStats = [ - { - value: "$268B", - text: "Global cybersecurity market size in 2024", - }, - { - value: "$800B", - text: "Projected cybersecurity market size in 2034", - }, - { - value: "12.6%", - text: "CAGR projected through 2034", - }, -]; - -const competitors = [ - { - name: "Vanta", - price: "$10,000", - logo: "https://www.pricelevel.com/images/vendor-logos/vanta.webp", - link: "https://www.pricelevel.com/vendors/vanta/pricing", - }, - { - name: "Drata", - price: "$15,000", - logo: "https://www.pricelevel.com/images/vendor-logos/drata.webp", - link: "https://www.pricelevel.com/vendors/drata/pricing", - }, - { - name: "Secureframe", - price: "$46,000", - logo: "https://www.pricelevel.com/images/vendor-logos/secureframe.webp", - link: "https://www.pricelevel.com/vendors/secureframe/pricing", - }, -]; - -export function SectionMarket() { - return ( -
- - Comp AI - - - - The Market - - - {/* Mobile View - Market Stats */} -
-
- {marketStats.map((stat) => ( -
- - {stat.value} - - {stat.text} -
- ))} -
-
- - {/* Desktop View - Market Stats */} -
- -
    - {marketStats.map((stat) => ( -
  • - - {stat.value} - - {stat.text} -
  • - ))} -
-
-
- - {/* Mobile View - Competitors */} -
-
- {competitors.map((competitor) => ( -
- {competitor.name} -
- - {competitor.name} - -
- - {competitor.price} - {" "} - - median annual spend per customer - -
-
-
- ))} -
-
- - {/* Desktop View - Competitors */} - - {competitors.map((competitor) => ( - - - - - {competitor.name} - - - - {competitor.name} - - - - - {competitor.price} - -
- - median annual price reported by actual buyers. - -
-
- ))} -
-
- ); -} diff --git a/apps/web/src/app/components/pitch/pitch-mission.tsx b/apps/web/src/app/components/pitch/pitch-mission.tsx deleted file mode 100644 index f8dd741fe8..0000000000 --- a/apps/web/src/app/components/pitch/pitch-mission.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { motion } from "framer-motion"; -import Link from "next/link"; - -export function SectionMission() { - return ( -
- - Comp AI - - - - The Mission - - - - Get 100,000 companies SOC 2, ISO 27001, and GDPR compliant by 2032 - - achieved by building the first open source compliance automation - platform. - -
- ); -} diff --git a/apps/web/src/app/components/pitch/pitch-navigation.tsx b/apps/web/src/app/components/pitch/pitch-navigation.tsx deleted file mode 100644 index b8948b4314..0000000000 --- a/apps/web/src/app/components/pitch/pitch-navigation.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { cn } from "@bubba/ui/cn"; - -interface PitchNavigationProps { - totalSlides: number; - currentSlide: number; - onNavigate: (index: number) => void; -} - -export function PitchNavigation({ - totalSlides, - currentSlide, - onNavigate, -}: PitchNavigationProps) { - return ( -
- {Array.from({ length: totalSlides }).map((_, index) => ( -
- ); -} diff --git a/apps/web/src/app/components/pitch/pitch-problem.tsx b/apps/web/src/app/components/pitch/pitch-problem.tsx deleted file mode 100644 index 4953472085..0000000000 --- a/apps/web/src/app/components/pitch/pitch-problem.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { motion } from "framer-motion"; -import Link from "next/link"; - -export function SectionProblem() { - return ( -
- - Comp AI - - - - The Problem - - - - AI has changed how fast startups launch and develop B2B software, we - haven't solved how to sell that same B2B software to companies that - require compliance with cyber security frameworks. - - - {/* Mobile View */} -
-
-
-

- Security Risks -

-
    - - - -
-
- -
-

- Compliance Burden -

-
    - - - -
-
-
-
- - {/* Desktop View */} -
-
-

- Security Risks -

-
- - - -
-
- -
-

- Compliance Burden -

-
- - - -
-
-
- -

- Source:{" "} - - State of Trust Report 2024 - -

-
- ); -} - -function StatItem({ value, text }: { value: string; text: string }) { - return ( -
- - {value} - - {text} -
- ); -} - -function BulletPoint({ value, text }: { value: string; text: string }) { - return ( -
  • - {value} {text} -
  • - ); -} diff --git a/apps/web/src/app/components/pitch/pitch-solution.tsx b/apps/web/src/app/components/pitch/pitch-solution.tsx deleted file mode 100644 index 3b0071d3e7..0000000000 --- a/apps/web/src/app/components/pitch/pitch-solution.tsx +++ /dev/null @@ -1,116 +0,0 @@ -"use client"; - -import { Button } from "@bubba/ui/button"; -import { motion } from "framer-motion"; -import { Brain, Code, Link, SquareSlash, Store, Workflow } from "lucide-react"; -import NextLink from "next/link"; -import { createElement, useState } from "react"; -import { FeatureCard } from "./feature-card"; -import { StatItem } from "./stat-item"; - -const features = [ - { - title: "Open Source", - description: - "Platform designed to automate the most complex compliance frameworks.", - icon: "Code" as const, - }, - { - title: "AI First", - description: - "Comp AI automates huge pain points and reduces the cost of compliance.", - icon: "SquareSlash" as const, - }, - { - title: "Deep Integrations", - description: - "Integrations with the leading HR, Cloud, and Device Management systems.", - icon: "Link" as const, - }, - { - title: "Automated Evidence", - description: - "Evidence collection is automated, so users can focus on more important areas.", - icon: "Workflow" as const, - }, - { - title: "Built-in Marketplace", - description: - "A marketplace of compliance software, training, and auditing services.", - icon: "Store" as const, - }, -]; - -const icons = { - Code, - Link, - Brain, - Workflow, - Store, - SquareSlash, -}; - -export function SectionSolution() { - return ( -
    - - Comp AI - - - - The Solution - - - - Comp AI, the first commercial open-source platform that automates SOC 2, - ISO 27001 and GDPR compliance. - - - {/* Mobile View */} -
    -
    - {features.map((feature) => ( -
    -
    -

    {feature.title}

    -

    - {feature.description} -

    -
    -
    - ))} -
    -
    - - {/* Desktop View */} - - {features.map((feature) => ( - - ))} - -
    - ); -} diff --git a/apps/web/src/app/components/pitch/pitch-start.tsx b/apps/web/src/app/components/pitch/pitch-start.tsx deleted file mode 100644 index 6972f531aa..0000000000 --- a/apps/web/src/app/components/pitch/pitch-start.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import NextLink from "next/link"; - -export function SectionStart() { - return ( -
    -
    - - Pitch/2025 - -
    - -
    -

    - - Comp AI - -

    -
    - -
    -
    - ); -} diff --git a/apps/web/src/app/components/pitch/pitch-team.tsx b/apps/web/src/app/components/pitch/pitch-team.tsx deleted file mode 100644 index 9f014262b4..0000000000 --- a/apps/web/src/app/components/pitch/pitch-team.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { Button } from "@bubba/ui/button"; -import { motion } from "framer-motion"; -import { Github, Linkedin, TwitterIcon } from "lucide-react"; -import Image from "next/image"; -import Link from "next/link"; - -const team = [ - { - name: "Lewis Carhart", - role: "Founder", - x: "https://x.com/lewisbuildsai", - linkedin: "https://www.linkedin.com/in/lewis-carhart-4292b5325/", - github: "/carhartlewis", - image: "https://avatars.githubusercontent.com/u/78215809?v=4", - }, -]; - -export function SectionTeam() { - return ( -
    - - Comp AI - - - - The Team - - - - We're technical at our core. We're a team of experienced compliance - professionals and software engineers who are passionate about automating - compliance. - - - {/* Mobile View */} -
    -
    - {team.map((member) => ( -
    - {member.name} -
    -

    {member.name}

    -

    {member.role}

    -
    - - - - - - -
    -
    -
    - ))} -
    -
    - - {/* Desktop View */} -
    - {team.map((member) => ( -
    - {member.name} -

    {member.name}

    -

    {member.role}

    -
    - - - - - - - - - -
    -
    - ))} -
    - -
    - - To invest, reach out to founders@trycomp.ai. - - -
    - -
    -
    -
    - ); -} diff --git a/apps/web/src/app/components/pitch/pitch-traction.tsx b/apps/web/src/app/components/pitch/pitch-traction.tsx deleted file mode 100644 index 12d4c32f5b..0000000000 --- a/apps/web/src/app/components/pitch/pitch-traction.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { motion } from "framer-motion"; -import Link from "next/link"; - -const businessModels = [ - { - title: "Cloud Hosted SaaS", - description: "Cloud Hosted Software as a Service (SaaS)", - }, - { - title: "Enterprise Features", - description: "Additional features and support for customers", - }, - { - title: "Professional Services", - description: "Optional compliance consulting and audits", - }, - { - title: "Marketplace", - description: - "Affiliate marketplace for partners to sell software, consulting, and compliance services.", - }, -]; - -export function SectionTraction() { - return ( -
    - - Comp AI - - - - Business Model - - - - Comp AI is a commercial open source company. We sell a cloud-hosted - version of Comp AI, with additional features and support. Our plan is to - create an in-app marketplace where users can find compliance software, - training and auditors and we will take a small percentage of the sale. - - - {/* Mobile View */} -
    -
    - {businessModels.map((model) => ( -
    -

    - {model.title} -

    -

    - {model.description} -

    -
    - ))} -
    -
    - - {/* Desktop View */} - -
      - {businessModels.map((model) => ( -
    • -

      - {model.title} -

      -

      - {model.description} -

      -
    • - ))} -
    -
    -
    - ); -} diff --git a/apps/web/src/app/components/pitch/stat-item.tsx b/apps/web/src/app/components/pitch/stat-item.tsx deleted file mode 100644 index a82fd1413f..0000000000 --- a/apps/web/src/app/components/pitch/stat-item.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ScrollArea } from "@bubba/ui/scroll-area"; -import { motion } from "framer-motion"; -import { Activity, Clock, TrendingUp } from "lucide-react"; - -const icons = { - Clock, - Activity, - TrendingUp, -}; - -export function StatItem({ - value, - text, - icon, -}: { - value: string; - text: string; - icon: keyof typeof icons; -}) { - const Icon = icons[icon]; - - return ( - - -

    {value}

    -

    {text}

    -
    - ); -} diff --git a/apps/web/src/app/components/roadmap.tsx b/apps/web/src/app/components/roadmap.tsx new file mode 100644 index 0000000000..8b9132b540 --- /dev/null +++ b/apps/web/src/app/components/roadmap.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { useState } from "react"; +import { FlickeringGrid } from "@/app/components/flickering-grid"; +import { motion } from "motion/react"; +import { cn } from "@/lib/utils"; +import { TabContentOne } from "./tab-one"; + +type ComplianceTab = + | "soc2-type1" + | "soc2-type2" + | "hipaa" + | "iso27001" + | "gdpr" + | "pci-dss"; + +export function ComplianceRoadmap() { + const [activeTab, setActiveTab] = useState("soc2-type1"); + + // Tab configuration + const tabs = [ + { + id: "soc2-type1", + label: "SOC 2 Type I", + content: , + }, + { + id: "soc2-type2", + label: "SOC 2 Type II", + content: ( +
    +

    + Coming Soon +

    +
    + ), + }, + { + id: "hipaa", + label: "HIPAA", + content: ( +
    +

    + Coming Soon +

    +
    + ), + }, + { + id: "iso27001", + label: "ISO 27001", + content: ( +
    +

    + Coming Soon +

    +
    + ), + }, + { + id: "gdpr", + label: "GDPR", + content: ( +
    +

    + Coming Soon +

    +
    + ), + }, + { + id: "pci-dss", + label: "PCI DSS", + content: ( +
    +

    + Coming Soon +

    +
    + ), + }, + ]; + + return ( +
    + {/* Updated Tab navigation with horizontal scroll */} +
    +
    + {tabs.map((tab, index) => ( + + ))} +
    +
    + + {/* Content section */} +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + {tabs.find((tab) => tab.id === activeTab)?.content} +
    +
    + ); +} diff --git a/apps/web/src/app/components/site-footer.tsx b/apps/web/src/app/components/site-footer.tsx deleted file mode 100644 index c6f79ec1de..0000000000 --- a/apps/web/src/app/components/site-footer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Link from "next/link"; - -export function SiteFooter() { - return ( -
    -
    -
      -
    • - - GitHub - -
    • -
    • - - Privacy - -
    • -
    • - - Terms - -
    • -
    -
    -
    - ); -} diff --git a/apps/web/src/app/components/site-header.tsx b/apps/web/src/app/components/site-header.tsx deleted file mode 100644 index 5bee19a3e2..0000000000 --- a/apps/web/src/app/components/site-header.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { MainNav } from "@/app/components/main-nav"; - -export function SiteHeader() { - return ( -
    -
    - -
    -
    - ); -} diff --git a/apps/web/src/app/components/tab-one.tsx b/apps/web/src/app/components/tab-one.tsx new file mode 100644 index 0000000000..a9a0be84e3 --- /dev/null +++ b/apps/web/src/app/components/tab-one.tsx @@ -0,0 +1,119 @@ +import { useRef, useEffect, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; + +export function TabContentOne() { + const containerRef = useRef(null); + const [translateXValues, setTranslateXValues] = useState([]); + + useEffect(() => { + const updateWidth = () => { + if (containerRef.current) { + const containerWidth = + containerRef.current.getBoundingClientRect().width; + const itemWidth = 350; // Width of each item + const numberOfItems = 4; // Total number of items + const totalItemsWidth = itemWidth * numberOfItems; + const availableSpace = containerWidth - totalItemsWidth; + const gap = availableSpace / (numberOfItems - 1); + + const newTranslateXValues = Array.from( + { length: numberOfItems }, + (_, index) => { + return (itemWidth + gap) * index; + } + ); + + setTranslateXValues(newTranslateXValues); + } + }; + + // Update width on initial render + updateWidth(); + + // Add event listener for window resize + window.addEventListener("resize", updateWidth); + + // Cleanup event listener on component unmount + return () => { + window.removeEventListener("resize", updateWidth); + }; + }, []); + + return ( +
    + {/* Container for the cards with proper spacing */} +
    + + {translateXValues.map((translateX, index) => ( + +
    + {index === 3 ? ( + + + + + ) : ( + + + + + )} +

    {index === 3 ? "Done" : "Onboarding"}

    +
    +

    + 30 mins +

    +
    + ))} +
    +
    +
    + ); +} diff --git a/apps/web/src/app/components/waitlist-form.tsx b/apps/web/src/app/components/waitlist-form.tsx deleted file mode 100644 index 4360052b96..0000000000 --- a/apps/web/src/app/components/waitlist-form.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client"; - -import { waitlistSchema } from "@/app/actions/schema"; -import { joinWaitlist } from "@/app/actions/waitlist"; -import { Button } from "@bubba/ui/button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@bubba/ui/form"; -import { Input } from "@bubba/ui/input"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { ArrowRight, Loader2 } from "lucide-react"; -import { useAction } from "next-safe-action/hooks"; -import { useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import type { z } from "zod"; - -export function WaitlistForm() { - const router = useRouter(); - - const waitlistAction = useAction(joinWaitlist, { - onSuccess: async () => { - toast.success("Thanks, you're on the list!"); - await router.push("/success"); - }, - onError: () => { - toast.error( - "Your email is already on the waitlist, or something went wrong.", - ); - }, - }); - - const form = useForm>({ - resolver: zodResolver(waitlistSchema), - defaultValues: { - email: "", - }, - }); - - const onSubmit = (data: z.infer) => { - waitlistAction.execute({ - email: data.email, - }); - }; - - return ( -
    - - ( - - - - - - - )} - /> - - - - -

    - Join our waitlist to help make 100,000 companies compliant by 2032. -

    - - ); -} diff --git a/apps/web/src/app/components/why-us.tsx b/apps/web/src/app/components/why-us.tsx deleted file mode 100644 index 9d40583170..0000000000 --- a/apps/web/src/app/components/why-us.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { Badge } from "@bubba/ui/badge"; -import Balancer from "react-wrap-balancer"; -import ComplianceSection from "./compliance-section"; -import Image from "next/image"; - -const sections = [ - { - title: "Compliance, finally made easy", - description: - "Create your information security program and controls, track, monitor and access risks, and manage your vendors, all from one platform.", - image: "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/assets/compliance-made-easy.png", - alt: "Compliance progress tracking interface", - }, - { - title: "One platform, every framework", - description: - "One platform for frameworks like SOC 2, ISO 27001, and GDPR—streamlined compliance, automated with AI & integrations.", - image: "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/assets/one-platform.png", - alt: "Compliance framework search interface", - }, - { - title: "Collect evidence on autopilot", - description: - "Automated evidence gathering keeps you compliant, while real-time monitoring catches gaps before they become problems.", - image: "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/assets/evidence.png", - alt: "Security shield with automation indicators", - }, -]; - -export default function WhyUs() { - return ( -
    -
    - WHY US -

    - - Everything you need to get
    compliant, fast. -
    -

    -

    - - Transparent, automated, and cost-effective compliance powered by - open source. - -

    -
    - -
    - {sections.map((section, index) => ( - - ))} -
    - -
    -
    - Security and Compliance Certifications -
    -

    - Enterprise platform, low barrier to entry -

    -

    - - Get started with Comp AI in minutes - simply create your account - & integrate your tech stack to get an instant overview, all - without frustrating sales calls and upfront annual contracts. - -

    -
    -
    - -
    - Security and Compliance Certifications -
    -

    - Security & Compliance -

    -

    - - Automates your journey with frameworks like SOC 2, ISO 27001, - and GDPR - from start to finish, and beyond - backed by our open - source community. - -

    -
    -
    -
    -
    - ); -} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000000..1138bd704b --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,163 @@ +@import "tailwindcss"; + +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive-foreground: var(--destructive-foreground); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --pattern-fg: color-mix(in oklab, #000 5%, transparent); +} + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + --pattern-fg: color-mix(in oklab, #000 5%, transparent); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply text-foreground; + scrollbar-width: thin; + scrollbar-color: var(--primary) var(--background); + } + + .grid-background { + position: absolute; + top: -10; + left: 10; + width: 200%; + height: 200%; + display: grid; + rotate: 5deg; + grid-template-columns: repeat(60, 1fr); + grid-template-rows: repeat(60, 1fr); + gap: 1px; + transform: perspective(800px) rotateX(35deg) rotateZ(45deg); + transform-origin: center; + background-color: transparent; + z-index: -1; + } + + .grid-background::before { + content: ""; + height: 150%; + grid-column: 1 / -1; + grid-row: 1 / -1; + background-image: linear-gradient(to right, #ffffff 1px, transparent 1px), + linear-gradient(to bottom, #ffffff 1px, transparent 1px); + background-size: 100px 100px; + position: absolute; + inset: 0; + opacity: 0.1; + z-index: -1; + pointer-events: none; + mask-image: linear-gradient(to bottom, transparent, #000000, transparent); + } +} \ No newline at end of file diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index be178986af..26e5963594 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,6 +1,6 @@ import { GeistMono } from "geist/font/mono"; import { Toaster } from "sonner"; -import "@bubba/ui/globals.css"; +import "./globals.css"; import { Providers } from "@/app/providers"; import { env } from "@/env.mjs"; import { generatePageMeta } from "@/lib/seo"; @@ -8,15 +8,18 @@ import { cn } from "@/lib/utils"; import { GoogleTagManager } from "@next/third-parties/google"; import localFont from "next/font/local"; import { initializeServer } from "@bubba/analytics"; +import { Footer } from "./components/footer"; +import Header from "./components/header"; +import { CTA } from "./components/cta-section"; export const metadata = generatePageMeta({ - url: "/", + url: "/", }); const font = localFont({ - src: "../../public/fonts/GeneralSans-Variable.ttf", - display: "swap", - variable: "--font-general-sans", + src: "../../public/fonts/GeneralSans-Variable.ttf", + display: "swap", + variable: "--font-general-sans", }); if (env.NEXT_PUBLIC_POSTHOG_KEY && env.NEXT_PUBLIC_POSTHOG_HOST) { @@ -29,24 +32,27 @@ if (env.NEXT_PUBLIC_POSTHOG_KEY && env.NEXT_PUBLIC_POSTHOG_HOST) { export const preferredRegion = ["auto"]; export default function RootLayout({ - children, + children, }: { - children: React.ReactNode; + children: React.ReactNode; }) { - return ( - - - - -
    {children}
    -
    - - - - ); + return ( + + + + +
    +
    {children}
    + +