diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 45d2f68be1..a7c590fca9 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -6,6 +6,7 @@
"esbenp.prettier-vscode",
"csstools.postcss",
"bradlc.vscode-tailwindcss",
- "connor4312.esbuild-problem-matchers"
+ "connor4312.esbuild-problem-matchers",
+ "yoavbls.pretty-ts-errors"
]
}
diff --git a/apps/website/.env.example b/apps/website/.env.example
new file mode 100644
index 0000000000..9e3e927dd2
--- /dev/null
+++ b/apps/website/.env.example
@@ -0,0 +1,11 @@
+# PostHog Analytics Configuration
+# Replace these values with your actual PostHog API key and host
+NEXT_PUBLIC_POSTHOG_KEY=your_posthog_api_key_here
+NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
+
+# Basin Form Endpoint for Static Form Submissions
+# Replace this with your actual Basin form endpoint (e.g., https://usebasin.com/f/your-form-id)
+NEXT_PUBLIC_BASIN_ENDPOINT=https://usebasin.com/f/your-form-id-here
+
+TURSO_CONNECTION_URL=libsql://development-roo-code.aws-us-east-1.turso.io
+TURSO_AUTH_TOKEN=your-auth-token-here
diff --git a/apps/website/.gitignore b/apps/website/.gitignore
new file mode 100644
index 0000000000..7b8da95f5e
--- /dev/null
+++ b/apps/website/.gitignore
@@ -0,0 +1,42 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+!.env.example
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/apps/website/components.json b/apps/website/components.json
new file mode 100644
index 0000000000..87c8a91faa
--- /dev/null
+++ b/apps/website/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/app/globals.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/apps/website/drizzle.config.ts b/apps/website/drizzle.config.ts
new file mode 100644
index 0000000000..0ebce84cbd
--- /dev/null
+++ b/apps/website/drizzle.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from "drizzle-kit"
+
+const dialect = process.env.BENCHMARKS_DB_PATH ? "sqlite" : "turso"
+
+const dbCredentials = process.env.BENCHMARKS_DB_PATH
+ ? { url: process.env.BENCHMARKS_DB_PATH }
+ : { url: process.env.TURSO_CONNECTION_URL!, authToken: process.env.TURSO_AUTH_TOKEN! }
+
+export default defineConfig({
+ out: "./drizzle",
+ schema: "./src/db/schema.ts",
+ dialect,
+ dbCredentials,
+})
diff --git a/apps/website/eslint.config.mjs b/apps/website/eslint.config.mjs
new file mode 100644
index 0000000000..9668cb3bae
--- /dev/null
+++ b/apps/website/eslint.config.mjs
@@ -0,0 +1,4 @@
+import { nextJsConfig } from "@roo-code/config-eslint/next-js"
+
+/** @type {import("eslint").Linter.Config} */
+export default [...nextJsConfig]
diff --git a/apps/website/next.config.ts b/apps/website/next.config.ts
new file mode 100644
index 0000000000..27f71d6b57
--- /dev/null
+++ b/apps/website/next.config.ts
@@ -0,0 +1,28 @@
+import type { NextConfig } from "next"
+
+const nextConfig: NextConfig = {
+ webpack: (config) => {
+ config.resolve.extensionAlias = { ".js": [".ts", ".tsx", ".js", ".jsx"] }
+ return config
+ },
+ async redirects() {
+ return [
+ // Redirect www to non-www
+ {
+ source: "/:path*",
+ has: [{ type: "host", value: "www.roocode.com" }],
+ destination: "https://roocode.com/:path*",
+ permanent: true,
+ },
+ // Redirect HTTP to HTTPS
+ {
+ source: "/:path*",
+ has: [{ type: "header", key: "x-forwarded-proto", value: "http" }],
+ destination: "https://roocode.com/:path*",
+ permanent: true,
+ },
+ ]
+ },
+}
+
+export default nextConfig
diff --git a/apps/website/package.json b/apps/website/package.json
new file mode 100644
index 0000000000..d277811287
--- /dev/null
+++ b/apps/website/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@roo-code/website",
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "lint": "next lint",
+ "check-types": "tsc --noEmit",
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "drizzle-kit": "dotenvx run -f .env -- tsx node_modules/drizzle-kit/bin.cjs",
+ "db:generate": "pnpm drizzle-kit generate",
+ "db:migrate": "pnpm drizzle-kit migrate",
+ "db:push": "pnpm drizzle-kit push",
+ "db:pull": "pnpm drizzle-kit pull",
+ "db:check": "pnpm drizzle-kit check",
+ "db:up": "pnpm drizzle-kit up",
+ "db:studio": "pnpm drizzle-kit studio"
+ },
+ "dependencies": {
+ "@libsql/client": "^0.15.7",
+ "@radix-ui/react-dialog": "^1.1.14",
+ "@radix-ui/react-slot": "^1.2.3",
+ "@roo-code/types": "workspace:^",
+ "@tanstack/react-query": "^5.79.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "drizzle-orm": "^0.41.0",
+ "drizzle-zod": "^0.7.1",
+ "embla-carousel-auto-scroll": "^8.6.0",
+ "embla-carousel-autoplay": "^8.6.0",
+ "embla-carousel-react": "^8.6.0",
+ "framer-motion": "^12.15.0",
+ "lucide-react": "^0.479.0",
+ "next": "15.2.4",
+ "next-themes": "^0.4.6",
+ "posthog-js": "^1.248.1",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.5.0",
+ "recharts": "^2.15.3",
+ "tailwind-merge": "^3.3.0",
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.25.41"
+ },
+ "devDependencies": {
+ "@roo-code/config-eslint": "workspace:^",
+ "@roo-code/config-typescript": "workspace:^",
+ "@tailwindcss/typography": "^0.5.16",
+ "@types/node": "^20.17.54",
+ "@types/react": "^18.3.23",
+ "@types/react-dom": "^18.3.7",
+ "autoprefixer": "^10.4.21",
+ "drizzle-kit": "^0.30.6",
+ "postcss": "^8.5.4",
+ "tailwindcss": "^3.4.17"
+ }
+}
diff --git a/apps/website/postcss.config.cjs b/apps/website/postcss.config.cjs
new file mode 100644
index 0000000000..33ad091d26
--- /dev/null
+++ b/apps/website/postcss.config.cjs
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/apps/website/public/Roo-Code-Logo-Horiz-blk.svg b/apps/website/public/Roo-Code-Logo-Horiz-blk.svg
new file mode 100644
index 0000000000..5be021c28f
--- /dev/null
+++ b/apps/website/public/Roo-Code-Logo-Horiz-blk.svg
@@ -0,0 +1,2971 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/website/public/Roo-Code-Logo-Horiz-white.svg b/apps/website/public/Roo-Code-Logo-Horiz-white.svg
new file mode 100644
index 0000000000..89a15f16d4
--- /dev/null
+++ b/apps/website/public/Roo-Code-Logo-Horiz-white.svg
@@ -0,0 +1,2965 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/website/public/android-chrome-192x192.png b/apps/website/public/android-chrome-192x192.png
new file mode 100644
index 0000000000..04363a3e85
Binary files /dev/null and b/apps/website/public/android-chrome-192x192.png differ
diff --git a/apps/website/public/android-chrome-512x512.png b/apps/website/public/android-chrome-512x512.png
new file mode 100644
index 0000000000..582c0b7d16
Binary files /dev/null and b/apps/website/public/android-chrome-512x512.png differ
diff --git a/apps/website/public/apple-touch-icon.png b/apps/website/public/apple-touch-icon.png
new file mode 100644
index 0000000000..5dfc8723e8
Binary files /dev/null and b/apps/website/public/apple-touch-icon.png differ
diff --git a/apps/website/public/favicon-16x16.png b/apps/website/public/favicon-16x16.png
new file mode 100644
index 0000000000..33f1ed8ef2
Binary files /dev/null and b/apps/website/public/favicon-16x16.png differ
diff --git a/apps/website/public/favicon-32x32.png b/apps/website/public/favicon-32x32.png
new file mode 100644
index 0000000000..526f8317de
Binary files /dev/null and b/apps/website/public/favicon-32x32.png differ
diff --git a/apps/website/public/favicon.ico b/apps/website/public/favicon.ico
new file mode 100644
index 0000000000..b739db10d5
Binary files /dev/null and b/apps/website/public/favicon.ico differ
diff --git a/apps/website/public/placeholder.svg b/apps/website/public/placeholder.svg
new file mode 100644
index 0000000000..e763910b27
--- /dev/null
+++ b/apps/website/public/placeholder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/website/public/placeholder_pfp.png b/apps/website/public/placeholder_pfp.png
new file mode 100644
index 0000000000..f8b690afba
Binary files /dev/null and b/apps/website/public/placeholder_pfp.png differ
diff --git a/apps/website/src/app/enterprise/page.tsx b/apps/website/src/app/enterprise/page.tsx
new file mode 100644
index 0000000000..d5eab2af4d
--- /dev/null
+++ b/apps/website/src/app/enterprise/page.tsx
@@ -0,0 +1,427 @@
+import { Code, CheckCircle, Shield, Users, Zap, Workflow } from "lucide-react"
+
+import { Button } from "@/components/ui"
+import { AnimatedText } from "@/components/animated-text"
+import { AnimatedBackground } from "@/components/homepage"
+import { ContactForm } from "@/components/enterprise/contact-form"
+
+export default async function Enterprise() {
+ return (
+ <>
+ {/* Hero Section */}
+
+
+
+
+
+
+
+ Roo Code for
+
+ Enterprise
+
+
+
+ A next-generation, AI-powered{" "}
+
+ coding partner
+ {" "}
+ for enterprise development teams.
+
+
+
+
+
+
+
+
+
+
Roo Code Enterprise
+
+
+ An AI extension of your team that handles coding tasks, from new code generation to
+ refactoring, bug fixing, and documentation.
+
+
+
+
+ Accelerate development cycles
+
+
+
+ Enterprise-grade security
+
+
+
+ Custom-tailored to your workflow
+
+
+
+ Improve collaboration and onboarding
+
+
+
+
+
+
+
+
+ {/* Key Messaging Sections */}
+
+
+
+
Empower Your Development Team
+
+ Roo Code functions like an entire AI dev team embedded in your developers' IDE, ready
+ to accelerate software delivery and improve code quality.
+
+
+
+
+ {/* Card 1 */}
+
+
+
+
+
Accelerate Development Cycles
+
+ Supercharge development with AI assistance that helps developers code faster while
+ maintaining quality.
+
+
+
+
+ Faster time-to-market
+
+
+
+ AI pair-programming
+
+
+
+ Improved code quality
+
+
+
+
+ {/* Card 2 */}
+
+
+
+
+
Augment Your Team with AI Agents
+
+ Roo Code functions like an AI extension of your team, handling various coding tasks.
+
+
+
+
+ New code generation
+
+
+
+ Refactoring and bug fixing
+
+
+
+ Automate complex migrations
+
+
+
+
+ {/* Card 3 */}
+
+
+
+
+
Enterprise-Grade Security
+
+ Keep your data private with on-premises models, keeping proprietary code in-house.
+
+
+
+
+ Security and compliance
+
+
+
+ No external cloud dependencies
+
+
+
+ Open-source and extensible
+
+
+
+
+ {/* Card 4 */}
+
+
+
+
+
Custom-Tailored to Your Workflow
+
+ Developers can create Custom Modes for specialized tasks like security auditing or
+ performance tuning.
+
+
+
+
+ Integrate with internal tools
+
+
+
+ Adapt to existing workflows
+
+
+
+ Custom AI behaviors
+
+
+
+
+ {/* Card 5 */}
+
+
+
+
+
Collaboration and Onboarding
+
+ Ask Mode enables developers to query their codebase in plain language and receive
+ instant answers.
+
+
+
+
+ Accelerates onboarding
+
+
+
+ Improves cross-team collaboration
+
+
+
+ Makes code more accessible
+
+
+
+
+ {/* Card 6 */}
+
+
+
+
+
Faster Delivery, Lower Costs
+
+ Automate routine tasks to accelerate software releases and reduce costs.
+
+
+
+
+ Improved code quality & consistency
+
+
+
+ Empowered developers, happier teams
+
+
+
+ Rapid knowledge sharing
+
+
+
+
+
+
+
+ {/* Differentiator Section */}
+
+
+
+
What Makes Roo Code Unique
+
+ Unlike traditional code editors or basic autocomplete tools, Roo Code is an autonomous
+ coding agent with powerful capabilities.
+
+
+
+
+
+
Traditional AI Coding Assistants
+
+
+
+
+
+ Limited to autocomplete and simple suggestions
+
+
+
+
+
+ Lack project-wide context understanding
+
+
+
+
+
+ Can't execute commands or perform web actions
+
+
+
+
+
+ No customization for enterprise workflows
+
+
+
+
+
+ Often require sending code to external cloud services
+
+
+
+
+
+
Roo Code Enterprise
+
+
+
+ Full-featured AI dev team with natural language communication
+
+
+
+ Deep understanding of your entire codebase
+
+
+
+ Can run tests, execute commands, and perform web actions
+
+
+
+ Custom modes for specialized enterprise tasks
+
+
+
+ On-premises deployment option for data privacy
+
+
+
+
+
+
+
+ {/* CTA Section */}
+
+ >
+ )
+}
diff --git a/apps/website/src/app/evals/evals.tsx b/apps/website/src/app/evals/evals.tsx
new file mode 100644
index 0000000000..4fada39f46
--- /dev/null
+++ b/apps/website/src/app/evals/evals.tsx
@@ -0,0 +1,224 @@
+"use client"
+
+import { useMemo } from "react"
+import { ScatterChart, Scatter, XAxis, YAxis, Label, Customized, Cross } from "recharts"
+
+import { TaskMetrics, type Run } from "@/db"
+
+import { ChartConfig, ChartLegend, ChartLegendContent } from "@/components/ui/chart"
+import { formatTokens, formatCurrency, formatDuration, formatScore } from "@/lib"
+import {
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui"
+import { useOpenRouterModels } from "@/lib/hooks/use-open-router-models"
+
+export function Evals({
+ runs,
+}: {
+ runs: (Run & {
+ label: string
+ score: number
+ languageScores?: Record<"go" | "java" | "javascript" | "python" | "rust", number>
+ taskMetrics: TaskMetrics
+ modelId?: string
+ })[]
+}) {
+ const { data: openRouterModels } = useOpenRouterModels()
+
+ const tableData = useMemo(
+ () =>
+ runs.map((run) => ({
+ ...run,
+ label: run.description || run.model,
+ score: run.score,
+ cost: run.taskMetrics.cost,
+ model: openRouterModels?.[run.modelId ?? ""],
+ modelInfo: openRouterModels?.[run.modelId ?? ""]?.modelInfo,
+ })),
+ [runs, openRouterModels],
+ )
+
+ const chartData = useMemo(() => tableData.filter(({ cost }) => cost < 100), [tableData])
+
+ const chartConfig = useMemo(
+ () => chartData.reduce((acc, run) => ({ ...acc, [run.label]: run }), {} as ChartConfig),
+ [chartData],
+ )
+
+ return (
+
+
+
+ Roo Code tests each frontier model against{" "}
+
+ a suite of hundreds of exercises
+ {" "}
+ across 5 programming languages with varying difficulty. These results can help you find the right
+ price-to-intelligence ratio for your use case.
+
+
+ Want to see the results for a model we haven't tested yet? Ping us in{" "}
+
+ Discord
+
+ .
+
+
+
+
+
+
+ Model
+
+
+ Metrics
+
+
+ Scores
+
+
+
+
+ Name
+ Context Window
+
+
+ Price
+ In / Out
+
+ Duration
+
+ Tokens
+ In / Out
+
+
+ Cost
+ USD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total
+
+
+
+ {tableData.map((run) => (
+
+
+ {run.model?.name || run.label}
+
+ {formatTokens(run.modelInfo?.contextWindow ?? 0)}
+
+
+
+
+
{formatCurrency(run.modelInfo?.inputPrice ?? 0)}
+
/
+
{formatCurrency(run.modelInfo?.outputPrice ?? 0)}
+
+
+ {formatDuration(run.taskMetrics.duration)}
+
+
+
{formatTokens(run.taskMetrics.tokensIn)}
+
/
+
{formatTokens(run.taskMetrics.tokensOut)}
+
+
+ {formatCurrency(run.taskMetrics.cost)}
+
+ {formatScore(run.languageScores?.go ?? 0)}%
+
+
+ {formatScore(run.languageScores?.java ?? 0)}%
+
+
+ {formatScore(run.languageScores?.javascript ?? 0)}%
+
+
+ {formatScore(run.languageScores?.python ?? 0)}%
+
+
+ {formatScore(run.languageScores?.rust ?? 0)}%
+
+ {run.score}%
+
+ ))}
+
+
+ Cost Versus Score
+
+
+ Math.round((dataMin - 5) / 5) * 5,
+ (dataMax: number) => Math.round((dataMax + 5) / 5) * 5,
+ ]}
+ tickFormatter={(value) => formatCurrency(value)}>
+
+
+ Math.max(0, Math.round((dataMin - 5) / 5) * 5),
+ (dataMax: number) => Math.min(100, Math.round((dataMax + 5) / 5) * 5),
+ ]}
+ tickFormatter={(value) => `${value}%`}>
+
+
+ } />
+
+ {chartData.map((d, i) => (
+
+ ))}
+ } />
+
+
+
+ (Note: Very expensive models are exluded from the scatter plot.)
+
+
+
+
+ )
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const renderQuadrant = (props: any) => (
+
+)
diff --git a/apps/website/src/app/evals/page.tsx b/apps/website/src/app/evals/page.tsx
new file mode 100644
index 0000000000..8171292544
--- /dev/null
+++ b/apps/website/src/app/evals/page.tsx
@@ -0,0 +1,49 @@
+import type { Metadata } from "next"
+
+import { rooCodeSettingsSchema, getModelId } from "@roo-code/types"
+
+import { getRuns } from "@/db"
+import { getLanguageScores } from "@/lib/server"
+import { formatScore } from "@/lib"
+
+import { Evals } from "./evals"
+
+export const revalidate = 300
+
+export const metadata: Metadata = {
+ title: "Roo Code Evals",
+ openGraph: {
+ title: "Roo Code Evals",
+ description: "Quantitative evals of LLM coding skills.",
+ url: "https://roocode.com/evals",
+ siteName: "Roo Code",
+ images: {
+ url: "https://i.imgur.com/ijP7aZm.png",
+ width: 1954,
+ height: 1088,
+ },
+ },
+}
+
+export default async function Page() {
+ const languageScores = await getLanguageScores()
+
+ const runs = (await getRuns())
+ .filter((run) => !!run.taskMetrics)
+ .filter(({ settings }) => rooCodeSettingsSchema.safeParse(settings).success)
+ .sort((a, b) => b.passed - a.passed)
+ .map((run) => {
+ const settings = rooCodeSettingsSchema.parse(run.settings)
+
+ return {
+ ...run,
+ label: run.description || run.model,
+ score: formatScore(run.passed / (run.passed + run.failed)),
+ languageScores: languageScores[run.id],
+ taskMetrics: run.taskMetrics!,
+ modelId: getModelId(settings),
+ }
+ })
+
+ return
+}
diff --git a/apps/website/src/app/globals.css b/apps/website/src/app/globals.css
new file mode 100644
index 0000000000..e7a3a03e9d
--- /dev/null
+++ b/apps/website/src/app/globals.css
@@ -0,0 +1,72 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --radius: 0.5rem;
+
+ --chart-1: 0 100% 50%;
+ --chart-2: 29 100% 50%;
+ --chart-3: 51 100% 50%;
+ --chart-4: 83 100% 50%;
+ --chart-5: 115 100% 50%;
+ --chart-6: 147 100% 50%;
+ --chart-7: 168 100% 50%;
+ --chart-8: 196 100% 50%;
+ --chart-9: 224 100% 50%;
+ --chart-10: 261 100% 50%;
+ --chart-11: 279 100% 50%;
+ --chart-12: 298 100% 50%;
+ --chart-13: 324 100% 50%;
+ --chart-14: 358 100% 50%;
+}
+
+.dark {
+ --background: 0 0% 0%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/apps/website/src/app/layout.tsx b/apps/website/src/app/layout.tsx
new file mode 100644
index 0000000000..23d67ea48f
--- /dev/null
+++ b/apps/website/src/app/layout.tsx
@@ -0,0 +1,65 @@
+import React from "react"
+import type { Metadata } from "next"
+import { Inter } from "next/font/google"
+
+import { Providers } from "@/components/providers"
+
+import Shell from "./shell"
+
+import "./globals.css"
+
+const inter = Inter({ subsets: ["latin"] })
+
+export const metadata: Metadata = {
+ title: "Roo Code – Your AI-Powered Dev Team in VS Code",
+ description:
+ "Roo Code puts an entire AI dev team right in your editor, outpacing closed tools with deep project-wide context, multi-step agentic coding, and unmatched developer-centric flexibility.",
+ alternates: {
+ canonical: "https://roocode.com",
+ },
+ icons: {
+ icon: [
+ { url: "/favicon.ico" },
+ { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
+ { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
+ ],
+ apple: [{ url: "/apple-touch-icon.png" }],
+ other: [
+ {
+ rel: "android-chrome-192x192",
+ url: "/android-chrome-192x192.png",
+ sizes: "192x192",
+ type: "image/png",
+ },
+ {
+ rel: "android-chrome-512x512",
+ url: "/android-chrome-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ },
+ ],
+ },
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx
new file mode 100644
index 0000000000..971332b105
--- /dev/null
+++ b/apps/website/src/app/page.tsx
@@ -0,0 +1,98 @@
+/* eslint-disable react/jsx-no-target-blank */
+
+import { getVSCodeDownloads } from "@/lib/stats"
+
+import { Button } from "@/components/ui"
+import { AnimatedText } from "@/components/animated-text"
+import {
+ AnimatedBackground,
+ InstallSection,
+ Features,
+ Testimonials,
+ FAQSection,
+ CodeExample,
+} from "@/components/homepage"
+
+// Invalidate cache when a request comes in, at most once every hour.
+export const revalidate = 3600
+
+export default async function Home() {
+ const downloads = await getVSCodeDownloads()
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Your
+
+ AI-Powered
+
+ Dev Team, Right in Your Editor.
+
+
+ Supercharge your editor with AI that{" "}
+
+ understands your codebase
+
+ , streamlines development, and helps you write, refactor, and debug with ease.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/apps/website/src/app/privacy/page.tsx b/apps/website/src/app/privacy/page.tsx
new file mode 100644
index 0000000000..7db1bbd17b
--- /dev/null
+++ b/apps/website/src/app/privacy/page.tsx
@@ -0,0 +1,177 @@
+import { Metadata } from "next"
+
+export const metadata: Metadata = {
+ title: "Privacy Policy - Roo Code Marketing Website",
+ description:
+ "Privacy policy for the Roo Code marketing website. Learn how we handle your data and protect your privacy.",
+}
+
+export default function Privacy() {
+ return (
+ <>
+
+
+
+ Roo Code Marketing Landing Page Privacy Policy
+
+
Last Updated: March 7th, 2025
+
+
+ Roo Code respects your privacy and is committed to being transparent about how data is collected
+ and used on our marketing landing page. This policy focuses on data handling for the Roo Code
+ marketing website. For details on how your data is handled within the Roo Code extension itself,
+ please refer to our separate{" "}
+
+ Roo Code Extension Privacy Policy
+
+ .
+
+
+
Where Your Data Goes (And Where It Doesn't)
+
+
Website Analytics & Tracking
+
+
+ We use PostHog (and its standard features) on our marketing landing page to analyze site
+ traffic and usage trends. This collection includes information such as your IP address,
+ browser type, device information, and pages visited.
+
+
+ These analytics help us understand how users engage with the website, so we can improve
+ content and design.
+
+ We do not collect code, project data, or any AI-related prompts on this page.
+
+
+
Cookies and Similar Technologies
+
+
+ Our marketing website may use cookies or similar tracking technologies to remember user
+ preferences and provide aggregated analytics.
+
+
+ Cookies help with things like user session management, remembering certain selections or
+ preferences, and compiling anonymous statistics.
+
+
+
+
Forms & Voluntary Submissions
+
+
+ If you submit your email or other personal data on our landing page (for example, to receive
+ updates or join a waiting list), we collect that information voluntarily provided by you.
+
+
+ We do not share or sell this data to third parties for their own marketing purposes. It is
+ used only to communicate with you about Roo Code, respond to inquiries, or send updates
+ you've requested.
+
+
+
+
Third-Party Integrations
+
+
+ Our website may embed content or links to external platforms (e.g., for processing payments
+ or handling support). Any data you provide through these external sites is governed by the
+ privacy policies of those platforms.
+
+
+
+
How We Use Your Data
+
+
Site Improvements & Marketing
+
+
+ We analyze aggregated user behavior to measure the effectiveness of our site, troubleshoot
+ any issues, and guide future improvements.
+
+
+ If you sign up for newsletters or updates, we use your email or other contact information
+ only to send you relevant Roo Code communications.
+
+
+
+
No Selling or Sharing of Data
+
+
+ We do not sell or share your personally identifiable information with third parties for
+ their marketing.
+
+ We do not train any models on your marketing site data.
+
+
+
Your Choices & Control
+
+
Manage Cookies
+
+
+ Most browsers allow you to manage or block cookies. If you disable cookies, some features of
+ the site may not function properly.
+
+
+
+
Opt-Out of Communications
+
+
+ If you have signed up to receive updates, you can unsubscribe anytime by following the
+ instructions in our emails or contacting us directly.
+
+
+
+
Request Deletion
+
+
+ You may request the deletion of any personal data you've provided through our marketing
+ forms by reaching out to us at{" "}
+
+ support@roocode.com
+
+ .
+
+
+
+
Security & Updates
+
+
+ We take reasonable measures to protect your data from unauthorized access or disclosure, but
+ no website can be 100% secure.
+
+
+ If our privacy practices for the marketing site change, we will update this policy and note
+ the effective date at the top.
+
+
+
+
Contact Us
+
+ If you have questions or concerns about this Privacy Policy or wish to make a request regarding
+ your data, please reach out to us at{" "}
+
+ support@roocode.com
+
+ .
+
+
+
+
+ By using the Roo Code marketing landing page, you agree to this Privacy Policy. If you use
+ the Roo Code extension, please see our separate{" "}
+
+ Roo Code Extension Privacy Policy
+ {" "}
+ for details on data handling in the extension.
+
+
+
+
+ >
+ )
+}
diff --git a/apps/website/src/app/shell.tsx b/apps/website/src/app/shell.tsx
new file mode 100644
index 0000000000..84a42bed21
--- /dev/null
+++ b/apps/website/src/app/shell.tsx
@@ -0,0 +1,18 @@
+import { getGitHubStars, getVSCodeDownloads } from "@/lib/stats"
+
+import { NavBar, Footer } from "@/components/chromes"
+
+// Invalidate cache when a request comes in, at most once every hour.
+export const revalidate = 3600
+
+export default async function Shell({ children }: { children: React.ReactNode }) {
+ const [stars, downloads] = await Promise.all([getGitHubStars(), getVSCodeDownloads()])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/apps/website/src/app/sitemap.xml b/apps/website/src/app/sitemap.xml
new file mode 100644
index 0000000000..43ac973306
--- /dev/null
+++ b/apps/website/src/app/sitemap.xml
@@ -0,0 +1,9 @@
+
+
+
+ https://roocode.com/
+ 2025-03-13T22:26:09Z
+ yearly
+ 1.0
+
+
diff --git a/apps/website/src/components/animated-text.tsx b/apps/website/src/components/animated-text.tsx
new file mode 100644
index 0000000000..7b7675dcf9
--- /dev/null
+++ b/apps/website/src/components/animated-text.tsx
@@ -0,0 +1,24 @@
+"use client"
+
+import type React from "react"
+import { motion } from "framer-motion"
+
+interface AnimatedTextProps {
+ children: React.ReactNode
+ className?: string
+}
+
+export function AnimatedText({ children, className }: AnimatedTextProps) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/website/src/components/chromes/footer.tsx b/apps/website/src/components/chromes/footer.tsx
new file mode 100644
index 0000000000..24f2de59cf
--- /dev/null
+++ b/apps/website/src/components/chromes/footer.tsx
@@ -0,0 +1,269 @@
+"use client"
+
+import { useState, useRef, useEffect } from "react"
+import Link from "next/link"
+import Image from "next/image"
+import { ChevronDown } from "lucide-react"
+import { RxGithubLogo, RxDiscordLogo } from "react-icons/rx"
+import { FaReddit } from "react-icons/fa6"
+
+import { EXTERNAL_LINKS, INTERNAL_LINKS } from "@/lib/constants"
+import { useLogoSrc } from "@/lib/hooks/use-logo-src"
+import { ScrollButton } from "@/components/ui"
+
+export function Footer() {
+ const [privacyDropdownOpen, setPrivacyDropdownOpen] = useState(false)
+ const dropdownRef = useRef(null)
+ const logoSrc = useLogoSrc()
+
+ // Close dropdown when clicking outside
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setPrivacyDropdownOpen(false)
+ }
+ }
+
+ document.addEventListener("mousedown", handleClickOutside)
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside)
+ }
+ }, [])
+ return (
+
+
+
+
+
+
+
+
+ Empowering developers to build better software faster with AI-powered tools and insights.
+
+
+
+
+
+
+
+
+
+
Company
+
+
+
+ Contact
+
+
+
+
+ Careers
+
+
+
+
+
setPrivacyDropdownOpen(!privacyDropdownOpen)}
+ className="flex items-center text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground"
+ aria-expanded={privacyDropdownOpen}
+ aria-haspopup="true">
+
+ Privacy Policy
+
+
+
+
+ {privacyDropdownOpen && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ © {new Date().getFullYear()} Roo Code. All rights reserved.
+
+
+
+
+ )
+}
diff --git a/apps/website/src/components/chromes/index.ts b/apps/website/src/components/chromes/index.ts
new file mode 100644
index 0000000000..2fe8f8b07c
--- /dev/null
+++ b/apps/website/src/components/chromes/index.ts
@@ -0,0 +1,4 @@
+export * from "./footer"
+export * from "./nav-bar"
+export * from "./stats-display"
+export * from "./theme-toggle"
diff --git a/apps/website/src/components/chromes/nav-bar.tsx b/apps/website/src/components/chromes/nav-bar.tsx
new file mode 100644
index 0000000000..412566df10
--- /dev/null
+++ b/apps/website/src/components/chromes/nav-bar.tsx
@@ -0,0 +1,188 @@
+/* eslint-disable react/jsx-no-target-blank */
+
+"use client"
+
+import Link from "next/link"
+import Image from "next/image"
+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"
+
+interface NavBarProps {
+ stars: string | null
+ downloads: string | null
+}
+
+export function NavBar({ stars, downloads }: NavBarProps) {
+ const [isMenuOpen, setIsMenuOpen] = useState(false)
+ const logoSrc = useLogoSrc()
+
+ return (
+
+ )
+}
diff --git a/apps/website/src/components/chromes/stats-display.tsx b/apps/website/src/components/chromes/stats-display.tsx
new file mode 100644
index 0000000000..452cb34f16
--- /dev/null
+++ b/apps/website/src/components/chromes/stats-display.tsx
@@ -0,0 +1,31 @@
+import Link from "next/link"
+import { RxGithubLogo } from "react-icons/rx"
+import { VscVscode } from "react-icons/vsc"
+import { getGitHubStars, getVSCodeDownloads } from "@/lib/stats"
+
+export default async function StatsDisplay() {
+ const stars = await getGitHubStars()
+ const downloads = await getVSCodeDownloads()
+
+ return (
+ <>
+
+
+ {stars !== null && {stars} }
+
+
+
+
+ Install ·
+
+ {downloads !== null && {downloads} }
+
+ >
+ )
+}
diff --git a/apps/website/src/components/chromes/theme-toggle.tsx b/apps/website/src/components/chromes/theme-toggle.tsx
new file mode 100644
index 0000000000..f0e075a8cb
--- /dev/null
+++ b/apps/website/src/components/chromes/theme-toggle.tsx
@@ -0,0 +1,40 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { useTheme } from "next-themes"
+import { RxSun, RxMoon } from "react-icons/rx"
+
+import { Button } from "@/components/ui"
+
+export default function ThemeToggle() {
+ const { theme, setTheme } = useTheme()
+ const [mounted, setMounted] = useState(false)
+
+ // Avoid hydration mismatch.
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+ setTheme(theme === "dark" ? "light" : "dark")}
+ className="h-9 w-9"
+ aria-label="Toggle theme">
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+
+ )
+}
diff --git a/apps/website/src/components/enterprise/contact-form.tsx b/apps/website/src/components/enterprise/contact-form.tsx
new file mode 100644
index 0000000000..b90435e1f3
--- /dev/null
+++ b/apps/website/src/components/enterprise/contact-form.tsx
@@ -0,0 +1,291 @@
+"use client"
+
+import { useState, useRef } from "react"
+import { z } from "zod"
+
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui"
+
+// Define the form schema using Zod
+const contactFormSchema = z.object({
+ name: z.string().min(1, "Name is required"),
+ company: z.string().min(1, "Company is required"),
+ email: z.string().email("Invalid email address"),
+ website: z.string().url("Invalid website URL").or(z.string().length(0)),
+ engineerCount: z.enum(["1-10", "11-50", "51-200", "201-500", "501-1000", "1000+"]),
+ formType: z.enum(["early-access", "demo"]),
+ _honeypot: z.string().optional(),
+})
+
+interface ContactFormProps {
+ formType: "early-access" | "demo"
+ buttonText: string
+ buttonClassName?: string
+}
+
+export function ContactForm({ formType, buttonText, buttonClassName }: ContactFormProps) {
+ const [isOpen, setIsOpen] = useState(false)
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [formErrors, setFormErrors] = useState>({})
+ const [submitStatus, setSubmitStatus] = useState<"idle" | "success" | "error">("idle")
+ const formRef = useRef(null)
+
+ const formTitle = formType === "early-access" ? "Become an Early Access Partner" : "Request a Demo"
+
+ const formDescription =
+ formType === "early-access"
+ ? "Fill out the form below to collaborate in shaping Roo Code's enterprise solution."
+ : "Fill out the form below to see Roo Code's enterprise capabilities in action."
+
+ // Get Basin endpoint from environment variable
+ // This should be set in .env.local as NEXT_PUBLIC_BASIN_ENDPOINT="https://usebasin.com/f/your-form-id"
+ const BASIN_ENDPOINT = process.env.NEXT_PUBLIC_BASIN_ENDPOINT || ""
+
+ // Check if Basin endpoint is configured
+ if (!BASIN_ENDPOINT) {
+ console.warn("NEXT_PUBLIC_BASIN_ENDPOINT is not configured. Form submissions will not work.")
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsSubmitting(true)
+ setFormErrors({})
+ setSubmitStatus("idle")
+
+ const form = e.currentTarget
+ const formData = new FormData(form)
+
+ // Create a data object for validation and submission
+ const data = {
+ name: formData.get("name") as string,
+ company: formData.get("company") as string,
+ email: formData.get("email") as string,
+ website: formData.get("website") as string,
+ engineerCount: formData.get("engineerCount") as string,
+ formType,
+ // Include honeypot field for spam protection
+ _honeypot: formData.get("_honeypot") as string,
+ }
+
+ // Validate form data on client side
+ try {
+ contactFormSchema.parse(data)
+
+ // Submit data to Basin
+ try {
+ const response = await fetch(BASIN_ENDPOINT, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ mode: "cors", // Ensure proper CORS handling
+ body: JSON.stringify(data),
+ })
+
+ // Basin returns a 200 status code on success
+ if (response.ok) {
+ try {
+ const responseData = await response.json()
+
+ // Basin JSON API typically returns a 'status' property of 'success' when submission succeeds
+ if (responseData && (responseData.success === true || responseData.status === "success")) {
+ setSubmitStatus("success")
+ // Reset form safely
+ if (form) {
+ form.reset()
+ }
+ } else {
+ console.error("Basin error:", responseData)
+ setSubmitStatus("error")
+ }
+ } catch (jsonError) {
+ // In case response parsing fails but status was OK, assume success
+ console.error("Error parsing JSON response:", jsonError)
+ setSubmitStatus("success")
+ if (form) {
+ form.reset()
+ }
+ }
+ } else {
+ // Handle error response from Basin (4xx or 5xx)
+ try {
+ const errorData = await response.json()
+ console.error("Basin API error:", response.status, errorData)
+ } catch {
+ console.error("Basin returned error status:", response.status)
+ }
+ setSubmitStatus("error")
+ }
+ } catch (error) {
+ console.error("Error submitting form:", error)
+ setSubmitStatus("error")
+ }
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const errors: Record = {}
+ error.errors.forEach((err) => {
+ if (err.path[0]) {
+ errors[err.path[0] as string] = err.message
+ }
+ })
+ setFormErrors(errors)
+ } else {
+ setSubmitStatus("error")
+ }
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+
+ return (
+
+
+ {buttonText}
+
+
+
+ {formTitle}
+ {formDescription}
+
+
+ {submitStatus === "success" ? (
+
+
+
Thank You!
+
+ Your information has been submitted successfully. Our team will be in touch with you
+ shortly.
+
+
setIsOpen(false)}>
+ Close
+
+
+ ) : (
+
+ )}
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/animated-background.tsx b/apps/website/src/components/homepage/animated-background.tsx
new file mode 100644
index 0000000000..7d67f45429
--- /dev/null
+++ b/apps/website/src/components/homepage/animated-background.tsx
@@ -0,0 +1,280 @@
+"use client"
+
+import { useEffect, useRef } from "react"
+
+export function AnimatedBackground() {
+ const canvasRef = useRef(null)
+
+ useEffect(() => {
+ const canvas = canvasRef.current
+ if (!canvas) return
+
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return
+
+ // grid settings
+ const gridSize = 50
+ const gridOpacity = 0.15
+
+ // initialize gradient points for lighting effects
+ let gradientPoints = [
+ {
+ x: canvas.width * 0.2,
+ y: canvas.height * 0.3,
+ radius: canvas.width * 0.4,
+ color: "rgba(0, 100, 255, 0.15)",
+ },
+ {
+ x: canvas.width * 0.8,
+ y: canvas.height * 0.7,
+ radius: canvas.width * 0.5,
+ color: "rgba(100, 0, 255, 0.1)",
+ },
+ ]
+
+ // particle system
+ const particles: Particle[] = []
+ const particleCount = Math.min(50, Math.floor(window.innerWidth / 40))
+
+ // set canvas dimensions
+ const resizeCanvas = () => {
+ canvas.width = window.innerWidth
+ canvas.height = window.innerHeight
+
+ // update gradient points when canvas is resized
+ gradientPoints = [
+ {
+ x: canvas.width * 0.2,
+ y: canvas.height * 0.3,
+ radius: canvas.width * 0.4,
+ color: "rgba(0, 100, 255, 0.15)",
+ },
+ {
+ x: canvas.width * 0.8,
+ y: canvas.height * 0.7,
+ radius: canvas.width * 0.5,
+ color: "rgba(100, 0, 255, 0.1)",
+ },
+ ]
+
+ // redraw grid after resize
+ drawGrid()
+ }
+
+ resizeCanvas()
+ window.addEventListener("resize", resizeCanvas)
+
+ // draw grid with perspective effect
+ function drawGrid() {
+ if (!ctx) {
+ throw new Error("Context is null (not initialized?)")
+ }
+
+ if (!canvas) {
+ throw new Error("Canvas is null (not initialized?)")
+ }
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+
+ // Draw gradient lighting effects.
+ gradientPoints.forEach((point) => {
+ const gradient = ctx.createRadialGradient(point.x, point.y, 0, point.x, point.y, point.radius)
+ gradient.addColorStop(0, point.color)
+ gradient.addColorStop(1, "rgba(0, 0, 0, 0)")
+
+ ctx.fillStyle = gradient
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
+ })
+
+ // Draw grid lines with perspective effect.
+ ctx.strokeStyle = `rgba(50, 50, 70, ${gridOpacity})`
+ ctx.lineWidth = 0.5
+
+ // horizontal lines with perspective.
+ const horizonY = canvas.height * 0.7 // Horizon point.
+ const vanishingPointX = canvas.width * 0.5 // Center vanishing point.
+
+ // Vertical lines.
+ for (let x = 0; x <= canvas.width; x += gridSize) {
+ const normalizedX = x / canvas.width - 0.5 // -0.5 to 0.5
+
+ ctx.beginPath()
+ ctx.moveTo(x, 0)
+
+ // Calculate curve based on distance from center.
+ const curveStrength = 50 * Math.abs(normalizedX)
+ const controlPointY = horizonY - curveStrength
+
+ // Create curved line toward vanishing point.
+ ctx.quadraticCurveTo(
+ x + (vanishingPointX - x) * 0.3,
+ controlPointY,
+ vanishingPointX + (x - vanishingPointX) * 0.2,
+ horizonY,
+ )
+
+ ctx.stroke()
+ }
+
+ // Horizontal lines.
+ for (let y = 0; y <= horizonY; y += gridSize) {
+ const normalizedY = y / horizonY // 0 to 1
+ const lineWidth = gridSize * (1 + normalizedY * 5) // lines get wider as they get closer
+
+ ctx.beginPath()
+ ctx.moveTo(vanishingPointX - lineWidth, y)
+ ctx.lineTo(vanishingPointX + lineWidth, y)
+ ctx.stroke()
+ }
+
+ updateParticles()
+ }
+
+ class Particle {
+ x: number
+ y: number
+ size: number
+ speedX: number
+ speedY: number
+ color: string
+ opacity: number
+
+ constructor() {
+ if (!canvas) {
+ throw new Error("Canvas is null (not initialized?)")
+ }
+
+ this.x = Math.random() * canvas.width
+ this.y = Math.random() * (canvas.height * 0.7) // Keep particles above horizon.
+ this.size = Math.random() * 2 + 1
+ this.speedX = (Math.random() - 0.5) * 0.8
+ this.speedY = (Math.random() - 0.5) * 0.8
+ this.color = "rgba(100, 150, 255, "
+ this.opacity = Math.random() * 0.5 + 0.2
+ }
+
+ update() {
+ if (!canvas) {
+ throw new Error("Canvas is null (not initialized?)")
+ }
+
+ this.x += this.speedX
+ this.y += this.speedY
+
+ // Boundary check.
+ if (this.x > canvas.width) this.x = 0
+ else if (this.x < 0) this.x = canvas.width
+ if (this.y > canvas.height * 0.7) this.y = 0
+ else if (this.y < 0) this.y = canvas.height * 0.7
+
+ // Pulsate opacity.
+ this.opacity += Math.sin(Date.now() * 0.001) * 0.01
+ this.opacity = Math.max(0.1, Math.min(0.7, this.opacity))
+ }
+
+ draw() {
+ if (!ctx) {
+ throw new Error("Context is null (not initialized?)")
+ }
+
+ ctx.fillStyle = `${this.color}${this.opacity})`
+ ctx.beginPath()
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
+ ctx.fill()
+ }
+ }
+
+ // Initialize particles.
+ for (let i = 0; i < particleCount; i++) {
+ particles.push(new Particle())
+ }
+
+ // Connect particles with lines.
+ function connectParticles() {
+ if (!ctx) {
+ throw new Error("Context is null (not initialized?)")
+ }
+
+ const maxDistance = 150
+
+ for (let a = 0; a < particles.length; a++) {
+ for (let b = a; b < particles.length; b++) {
+ const dx = particles[a]!.x - particles[b]!.x
+ const dy = particles[a]!.y - particles[b]!.y
+ const distance = Math.sqrt(dx * dx + dy * dy)
+
+ if (distance < maxDistance) {
+ const opacity = (1 - distance / maxDistance) * 0.5
+ ctx.strokeStyle = `rgba(100, 150, 255, ${opacity})`
+ ctx.lineWidth = 0.5
+ ctx.beginPath()
+ ctx.moveTo(particles[a]!.x, particles[a]!.y)
+ ctx.lineTo(particles[b]!.x, particles[b]!.y)
+ ctx.stroke()
+ }
+ }
+ }
+ }
+
+ function updateParticles() {
+ particles.forEach((particle) => {
+ particle.update()
+ particle.draw()
+ })
+
+ connectParticles()
+ }
+
+ // Animation loop.
+ let animationId: number
+
+ // Target position for smooth following.
+ let targetX = canvas.width * 0.2
+ let targetY = canvas.height * 0.3
+ const moveSpeed = 0.05 // Adjust this value to control movement speed (0-1).
+
+ // Move gradient points with mouse.
+ const handleMouseMove = (e: MouseEvent) => {
+ targetX = e.clientX
+ targetY = e.clientY
+ }
+
+ // Update gradient point position in animation loop.
+ function updateGradientPosition() {
+ if (!canvas) throw new Error("Canvas is null (not initialized?)")
+
+ // Calculate direction vector.
+ const dx = targetX - gradientPoints[0]!.x
+ const dy = targetY - gradientPoints[0]!.y
+
+ // Smooth movement using linear interpolation.
+ gradientPoints[0]!.x += dx * moveSpeed
+ gradientPoints[0]!.y += dy * moveSpeed
+
+ // Adjust radius based on distance to target.
+ const distanceToTarget = Math.sqrt(dx * dx + dy * dy)
+ gradientPoints[0]!.radius = Math.max(
+ canvas.width * 0.2,
+ Math.min(canvas.width * 0.4, canvas.width * 0.3 + distanceToTarget * 0.1),
+ )
+ }
+
+ function animate() {
+ animationId = requestAnimationFrame(animate)
+ updateGradientPosition()
+ drawGrid()
+ }
+
+ animate()
+
+ window.addEventListener("mousemove", handleMouseMove)
+
+ return () => {
+ window.removeEventListener("resize", resizeCanvas)
+ window.removeEventListener("mousemove", handleMouseMove)
+ cancelAnimationFrame(animationId)
+ }
+ }, [])
+
+ return
+}
diff --git a/apps/website/src/components/homepage/code-example.tsx b/apps/website/src/components/homepage/code-example.tsx
new file mode 100644
index 0000000000..d1b1f71a13
--- /dev/null
+++ b/apps/website/src/components/homepage/code-example.tsx
@@ -0,0 +1,236 @@
+"use client"
+
+import { useState, useEffect, useRef } from "react"
+import { motion } from "framer-motion"
+
+export function CodeExample() {
+ const [currentMode, setCurrentMode] = useState<"code" | "architect" | "debug">("code")
+ const [isTyping, setIsTyping] = useState(false)
+ const [currentText, setCurrentText] = useState("")
+ const [textIndex, setTextIndex] = useState(0)
+ const codeContainerRef = useRef(null)
+
+ // simulate typing effect
+ useEffect(() => {
+ if (isTyping && textIndex < codeExamples[currentMode].code.length) {
+ const timer = setTimeout(() => {
+ setCurrentText((prev) => prev + codeExamples[currentMode].code[textIndex])
+ setTextIndex(textIndex + 1)
+
+ // Auto-scroll to the bottom
+ if (codeContainerRef.current) {
+ codeContainerRef.current.scrollTop = codeContainerRef.current.scrollHeight
+ }
+ }, 15) // adjust speed as needed
+ return () => clearTimeout(timer)
+ } else if (textIndex >= codeExamples[currentMode].code.length) {
+ setIsTyping(false)
+ // switch to next mode after a delay
+ const timer = setTimeout(() => {
+ const nextMode = currentMode === "code" ? "architect" : currentMode === "architect" ? "debug" : "code"
+ switchMode(nextMode)
+ }, 1000) // wait a second before switching
+ return () => clearTimeout(timer)
+ }
+ }, [isTyping, textIndex, currentMode])
+
+ // switch modes with typing effect
+ const switchMode = (mode: "code" | "architect" | "debug") => {
+ setCurrentMode(mode)
+ setCurrentText("")
+ setTextIndex(0)
+ setIsTyping(true)
+
+ // Reset scroll position when switching modes
+ if (codeContainerRef.current) {
+ codeContainerRef.current.scrollTop = 0
+ }
+ }
+
+ // start typing on initial load
+ useEffect(() => {
+ setIsTyping(true)
+ }, [])
+
+ return (
+
+
+
+
+
+ switchMode("code")}
+ className={`rounded px-2 py-0.5 text-xs font-medium transition-colors sm:text-sm ${currentMode === "code" ? "bg-blue-500/20 text-blue-400" : "text-gray-400 hover:bg-gray-800"}`}>
+ Code
+
+ switchMode("architect")}
+ className={`rounded px-2 py-0.5 text-xs font-medium transition-colors sm:text-sm ${currentMode === "architect" ? "bg-purple-500/20 text-purple-400" : "text-gray-400 hover:bg-gray-800"}`}>
+ Architect
+
+ switchMode("debug")}
+ className={`rounded px-2 py-0.5 text-xs font-medium transition-colors sm:text-sm ${currentMode === "debug" ? "bg-green-500/20 text-green-400" : "text-gray-400 hover:bg-gray-800"}`}>
+ Debug
+
+
+
+
+
+
+ {currentText}
+ {isTyping && (
+
+ )}
+
+
+
+
+
+ )
+}
+
+const codeExamples = {
+ code: {
+ code: `// Code Mode
+You: Generate a React component for a user profile card
+
+Roo: Here's a React component for a user profile card:
+
+import React from 'react';
+
+interface UserProfileProps {
+ name: string;
+ role: string;
+ avatar: string;
+ bio: string;
+ stats: {
+ projects: number;
+ followers: number;
+ following: number;
+ };
+}
+
+export const UserProfile: React.FC = ({
+ name,
+ role,
+ avatar,
+ bio,
+ stats
+}) => {
+ return (
+
+
+
+
+
+
{bio}
+
+
+ {stats.projects}
+ Projects
+
+
+ {stats.followers}
+ Followers
+
+
+ {stats.following}
+ Following
+
+
+
+ );
+};`,
+ },
+ architect: {
+ code: `// Architect Mode
+// Project: E-commerce Platform
+// Request: Design the authentication system
+
+Roo is analyzing your project...
+
+📂 Recommended Architecture:
+
+auth/
+├─ services/
+│ ├─ authService.ts // Core authentication logic
+│ ├─ tokenService.ts // JWT handling and refresh
+│ └─ userService.ts // User data operations
+├─ hooks/
+│ ├─ useAuth.ts // React hook for auth state
+│ └─ useProtectedRoute.ts // Route protection
+├─ components/
+│ ├─ LoginForm.tsx // Login UI
+│ ├─ SignupForm.tsx // Registration UI
+│ ├─ PasswordReset.tsx // Password recovery
+│ └─ TwoFactorAuth.tsx // 2FA implementation
+└─ context/
+ └─ AuthContext.tsx // Global auth state
+
+🔐 Security Recommendations:
+- Implement PKCE flow for auth code exchange
+- Use HttpOnly cookies for refresh tokens
+- Rate limit authentication attempts
+- Add device fingerprinting for suspicious login detection
+
+⚡ Performance Considerations:
+- Prefetch user data on auth
+- Implement token refresh without UI disruption
+- Lazy load auth components
+
+Would you like me to generate any of these files?`,
+ },
+ debug: {
+ code: `// Debug Mode
+// Analyzing error: TypeError: Cannot read property 'map' of undefined
+
+Roo has analyzed your code and found 3 issues:
+
+🐛 Issue #1: Null data reference
+ Line 42: const items = data.items.map(item => item.name);
+
+ ✓ Root Cause: 'data' is undefined when component mounts
+ ✓ Context: API request in useEffect hasn't completed yet
+
+ Recommended Fix:
+ const items = data?.items?.map(item => item.name) || [];
+
+🐛 Issue #2: Missing dependency in useEffect
+ Line 37: useEffect(() => { fetchData() }, []);
+
+ ✓ Root Cause: fetchData depends on 'userId' but isn't in deps array
+ ✓ Context: This causes stale data when userId changes
+
+ Recommended Fix:
+ useEffect(() => { fetchData() }, [userId, fetchData]);
+
+🐛 Issue #3: Memory leak from unfinished API call
+ Line 38: const response = await api.getItems(userId);
+
+ ✓ Root Cause: No cleanup when component unmounts during API call
+ ✓ Context: This triggers React warning in development
+
+ Recommended Fix:
+ Add AbortController to cancel pending requests on unmount
+
+Apply these fixes automatically? [Yes/No]`,
+ },
+}
diff --git a/apps/website/src/components/homepage/company-logos.tsx b/apps/website/src/components/homepage/company-logos.tsx
new file mode 100644
index 0000000000..cedc681759
--- /dev/null
+++ b/apps/website/src/components/homepage/company-logos.tsx
@@ -0,0 +1,40 @@
+"use client"
+
+import { motion } from "framer-motion"
+
+export function CompanyLogos() {
+ const logos = [
+ { name: "Company 1", logo: "/placeholder.svg?height=40&width=120" },
+ { name: "Company 2", logo: "/placeholder.svg?height=40&width=120" },
+ { name: "Company 3", logo: "/placeholder.svg?height=40&width=120" },
+ { name: "Company 4", logo: "/placeholder.svg?height=40&width=120" },
+ { name: "Company 5", logo: "/placeholder.svg?height=40&width=120" },
+ { name: "Company 6", logo: "/placeholder.svg?height=40&width=120" },
+ ]
+
+ return (
+
+
+ {logos.map((company, index) => (
+
+ {/* eslint-disable @next/next/no-img-element */}
+
+
+ ))}
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/faq-section.tsx b/apps/website/src/components/homepage/faq-section.tsx
new file mode 100644
index 0000000000..e870474661
--- /dev/null
+++ b/apps/website/src/components/homepage/faq-section.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import { useState } from "react"
+import { motion } from "framer-motion"
+import { ChevronDown } from "lucide-react"
+import { cn } from "@/lib/utils"
+
+interface FAQItem {
+ question: string
+ answer: string
+}
+
+const faqs: FAQItem[] = [
+ {
+ question: "What exactly is Roo Code?",
+ answer: "Roo Code is an open-source, AI-powered coding assistant that runs in VS Code. It goes beyond simple autocompletion by reading and writing across multiple files, executing commands, and adapting to your workflow—like having a whole dev team right inside your editor.",
+ },
+ {
+ question: "How does Roo Code differ from Copilot, Cursor, or Windsurf?",
+ answer: "Open & Customizable: Roo Code is open-source and allows you to integrate any AI model (OpenAI, Anthropic, local LLMs, etc.). Multi-File Edits: It can read, refactor, and update multiple files at once for more holistic changes. Agentic Abilities: Roo Code can run tests, open a browser, or do deeper tasks than a typical AI autocomplete. Permission-Based: You control and approve any file changes or command executions.",
+ },
+ {
+ question: "Is Roo Code really free?",
+ answer: "Yes! Roo Code is completely free and open-source. You'll only pay for the AI model usage if you use a paid API (like OpenAI). If you choose free or self-hosted models, there's no cost at all.",
+ },
+ {
+ question: "Will my code stay private?",
+ answer: "Yes. Because Roo Code is an extension in your local VS Code, your code never leaves your machine unless you connect to an external AI API. Even then, you control exactly what is sent to the AI model. You can use tools like .rooignore to exclude sensitive files, and you can also run Roo Code with offline/local models for full privacy.",
+ },
+ {
+ question: "Which AI models does Roo Code support?",
+ answer: "Roo Code is model-agnostic. It works with: OpenAI models (GPT-3.5, GPT-4, etc.), Anthropic Claude, Local LLMs (through APIs or special plugins), Any other API that follows Roo Code's Model Context Protocol (MCP).",
+ },
+ {
+ question: "Does Roo Code support my programming language?",
+ answer: "Likely yes! Roo Code supports a wide range of languages—Python, Java, C#, JavaScript/TypeScript, Go, Rust, etc. Since it leverages the AI model's understanding, new or lesser-known languages may also work, depending on model support.",
+ },
+ {
+ question: "How do I install and get started?",
+ answer: "Install Roo Code from the VS Code Marketplace (or GitHub). Add your AI keys (OpenAI, Anthropic, or other) in the extension settings. Open the Roo panel (the rocket icon) in VS Code, and start typing commands in plain English!",
+ },
+ {
+ question: "Can it handle large, enterprise-scale projects?",
+ answer: "Absolutely. Roo Code uses efficient strategies (like partial-file analysis, summarization, or user-specified context) to handle large codebases. Enterprises especially appreciate the on-prem or self-hosted model option for compliance and security needs.",
+ },
+ {
+ question: "Is it safe for enterprise use?",
+ answer: "Yes. Roo Code was designed with enterprise in mind: Self-host AI models or choose your own provider. Permission gating on file writes and commands. Auditable: The entire code is open-source, so you know exactly how it operates.",
+ },
+ {
+ question: "Can Roo Code run commands and tests automatically?",
+ answer: "Yes! One of Roo Code's superpowers is command execution (optional and fully permission-based). It can: Run npm install or any terminal command you grant permission for. Execute your test suites. Open a web browser for integration tests.",
+ },
+ {
+ question: "What if I just want a casual coding 'vibe'?",
+ answer: 'Roo Code shines for both serious enterprise development and casual "vibe coding." You can ask it to quickly prototype ideas, refactor on the fly, or provide design suggestions—without a rigid, step-by-step process.',
+ },
+ {
+ question: "Can I contribute to Roo Code?",
+ answer: "Yes, please do! Roo Code is open-source on GitHub. Submit issues, suggest features, or open a pull request. There's also an active community on Discord and Reddit if you want to share feedback or help others.",
+ },
+ {
+ question: "Where can I learn more or get help?",
+ answer: "Check out: Official Documentation for setup and advanced guides. Discord & Reddit channels for community support. YouTube tutorials and blog posts from fellow developers showcasing real-world usage.",
+ },
+]
+
+export function FAQSection() {
+ const [openIndex, setOpenIndex] = useState(null)
+
+ const toggleFAQ = (index: number) => {
+ setOpenIndex(openIndex === index ? null : index)
+ }
+
+ return (
+
+
+
+
+
+ Frequently Asked Questions
+
+
+ Everything you need to know about Roo Code and how it can transform your development
+ workflow.
+
+
+
+
+
+
+ {faqs.map((faq, index) => (
+
+
+
toggleFAQ(index)}
+ className="flex w-full items-center justify-between p-6 text-left"
+ aria-expanded={openIndex === index}>
+ {faq.question}
+
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/features-mobile.tsx b/apps/website/src/components/homepage/features-mobile.tsx
new file mode 100644
index 0000000000..7e623ecfd4
--- /dev/null
+++ b/apps/website/src/components/homepage/features-mobile.tsx
@@ -0,0 +1,113 @@
+"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([])
+
+ 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 (
+
+
+
+
+ {features.map((feature, index) => (
+
+
+
+
{feature.title}
+
{feature.description}
+
+
+ ))}
+
+
+
+ {/* Navigation Controls */}
+
+
+ emblaApi?.scrollPrev()}>
+
+ Previous slide
+
+ emblaApi?.scrollNext()}>
+
+ Next slide
+
+
+
+
+ {scrollSnaps.map((_, index) => (
+ scrollTo(index)}
+ aria-label={`Go to slide ${index + 1}`}
+ />
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/features.tsx b/apps/website/src/components/homepage/features.tsx
new file mode 100644
index 0000000000..ce5534ab6e
--- /dev/null
+++ b/apps/website/src/components/homepage/features.tsx
@@ -0,0 +1,172 @@
+"use client"
+
+import { motion } from "framer-motion"
+import { FaRobot, FaCode, FaBrain, FaTools, FaTerminal, FaPuzzlePiece, FaGlobe } from "react-icons/fa"
+import { FeaturesMobile } from "./features-mobile"
+
+import { ReactNode } from "react"
+
+export interface Feature {
+ icon: ReactNode
+ title: string
+ description: string
+ size: "small" | "large"
+}
+
+export const features: Feature[] = [
+ {
+ icon: ,
+ title: "Your AI Dev Team in VS Code",
+ 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.",
+ size: "large",
+ },
+ {
+ icon: ,
+ 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.",
+ size: "small",
+ },
+ {
+ icon: ,
+ title: "Deep Project-wide Context",
+ description:
+ "Roo Code reads your entire codebase, preserving valid code through diff-based edits for seamless multi-file refactors.",
+ size: "small",
+ },
+ {
+ icon: ,
+ title: "Open-Source and Model-Agnostic",
+ description:
+ "Bring your own model or use local AI—no vendor lock-in. Roo Code is free, open, and adaptable to your needs.",
+ size: "large",
+ },
+ {
+ icon: ,
+ title: "Guarded Command Execution",
+ description:
+ "Approve or deny commands as needed. Roo Code automates your dev workflow while keeping oversight firmly in your hands.",
+ size: "small",
+ },
+ {
+ icon: ,
+ title: "Fully Customizable",
+ description:
+ "Create or tweak modes, define usage rules, and shape Roo Code’s behavior precisely—your code, your way.",
+ size: "small",
+ },
+ {
+ icon: ,
+ 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.",
+ size: "small",
+ },
+]
+
+export function Features() {
+ 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],
+ },
+ },
+ }
+
+ const backgroundVariants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ transition: {
+ duration: 1.2,
+ ease: "easeOut",
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Powerful features for modern developers.
+
+
+ Everything you need to build faster and write better code.
+
+
+
+
+ {/* Mobile Carousel */}
+
+
+ {/* Desktop Grid */}
+
+
+ {features.map((feature, index) => (
+
+
+
+
+
{feature.title}
+
{feature.description}
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/index.ts b/apps/website/src/components/homepage/index.ts
new file mode 100644
index 0000000000..192c155473
--- /dev/null
+++ b/apps/website/src/components/homepage/index.ts
@@ -0,0 +1,10 @@
+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"
diff --git a/apps/website/src/components/homepage/install-section.tsx b/apps/website/src/components/homepage/install-section.tsx
new file mode 100644
index 0000000000..224a83cee3
--- /dev/null
+++ b/apps/website/src/components/homepage/install-section.tsx
@@ -0,0 +1,87 @@
+"use client"
+
+import { VscVscode } from "react-icons/vsc"
+import Link from "next/link"
+import { motion } from "framer-motion"
+
+interface InstallSectionProps {
+ downloads: string | null
+}
+
+export function InstallSection({ downloads }: InstallSectionProps) {
+ const backgroundVariants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ transition: {
+ duration: 1.2,
+ ease: "easeOut",
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+
+
+ Install Roo Code — Open & Flexible
+
+
+ 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.
+
+
+
+
+
+
+
+ VSCode Marketplace
+ {downloads !== null && (
+ <>
+
+ ·
+
+ {downloads} Downloads
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+ code --install-extension RooVeterinaryInc.roo-cline
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/testimonials-mobile.tsx b/apps/website/src/components/homepage/testimonials-mobile.tsx
new file mode 100644
index 0000000000..e4b3b6863a
--- /dev/null
+++ b/apps/website/src/components/homepage/testimonials-mobile.tsx
@@ -0,0 +1,49 @@
+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 (
+
+
+
+ {testimonials.map((testimonial) => (
+
+
+
+
+
+
+
+
+ "{testimonial.quote}"
+
+
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/testimonials.tsx b/apps/website/src/components/homepage/testimonials.tsx
new file mode 100644
index 0000000000..8ffed444cc
--- /dev/null
+++ b/apps/website/src/components/homepage/testimonials.tsx
@@ -0,0 +1,183 @@
+"use client"
+
+import { useRef } from "react"
+import { motion } from "framer-motion"
+import Image from "next/image"
+import { TestimonialsMobile } from "./testimonials-mobile"
+
+export interface Testimonial {
+ id: number
+ name: string
+ role: string
+ company: string
+ image?: string
+ quote: string
+}
+
+export const testimonials: Testimonial[] = [
+ {
+ id: 1,
+ name: "Luca",
+ role: "Reviewer",
+ company: "VS Code Marketplace",
+ quote: "Roo Code is an absolute game-changer! 🚀 It makes coding faster, easier, and more intuitive with its smart AI-powered suggestions, real-time debugging, and automation features. The seamless integration with VS Code is a huge plus, and the constant updates ensure it keeps getting better",
+ },
+ {
+ id: 2,
+ name: "Taro Woollett-Chiba",
+ role: "AI Product Lead",
+ company: "Vendidit",
+ quote: "Easily the best AI code editor. Roo Code has the best features and capabilities, along with the best development team. I swear, they're the fastest to support new models and implement useful functionality whenever users mention it... simply amazing.",
+ },
+ {
+ id: 3,
+ name: "Can Nuri",
+ role: "Reviewer",
+ company: "VS Code Marketplace",
+ quote: "Roo Code is one of the most inspiring projects I have seen for a long time. It shapes the way I think and deal with software development.",
+ },
+ {
+ id: 4,
+ name: "Michael",
+ role: "Reviewer",
+ company: "VS Code Marketplace",
+ quote: "I switched from Windsurf to Roo Code in January and honestly, it's been a huge upgrade. Windsurf kept making mistakes and being dumb when I ask it for things. Roo just gets it. Projects that used to take a full day now wrap up before lunch. ",
+ },
+]
+
+export function Testimonials() {
+ const containerRef = useRef(null)
+
+ 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],
+ },
+ },
+ }
+
+ const backgroundVariants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ transition: {
+ duration: 1.2,
+ ease: "easeOut",
+ },
+ },
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Empowering developers worldwide.
+
+
+ Join thousands of developers who are revolutionizing their workflow with AI-powered
+ assistance.
+
+
+
+
+ {/* Mobile Carousel */}
+
+
+ {/* Desktop Grid */}
+
+
+ {testimonials.map((testimonial, index) => (
+
+
+
+ {testimonial.image && (
+
+ )}
+
+
+
+
+
+ {testimonial.quote}
+
+
+
+
+
{testimonial.name}
+
+ {testimonial.role} at {testimonial.company}
+
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/website/src/components/homepage/whats-new-button.tsx b/apps/website/src/components/homepage/whats-new-button.tsx
new file mode 100644
index 0000000000..e00d5ead14
--- /dev/null
+++ b/apps/website/src/components/homepage/whats-new-button.tsx
@@ -0,0 +1,229 @@
+"use client"
+
+import { useState, useRef, useEffect } from "react"
+import { motion, AnimatePresence } from "framer-motion"
+import { X, ArrowRight, Code2, Users2, Zap } from "lucide-react"
+import Link from "next/link"
+
+interface FeatureProps {
+ icon: React.ComponentType<{ className?: string }>
+ color: "blue" | "purple" | "green"
+ title: string
+ description: string
+}
+
+function Feature({ icon: Icon, color, title, description }: FeatureProps) {
+ const bgColor = {
+ blue: "bg-blue-500/20",
+ purple: "bg-purple-500/20",
+ green: "bg-green-500/20",
+ }[color]
+
+ const textColor = {
+ blue: "text-blue-400",
+ purple: "text-purple-400",
+ green: "text-green-400",
+ }[color]
+
+ return (
+
+ )
+}
+
+const version = "v3.8.0"
+
+export function WhatsNewButton() {
+ const [isOpen, setIsOpen] = useState(false)
+ const buttonRef = useRef(null)
+ const canvasRef = useRef(null)
+
+ // animated border effect
+ useEffect(() => {
+ const canvas = canvasRef.current
+ const button = buttonRef.current
+
+ if (!canvas || !button) return
+
+ const ctx = canvas.getContext("2d")
+ if (!ctx) return
+
+ // set canvas size to match button size with extra space for glow
+ const updateCanvasSize = () => {
+ const rect = button.getBoundingClientRect()
+ // add extra padding for the glow effect
+ canvas.width = rect.width + 8
+ canvas.height = rect.height + 8
+
+ // position the canvas precisely
+ canvas.style.width = `${canvas.width}px`
+ canvas.style.height = `${canvas.height}px`
+ }
+
+ updateCanvasSize()
+ window.addEventListener("resize", updateCanvasSize)
+
+ // animation variables
+ let animationId: number
+ let position = 0
+
+ const animate = () => {
+ if (!ctx || !canvas) return
+
+ // clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+
+ // calculate border path
+ const width = canvas.width - 4
+ const height = canvas.height - 4
+ const x = 2
+ const y = 2
+ const radius = height / 2
+
+ // draw rounded rectangle path
+ ctx.beginPath()
+ ctx.moveTo(x + radius, y)
+ ctx.lineTo(x + width - radius, y)
+ ctx.arcTo(x + width, y, x + width, y + radius, radius)
+ ctx.lineTo(x + width, y + height - radius)
+ ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius)
+ ctx.lineTo(x + radius, y + height)
+ ctx.arcTo(x, y + height, x, y + height - radius, radius)
+ ctx.lineTo(x, y + radius)
+ ctx.arcTo(x, y, x + radius, y, radius)
+ ctx.closePath()
+
+ // create rotating gradient effect
+ position = (position + 0.016) % (Math.PI * 2)
+
+ const centerX = canvas.width / 2
+ const centerY = canvas.height / 2
+ const blueColor = "70, 130, 255"
+
+ // create rotating gradient
+ const gradient = ctx.createConicGradient(position, centerX, centerY)
+
+ // add color stops for a single flowing stream
+ gradient.addColorStop(0, `rgba(${blueColor}, 0)`)
+ gradient.addColorStop(0.2, `rgba(${blueColor}, 0.8)`)
+ gradient.addColorStop(0.4, `rgba(${blueColor}, 0)`)
+ gradient.addColorStop(1, `rgba(${blueColor}, 0)`)
+
+ // apply gradient
+ ctx.strokeStyle = gradient
+ ctx.lineWidth = 1.5
+ ctx.stroke()
+
+ // add subtle glow effect
+ ctx.shadowColor = `rgba(${blueColor}, 0.6)`
+ ctx.shadowBlur = 5
+ ctx.strokeStyle = `rgba(${blueColor}, 0.3)`
+ ctx.lineWidth = 0.5
+ ctx.stroke()
+
+ animationId = requestAnimationFrame(animate)
+ }
+
+ animate()
+
+ return () => {
+ window.removeEventListener("resize", updateCanvasSize)
+ if (animationId) cancelAnimationFrame(animationId)
+ }
+ }, [])
+
+ return (
+ <>
+
+
+
{
+ e.preventDefault()
+ setIsOpen(true)
+ }}
+ className="relative z-10 flex items-center space-x-2 rounded-full bg-black px-4 py-2 text-sm font-medium text-white transition-all hover:bg-gray-900">
+
See what's new in {version}
+
+
+
+
+
+ {isOpen && (
+ <>
+
+ setIsOpen(false)}>
+
+
{
+ // prevent clicks inside the panel from closing it
+ e.stopPropagation()
+ }}>
+
+
+ What's New in Roo Code {version}
+
+ setIsOpen(false)}
+ className="flex-shrink-0 rounded-full p-1.5 text-gray-400 hover:bg-gray-800 hover:text-white">
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ >
+ )
+}
diff --git a/apps/website/src/components/providers/index.ts b/apps/website/src/components/providers/index.ts
new file mode 100644
index 0000000000..a02b0db6f8
--- /dev/null
+++ b/apps/website/src/components/providers/index.ts
@@ -0,0 +1 @@
+export { Providers } from "./providers"
diff --git a/apps/website/src/components/providers/posthog-provider.tsx b/apps/website/src/components/providers/posthog-provider.tsx
new file mode 100644
index 0000000000..a0c23cf989
--- /dev/null
+++ b/apps/website/src/components/providers/posthog-provider.tsx
@@ -0,0 +1,78 @@
+"use client"
+
+import { usePathname, useSearchParams } from "next/navigation"
+import posthog from "posthog-js"
+import { PostHogProvider as OriginalPostHogProvider } from "posthog-js/react"
+import { useEffect, Suspense } from "react"
+
+// Create a separate component for analytics tracking that uses useSearchParams
+function PageViewTracker() {
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+
+ // Track page views
+ useEffect(() => {
+ // Only track page views if PostHog is properly initialized
+ if (pathname && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
+ let url = window.location.origin + pathname
+ if (searchParams && searchParams.toString()) {
+ url = url + `?${searchParams.toString()}`
+ }
+ posthog.capture("$pageview", {
+ $current_url: url,
+ })
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [pathname, searchParams.toString()])
+
+ return null
+}
+
+export function PostHogProvider({ children }: { children: React.ReactNode }) {
+ useEffect(() => {
+ // Initialize PostHog only on the client side
+ if (typeof window !== "undefined") {
+ const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY
+ const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST
+
+ // Check if environment variables are set
+ if (!posthogKey) {
+ console.warn(
+ "PostHog API key is missing. Analytics will be disabled. " +
+ "Please set NEXT_PUBLIC_POSTHOG_KEY in your .env file.",
+ )
+ return
+ }
+
+ if (!posthogHost) {
+ console.warn(
+ "PostHog host URL is missing. Using default host. " +
+ "Please set NEXT_PUBLIC_POSTHOG_HOST in your .env file.",
+ )
+ }
+
+ posthog.init(posthogKey, {
+ api_host: posthogHost || "https://us.i.posthog.com",
+ capture_pageview: false, // We'll handle this manually
+ loaded: (posthogInstance) => {
+ if (process.env.NODE_ENV === "development") {
+ // Log to console in development
+ posthogInstance.debug()
+ }
+ },
+ respect_dnt: true, // Respect Do Not Track
+ })
+ }
+
+ // No explicit cleanup needed for posthog-js v1.231.0
+ }, [])
+
+ return (
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/apps/website/src/components/providers/providers.tsx b/apps/website/src/components/providers/providers.tsx
new file mode 100644
index 0000000000..a0e77b38e2
--- /dev/null
+++ b/apps/website/src/components/providers/providers.tsx
@@ -0,0 +1,20 @@
+"use client"
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
+import { ThemeProvider } from "next-themes"
+
+import { PostHogProvider } from "./posthog-provider"
+
+const queryClient = new QueryClient()
+
+export const Providers = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/apps/website/src/components/ui/button.tsx b/apps/website/src/components/ui/button.tsx
new file mode 100644
index 0000000000..18324ad791
--- /dev/null
+++ b/apps/website/src/components/ui/button.tsx
@@ -0,0 +1,47 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return
+ },
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/apps/website/src/components/ui/chart.tsx b/apps/website/src/components/ui/chart.tsx
new file mode 100644
index 0000000000..4acea86503
--- /dev/null
+++ b/apps/website/src/components/ui/chart.tsx
@@ -0,0 +1,311 @@
+"use client"
+
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & ({ color?: string; theme?: never } | { color?: never; theme: Record })
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a ")
+ }
+
+ return context
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps["children"]
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+
+
+
+ {children}
+
+
+ )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color)
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+