Files
heicode/website/app/docs/page.tsx
T

444 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import Link from "next/link";
import { ArrowLeft, ArrowRight, Cable, LockKeyhole, Network, CirclePlay, CheckCircle2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useLang } from "@/lib/language";
const MANAGER_URL = "https://code.xinghanlab.com/";
/* ─── i18n ─── */
const zh = {
back: "返回首页",
start: "开始使用",
nav: [
{ href: "#product", label: "产品说明" },
{ href: "#quickstart", label: "快速上手" },
{ href: "#resource-binding", label: "资源绑定" },
{ href: "#secret-vault", label: "密钥保管器" },
{ href: "#codegw", label: "模型网关" },
{ href: "#demo", label: "产品演示" },
],
headerBadge: "产品文档",
headerTitle: "Heicode 说明与指南",
headerDesc: "从一个想法,到可上线的软件产品。了解 Heicode 如何组织 AI 开发团队,在权限和审计边界内完成完整的软件生命周期。",
// product
productBadge: "产品说明",
productH2: "Heicode 是什么",
productDesc: "Heicode 是面向全流程智能开发的 SaaS 平台。用户注册登录后,可以从一个产品想法开始,接入自己的代码仓库、项目文档、SK 技能和云资源,由平台生成 AI 开发团队,完成需求、开发、检查、部署、观测和后续维护。",
productCards: [
{ title: "Heicode 客户端(主体验)", desc: "本地对话、输入想法、任务推进、查看执行反馈、接收交付结果、审批高危操作。登录 Heicode 即可,无需配置模型或 API Key。" },
{ title: "Heicode Manager(辅助控制台)", desc: "浏览器端。账号与安全、客户端下载、Git 与云资源绑定、Agnet 部署、任务状态、模型余额与用量、审计日志。" },
{ title: "Agnet 平台(AI 执行层)", desc: "运行子 Agnet,按需求→开发→测试→修复→部署子环节持续推进任务,可调用 SK 工具,结果回传到客户端。" },
{ title: "CodeGW(模型网关)", desc: "统一路由用户模型请求,管理额度、用量和计费。普通用户无需直接接触,客户端展示的模型由 Heicode 定义。" },
],
// quickstart
qsBadge: "快速上手",
qsH2: "五步开始使用",
qsDesc: "从注册到第一个任务运行,全程不超过 10 分钟。",
qsHint: "提示:",
qsBtn: "立即开始",
qsSteps: [
{ step: "01", title: "注册并登录", desc: "访问 code.xinghanlab.com,完成账号注册。登录后进入 Heicode 工作区。", note: "平台支持邮箱登录,后续可绑定 GitHub 账号。" },
{ step: "02", title: "下载客户端", desc: "在控制台进入「客户端下载」,选择 macOS 或 Windows 版本下载安装。", note: "macOS 首次安装若出现安全提示,按平台说明操作即可正常打开。" },
{ step: "03", title: "在客户端登录", desc: "打开客户端,点击「登录」,跳转浏览器完成 OAuth 授权后自动返回。", note: "客户端不需要手动输入 API Key,登录 Heicode 即激活所有模型能力。" },
{ step: "04", title: "输入第一个想法", desc: "在客户端对话框描述你想做的事情:产品功能、技术栈、部署目标等。Heicode 生成需求摘要和任务草案。", note: "不需要写完整 PRD,用自然语言描述即可,后续可随时补充。" },
{ step: "05", title: "绑定资源,启动执行", desc: "在 Manager 控制台绑定 Git 仓库、云账号或工具包,确认权限范围后 Agnet 开始执行。", note: "高危操作(生产部署、密钥访问等)会在客户端弹出审批,需明确确认。" },
],
// resource binding
rbBadge: "资源绑定",
rbH2: "Resource Binding",
rbDesc: "一次授权,全流程复用。Heicode 把用户授权的 Git 仓库、云账号、SK 工具包和项目文档统一保存为资源绑定记录。Agnet 在任务执行中按需申请使用,权限有边界,操作有记录。",
rbTh: ["资源类型", "说明", "授权方式"],
rbRows: [
["Git 仓库", "代码仓库,拉取、提交和 PR", "OAuth 或 Access Token"],
["云账号", "Azure / AWS / GCP 等云资源", "服务账号或云平台 OAuth"],
["SK 工具包", "行业技能包或自定义工具集", "仓库地址 + 授权"],
["项目文档", "需求、设计、规格等文档", "文件上传或仓库路径"],
],
rbPermLabel: "权限控制:",
rbPermDesc: "每次资源绑定都会记录授权范围。Agnet 只能访问任务对应的资源,不能跨任务访问。高危操作(生产环境写入、密钥轮换)需要客户端审批后才能执行。",
// secret vault
svBadge: "密钥保管器",
svH2: "Secret Vault",
svDesc: "长期凭证不存在数据库里,也不出现在日志和代码中。Heicode 把所有长期密钥保存在 OpenBao 密钥保管器中,数据库只保存不含密钥明文的引用(secret_ref)。",
svCards: [
{ title: "长期密钥安全存放", desc: "Git Token、云访问密钥、数据库连接串等长期凭证全部进入 OpenBao,不落入业务数据库。" },
{ title: "Agnet 只拿短期凭证", desc: "子 Agnet 执行任务时,按审批结果派生最小权限的短期凭证,TTL 到期或任务结束后自动失效。" },
{ title: "前端不展示明文", desc: "控制台和客户端只展示脱敏引用和状态(如「已绑定」「已过期」),不返回密钥明文。" },
],
svLifecycleTitle: "凭证生命周期",
svLifecycle: [
"用户在 Manager 完成资源授权",
"Heicode Secret Broker 把凭证写入 OpenBao",
"数据库保存 secret_ref(不含明文)",
"任务执行时,经客户端审批后生成短期凭证",
"短期凭证注入 Agnet 运行时,TTL 到期失效",
"所有访问记录写入审计日志",
],
// codegw
cgBadge: "模型网关",
cgH2: "CodeGW Boundary",
cgDesc: "所有模型调用统一经过 CodeGW 路由。用户在客户端看到的是 Heicode 提供的模型,不感知底层提供商。额度、用量、计费和速率限制在网关层集中管控。",
cgPathLabel: "请求路径",
cgPath: [
{ label: "Heicode 客户端 / Agnet", sub: "发起模型请求" },
{ label: "CodeGW", sub: "路由 · 鉴权 · 额度检查 · 日志" },
{ label: "底层模型提供商", sub: "Claude · GPT · 其他" },
],
cgFeatures: [
{ title: "用户不感知底层提供商", desc: "客户端展示的模型名称由 Heicode 定义,不暴露底层 Claude、GPT 或其他提供商。" },
{ title: "额度统一管控", desc: "每次调用的 token 消耗记录在 CodeGW,用户可在 Manager 查看用量和余额。" },
{ title: "速率限制", desc: "网关层按用户等级和订阅类型设置请求速率,防止滥用。" },
{ title: "用量审计", desc: "所有模型调用可在 Manager 审计日志中查询,含时间、模型、token 用量。" },
],
// demo
demoBadge: "产品演示",
demoH2: "Product Demo",
demoDesc: "完整演示流程,展示从登录到任务执行的全链路体验。",
demoSteps: [
{ time: "第一步", title: "登录 Heicode", actions: ["打开 code.xinghanlab.com", "输入账号密码完成登录", "进入工作区首页,查看资源状态和模型余额"], note: "用户只登录 Heicode,不需要接触 CodeGW 后台或密钥保管器。" },
{ time: "第二步", title: "下载并打开客户端", actions: ["在控制台找到「客户端下载」", "下载对应系统版本,安装后打开", "在客户端点击登录,授权后自动进入工作区"], note: "客户端登录会跳转浏览器完成 OAuth 授权,无需手动输入 API Key。" },
{ time: "第三步", title: "输入产品想法", actions: ["在客户端对话框描述要做的产品", "例如:「做一个面向小团队的任务管理工具,需要登录、项目、任务和评论,部署到 Azure」", "Heicode 生成需求摘要和推荐资源清单"], note: "描述越具体越好,包含技术偏好、规模预期和部署目标。" },
{ time: "第四步", title: "绑定资源,授权执行", actions: ["根据推荐清单,在 Manager 绑定 Git 仓库和云账号", "确认 Agnet 可使用的资源范围", "Heicode 生成任务计划,Agnet 开始执行"], note: "绑定时只需完成 OAuth 或填写 Token,凭证由平台托管。" },
{ time: "第五步", title: "审批与交付", actions: ["查看 Agnet 执行进度和中间反馈", "生产部署时,客户端弹出审批提示,确认操作范围", "审批通过后,Agnet 完成部署并回传交付结果"], note: "所有高危操作都需用户明确审批,审批记录写入审计日志。" },
],
demoCtaTitle: "准备好了吗?",
demoCtaDesc: "注册 Heicode,10 分钟内完成第一个任务。",
demoCtaBtn1: "立即注册",
demoCtaBtn2: "打开 Manager",
};
const en = {
back: "Back to home",
start: "Get started",
nav: [
{ href: "#product", label: "Product" },
{ href: "#quickstart", label: "Quick start" },
{ href: "#resource-binding", label: "Resource binding" },
{ href: "#secret-vault", label: "Secret vault" },
{ href: "#codegw", label: "Model GW" },
{ href: "#demo", label: "Demo" },
],
headerBadge: "Product docs",
headerTitle: "Heicode Guide",
headerDesc: "From an idea to a shipped software product. Learn how Heicode orchestrates an AI dev team to complete the full software lifecycle within permission and audit boundaries.",
productBadge: "Product overview",
productH2: "What is Heicode",
productDesc: "Heicode is a full-lifecycle intelligent development SaaS platform. After registering and logging in, users can start from a product idea, connect their code repos, project docs, SK skills and cloud resources, and have the platform generate an AI dev team to handle requirements, development, review, deployment, monitoring and ongoing maintenance.",
productCards: [
{ title: "Heicode Client (main experience)", desc: "Local conversations, input ideas, push tasks forward, view execution feedback, receive delivery results, approve high-risk operations. Log in to Heicode — no model or API Key configuration needed." },
{ title: "Heicode Manager (aux console)", desc: "Browser-based. Account & security, client download, Git & cloud resource binding, Agnet deployment, task status, model balance & usage, audit logs." },
{ title: "Agnet Platform (AI execution layer)", desc: "Runs sub-Agnets that continuously push tasks forward through plan→dev→test→fix→deploy, calling SK tools as needed, results sent back to the client." },
{ title: "CodeGW (model gateway)", desc: "Unified routing for model requests, managing quotas, usage and billing. Regular users don't interact with it directly — client-visible models are defined by Heicode." },
],
qsBadge: "Quick start",
qsH2: "Five steps to get started",
qsDesc: "From registration to your first running task, all in under 10 minutes.",
qsHint: "Tip: ",
qsBtn: "Get started now",
qsSteps: [
{ step: "01", title: "Register & log in", desc: "Visit code.xinghanlab.com and create an account. After login, enter the Heicode workspace.", note: "Email login is supported; you can bind a GitHub account later." },
{ step: "02", title: "Download the client", desc: "Go to 'Client Download' in the console, select macOS or Windows, download and install.", note: "On macOS, if a security prompt appears on first launch, follow the platform instructions to open normally." },
{ step: "03", title: "Log in on the client", desc: "Open the client, click 'Login', complete OAuth authorization in the browser — it returns automatically.", note: "No manual API Key input needed. Logging into Heicode activates all model capabilities." },
{ step: "04", title: "Input your first idea", desc: "Describe what you want to build in the client dialog: product features, tech stack, deployment target, etc. Heicode generates a requirements summary and task draft.", note: "No full PRD required — natural language works fine, and you can add details anytime." },
{ step: "05", title: "Bind resources & launch", desc: "In the Manager console, bind Git repos, cloud accounts or toolkits. Confirm permission scope and Agnet starts executing.", note: "High-risk operations (prod deployment, secret access, etc.) trigger approval prompts in the client." },
],
rbBadge: "Resource Binding",
rbH2: "Resource Binding",
rbDesc: "Authorize once, reuse throughout the full lifecycle. Heicode saves user-authorized Git repos, cloud accounts, SK toolkits and project docs as resource binding records. Agnets request access per-task, with bounded permissions and logged operations.",
rbTh: ["Resource type", "Description", "Auth method"],
rbRows: [
["Git repo", "Code repository — pull, commit and PR", "OAuth or Access Token"],
["Cloud account", "Azure / AWS / GCP cloud resources", "Service account or cloud OAuth"],
["SK toolkit", "Industry skill packs or custom toolsets", "Repo URL + authorization"],
["Project docs", "Requirements, design, spec docs", "File upload or repo path"],
],
rbPermLabel: "Permission control: ",
rbPermDesc: "Each resource binding records its authorization scope. Agnets can only access resources for their assigned task, not cross-task. High-risk operations (production writes, key rotation) require client approval before execution.",
svBadge: "Secret Vault",
svH2: "Secret Vault",
svDesc: "Long-term credentials never exist in the database, logs or code. Heicode stores all long-term secrets in an OpenBao secret vault; the database only stores references without secret plaintext (secret_ref).",
svCards: [
{ title: "Secure long-term secret storage", desc: "Git tokens, cloud access keys, DB connection strings — all long-term credentials go into OpenBao, never into the business database." },
{ title: "Agnets only get short-term creds", desc: "Sub-Agnets derive least-privilege short-term credentials per approval result. TTL expiry or task completion auto-revokes them." },
{ title: "No plaintext on frontend", desc: "The console and client only show masked references and status (e.g. 'Bound', 'Expired'), never returning secret plaintext." },
],
svLifecycleTitle: "Credential lifecycle",
svLifecycle: [
"User completes resource authorization in Manager",
"Heicode Secret Broker writes credentials to OpenBao",
"Database stores secret_ref (no plaintext)",
"At task execution, short-term creds generated after client approval",
"Short-term creds injected into Agnet runtime, TTL auto-expires",
"All access records written to audit log",
],
cgBadge: "Model Gateway",
cgH2: "CodeGW Boundary",
cgDesc: "All model calls route through CodeGW. Users see Heicode-defined models in the client, never the underlying provider. Quotas, usage, billing and rate limits are centrally managed at the gateway layer.",
cgPathLabel: "Request path",
cgPath: [
{ label: "Heicode Client / Agnet", sub: "Initiates model request" },
{ label: "CodeGW", sub: "Routing · Auth · Quota check · Logging" },
{ label: "Underlying model provider", sub: "Claude · GPT · Others" },
],
cgFeatures: [
{ title: "Users don't see the underlying provider", desc: "Model names shown in the client are defined by Heicode, never exposing Claude, GPT or others." },
{ title: "Unified quota management", desc: "Token consumption per call is recorded in CodeGW; users can view usage and balance in Manager." },
{ title: "Rate limiting", desc: "The gateway layer sets request rates by user tier and subscription type to prevent abuse." },
{ title: "Usage audit", desc: "All model calls can be queried in Manager audit logs, including time, model and token usage." },
],
demoBadge: "Product Demo",
demoH2: "Product Demo",
demoDesc: "Complete demo flow showing the full journey from login to task execution.",
demoSteps: [
{ time: "Step 1", title: "Log in to Heicode", actions: ["Open code.xinghanlab.com", "Enter credentials to log in", "View workspace home, resource status and model balance"], note: "Users only log into Heicode — no need to touch CodeGW backend or secret vault." },
{ time: "Step 2", title: "Download & open client", actions: ["Find 'Client Download' in the console", "Download the version for your OS, install and open", "Click login in the client, authorize and enter workspace automatically"], note: "Client login redirects to browser for OAuth — no manual API Key input needed." },
{ time: "Step 3", title: "Input product idea", actions: ["Describe the product you want to build in the client dialog", "Example: 'Build a task management tool for small teams with login, projects, tasks and comments, deploy to Azure'", "Heicode generates requirements summary and recommended resource list"], note: "The more specific the better — include tech preferences, scale expectations and deployment targets." },
{ time: "Step 4", title: "Bind resources & authorize", actions: ["Based on recommended list, bind Git repos and cloud accounts in Manager", "Confirm the scope of resources Agnet can use", "Heicode generates task plan, Agnet starts executing"], note: "Binding only requires OAuth or Token entry — credentials are managed by the platform." },
{ time: "Step 5", title: "Approve & deliver", actions: ["View Agnet execution progress and intermediate feedback", "For production deployment, client shows approval prompt to confirm scope", "After approval, Agnet completes deployment and returns delivery results"], note: "All high-risk operations require explicit user approval; approval records are written to audit logs." },
],
demoCtaTitle: "Ready?",
demoCtaDesc: "Register Heicode and complete your first task in 10 minutes.",
demoCtaBtn1: "Register now",
demoCtaBtn2: "Open Manager",
};
export default function DocsPage() {
const { lang, toggle } = useLang();
const T = lang === "en" ? en : zh;
return (
<div className="min-h-screen bg-background">
{/* Top bar */}
<div className="sticky top-0 z-40 border-b border-border/50 bg-background/90 backdrop-blur-md">
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
<Link href="/" className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors">
<ArrowLeft className="h-4 w-4" />
{T.back}
</Link>
<div className="hidden items-center gap-6 text-sm md:flex">
{T.nav.map((item) => (
<a key={item.href} href={item.href} className="text-muted-foreground hover:text-foreground transition-colors">{item.label}</a>
))}
</div>
<div className="flex items-center gap-3">
<button onClick={toggle} className="rounded-md border border-border/60 px-2.5 py-1 font-mono text-xs text-muted-foreground transition-colors hover:border-accent/50 hover:text-foreground">
{lang === "zh" ? "EN" : "中"}
</button>
<Button size="sm" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{T.start}
<ArrowRight className="ml-1.5 h-3.5 w-3.5" />
</a>
</Button>
</div>
</div>
</div>
<div className="mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8">
{/* Header */}
<div className="mb-16">
<p className="text-sm font-medium uppercase tracking-widest text-accent">{T.headerBadge}</p>
<h1 className="mt-3 text-4xl font-bold tracking-tight sm:text-5xl">{T.headerTitle}</h1>
<p className="mt-4 max-w-2xl text-lg text-muted-foreground leading-relaxed">{T.headerDesc}</p>
</div>
{/* ── Product ── */}
<section id="product" className="mb-20 scroll-mt-20">
<Label>{T.productBadge}</Label>
<h2 className="mt-3 text-2xl font-bold sm:text-3xl">{T.productH2}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{T.productDesc}</p>
<div className="mt-8 grid gap-4 sm:grid-cols-2">
{T.productCards.map((c) => (
<div key={c.title} className="rounded-xl border border-border bg-card/50 p-5">
<h3 className="font-semibold">{c.title}</h3>
<p className="mt-2 text-sm text-muted-foreground leading-relaxed">{c.desc}</p>
</div>
))}
</div>
</section>
<Divider />
{/* ── Quick start ── */}
<section id="quickstart" className="mb-20 scroll-mt-20">
<Label>{T.qsBadge}</Label>
<h2 className="mt-3 text-2xl font-bold sm:text-3xl">{T.qsH2}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{T.qsDesc}</p>
<div className="mt-8 space-y-4">
{T.qsSteps.map((s) => (
<div key={s.step} className="flex gap-5 rounded-xl border border-border bg-card/50 p-6">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-secondary font-mono text-sm font-bold text-accent">{s.step}</div>
<div className="min-w-0">
<h3 className="font-semibold">{s.title}</h3>
<p className="mt-1 text-sm text-muted-foreground leading-relaxed">{s.desc}</p>
<p className="mt-2 rounded-lg bg-secondary/60 px-3 py-2 text-xs text-muted-foreground">{T.qsHint}{s.note}</p>
</div>
</div>
))}
</div>
<div className="mt-8 flex justify-center">
<Button asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">{T.qsBtn}<ArrowRight className="ml-2 h-4 w-4" /></a>
</Button>
</div>
</section>
<Divider />
{/* ── Resource Binding ── */}
<section id="resource-binding" className="mb-20 scroll-mt-20">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent/10 text-accent"><Cable className="h-5 w-5" /></div>
<Label className="mt-0">{T.rbBadge}</Label>
</div>
<h2 className="mt-3 text-2xl font-bold sm:text-3xl">{T.rbH2}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{T.rbDesc}</p>
<div className="mt-6 overflow-hidden rounded-xl border border-border">
<table className="w-full text-sm">
<thead><tr className="border-b border-border bg-card/70">{T.rbTh.map((h) => <th key={h} className="px-5 py-3 text-left font-medium">{h}</th>)}</tr></thead>
<tbody className="divide-y divide-border">
{T.rbRows.map(([type, desc, auth]) => (
<tr key={type} className="bg-card/20">
<td className="px-5 py-3 font-mono text-xs font-medium">{type}</td>
<td className="px-5 py-3 text-muted-foreground">{desc}</td>
<td className="px-5 py-3 text-muted-foreground">{auth}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-6 rounded-xl border border-border bg-card/30 p-5 text-sm text-muted-foreground leading-relaxed">
<span className="font-medium text-foreground">{T.rbPermLabel}</span>{T.rbPermDesc}
</div>
</section>
<Divider />
{/* ── Secret Vault ── */}
<section id="secret-vault" className="mb-20 scroll-mt-20">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent/10 text-accent"><LockKeyhole className="h-5 w-5" /></div>
<Label className="mt-0">{T.svBadge}</Label>
</div>
<h2 className="mt-3 text-2xl font-bold sm:text-3xl">{T.svH2}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{T.svDesc}</p>
<div className="mt-8 grid gap-4 sm:grid-cols-3">
{T.svCards.map((c) => (
<div key={c.title} className="rounded-xl border border-border bg-card/50 p-5">
<h3 className="text-sm font-semibold">{c.title}</h3>
<p className="mt-2 text-xs text-muted-foreground leading-relaxed">{c.desc}</p>
</div>
))}
</div>
<div className="mt-6 rounded-xl border border-border bg-card/30 p-5">
<h3 className="mb-3 text-sm font-semibold">{T.svLifecycleTitle}</h3>
<div className="space-y-2 text-sm text-muted-foreground">
{T.svLifecycle.map((step, i) => (
<div key={i} className="flex items-start gap-3">
<span className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-secondary font-mono text-xs font-bold">{i + 1}</span>
<span>{step}</span>
</div>
))}
</div>
</div>
</section>
<Divider />
{/* ── CodeGW ── */}
<section id="codegw" className="mb-20 scroll-mt-20">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent/10 text-accent"><Network className="h-5 w-5" /></div>
<Label className="mt-0">{T.cgBadge}</Label>
</div>
<h2 className="mt-3 text-2xl font-bold sm:text-3xl">{T.cgH2}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{T.cgDesc}</p>
<div className="mt-6 overflow-hidden rounded-xl border border-border">
<div className="bg-card/70 px-5 py-3 text-sm font-medium">{T.cgPathLabel}</div>
<div className="p-6">
<div className="flex flex-col items-center gap-2 text-sm">
{T.cgPath.map((item, i, arr) => (
<div key={item.label} className="flex w-full flex-col items-center gap-2">
<div className="w-full max-w-sm rounded-lg border border-border bg-secondary/40 px-4 py-3 text-center">
<p className="font-medium">{item.label}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{item.sub}</p>
</div>
{i < arr.length - 1 && <span className="text-muted-foreground/50">↓</span>}
</div>
))}
</div>
</div>
</div>
<div className="mt-6 grid gap-4 sm:grid-cols-2">
{T.cgFeatures.map((f) => (
<div key={f.title} className="flex gap-4 rounded-xl border border-border bg-card/50 p-5">
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-accent" />
<div>
<h3 className="text-sm font-semibold">{f.title}</h3>
<p className="mt-1 text-xs text-muted-foreground leading-relaxed">{f.desc}</p>
</div>
</div>
))}
</div>
</section>
<Divider />
{/* ── Demo ── */}
<section id="demo" className="mb-20 scroll-mt-20">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent/10 text-accent"><CirclePlay className="h-5 w-5" /></div>
<Label className="mt-0">{T.demoBadge}</Label>
</div>
<h2 className="mt-3 text-2xl font-bold sm:text-3xl">{T.demoH2}</h2>
<p className="mt-4 text-muted-foreground leading-relaxed">{T.demoDesc}</p>
<div className="mt-8 space-y-0">
{T.demoSteps.map((item, i, arr) => (
<div key={item.time} className="flex gap-5">
<div className="flex flex-col items-center">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 border-accent/40 bg-background font-mono text-xs font-bold text-accent">{i + 1}</div>
{i < arr.length - 1 && <div className="mt-1 h-full min-h-[2rem] w-px bg-border" />}
</div>
<div className="mb-6 min-w-0 flex-1 pb-2">
<p className="text-xs font-medium uppercase tracking-wider text-accent">{item.time}</p>
<h3 className="mt-1 text-base font-semibold">{item.title}</h3>
<ul className="mt-3 space-y-1.5">
{item.actions.map((a) => (
<li key={a} className="flex items-start gap-2 text-sm text-muted-foreground">
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent" />{a}
</li>
))}
</ul>
<p className="mt-3 rounded-lg bg-secondary/60 px-3 py-2 text-xs text-muted-foreground">{item.note}</p>
</div>
</div>
))}
</div>
<div className="mt-4 rounded-xl border border-accent/30 bg-accent/5 p-8 text-center">
<h3 className="text-xl font-bold">{T.demoCtaTitle}</h3>
<p className="mt-2 text-sm text-muted-foreground">{T.demoCtaDesc}</p>
<div className="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
<Button size="lg" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">{T.demoCtaBtn1}<ArrowRight className="ml-2 h-4 w-4" /></a>
</Button>
<Button variant="outline" size="lg" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">{T.demoCtaBtn2}</a>
</Button>
</div>
</div>
</section>
</div>
</div>
);
}
function Label({ children, className = "" }: { children: React.ReactNode; className?: string }) {
return <p className={`text-sm font-medium uppercase tracking-widest text-accent ${className}`}>{children}</p>;
}
function Divider() {
return <hr className="mb-20 border-border" />;
}