diff --git a/website/.dockerignore b/website/.dockerignore
deleted file mode 100644
index 4a246ec6..00000000
--- a/website/.dockerignore
+++ /dev/null
@@ -1,2 +0,0 @@
-Dockerfile
-.dockerignore
diff --git a/website/.gitignore b/website/.gitignore
new file mode 100644
index 00000000..edfbcc72
--- /dev/null
+++ b/website/.gitignore
@@ -0,0 +1,16 @@
+# v0 sandbox internal files
+__v0_runtime_loader.js
+__v0_devtools.tsx
+__v0_jsx-dev-runtime.ts
+.snowflake/
+.v0-trash/
+.vercel/
+next.user-config.*
+
+# Environment variables
+.env*.local
+
+# Common ignores
+node_modules
+.next/
+.DS_Store
\ No newline at end of file
diff --git a/website/Dockerfile b/website/Dockerfile
deleted file mode 100644
index ff024152..00000000
--- a/website/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM nginx:alpine
-COPY nginx.conf /etc/nginx/conf.d/default.conf
-COPY . /usr/share/nginx/html
diff --git a/website/app/globals.css b/website/app/globals.css
new file mode 100644
index 00000000..1f519be5
--- /dev/null
+++ b/website/app/globals.css
@@ -0,0 +1,125 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: oklch(0.07 0 0);
+ --foreground: oklch(0.98 0 0);
+ --card: oklch(0.12 0 0);
+ --card-foreground: oklch(0.98 0 0);
+ --popover: oklch(0.12 0 0);
+ --popover-foreground: oklch(0.98 0 0);
+ --primary: oklch(0.98 0 0);
+ --primary-foreground: oklch(0.07 0 0);
+ --secondary: oklch(0.18 0 0);
+ --secondary-foreground: oklch(0.98 0 0);
+ --muted: oklch(0.18 0 0);
+ --muted-foreground: oklch(0.6 0 0);
+ --accent: oklch(0.7 0.15 160);
+ --accent-foreground: oklch(0.98 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.22 0 0);
+ --input: oklch(0.22 0 0);
+ --ring: oklch(0.5 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.5rem;
+ --sidebar: oklch(0.1 0 0);
+ --sidebar-foreground: oklch(0.98 0 0);
+ --sidebar-primary: oklch(0.98 0 0);
+ --sidebar-primary-foreground: oklch(0.07 0 0);
+ --sidebar-accent: oklch(0.18 0 0);
+ --sidebar-accent-foreground: oklch(0.98 0 0);
+ --sidebar-border: oklch(0.22 0 0);
+ --sidebar-ring: oklch(0.5 0 0);
+}
+
+.dark {
+ --background: oklch(0.07 0 0);
+ --foreground: oklch(0.98 0 0);
+ --card: oklch(0.12 0 0);
+ --card-foreground: oklch(0.98 0 0);
+ --popover: oklch(0.12 0 0);
+ --popover-foreground: oklch(0.98 0 0);
+ --primary: oklch(0.98 0 0);
+ --primary-foreground: oklch(0.07 0 0);
+ --secondary: oklch(0.18 0 0);
+ --secondary-foreground: oklch(0.98 0 0);
+ --muted: oklch(0.18 0 0);
+ --muted-foreground: oklch(0.6 0 0);
+ --accent: oklch(0.7 0.15 160);
+ --accent-foreground: oklch(0.98 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.22 0 0);
+ --input: oklch(0.22 0 0);
+ --ring: oklch(0.5 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.1 0 0);
+ --sidebar-foreground: oklch(0.98 0 0);
+ --sidebar-primary: oklch(0.98 0 0);
+ --sidebar-primary-foreground: oklch(0.07 0 0);
+ --sidebar-accent: oklch(0.18 0 0);
+ --sidebar-accent-foreground: oklch(0.98 0 0);
+ --sidebar-border: oklch(0.22 0 0);
+ --sidebar-ring: oklch(0.5 0 0);
+}
+
+@theme inline {
+ --font-sans: 'Geist', 'Geist Fallback';
+ --font-mono: 'Geist Mono', 'Geist Mono Fallback';
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/website/app/layout.tsx b/website/app/layout.tsx
new file mode 100644
index 00000000..8667437c
--- /dev/null
+++ b/website/app/layout.tsx
@@ -0,0 +1,45 @@
+import type { Metadata } from 'next'
+import { Geist, Geist_Mono } from 'next/font/google'
+import { Analytics } from '@vercel/analytics/next'
+import './globals.css'
+
+const _geist = Geist({ subsets: ["latin"] });
+const _geistMono = Geist_Mono({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: 'Heicode - 团队软件交付智能体平台',
+ description: '让团队能以可复述的方式回答「谁在何时对什么负责」「依据是什么」「如何回溯」。智能体适合承接标准化、可重复的环节,人机分工与审批边界由组织策略定义。',
+ generator: 'v0.app',
+ icons: {
+ icon: [
+ {
+ url: '/icon-light-32x32.png',
+ media: '(prefers-color-scheme: light)',
+ },
+ {
+ url: '/icon-dark-32x32.png',
+ media: '(prefers-color-scheme: dark)',
+ },
+ {
+ url: '/icon.svg',
+ type: 'image/svg+xml',
+ },
+ ],
+ apple: '/apple-icon.png',
+ },
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+ {children}
+ {process.env.NODE_ENV === 'production' && }
+
+
+ )
+}
diff --git a/website/app/page.tsx b/website/app/page.tsx
new file mode 100644
index 00000000..e3341a70
--- /dev/null
+++ b/website/app/page.tsx
@@ -0,0 +1,25 @@
+import { Header } from "@/components/header";
+import { Hero } from "@/components/hero";
+import { Products } from "@/components/products";
+import { Features } from "@/components/features";
+import { Paradigm } from "@/components/paradigm";
+import { Principles } from "@/components/principles";
+import { Roadmap } from "@/components/roadmap";
+import { CTA } from "@/components/cta";
+import { Footer } from "@/components/footer";
+
+export default function HomePage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/website/components.json b/website/components.json
new file mode 100644
index 00000000..4ee62ee1
--- /dev/null
+++ b/website/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/website/components/cta.tsx b/website/components/cta.tsx
new file mode 100644
index 00000000..d35a90d3
--- /dev/null
+++ b/website/components/cta.tsx
@@ -0,0 +1,28 @@
+import { Button } from "@/components/ui/button";
+import { ArrowRight } from "lucide-react";
+
+export function CTA() {
+ return (
+
+
+
+
+ 准备好改变交付方式了吗?
+
+
+ 让团队能以可复述的方式回答——「谁在何时对什么负责」「依据是什么」「如何回溯」
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/website/components/features.tsx b/website/components/features.tsx
new file mode 100644
index 00000000..1b1977e2
--- /dev/null
+++ b/website/components/features.tsx
@@ -0,0 +1,70 @@
+import { Shield, Users, Eye, Workflow } from "lucide-react";
+
+const features = [
+ {
+ icon: Shield,
+ title: "身份与策略一元化",
+ description:
+ "从个人到团队,谁能访问何种模型与渠道、何种环境、何种仓库——应有清晰归属与审计预期,而不是散落在若干控制台口径不一致。",
+ },
+ {
+ icon: Workflow,
+ title: "编排与角色范式",
+ description:
+ "既支持个人工作流也支持团队协作:个人可单人执行全流程,团队可按阶段或迭代组织分工。",
+ },
+ {
+ icon: Eye,
+ title: "全生命周期可信交付",
+ description:
+ "从构想到运营,关键产物应能对齐到分支、标签或发布单元:规格、实现、验证、发布、运维与复盘之间有可追溯链路。",
+ },
+ {
+ icon: Users,
+ title: "平台化协作",
+ description:
+ "无论个人还是团队,客户端与 Manager 共同服务协作叙事——跨角色、跨会话、跨环境的一体化体验。",
+ },
+];
+
+export function Features() {
+ return (
+
+
+
+
+ 战略支柱
+
+
+ 四大核心能力
+
+
+ 个人开发者到多人团队,都能获得清晰的责任边界与可追溯的交付过程
+
+
+
+
+ {features.map((feature, index) => (
+
+
+
+
+
+
{feature.title}
+
+ {feature.description}
+
+
+
+ {String(index + 1).padStart(2, "0")}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/website/components/footer.tsx b/website/components/footer.tsx
new file mode 100644
index 00000000..c8d63d44
--- /dev/null
+++ b/website/components/footer.tsx
@@ -0,0 +1,76 @@
+import Link from "next/link";
+
+const footerLinks = {
+ 产品: [
+ { label: "Heicode Manager", href: "#" },
+ { label: "Heicode 客户端", href: "#" },
+ { label: "定价", href: "#" },
+ ],
+ 资源: [
+ { label: "文档", href: "#" },
+ { label: "API 参考", href: "#" },
+ { label: "更新日志", href: "#" },
+ { label: "路线图", href: "#" },
+ ],
+ 公司: [
+ { label: "关于我们", href: "#" },
+ { label: "博客", href: "#" },
+ { label: "加入我们", href: "#" },
+ { label: "联系方式", href: "#" },
+ ],
+ 法律: [
+ { label: "隐私政策", href: "#" },
+ { label: "服务条款", href: "#" },
+ { label: "安全", href: "#" },
+ ],
+};
+
+export function Footer() {
+ return (
+
+ );
+}
diff --git a/website/components/header.tsx b/website/components/header.tsx
new file mode 100644
index 00000000..f0170363
--- /dev/null
+++ b/website/components/header.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { Menu, X } from "lucide-react";
+
+const navLinks = [
+ { href: "#products", label: "产品" },
+ { href: "#features", label: "能力" },
+ { href: "#paradigm", label: "范式" },
+ { href: "#principles", label: "原则" },
+];
+
+export function Header() {
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
+
+ return (
+
+ );
+}
diff --git a/website/components/hero.tsx b/website/components/hero.tsx
new file mode 100644
index 00000000..91d4b0b7
--- /dev/null
+++ b/website/components/hero.tsx
@@ -0,0 +1,79 @@
+import { Button } from "@/components/ui/button";
+import { ArrowRight, Play } from "lucide-react";
+
+export function Hero() {
+ return (
+
+ {/* Background Grid */}
+
+
+ {/* Gradient Glow */}
+
+
+
+
+
+ 个人与团队的软件交付智能体平台
+
+
+
+ 让交付过程
+
+ 可追溯、可复述
+
+
+
+ 无论是个人开发者还是多人团队,在复杂环境与迭代条件下,让对齐方式、责任边界与可追溯性默认成立。
+ 智能体承接标准化、可重复的环节,人机分工由个人或组织策略定义。
+
+
+
+
+
+
+
+
+ {/* Architecture Diagram Preview */}
+
+
+
+
+
人机入口
+
官网 · 控制台 · CLI / Desktop
+
+
+
+
同一身份与策略边界
+
+
+
+
+
+
+
+
资产与环境
+
仓库 · 流水线 · 运行环境与观测
+
+
+
+
+
+
+ );
+}
diff --git a/website/components/paradigm.tsx b/website/components/paradigm.tsx
new file mode 100644
index 00000000..ef2783eb
--- /dev/null
+++ b/website/components/paradigm.tsx
@@ -0,0 +1,127 @@
+import { CheckCircle2 } from "lucide-react";
+
+const paradigms = [
+ {
+ name: "个人开发者",
+ tagline: "单人工作流,完整的交付追踪",
+ description: "独立开发者也需要记录「做了什么」「为什么这样做」「如何验证」",
+ minTeam: 1,
+ maxTeam: 1,
+ roles: ["开发者(全栈角色)"],
+ features: [
+ "构想→实现→验证→发布",
+ "本地与远程一体化",
+ "关键产物自动对齐",
+ "自我审计与版本追踪",
+ ],
+ },
+ {
+ name: "瀑布范式",
+ tagline: "阶段闸门与可审计链条",
+ description: "适合强合规、长评审链的组织语境",
+ minTeam: 5,
+ maxTeam: 9,
+ roles: ["WF-BA 业务分析师", "WF-ARC 架构师", "WF-DEV 工程师", "WF-QA 测试", "WF-REL 发布运维"],
+ features: [
+ "规格→设计→实现→验证→上线",
+ "阶段闸门强、评审链长",
+ "关键闸门不被静默跳过",
+ "可扩展至 9 个专岗",
+ ],
+ },
+ {
+ name: "敏捷范式",
+ tagline: "短迭代与跨职能闭环",
+ description: "适合快速试错的产品语境",
+ minTeam: 3,
+ maxTeam: 8,
+ roles: ["AG-PO 产品负责人", "AG-DEV 工程师", "AG-QA 测试"],
+ features: [
+ "迭代短、反馈密",
+ "跨职能团队闭环",
+ "独立质量门禁",
+ "可扩展双轨并行",
+ ],
+ },
+];
+
+export function Paradigm() {
+ return (
+
+
+
+
+ 协作范式
+
+
+ 从个人到团队,灵活的协作模式
+
+
+ 无论规模如何,都能保持交付过程的可追溯与责任清晰
+
+
+
+
+ {paradigms.map((paradigm) => (
+
+
+
+
{paradigm.name}
+
{paradigm.tagline}
+
+
+
{paradigm.minTeam}-{paradigm.maxTeam}
+
执行单元
+
+
+
+
+ {paradigm.description}
+
+
+
+
+ 最小编队角色
+
+
+ {paradigm.roles.map((role) => (
+
+ {role}
+
+ ))}
+
+
+
+
+
+ 特征
+
+
+ {paradigm.features.map((feature) => (
+ -
+
+ {feature}
+
+ ))}
+
+
+
+ ))}
+
+
+
+
+ 超出协调上限时:
+ 拆分子系统、拆分 Squad 或引入共享平台能力,而不是在同一队列上无限堆角色
+
+
+
+
+ );
+}
diff --git a/website/components/principles.tsx b/website/components/principles.tsx
new file mode 100644
index 00000000..4eefdad5
--- /dev/null
+++ b/website/components/principles.tsx
@@ -0,0 +1,63 @@
+const principles = [
+ {
+ number: "01",
+ title: "可追溯优于单次极速",
+ description: "宁可多一道可查的记录,也不依赖口头默契替代闸门。",
+ },
+ {
+ number: "02",
+ title: "范式可裁剪",
+ description: "瀑布与敏捷是协作隐喻,不是教条;规模与角色应由组织工作坊裁剪,而非照搬单一数字。",
+ },
+ {
+ number: "03",
+ title: "集成可替换",
+ description: "Manager、客户端、编排平台、云与流水线均以「契约与边界」相接,避免叙事绑死在某一厂商。",
+ },
+ {
+ number: "04",
+ title: "人机协同",
+ description: "涉及权限、费用、生产变更与高敏数据的决策,默认保留人在回路;自动化扩展提案权,不默认无限代理权。",
+ },
+ {
+ number: "05",
+ title: "对内对外叙事分层",
+ description: "愿景文档不写实施说明书;角色代号与编队明细放入附录,以免喧宾夺主。",
+ },
+];
+
+export function Principles() {
+ return (
+
+
+
+
+ 指导原则
+
+
+ 我们坚持什么
+
+
+
+
+ {principles.map((principle) => (
+
+
+ {principle.number}
+
+
+
{principle.title}
+
+ {principle.description}
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/website/components/products.tsx b/website/components/products.tsx
new file mode 100644
index 00000000..8b271ba3
--- /dev/null
+++ b/website/components/products.tsx
@@ -0,0 +1,67 @@
+import { Layers, Terminal } from "lucide-react";
+
+const products = [
+ {
+ icon: Layers,
+ name: "Heicode Manager",
+ description:
+ "账户、模型与路由策略、计费与渠道等能力的网关及管理控制台。无论个人或团队,统一身份与策略,清晰归属与审计预期。",
+ features: ["身份管理", "策略配置", "模型路由", "计费管理"],
+ },
+ {
+ icon: Terminal,
+ name: "Heicode 客户端",
+ description:
+ "终端与桌面侧人机编程客户端及本地服务。与 Manager 协同,在统一策略边界下工作,适配个人与团队工作流。",
+ features: ["IDE 集成", "本地服务", "会话管理", "安全连接"],
+ },
+];
+
+export function Products() {
+ return (
+
+
+
+
+ 产品矩阵
+
+
+ 个人与团队通用的交付平台
+
+
+ 无论规模如何,Manager 与客户端共同服务于统一的交付故事线
+
+
+
+
+ {products.map((product) => (
+
+
+
+
{product.name}
+
+ {product.description}
+
+
+
+ {product.features.map((feature) => (
+
+ {feature}
+
+ ))}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/website/components/roadmap.tsx b/website/components/roadmap.tsx
new file mode 100644
index 00000000..6d25de8f
--- /dev/null
+++ b/website/components/roadmap.tsx
@@ -0,0 +1,65 @@
+const phases = [
+ {
+ phase: "对齐",
+ description: "身份、策略与叙事口径一致,避免「各说各话」",
+ status: "进行中",
+ },
+ {
+ phase: "贯通",
+ description: "人机链路在一条交付故事线下可走通,关键闸门有据可查",
+ status: "规划中",
+ },
+ {
+ phase: "规模化",
+ description: "编队模板与审批策略可按组织复制,而不是单次项目手工拼装",
+ status: "展望",
+ },
+];
+
+export function Roadmap() {
+ return (
+
+
+
+
+ 阶段性方向
+
+
+ 三层递进
+
+
+ 仅给出时间无序的递进意象,方便对齐讨论;不绑定季度、不设里程碑编号
+
+
+
+
+
+ {/* Connection Line */}
+
+
+
+ {phases.map((item, index) => (
+
+ {/* Circle Indicator */}
+
+ {index + 1}
+
+
+
+
+ {item.status}
+
+
{item.phase}
+
+ {item.description}
+
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/website/components/theme-provider.tsx b/website/components/theme-provider.tsx
new file mode 100644
index 00000000..55c2f6eb
--- /dev/null
+++ b/website/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/website/components/ui/accordion.tsx b/website/components/ui/accordion.tsx
new file mode 100644
index 00000000..e538a33b
--- /dev/null
+++ b/website/components/ui/accordion.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import * as React from 'react'
+import * as AccordionPrimitive from '@radix-ui/react-accordion'
+import { ChevronDownIcon } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180',
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/website/components/ui/alert-dialog.tsx b/website/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..97044526
--- /dev/null
+++ b/website/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+'use client'
+
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+import { cn } from '@/lib/utils'
+import { buttonVariants } from '@/components/ui/button'
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/website/components/ui/alert.tsx b/website/components/ui/alert.tsx
new file mode 100644
index 00000000..e6751abe
--- /dev/null
+++ b/website/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const alertVariants = cva(
+ 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
+ {
+ variants: {
+ variant: {
+ default: 'bg-card text-card-foreground',
+ destructive:
+ 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/website/components/ui/aspect-ratio.tsx b/website/components/ui/aspect-ratio.tsx
new file mode 100644
index 00000000..40bb1208
--- /dev/null
+++ b/website/components/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+export { AspectRatio }
diff --git a/website/components/ui/avatar.tsx b/website/components/ui/avatar.tsx
new file mode 100644
index 00000000..aa98465a
--- /dev/null
+++ b/website/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+'use client'
+
+import * as React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+
+import { cn } from '@/lib/utils'
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/website/components/ui/badge.tsx b/website/components/ui/badge.tsx
new file mode 100644
index 00000000..fc4126b7
--- /dev/null
+++ b/website/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+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 badgeVariants = cva(
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
+ secondary:
+ 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
+ destructive:
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'span'> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'span'
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/website/components/ui/breadcrumb.tsx b/website/components/ui/breadcrumb.tsx
new file mode 100644
index 00000000..1750ff26
--- /dev/null
+++ b/website/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { ChevronRight, MoreHorizontal } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
+ return
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : 'a'
+
+ return (
+
+ )
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<'li'>) {
+ return (
+ svg]:size-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/website/components/ui/button-group.tsx b/website/components/ui/button-group.tsx
new file mode 100644
index 00000000..09d44309
--- /dev/null
+++ b/website/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+import { Separator } from '@/components/ui/separator'
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ '[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
+ vertical:
+ 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
+ },
+ },
+ defaultVariants: {
+ orientation: 'horizontal',
+ },
+ },
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : 'div'
+
+ return (
+
+ )
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = 'vertical',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/website/components/ui/button.tsx b/website/components/ui/button.tsx
new file mode 100644
index 00000000..f64632d1
--- /dev/null
+++ b/website/components/ui/button.tsx
@@ -0,0 +1,60 @@
+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-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary:
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ ghost:
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ 'icon-sm': 'size-8',
+ 'icon-lg': 'size-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : 'button'
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/website/components/ui/calendar.tsx b/website/components/ui/calendar.tsx
new file mode 100644
index 00000000..eaa373e2
--- /dev/null
+++ b/website/components/ui/calendar.tsx
@@ -0,0 +1,213 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from 'lucide-react'
+import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker'
+
+import { cn } from '@/lib/utils'
+import { Button, buttonVariants } from '@/components/ui/button'
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = 'label',
+ buttonVariant = 'ghost',
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps['variant']
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className,
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString('default', { month: 'short' }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn('w-fit', defaultClassNames.root),
+ months: cn(
+ 'flex gap-4 flex-col md:flex-row relative',
+ defaultClassNames.months,
+ ),
+ month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
+ nav: cn(
+ 'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',
+ defaultClassNames.nav,
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
+ defaultClassNames.button_previous,
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
+ defaultClassNames.button_next,
+ ),
+ month_caption: cn(
+ 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
+ defaultClassNames.month_caption,
+ ),
+ dropdowns: cn(
+ 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
+ defaultClassNames.dropdowns,
+ ),
+ dropdown_root: cn(
+ 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
+ defaultClassNames.dropdown_root,
+ ),
+ dropdown: cn(
+ 'absolute bg-popover inset-0 opacity-0',
+ defaultClassNames.dropdown,
+ ),
+ caption_label: cn(
+ 'select-none font-medium',
+ captionLayout === 'label'
+ ? 'text-sm'
+ : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
+ defaultClassNames.caption_label,
+ ),
+ table: 'w-full border-collapse',
+ weekdays: cn('flex', defaultClassNames.weekdays),
+ weekday: cn(
+ 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
+ defaultClassNames.weekday,
+ ),
+ week: cn('flex w-full mt-2', defaultClassNames.week),
+ week_number_header: cn(
+ 'select-none w-(--cell-size)',
+ defaultClassNames.week_number_header,
+ ),
+ week_number: cn(
+ 'text-[0.8rem] select-none text-muted-foreground',
+ defaultClassNames.week_number,
+ ),
+ day: cn(
+ 'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
+ defaultClassNames.day,
+ ),
+ range_start: cn(
+ 'rounded-l-md bg-accent',
+ defaultClassNames.range_start,
+ ),
+ range_middle: cn('rounded-none', defaultClassNames.range_middle),
+ range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
+ today: cn(
+ 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
+ defaultClassNames.today,
+ ),
+ outside: cn(
+ 'text-muted-foreground aria-selected:text-muted-foreground',
+ defaultClassNames.outside,
+ ),
+ disabled: cn(
+ 'text-muted-foreground opacity-50',
+ defaultClassNames.disabled,
+ ),
+ hidden: cn('invisible', defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === 'left') {
+ return (
+
+ )
+ }
+
+ if (orientation === 'right') {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+