wip: Website Improvements (#8303)

Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
Bruno Bergher 2025-09-25 21:31:04 +01:00 committed by GitHub
parent 8485548f53
commit 7fd01ab2a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 722 additions and 6539 deletions

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -3,7 +3,6 @@
import { getVSCodeDownloads } from "@/lib/stats"
import { Button } from "@/components/ui"
import { AnimatedText } from "@/components/animated-text"
import {
AnimatedBackground,
InstallSection,
@ -12,6 +11,8 @@ import {
FAQSection,
CodeExample,
} from "@/components/homepage"
import { EXTERNAL_LINKS } from "@/lib/constants"
import { ArrowRight } from "lucide-react"
// Invalidate cache when a request comes in, at most once every hour.
export const revalidate = 3600
@ -21,28 +22,18 @@ export default async function Home() {
return (
<>
<section className="relative flex h-[calc(125vh-theme(spacing.12))] items-center overflow-hidden md:h-[calc(100svh-theme(spacing.12))] lg:h-[calc(100vh-theme(spacing.12))]">
<section className="relative flex h-[calc(125vh-theme(spacing.12))] items-center overflow-hidden md:h-[calc(80svh-theme(spacing.12))]">
<AnimatedBackground />
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
<div className="flex flex-col justify-center space-y-6 sm:space-y-8">
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid h-full relative gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
<div className="flex flex-col px-4 justify-center space-y-6 sm:space-y-8">
<div>
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl lg:text-6xl">
<span className="block">Your</span>
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
AI-Powered
</AnimatedText>
<span className="block">Dev Team, in Your Editor</span>
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
and Beyond
</AnimatedText>
<h1 className="text-3xl font-bold tracking-tight mt-8 sm:text-4xl md:text-5xl lg:text-6xl lg:mt-0">
An entire AI-powered dev team. In your editor and beyond.
</h1>
<p className="mt-4 max-w-md text-base text-muted-foreground sm:mt-6 sm:text-lg">
Supercharge your software development with AI that{" "}
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
understands your codebase
</AnimatedText>{" "}
and helps you write, refactor, and debug with ease in your editor and in the cloud.
Roo&apos;s model-agnostic, specialized modes and fine-grained auto-approval controls
give you the tools (and the confidence) to get AI working for you.
</p>
</div>
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
@ -53,18 +44,8 @@ export default async function Home() {
href="https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline"
target="_blank"
className="flex w-full items-center justify-center">
Install Roo Code
<svg
xmlns="http://www.w3.org/2000/svg"
className="ml-2 h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor">
<path
fillRule="evenodd"
d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
Install Extension
<ArrowRight className="ml-2" />
</a>
</Button>
<Button
@ -72,24 +53,24 @@ export default async function Home() {
size="lg"
className="w-full sm:w-auto bg-white/20 dark:bg-white/10 backdrop-blur-sm border border-black/40 dark:border-white/30 hover:border-blue-400 hover:bg-white/30 dark:hover:bg-white/20 hover:shadow-[0_0_20px_rgba(59,130,246,0.5)] transition-all duration-300">
<a
href="https://roocode.com/enterprise"
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
target="_blank"
className="flex w-full items-center justify-center">
For Enterprise
Get started with Cloud
<ArrowRight className="ml-2" />
</a>
</Button>
</div>
</div>
<div className="relative mt-8 flex items-center justify-center lg:mt-0">
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-[250px] w-[250px] rounded-full bg-blue-500/20 blur-[100px] sm:h-[300px] sm:w-[300px] md:h-[350px] md:w-[350px]" />
<div className="relative flex items-center mx-auto h-full mt-8 lg:mt-0">
<div className="flex items-center justify-center">
<CodeExample />
</div>
<CodeExample />
</div>
</div>
</div>
</section>
<div id="features">
<div id="product">
<Features />
</div>
<div id="testimonials">

View file

@ -0,0 +1,328 @@
import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud } from "lucide-react"
import type { Metadata } from "next"
import Link from "next/link"
import { Button } from "@/components/ui"
import { AnimatedBackground } from "@/components/homepage"
import { ContactForm } from "@/components/enterprise/contact-form"
import { SEO } from "@/lib/seo"
import { EXTERNAL_LINKS } from "@/lib/constants"
const TITLE = "Pricing - Roo Code Cloud"
const DESCRIPTION =
"Simple, transparent pricing for Roo Code Cloud. The VS Code extension is free forever. Choose the cloud plan that fits your needs."
const PATH = "/pricing"
const OG_IMAGE = SEO.ogImage
export const metadata: Metadata = {
title: TITLE,
description: DESCRIPTION,
alternates: {
canonical: `${SEO.url}${PATH}`,
},
openGraph: {
title: TITLE,
description: DESCRIPTION,
url: `${SEO.url}${PATH}`,
siteName: SEO.name,
images: [
{
url: OG_IMAGE.url,
width: OG_IMAGE.width,
height: OG_IMAGE.height,
alt: OG_IMAGE.alt,
},
],
locale: SEO.locale,
type: "website",
},
twitter: {
card: SEO.twitterCard,
title: TITLE,
description: DESCRIPTION,
images: [OG_IMAGE.url],
},
keywords: [
...SEO.keywords,
"pricing",
"plans",
"subscription",
"cloud pricing",
"AI development pricing",
"team pricing",
"enterprise pricing",
],
}
interface PricingTier {
name: string
icon: LucideIcon
price: string
period?: string
trial?: string
cancellation?: string
description: string
featuresIntro?: string
features: string[]
cta: {
text: string
href?: string
isContactForm?: boolean
}
}
const pricingTiers: PricingTier[] = [
{
name: "Cloud Free",
icon: Cloud,
price: "$0",
cancellation: "Cancel anytime",
description: "For folks just getting started",
features: [
"Token usage analytics",
"Access your task history across devices",
"Follow your tasks from anywhere",
"Community support",
],
cta: {
text: "Get started",
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP,
},
},
{
name: "Pro",
icon: Star,
price: "$20",
period: "/mo",
trial: "Free 14-day trial · ",
cancellation: "Cancel anytime",
description: "For pro Roo coders",
featuresIntro: "Everything in Free, plus:",
features: [
"Roomote Control",
"Start, stop and control tasks from anywhere",
"Course-correct Roo from afar",
"Paid support",
],
cta: {
text: "Get started",
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
},
},
{
name: "Team",
icon: Users,
price: "$99",
period: "/mo",
trial: "Free 14-day trial · ",
cancellation: "Cancel anytime",
description: "For AI-forward teams",
featuresIntro: "Everything in Pro, plus:",
features: ["Unlimited users (no per-seat cost)", "Shared configuration & policies", "Centralized billing"],
cta: {
text: "Get started",
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
},
},
{
name: "Enterprise",
icon: Building2,
price: "Custom",
description: "For complex orgs",
featuresIntro: "Everything in Team, plus:",
features: [
"SAML SSO provisioning",
"Custom integrations and terms",
"Security questionnaires and all that fun stuff",
"Dedicated support",
],
cta: {
text: "Talk to Sales",
isContactForm: true,
},
},
]
export default function PricingPage() {
return (
<>
<AnimatedBackground />
{/* Hero Section */}
<section className="relative overflow-hidden pt-16 pb-12">
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-5xl font-bold tracking-tight">Roo Code Cloud Pricing</h1>
<p className="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">
Simple, transparent pricing that scales with your needs.
<br />
Free 14-day trials to kick the tires.
</p>
</div>
</div>
</section>
{/* Free Extension Notice */}
<div className="mx-auto max-w-6xl">
<div className="rounded-xl p-4 mb-8 text-center bg-gradient-to-r from-blue-500/10 via-cyan-500/10 to-purple-500/10 border border-blue-500/20 dark:border-white/20">
<p className="text-center">
<strong className="font-semibold">The Roo Code extension is free! </strong>
Roo Code Cloud is an optional service which takes it to the next level.
</p>
</div>
</div>
{/* Pricing Tiers */}
<section className="">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto grid max-w-6xl gap-4 lg:grid-cols-4">
{pricingTiers.map((tier) => {
const Icon = tier.icon
return (
<div
key={tier.name}
className="relative p-6 flex flex-col justify-start bg-background border rounded-2xl transition-all hover:shadow-lg">
<div className="mb-6">
<div className="flex items-center justify-between">
<h3 className="text-2xl font-bold tracking-tight">{tier.name}</h3>
<Icon className="size-6" />
</div>
<p className="text-sm text-muted-foreground">{tier.description}</p>
</div>
<div className="grow mb-8">
<p className="text-sm text-muted-foreground font-light mb-2">
{tier.featuresIntro}&nbsp;
</p>
<ul className="space-y-3 my-0">
{tier.features.map((feature) => (
<li key={feature} className="flex items-start gap-2">
<Check className="mt-0.5 h-4 w-4 text-muted-foreground shrink-0" />
<span className="text-sm">{feature}</span>
</li>
))}
</ul>
</div>
<p className="text-2xl mt-0 mb-1 tracking-tight">
<strong>{tier.price}</strong>
{tier.period}
</p>
<p className="text-xs text-muted-foreground mb-4">
{tier.trial}
{tier.cancellation}&nbsp;
</p>
{tier.cta.isContactForm ? (
<ContactForm
formType="demo"
buttonText={tier.cta.text}
buttonClassName="w-full transition-all duration-300"
/>
) : (
<Button size="lg" className="w-full transition-all duration-300" asChild>
<Link href={tier.cta.href!} className="flex items-center justify-center">
{tier.cta.text}
</Link>
</Button>
)}
</div>
)
})}
</div>
</div>
</section>
{/* Additional Information */}
<section className="bg-background py-16 my-16 border-t border-b relative z-50">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">Frequently Asked Questions</h2>
<p className="mt-4 text-lg text-muted-foreground">Got questions about our pricing?</p>
</div>
<div className="mx-auto mt-12 grid max-w-5xl gap-8 md:grid-cols-2">
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">Wait, is Roo Code free or not?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes! The Roo Code VS Code extension is open source and free forever. The extension acts
as a powerful AI coding assistant right in your editor. These are the prices for Roo
Code Cloud.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">Is there a free trial?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes, all paid plans come with a 14-day free trial.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">Do I need a credit card for the free trial?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes, but you won&apos;t be charged until your trial ends. You can cancel anytime with
one click .
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">What payment methods do you accept?</h3>
<p className="mt-2 text-sm text-muted-foreground">
We accept all major credit cards, debit cards, and can arrange invoice billing for
Enterprise customers.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">Can I change plans anytime?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes, you can upgrade or downgrade your plan at any time. Changes will be reflected in
your next billing cycle.
</p>
</div>
</div>
<div className="mt-12 text-center">
<p className="text-muted-foreground">
Still have questions?{" "}
<a
href={EXTERNAL_LINKS.DISCORD}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
Join our Discord
</a>{" "}
or{" "}
<Link
href="/enterprise#contact"
className="font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
contact our sales team
</Link>
</p>
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-blue-500/5 via-cyan-500/5 to-purple-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/20 dark:bg-gradient-to-br dark:from-gray-800 dark:via-gray-900 dark:to-black sm:p-12">
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">Try Roo Code Cloud now</h2>
<p className="mx-auto mb-8 max-w-2xl text-lg text-muted-foreground">Code from anywhere.</p>
<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<Button
size="lg"
className="bg-black text-white hover:bg-gray-800 hover:shadow-lg hover:shadow-black/20 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:hover:shadow-white/20 transition-all duration-300"
asChild>
<a
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center">
Create a free Cloud account
<ArrowRight className="ml-2 h-4 w-4" />
</a>
</Button>
</div>
</div>
</div>
</section>
</>
)
}

View file

@ -64,43 +64,18 @@ export function Footer() {
<ul className="mt-6 space-y-4">
<li>
<ScrollButton
targetId="features"
targetId="product"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Features
</ScrollButton>
</li>
<li>
<Link
href="/enterprise"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Enterprise
</Link>
</li>
<li>
<a
href={EXTERNAL_LINKS.EVALS}
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Evals
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.SECURITY}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Security
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.INTEGRATIONS}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Integrations
Docs
</a>
</li>
<li>
@ -112,11 +87,45 @@ export function Footer() {
Changelog
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.TESTIMONIALS}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Testimonials
</a>
</li>
<li>
<Link
href="/enterprise"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Enterprise
</Link>
</li>
<li>
<a
href={EXTERNAL_LINKS.SECURITY}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Security Center
</a>
</li>
</ul>
</div>
<div className="mt-10 md:mt-0">
<h3 className="text-sm font-semibold uppercase leading-6 text-foreground">Resources</h3>
<ul className="mt-6 space-y-4">
<li>
<a
href={EXTERNAL_LINKS.EVALS}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Evals
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.FAQ}
@ -126,15 +135,6 @@ export function Footer() {
FAQ
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Docs
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.TUTORIALS}
@ -194,24 +194,6 @@ export function Footer() {
Careers
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.BLOG}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Blog
</a>
</li>
<li>
<a
href={EXTERNAL_LINKS.TESTIMONIALS}
target="_blank"
rel="noopener noreferrer"
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
Testimonials
</a>
</li>
<li>
<Link
href="/terms"

View file

@ -8,12 +8,12 @@ import { useState } from "react"
import { RxGithubLogo } from "react-icons/rx"
import { VscVscode } from "react-icons/vsc"
import { HiMenu } from "react-icons/hi"
import { IoClose } from "react-icons/io5"
import { EXTERNAL_LINKS } from "@/lib/constants"
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
import { ScrollButton } from "@/components/ui"
import ThemeToggle from "@/components/chromes/theme-toggle"
import { ChevronDown, Cloud, X } from "lucide-react"
interface NavBarProps {
stars: string | null
@ -26,56 +26,66 @@ export function NavBar({ stars, downloads }: NavBarProps) {
return (
<header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
<div className="container mx-auto flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
<div className="container flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
<div className="flex items-center">
<Link href="/" className="flex items-center">
<Image src={logoSrc} alt="Roo Code Logo" width={120} height={40} className="h-8 w-auto" />
<Image src={logoSrc} alt="Roo Code Logo" width={130} height={24} className="h-[24px] w-auto" />
</Link>
</div>
{/* Desktop Navigation */}
<nav className="hidden text-sm font-medium md:flex md:items-center md:space-x-3 xl:space-x-8">
{/* note: features and testimonials links are hidden for screen sizes smaller than lg */}
<nav className="grow ml-6 hidden text-sm font-medium md:flex md:items-center">
<ScrollButton
targetId="features"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
Features
targetId="product"
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
Product
</ScrollButton>
<ScrollButton
targetId="testimonials"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
Testimonials
</ScrollButton>
<Link
href="/evals"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
Evals
</Link>
<Link
href="/enterprise"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
Enterprise
</Link>
<a
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
Docs
</a>
<a
href={EXTERNAL_LINKS.DISCORD}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
Community
</a>
<a
href={EXTERNAL_LINKS.CLOUD_APP}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
Cloud
</a>
<Link
href="/pricing"
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
Pricing
</Link>
{/* Resources Dropdown */}
<div className="relative group">
<button className="flex items-center px-4 py-6 gap-1 text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
Resources
<ChevronDown className="size-3" />
</button>
{/* Dropdown Menu */}
<div className="absolute left-0 top-12 mt-2 w-40 rounded-md border border-border bg-background py-1 shadow-lg opacity-0 -translate-y-2 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-200">
<ScrollButton
targetId="faq"
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
FAQ
</ScrollButton>
<Link
href="/evals"
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
Evals
</Link>
<a
href={EXTERNAL_LINKS.DISCORD}
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
Discord
</a>
<a
href={EXTERNAL_LINKS.SECURITY}
target="_blank"
rel="noopener noreferrer"
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Trust Center
</a>
</div>
</div>
</nav>
<div className="hidden md:flex md:items-center md:space-x-4">
@ -92,108 +102,128 @@ export function NavBar({ stars, downloads }: NavBarProps) {
<Link
href={EXTERNAL_LINKS.MARKETPLACE}
target="_blank"
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all duration-200 hover:bg-primary/80 hover:shadow-lg hover:scale-105 md:flex">
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex">
<VscVscode className="-mr-[2px] mt-[1px] h-4 w-4" />
<span>
Install <span className="font-black max-lg:text-xs">&middot;</span>
</span>
{downloads !== null && <span>{downloads}</span>}
</Link>
<a
href={EXTERNAL_LINKS.CLOUD_APP_LOGIN}
target="_blank"
rel="noopener noreferrer"
className="hidden items-center gap-1.5 rounded-md py-2 text-sm border border-primary-background px-4 font-medium text-primary-background transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex">
<Cloud className="inline h-4 w-4" />
Log in
</a>
</div>
{/* Mobile Menu Button */}
<button
aria-expanded={isMenuOpen}
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="flex items-center justify-center rounded-full p-2 transition-colors hover:bg-accent md:hidden"
className="relative z-10 flex items-center justify-center rounded-full p-2 transition-colors hover:bg-accent md:hidden"
aria-label="Toggle mobile menu">
{isMenuOpen ? <IoClose className="h-6 w-6" /> : <HiMenu className="h-6 w-6" />}
<HiMenu className={`h-6 w-6 ${isMenuOpen ? "hidden" : "block"}`} />
<X className={`h-6 w-6 ${isMenuOpen ? "block" : "hidden"}`} />
</button>
</div>
{/* Mobile Menu Panel */}
{/* Mobile Menu Panel - Full Screen */}
<div
className={`absolute left-0 right-0 top-16 z-50 transform border-b border-border bg-background shadow-lg backdrop-blur-none transition-all duration-200 md:hidden ${isMenuOpen ? "translate-y-0 opacity-100" : "pointer-events-none -translate-y-2 opacity-0"}`}>
<nav className="flex flex-col py-2">
<ScrollButton
targetId="features"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Features
</ScrollButton>
<ScrollButton
targetId="testimonials"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Testimonials
</ScrollButton>
<Link
href="/evals"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Evals
</Link>
<Link
href="/enterprise"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Enterprise
</Link>
<a
href={EXTERNAL_LINKS.SECURITY}
target="_blank"
rel="noopener noreferrer"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Security
</a>
<a
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Docs
</a>
<a
href={EXTERNAL_LINKS.DISCORD}
target="_blank"
rel="noopener noreferrer"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Community
</a>
<a
href={EXTERNAL_LINKS.CLOUD_APP}
target="_blank"
rel="noopener noreferrer"
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
Cloud
</a>
<hr className="mx-8 my-2 border-t border-border/50" />
{/* Icons & Stats */}
<div className="flex items-center justify-center gap-8 px-8 py-3">
<Link
href={EXTERNAL_LINKS.GITHUB}
target="_blank"
className="inline-flex items-center gap-2 rounded-md p-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
className={`fixed top-16 left-0 bg-background right-0 z-[100] transition-all duration-200 pointer-events-none md:hidden ${isMenuOpen ? "block h-dvh" : "hidden"}`}>
<nav className="flex flex-col justify-between h-full pb-16 overflow-y-auto bg-background pointer-events-auto">
{/* Main navigation items */}
<div className="grow-1 py-4 font-semibold text-lg">
<ScrollButton
targetId="product"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
<RxGithubLogo className="h-5 w-5" />
{stars !== null && <span>{stars}</span>}
Product
</ScrollButton>
<a
href={EXTERNAL_LINKS.DOCUMENTATION}
target="_blank"
className="block w-full p-5 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Docs
</a>
<Link
href="/pricing"
className="block w-full p-5 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Pricing
</Link>
<div className="flex items-center rounded-md p-2 transition-colors hover:bg-accent">
<ThemeToggle />
{/* Resources Section */}
<div className="mt-4 w-full">
<div className="px-5 pb-2 pt-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
Resources
</div>
<ScrollButton
targetId="faq"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
FAQ
</ScrollButton>
<Link
href="/evals"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Evals
</Link>
<a
href={EXTERNAL_LINKS.DISCORD}
target="_blank"
rel="noopener noreferrer"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Discord
</a>
<a
href={EXTERNAL_LINKS.SECURITY}
target="_blank"
rel="noopener noreferrer"
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
onClick={() => setIsMenuOpen(false)}>
Security Center
</a>
</div>
<Link
href={EXTERNAL_LINKS.MARKETPLACE}
</div>
{/* Bottom section with Cloud Login and stats */}
<div className="border-t border-border">
<div className="flex items-center justify-around px-6 pt-2">
<Link
href={EXTERNAL_LINKS.GITHUB}
target="_blank"
className="inline-flex items-center gap-2 rounded-md p-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
<RxGithubLogo className="h-6 w-6" />
{stars !== null && <span>{stars}</span>}
</Link>
<div className="flex items-center rounded-md p-3 transition-colors hover:bg-accent">
<ThemeToggle />
</div>
<Link
href={EXTERNAL_LINKS.MARKETPLACE}
target="_blank"
className="inline-flex items-center gap-2 rounded-md p-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
onClick={() => setIsMenuOpen(false)}>
<VscVscode className="h-6 w-6" />
{downloads !== null && <span>{downloads}</span>}
</Link>
</div>
<a
href={EXTERNAL_LINKS.CLOUD_APP_LOGIN}
target="_blank"
className="inline-flex items-center gap-2 rounded-md p-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 rounded-lg border border-primary bg-background p-4 mx-4 mb-4 text-base font-semibold text-primary"
onClick={() => setIsMenuOpen(false)}>
<VscVscode className="h-5 w-5" />
{downloads !== null && <span>{downloads}</span>}
</Link>
<Cloud className="h-5 w-5" />
Log in
</a>
</div>
</nav>
</div>

View file

@ -1,113 +0,0 @@
"use client"
import { useEffect, useState, useCallback } from "react"
import useEmblaCarousel from "embla-carousel-react"
import Autoplay from "embla-carousel-autoplay"
import { Button } from "@/components/ui/button"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { features } from "@/components/homepage/features"
export function FeaturesMobile() {
// configure autoplay with Embla
const autoplayPlugin = Autoplay({
delay: 5000,
stopOnInteraction: true,
stopOnMouseEnter: true,
rootNode: (emblaRoot) => emblaRoot,
})
const [emblaRef, emblaApi] = useEmblaCarousel(
{
loop: true,
containScroll: "trimSnaps",
},
[autoplayPlugin],
)
const [selectedIndex, setSelectedIndex] = useState(0)
const [scrollSnaps, setScrollSnaps] = useState<number[]>([])
const scrollTo = useCallback((index: number) => emblaApi && emblaApi.scrollTo(index), [emblaApi])
/* eslint-disable @typescript-eslint/no-explicit-any */
const onInit = useCallback((emblaApi: any) => {
setScrollSnaps(emblaApi.scrollSnapList())
}, [])
/* eslint-disable @typescript-eslint/no-explicit-any */
const onSelect = useCallback((emblaApi: any) => {
setSelectedIndex(emblaApi.selectedScrollSnap())
}, [])
useEffect(() => {
if (!emblaApi) return
onInit(emblaApi)
onSelect(emblaApi)
emblaApi.on("reInit", onInit)
emblaApi.on("select", onSelect)
return () => {
emblaApi.off("reInit", onInit)
emblaApi.off("select", onSelect)
}
}, [emblaApi, onInit, onSelect])
return (
<div className="md:hidden">
<div className="relative px-4">
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex">
{features.map((feature, index) => (
<div className="flex min-w-0 flex-[0_0_100%] px-4" key={index}>
<div className="relative h-full min-h-[280px] rounded-2xl border border-border/50 bg-background/30 p-6 backdrop-blur-xl transition-all duration-300 hover:border-border hover:bg-background/50 dark:hover:border-border/80 dark:hover:bg-background/40">
<div className="mb-2 inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-blue-500/10 to-cyan-500/10 p-2.5 dark:from-blue-500/20 dark:to-cyan-500/20">
<div className="rounded-lg bg-gradient-to-r from-blue-500/80 to-cyan-500/80 p-2.5">
<div className="text-white">{feature.icon}</div>
</div>
</div>
<h3 className="mb-3 text-xl font-medium text-foreground/90">{feature.title}</h3>
<p className="leading-relaxed text-muted-foreground">{feature.description}</p>
</div>
</div>
))}
</div>
</div>
{/* Navigation Controls */}
<div className="mt-6 flex items-center justify-between px-4">
<div className="flex gap-2">
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-full border-border/50 bg-background/80 hover:bg-background"
onClick={() => emblaApi?.scrollPrev()}>
<ChevronLeft className="h-4 w-4 text-foreground/80" />
<span className="sr-only">Previous slide</span>
</Button>
<Button
variant="outline"
size="icon"
className="h-8 w-8 rounded-full border-border/50 bg-background/80 hover:bg-background"
onClick={() => emblaApi?.scrollNext()}>
<ChevronRight className="h-4 w-4 text-foreground/80" />
<span className="sr-only">Next slide</span>
</Button>
</div>
<div className="flex gap-2">
{scrollSnaps.map((_, index) => (
<button
key={index}
type="button"
className={`h-3 w-3 rounded-full border border-border p-0 ${index === selectedIndex ? "bg-foreground" : "bg-background"}`}
onClick={() => scrollTo(index)}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
</div>
</div>
</div>
)
}

View file

@ -1,71 +1,48 @@
"use client"
import { motion } from "framer-motion"
import { Bot, Code, Brain, Wrench, Terminal, Puzzle, Globe, Shield, Zap } from "lucide-react"
import { FeaturesMobile } from "./features-mobile"
import { ReactNode } from "react"
import { Brain, Shield, Users2, ReplaceAll, Keyboard, LucideIcon, CheckCheck } from "lucide-react"
export interface Feature {
icon: ReactNode
icon: LucideIcon
title: string
description: string
}
export const features: Feature[] = [
{
icon: <Bot className="h-6 w-6 text-white" />,
title: "Your AI Dev Team in VS Code",
icon: Users2,
title: "Specialized Modes",
description:
"Roo Code puts a team of agentic AI assistants directly in your editor, with the power to plan, write, and fix code across multiple files.",
"Planning, Architecture, Debugging and beyond: Roo's modes stay on-task and deliver. Create your own modes or download from the marketplace.",
},
{
icon: <Code className="h-6 w-6 text-white" />,
title: "Multiple Specialized Modes",
description:
"From coding to debugging to architecture, Roo Code has a mode for every dev scenario—just switch on the fly.",
icon: ReplaceAll,
title: "Model-Agnostic",
description: "Bring your own model key or use local inference — no markup, lock-in, no restrictions.",
},
{
icon: <Brain className="h-6 w-6 text-white" />,
icon: CheckCheck,
title: "Granular auto-approval",
description: "Control each action and make Roo as autonomous as you want as you build confidence. Or go YOLO.",
},
{
icon: Keyboard,
title: "Highly Customizable",
description:
"Fine-tune settings for Roo to work for you, like inference context, model properties, slash commands and more.",
},
{
icon: Brain,
title: "Deep Project-wide Context",
description:
"Roo Code reads your entire codebase, preserving valid code through diff-based edits for seamless multi-file refactors.",
},
{
icon: <Wrench className="h-6 w-6 text-white" />,
title: "Open-Source and Model-Agnostic",
icon: Shield,
title: "Secure and Private by Design",
description:
"Bring your own model or use local AI—no vendor lock-in. Roo Code is free, open, and adaptable to your needs.",
},
{
icon: <Terminal className="h-6 w-6 text-white" />,
title: "Guarded Command Execution",
description:
"Approve or deny commands as needed. Roo Code automates your dev workflow while keeping oversight firmly in your hands.",
},
{
icon: <Puzzle className="h-6 w-6 text-white" />,
title: "Fully Customizable",
description:
"Create or tweak modes, define usage rules, and shape Roo Code's behavior precisely—your code, your way.",
},
{
icon: <Globe className="h-6 w-6 text-white" />,
title: "Automated Browser Actions",
description:
"Seamlessly test and verify your web app directly from VS Code—Roo Code can open a browser, run checks, and more.",
},
{
icon: <Shield className="h-6 w-6 text-white" />,
title: "Secure by Design",
description:
"Security-first from the ground up, Roo Code meets rigorous standards without slowing you down. Monitoring and strict policies keep your code safe at scale.",
},
{
icon: <Zap className="h-6 w-6 text-white" />,
title: "Seamless Setup and Workflows",
description:
"Get started in minutes—no heavy configs. Roo Code fits alongside your existing tools and dev flow, while supercharging your productivity.",
"Open source and local-first. No code leaves your machine unless you say so. SOC 2 Type II compliant.",
},
]
@ -81,21 +58,6 @@ export function Features() {
},
}
const itemVariants = {
hidden: {
opacity: 0,
y: 20,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: [0.21, 0.45, 0.27, 0.9],
},
},
}
const backgroundVariants = {
hidden: {
opacity: 0,
@ -118,11 +80,11 @@ export function Features() {
viewport={{ once: true }}
variants={backgroundVariants}>
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 dark:bg-blue-700/30 blur-[120px]" />
</div>
</motion.div>
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto mb-24 max-w-3xl text-center">
<div className="mx-auto mb-12 md:mb-24 max-w-4xl text-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -132,40 +94,36 @@ export function Features() {
ease: [0.21, 0.45, 0.27, 0.9],
}}>
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
Powerful features for modern developers.
Power and flexibility to get stuff done.
</h2>
<p className="mt-6 text-lg text-muted-foreground">
Everything you need to build faster and write better code.
The features you need to build, debug and ship faster without compromising quality.
</p>
</motion.div>
</div>
{/* Mobile Carousel */}
<FeaturesMobile />
{/* Desktop Grid */}
<motion.div
className="relative mx-auto hidden max-w-[1200px] md:block"
className="relative mx-auto md:max-w-[1200px]"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
{features.map((feature, index) => (
<motion.div key={index} variants={itemVariants} className="group relative">
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-all duration-500 group-hover:opacity-100 dark:from-blue-500/50 dark:via-cyan-500/50 dark:to-purple-500/50" />
<div className="relative h-full rounded-2xl border border-border/50 bg-background/30 p-8 backdrop-blur-xl transition-all duration-300 hover:border-border hover:bg-background/50 dark:hover:border-border/80 dark:hover:bg-background/40">
<div className="mb-5 inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-blue-500/10 to-cyan-500/10 p-2.5 dark:from-blue-500/20 dark:to-cyan-500/20">
<div className="rounded-lg bg-gradient-to-r from-blue-500/80 to-cyan-500/80 p-2.5">
{feature.icon}
</div>
</div>
<h3 className="mb-3 text-xl font-medium text-foreground/90">{feature.title}</h3>
<p className="leading-relaxed text-muted-foreground">{feature.description}</p>
</div>
</motion.div>
))}
</div>
<ul className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
{features.map((feature, index) => {
const Icon = feature.icon
return (
<li
key={index}
className="relative h-full border border-border rounded-2xl bg-background p-8 transition-all duration-300">
<Icon className="size-6 text-foreground/80" />
<h3 className="mb-3 mt-3 text-xl font-semibold text-foreground">{feature.title}</h3>
<p className="leading-relaxed font-light text-muted-foreground">
{feature.description}
</p>
</li>
)
})}
</ul>
</motion.div>
</div>
</section>

View file

@ -2,9 +2,7 @@ export * from "./animated-background"
export * from "./code-example"
export * from "./company-logos"
export * from "./faq-section"
export * from "./features-mobile"
export * from "./features"
export * from "./install-section"
export * from "./testimonials-mobile"
export * from "./testimonials"
export * from "./whats-new-button"

View file

@ -46,12 +46,13 @@ export function InstallSection({ downloads }: InstallSectionProps) {
<div className="relative text-center">
{/* Updated h2 to match other sections */}
<h2 className="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl">
Install Roo Code Open & Flexible
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-5xl">
Install Roo Code now
</h2>
<p className="mt-6 text-lg text-muted-foreground">
Roo Code is open-source, model-agnostic, and developer-focused. Install from the VS Code
Marketplace or the CLI in minutes, then bring your own AI model.
Install from the VSCode Marketplace or the CLI in minutes, then bring your own AI model.
<br />
Roo Code is also compatible with all VSCode forks.
</p>
<div className="mt-12 flex flex-col items-center justify-center gap-6">
@ -64,7 +65,7 @@ export function InstallSection({ downloads }: InstallSectionProps) {
<div className="relative flex items-center gap-3">
<VscVscode className="h-6 w-6 sm:h-7 sm:w-7" />
<span className="flex flex-wrap items-center gap-2">
<span>VSCode Marketplace</span>
<span>From VSCode Marketplace</span>
{downloads !== null && (
<>
<span className="font-black opacity-60">&middot;</span>
@ -80,7 +81,7 @@ export function InstallSection({ downloads }: InstallSectionProps) {
<div className="absolute -inset-px rounded-xl bg-gradient-to-r from-blue-500/50 via-cyan-500/50 to-purple-500/50 opacity-30 blur-sm transition-all duration-500 group-hover:opacity-60 dark:opacity-40 dark:group-hover:opacity-70" />
<div className="relative overflow-hidden rounded-xl border border-border bg-background/80 shadow-lg backdrop-blur-xl transition-all duration-500 ease-out group-hover:border-blue-500/50 group-hover:shadow-xl group-hover:shadow-blue-500/10 dark:border-border/50 dark:bg-background/60 dark:group-hover:border-blue-400/50">
<div className="border-b border-border/50 bg-muted/30 px-4 py-3 dark:bg-muted/20">
<div className="text-sm font-medium text-foreground">Install via CLI</div>
<div className="text-sm font-medium text-foreground">or via CLI</div>
</div>
<div className="overflow-x-auto bg-background/50 dark:bg-background/30">
<pre className="p-4">

View file

@ -1,63 +0,0 @@
import useEmblaCarousel from "embla-carousel-react"
import AutoScroll from "embla-carousel-auto-scroll"
import { testimonials } from "@/components/homepage/testimonials"
export function TestimonialsMobile() {
const [emblaRef] = useEmblaCarousel({ loop: true }, [
AutoScroll({
playOnInit: true,
speed: 1, // pixels per second - slower for smoother scrolling
stopOnInteraction: true,
stopOnMouseEnter: true,
}),
])
return (
<div className="md:hidden">
<div className="overflow-hidden px-4" ref={emblaRef}>
<div className="flex">
{testimonials.map((testimonial) => (
<div className="min-w-0 flex-[0_0_100%] px-4" key={testimonial.id}>
<div className="relative rounded-2xl border border-border/50 bg-background/30 p-8 backdrop-blur-xl dark:border-border/70 dark:bg-background/40">
<svg
className="absolute left-8 top-8 h-8 w-8 text-blue-500/30 dark:text-blue-400/50"
fill="currentColor"
viewBox="0 0 32 32">
<defs>
<filter id="glow-mobile">
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<path
d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"
className="dark:filter dark:drop-shadow-[0_0_8px_rgba(96,165,250,0.4)]"
/>
</svg>
<blockquote className="mt-12">
<p className="text-lg font-light italic leading-relaxed text-muted-foreground dark:text-foreground/70">
&quot;{testimonial.quote}&quot;
</p>
<footer className="mt-6">
<div className="h-px w-12 bg-gradient-to-r from-blue-500/50 to-transparent dark:from-blue-400/70" />
<p className="mt-4 font-medium text-foreground/90 dark:text-foreground">
{testimonial.name}
</p>
<p className="text-sm text-muted-foreground dark:text-muted-foreground/80">
{testimonial.role} at {testimonial.company}
</p>
</footer>
</blockquote>
</div>
</div>
))}
</div>
</div>
</div>
)
}

View file

@ -1,9 +1,10 @@
"use client"
import { useRef } from "react"
import { useRef, useCallback, useEffect } from "react"
import { motion } from "framer-motion"
import Image from "next/image"
import { TestimonialsMobile } from "./testimonials-mobile"
import useEmblaCarousel from "embla-carousel-react"
import AutoPlay from "embla-carousel-autoplay"
import { ChevronLeft, ChevronRight } from "lucide-react"
export interface Testimonial {
id: number
@ -47,26 +48,66 @@ export const testimonials: Testimonial[] = [
export function Testimonials() {
const containerRef = useRef<HTMLDivElement>(null)
const [emblaRef, emblaApi] = useEmblaCarousel(
{
loop: true,
align: "center",
skipSnaps: false,
containScroll: false,
},
[
AutoPlay({
playOnInit: true,
delay: 4000,
stopOnInteraction: true,
stopOnMouseEnter: true,
stopOnFocusIn: true,
}),
],
)
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev()
}, [emblaApi])
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext()
}, [emblaApi])
// Re-init auto-play on user interaction
useEffect(() => {
if (!emblaApi) return
const autoPlay = emblaApi?.plugins()?.autoPlay as
| {
isPlaying?: () => boolean
play?: () => void
}
| undefined
if (!autoPlay) return
const handleInteraction = () => {
const isPlaying = autoPlay.isPlaying && autoPlay.isPlaying()
if (!isPlaying) {
setTimeout(() => {
if (autoPlay.play) {
autoPlay.play()
}
}, 2000)
}
}
emblaApi.on("pointerUp", handleInteraction)
return () => {
emblaApi.off("pointerUp", handleInteraction)
}
}, [emblaApi])
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.3,
},
},
}
const itemVariants = {
hidden: {
opacity: 0,
y: 20,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: [0.21, 0.45, 0.27, 0.9],
@ -74,123 +115,78 @@ export function Testimonials() {
},
}
const backgroundVariants = {
hidden: {
opacity: 0,
},
visible: {
opacity: 1,
transition: {
duration: 1.2,
ease: "easeOut",
},
},
}
return (
<section ref={containerRef} className="relative overflow-hidden border-t border-border py-32">
<motion.div
className="absolute inset-0"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={backgroundVariants}>
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
</div>
</motion.div>
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-violet-500/10 dark:bg-violet-700/30 blur-[120px]" />
</div>
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto mb-24 max-w-3xl text-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.6,
ease: [0.21, 0.45, 0.27, 0.9],
}}>
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
Empowering developers worldwide.
</h2>
<p className="mt-6 text-lg text-muted-foreground">
Join thousands of developers who are revolutionizing their workflow with AI-powered
assistance.
</p>
</motion.div>
<div className="mx-auto mb-8 max-w-5xl text-center">
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
AI-forward developers are using Roo Code
</h2>
<p className="mt-6 text-lg text-muted-foreground">
Join more than 800k people revolutionizing their workflow worldwide
</p>
</div>
{/* Mobile Carousel */}
<TestimonialsMobile />
{/* Desktop Grid */}
<motion.div
className="relative mx-auto hidden max-w-[1200px] md:block"
className="relative mx-auto max-w-[1400px]"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}>
<div className="relative grid grid-cols-1 gap-12 md:grid-cols-2">
{testimonials.map((testimonial, index) => (
<motion.div
key={testimonial.id}
variants={itemVariants}
className={`group relative ${index % 2 === 0 ? "md:translate-y-4" : "md:translate-y-12"}`}>
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-all duration-500 ease-out group-hover:opacity-100 dark:from-blue-400/40 dark:via-cyan-400/40 dark:to-purple-400/40" />
<div className="relative flex h-full flex-col rounded-2xl border border-border/50 bg-background/30 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-[1.02] group-hover:border-border group-hover:bg-background/40 group-hover:shadow-2xl dark:border-border/70 dark:bg-background/40 dark:group-hover:border-border dark:group-hover:bg-background/60 dark:group-hover:shadow-[0_20px_50px_rgba(59,130,246,0.15)]">
{testimonial.image && (
<div className="absolute -right-3 -top-3 h-16 w-16 overflow-hidden rounded-xl border border-border/50 bg-background/50 p-1.5 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-110 dark:border-border/70 dark:bg-background/60">
<div className="relative h-full w-full overflow-hidden rounded-lg">
<Image
src={testimonial.image || "/placeholder_pfp.png"}
alt={testimonial.name}
fill
className="object-cover"
/>
{/* Previous Button */}
<button
onClick={scrollPrev}
className="absolute left-0 top-1/2 z-20 -translate-y-1/2 rounded-full border border-border/50 bg-background/80 p-2 backdrop-blur-xl transition-all duration-300 hover:scale-110 hover:shadow-lg md:left-4 md:p-3 lg:left-8"
aria-label="Previous testimonial">
<ChevronLeft className="h-5 w-5 text-muted-foreground transition-colors hover:text-foreground md:h-6 md:w-6" />
</button>
{/* Next Button */}
<button
onClick={scrollNext}
className="absolute right-0 top-1/2 z-20 -translate-y-1/2 rounded-full border border-border/50 bg-background/80 p-2 backdrop-blur-xl transition-all duration-300 hover:scale-110 hover:shadow-lg md:right-4 md:p-3 lg:right-8"
aria-label="Next testimonial">
<ChevronRight className="h-5 w-5 text-muted-foreground transition-colors hover:text-foreground md:h-6 md:w-6" />
</button>
{/* Gradient Overlays */}
<div className="absolute inset-y-0 left-0 z-10 w-[10%] bg-gradient-to-r from-background to-transparent pointer-events-none md:w-[15%]" />
<div className="absolute inset-y-0 right-0 z-10 w-[10%] bg-gradient-to-l from-background to-transparent pointer-events-none md:w-[15%]" />
{/* Embla Carousel Container */}
<div className="overflow-hidden" ref={emblaRef}>
<div className="flex">
{testimonials.map((testimonial) => (
<div
key={testimonial.id}
className="relative min-w-0 flex-[0_0_85%] px-2 md:flex-[0_0_70%] md:px-4 lg:flex-[0_0_60%]">
<div className="group relative py-10 h-full">
<div className="relative flex h-full flex-col rounded-2xl border border-border bg-background transition-all duration-500 ease-out group-hover:scale-[1.02] group-hover:border-border group-hover:bg-background/40 group-hover:shadow-xl dark:border-border/70 dark:bg-background/40 dark:group-hover:border-border dark:group-hover:bg-background/60 dark:group-hover:shadow-[0_20px_50px_rgba(59,130,246,0.15)]">
<div className="flex flex-1 flex-col p-6 md:p-8">
<div className="flex-1">
<p className="relative text-sm leading-relaxed text-muted-foreground transition-colors duration-300 group-hover:text-foreground/80 dark:text-foreground/70 dark:group-hover:text-foreground/90 md:text-lg">
{testimonial.quote}
</p>
</div>
<div className="relative mt-4 md:mt-6">
<h3 className="font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
{testimonial.name}
</h3>
<p className="text-sm text-muted-foreground transition-colors duration-300 dark:text-muted-foreground/80">
{testimonial.role} at {testimonial.company}
</p>
</div>
</div>
</div>
)}
<div className="flex flex-1 flex-col p-8">
<div className="flex-1">
<div className="mb-6">
<svg
className="h-8 w-8 text-blue-500/20 transition-all duration-500 group-hover:text-blue-500/30 dark:text-blue-400/40 dark:group-hover:text-blue-400/60"
fill="currentColor"
viewBox="0 0 32 32">
<defs>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<path
d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"
className="dark:filter dark:drop-shadow-[0_0_8px_rgba(96,165,250,0.4)]"
/>
</svg>
</div>
<p className="relative text-lg leading-relaxed text-muted-foreground transition-colors duration-300 group-hover:text-foreground/80 dark:text-foreground/70 dark:group-hover:text-foreground/90">
{testimonial.quote}
</p>
</div>
<div className="relative mt-6">
<div className="mb-4 h-px w-12 bg-gradient-to-r from-blue-500/50 to-transparent transition-all duration-500 group-hover:w-16 group-hover:from-blue-500/70 dark:from-blue-400/70 dark:group-hover:from-blue-400/90" />
<h3 className="font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
{testimonial.name}
</h3>
<p className="text-sm text-muted-foreground transition-colors duration-300 dark:text-muted-foreground/80">
{testimonial.role} at {testimonial.company}
</p>
</div>
</div>
</div>
</motion.div>
))}
))}
</div>
</div>
</motion.div>
</div>

View file

@ -24,7 +24,8 @@ export const EXTERNAL_LINKS = {
OFFICE_HOURS_PODCAST: "https://www.youtube.com/@RooCodeYT/podcasts",
FAQ: "https://roocode.com/#faq",
TESTIMONIALS: "https://roocode.com/#testimonials",
CLOUD_APP: "https://app.roocode.com",
CLOUD_APP_LOGIN: "https://app.roocode.com/sign-in",
CLOUD_APP_SIGNUP: "https://app.roocode.com/sign-up",
}
export const INTERNAL_LINKS = {