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 ( +
+
+ +
+ H +
+ Heicode + + + {/* Desktop Navigation */} + + +
+ + +
+ + {/* Mobile Menu Button */} + +
+ + {/* Mobile Navigation */} + {mobileMenuOpen && ( +
+ +
+ )} +
+ ); +} 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

+
+ +
+

同一身份与策略边界

+
+ +
+
+

Heicode Manager

+
+
+ ↔ +
+
+

Heicode 客户端

+
+
+ +
+ +
+

资产与环境

+

仓库 · 流水线 · 运行环境与观测

+
+
+
+
+
+
+ ); +} 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