chore(web): move site under packages
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Documentation",
|
||||
description:
|
||||
"Configuration reference for Oh My OpenAgent. Agents, categories, skills, hooks, MCPs, and more.",
|
||||
}
|
||||
|
||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||
return children
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { DocsShell } from "@/components/docs/docs-shell"
|
||||
import { DOC_SECTIONS } from "@/lib/docs-sections"
|
||||
import { loadDocSource } from "@/lib/docs-source"
|
||||
|
||||
export default async function DocsPage() {
|
||||
const t = await getTranslations("docs")
|
||||
|
||||
const sectionsWithHtml = DOC_SECTIONS.map((section) => ({
|
||||
...section,
|
||||
html: loadDocSource(section.file),
|
||||
}))
|
||||
|
||||
return (
|
||||
<DocsShell
|
||||
mobileHeader={t("mobileHeader")}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
sections={DOC_SECTIONS.map((s) => ({ id: s.id, title: s.title }))}
|
||||
>
|
||||
{sectionsWithHtml.map((section) => (
|
||||
<section key={section.id} id={section.id} className="scroll-mt-24">
|
||||
<article className="docs-content" dangerouslySetInnerHTML={{ __html: section.html }} />
|
||||
</section>
|
||||
))}
|
||||
</DocsShell>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX } from "react"
|
||||
import { notFound } from "next/navigation"
|
||||
import { hasLocale } from "next-intl"
|
||||
import { setRequestLocale } from "next-intl/server"
|
||||
import { LocalizedPageShell } from "@/app/_components/localized-page-shell"
|
||||
import { routing } from "@/i18n/routing"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, background agents, 40+ lifecycle hooks.",
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({ locale }))
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ locale: string }>
|
||||
}): Promise<JSX.Element> {
|
||||
const { locale } = await params
|
||||
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
setRequestLocale(locale)
|
||||
|
||||
return <LocalizedPageShell locale={locale}>{children}</LocalizedPageShell>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Ultrawork Manifesto",
|
||||
description:
|
||||
"The philosophy of high-output engineering. Why human developers should be architects, not spell-checkers.",
|
||||
}
|
||||
|
||||
export default function ManifestoLayout({ children }: { children: React.ReactNode }) {
|
||||
return children
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import Image from "next/image"
|
||||
import { ArrowRight, Check, Terminal, Zap } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Section } from "@/components/ui/section"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Link } from "@/i18n/routing"
|
||||
|
||||
async function ManifestoPage() {
|
||||
const t = await getTranslations("manifesto")
|
||||
|
||||
const painPointKeys = ["fixing", "syntax", "copyPasting", "reviewing"] as const
|
||||
const indistinguishableKeys = [
|
||||
"patterns",
|
||||
"errorHandling",
|
||||
"tests",
|
||||
"noSlop",
|
||||
"comments",
|
||||
] as const
|
||||
const ultraworkStepKeys = ["analyze", "breakdown", "execute", "verify", "commit"] as const
|
||||
|
||||
const coreLoopKeys = [
|
||||
"prometheus",
|
||||
"metis",
|
||||
"momus",
|
||||
"orchestrator",
|
||||
"todoContinuation",
|
||||
"categorySystem",
|
||||
"backgroundAgents",
|
||||
"wisdomAccumulation",
|
||||
] as const
|
||||
|
||||
const futureKeys = ["focus", "quality", "complexity", "promptEngineering"] as const
|
||||
|
||||
return (
|
||||
<main className="bg-background text-foreground min-h-screen overflow-x-hidden">
|
||||
<section className="relative flex min-h-[80vh] flex-col items-center justify-center overflow-hidden px-6 pt-20 text-center">
|
||||
<div className="absolute inset-0 z-0 opacity-20">
|
||||
<Image
|
||||
src="/images/core-loop.png"
|
||||
alt="Background"
|
||||
fill
|
||||
className="object-cover object-center"
|
||||
priority
|
||||
/>
|
||||
<div className="from-background/80 via-background/90 to-background absolute inset-0 bg-gradient-to-b" />
|
||||
</div>
|
||||
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2 relative z-10 mx-auto max-w-4xl space-y-6 duration-500">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-primary/50 text-primary mb-4 px-4 py-1 text-sm"
|
||||
>
|
||||
{t("badge")}
|
||||
</Badge>
|
||||
<h1 className="from-foreground to-foreground/60 bg-gradient-to-b bg-clip-text text-5xl font-bold tracking-tight text-transparent md:text-7xl">
|
||||
{t("hero.title")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-xl font-light tracking-wide md:text-2xl">
|
||||
{t("hero.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Section className="mx-auto max-w-3xl">
|
||||
<div className="space-y-12">
|
||||
<div className="text-primary/90 border-primary/20 bg-primary/5 border-y py-8 text-center font-mono text-lg md:text-xl">
|
||||
{t("bottleneck")}
|
||||
</div>
|
||||
|
||||
<div className="prose prose-invert prose-lg max-w-none">
|
||||
<p>{t("autonomousCar")}</p>
|
||||
|
||||
<h2 className="mt-8 mb-4 text-2xl font-bold">{t("whyDifferent")}</h2>
|
||||
|
||||
<p>{t("micromanagement")}</p>
|
||||
|
||||
<ul className="my-6 list-none space-y-4 pl-0">
|
||||
{painPointKeys.map((key) => (
|
||||
<li key={key} className="flex items-start gap-3">
|
||||
<span className="mt-1 text-red-500">✕</span>
|
||||
<span>{t(`painPoints.${key}`)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p className="my-8 border-l-4 border-red-500 bg-red-500/5 py-2 pl-6 text-xl font-semibold">
|
||||
{t("notCollaboration")}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Link href="/" className="text-primary underline-offset-4 hover:underline">
|
||||
{t("premiseLinkText")}
|
||||
</Link>{" "}
|
||||
{t("premise", { linkText: "" })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Separator className="mx-auto max-w-4xl opacity-20" />
|
||||
|
||||
<Section className="mx-auto max-w-3xl">
|
||||
<h2 className="mb-8 text-3xl font-bold md:text-4xl">{t("indistinguishable.title")}</h2>
|
||||
|
||||
<p className="text-muted-foreground mb-8 text-xl">{t("indistinguishable.subtitle")}</p>
|
||||
|
||||
<div className="mb-10 grid gap-6">
|
||||
{indistinguishableKeys.map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="bg-secondary/30 border-border/50 flex items-start gap-4 rounded-lg border p-4"
|
||||
>
|
||||
<Check className="h-6 w-6 shrink-0 text-green-500" />
|
||||
<span>{t(`indistinguishable.items.${key}`)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<blockquote className="border-primary bg-primary/5 rounded-r-lg border-l-4 py-4 pl-6 text-2xl font-light italic">
|
||||
{t("indistinguishable.quote")}
|
||||
</blockquote>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-4xl">
|
||||
<div className="grid items-center gap-12 md:grid-cols-2">
|
||||
<div>
|
||||
<h2 className="mb-6 text-3xl font-bold md:text-4xl">{t("tokenCost.title")}</h2>
|
||||
<p className="text-muted-foreground mb-6 text-lg">{t("tokenCost.description")}</p>
|
||||
<ul className="mb-8 space-y-3">
|
||||
<li className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
<span>{t("tokenCost.parallelAgents")}</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
<span>{t("tokenCost.completeWork")}</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
<span>{t("tokenCost.selfVerification")}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-secondary/20 border-border/50 rounded-xl border p-8">
|
||||
<h3 className="text-primary mb-4 text-xl font-semibold">{t("tokenCost.however")}</h3>
|
||||
<p className="text-muted-foreground mb-4">{t("tokenCost.optimizeDescription")}</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.cheaperModels")}
|
||||
</li>
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.avoidingRedundant")}
|
||||
</li>
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.intelligentCaching")}
|
||||
</li>
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.stoppingExactly")}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-5xl">
|
||||
<div className="mb-16 text-center">
|
||||
<h2 className="mb-4 text-3xl font-bold md:text-4xl">{t("cognitiveLoad.title")}</h2>
|
||||
<p className="text-muted-foreground mx-auto max-w-2xl text-xl">
|
||||
{t("cognitiveLoad.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<Card className="from-background to-primary/5 border-primary/20 relative overflow-hidden bg-gradient-to-br">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-10">
|
||||
<Terminal className="h-24 w-24" />
|
||||
</div>
|
||||
<CardHeader>
|
||||
<Badge className="mb-2 w-fit">{t("cognitiveLoad.ultrawork.badge")}</Badge>
|
||||
<CardTitle className="text-2xl">{t("cognitiveLoad.ultrawork.title")}</CardTitle>
|
||||
<p className="text-muted-foreground">{t("cognitiveLoad.ultrawork.subtitle")}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="border-primary/20 relative ml-2 space-y-6 border-l pl-4">
|
||||
{ultraworkStepKeys.map((key) => (
|
||||
<div key={key} className="relative">
|
||||
<div className="bg-primary border-background absolute top-1.5 -left-[21px] h-3 w-3 rounded-full border-2" />
|
||||
<p className="text-sm">{t(`cognitiveLoad.ultrawork.steps.${key}`)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="border-border/50 text-primary mt-8 border-t pt-6 text-center font-bold">
|
||||
{t("cognitiveLoad.ultrawork.footer")}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-secondary/10 border-border/50">
|
||||
<CardHeader>
|
||||
<Badge variant="secondary" className="mb-2 w-fit">
|
||||
{t("cognitiveLoad.prometheus.badge")}
|
||||
</Badge>
|
||||
<CardTitle className="text-2xl">{t("cognitiveLoad.prometheus.title")}</CardTitle>
|
||||
<p className="text-muted-foreground">{t("cognitiveLoad.prometheus.subtitle")}</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="bg-background/50 border-border/50 rounded-lg border p-4">
|
||||
<h3 className="text-primary mb-1 font-semibold">
|
||||
{t("cognitiveLoad.prometheus.prometheusTitle")}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("cognitiveLoad.prometheus.prometheusDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<ArrowRight className="text-muted-foreground/50 rotate-90 md:rotate-0" />
|
||||
</div>
|
||||
<div className="bg-background/50 border-border/50 rounded-lg border p-4">
|
||||
<h3 className="text-primary mb-1 font-semibold">
|
||||
{t("cognitiveLoad.prometheus.atlasTitle")}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("cognitiveLoad.prometheus.atlasDescription")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-border/50 text-muted-foreground mt-4 border-t pt-6 text-center font-bold">
|
||||
{t("cognitiveLoad.prometheus.footer")}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
{(["predictable", "continuous", "delegatable"] as const).map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="bg-secondary/10 border-border/30 rounded-xl border p-6 text-center transition-colors"
|
||||
>
|
||||
<div className="mb-4 flex justify-center">
|
||||
<Image
|
||||
src={`/images/${key}.png`}
|
||||
alt={key}
|
||||
width={64}
|
||||
height={64}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="mb-3 text-xl font-bold">{t(`principles.${key}.title`)}</h3>
|
||||
<p className="text-muted-foreground">{t(`principles.${key}.description`)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Separator className="mx-auto max-w-4xl opacity-20" />
|
||||
|
||||
<Section className="mx-auto max-w-5xl">
|
||||
<h2 className="mb-12 text-center text-3xl font-bold md:text-4xl">{t("coreLoop.title")}</h2>
|
||||
|
||||
<div className="bg-background border-border/50 mb-16 rounded-xl border p-6 shadow-lg">
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 py-8 md:gap-8">
|
||||
<div className="rounded-lg border-2 border-white bg-black px-6 py-3 text-sm font-semibold text-white md:text-base">
|
||||
Human Intent
|
||||
</div>
|
||||
<svg
|
||||
className="text-muted-foreground h-8 w-8 shrink-0"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="rounded-lg border-2 border-zinc-600 bg-zinc-900 px-6 py-3 text-sm font-semibold text-white md:text-base">
|
||||
Agent Execution
|
||||
</div>
|
||||
<svg
|
||||
className="text-muted-foreground h-8 w-8 shrink-0"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="rounded-lg border-2 border-cyan-500 bg-black px-6 py-3 text-sm font-semibold text-cyan-400 md:text-base">
|
||||
Verified Result
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-2 text-center text-xs">↻ Minimum Intervention</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{coreLoopKeys.map((key) => (
|
||||
<Card key={key} className="bg-secondary/5 border-border/40 transition-colors">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-primary text-lg">
|
||||
{t(`coreLoop.features.${key}.feature`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t(`coreLoop.features.${key}.purpose`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="mb-8 text-3xl font-bold md:text-4xl">{t("future.title")}</h2>
|
||||
|
||||
<div className="mx-auto mb-12 max-w-2xl space-y-4 text-left">
|
||||
{futureKeys.map((key) => (
|
||||
<div key={key} className="flex items-center gap-3">
|
||||
<div className="bg-primary h-2 w-2 shrink-0 rounded-full" />
|
||||
<span className="text-lg">{t(`future.items.${key}`)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<p className="text-2xl font-light">{t("future.quote1")}</p>
|
||||
<p className="text-primary text-3xl font-bold">{t("future.quote2")}</p>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<section className="from-primary/10 to-background bg-gradient-to-t px-6 py-32 text-center">
|
||||
<div className="space-y-8">
|
||||
<h2 className="text-foreground text-6xl font-black tracking-tighter md:text-8xl">
|
||||
{t("finalCta.title")}
|
||||
</h2>
|
||||
|
||||
<Button size="lg" className="rounded-full px-8 py-6 text-lg" asChild>
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
{t("finalCta.button")} <ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default ManifestoPage
|
||||
@@ -0,0 +1,17 @@
|
||||
export { landingMetadata as metadata } from "@/app/_components/landing-page"
|
||||
|
||||
import type { JSX } from "react"
|
||||
import { setRequestLocale } from "next-intl/server"
|
||||
import { LandingPage } from "@/app/_components/landing-page"
|
||||
|
||||
export default async function LocaleLandingPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>
|
||||
}): Promise<JSX.Element> {
|
||||
const { locale } = await params
|
||||
|
||||
setRequestLocale(locale)
|
||||
|
||||
return <LandingPage />
|
||||
}
|
||||
@@ -0,0 +1,832 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX, SVGProps } from "react"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import {
|
||||
Layers,
|
||||
Star,
|
||||
Check,
|
||||
Zap,
|
||||
Search,
|
||||
Code2,
|
||||
Brain,
|
||||
Eye,
|
||||
MessageSquare,
|
||||
Shield,
|
||||
Lightbulb,
|
||||
Route,
|
||||
HardDrive,
|
||||
ArrowRight,
|
||||
Target,
|
||||
Users,
|
||||
Network,
|
||||
Terminal,
|
||||
Wrench,
|
||||
Sparkles,
|
||||
Sword,
|
||||
} from "lucide-react"
|
||||
import { HeroStats } from "@/components/landing/hero-stats"
|
||||
import { InstallCommand } from "@/components/landing/install-command"
|
||||
import { TerminalTypewriter } from "@/components/landing/motion-wrappers"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Link } from "@/i18n/routing"
|
||||
import { formatStats, getStats } from "@/lib/stats"
|
||||
|
||||
const FALLBACK_STATS = {
|
||||
stars: "40k+",
|
||||
totalDownloads: "1M+",
|
||||
monthlyDownloads: "580k+",
|
||||
weeklyDownloads: "90k+",
|
||||
}
|
||||
|
||||
export const landingMetadata: Metadata = {
|
||||
title: "Oh My OpenAgent — The Best Agent Harness",
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, Team Mode, background agents, 54+ lifecycle hooks.",
|
||||
}
|
||||
|
||||
export async function LandingPage(): Promise<JSX.Element> {
|
||||
const t = await getTranslations("landing")
|
||||
|
||||
let formattedStats = FALLBACK_STATS
|
||||
try {
|
||||
const stats = await getStats()
|
||||
formattedStats = formatStats(stats)
|
||||
} catch {
|
||||
formattedStats = FALLBACK_STATS
|
||||
}
|
||||
|
||||
const subAgentKeys = ["oracle", "librarian", "explore", "metis", "momus"] as const
|
||||
type SubAgentKey = (typeof subAgentKeys)[number]
|
||||
|
||||
const agentStyles: Record<
|
||||
SubAgentKey,
|
||||
{ color: string; border: string; bg: string; icon: typeof Brain }
|
||||
> = {
|
||||
oracle: {
|
||||
color: "text-purple-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-purple-400/5",
|
||||
icon: Eye,
|
||||
},
|
||||
librarian: {
|
||||
color: "text-green-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-green-400/5",
|
||||
icon: Search,
|
||||
},
|
||||
explore: {
|
||||
color: "text-blue-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-blue-400/5",
|
||||
icon: Code2,
|
||||
},
|
||||
metis: {
|
||||
color: "text-pink-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-pink-400/5",
|
||||
icon: MessageSquare,
|
||||
},
|
||||
momus: { color: "text-red-400", border: "border-zinc-800", bg: "bg-red-400/5", icon: Check },
|
||||
}
|
||||
|
||||
const reviewKeys = ["review1", "review2", "review3", "review4", "review5", "review6"] as const
|
||||
|
||||
const principleKeys = [
|
||||
"specialization",
|
||||
"trustVerify",
|
||||
"wisdom",
|
||||
"modelOptimization",
|
||||
"categories",
|
||||
"continuity",
|
||||
] as const
|
||||
type PrincipleKey = (typeof principleKeys)[number]
|
||||
|
||||
const principleIcons: Record<PrincipleKey, typeof Brain> = {
|
||||
specialization: Target,
|
||||
trustVerify: Shield,
|
||||
wisdom: Lightbulb,
|
||||
modelOptimization: Zap,
|
||||
categories: Route,
|
||||
continuity: HardDrive,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col overflow-x-hidden">
|
||||
<link rel="preload" as="image" href="/images/hero.webp" fetchPriority="low" />
|
||||
<section className="relative flex min-h-[90vh] items-center justify-center overflow-hidden pt-16">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="hero-bg absolute inset-0 z-0 bg-cover bg-center"
|
||||
style={{ backgroundImage: "url(/images/hero.webp)" }}
|
||||
/>
|
||||
<div className="absolute inset-0 z-0 bg-gradient-to-b from-black/80 via-black/90 to-[#0a0a0a]" />
|
||||
|
||||
<div className="relative z-10 container mx-auto flex flex-col items-center gap-8 px-4 text-center md:px-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-5xl font-bold tracking-tighter text-white md:text-7xl">
|
||||
{t("hero.title")}
|
||||
<span className="text-cyan-400">{t("hero.titleHighlight")}</span>
|
||||
</h1>
|
||||
<p className="mx-auto max-w-3xl text-xl font-light text-zinc-400 md:text-2xl">
|
||||
{t("hero.subtitle", {
|
||||
stars: formattedStats.stars,
|
||||
downloads: formattedStats.totalDownloads,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<HeroStats
|
||||
initialStats={{
|
||||
stars: formattedStats.stars,
|
||||
totalDownloads: formattedStats.totalDownloads,
|
||||
monthlyDownloads: formattedStats.monthlyDownloads,
|
||||
weeklyDownloads: formattedStats.weeklyDownloads,
|
||||
}}
|
||||
labels={{
|
||||
githubStars: t("hero.githubStars", { count: "{count}" }),
|
||||
specializedAgents: t("hero.specializedAgents", { count: "11" }),
|
||||
totalDownloads: t("hero.totalDownloads", { count: "{count}" }),
|
||||
monthlyDownloads: t("hero.monthlyDownloads", { count: "{count}" }),
|
||||
lifecycleHooks: t("hero.lifecycleHooks", { count: "54+" }),
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
<InstallCommand command={t("hero.installCommand")} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 sm:flex-row">
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
className="h-12 bg-cyan-500 px-8 text-lg font-bold text-black shadow-sm hover:bg-cyan-600"
|
||||
>
|
||||
{t("hero.getStarted")}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="h-12 border-zinc-700 px-8 text-lg text-white hover:bg-zinc-800"
|
||||
>
|
||||
<GithubIcon className="mr-2 h-5 w-5" />
|
||||
{t("hero.viewOnGitHub")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features" className="overflow-hidden border-t border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="flex flex-col items-center gap-16 lg:flex-row">
|
||||
<div className="flex-1 space-y-8">
|
||||
<Badge className="border-cyan-500/20 bg-cyan-500/10 px-4 py-1.5 text-cyan-400">
|
||||
{t("ulw.badge")}
|
||||
</Badge>
|
||||
<h2 className="bg-gradient-to-r from-cyan-400 to-purple-600 bg-clip-text text-4xl font-black tracking-tighter text-transparent md:text-5xl">
|
||||
{t("ulw.title")}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-3xl font-bold text-white">{t("ulw.headline")}</h3>
|
||||
<p className="text-xl leading-relaxed text-zinc-400">{t("ulw.description")}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Badge className="border-cyan-500/20 bg-cyan-500/10 px-4 py-2 text-sm text-cyan-400">
|
||||
{t("ulw.autoPlanning")}
|
||||
</Badge>
|
||||
<Badge className="border-purple-500/20 bg-purple-500/10 px-4 py-2 text-sm text-purple-400">
|
||||
{t("ulw.deepResearch")}
|
||||
</Badge>
|
||||
<Badge className="border-green-500/20 bg-green-500/10 px-4 py-2 text-sm text-green-400">
|
||||
{t("ulw.selfCorrection")}
|
||||
</Badge>
|
||||
<Badge className="border-amber-500/20 bg-amber-500/10 px-4 py-2 text-sm text-amber-400">
|
||||
{t("ulw.parallelAgents")}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-lg text-zinc-400/90 italic">{t("ulw.tagline")}</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-xl flex-1">
|
||||
<div className="overflow-hidden rounded-xl border border-zinc-800 bg-black shadow-xl">
|
||||
<div className="flex items-center gap-2 border-b border-zinc-800 bg-zinc-900/50 px-4 py-3">
|
||||
<div className="h-3 w-3 rounded-full border border-red-500/50 bg-red-500/20" />
|
||||
<div className="h-3 w-3 rounded-full border border-yellow-500/50 bg-yellow-500/20" />
|
||||
<div className="h-3 w-3 rounded-full border border-green-500/50 bg-green-500/20" />
|
||||
<div className="ml-2 font-mono text-xs text-zinc-400">
|
||||
{t("ulw.terminalTitle")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 overflow-x-auto p-6 font-mono text-sm">
|
||||
<div className="flex gap-2">
|
||||
<span className="text-green-500">➜</span>
|
||||
<span className="text-cyan-500">~</span>
|
||||
<TerminalTypewriter text={t("ulw.terminalInput")} />
|
||||
</div>
|
||||
<div className="space-y-2 border-l-2 border-zinc-800 pl-4">
|
||||
<div className="text-cyan-400">{t("ulw.steps.scanning")}</div>
|
||||
<div className="text-zinc-400">{t("ulw.steps.context")}</div>
|
||||
<div className="text-purple-400">{t("ulw.steps.planning")}</div>
|
||||
<div className="text-amber-400">{t("ulw.steps.delegating")}</div>
|
||||
<div className="text-blue-400">{t("ulw.steps.verifying")}</div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-4">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="font-bold text-green-400">{t("ulw.steps.complete")}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="text-green-500">➜</span>
|
||||
<span className="text-cyan-500">~</span>
|
||||
<span className="animate-pulse text-white">_</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="agents" className="relative overflow-hidden bg-black py-24">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-cyan-900/20 via-black to-black opacity-50" />
|
||||
<div className="relative z-10 container mx-auto px-4 md:px-6">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<Badge className="border-cyan-500/20 bg-cyan-500/10 px-4 py-1.5 text-cyan-400">
|
||||
{t("sisyphus.badge")}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("sisyphus.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">
|
||||
<span className="text-cyan-400">{t("sisyphus.title")}</span>
|
||||
</h2>
|
||||
<h3 className="mb-6 text-2xl font-bold text-zinc-300 md:text-3xl">
|
||||
{t("sisyphus.headline")}
|
||||
</h3>
|
||||
<p className="mb-12 max-w-3xl text-xl leading-relaxed text-zinc-400">
|
||||
{t("sisyphus.description")}
|
||||
</p>
|
||||
|
||||
<div className="mb-12 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{(["intent", "explore", "delegate", "verify"] as const).map((phase, i) => (
|
||||
<div key={phase}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="mb-1 font-mono text-xs text-cyan-400">PHASE {i + 1}</div>
|
||||
<CardTitle className="text-lg text-white">
|
||||
{t(`sisyphus.phases.${phase}.title`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-zinc-400">
|
||||
{t(`sisyphus.phases.${phase}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="rounded-xl border border-cyan-400/20 bg-cyan-400/5 p-6 md:p-8">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="rounded-lg bg-cyan-400/10 p-3">
|
||||
<HardDrive className="h-6 w-6 text-cyan-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-2 text-xl font-bold text-cyan-400">
|
||||
{t("sisyphus.boulderTitle")}
|
||||
</h4>
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t("sisyphus.boulderDescription")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-y border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mb-16 text-center">
|
||||
<Badge className="mb-6 border-amber-500/20 bg-amber-500/10 px-4 py-1.5 text-amber-400">
|
||||
{t("prometheusAtlas.badge")}
|
||||
</Badge>
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">
|
||||
{t("prometheusAtlas.title")}
|
||||
</h2>
|
||||
<p className="mx-auto max-w-2xl text-xl text-zinc-400">
|
||||
{t("prometheusAtlas.headline")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-12 grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
<div>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="rounded-lg bg-amber-400/10 p-2">
|
||||
<Brain className="h-6 w-6 text-amber-400" />
|
||||
</div>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("prometheusAtlas.prometheus.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="mt-4 text-2xl text-amber-400">
|
||||
{t("prometheusAtlas.prometheus.name")}
|
||||
</CardTitle>
|
||||
<CardDescription className="font-medium text-zinc-400">
|
||||
{t("prometheusAtlas.prometheus.role")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t("prometheusAtlas.prometheus.description")}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{([0, 1, 2, 3] as const).map((i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-zinc-400">
|
||||
<ArrowRight className="h-3 w-3 shrink-0 text-amber-400" />
|
||||
{t(`prometheusAtlas.prometheus.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="rounded-lg bg-indigo-400/10 p-2">
|
||||
<Layers className="h-6 w-6 text-indigo-400" />
|
||||
</div>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("prometheusAtlas.atlas.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="mt-4 text-2xl text-indigo-400">
|
||||
{t("prometheusAtlas.atlas.name")}
|
||||
</CardTitle>
|
||||
<CardDescription className="font-medium text-zinc-400">
|
||||
{t("prometheusAtlas.atlas.role")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t("prometheusAtlas.atlas.description")}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{([0, 1, 2, 3] as const).map((i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-zinc-400">
|
||||
<ArrowRight className="h-3 w-3 shrink-0 text-indigo-400" />
|
||||
{t(`prometheusAtlas.atlas.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="overflow-x-auto rounded-xl border border-zinc-800 bg-zinc-900/20 p-6 md:p-8">
|
||||
<div className="flex min-w-[600px] flex-col items-start justify-between gap-4 md:min-w-0 md:flex-row md:items-center md:gap-0">
|
||||
{([1, 2, 3, 4, 5] as const).map((step, i) => (
|
||||
<div key={step} className="flex flex-1 items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full border border-zinc-700 bg-zinc-800 font-mono text-xs text-zinc-400">
|
||||
{step}
|
||||
</div>
|
||||
<span className="text-sm whitespace-nowrap text-zinc-300">
|
||||
{t(`prometheusAtlas.workflow.step${step}`)}
|
||||
</span>
|
||||
</div>
|
||||
{i < 4 && (
|
||||
<ArrowRight className="ml-auto hidden h-4 w-4 shrink-0 text-zinc-600 md:block" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-6 border-t border-zinc-800 pt-6 text-center text-zinc-400 italic">
|
||||
{t("prometheusAtlas.whyItWorks")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="relative overflow-hidden bg-black py-24">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom,_var(--tw-gradient-stops))] from-orange-900/10 via-black to-black opacity-70" />
|
||||
<div className="relative z-10 container mx-auto px-4 md:px-6">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<Badge className="border-orange-500/20 bg-orange-500/10 px-4 py-1.5 text-orange-400">
|
||||
{t("hephaestus.badge")}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("hephaestus.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-4 text-4xl font-bold md:text-5xl">
|
||||
<span className="text-orange-400">{t("hephaestus.title")}</span>
|
||||
</h2>
|
||||
<h3 className="mb-6 text-2xl font-bold text-zinc-300 md:text-3xl">
|
||||
{t("hephaestus.headline")}
|
||||
</h3>
|
||||
<p className="mb-12 max-w-3xl text-xl leading-relaxed text-zinc-400">
|
||||
{t("hephaestus.description")}
|
||||
</p>
|
||||
|
||||
<div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
||||
{(["explore", "plan", "decide", "execute", "verify"] as const).map((step, i) => (
|
||||
<div key={step}>
|
||||
<div className="rounded-lg border border-orange-400/20 bg-orange-400/5 p-4 text-center">
|
||||
<div className="mb-2 font-mono text-xs text-orange-400">0{i + 1}</div>
|
||||
<p className="text-sm leading-snug break-keep text-zinc-300">
|
||||
{t(`hephaestus.loop.${step}`)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-center text-lg text-zinc-400/90 italic">{t("hephaestus.tagline")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="team-mode"
|
||||
className="relative overflow-hidden border-t border-white/5 bg-[#0a0a0a] py-24"
|
||||
>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-fuchsia-900/20 via-black to-black opacity-70" />
|
||||
<div className="relative z-10 container mx-auto px-4 md:px-6">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="mb-6 flex flex-wrap items-center gap-3">
|
||||
<Badge className="border-fuchsia-500/30 bg-fuchsia-500/10 px-4 py-1.5 font-mono text-fuchsia-300">
|
||||
{t("teamMode.badge")}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
opt-in
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-4 text-4xl font-bold tracking-tight md:text-5xl">
|
||||
<span className="bg-gradient-to-r from-fuchsia-400 via-purple-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
{t("teamMode.title")}
|
||||
</span>
|
||||
</h2>
|
||||
<h3 className="mb-6 text-2xl font-bold text-zinc-200 md:text-3xl">
|
||||
{t("teamMode.headline")}
|
||||
</h3>
|
||||
<p className="mb-12 max-w-3xl text-xl leading-relaxed text-zinc-400">
|
||||
{t("teamMode.description")}
|
||||
</p>
|
||||
|
||||
<div className="mb-16 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{(
|
||||
[
|
||||
{ key: "lead", icon: Network },
|
||||
{ key: "parallel", icon: Users },
|
||||
{ key: "tmux", icon: Terminal },
|
||||
{ key: "tools", icon: Wrench },
|
||||
] as const
|
||||
).map(({ key, icon: Icon }) => (
|
||||
<div key={key}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30 transition-colors hover:border-fuchsia-500/30">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="w-fit rounded-lg bg-fuchsia-500/10 p-2 text-fuchsia-300">
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<CardTitle className="mt-3 text-lg text-fuchsia-200">
|
||||
{t(`teamMode.features.${key}.title`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
{t(`teamMode.features.${key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mb-10 flex items-center gap-3">
|
||||
<Sparkles className="h-4 w-4 text-fuchsia-400" />
|
||||
<span className="font-mono text-sm tracking-widest text-fuchsia-300/80 uppercase">
|
||||
{t("teamMode.poweredBy")}
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-fuchsia-500/30 to-transparent" />
|
||||
</div>
|
||||
|
||||
<div className="mb-12 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{(
|
||||
[
|
||||
{ key: "hyperplan", icon: Sword, accent: "purple" as const },
|
||||
{ key: "securityResearch", icon: Shield, accent: "rose" as const },
|
||||
] as const
|
||||
).map(({ key, icon: Icon, accent }) => (
|
||||
<div key={key}>
|
||||
<Card
|
||||
className={`h-full border-zinc-800 bg-zinc-900/30 ${
|
||||
accent === "purple"
|
||||
? "hover:border-purple-500/40"
|
||||
: "hover:border-rose-500/40"
|
||||
} transition-colors`}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={`rounded-lg p-2 ${
|
||||
accent === "purple"
|
||||
? "bg-purple-500/10 text-purple-300"
|
||||
: "bg-rose-500/10 text-rose-300"
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<CardTitle
|
||||
className={`font-mono text-lg ${
|
||||
accent === "purple" ? "text-purple-300" : "text-rose-300"
|
||||
}`}
|
||||
>
|
||||
{t(`teamMode.skills.${key}.name`)}
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t(`teamMode.skills.${key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start gap-4 rounded-xl border border-zinc-800 bg-black/40 p-6 sm:flex-row sm:items-center sm:justify-between">
|
||||
<code className="font-mono text-sm break-all text-fuchsia-200/90">
|
||||
{t("teamMode.optIn")}
|
||||
</code>
|
||||
<p className="text-sm text-zinc-400 italic sm:text-right">{t("teamMode.tagline")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-t border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mb-16 text-center">
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">{t("agents.title")}</h2>
|
||||
<p className="text-xl text-zinc-400">{t("agents.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-5">
|
||||
{subAgentKeys.map((key) => {
|
||||
const style = agentStyles[key]
|
||||
const Icon = style.icon
|
||||
return (
|
||||
<div key={key}>
|
||||
<Card
|
||||
className={`h-full border-zinc-800 bg-zinc-900/30 ${style.border} ${style.bg}`}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className={`rounded-lg bg-black/50 p-2 ${style.color}`}>
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t(`agents.${key}.model`)}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className={`mt-3 text-lg ${style.color}`}>
|
||||
{t(`agents.${key}.name`)}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-sm font-medium text-zinc-400">
|
||||
{t(`agents.${key}.role`)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-relaxed text-zinc-300">
|
||||
{t(`agents.${key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
<div className="col-span-1 mt-4 md:col-span-2 lg:col-span-5">
|
||||
<Card className="h-full overflow-hidden border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="mb-2 flex items-center gap-3">
|
||||
<Badge className="border-teal-500/20 bg-teal-500/10 px-3 py-1 text-teal-400">
|
||||
{t("agents.dynamicSystem.role")}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-2xl text-teal-400">
|
||||
{t("agents.dynamicSystem.name")}
|
||||
</CardTitle>
|
||||
<CardDescription className="max-w-3xl text-base text-zinc-400">
|
||||
{t("agents.dynamicSystem.description")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mt-4 grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-bold tracking-wider text-zinc-300 uppercase">
|
||||
Category Routing
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ cat: "visual-engineering", model: "Gemini 3.1 Pro" },
|
||||
{ cat: "ultrabrain", model: "GPT 5.5 xHigh" },
|
||||
{ cat: "artistry", model: "Gemini 3.1 Pro" },
|
||||
{ cat: "quick", model: "GPT 5.4 Mini" },
|
||||
{ cat: "deep", model: "GPT 5.5 Medium" },
|
||||
{ cat: "writing", model: "Kimi K2.5" },
|
||||
{ cat: "git", model: "Claude Haiku 4.5" },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.cat}
|
||||
className="flex items-center justify-between rounded border border-zinc-800/50 bg-black/40 p-2"
|
||||
>
|
||||
<span className="font-mono text-sm text-teal-400">{item.cat}</span>
|
||||
<ArrowRight className="h-3 w-3 text-zinc-600" />
|
||||
<span className="text-sm text-zinc-300">{item.model}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-bold tracking-wider text-zinc-300 uppercase">
|
||||
Skill Injection
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{["playwright", "git-master", "frontend-ui-ux", "team-mode"].map(
|
||||
(skill) => (
|
||||
<div
|
||||
key={skill}
|
||||
className="flex items-center gap-2 rounded border border-zinc-700/50 bg-zinc-800/30 p-3"
|
||||
>
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
<span className="font-mono text-sm text-zinc-200">{skill}</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 rounded-lg border border-teal-500/10 bg-teal-500/5 p-4">
|
||||
<p className="text-sm text-teal-300 italic">
|
||||
"The right model + right expertise, every time."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-black py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mb-16 text-center">
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">
|
||||
{t("architecture.title")}
|
||||
</h2>
|
||||
<p className="text-xl text-zinc-400">{t("architecture.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto grid max-w-5xl grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{principleKeys.map((key) => {
|
||||
const Icon = principleIcons[key]
|
||||
return (
|
||||
<div key={key}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="w-fit rounded-lg bg-zinc-800 p-2">
|
||||
<Icon className="h-5 w-5 text-zinc-300" />
|
||||
</div>
|
||||
<CardTitle className="mt-3 text-lg text-white">
|
||||
{t(`architecture.principles.${key}.title`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
{t(`architecture.principles.${key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-t border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div>
|
||||
<h2 className="mb-16 text-center text-4xl font-bold text-white md:text-5xl">
|
||||
{t("reviews.title")}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{reviewKeys.map((key) => (
|
||||
<div key={key}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardContent className="pt-6">
|
||||
<div className="mb-4 text-cyan-500">
|
||||
<Star className="h-5 w-5 fill-cyan-500" />
|
||||
</div>
|
||||
<p className="mb-6 leading-relaxed text-zinc-300 italic">
|
||||
“{t(`reviews.${key}.text`)}”
|
||||
</p>
|
||||
<p className="text-sm font-medium text-zinc-400">
|
||||
— {t(`reviews.${key}.author`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-black py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div>
|
||||
<div className="relative overflow-hidden rounded-3xl border border-zinc-800 bg-zinc-900/30 p-8 text-center md:p-16">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 via-purple-500/10 to-cyan-500/10 opacity-50" />
|
||||
<div className="relative z-10 mx-auto max-w-3xl space-y-8">
|
||||
<h2 className="text-4xl font-bold text-white md:text-5xl">{t("cta.title")}</h2>
|
||||
<p className="text-lg text-zinc-400">{t("cta.subtitle")}</p>
|
||||
<div className="flex justify-center">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-zinc-800 bg-black px-6 py-3 font-mono text-sm text-zinc-300">
|
||||
<span className="text-cyan-500">$</span>
|
||||
{t("cta.installCommand")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center gap-4 sm:flex-row">
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
className="h-12 bg-cyan-500 px-8 font-bold text-black hover:bg-cyan-600"
|
||||
>
|
||||
{t("cta.installNow")}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/docs">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="h-12 border-zinc-700 px-8 text-white hover:bg-zinc-800"
|
||||
>
|
||||
{t("cta.readTheDocs")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GithubIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
role="img"
|
||||
>
|
||||
<title>GitHub</title>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { JSX } from "react"
|
||||
import { NextIntlClientProvider } from "next-intl"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { NavHeader } from "@/components/nav-header"
|
||||
import type { Locale } from "@/i18n/config"
|
||||
|
||||
type LocalizedPageShellProps = {
|
||||
children: React.ReactNode
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
type IntlMessages = Record<string, Record<string, unknown>>
|
||||
|
||||
function getLanguageTag(locale: Locale): string {
|
||||
switch (locale) {
|
||||
case "zh":
|
||||
return "zh-CN"
|
||||
default:
|
||||
return locale
|
||||
}
|
||||
}
|
||||
|
||||
export async function LocalizedPageShell({
|
||||
children,
|
||||
locale,
|
||||
}: LocalizedPageShellProps): Promise<JSX.Element> {
|
||||
const messages = (await import(`../../messages/${locale}.json`)).default as IntlMessages
|
||||
const languageTag = getLanguageTag(locale)
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<div lang={languageTag} data-locale={locale} className="flex min-h-screen flex-col">
|
||||
<NavHeader />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer locale={locale} />
|
||||
</div>
|
||||
</NextIntlClientProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getStats } from "@/lib/stats"
|
||||
|
||||
/**
|
||||
* Shields.io endpoint badge for combined NPM downloads.
|
||||
* Usage: https://img.shields.io/endpoint?url=https://ohmyopenagent.com/api/npm-downloads
|
||||
*
|
||||
* Combines downloads from both oh-my-opencode and oh-my-openagent packages.
|
||||
*/
|
||||
|
||||
function formatDownloads(num: number): string {
|
||||
if (num >= 1_000_000) {
|
||||
const formatted = (num / 1_000_000).toFixed(1)
|
||||
return `${formatted.replace(/\.0$/, "")}M`
|
||||
}
|
||||
if (num >= 1_000) {
|
||||
const formatted = (num / 1_000).toFixed(1)
|
||||
return `${formatted.replace(/\.0$/, "")}k`
|
||||
}
|
||||
return String(num)
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const period = searchParams.get("period") ?? "total"
|
||||
|
||||
try {
|
||||
const stats = await getStats()
|
||||
|
||||
let value: number
|
||||
let label: string
|
||||
|
||||
switch (period) {
|
||||
case "monthly":
|
||||
value = stats.monthlyDownloads
|
||||
label = "npm downloads/month"
|
||||
break
|
||||
case "weekly":
|
||||
value = stats.weeklyDownloads
|
||||
label = "npm downloads/week"
|
||||
break
|
||||
case "total":
|
||||
default:
|
||||
value = stats.totalDownloads
|
||||
label = "npm downloads"
|
||||
break
|
||||
}
|
||||
|
||||
// Shields.io endpoint badge schema
|
||||
// https://shields.io/badges/endpoint-badge
|
||||
const badge = {
|
||||
schemaVersion: 1,
|
||||
label,
|
||||
message: formatDownloads(value),
|
||||
color: "ff6b35",
|
||||
labelColor: "000000",
|
||||
style: "flat-square",
|
||||
}
|
||||
|
||||
return NextResponse.json(badge, {
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Fallback badge
|
||||
return NextResponse.json(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
label: "npm downloads",
|
||||
message: "1M+",
|
||||
color: "ff6b35",
|
||||
labelColor: "000000",
|
||||
style: "flat-square",
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getStats, formatStats } from "@/lib/stats"
|
||||
|
||||
const FALLBACK = {
|
||||
stars: "37.3k",
|
||||
totalDownloads: "1M+",
|
||||
monthlyDownloads: "580k+",
|
||||
weeklyDownloads: "90k+",
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const stats = await getStats()
|
||||
const formatted = formatStats(stats)
|
||||
|
||||
return NextResponse.json(
|
||||
{ ...formatted, raw: stats },
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
|
||||
},
|
||||
},
|
||||
)
|
||||
} catch {
|
||||
return NextResponse.json(FALLBACK, {
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180" viewBox="0 0 180 180">
|
||||
<rect width="180" height="180" rx="40" fill="#0a0a0a"/>
|
||||
<text x="90" y="128" font-family="system-ui, sans-serif" font-size="120" font-weight="bold" fill="#00d4ff" text-anchor="middle">O</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 292 B |
@@ -0,0 +1,312 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace;
|
||||
|
||||
--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-lg: var(--radius);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
}
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* Dark Theme Only - Terminal/Hacker Aesthetic */
|
||||
|
||||
/* Colors */
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
|
||||
--card: #111111;
|
||||
--card-foreground: #ededed;
|
||||
|
||||
--popover: #111111;
|
||||
--popover-foreground: #ededed;
|
||||
|
||||
--primary: #00d4ff;
|
||||
--primary-foreground: #000000;
|
||||
|
||||
--secondary: #7c3aed;
|
||||
--secondary-foreground: #ffffff;
|
||||
|
||||
--muted: #1a1a1a;
|
||||
--muted-foreground: #a1a1a1;
|
||||
|
||||
--accent: #1a1a1a;
|
||||
--accent-foreground: #ededed;
|
||||
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #ffffff;
|
||||
|
||||
--border: #262626;
|
||||
--input: #262626;
|
||||
--ring: #00d4ff;
|
||||
|
||||
/* Charts */
|
||||
--chart-1: #00d4ff;
|
||||
--chart-2: #7c3aed;
|
||||
--chart-3: #10b981;
|
||||
--chart-4: #f59e0b;
|
||||
--chart-5: #ef4444;
|
||||
|
||||
/* Code */
|
||||
--code-bg: #1e1e2e;
|
||||
--code-text: #cdd6f4;
|
||||
|
||||
/* Spacing */
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Typography */
|
||||
--font-geist-sans: var(--font-geist-sans);
|
||||
--font-geist-mono: var(--font-geist-mono);
|
||||
|
||||
/* Semantic Fonts */
|
||||
--font-heading: var(--font-geist-sans);
|
||||
--font-body: var(--font-geist-sans);
|
||||
--font-code: var(--font-geist-mono);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings:
|
||||
"rlig" 1,
|
||||
"calt" 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hero background image fades in after first paint so the headline text is
|
||||
* the LCP candidate. The image is decorative (opacity 30%) and is preloaded
|
||||
* with low priority so it doesn't compete with critical resources.
|
||||
*/
|
||||
.hero-bg {
|
||||
opacity: 0;
|
||||
animation: hero-bg-fade-in 600ms ease-out 200ms forwards;
|
||||
}
|
||||
@keyframes hero-bg-fade-in {
|
||||
to {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero-bg {
|
||||
animation: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: var(--font-geist-sans);
|
||||
}
|
||||
|
||||
[lang|="ko"] h1,
|
||||
[lang|="ko"] h2,
|
||||
[lang|="ko"] h3,
|
||||
[lang|="ko"] h4,
|
||||
[lang|="ko"] h5,
|
||||
[lang|="ko"] h6,
|
||||
[lang|="ja"] h1,
|
||||
[lang|="ja"] h2,
|
||||
[lang|="ja"] h3,
|
||||
[lang|="ja"] h4,
|
||||
[lang|="ja"] h5,
|
||||
[lang|="ja"] h6,
|
||||
[lang|="zh"] h1,
|
||||
[lang|="zh"] h2,
|
||||
[lang|="zh"] h3,
|
||||
[lang|="zh"] h4,
|
||||
[lang|="zh"] h5,
|
||||
[lang|="zh"] h6 {
|
||||
letter-spacing: normal !important;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
:where([lang|="ko"], [lang|="ja"], [lang|="zh"])
|
||||
:where(p, li, blockquote, figcaption, td, th, a, button, span) {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
[lang|="ko"]
|
||||
:where(h1, h2, h3, h4, h5, h6, p, li, blockquote, figcaption, td, th, a, button, span) {
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
:where([lang|="ja"], [lang|="zh"])
|
||||
:where(h1, h2, h3, h4, h5, h6, p, li, blockquote, figcaption, td, th, a, button, span) {
|
||||
word-break: normal;
|
||||
line-break: strict;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
::selection {
|
||||
@apply bg-primary/20 text-primary;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-muted;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-border hover:bg-muted-foreground/50 rounded-full transition-colors;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.glow-cyan {
|
||||
box-shadow: 0 0 15px -5px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
.glow-purple {
|
||||
box-shadow: 0 0 15px -5px rgba(124, 58, 237, 0.3);
|
||||
}
|
||||
|
||||
.text-glow-cyan {
|
||||
text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.docs-content h1 {
|
||||
@apply mt-8 mb-4 scroll-mt-24 text-4xl font-bold tracking-tight first:mt-0;
|
||||
}
|
||||
.docs-content h2 {
|
||||
@apply mt-12 mb-4 scroll-mt-24 text-2xl font-semibold tracking-tight;
|
||||
}
|
||||
.docs-content h3 {
|
||||
@apply mt-8 mb-3 scroll-mt-24 text-xl font-semibold tracking-tight;
|
||||
}
|
||||
.docs-content h4 {
|
||||
@apply mt-6 mb-2 scroll-mt-24 text-lg font-semibold tracking-tight;
|
||||
}
|
||||
.docs-content p {
|
||||
@apply text-muted-foreground my-4 leading-7;
|
||||
}
|
||||
.docs-content a {
|
||||
@apply text-primary font-medium underline underline-offset-4;
|
||||
}
|
||||
.docs-content ul {
|
||||
@apply text-muted-foreground my-4 ml-6 list-disc space-y-1;
|
||||
}
|
||||
.docs-content ol {
|
||||
@apply text-muted-foreground my-4 ml-6 list-decimal space-y-1;
|
||||
}
|
||||
.docs-content li {
|
||||
@apply leading-7;
|
||||
}
|
||||
.docs-content blockquote {
|
||||
@apply border-primary/40 my-6 border-l-4 pl-4 italic;
|
||||
}
|
||||
.docs-content code:not(pre code) {
|
||||
@apply bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-sm;
|
||||
}
|
||||
.docs-content pre {
|
||||
@apply border-border/50 my-4 overflow-x-auto rounded-lg border bg-[#1e1e2e] p-4 font-mono text-sm text-[#cdd6f4] shadow-sm;
|
||||
}
|
||||
.docs-content pre code {
|
||||
@apply bg-transparent p-0 text-inherit;
|
||||
}
|
||||
.docs-content table {
|
||||
@apply border-border my-6 w-full border-collapse border text-sm;
|
||||
}
|
||||
.docs-content thead {
|
||||
@apply bg-muted;
|
||||
}
|
||||
.docs-content th {
|
||||
@apply border-border border px-3 py-2 text-left font-semibold;
|
||||
}
|
||||
.docs-content td {
|
||||
@apply border-border text-muted-foreground border px-3 py-2;
|
||||
}
|
||||
.docs-content hr {
|
||||
@apply border-border my-8;
|
||||
}
|
||||
.docs-content strong {
|
||||
@apply text-foreground font-semibold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="8" fill="#0a0a0a"/>
|
||||
<text x="16" y="23" font-family="system-ui, sans-serif" font-size="22" font-weight="bold" fill="#00d4ff" text-anchor="middle">O</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
@@ -0,0 +1,113 @@
|
||||
import type { Metadata } from "next"
|
||||
import { GeistSans } from "geist/font/sans"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
import Script from "next/script"
|
||||
import "./globals.css"
|
||||
|
||||
const primarySiteUrl = "https://ohmyopenagent.com"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(primarySiteUrl),
|
||||
title: {
|
||||
default: "Oh My OpenAgent — The Best Agent Harness",
|
||||
template: "%s | Oh My OpenAgent",
|
||||
},
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, Team Mode, background agents, 54+ lifecycle hooks.",
|
||||
keywords: [
|
||||
"opencode",
|
||||
"oh-my-opencode",
|
||||
"openagent",
|
||||
"oh-my-openagent",
|
||||
"ai agent",
|
||||
"code agent",
|
||||
"sisyphus",
|
||||
"multi-model",
|
||||
"team mode",
|
||||
"agent orchestration",
|
||||
"claude",
|
||||
"gpt",
|
||||
"gemini",
|
||||
"coding assistant",
|
||||
],
|
||||
authors: [{ name: "Yeongyu Kim", url: "https://github.com/code-yeongyu" }],
|
||||
creator: "Yeongyu Kim",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: primarySiteUrl,
|
||||
siteName: "Oh My OpenAgent",
|
||||
title: "Oh My OpenAgent — The Best Agent Harness",
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, Team Mode, background agents, 54+ lifecycle hooks.",
|
||||
images: [{ url: "/images/hero.webp", width: 1024, height: 683, alt: "Oh My OpenAgent" }],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Oh My OpenAgent — The Best Agent Harness",
|
||||
description: "Meet Sisyphus: The batteries-included agent that codes like you.",
|
||||
images: ["/images/hero.webp"],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
name: "Oh My OpenAgent",
|
||||
applicationCategory: "DeveloperApplication",
|
||||
operatingSystem: "macOS, Linux, Windows",
|
||||
url: primarySiteUrl,
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Yeongyu Kim",
|
||||
url: "https://github.com/code-yeongyu",
|
||||
},
|
||||
description:
|
||||
"The batteries-included agent harness for OpenCode. Multi-model orchestration, Team Mode, background agents, 54+ lifecycle hooks.",
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: "0",
|
||||
priceCurrency: "USD",
|
||||
},
|
||||
}
|
||||
|
||||
const gaMeasurementId = "G-S0QJFKT46Q"
|
||||
const gaTrackedDomain = "ohmyopenagent.com"
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`dark ${GeistSans.variable} ${GeistMono.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body className="flex min-h-screen flex-col bg-[#0a0a0a] text-[#ededed] antialiased">
|
||||
<Script id="google-analytics-loader" strategy="lazyOnload">
|
||||
{`if (typeof window !== 'undefined' && window.location.hostname === '${gaTrackedDomain}') {
|
||||
var s = document.createElement('script');
|
||||
s.async = true;
|
||||
s.src = 'https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}';
|
||||
document.head.appendChild(s);
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${gaMeasurementId}', { cookie_domain: '${gaTrackedDomain}' });
|
||||
}`}
|
||||
</Script>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "Oh My OpenAgent",
|
||||
short_name: "OMO",
|
||||
description:
|
||||
"The Best Agent Harness. Meet Sisyphus: The batteries-included agent that codes like you.",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#0a0a0a",
|
||||
theme_color: "#00d4ff",
|
||||
icons: [
|
||||
{
|
||||
src: "/icon.svg",
|
||||
sizes: "any",
|
||||
type: "image/svg+xml",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export { landingMetadata as metadata } from "@/app/_components/landing-page"
|
||||
|
||||
import type { JSX } from "react"
|
||||
import { setRequestLocale } from "next-intl/server"
|
||||
import { LandingPage } from "@/app/_components/landing-page"
|
||||
import { LocalizedPageShell } from "@/app/_components/localized-page-shell"
|
||||
import { defaultLocale } from "@/i18n/config"
|
||||
|
||||
export default function HomePage(): JSX.Element {
|
||||
setRequestLocale(defaultLocale)
|
||||
|
||||
return (
|
||||
<LocalizedPageShell locale={defaultLocale}>
|
||||
<LandingPage />
|
||||
</LocalizedPageShell>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
},
|
||||
sitemap: "https://ohmyopenagent.com/sitemap.xml",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
const BASE_URL = "https://ohmyopenagent.com"
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const routes = ["", "/docs", "/manifesto"]
|
||||
const locales = ["en", "ko", "ja", "zh"]
|
||||
|
||||
return routes.flatMap((route) =>
|
||||
locales.map((locale) => ({
|
||||
url: `${BASE_URL}/${locale}${route}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly" as const,
|
||||
priority: route === "" ? 1 : 0.8,
|
||||
})),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user