navbar and hero section fixed
This commit is contained in:
parent
70577534f5
commit
dcd10061ac
|
|
@ -8,19 +8,9 @@ export const metadata: Metadata = {
|
|||
|
||||
const themeScript = `
|
||||
(() => {
|
||||
try {
|
||||
const stored = window.localStorage.getItem("ao-theme");
|
||||
const theme = stored === "light" || stored === "dark"
|
||||
? stored
|
||||
: window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
||||
document.documentElement.dataset.theme = theme;
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
} catch {
|
||||
document.documentElement.dataset.theme = "dark";
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
}
|
||||
document.documentElement.dataset.theme = "dark";
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
})();
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,32 @@
|
|||
const agents = [
|
||||
{ name: "Claude Code", src: "/docs/logos/claude-code.svg", alt: "Claude Code", className: "h-7 sm:h-8 lg:h-10" },
|
||||
{ name: "Codex", src: "/docs/logos/codex.svg", alt: "Codex", className: "h-7 sm:h-8 lg:h-10 rounded-md" },
|
||||
{ name: "Cursor", src: "/docs/logos/cursor.svg", alt: "Cursor", className: "agent-logo-contrast h-7 sm:h-8 lg:h-10" },
|
||||
{ name: "Aider", src: "/docs/logos/aider.png", alt: "Aider", className: "h-6 sm:h-7 lg:h-8" },
|
||||
{
|
||||
name: "OpenCode",
|
||||
src: "/docs/logos/opencode.svg",
|
||||
alt: "OpenCode",
|
||||
className: "agent-logo-contrast h-7 sm:h-8 lg:h-10",
|
||||
},
|
||||
{ name: "Claude Code", id: "claude-code", src: "/docs/logos/claude-code.svg" },
|
||||
{ name: "Codex", id: "codex", src: "/docs/logos/codex.svg" },
|
||||
{ name: "Aider", id: "aider", src: "/docs/logos/aider.png" },
|
||||
{ name: "OpenCode", id: "opencode", src: "/docs/logos/opencode.svg" },
|
||||
{ name: "Grok", id: "grok", src: "https://www.google.com/s2/favicons?domain=x.ai&sz=64" },
|
||||
{ name: "Droid", id: "droid", src: "https://www.google.com/s2/favicons?domain=factory.ai&sz=64" },
|
||||
{ name: "Amp", id: "amp", src: "https://www.google.com/s2/favicons?domain=ampcode.com&sz=64" },
|
||||
{ name: "Agy", id: "agy", src: "https://www.google.com/s2/favicons?domain=antigravity.google&sz=64" },
|
||||
{ name: "Crush", id: "crush", src: "https://www.google.com/s2/favicons?domain=charm.land&sz=64" },
|
||||
{ name: "Cursor", id: "cursor", src: "/docs/logos/cursor.svg" },
|
||||
{ name: "Qwen", id: "qwen", src: "https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" },
|
||||
{ name: "Copilot", id: "copilot", src: "https://www.google.com/s2/favicons?domain=github.com&sz=64" },
|
||||
{ name: "Goose", id: "goose", src: "https://www.google.com/s2/favicons?domain=goose-docs.ai&sz=64" },
|
||||
{ name: "Auggie", id: "auggie", src: "https://www.google.com/s2/favicons?domain=augmentcode.com&sz=64" },
|
||||
{ name: "Continue", id: "continue", src: "https://www.google.com/s2/favicons?domain=continue.dev&sz=64" },
|
||||
{ name: "Devin", id: "devin", src: "https://www.google.com/s2/favicons?domain=cognition.ai&sz=64" },
|
||||
{ name: "Cline", id: "cline", src: "https://www.google.com/s2/favicons?domain=cline.bot&sz=64" },
|
||||
{ name: "Kimi", id: "kimi", src: "https://www.google.com/s2/favicons?domain=kimi.com&sz=64" },
|
||||
{ name: "Kiro", id: "kiro", src: "https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" },
|
||||
{ name: "Kilo Code", id: "kilocode", src: "https://www.google.com/s2/favicons?domain=kilocode.ai&sz=64" },
|
||||
{ name: "Vibe", id: "vibe", src: "https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" },
|
||||
{ name: "Pi", id: "pi", src: "https://www.google.com/s2/favicons?domain=github.com&sz=64" },
|
||||
{ name: "Autohand", id: "autohand", src: "https://www.google.com/s2/favicons?domain=npmjs.com&sz=64" },
|
||||
];
|
||||
|
||||
export function LandingAgentsBar() {
|
||||
const marqueeAgents = [...agents, ...agents];
|
||||
|
||||
return (
|
||||
<section
|
||||
id="agents"
|
||||
|
|
@ -23,7 +38,7 @@ export function LandingAgentsBar() {
|
|||
<div className="flex flex-wrap items-baseline gap-x-4 gap-y-2">
|
||||
<span className="serial-num font-mono text-xs">01 - coverage</span>
|
||||
<h2 className="font-display text-2xl font-bold leading-none tracking-tight text-[color:var(--fg)] sm:text-3xl">
|
||||
One daemon. <span className="text-[color:var(--fg-muted)]">Twenty-three agent harnesses.</span>
|
||||
One daemon. <span className="text-[color:var(--fg-muted)]">23 agent harnesses.</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p className="max-w-md font-mono text-xs leading-relaxed text-[color:var(--fg-dim)]">
|
||||
|
|
@ -33,21 +48,29 @@ export function LandingAgentsBar() {
|
|||
</div>
|
||||
|
||||
<div className="container-page pb-6">
|
||||
<div className="mx-auto grid max-w-2xl grid-cols-2 items-end justify-items-center gap-x-0 gap-y-4 sm:grid-cols-3 lg:grid-cols-5">
|
||||
{agents.map((agent) => (
|
||||
<div key={agent.name} className="group flex min-h-[60px] w-full flex-col items-center justify-end gap-2">
|
||||
<div className="flex h-9 items-end justify-center sm:h-10 lg:h-11">
|
||||
<img
|
||||
src={agent.src}
|
||||
alt={agent.alt}
|
||||
className={`${agent.className} max-w-[56px] object-contain transition-transform duration-300 group-hover:-translate-y-0.5`}
|
||||
/>
|
||||
<div className="relative mx-auto max-w-3xl overflow-hidden">
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 z-10 w-20 bg-gradient-to-r from-[color:var(--bg-deep)] to-transparent" />
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 z-10 w-20 bg-gradient-to-l from-[color:var(--bg-deep)] to-transparent" />
|
||||
<div className="agents-marquee-track flex w-max items-end gap-3">
|
||||
{marqueeAgents.map((agent, index) => (
|
||||
<div
|
||||
key={`${agent.id}-${index}`}
|
||||
className="group flex h-[78px] w-[118px] shrink-0 flex-col items-center justify-end gap-2 px-2 py-2"
|
||||
>
|
||||
<div className="flex h-10 items-end justify-center">
|
||||
<img
|
||||
src={agent.src}
|
||||
alt=""
|
||||
referrerPolicy="no-referrer"
|
||||
className="h-8 max-w-[44px] object-contain transition-transform duration-200 ease-out group-hover:scale-110"
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-full truncate font-mono text-[14px] leading-none tracking-[0.04em] text-[color:var(--fg-dim)]">
|
||||
{agent.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-mono text-[14px] leading-none tracking-[0.06em] text-[color:var(--fg-dim)] sm:text-[16px] lg:text-[18px]">
|
||||
{agent.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ export function LandingCTA() {
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
data-testid="cta-github-btn"
|
||||
className="group inline-flex items-center gap-2 rounded-lg bg-[color:var(--accent)] px-6 py-3.5 text-[15px] font-semibold text-white shadow-[0_0_0_1px_rgba(255,255,255,0.1)_inset,0_8px_24px_-8px_rgba(77,141,255,0.6)] transition-all hover:brightness-110"
|
||||
style={{ color: "#fff" }}
|
||||
className="group inline-flex items-center gap-2 rounded-lg bg-[color:var(--accent)] px-6 py-3.5 text-[15px] font-semibold shadow-[0_0_0_1px_rgba(255,255,255,0.1)_inset,0_8px_24px_-8px_rgba(130,170,255,0.42)] transition-all hover:brightness-110"
|
||||
style={{ color: "#081225" }}
|
||||
>
|
||||
<GithubIcon className="h-4 w-4" />
|
||||
Star on GitHub · 7.7k
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
function GithubIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
|
|
@ -15,21 +19,444 @@ function ArrowRightIcon({ className = "" }: { className?: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function BookIcon({ className = "" }: { className?: string }) {
|
||||
function DownloadIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M12 7v14" />
|
||||
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
|
||||
<path d="M12 3v12" />
|
||||
<path d="m7 10 5 5 5-5" />
|
||||
<path d="M5 21h14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const projects = [
|
||||
{
|
||||
name: "api-gateway",
|
||||
count: 4,
|
||||
tasks: ["Split terminal mux responsibilities", "fix auth timeout retry loop", "add rate limit headers"],
|
||||
},
|
||||
{
|
||||
name: "webgl-preview",
|
||||
count: 8,
|
||||
tasks: ["Restore fallback renderer affordance", "cache compiled shader programs", "ship frame statistics overlay"],
|
||||
},
|
||||
{
|
||||
name: "mobile-shell",
|
||||
count: 2,
|
||||
tasks: ["repair back swipe gesture", "profile sheet accessibility pass"],
|
||||
},
|
||||
{
|
||||
name: "billing-portal",
|
||||
count: 2,
|
||||
tasks: ["invoice CSV export", "tax id validation errors"],
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Working",
|
||||
color: "#ffb454",
|
||||
cards: [
|
||||
{
|
||||
status: "Working",
|
||||
agent: "claude",
|
||||
title: "Split terminal mux responsibilities",
|
||||
branch: "session/ao-204",
|
||||
meta: "no PR yet",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Needs you",
|
||||
color: "#ff6b6b",
|
||||
cards: [
|
||||
{
|
||||
status: "CI failed",
|
||||
agent: "codex",
|
||||
title: "fix auth timeout retry loop",
|
||||
branch: "fix/auth-timeouts",
|
||||
meta: "PR #184 · open",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "In review",
|
||||
color: "#8fb7ff",
|
||||
cards: [
|
||||
{
|
||||
status: "Review pending",
|
||||
agent: "opencode",
|
||||
title: "add rate limit headers",
|
||||
branch: "feat/rate-limit-headers",
|
||||
meta: "PR #185 · open",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Ready to merge",
|
||||
color: "#72e29b",
|
||||
cards: [
|
||||
{
|
||||
status: "Mergeable",
|
||||
agent: "cursor",
|
||||
title: "Build end-to-end onboarding test for published npm package",
|
||||
branch: "test/onboarding-harness",
|
||||
meta: "PR #204 · approved",
|
||||
},
|
||||
{
|
||||
status: "Checks passed",
|
||||
agent: "aider",
|
||||
title: "publish linux desktop install path",
|
||||
branch: "docs/linux-install",
|
||||
meta: "PR #211 · 2 approvals",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function PlusIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M12 5v14" />
|
||||
<path d="M5 12h14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function NetworkIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M12 5v5" />
|
||||
<path d="M6 19v-4h12v4" />
|
||||
<path d="M4 19h4" />
|
||||
<path d="M10 10h4" />
|
||||
<path d="M16 19h4" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function GridIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
|
||||
<path d="M4 4h6v6H4z" />
|
||||
<path d="M14 4h6v6h-6z" />
|
||||
<path d="M4 14h6v6H4z" />
|
||||
<path d="M14 14h6v6h-6z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function MoreIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="12" cy="5" r="1.6" />
|
||||
<circle cx="12" cy="12" r="1.6" />
|
||||
<circle cx="12" cy="19" r="1.6" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
|
||||
<path d="M12 15.5A3.5 3.5 0 1 0 12 8a3.5 3.5 0 0 0 0 7.5Z" />
|
||||
<path d="M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.88-.34 1.7 1.7 0 0 0-1.03 1.56V21a2 2 0 0 1-4 0v-.08a1.7 1.7 0 0 0-1.03-1.56 1.7 1.7 0 0 0-1.88.34l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.56-1.03H3a2 2 0 0 1 0-4h.08A1.7 1.7 0 0 0 4.6 8.94a1.7 1.7 0 0 0-.34-1.88l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.88.34H9A1.7 1.7 0 0 0 10 3V3a2 2 0 0 1 4 0v.08a1.7 1.7 0 0 0 1.03 1.56 1.7 1.7 0 0 0 1.88-.34l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.88V9c.22.6.8 1 1.44 1H21a2 2 0 0 1 0 4h-.08A1.7 1.7 0 0 0 19.4 15Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
|
||||
<rect x="4" y="5" width="16" height="14" rx="2" />
|
||||
<path d="M9 5v14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function HeroDashboardMockup() {
|
||||
const [activeProject, setActiveProject] = useState("api-gateway");
|
||||
const [activeCard, setActiveCard] = useState("fix auth timeout retry loop");
|
||||
const [activeProjectAction, setActiveProjectAction] = useState("api-gateway-board");
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||
const [openProjects, setOpenProjects] = useState<Record<string, boolean>>({
|
||||
"api-gateway": true,
|
||||
"webgl-preview": true,
|
||||
"mobile-shell": true,
|
||||
"billing-portal": true,
|
||||
});
|
||||
|
||||
function toggleProject(projectName: string) {
|
||||
setActiveProject(projectName);
|
||||
setOpenProjects((current) => ({
|
||||
...current,
|
||||
[projectName]: !current[projectName],
|
||||
}));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto mt-6 max-w-[1600px]" data-testid="hero-dashboard-interactive">
|
||||
<div className="pointer-events-none absolute -inset-x-8 -inset-y-10 rounded-[30px] bg-[color:var(--accent)] opacity-[0.04] blur-3xl" />
|
||||
<div className="relative overflow-hidden rounded-[18px] border border-[#17191f] bg-[#020203] shadow-[0_34px_120px_-76px_rgba(0,0,0,1)]">
|
||||
<div
|
||||
className="grid min-h-[640px] text-left text-[color:var(--fg)] transition-[grid-template-columns] duration-300"
|
||||
style={{ gridTemplateColumns: sidebarOpen ? "264px minmax(0, 1fr)" : "64px minmax(0, 1fr)" }}
|
||||
>
|
||||
<aside className="flex min-h-[640px] flex-col border-b border-[#15171d] bg-[#030304] md:border-b-0 md:border-r">
|
||||
<div className="flex h-14 items-center justify-between border-b border-[#17191f] px-5">
|
||||
<div className={`flex items-center gap-2 text-[15px] font-semibold ${sidebarOpen ? "" : "md:justify-center"}`}>
|
||||
<img src="/ao-logo.svg" alt="" className="h-5 w-5" draggable="false" />
|
||||
<span className={sidebarOpen ? "" : "md:hidden"}>Agent Orchestrator</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSidebarOpen((current) => !current)}
|
||||
className="cursor-pointer rounded-md p-1.5 text-[#7a828f] transition-colors hover:bg-white/[0.06] hover:text-white"
|
||||
aria-label={sidebarOpen ? "Collapse sidebar" : "Expand sidebar"}
|
||||
aria-expanded={sidebarOpen}
|
||||
>
|
||||
<SidebarIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{sidebarOpen ? (
|
||||
<div className="min-h-0 flex-1 overflow-hidden p-3">
|
||||
<div className="mb-3 flex items-center justify-between px-2 text-[11px] font-bold uppercase tracking-[0.18em] text-[#737b89]">
|
||||
Projects
|
||||
<span className="text-lg font-normal leading-none">+</span>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{projects.map((project) => (
|
||||
<div
|
||||
key={project.name}
|
||||
className="group"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleProject(project.name)}
|
||||
className={`flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-2 text-left transition-colors ${
|
||||
activeProject === project.name
|
||||
? "bg-[#11131a] text-[#eceef2]"
|
||||
: "text-[#a0a6b2] hover:bg-[#0b0d12] hover:text-[#eceef2]"
|
||||
}`}
|
||||
>
|
||||
<ChevronIcon className={`h-3.5 w-3.5 shrink-0 transition-transform ${openProjects[project.name] ? "rotate-90" : ""}`} />
|
||||
<span className="min-w-0 flex-1 truncate text-[15px] font-medium">{project.name}</span>
|
||||
<span className="hidden items-center gap-2 text-[#737b89] group-hover:flex">
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${project.name} board`}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActiveProject(project.name);
|
||||
setActiveProjectAction(`${project.name}-board`);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setActiveProject(project.name);
|
||||
setActiveProjectAction(`${project.name}-board`);
|
||||
}
|
||||
}}
|
||||
className={`cursor-pointer rounded p-0.5 transition-colors hover:bg-white/10 hover:text-white ${
|
||||
activeProjectAction === `${project.name}-board` ? "text-white" : ""
|
||||
}`}
|
||||
>
|
||||
<GridIcon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${project.name} orchestrator`}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActiveProject(project.name);
|
||||
setActiveProjectAction(`${project.name}-orchestrator`);
|
||||
setActiveCard(`Spawn ${project.name} orchestrator`);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setActiveProject(project.name);
|
||||
setActiveProjectAction(`${project.name}-orchestrator`);
|
||||
setActiveCard(`Spawn ${project.name} orchestrator`);
|
||||
}
|
||||
}}
|
||||
className={`cursor-pointer rounded p-0.5 transition-colors hover:bg-white/10 hover:text-white ${
|
||||
activeProjectAction === `${project.name}-orchestrator` ? "text-white" : ""
|
||||
}`}
|
||||
>
|
||||
<NetworkIcon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${project.name} options`}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActiveProject(project.name);
|
||||
setActiveProjectAction(`${project.name}-options`);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setActiveProject(project.name);
|
||||
setActiveProjectAction(`${project.name}-options`);
|
||||
}
|
||||
}}
|
||||
className={`cursor-pointer rounded p-0.5 transition-colors hover:bg-white/10 hover:text-white ${
|
||||
activeProjectAction === `${project.name}-options` ? "text-white" : ""
|
||||
}`}
|
||||
>
|
||||
<MoreIcon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
</span>
|
||||
<span className="min-w-5 rounded-md border border-[#181b22] bg-[#08090c] px-1.5 py-0.5 text-center text-[11px] leading-none text-[#7a828f] group-hover:hidden">
|
||||
{project.count}
|
||||
</span>
|
||||
</button>
|
||||
{openProjects[project.name] ? (
|
||||
<div className="mt-2 space-y-1.5 pl-8">
|
||||
{project.tasks.map((task, index) => (
|
||||
<button
|
||||
type="button"
|
||||
key={task}
|
||||
className={`flex w-full min-w-0 cursor-pointer items-center gap-2 rounded py-1 text-left text-[13px] transition-colors ${
|
||||
activeCard === task ? "text-[#eceef2]" : "text-[#9aa1ac] hover:text-[#eceef2]"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveProject(project.name);
|
||||
setActiveCard(task);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="h-1.5 w-1.5 shrink-0 rounded-full"
|
||||
style={{ backgroundColor: index === 1 ? "#ff7070" : index === 2 ? "#89919e" : "#ffad4d" }}
|
||||
/>
|
||||
<span className="truncate">{task}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="hidden min-h-0 flex-1 flex-col items-center gap-3 p-3 md:flex">
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project.name}
|
||||
type="button"
|
||||
onClick={() => setActiveProject(project.name)}
|
||||
className={`flex h-9 w-9 cursor-pointer items-center justify-center rounded-lg text-xs font-semibold transition-colors ${
|
||||
activeProject === project.name
|
||||
? "bg-[#11131a] text-[#eceef2]"
|
||||
: "text-[#8a92a0] hover:bg-[#0b0d12] hover:text-white"
|
||||
}`}
|
||||
aria-label={project.name}
|
||||
title={project.name}
|
||||
>
|
||||
{project.name.slice(0, 2)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button className={`flex h-14 cursor-pointer items-center gap-3 border-t border-[#15171d] px-5 text-sm text-[#8a92a0] transition-colors hover:bg-[#090b10] hover:text-white ${sidebarOpen ? "" : "md:justify-center md:px-0"}`}>
|
||||
<SettingsIcon className="h-4 w-4" />
|
||||
<span className={sidebarOpen ? "" : "md:hidden"}>Settings</span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<div className="min-w-0 bg-[#020203] p-5 sm:p-7">
|
||||
<div className="mb-6 flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div className="flex items-end gap-3">
|
||||
<h2 className="text-[23px] font-bold leading-none">Board</h2>
|
||||
<p className="pb-0.5 text-[13px] text-[#6f7784]">Live agent sessions flowing from work → review → merge.</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveCard("New task drafted")}
|
||||
className="hero-pressable inline-flex cursor-pointer items-center gap-2 rounded-lg border border-[#181b22] bg-[#050608] px-4 py-2 text-sm font-semibold text-[#9aa2af] hover:border-[#2b313d] hover:text-white"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
New task
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveCard("Spawn Orchestrator")}
|
||||
className="hero-pressable inline-flex cursor-pointer items-center gap-2 rounded-lg bg-[color:var(--accent)] px-4 py-2 text-sm font-semibold hover:brightness-110"
|
||||
style={{ color: "#081225" }}
|
||||
>
|
||||
<NetworkIcon className="h-4 w-4" />
|
||||
Spawn Orchestrator
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 lg:grid-cols-4">
|
||||
{columns.map((column) => (
|
||||
<section key={column.title} className="min-h-[510px] rounded-xl bg-[#07080a] p-4">
|
||||
<div className="mb-5 flex items-center justify-between px-0.5">
|
||||
<div className="flex items-center gap-2 text-[12px] font-bold uppercase tracking-[0.14em]" style={{ color: column.color }}>
|
||||
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: column.color }} />
|
||||
{column.title}
|
||||
</div>
|
||||
<span className="text-xs text-[#68707d]">{column.cards.length}</span>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{column.cards.map((card) => (
|
||||
<button
|
||||
key={card.title}
|
||||
type="button"
|
||||
onClick={() => setActiveCard(card.title)}
|
||||
className={`hero-pressable w-full cursor-pointer rounded-lg border p-4 text-left ${
|
||||
activeCard === card.title
|
||||
? "border-[#2a3b5d] bg-[#0d111a]"
|
||||
: "border-[#181b22] bg-[#0d0f13] hover:border-[#2b313d] hover:bg-[#11141a]"
|
||||
}`}
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<span className="flex items-center gap-2 text-xs font-medium" style={{ color: column.color }}>
|
||||
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: column.color }} />
|
||||
{card.status}
|
||||
</span>
|
||||
<span className="font-mono text-[11px] text-[#6e7684]">{card.agent}</span>
|
||||
</div>
|
||||
<div className="text-[14px] font-semibold leading-[1.45] text-[#f0f1f4]">{card.title}</div>
|
||||
<div className="mt-4 font-mono text-[11px] text-[#68707d]">{card.branch}</div>
|
||||
<div className="mt-3 border-t border-[#252933] pt-3 font-mono text-[11px] text-[#68707d]">{card.meta}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LandingHero() {
|
||||
return (
|
||||
<section
|
||||
data-testid="hero-section"
|
||||
id="top"
|
||||
className="relative overflow-hidden border-b border-[color:var(--border)] pb-20 pt-14 sm:pt-16 lg:pb-24"
|
||||
className="relative overflow-hidden border-b border-[color:var(--border)] pb-10 pt-28 sm:pt-32 lg:pb-14"
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 opacity-[0.24]"
|
||||
|
|
@ -42,92 +469,57 @@ export function LandingHero() {
|
|||
}}
|
||||
/>
|
||||
<div className="relative z-10 mx-auto w-full max-w-[1680px] px-5 sm:px-8 lg:px-12 xl:px-16">
|
||||
<div className="grid items-center gap-10 lg:grid-cols-[0.9fr_1.1fr] lg:gap-10 xl:gap-14">
|
||||
<div className="max-w-[760px] text-left">
|
||||
<div className="mb-5 font-mono text-[11px] font-semibold uppercase tracking-[0.22em] text-[color:var(--accent)]">
|
||||
Agent work, from issue to merge
|
||||
</div>
|
||||
<h1
|
||||
data-testid="hero-headline"
|
||||
className="font-display font-bold leading-[0.94] tracking-tight text-[color:var(--fg)]"
|
||||
style={{ fontSize: "clamp(52px, 5.05vw, 96px)" }}
|
||||
<div className="mx-auto max-w-[1500px] text-center">
|
||||
<h1
|
||||
data-testid="hero-headline"
|
||||
className="mx-auto max-w-[1320px] font-display font-semibold leading-[0.98] text-[#f2f3f5]"
|
||||
style={{ fontSize: "clamp(38px, 4.35vw, 78px)", letterSpacing: "-0.035em" }}
|
||||
>
|
||||
<span className="block">
|
||||
Stop babysitting coding agents.
|
||||
</span>
|
||||
<span className="mt-2 block">
|
||||
Start merging <span className="text-[#93b4f8]">real work.</span>
|
||||
</span>
|
||||
</h1>
|
||||
<p className="mx-auto mt-7 max-w-[680px] text-[15px] font-medium leading-[1.75] text-[color:var(--fg-muted)] sm:text-[17px]">
|
||||
Free, Apache 2.0 licensed, and runs on your laptop. Fork it, inspect it, and ship your first
|
||||
parallel agent workflow in minutes.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<a
|
||||
href="/docs/installation"
|
||||
className="hero-pressable group inline-flex items-center gap-2 rounded-lg bg-[color:var(--accent)] px-7 py-3.5 text-[15px] font-bold hover:brightness-110"
|
||||
style={{ color: "#081225" }}
|
||||
>
|
||||
<span className="block 2xl:whitespace-nowrap">Review the work,</span>
|
||||
<span className="block text-[color:var(--accent)] 2xl:whitespace-nowrap">Not the agents.</span>
|
||||
</h1>
|
||||
<p
|
||||
data-testid="hero-subtitle"
|
||||
className="mt-6 max-w-[720px] text-[18px] font-semibold leading-[1.62] text-[color:var(--fg-muted)] sm:text-[20px]"
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
Install Agent Orchestrator
|
||||
<ArrowRightIcon className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/AgentWrapper/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hero-pressable inline-flex items-center gap-2 rounded-lg border border-[color:var(--border-strong)] bg-transparent px-5 py-3.5 text-[15px] font-semibold text-[color:var(--fg)] hover:bg-[color:var(--bg-card-hover)]"
|
||||
>
|
||||
Every issue gets its own checkout, session, branch, PR, checks, and review thread. When something breaks,
|
||||
the right context goes back to the right agent.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap items-center gap-3">
|
||||
<a
|
||||
href="https://github.com/AgentWrapper/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
data-testid="hero-primary-cta"
|
||||
className="group inline-flex items-center gap-2 rounded-lg bg-[color:var(--accent)] px-5 py-3 text-[14px] font-semibold text-white shadow-[0_0_0_1px_rgba(255,255,255,0.1)_inset] transition-all hover:brightness-110"
|
||||
style={{ color: "#fff" }}
|
||||
>
|
||||
<GithubIcon className="h-4 w-4" />
|
||||
Install Agent Orchestrator
|
||||
<ArrowRightIcon className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
|
||||
</a>
|
||||
<a
|
||||
href="/docs"
|
||||
data-testid="hero-secondary-cta"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-[color:var(--border-strong)] bg-[color:var(--bg-card)] px-5 py-3 text-[14px] font-semibold text-[color:var(--fg)] transition-colors hover:bg-[color:var(--bg-card-hover)]"
|
||||
>
|
||||
<BookIcon className="h-4 w-4" />
|
||||
Read the docs
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-8 border-l border-[color:var(--border-strong)] pl-4 font-mono text-[11px] uppercase tracking-[0.16em] text-[color:var(--fg-dim)]">
|
||||
issue -> worktree -> session -> pull request -> review loop
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative min-w-0 overflow-visible lg:pl-2" data-testid="hero-screenshot">
|
||||
<div className="relative ml-auto w-full max-w-[1080px] pr-[clamp(70px,8vw,150px)] sm:pr-[clamp(95px,10vw,180px)] lg:pr-[clamp(110px,8vw,190px)]">
|
||||
<div className="relative rounded-[18px] border border-[color:var(--border)] bg-[color:var(--bg-elevated)] p-1">
|
||||
<div className="overflow-hidden rounded-[13px] bg-[color:var(--bg-deep)]">
|
||||
<img
|
||||
src="/hero-dashboard.png"
|
||||
alt="Agent Orchestrator dashboard board view"
|
||||
className="theme-dark-only block w-full"
|
||||
draggable="false"
|
||||
/>
|
||||
<img
|
||||
src="/hero-dashboard-light.png"
|
||||
alt="Agent Orchestrator dashboard board view in light theme"
|
||||
className="theme-light-only hidden w-full"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 right-0 hidden w-[26%] min-w-[170px] translate-y-[12%] sm:block">
|
||||
<div className="rounded-[24px] border border-[color:var(--border)] bg-[color:var(--bg-elevated)] p-1 shadow-[0_24px_80px_-48px_rgba(0,0,0,0.95)]">
|
||||
<div className="relative overflow-hidden rounded-[19px] bg-[color:var(--bg-deep)]">
|
||||
<img
|
||||
src="/hero-new-task.png"
|
||||
alt="Agent Orchestrator mobile workflow preview"
|
||||
className="theme-dark-only block aspect-[9/16] h-auto w-full object-cover object-center"
|
||||
draggable="false"
|
||||
/>
|
||||
<img
|
||||
src="/hero-dashboard-light.png"
|
||||
alt="Agent Orchestrator mobile workflow preview in light theme"
|
||||
className="theme-light-only hidden aspect-[9/16] h-auto w-full object-cover object-center"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<GithubIcon className="h-4 w-4" />
|
||||
<span>Star on GitHub</span>
|
||||
<span className="rounded-md border border-white/10 bg-white/[0.04] px-1.5 py-0.5 text-[12px] leading-none text-[color:var(--fg-muted)]">
|
||||
7.7k
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-12 flex max-w-[1600px] items-center gap-4 px-1 text-left">
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-[color:var(--border-strong)] to-[color:var(--border-strong)]" />
|
||||
<div className="whitespace-nowrap text-[11px] font-bold uppercase tracking-[0.18em] text-[color:var(--fg-dim)]">
|
||||
Live board preview
|
||||
</div>
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-[color:var(--border-strong)] via-[color:var(--border-strong)] to-transparent" />
|
||||
</div>
|
||||
|
||||
<HeroDashboardMockup />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,6 @@
|
|||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const navItems = [
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "How it works", href: "#how" },
|
||||
{ label: "Architecture", href: "#architecture" },
|
||||
{ label: "Quickstart", href: "#quickstart" },
|
||||
];
|
||||
|
||||
function GithubIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
|
|
@ -26,6 +19,16 @@ function ArrowUpRightIcon({ className = "" }: { className?: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function DownloadIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M12 3v12" />
|
||||
<path d="m7 10 5 5 5-5" />
|
||||
<path d="M5 21h14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function MenuIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
|
|
@ -36,7 +39,7 @@ function MenuIcon({ className = "" }: { className?: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function XIcon({ className = "" }: { className?: string }) {
|
||||
function CloseIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M18 6 6 18" />
|
||||
|
|
@ -45,123 +48,167 @@ function XIcon({ className = "" }: { className?: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function SunIcon({ className = "" }: { className?: string }) {
|
||||
function XSocialIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
|
||||
<svg className={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M18.9 2.25h3.24l-7.08 8.09 8.33 11.41h-6.52l-5.11-6.91-5.84 6.91H2.66l7.57-8.67L2.25 2.25h6.69l4.62 6.3 5.34-6.3Zm-1.14 17.5h1.8L7.96 4.14H6.03l11.73 15.61Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function MoonIcon({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M20.99 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 20.99 12.79Z" />
|
||||
</svg>
|
||||
);
|
||||
const socials = [
|
||||
{
|
||||
label: "GitHub",
|
||||
href: "https://github.com/AgentWrapper/agent-orchestrator",
|
||||
icon: GithubIcon,
|
||||
},
|
||||
{
|
||||
label: "X",
|
||||
href: "https://twitter.com/aoagents",
|
||||
icon: XSocialIcon,
|
||||
},
|
||||
];
|
||||
|
||||
const navLinks = [
|
||||
{ label: "Demo", href: "#see-it" },
|
||||
{ label: "Agents", href: "#agents" },
|
||||
{ label: "Docs", href: "/docs" },
|
||||
];
|
||||
|
||||
function getPlatformLabel() {
|
||||
if (typeof navigator === "undefined") return "Install AO";
|
||||
|
||||
const platform = `${navigator.platform} ${navigator.userAgent}`.toLowerCase();
|
||||
if (platform.includes("mac")) return "Install for macOS";
|
||||
if (platform.includes("win")) return "Install for Windows";
|
||||
if (platform.includes("linux") || platform.includes("x11")) return "Install for Linux";
|
||||
return "Install AO";
|
||||
}
|
||||
|
||||
export function LandingNav() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [theme, setTheme] = useState("dark");
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const isLight = theme === "light";
|
||||
const [installLabel, setInstallLabel] = useState("Install AO");
|
||||
|
||||
useEffect(() => {
|
||||
const current = document.documentElement.dataset.theme;
|
||||
setTheme(current === "light" ? "light" : "dark");
|
||||
setMounted(true);
|
||||
setInstallLabel(getPlatformLabel());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mounted) return;
|
||||
document.documentElement.dataset.theme = theme;
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
window.localStorage.setItem("ao-theme", theme);
|
||||
}, [mounted, theme]);
|
||||
document.documentElement.dataset.theme = "dark";
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header
|
||||
data-testid="site-nav"
|
||||
className="sticky top-0 z-40 border-b border-[color:var(--border)] bg-[color:var(--nav-bg)] backdrop-blur-xl"
|
||||
className="pointer-events-none fixed inset-x-0 top-4 z-40 flex justify-center px-4"
|
||||
>
|
||||
<div className="container-page flex h-16 items-center justify-between">
|
||||
<a href="#top" data-testid="nav-logo" className="group inline-flex h-10 items-center gap-2.5">
|
||||
<img src="/ao-logo.svg" alt="Agent Orchestrator" className="block h-10 w-10 shrink-0 object-contain" />
|
||||
<span className="font-display text-[15px] font-bold leading-none tracking-tight text-[color:var(--fg)]">
|
||||
<div
|
||||
className="pointer-events-auto grid h-14 w-full max-w-[1040px] grid-cols-[1fr_auto] items-center gap-4 rounded-2xl bg-black/[0.58] px-4 shadow-[0_20px_70px_-52px_rgba(0,0,0,1),inset_0_1px_0_rgba(255,255,255,0.08),inset_0_0_0_1px_rgba(255,255,255,0.055)] backdrop-blur-2xl sm:px-5 md:grid-cols-[1fr_auto_1fr]"
|
||||
>
|
||||
<a
|
||||
href="#top"
|
||||
data-testid="nav-logo"
|
||||
className="group inline-flex h-10 shrink-0 items-center gap-3 justify-self-start"
|
||||
>
|
||||
<img src="/ao-logo.svg" alt="Agent Orchestrator" className="block h-9 w-9 shrink-0 -translate-y-1 object-contain" />
|
||||
<span className="font-display text-[15px] font-bold leading-[1.1] tracking-tight text-[color:var(--fg)]">
|
||||
Agent Orchestrator
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav className="hidden items-center gap-7 md:flex">
|
||||
{navItems.map((item) => (
|
||||
<nav className="hidden items-center justify-center gap-1 rounded-xl bg-white/[0.035] p-1 justify-self-center md:flex" aria-label="Primary">
|
||||
{navLinks.map((item) => (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
className="text-[13px] font-medium text-[color:var(--fg-muted)] transition-colors hover:text-[color:var(--fg)]"
|
||||
className="rounded-lg px-4 py-2 text-[14px] font-semibold text-[color:var(--fg-muted)] transition-[background-color,color,transform] duration-160 ease-out hover:bg-white/[0.08] hover:text-[color:var(--fg)] active:scale-95"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="hidden items-center justify-end gap-2 justify-self-end md:flex">
|
||||
{socials.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={item.label}
|
||||
title={item.label}
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-white/[0.035] text-[color:var(--fg-muted)] transition-[background-color,color,transform,filter] duration-160 ease-out hover:scale-105 hover:bg-white/[0.075] hover:text-[color:var(--fg)] active:scale-95"
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
<a
|
||||
href="https://github.com/AgentWrapper/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
data-testid="nav-star-btn"
|
||||
className="hidden items-center gap-1.5 rounded-md border border-[color:var(--border-strong)] px-2.5 py-1.5 text-[12px] font-medium text-[color:var(--fg-muted)] transition-colors hover:border-[color:var(--border-bright)] hover:text-[color:var(--fg)] sm:inline-flex"
|
||||
>
|
||||
<GithubIcon className="h-3.5 w-3.5" />
|
||||
<span className="font-mono">7.7k</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTheme(isLight ? "dark" : "light")}
|
||||
data-testid="theme-toggle"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-[color:var(--border-strong)] text-[color:var(--fg-muted)] transition-colors hover:border-[color:var(--border-bright)] hover:text-[color:var(--fg)]"
|
||||
aria-label={isLight ? "Switch to dark theme" : "Switch to light theme"}
|
||||
title={isLight ? "Dark theme" : "Light theme"}
|
||||
>
|
||||
{isLight ? <MoonIcon className="h-4 w-4" /> : <SunIcon className="h-4 w-4" />}
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/AgentWrapper/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="/docs/installation"
|
||||
data-testid="nav-cta-btn"
|
||||
className="inline-flex items-center gap-1.5 rounded-md bg-[color:var(--accent)] px-3.5 py-1.5 text-[13px] font-semibold text-white transition-all hover:brightness-110"
|
||||
style={{ color: "#fff" }}
|
||||
className="group ml-1 inline-flex h-9 items-center gap-2 rounded-md bg-[color:var(--accent)] px-4 text-[13px] font-semibold shadow-[0_0_0_1px_rgba(255,255,255,0.08)_inset] transition-all hover:brightness-110"
|
||||
style={{ color: "#081225" }}
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
<span>{installLabel}</span>
|
||||
<ArrowUpRightIcon className="h-3.5 w-3.5 opacity-80 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex items-center gap-2 md:hidden"
|
||||
>
|
||||
<a
|
||||
href="/docs/installation"
|
||||
data-testid="nav-mobile-cta-btn"
|
||||
className="inline-flex h-9 items-center gap-1.5 rounded-md bg-[color:var(--accent)] px-3 text-[12px] font-semibold"
|
||||
style={{ color: "#081225" }}
|
||||
>
|
||||
<DownloadIcon className="h-3.5 w-3.5" />
|
||||
Install
|
||||
<ArrowUpRightIcon className="h-3.5 w-3.5" />
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="rounded-md border border-[color:var(--border-strong)] p-1.5 text-[color:var(--fg)] md:hidden"
|
||||
className="rounded-md border border-[color:var(--border-strong)] p-2 text-[color:var(--fg)]"
|
||||
data-testid="nav-mobile-toggle"
|
||||
aria-label="menu"
|
||||
>
|
||||
{open ? <XIcon className="h-4 w-4" /> : <MenuIcon className="h-4 w-4" />}
|
||||
{open ? <CloseIcon className="h-4 w-4" /> : <MenuIcon className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{open && (
|
||||
<div className="border-t border-[color:var(--border)] bg-[color:var(--bg-card)] md:hidden">
|
||||
<div className="flex flex-col gap-3.5 px-5 py-4">
|
||||
{navItems.map((item) => (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
onClick={() => setOpen(false)}
|
||||
className="text-sm font-medium text-[color:var(--fg-muted)]"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
<div className="pointer-events-auto mt-2 w-[calc(100%-2rem)] max-w-[980px] rounded-2xl bg-black/[0.72] p-3 shadow-[0_20px_70px_-52px_rgba(0,0,0,1),inset_0_1px_0_rgba(255,255,255,0.08),inset_0_0_0_1px_rgba(255,255,255,0.055)] backdrop-blur-2xl md:hidden">
|
||||
<div className="flex flex-col gap-3">
|
||||
{socials.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => setOpen(false)}
|
||||
className="inline-flex items-center gap-2 rounded-md border border-[color:var(--border)] px-3 py-2 text-sm font-medium text-[color:var(--fg-muted)]"
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
{item.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
<a
|
||||
href="/docs/installation"
|
||||
onClick={() => setOpen(false)}
|
||||
className="inline-flex items-center justify-center gap-2 rounded-md bg-[color:var(--accent)] px-3 py-2.5 text-sm font-semibold"
|
||||
style={{ color: "#081225" }}
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
{installLabel}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,11 @@ export function LandingVideo() {
|
|||
className="relative border-t border-[color:var(--border)] py-24 sm:py-32"
|
||||
>
|
||||
<div className="container-page">
|
||||
<div className="mb-10 text-center">
|
||||
<div className="serial-num mb-3 font-mono text-xs opacity-70">see it in action</div>
|
||||
<div className="mx-auto mb-10 max-w-[1180px] text-left">
|
||||
<h2
|
||||
className="inline-block font-display font-bold leading-[1.02] tracking-tight text-[color:var(--fg)]"
|
||||
style={{ fontSize: "clamp(28px, 3.6vw, 44px)" }}
|
||||
className="inline-block font-mono text-[13px] font-bold uppercase leading-none tracking-[0.18em] text-[color:var(--fg-muted)]"
|
||||
>
|
||||
Watch the founder walk through it -{" "}
|
||||
<span className="font-editorial font-medium italic text-[color:var(--accent)]">100 PRs in 6 days.</span>
|
||||
See it in action
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -44,12 +44,37 @@ If you plan to use GitLab or Linear, install and authenticate their CLIs or cred
|
|||
## Install AO
|
||||
|
||||
<Tabs items={["npm", "pnpm", "yarn", "from source"]}>
|
||||
<Tab value="npm">```bash npm install -g @aoagents/ao ```</Tab>
|
||||
<Tab value="pnpm">```bash pnpm add -g @aoagents/ao ```</Tab>
|
||||
<Tab value="yarn">```bash yarn global add @aoagents/ao ```</Tab>
|
||||
<Tab value="npm">
|
||||
|
||||
```bash
|
||||
npm install -g @aoagents/ao
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add -g @aoagents/ao
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="yarn">
|
||||
|
||||
```bash
|
||||
yarn global add @aoagents/ao
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="from source">
|
||||
```bash git clone https://github.com/AgentWrapper/agent-orchestrator cd agent-orchestrator pnpm install pnpm build
|
||||
pnpm --filter @aoagents/ao link --global ```
|
||||
|
||||
```bash
|
||||
git clone https://github.com/AgentWrapper/agent-orchestrator
|
||||
cd agent-orchestrator
|
||||
pnpm install
|
||||
pnpm build
|
||||
pnpm --filter @aoagents/ao link --global
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
|
@ -83,11 +108,47 @@ The authenticated account must be able to read the repository, create branches,
|
|||
AO launches an agent CLI inside each worker session. Start with the one you already use, then add more later if you want per-project or per-role choices.
|
||||
|
||||
<Tabs items={["Claude Code", "Codex", "Cursor", "Aider", "OpenCode"]}>
|
||||
<Tab value="Claude Code">```bash npm install -g @anthropic-ai/claude-code claude ```</Tab>
|
||||
<Tab value="Codex">```bash npm install -g @openai/codex codex ```</Tab>
|
||||
<Tab value="Cursor">```bash curl https://cursor.com/install -fsS | bash agent --help ```</Tab>
|
||||
<Tab value="Aider">```bash pip install aider-install aider-install aider --help ```</Tab>
|
||||
<Tab value="OpenCode">```bash npm install -g opencode-ai opencode --help ```</Tab>
|
||||
<Tab value="Claude Code">
|
||||
|
||||
```bash
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
claude
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="Codex">
|
||||
|
||||
```bash
|
||||
npm install -g @openai/codex
|
||||
codex
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="Cursor">
|
||||
|
||||
```bash
|
||||
curl https://cursor.com/install -fsS | bash
|
||||
agent --help
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="Aider">
|
||||
|
||||
```bash
|
||||
pip install aider-install
|
||||
aider-install
|
||||
aider --help
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab value="OpenCode">
|
||||
|
||||
```bash
|
||||
npm install -g opencode-ai
|
||||
opencode --help
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
</Step>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
--color-bg-base: #f8fafc;
|
||||
--color-bg-surface: #ffffff;
|
||||
--color-bg-elevated: #f9fafb;
|
||||
--color-bg-subtle: rgba(37, 99, 235, 0.06);
|
||||
--color-bg-subtle: rgba(80, 120, 220, 0.08);
|
||||
--color-bg-inset: #f1f5f9;
|
||||
--color-bg-sidebar: #f3f4f6;
|
||||
|
||||
|
|
@ -19,9 +19,9 @@
|
|||
--color-border-default: rgba(28, 28, 31, 0.16);
|
||||
--color-border-subtle: rgba(28, 28, 31, 0.09);
|
||||
|
||||
--color-accent: #2563eb;
|
||||
--color-accent-dim: rgba(37, 99, 235, 0.1);
|
||||
--color-accent-border: rgba(37, 99, 235, 0.28);
|
||||
--color-accent: #4f7ee8;
|
||||
--color-accent-dim: rgba(79, 126, 232, 0.12);
|
||||
--color-accent-border: rgba(79, 126, 232, 0.32);
|
||||
--color-accent-amber: var(--color-accent);
|
||||
--color-accent-amber-dim: var(--color-accent-dim);
|
||||
--color-accent-amber-border: var(--color-accent-border);
|
||||
|
|
@ -31,12 +31,12 @@
|
|||
|
||||
/* ── Dark mode ────────────────────────────────────────────────── */
|
||||
.dark {
|
||||
--color-bg-base: #0a0a0b;
|
||||
--color-bg-surface: #0c0c0e;
|
||||
--color-bg-elevated: #111113;
|
||||
--color-bg-subtle: #101013;
|
||||
--color-bg-inset: #070708;
|
||||
--color-bg-sidebar: #080809;
|
||||
--color-bg-base: #000000;
|
||||
--color-bg-surface: #050506;
|
||||
--color-bg-elevated: #09090b;
|
||||
--color-bg-subtle: #070708;
|
||||
--color-bg-inset: #020203;
|
||||
--color-bg-sidebar: #030304;
|
||||
|
||||
--color-text-primary: #f5f5f4;
|
||||
--color-text-secondary: #8b8b89;
|
||||
|
|
@ -45,9 +45,9 @@
|
|||
--color-border-default: rgba(255, 255, 255, 0.12);
|
||||
--color-border-subtle: rgba(255, 255, 255, 0.06);
|
||||
|
||||
--color-accent: #4d8dff;
|
||||
--color-accent-dim: rgba(77, 141, 255, 0.14);
|
||||
--color-accent-border: rgba(77, 141, 255, 0.28);
|
||||
--color-accent: #82aaff;
|
||||
--color-accent-dim: rgba(130, 170, 255, 0.14);
|
||||
--color-accent-border: rgba(130, 170, 255, 0.32);
|
||||
--color-accent-amber: var(--color-accent);
|
||||
--color-accent-amber-dim: var(--color-accent-dim);
|
||||
--color-accent-amber-border: var(--color-accent-border);
|
||||
|
|
@ -138,18 +138,6 @@ body::-webkit-scrollbar,
|
|||
--status-ok: #22c55e;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-dark-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-light-only {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[data-theme="light"] .agent-logo-contrast {
|
||||
filter: invert(1) contrast(1.1);
|
||||
}
|
||||
|
||||
.liquid-glass-solid {
|
||||
background: var(--landing-accent);
|
||||
color: #121110;
|
||||
|
|
@ -188,6 +176,47 @@ body::-webkit-scrollbar,
|
|||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.hero-pressable {
|
||||
transition:
|
||||
transform 140ms cubic-bezier(0.23, 1, 0.32, 1),
|
||||
filter 160ms ease,
|
||||
background-color 160ms ease,
|
||||
border-color 160ms ease,
|
||||
color 160ms ease;
|
||||
}
|
||||
|
||||
.hero-pressable:active {
|
||||
transform: scale(0.975);
|
||||
}
|
||||
|
||||
.agents-marquee-track {
|
||||
animation: agents-marquee 38s linear infinite;
|
||||
}
|
||||
|
||||
.agents-marquee-track:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes agents-marquee {
|
||||
from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
to {
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agents-marquee-track {
|
||||
animation: none;
|
||||
flex-wrap: wrap;
|
||||
width: auto;
|
||||
justify-content: center;
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.surface {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
|
|
|
|||
Loading…
Reference in New Issue