feat(website): complete landing page redesign
- New dark design system: pure black base (#050505), animated mesh gradient hero with floating orbs, film grain overlay, shimmer/glow card effects - Floating pill nav with frosted glass backdrop blur - Bento grid feature cards with colored accents and hover glow - Animated workflow pipeline with gradient progress line - Animated stat counters with gradient text - Staggered scroll reveal animations - Gradient text utilities (orange, purple, blue) - Step-by-step HowItWorks with code previews - CTA with radial orange glow background - All 14 landing components rewritten - ~2000 lines of redesigned code
This commit is contained in:
parent
96ea22a774
commit
af8887d4ff
|
|
@ -72,158 +72,342 @@ a {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
/* ── Landing Page ─────────────────────────────────────────────────── */
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
LANDING PAGE — Redesigned Design System
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.landing-page {
|
||||
--landing-bg: #121110;
|
||||
--landing-fg: #f0ece8;
|
||||
--landing-muted: #a8a29e;
|
||||
--landing-muted-dim: #57534e;
|
||||
--landing-accent: #f97316;
|
||||
--landing-border-subtle: rgba(255, 240, 220, 0.07);
|
||||
--landing-border-default: rgba(255, 240, 220, 0.13);
|
||||
--landing-border-strong: rgba(255, 240, 220, 0.24);
|
||||
--landing-surface: #1a1918;
|
||||
--landing-card-bg: #1c1b19;
|
||||
/* Core palette */
|
||||
--landing-bg: #050505;
|
||||
--landing-bg-secondary: #0a0a0a;
|
||||
--landing-fg: #fafafa;
|
||||
--landing-fg-secondary: #e5e5e5;
|
||||
--landing-muted: #888;
|
||||
--landing-muted-dim: #444;
|
||||
--landing-border: rgba(255,255,255,0.06);
|
||||
--landing-border-hover: rgba(255,255,255,0.12);
|
||||
--landing-border-strong: rgba(255,255,255,0.18);
|
||||
|
||||
/* Accent colors */
|
||||
--accent-orange: #f97316;
|
||||
--accent-blue: #3b82f6;
|
||||
--accent-purple: #8b5cf6;
|
||||
--accent-green: #22c55e;
|
||||
--accent-cyan: #06b6d4;
|
||||
|
||||
/* Surfaces */
|
||||
--surface-card: rgba(255,255,255,0.02);
|
||||
--surface-card-hover: rgba(255,255,255,0.04);
|
||||
--surface-glass: rgba(255,255,255,0.03);
|
||||
|
||||
background: var(--landing-bg);
|
||||
color: var(--landing-fg);
|
||||
font-family: var(--font-sans, -apple-system, "SF Pro Text", system-ui, sans-serif);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.liquid-glass-solid {
|
||||
background: var(--landing-accent);
|
||||
color: #121110;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.liquid-glass-solid:hover {
|
||||
opacity: 0.9;
|
||||
/* ── Grain overlay ─────────────────────────────────────────────── */
|
||||
.landing-page::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
opacity: 0.035;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||
background-repeat: repeat;
|
||||
background-size: 256px 256px;
|
||||
}
|
||||
|
||||
/* ── Glass card ────────────────────────────────────────────────── */
|
||||
.landing-card {
|
||||
background: var(--landing-card-bg);
|
||||
border: 1px solid var(--landing-border-subtle);
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
background: var(--surface-card);
|
||||
border: 1px solid var(--landing-border);
|
||||
border-radius: 16px;
|
||||
transition: border-color 0.3s, background 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.landing-card:hover {
|
||||
border-color: var(--landing-border-default);
|
||||
border-color: var(--landing-border-hover);
|
||||
background: var(--surface-card-hover);
|
||||
}
|
||||
|
||||
.landing-hero-grid {
|
||||
background-image: radial-gradient(rgba(255, 240, 220, 0.04) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%);
|
||||
-webkit-mask-image: radial-gradient(ellipse at center, black 30%, transparent 70%);
|
||||
/* ── Shimmer border card ───────────────────────────────────────── */
|
||||
.shimmer-card {
|
||||
position: relative;
|
||||
background: var(--surface-card);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.shimmer-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 16px;
|
||||
padding: 1px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(255,255,255,0) 0%,
|
||||
rgba(255,255,255,0.1) 25%,
|
||||
rgba(255,255,255,0) 50%
|
||||
);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
animation: shimmer-rotate 6s linear infinite;
|
||||
}
|
||||
@keyframes shimmer-rotate {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 200% 200%; }
|
||||
}
|
||||
|
||||
@keyframes landing-fade-rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
/* ── Glow card ─────────────────────────────────────────────────── */
|
||||
.glow-card {
|
||||
position: relative;
|
||||
background: var(--surface-card);
|
||||
border: 1px solid var(--landing-border);
|
||||
border-radius: 16px;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
.glow-card:hover {
|
||||
border-color: var(--landing-border-hover);
|
||||
box-shadow: 0 0 40px -10px rgba(249,115,22,0.15);
|
||||
}
|
||||
|
||||
@keyframes landing-pulse-dot {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
/* ── Animated gradient mesh ────────────────────────────────────── */
|
||||
.mesh-gradient {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mesh-gradient .orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.3;
|
||||
animation: float-orb 20s ease-in-out infinite;
|
||||
}
|
||||
.mesh-gradient .orb-1 {
|
||||
width: 600px; height: 600px;
|
||||
background: radial-gradient(circle, rgba(249,115,22,0.4), transparent 70%);
|
||||
top: -20%; left: -10%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
.mesh-gradient .orb-2 {
|
||||
width: 500px; height: 500px;
|
||||
background: radial-gradient(circle, rgba(139,92,246,0.3), transparent 70%);
|
||||
top: 10%; right: -15%;
|
||||
animation-delay: -7s;
|
||||
}
|
||||
.mesh-gradient .orb-3 {
|
||||
width: 400px; height: 400px;
|
||||
background: radial-gradient(circle, rgba(59,130,246,0.25), transparent 70%);
|
||||
bottom: -10%; left: 30%;
|
||||
animation-delay: -14s;
|
||||
}
|
||||
@keyframes float-orb {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
25% { transform: translate(30px, -40px) scale(1.05); }
|
||||
50% { transform: translate(-20px, 20px) scale(0.95); }
|
||||
75% { transform: translate(40px, 30px) scale(1.02); }
|
||||
}
|
||||
|
||||
.landing-fade-rise {
|
||||
animation: landing-fade-rise 0.8s ease-out both;
|
||||
/* ── Grid pattern background ───────────────────────────────────── */
|
||||
.grid-pattern {
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(ellipse 70% 50% at 50% 50%, black 30%, transparent 70%);
|
||||
-webkit-mask-image: radial-gradient(ellipse 70% 50% at 50% 50%, black 30%, transparent 70%);
|
||||
}
|
||||
|
||||
.landing-fade-rise-d1 {
|
||||
animation: landing-fade-rise 0.8s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
.landing-fade-rise-d2 {
|
||||
animation: landing-fade-rise 0.8s ease-out 0.4s both;
|
||||
/* ── Fade-in animations ────────────────────────────────────────── */
|
||||
@keyframes fade-up {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes scale-in {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
.landing-fade-up {
|
||||
animation: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
.landing-fade-up-d1 {
|
||||
animation: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both;
|
||||
}
|
||||
.landing-fade-up-d2 {
|
||||
animation: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both;
|
||||
}
|
||||
.landing-fade-up-d3 {
|
||||
animation: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.45s both;
|
||||
}
|
||||
.landing-fade-up-d4 {
|
||||
animation: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.6s both;
|
||||
}
|
||||
|
||||
/* ── Scroll reveal ─────────────────────────────────────────────── */
|
||||
.landing-reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(32px);
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
transform: translateY(40px);
|
||||
transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1), transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.landing-reveal.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.landing-agent-dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: rgba(134, 239, 172, 0.7);
|
||||
animation: landing-pulse-dot 2s ease-in-out infinite;
|
||||
/* ── Cursor blink ──────────────────────────────────────────────── */
|
||||
@keyframes cursor-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Terminal typing animations */
|
||||
.landing-line-appear {
|
||||
animation: landing-fade-rise 0.3s ease-out both;
|
||||
}
|
||||
|
||||
@keyframes landing-cursor-blink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.landing-cursor-blink {
|
||||
animation: landing-cursor-blink 0.8s step-end infinite;
|
||||
animation: cursor-blink 0.8s step-end infinite;
|
||||
}
|
||||
|
||||
/* Workflow pipeline pulse */
|
||||
@keyframes landing-node-pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 currentColor;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px transparent;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
opacity: 0;
|
||||
}
|
||||
/* ── Pulse dot ─────────────────────────────────────────────────── */
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 0.5; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.2); }
|
||||
}
|
||||
.landing-pulse-dot {
|
||||
animation: pulse-dot 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ── Node pulse ring ───────────────────────────────────────────── */
|
||||
@keyframes node-pulse {
|
||||
0% { box-shadow: 0 0 0 0 currentColor; opacity: 0.6; }
|
||||
70% { box-shadow: 0 0 0 12px transparent; opacity: 0; }
|
||||
100% { box-shadow: 0 0 0 0 transparent; opacity: 0; }
|
||||
}
|
||||
.landing-node-pulse {
|
||||
border: 1.5px solid;
|
||||
animation: landing-node-pulse 1.5s ease-out infinite;
|
||||
animation: node-pulse 1.5s ease-out infinite;
|
||||
}
|
||||
|
||||
/* ── Landing page spacing protection ─────────────────────────────────────────
|
||||
Fumadocs bundles its own Tailwind inside @layer fumadocs. Because that sheet
|
||||
loads after the root layout, @layer fumadocs has higher cascade priority than
|
||||
root-level @layer utilities. Its universal preflight reset (* { margin:0;
|
||||
padding:0 }) zeros any spacing utility not present in fumadocs' own bundle.
|
||||
These unlayered rules (outside any @layer) beat ALL named layers and restore
|
||||
the correct values for every margin/padding class used in the landing page. */
|
||||
/* ── Line appear ───────────────────────────────────────────────── */
|
||||
.landing-line-appear {
|
||||
animation: fade-up 0.3s ease-out both;
|
||||
}
|
||||
|
||||
/* ── Gradient text ─────────────────────────────────────────────── */
|
||||
.text-gradient-orange {
|
||||
background: linear-gradient(135deg, #f97316, #fb923c, #fbbf24);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.text-gradient-purple {
|
||||
background: linear-gradient(135deg, #8b5cf6, #a78bfa, #c4b5fd);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.text-gradient-blue {
|
||||
background: linear-gradient(135deg, #3b82f6, #60a5fa, #93c5fd);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* ── Animated counter ──────────────────────────────────────────── */
|
||||
@keyframes count-up {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Feature icon glow ─────────────────────────────────────────── */
|
||||
.icon-glow {
|
||||
position: relative;
|
||||
}
|
||||
.icon-glow::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: inherit;
|
||||
background: inherit;
|
||||
filter: blur(12px);
|
||||
opacity: 0.4;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* ── Button styles ─────────────────────────────────────────────── */
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 24px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9375rem;
|
||||
background: linear-gradient(135deg, #f97316, #ea580c);
|
||||
color: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 0 20px -5px rgba(249,115,22,0.4);
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
box-shadow: 0 0 30px -5px rgba(249,115,22,0.6);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 24px;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
font-size: 0.9375rem;
|
||||
background: var(--surface-card);
|
||||
color: var(--landing-fg);
|
||||
border: 1px solid var(--landing-border);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--landing-border-hover);
|
||||
background: var(--surface-card-hover);
|
||||
}
|
||||
|
||||
/* ── Animated pipeline line ────────────────────────────────────── */
|
||||
@keyframes pipeline-fill {
|
||||
from { transform: scaleX(0); }
|
||||
to { transform: scaleX(1); }
|
||||
}
|
||||
|
||||
/* ── Stagger children animation ────────────────────────────────── */
|
||||
.stagger-children > * {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
.stagger-children.visible > *:nth-child(1) { transition-delay: 0s; opacity: 1; transform: translateY(0); }
|
||||
.stagger-children.visible > *:nth-child(2) { transition-delay: 0.1s; opacity: 1; transform: translateY(0); }
|
||||
.stagger-children.visible > *:nth-child(3) { transition-delay: 0.2s; opacity: 1; transform: translateY(0); }
|
||||
.stagger-children.visible > *:nth-child(4) { transition-delay: 0.3s; opacity: 1; transform: translateY(0); }
|
||||
.stagger-children.visible > *:nth-child(5) { transition-delay: 0.4s; opacity: 1; transform: translateY(0); }
|
||||
.stagger-children.visible > *:nth-child(6) { transition-delay: 0.5s; opacity: 1; transform: translateY(0); }
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
SPACING PROTECTION — fumadocs cascade override
|
||||
Unlayered rules beat all named layers.
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Margin top */
|
||||
.landing-page .mt-1 { margin-top: calc(var(--spacing) * 1); }
|
||||
.landing-page .mt-2 { margin-top: calc(var(--spacing) * 2); }
|
||||
.landing-page .mt-3 { margin-top: calc(var(--spacing) * 3); }
|
||||
.landing-page .mt-4 { margin-top: calc(var(--spacing) * 4); }
|
||||
.landing-page .mt-5 { margin-top: calc(var(--spacing) * 5); }
|
||||
.landing-page .mt-6 { margin-top: calc(var(--spacing) * 6); }
|
||||
.landing-page .mt-8 { margin-top: calc(var(--spacing) * 8); }
|
||||
.landing-page .mt-10 { margin-top: calc(var(--spacing) * 10); }
|
||||
|
|
@ -231,6 +415,7 @@ a {
|
|||
.landing-page .mt-16 { margin-top: calc(var(--spacing) * 16); }
|
||||
.landing-page .mt-20 { margin-top: calc(var(--spacing) * 20); }
|
||||
|
||||
/* Margin bottom */
|
||||
.landing-page .mb-1 { margin-bottom: calc(var(--spacing) * 1); }
|
||||
.landing-page .mb-2 { margin-bottom: calc(var(--spacing) * 2); }
|
||||
.landing-page .mb-3 { margin-bottom: calc(var(--spacing) * 3); }
|
||||
|
|
@ -243,19 +428,22 @@ a {
|
|||
.landing-page .mb-16 { margin-bottom: calc(var(--spacing) * 16); }
|
||||
.landing-page .mb-1\.5 { margin-bottom: calc(var(--spacing) * 1.5); }
|
||||
|
||||
/* Margin left/right */
|
||||
.landing-page .ml-1 { margin-left: calc(var(--spacing) * 1); }
|
||||
.landing-page .ml-2 { margin-left: calc(var(--spacing) * 2); }
|
||||
.landing-page .ml-1\.5 { margin-left: calc(var(--spacing) * 1.5); }
|
||||
|
||||
.landing-page .mr-1 { margin-right: calc(var(--spacing) * 1); }
|
||||
.landing-page .mr-1\.5 { margin-right: calc(var(--spacing) * 1.5); }
|
||||
|
||||
/* Padding */
|
||||
.landing-page .p-2 { padding: calc(var(--spacing) * 2); }
|
||||
.landing-page .p-3 { padding: calc(var(--spacing) * 3); }
|
||||
.landing-page .p-6 { padding: calc(var(--spacing) * 6); }
|
||||
.landing-page .p-7 { padding: calc(var(--spacing) * 7); }
|
||||
.landing-page .p-8 { padding: calc(var(--spacing) * 8); }
|
||||
.landing-page .p-2\.5 { padding: calc(var(--spacing) * 2.5); }
|
||||
.landing-page .p-10 { padding: calc(var(--spacing) * 10); }
|
||||
.landing-page .p-12 { padding: calc(var(--spacing) * 12); }
|
||||
|
||||
.landing-page .pt-10 { padding-top: calc(var(--spacing) * 10); }
|
||||
.landing-page .pt-32 { padding-top: calc(var(--spacing) * 32); }
|
||||
|
|
@ -285,33 +473,36 @@ a {
|
|||
/* Arbitrary padding values */
|
||||
.landing-page .py-\[100px\] { padding-top: 100px; padding-bottom: 100px; }
|
||||
.landing-page .py-\[120px\] { padding-top: 120px; padding-bottom: 120px; }
|
||||
.landing-page .py-\[140px\] { padding-top: 140px; padding-bottom: 140px; }
|
||||
.landing-page .py-\[160px\] { padding-top: 160px; padding-bottom: 160px; }
|
||||
.landing-page .pt-\[60px\] { padding-top: 60px; }
|
||||
.landing-page .pb-\[120px\] { padding-bottom: 120px; }
|
||||
|
||||
/* Border colors — fumadocs * { border-color: var(--color-fd-border) } persists from docs
|
||||
navigation. In light mode --color-fd-border = #d6d3d1 which looks bright on the dark
|
||||
landing background, overriding explicit border-[var(--landing-border-*)] utilities. */
|
||||
.landing-page .border-\[var\(--landing-border-subtle\)\] { border-color: var(--landing-border-subtle); }
|
||||
.landing-page .border-\[var\(--landing-border-default\)\] { border-color: var(--landing-border-default); }
|
||||
.landing-page .border-\[var\(--landing-border-strong\)\] { border-color: var(--landing-border-strong); }
|
||||
/* Border colors */
|
||||
.landing-page .border-\[var\(--landing-border\)\] { border-color: var(--landing-border); }
|
||||
.landing-page .border-\[var\(--landing-border-hover\)\] { border-color: var(--landing-border-hover); }
|
||||
.landing-page .border-\[var\(--landing-border-strong\)\] { border-color: var(--landing-border-strong); }
|
||||
|
||||
/* Font weight — fumadocs heading reset zeroes h1-h6 { font-weight: inherit } */
|
||||
/* Font weight */
|
||||
.landing-page .font-\[680\] { font-weight: 680; }
|
||||
.landing-page .font-\[700\] { font-weight: 700; }
|
||||
|
||||
/* Font size (clamp values) — fumadocs heading reset zeroes h1-h6 { font-size: inherit } */
|
||||
/* Font size (clamp values) */
|
||||
.landing-page .text-\[clamp\(1\.75rem\,4vw\,2\.75rem\)\] { font-size: clamp(1.75rem, 4vw, 2.75rem); }
|
||||
.landing-page .text-\[clamp\(1\.375rem\,3vw\,2rem\)\] { font-size: clamp(1.375rem, 3vw, 2rem); }
|
||||
.landing-page .text-\[clamp\(2rem\,4vw\,3rem\)\] { font-size: clamp(2rem, 4vw, 3rem); }
|
||||
.landing-page .text-\[clamp\(2rem\,5vw\,3\.5rem\)\] { font-size: clamp(2rem, 5vw, 3.5rem); }
|
||||
.landing-page .text-\[clamp\(2\.5rem\,6vw\,4\.5rem\)\] { font-size: clamp(2.5rem, 6vw, 4.5rem); }
|
||||
.landing-page .text-\[clamp\(3rem\,8vw\,6rem\)\] { font-size: clamp(3rem, 8vw, 6rem); }
|
||||
|
||||
/* Responsive md: utilities — fumadocs bundles .hidden but not all md: variants,
|
||||
so its higher-priority layer keeps elements hidden / wrong grid at desktop widths. */
|
||||
/* Responsive md: utilities */
|
||||
@media (min-width: 768px) {
|
||||
.landing-page .md\:flex { display: flex; }
|
||||
.landing-page .md\:block { display: block; }
|
||||
.landing-page .md\:hidden { display: none; }
|
||||
|
||||
.landing-page .md\:flex-row { flex-direction: row; }
|
||||
.landing-page .md\:flex-col { flex-direction: column; }
|
||||
|
||||
.landing-page .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.landing-page .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
|
|
@ -330,3 +521,8 @@ a {
|
|||
.landing-page .md\:order-1 { order: 1; }
|
||||
.landing-page .md\:order-2 { order: 2; }
|
||||
}
|
||||
|
||||
/* Border radius overrides */
|
||||
.landing-page .rounded-2xl { border-radius: 16px; }
|
||||
.landing-page .rounded-xl { border-radius: 12px; }
|
||||
.landing-page .rounded-lg { border-radius: 8px; }
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ export default async function LandingPage() {
|
|||
<ScrollRevealProvider>
|
||||
<LandingNav />
|
||||
<LandingHero starsLabel={formatCompactNumber(githubStats.stars)} />
|
||||
<LandingAbout />
|
||||
<LandingAgentsBar />
|
||||
<LandingAbout />
|
||||
<LandingFeatures />
|
||||
<LandingWorkflow />
|
||||
<LandingUseCases />
|
||||
|
|
@ -34,8 +34,11 @@ export default async function LandingPage() {
|
|||
<LandingTestimonials />
|
||||
<LandingQuickStart />
|
||||
<LandingCTA />
|
||||
<footer className="py-12 px-8 text-center text-[var(--landing-muted)] opacity-30 text-[0.8125rem] border-t border-white/[0.04]">
|
||||
MIT Licensed · Open Source
|
||||
<footer className="py-12 px-8 text-center border-t" style={{
|
||||
borderColor: "var(--landing-border)",
|
||||
color: "var(--landing-muted-dim)"
|
||||
}}>
|
||||
<div className="text-[0.8125rem]">MIT Licensed · Open Source</div>
|
||||
</footer>
|
||||
</ScrollRevealProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,54 +1,118 @@
|
|||
'use client';
|
||||
|
||||
export function LandingAbout() {
|
||||
return (
|
||||
<div className="bg-[radial-gradient(ellipse_at_top,rgba(255,240,220,0.015)_0%,transparent_70%)]">
|
||||
<section className="landing-reveal py-[100px] px-6 max-w-[72rem] mx-auto">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted-dim)] mb-6 font-mono">
|
||||
The problem
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] text-[clamp(1.375rem,3vw,2rem)] leading-[1.1] tracking-[-1.5px] mb-10 max-w-[48rem]">
|
||||
You're running AI agents in 10 browser tabs.{" "}
|
||||
<span className="text-[var(--landing-muted)]">
|
||||
Checking if PRs landed. Re-running failed CI. Copy-pasting error logs.
|
||||
<section className="landing-reveal relative">
|
||||
{/* Subtle radial glow behind section */}
|
||||
<div className="absolute inset-0 pointer-events-none overflow-hidden">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[500px] bg-[radial-gradient(ellipse_at_center,rgba(249,115,22,0.04)_0%,transparent_70%)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative py-[120px] px-6 max-w-[72rem] mx-auto">
|
||||
{/* ── Section Label ─────────────────────────────────────── */}
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-[var(--accent-orange)] landing-pulse-dot" />
|
||||
<span className="font-mono text-[0.6875rem] tracking-[0.2em] uppercase text-[var(--accent-orange)]">
|
||||
The problem
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* ── Headline ─────────────────────────────────────────── */}
|
||||
<h2 className="font-sans font-[700] text-[clamp(1.75rem,4vw,2.75rem)] leading-[1.08] tracking-[-1.5px] mb-6 max-w-[40rem]">
|
||||
You're running AI agents{' '}
|
||||
<span className="text-gradient-orange">in 10 browser tabs.</span>
|
||||
</h2>
|
||||
<p className="text-[0.9375rem] text-[var(--landing-muted)] leading-[1.7] max-w-[32rem] mb-16">
|
||||
Checking if PRs landed. Re-running failed CI. Copy-pasting error logs into ChatGPT.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
|
||||
<p className="text-[0.9375rem] text-[var(--landing-muted)] leading-[1.8] max-w-[28rem]">
|
||||
Agent Orchestrator replaces that with one YAML file. Point it at
|
||||
your GitHub issues, pick your agents, and walk away. Each agent
|
||||
spawns in its own git worktree, creates PRs, fixes CI failures,
|
||||
addresses review comments, and moves toward merge. If you are new, start with the <a href="/docs/" className="underline decoration-[var(--landing-border-default)] underline-offset-4 hover:text-white">docs quickstart and configuration guides</a>.
|
||||
</p>
|
||||
{/* ── Side-by-side Layout ──────────────────────────────── */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_1.1fr] gap-12 items-center">
|
||||
{/* Left — Explanation */}
|
||||
<div className="space-y-6">
|
||||
<p className="text-[0.9375rem] text-[var(--landing-fg-secondary)] leading-[1.85] max-w-[28rem]">
|
||||
Agent Orchestrator replaces that with one YAML file. Point it at
|
||||
your GitHub issues, pick your agents, and walk away. Each agent
|
||||
spawns in its own git worktree, creates PRs, fixes CI failures,
|
||||
addresses review comments, and moves toward merge.
|
||||
</p>
|
||||
|
||||
{/* Config preview — show how simple setup is */}
|
||||
<div className="landing-card rounded-2xl overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-[var(--landing-border-subtle)]">
|
||||
<div className="w-2 h-2 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<div className="w-2 h-2 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<div className="w-2 h-2 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<span className="ml-1.5 font-mono text-[0.5625rem] text-[var(--landing-muted-dim)]">
|
||||
<p className="text-[0.8125rem] text-[var(--landing-muted)] leading-[1.75]">
|
||||
If you're new, start with the{' '}
|
||||
<a
|
||||
href="/docs/"
|
||||
className="text-[var(--accent-orange)] underline underline-offset-4 decoration-[rgba(249,115,22,0.3)] hover:decoration-[var(--accent-orange)] transition-colors"
|
||||
>
|
||||
docs quickstart and configuration guides
|
||||
</a>.
|
||||
</p>
|
||||
|
||||
{/* Feature pills */}
|
||||
<div className="flex flex-wrap gap-2 pt-4">
|
||||
{['Git Worktrees', 'Auto CI Fix', 'PR Reviews', 'Slack Alerts'].map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[0.6875rem] font-medium tracking-wide text-[var(--landing-muted)] border border-[var(--landing-border)] bg-[var(--surface-card)]"
|
||||
>
|
||||
<span className="w-1 h-1 rounded-full bg-[var(--accent-green)]" />
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right — Terminal Code Preview Card */}
|
||||
<div className="glow-card overflow-hidden">
|
||||
{/* Terminal title bar */}
|
||||
<div className="flex items-center justify-between px-5 py-3 border-b border-[var(--landing-border)] bg-[rgba(255,255,255,0.015)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-[10px] h-[10px] rounded-full bg-[rgba(255,255,255,0.06)]" />
|
||||
<div className="w-[10px] h-[10px] rounded-full bg-[rgba(255,255,255,0.06)]" />
|
||||
<div className="w-[10px] h-[10px] rounded-full bg-[rgba(255,255,255,0.06)]" />
|
||||
</div>
|
||||
<span className="font-mono text-[0.5625rem] tracking-wider text-[var(--landing-muted-dim)]">
|
||||
agent-orchestrator.yaml
|
||||
</span>
|
||||
<div className="w-[38px]" />
|
||||
</div>
|
||||
<pre className="px-5 py-4 font-mono text-[0.75rem] leading-[1.9] overflow-x-auto">
|
||||
<span className="text-[var(--landing-muted-dim)]">agent:</span>{" "}
|
||||
<span className="text-[var(--landing-fg)]">claude-code</span>
|
||||
{"\n"}
|
||||
<span className="text-[var(--landing-muted-dim)]">tracker:</span>{" "}
|
||||
<span className="text-[var(--landing-fg)]">github</span>
|
||||
{"\n"}
|
||||
<span className="text-[var(--landing-muted-dim)]">workspace:</span>{" "}
|
||||
<span className="text-[var(--landing-fg)]">worktree</span>
|
||||
{"\n"}
|
||||
<span className="text-[var(--landing-muted-dim)]">runtime:</span>{" "}
|
||||
<span className="text-[var(--landing-fg)]">tmux</span>
|
||||
{"\n"}
|
||||
<span className="text-[var(--landing-muted-dim)]">notifier:</span>{" "}
|
||||
<span className="text-[var(--landing-fg)]">slack</span>
|
||||
</pre>
|
||||
|
||||
{/* Code body */}
|
||||
<div className="px-6 py-5 font-mono text-[0.8125rem] leading-[2.1] overflow-x-auto">
|
||||
<code>
|
||||
<Line label="agent" value="claude-code" accent />
|
||||
<Line label="tracker" value="github" />
|
||||
<Line label="workspace" value="worktree" />
|
||||
<Line label="runtime" value="tmux" />
|
||||
<Line label="notifier" value="slack" />
|
||||
<span className="inline-block w-[7px] h-[15px] bg-[var(--accent-orange)] landing-cursor-blink align-middle ml-0.5" />
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Subtle bottom accent line */}
|
||||
<div className="h-[1px] bg-gradient-to-r from-transparent via-[rgba(249,115,22,0.2)] to-transparent" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Helper: single YAML key‑value line ─────────────────────────── */
|
||||
function Line({
|
||||
label,
|
||||
value,
|
||||
accent = false,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
accent?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<span className="block">
|
||||
<span className="text-[var(--landing-muted-dim)]">{label}:</span>{' '}
|
||||
<span className={accent ? 'text-[var(--accent-green)]' : 'text-[var(--landing-fg)]'}>
|
||||
{value}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,19 +28,24 @@ const agents = [
|
|||
|
||||
export function LandingAgentsBar() {
|
||||
return (
|
||||
<div className="landing-reveal text-center px-6 pt-[60px]">
|
||||
<div className="text-[0.6875rem] tracking-[0.15em] uppercase text-[var(--landing-muted)] opacity-40 mb-5">
|
||||
<div className="landing-reveal text-center px-6 pt-[60px] pb-4">
|
||||
<div className="text-[0.6875rem] tracking-[0.2em] uppercase text-[var(--landing-muted)] mb-8 font-mono">
|
||||
Works with your favorite AI agents
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-6 flex-wrap">
|
||||
<div className="flex items-center justify-center gap-8 flex-wrap">
|
||||
{agents.map((agent) => (
|
||||
<div key={agent.name} className="flex flex-col items-center gap-2">
|
||||
<img
|
||||
src={agent.src}
|
||||
alt={agent.alt}
|
||||
className="w-8 h-8 rounded-md object-contain"
|
||||
/>
|
||||
<div className="text-[0.6875rem] font-mono text-[var(--landing-muted)] opacity-50">
|
||||
<div
|
||||
key={agent.name}
|
||||
className="group flex flex-col items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 hover:bg-[var(--landing-border)]"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-lg bg-[var(--landing-border)] flex items-center justify-center p-2 transition-all duration-300 group-hover:bg-[var(--landing-border-hover)] group-hover:shadow-[0_0_20px_rgba(249,115,22,0.08)]">
|
||||
<img
|
||||
src={agent.src}
|
||||
alt={agent.alt}
|
||||
className="w-full h-full object-contain transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[0.6875rem] font-mono text-[var(--landing-muted)] transition-colors duration-300 group-hover:text-[var(--landing-fg-secondary)]">
|
||||
{agent.name}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,37 @@
|
|||
export function LandingCTA() {
|
||||
return (
|
||||
<section className="text-center py-40 px-6 bg-[radial-gradient(ellipse_at_center,rgba(255,255,255,0.015)_0%,transparent_60%)]">
|
||||
<div className="landing-reveal">
|
||||
<p className="text-[var(--landing-muted)] opacity-50 text-2xl font-sans font-[680] tracking-tight mb-4">
|
||||
<section className="relative py-[160px] px-6 text-center overflow-hidden">
|
||||
{/* Background glow */}
|
||||
<div className="absolute inset-0" style={{
|
||||
background: "radial-gradient(ellipse 60% 50% at 50% 50%, rgba(249,115,22,0.06) 0%, transparent 70%)"
|
||||
}} />
|
||||
|
||||
<div className="relative landing-reveal">
|
||||
<p className="text-[var(--landing-muted)] text-2xl font-bold tracking-tight mb-4">
|
||||
Stop babysitting.
|
||||
</p>
|
||||
<h2 className="font-sans font-[680] tracking-tight font-normal text-[clamp(1.375rem,3vw,2rem)] leading-[1.05] tracking-[-2px] mb-4">
|
||||
Start <em className="italic text-[var(--landing-muted)]">orchestrating.</em>
|
||||
<h2 className="text-[clamp(1.5rem,3vw,2.25rem)] leading-[1.1] tracking-[-1px] font-bold mb-6">
|
||||
Start <span className="text-gradient-orange">orchestrating.</span>
|
||||
</h2>
|
||||
<div className="landing-card inline-flex items-center gap-3 rounded-lg px-6 py-3 font-mono text-[0.9375rem] text-white mb-8">
|
||||
<span className="text-[var(--landing-muted)] opacity-40">$</span> npm i -g @aoagents/ao
|
||||
|
||||
{/* Install command */}
|
||||
<div className="landing-card inline-flex items-center gap-3 rounded-xl px-7 py-4 font-mono text-[0.9375rem] mb-10" style={{
|
||||
background: "rgba(0,0,0,0.4)",
|
||||
backdropFilter: "blur(10px)"
|
||||
}}>
|
||||
<span className="text-[var(--accent-orange)]">$</span>
|
||||
<span className="text-[var(--landing-fg)]">npm i -g @aoagents/ao</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 flex-wrap">
|
||||
<a
|
||||
href="/docs"
|
||||
className="landing-card rounded-lg px-6 py-3 text-[0.9375rem] text-[var(--landing-muted)] no-underline transition-colors hover:text-white"
|
||||
>
|
||||
Read Docs
|
||||
<a href="/docs" className="btn-primary">
|
||||
Read Docs →
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/ComposioHQ/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="liquid-glass-solid rounded-lg px-6 py-3 text-[0.9375rem] no-underline transition-transform hover:scale-[1.03]"
|
||||
className="btn-secondary"
|
||||
>
|
||||
View on GitHub
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ export function LandingDifferentiators() {
|
|||
return (
|
||||
<section className="py-[100px] px-6 max-w-[72rem] mx-auto">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted)] opacity-60 mb-6">
|
||||
<div className="text-xs tracking-[0.2em] uppercase text-[var(--accent-purple)] mb-4 font-mono font-medium">
|
||||
Why Agent Orchestrator
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] tracking-tight font-normal text-[clamp(1.375rem,3vw,2rem)] leading-[1.1] tracking-[-1.5px] mb-6 max-w-[42rem]">
|
||||
<h2 className="font-sans font-[680] tracking-tight text-[clamp(1.375rem,3vw,2rem)] leading-[1.1] tracking-[-1.5px] mb-6 max-w-[42rem] text-gradient-purple">
|
||||
The only{" "}
|
||||
<em className="italic text-[var(--landing-muted)]">open-source, web-based</em>{" "}
|
||||
<em className="italic text-[var(--landing-fg-secondary)]">open-source, web-based</em>{" "}
|
||||
agent orchestrator
|
||||
</h2>
|
||||
<p className="text-[0.9375rem] text-[var(--landing-muted)] leading-[1.7] max-w-[36rem] mb-12">
|
||||
|
|
@ -24,17 +24,19 @@ export function LandingDifferentiators() {
|
|||
your browser, works on any OS, and you can self-host or extend it.
|
||||
</p>
|
||||
</div>
|
||||
<div className="landing-reveal landing-card rounded-2xl overflow-hidden">
|
||||
<div className="landing-reveal shimmer-card rounded-2xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[var(--landing-border-subtle)]">
|
||||
<th className="text-left px-6 py-4 font-mono text-[0.625rem] tracking-[0.1em] uppercase text-[var(--landing-muted)] opacity-40">
|
||||
<tr className="border-b border-[var(--landing-border)]">
|
||||
<th className="text-left px-6 py-5 font-mono text-[0.625rem] tracking-[0.15em] uppercase text-[var(--landing-muted)]">
|
||||
Feature
|
||||
</th>
|
||||
<th className="text-center px-6 py-4 font-mono text-[0.625rem] tracking-[0.1em] uppercase text-[var(--landing-fg)] opacity-80">
|
||||
AO
|
||||
<th className="text-center px-6 py-5">
|
||||
<span className="font-mono text-[0.625rem] tracking-[0.15em] uppercase text-[var(--accent-orange)] font-medium">
|
||||
AO
|
||||
</span>
|
||||
</th>
|
||||
<th className="text-center px-6 py-4 font-mono text-[0.625rem] tracking-[0.1em] uppercase text-[var(--landing-muted)] opacity-40">
|
||||
<th className="text-center px-6 py-5 font-mono text-[0.625rem] tracking-[0.15em] uppercase text-[var(--landing-muted)]">
|
||||
Others
|
||||
</th>
|
||||
</tr>
|
||||
|
|
@ -43,15 +45,19 @@ export function LandingDifferentiators() {
|
|||
{rows.map((row, i) => (
|
||||
<tr
|
||||
key={row.feature}
|
||||
className={i < rows.length - 1 ? "border-b border-[var(--landing-border-subtle)]" : ""}
|
||||
className={`transition-colors duration-200 hover:bg-[rgba(255,255,255,0.02)] ${
|
||||
i < rows.length - 1 ? "border-b border-[var(--landing-border)]" : ""
|
||||
}`}
|
||||
>
|
||||
<td className="px-6 py-3.5 text-[0.8125rem] text-[var(--landing-fg)]/80">
|
||||
<td className="px-6 py-4 text-[0.8125rem] text-[var(--landing-fg-secondary)]">
|
||||
{row.feature}
|
||||
</td>
|
||||
<td className="px-6 py-3.5 text-center text-[rgba(134,239,172,0.8)]">
|
||||
✓
|
||||
<td className="px-6 py-4 text-center">
|
||||
<span className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-[rgba(34,197,94,0.1)] text-[var(--accent-green)] text-xs font-bold">
|
||||
✓
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-3.5 text-center text-[0.75rem] text-[var(--landing-muted)] opacity-40">
|
||||
<td className="px-6 py-4 text-center text-[0.75rem] text-[var(--landing-muted-dim)]">
|
||||
{row.others}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -3,62 +3,82 @@ const features = [
|
|||
label: "PARALLEL",
|
||||
title: "Multi-agent execution",
|
||||
desc: "Run Claude Code, Codex, Cursor, Aider, and OpenCode simultaneously. Each agent gets its own git worktree, its own branch, its own context.",
|
||||
icon: "⚡",
|
||||
accent: "var(--accent-orange)",
|
||||
span: "col-span-1 md:col-span-2",
|
||||
},
|
||||
{
|
||||
label: "RECOVERY",
|
||||
title: "Autonomous CI + review handling",
|
||||
desc: "CI fails? The agent reads the logs and pushes a fix. Review comments land? The agent addresses them. You sleep, your agents ship.",
|
||||
icon: "🔄",
|
||||
accent: "var(--accent-green)",
|
||||
span: "col-span-1 md:col-span-1",
|
||||
},
|
||||
{
|
||||
label: "PLUGINS",
|
||||
title: "7 swappable slots",
|
||||
desc: "Runtime, Agent, Workspace, Tracker, SCM, Notifier, Terminal. Use tmux or process. GitHub or GitLab. Slack or webhooks. Swap anything.",
|
||||
icon: "🧩",
|
||||
accent: "var(--accent-purple)",
|
||||
span: "col-span-1 md:col-span-1",
|
||||
},
|
||||
{
|
||||
label: "DASHBOARD",
|
||||
title: "Real-time Kanban + terminal",
|
||||
desc: "Every agent's state in one view. Attach to any terminal via the browser. SSE updates every 5 seconds. WebSocket for live terminal I/O.",
|
||||
icon: "📊",
|
||||
accent: "var(--accent-cyan)",
|
||||
span: "col-span-1 md:col-span-2",
|
||||
},
|
||||
];
|
||||
|
||||
export function LandingFeatures() {
|
||||
return (
|
||||
<section className="py-[100px] px-6 max-w-[72rem] mx-auto" id="features">
|
||||
<section className="py-[140px] px-6 max-w-[72rem] mx-auto" id="features">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted-dim)] mb-6 font-mono">
|
||||
<div className="font-mono text-[0.75rem] tracking-[0.2em] uppercase text-[var(--accent-orange)] mb-4">
|
||||
Capabilities
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] text-[clamp(2rem,5vw,3.5rem)] leading-[1.1] tracking-[-1.5px] mb-5">
|
||||
<h2 className="text-[clamp(2rem,5vw,3.5rem)] leading-[1.1] tracking-[-1.5px] font-bold mb-4">
|
||||
What it does
|
||||
</h2>
|
||||
<a href="/docs" className="text-[0.8125rem] text-[var(--landing-muted)] no-underline hover:text-white transition-colors">
|
||||
Explore full docs and plugin references
|
||||
<a href="/docs" className="text-[0.9375rem] text-[var(--landing-muted)] no-underline hover:text-[var(--landing-fg)] transition-colors">
|
||||
Explore full docs and plugin references →
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0 mt-6">
|
||||
{features.map((f, i) => (
|
||||
|
||||
{/* Bento grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-14">
|
||||
{features.map((f) => (
|
||||
<div
|
||||
key={f.label}
|
||||
className={`landing-reveal flex flex-col md:flex-row md:items-baseline gap-3 md:gap-12 py-8 ${
|
||||
i < features.length - 1
|
||||
? "border-b border-[var(--landing-border-subtle)]"
|
||||
: ""
|
||||
}`}
|
||||
className={`landing-reveal glow-card ${f.span} p-8 group`}
|
||||
>
|
||||
<div className="font-mono text-[0.625rem] tracking-[0.12em] text-[var(--landing-muted-dim)] w-20 shrink-0">
|
||||
{/* Icon */}
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl mb-6" style={{
|
||||
background: `color-mix(in srgb, ${f.accent} 10%, transparent)`,
|
||||
}}>
|
||||
{f.icon}
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div className="font-mono text-[0.625rem] tracking-[0.15em] uppercase mb-3" style={{ color: f.accent }}>
|
||||
{f.label}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-sans font-[680] text-lg tracking-tight mb-1.5">
|
||||
{f.title}
|
||||
</h3>
|
||||
<p className="text-[var(--landing-muted)] text-[0.875rem] leading-[1.7] max-w-[36rem]">
|
||||
{f.desc}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-xl font-bold tracking-tight mb-3">
|
||||
{f.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-[var(--landing-muted)] text-[0.9375rem] leading-[1.75]">
|
||||
{f.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ function TerminalTyping() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="px-5 py-4 font-mono text-[0.8125rem] leading-[1.9] text-left min-h-[280px]">
|
||||
<div ref={ref} className="px-6 py-5 font-mono text-[0.8125rem] leading-[2] text-left min-h-[280px]">
|
||||
{terminalLines.slice(0, visibleCount).map((line, i) => {
|
||||
if (line.type === "blank") return <div key={i}> </div>;
|
||||
|
||||
|
|
@ -58,10 +58,10 @@ function TerminalTyping() {
|
|||
line.type === "cmd"
|
||||
? "text-[var(--landing-fg)]"
|
||||
: line.type === "success"
|
||||
? "text-[rgba(134,239,172,0.8)]"
|
||||
? "text-[var(--accent-green)]"
|
||||
: line.type === "status"
|
||||
? "text-[var(--landing-muted)]"
|
||||
: "text-[var(--landing-muted)] opacity-50";
|
||||
? "text-[var(--landing-fg-secondary)]"
|
||||
: "text-[var(--landing-muted)]";
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -69,17 +69,20 @@ function TerminalTyping() {
|
|||
className={`${colorClass} landing-line-appear`}
|
||||
>
|
||||
{line.type === "cmd" && (
|
||||
<span className="text-[var(--landing-muted)] opacity-50">$ </span>
|
||||
<span className="text-[var(--accent-orange)] mr-1">$</span>
|
||||
)}
|
||||
{line.type === "status" && (
|
||||
<span className="landing-agent-dot mr-1.5 inline-block" />
|
||||
<span className="landing-pulse-dot mr-1.5 inline-block w-1.5 h-1.5 rounded-full bg-[var(--accent-green)]" />
|
||||
)}
|
||||
{line.type === "cmd" ? line.text.slice(2) : line.type === "status" ? line.text.slice(2) : line.text}
|
||||
{line.type === "success" && (
|
||||
<span className="text-[var(--accent-green)] mr-1">✓</span>
|
||||
)}
|
||||
{line.type === "cmd" ? line.text.slice(2) : line.type === "status" ? line.text.slice(2) : line.type === "success" ? line.text.slice(2) : line.text}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{visibleCount > 0 && visibleCount < terminalLines.length && (
|
||||
<span className="inline-block w-2 h-4 bg-[var(--landing-fg)] opacity-70 landing-cursor-blink" />
|
||||
<span className="inline-block w-2 h-4 bg-[var(--accent-orange)] landing-cursor-blink" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -87,50 +90,91 @@ function TerminalTyping() {
|
|||
|
||||
export function LandingHero({ starsLabel }: LandingHeroProps) {
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden">
|
||||
<div className="absolute inset-0 z-[1] landing-hero-grid" />
|
||||
<section className="relative z-10 flex flex-col items-center justify-center text-center px-6 pt-32 pb-20 min-h-screen">
|
||||
<div className="landing-fade-rise landing-card inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs text-[var(--landing-muted)] mb-8">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[rgba(134,239,172,0.7)]" />
|
||||
Open Source · MIT Licensed · {starsLabel} GitHub Stars
|
||||
<div className="relative min-h-screen overflow-hidden flex items-center justify-center">
|
||||
{/* Animated mesh gradient background */}
|
||||
<div className="mesh-gradient">
|
||||
<div className="orb orb-1" />
|
||||
<div className="orb orb-2" />
|
||||
<div className="orb orb-3" />
|
||||
</div>
|
||||
|
||||
{/* Grid pattern overlay */}
|
||||
<div className="absolute inset-0 grid-pattern z-[1]" />
|
||||
|
||||
{/* Radial vignette */}
|
||||
<div className="absolute inset-0 z-[2]" style={{
|
||||
background: "radial-gradient(ellipse 80% 60% at 50% 40%, transparent 0%, var(--landing-bg) 70%)"
|
||||
}} />
|
||||
|
||||
<section className="relative z-10 flex flex-col items-center justify-center text-center px-6 pt-28 pb-16 min-h-screen max-w-[72rem] mx-auto">
|
||||
{/* Badge */}
|
||||
<div className="landing-fade-up landing-card inline-flex items-center gap-2 rounded-full px-4 py-2 text-xs mb-8" style={{
|
||||
background: "rgba(249,115,22,0.08)",
|
||||
borderColor: "rgba(249,115,22,0.2)"
|
||||
}}>
|
||||
<span className="landing-pulse-dot w-2 h-2 rounded-full bg-[var(--accent-green)]" />
|
||||
<span className="text-[var(--landing-fg-secondary)]">
|
||||
Open Source · MIT ·{" "}
|
||||
<span className="text-[var(--accent-orange)] font-semibold">{starsLabel}</span> GitHub Stars
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="landing-fade-rise font-sans font-[680] text-[clamp(1.75rem,4vw,2.75rem)] leading-[1] tracking-[-2px] max-w-[56rem]">
|
||||
Run 30 AI agents in parallel.
|
||||
|
||||
{/* Main heading */}
|
||||
<h1 className="landing-fade-up-d1 text-[clamp(2.5rem,6vw,4.5rem)] leading-[1.05] tracking-[-2.5px] max-w-[56rem] font-bold">
|
||||
Run 30 AI agents
|
||||
<br />
|
||||
<span className="text-gradient-orange">in parallel.</span>
|
||||
<br />
|
||||
<span className="text-[var(--landing-muted)]">One dashboard.</span>
|
||||
</h1>
|
||||
<p className="landing-fade-rise-d1 text-[var(--landing-muted)] text-[0.9375rem] max-w-[38rem] mt-6 leading-[1.7]">
|
||||
Agent Orchestrator spawns Claude Code, Codex, Cursor, Aider, and OpenCode
|
||||
|
||||
{/* Subtitle */}
|
||||
<p className="landing-fade-up-d2 text-[var(--landing-muted)] text-[1.0625rem] max-w-[40rem] mt-7 leading-[1.75]">
|
||||
Agent Orchestrator spawns Claude Code, Codex, Cursor, Aider & OpenCode
|
||||
in isolated git worktrees. Each agent gets its own branch, creates PRs,
|
||||
fixes CI, and addresses reviews autonomously.
|
||||
fixes CI, and addresses reviews — <span className="text-[var(--landing-fg-secondary)] font-medium">autonomously</span>.
|
||||
</p>
|
||||
<div className="landing-fade-rise-d2 flex items-center gap-3 mt-10 flex-wrap justify-center">
|
||||
<div className="landing-card rounded-lg px-6 py-3 font-mono text-sm">
|
||||
<span className="text-[var(--landing-muted)] opacity-40">$</span> npx @aoagents/ao start
|
||||
|
||||
{/* CTA buttons */}
|
||||
<div className="landing-fade-up-d3 flex items-center gap-3 mt-10 flex-wrap justify-center">
|
||||
<div className="landing-card rounded-xl px-6 py-3.5 font-mono text-sm flex items-center gap-2" style={{
|
||||
background: "rgba(0,0,0,0.4)",
|
||||
backdropFilter: "blur(10px)"
|
||||
}}>
|
||||
<span className="text-[var(--accent-orange)]">$</span>
|
||||
<span>npx @aoagents/ao start</span>
|
||||
</div>
|
||||
<a
|
||||
href="/docs"
|
||||
className="landing-card rounded-lg px-6 py-3 text-sm no-underline transition-colors hover:text-white"
|
||||
>
|
||||
Read Docs
|
||||
<a href="/docs" className="btn-primary">
|
||||
Get Started →
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/ComposioHQ/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="liquid-glass-solid rounded-lg px-6 py-3 text-sm no-underline transition-colors"
|
||||
className="btn-secondary"
|
||||
>
|
||||
View on GitHub
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="landing-fade-rise-d2 w-full max-w-[52rem] mt-16">
|
||||
<div className="landing-card rounded-2xl overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-3 border-b border-[var(--landing-border-subtle)]">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.08)]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.08)]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.08)]" />
|
||||
<span className="ml-2 font-mono text-[0.625rem] text-[var(--landing-muted)] opacity-40">
|
||||
{/* Terminal demo */}
|
||||
<div className="landing-fade-up-d4 w-full max-w-[56rem] mt-16">
|
||||
<div className="landing-card rounded-2xl overflow-hidden" style={{
|
||||
background: "rgba(0,0,0,0.5)",
|
||||
backdropFilter: "blur(20px)",
|
||||
boxShadow: "0 0 60px -15px rgba(249,115,22,0.1), 0 0 120px -30px rgba(139,92,246,0.08)"
|
||||
}}>
|
||||
{/* Terminal header */}
|
||||
<div className="flex items-center gap-3 px-5 py-3.5 border-b" style={{ borderColor: "var(--landing-border)" }}>
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-[#ff5f57]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#febc2e]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#28c840]" />
|
||||
</div>
|
||||
<span className="font-mono text-[0.6875rem] text-[var(--landing-muted)] opacity-60">
|
||||
agent-orchestrator — my-saas-app
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,156 +1,136 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const stepDetails = [
|
||||
{
|
||||
num: "01",
|
||||
title: "Configure &",
|
||||
titleItalic: "assign",
|
||||
desc: "Point Agent Orchestrator at your repo with a YAML config. Choose your agent, set up trackers and notifiers. One file, full control.",
|
||||
code: `# agent-orchestrator.yaml
|
||||
agent: claude-code
|
||||
tracker: github
|
||||
workspace: worktree
|
||||
runtime: tmux
|
||||
notifier: slack`,
|
||||
accent: "var(--accent-orange)",
|
||||
},
|
||||
{
|
||||
num: "02",
|
||||
title: "Agents",
|
||||
titleItalic: "work",
|
||||
desc: "Each agent clones into a git worktree, reads the issue, plans, writes code, runs tests, and opens a PR — all autonomously.",
|
||||
code: `$ ao spawn 42
|
||||
⟡ Spawning claude-code for #42
|
||||
⟡ Worktree: .ao/worktrees/feat-auth
|
||||
⟡ Agent reading issue... writing code...
|
||||
✓ PR #312 opened → feat/auth-flow`,
|
||||
accent: "var(--accent-blue)",
|
||||
},
|
||||
{
|
||||
num: "03",
|
||||
title: "PRs",
|
||||
titleItalic: "land",
|
||||
desc: "Agents handle CI failures, address review comments, and keep pushing until merge. You just review and approve.",
|
||||
code: `✓ CI passing (48/48 tests)
|
||||
✓ Review comments addressed
|
||||
✓ Branch up-to-date with main
|
||||
✓ Ready for merge
|
||||
→ Merged into main`,
|
||||
accent: "var(--accent-green)",
|
||||
},
|
||||
];
|
||||
|
||||
export function LandingHowItWorks() {
|
||||
return (
|
||||
<section className="py-[120px] px-6 max-w-[72rem] mx-auto" id="how">
|
||||
<section className="py-[140px] px-6 max-w-[72rem] mx-auto" id="how">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted)] opacity-60 mb-6">
|
||||
<div className="font-mono text-[0.75rem] tracking-[0.2em] uppercase text-[var(--accent-cyan)] mb-4">
|
||||
Process
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] tracking-tight font-normal text-[clamp(1.375rem,3vw,2rem)] leading-[1.05] tracking-[-1.5px] mb-6">
|
||||
<h2 className="text-[clamp(1.5rem,3vw,2.25rem)] leading-[1.1] tracking-[-1px] font-bold">
|
||||
Three steps to{" "}
|
||||
<em className="italic text-[var(--landing-muted)]">orchestration</em>
|
||||
<span className="text-gradient-purple">orchestration</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-20 flex flex-col gap-20">
|
||||
{/* Step 1 */}
|
||||
<div className="landing-reveal grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<div className="font-mono text-xs tracking-[0.1em] text-[var(--landing-muted)] opacity-50 mb-4">01</div>
|
||||
<h3 className="font-sans font-[680] tracking-tight text-lg tracking-tight mb-4">
|
||||
Configure & <em className="italic text-[var(--landing-muted)]">assign</em>
|
||||
</h3>
|
||||
<p className="text-[var(--landing-muted)] text-[0.9375rem] leading-[1.7] max-w-[28rem]">
|
||||
Point Agent Orchestrator at your repo with a YAML config. Choose your agent, set up trackers and notifiers. One file, full control.
|
||||
</p>
|
||||
</div>
|
||||
<div className="landing-card rounded-2xl p-8 min-h-[260px]">
|
||||
<div className="bg-black/40 rounded-xl overflow-hidden font-mono text-[0.8125rem]">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-[var(--landing-surface)]">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
</div>
|
||||
<div className="px-5 py-4 leading-[1.8]">
|
||||
<div><span className="text-[var(--landing-muted)]">$</span> <span className="text-white">ao batch-spawn 42 43 44 45 46</span></div>
|
||||
<div className="text-[var(--landing-muted)] opacity-60"> </div>
|
||||
<div className="text-[var(--landing-muted)] opacity-60">⟡ Loading config from agent-orchestrator.yaml</div>
|
||||
<div className="text-[var(--landing-muted)] opacity-60">⟡ Resolving 5 issues from GitHub</div>
|
||||
<div className="text-[var(--landing-muted)] opacity-60">⟡ Spawning sessions in worktrees...</div>
|
||||
<div className="text-[rgba(134,239,172,0.8)]">✓ Session s-001 spawned → issue #42</div>
|
||||
<div className="text-[rgba(134,239,172,0.8)]">✓ Session s-002 spawned → issue #43</div>
|
||||
<div className="text-[rgba(134,239,172,0.8)]">✓ Session s-003 spawned → issue #44</div>
|
||||
<div className="text-[rgba(134,239,172,0.8)]">✓ Session s-004 spawned → issue #45</div>
|
||||
<div className="text-[rgba(134,239,172,0.8)]">✓ Session s-005 spawned → issue #46</div>
|
||||
<div className="text-[var(--landing-muted)] opacity-60"> </div>
|
||||
<div><span className="landing-agent-dot mr-1.5" /><span className="text-[var(--landing-muted)] opacity-60">5 agents working · Dashboard → http://localhost:3000</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2 */}
|
||||
<div className="landing-reveal grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div className="md:order-2">
|
||||
<div className="font-mono text-xs tracking-[0.1em] text-[var(--landing-muted)] opacity-50 mb-4">02</div>
|
||||
<h3 className="font-sans font-[680] tracking-tight text-lg tracking-tight mb-4">
|
||||
Agents <em className="italic text-[var(--landing-muted)]">work</em>
|
||||
</h3>
|
||||
<p className="text-[var(--landing-muted)] text-[0.9375rem] leading-[1.7] max-w-[28rem]">
|
||||
Each agent spawns in an isolated worktree. They write code, create PRs, run tests, and fix failures. Monitor everything from the live dashboard, or let them run.
|
||||
</p>
|
||||
</div>
|
||||
<div className="landing-card rounded-2xl p-8 min-h-[260px] md:order-1">
|
||||
<div className="rounded-2xl overflow-hidden bg-black/30">
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 bg-[var(--landing-card-bg)] border-b border-[var(--landing-border-subtle)]">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[rgba(255,240,220,0.12)]" />
|
||||
<span className="text-[0.6875rem] text-[var(--landing-muted)] opacity-50 ml-2">my-saas-app · 5 sessions</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2 p-3">
|
||||
<DashColumn title="Working" cards={[
|
||||
{ title: "Add user auth flow", meta: "#42 · feat/auth", agent: "claude-code" },
|
||||
{ title: "Fix pagination bug", meta: "#43 · fix/pagination", agent: "codex" },
|
||||
]} />
|
||||
<DashColumn title="Pending" cards={[
|
||||
{ title: "Add rate limiting", meta: "#44 · PR #312", agent: "aider" },
|
||||
]} />
|
||||
<DashColumn title="Review" cards={[
|
||||
{ title: "Update API tests", meta: "#45 · PR #310", agent: "claude-code", amber: true },
|
||||
]} />
|
||||
<DashColumn title="Merged" cards={[
|
||||
{ title: "Refactor DB layer", meta: "#46 · PR #308", agent: "opencode", done: true },
|
||||
]} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3 */}
|
||||
<div className="landing-reveal grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<div className="font-mono text-xs tracking-[0.1em] text-[var(--landing-muted)] opacity-50 mb-4">03</div>
|
||||
<h3 className="font-sans font-[680] tracking-tight text-lg tracking-tight mb-4">
|
||||
PRs <em className="italic text-[var(--landing-muted)]">land</em>
|
||||
</h3>
|
||||
<p className="text-[var(--landing-muted)] text-[0.9375rem] leading-[1.7] max-w-[28rem]">
|
||||
Agents create pull requests, address review comments, fix CI failures, and get them to mergeable state. Your morning starts with merged PRs, not a backlog.
|
||||
</p>
|
||||
</div>
|
||||
<div className="landing-card rounded-2xl p-6 min-h-[260px]">
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{[
|
||||
{ branch: "feat/user-auth", title: "Add user authentication flow" },
|
||||
{ branch: "fix/pagination-offset", title: "Fix off-by-one in cursor pagination" },
|
||||
{ branch: "feat/rate-limiting", title: "Add Redis-backed rate limiter" },
|
||||
{ branch: "refactor/db-layer", title: "Extract repository pattern from services" },
|
||||
].map((pr) => (
|
||||
<div key={pr.branch} className="bg-[var(--landing-surface)] border border-[var(--landing-border-subtle)] rounded-xl px-5 py-4 flex items-center justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="font-mono text-xs text-[var(--landing-fg)]/70">{pr.branch}</div>
|
||||
<div className="text-[0.8125rem] text-[var(--landing-muted)]">{pr.title}</div>
|
||||
</div>
|
||||
<div className="font-mono text-[0.625rem] tracking-[0.05em] px-3 py-1 rounded-full bg-[rgba(134,239,172,0.08)] text-[rgba(134,239,172,0.7)]">
|
||||
✓ Merged
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-6 mt-16">
|
||||
{stepDetails.map((step) => (
|
||||
<StepCard key={step.num} step={step} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
interface DashCardData {
|
||||
title: string;
|
||||
meta: string;
|
||||
agent: string;
|
||||
amber?: boolean;
|
||||
done?: boolean;
|
||||
}
|
||||
function StepCard({ step }: { step: typeof stepDetails[0] }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setVisible(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.2 }
|
||||
);
|
||||
if (ref.current) observer.observe(ref.current);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
function DashColumn({ title, cards }: { title: string; cards: DashCardData[] }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="font-mono text-[0.625rem] tracking-[0.1em] uppercase text-[var(--landing-muted)] opacity-40 px-2 mb-1">
|
||||
{title}
|
||||
</div>
|
||||
{cards.map((card) => (
|
||||
<div key={card.meta} className="bg-[var(--landing-surface)] border border-[var(--landing-border-subtle)] rounded-lg p-2.5 mb-1.5 text-[0.6875rem]">
|
||||
<div className="text-[var(--landing-fg)]/70 mb-1">{card.title}</div>
|
||||
<div className="font-mono text-[0.5625rem] text-[var(--landing-muted)] opacity-50">{card.meta}</div>
|
||||
<div className="flex items-center gap-1 mt-1 font-mono text-[0.5625rem] text-[var(--landing-muted)] opacity-60">
|
||||
{card.done ? (
|
||||
<span>✓</span>
|
||||
) : (
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full" style={{ background: card.amber ? "rgba(251,191,36,0.7)" : "rgba(134,239,172,0.7)" }} />
|
||||
)}
|
||||
{card.agent}
|
||||
<div
|
||||
ref={ref}
|
||||
className={`glow-card p-8 md:p-10 transition-all duration-700 ${
|
||||
visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
|
||||
}`}
|
||||
style={{ transitionDelay: "0.1s" }}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 items-start">
|
||||
{/* Text */}
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<span className="font-mono text-[0.75rem] px-2.5 py-1 rounded-md" style={{
|
||||
background: `color-mix(in srgb, ${step.accent} 10%, transparent)`,
|
||||
color: step.accent,
|
||||
}}>
|
||||
{step.num}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold tracking-tight mb-4">
|
||||
{step.title}
|
||||
<span className="text-[var(--landing-muted)] italic"> {step.titleItalic}</span>
|
||||
</h3>
|
||||
<p className="text-[var(--landing-muted)] text-[0.9375rem] leading-[1.8]">
|
||||
{step.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Code preview */}
|
||||
<div className="rounded-xl overflow-hidden" style={{
|
||||
background: "rgba(0,0,0,0.4)",
|
||||
border: "1px solid var(--landing-border)",
|
||||
}}>
|
||||
<div className="flex items-center gap-2 px-4 py-2.5" style={{
|
||||
borderBottom: "1px solid var(--landing-border)"
|
||||
}}>
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#ff5f57]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#febc2e]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#28c840]" />
|
||||
</div>
|
||||
<pre className="px-5 py-4 font-mono text-[0.75rem] leading-[2] text-[var(--landing-muted)] overflow-x-auto">
|
||||
{step.code.split("\n").map((line, i) => (
|
||||
<div key={i} className={line.startsWith("✓") ? "text-[var(--accent-green)]" : line.startsWith("$") ? "text-[var(--landing-fg-secondary)]" : line.startsWith("→") ? "text-[var(--accent-orange)]" : ""}>
|
||||
{line || "\u00A0"}
|
||||
</div>
|
||||
))}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,38 +26,37 @@ function GithubIcon() {
|
|||
|
||||
export function LandingNav() {
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-8 py-6 max-w-[80rem] mx-auto bg-[var(--landing-bg)]/90 backdrop-blur-sm">
|
||||
<nav className="fixed top-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-1 px-2 py-2 rounded-full" style={{
|
||||
background: "rgba(10,10,10,0.7)",
|
||||
backdropFilter: "blur(20px)",
|
||||
border: "1px solid rgba(255,255,255,0.06)",
|
||||
boxShadow: "0 8px 32px rgba(0,0,0,0.4)"
|
||||
}}>
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center gap-2 text-base font-semibold text-white no-underline font-sans font-[680] tracking-tight"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-semibold text-white no-underline hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<img src="/ao-logo.svg" alt="" aria-hidden="true" width={28} height={28} className="h-7 w-7" />
|
||||
Agent Orchestrator
|
||||
<img src="/ao-logo.svg" alt="" aria-hidden="true" width={22} height={22} className="h-[22px] w-[22px]" />
|
||||
<span className="hidden md:inline">Agent Orchestrator</span>
|
||||
</a>
|
||||
<ul className="hidden md:flex items-center gap-8 list-none">
|
||||
<li>
|
||||
<a href="/docs" className="text-sm text-[var(--landing-muted)] no-underline hover:text-white transition-colors">
|
||||
Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#features" className="text-sm text-[var(--landing-muted)] no-underline hover:text-white transition-colors">
|
||||
Features
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#how" className="text-sm text-[var(--landing-muted)] no-underline hover:text-white transition-colors">
|
||||
How It Works
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="hidden md:flex items-center gap-0.5 ml-2">
|
||||
<a href="/docs" className="px-3 py-1.5 rounded-full text-sm text-[var(--landing-muted)] no-underline hover:text-white hover:bg-white/5 transition-all">
|
||||
Docs
|
||||
</a>
|
||||
<a href="#features" className="px-3 py-1.5 rounded-full text-sm text-[var(--landing-muted)] no-underline hover:text-white hover:bg-white/5 transition-all">
|
||||
Features
|
||||
</a>
|
||||
<a href="#how" className="px-3 py-1.5 rounded-full text-sm text-[var(--landing-muted)] no-underline hover:text-white hover:bg-white/5 transition-all">
|
||||
How It Works
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5 ml-2">
|
||||
<a
|
||||
href="https://x.com/aoagents"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="X (Twitter)"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-white/80 transition-colors hover:text-white"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-[var(--landing-muted)] transition-all hover:text-white hover:bg-white/5"
|
||||
>
|
||||
<XIcon />
|
||||
</a>
|
||||
|
|
@ -66,7 +65,7 @@ export function LandingNav() {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Discord"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-white/80 transition-colors hover:text-white"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-[var(--landing-muted)] transition-all hover:text-white hover:bg-white/5"
|
||||
>
|
||||
<DiscordIcon />
|
||||
</a>
|
||||
|
|
@ -75,7 +74,7 @@ export function LandingNav() {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="GitHub"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-md text-white/80 transition-colors hover:text-white"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-[var(--landing-muted)] transition-all hover:text-white hover:bg-white/5"
|
||||
>
|
||||
<GithubIcon />
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -6,37 +6,50 @@ const steps = [
|
|||
|
||||
export function LandingQuickStart() {
|
||||
return (
|
||||
<section className="py-[120px] px-6 max-w-[72rem] mx-auto bg-[radial-gradient(ellipse_at_bottom,rgba(255,255,255,0.015)_0%,transparent_60%)]">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted)] opacity-60 mb-6">
|
||||
<section className="py-[120px] px-6 max-w-[72rem] mx-auto relative">
|
||||
{/* Subtle radial glow background */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(139,92,246,0.03)_0%,transparent_60%)] pointer-events-none" />
|
||||
|
||||
<div className="landing-reveal relative">
|
||||
<div className="text-xs tracking-[0.2em] uppercase text-[var(--accent-cyan)] mb-4 font-mono font-medium">
|
||||
Get started in 60 seconds
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] tracking-tight font-normal text-[clamp(1.375rem,3vw,2rem)] leading-[1.05] tracking-[-1.5px] mb-6">
|
||||
<h2 className="font-sans font-[680] tracking-tight text-[clamp(1.375rem,3vw,2rem)] leading-[1.05] tracking-[-1.5px]">
|
||||
Three commands to{" "}
|
||||
<em className="italic text-[var(--landing-muted)]">launch</em>
|
||||
<span className="text-gradient-purple">launch</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mt-12">
|
||||
{steps.map((s) => (
|
||||
<div key={s.num} className="landing-reveal landing-card rounded-2xl p-7">
|
||||
<div className="font-mono text-[0.625rem] tracking-[0.1em] text-[var(--landing-muted)] opacity-40 mb-3">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mt-12 stagger-children relative">
|
||||
{steps.map((s, idx) => (
|
||||
<div key={s.num} className="landing-reveal shimmer-card rounded-2xl p-7 group relative">
|
||||
{/* Step number accent */}
|
||||
<div className="absolute top-0 right-6 text-[6rem] font-sans font-[800] leading-none text-[rgba(255,255,255,0.02)] select-none pointer-events-none">
|
||||
{idx + 1}
|
||||
</div>
|
||||
|
||||
<div className="font-mono text-[0.625rem] tracking-[0.15em] text-[var(--accent-cyan)] mb-4 uppercase">
|
||||
{s.num}
|
||||
</div>
|
||||
<h3 className="font-sans font-[680] tracking-tight text-xl mb-2 tracking-tight">
|
||||
<h3 className="font-sans font-[680] tracking-tight text-xl mb-2 tracking-tight text-[var(--landing-fg)] group-hover:text-[var(--accent-cyan)] transition-colors duration-300">
|
||||
{s.title}
|
||||
</h3>
|
||||
<p className="text-[var(--landing-muted)] text-[0.8125rem] leading-[1.6] mb-4">
|
||||
<p className="text-[var(--landing-muted)] text-[0.8125rem] leading-[1.6] mb-5">
|
||||
{s.desc}
|
||||
</p>
|
||||
<div className="font-mono text-xs text-[var(--landing-fg)]/70 bg-black/30 px-3.5 py-2.5 rounded-lg">
|
||||
<span className="text-[var(--landing-muted)] opacity-40">$</span> {s.cmd}
|
||||
<div className="font-mono text-xs text-[var(--landing-fg-secondary)] bg-[rgba(0,0,0,0.4)] px-4 py-3 rounded-lg border border-[var(--landing-border)]">
|
||||
<span className="text-[var(--accent-cyan)] select-none">❯ </span>
|
||||
<span>{s.cmd}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="landing-reveal mt-8 text-center">
|
||||
<a href="/docs/" className="landing-card inline-flex rounded-lg px-4 py-2 text-[0.8125rem] text-[var(--landing-muted)] no-underline hover:text-white transition-colors">
|
||||
<div className="landing-reveal mt-10 text-center relative">
|
||||
<a
|
||||
href="/docs/"
|
||||
className="btn-secondary inline-flex items-center gap-2 rounded-xl px-6 py-3 text-[0.8125rem] no-underline"
|
||||
>
|
||||
Explore docs for setup and workflows
|
||||
<span className="text-[var(--landing-muted)] transition-transform duration-200 group-hover:translate-x-0.5">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,51 +1,312 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import type { GitHubRepoStats } from "@/lib/github-repo";
|
||||
|
||||
interface LandingStatsProps {
|
||||
stats: GitHubRepoStats;
|
||||
}
|
||||
|
||||
/* ── Animated counter hook ──────────────────────────────────── */
|
||||
function useAnimatedCounter(
|
||||
target: number,
|
||||
duration = 1800,
|
||||
enabled = true,
|
||||
) {
|
||||
const [value, setValue] = useState(enabled ? 0 : target);
|
||||
const raf = useRef<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const start = performance.now();
|
||||
const tick = (now: number) => {
|
||||
const t = Math.min((now - start) / duration, 1);
|
||||
// ease-out cubic
|
||||
const eased = 1 - Math.pow(1 - t, 3);
|
||||
setValue(Math.round(eased * target));
|
||||
if (t < 1) raf.current = requestAnimationFrame(tick);
|
||||
};
|
||||
raf.current = requestAnimationFrame(tick);
|
||||
return () => cancelAnimationFrame(raf.current);
|
||||
}, [target, duration, enabled]);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* ── Stat card ──────────────────────────────────────────────── */
|
||||
function StatCard({
|
||||
raw,
|
||||
label,
|
||||
icon,
|
||||
delay,
|
||||
visible,
|
||||
}: {
|
||||
raw: number;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
delay: number;
|
||||
visible: boolean;
|
||||
}) {
|
||||
const display = useAnimatedCounter(raw, 2000, visible);
|
||||
const formatted = display.toLocaleString();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="landing-card glow-card group relative flex flex-col items-center justify-center rounded-2xl py-9 px-5
|
||||
transition-all duration-500 ease-out
|
||||
hover:scale-[1.04] hover:shadow-[0_0_40px_rgba(249,115,22,0.08)]"
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
transform: visible ? "translateY(0)" : "translateY(24px)",
|
||||
transitionDelay: `${delay}ms`,
|
||||
}}
|
||||
>
|
||||
{/* Icon */}
|
||||
<span className="mb-3 text-[var(--landing-muted)] opacity-50 transition-opacity duration-300 group-hover:opacity-90">
|
||||
{icon}
|
||||
</span>
|
||||
|
||||
{/* Number — gradient text */}
|
||||
<span
|
||||
className="font-sans font-[800] leading-none tracking-tight
|
||||
text-[clamp(2.25rem,4.5vw,3.5rem)]"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(135deg, var(--landing-fg) 30%, var(--accent-orange) 100%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent",
|
||||
backgroundClip: "text",
|
||||
}}
|
||||
>
|
||||
{formatted}
|
||||
</span>
|
||||
|
||||
{/* Label */}
|
||||
<span
|
||||
className="mt-2 text-[0.8rem] font-medium tracking-wide uppercase"
|
||||
style={{ color: "var(--landing-muted)" }}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
|
||||
{/* Subtle border glow on hover */}
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 rounded-2xl opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(135deg, rgba(249,115,22,0.06) 0%, transparent 60%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Main section ───────────────────────────────────────────── */
|
||||
export function LandingStats({ stats }: LandingStatsProps) {
|
||||
const cards = [
|
||||
{ number: stats.stars.toLocaleString(), label: "GitHub Stars" },
|
||||
{ number: stats.forks.toLocaleString(), label: "Forks" },
|
||||
{ number: stats.openIssues.toLocaleString(), label: "Open Issues" },
|
||||
{ number: stats.watchers.toLocaleString(), label: "Watchers" },
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const observerCallback = useCallback(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
if (entries[0]?.isIntersecting) setVisible(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const el = sectionRef.current;
|
||||
if (!el) return;
|
||||
const obs = new IntersectionObserver(observerCallback, {
|
||||
threshold: 0.2,
|
||||
});
|
||||
obs.observe(el);
|
||||
return () => obs.disconnect();
|
||||
}, [observerCallback]);
|
||||
|
||||
const cards: {
|
||||
raw: number;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
raw: stats.stars,
|
||||
label: "GitHub Stars",
|
||||
icon: <StarIcon />,
|
||||
},
|
||||
{
|
||||
raw: stats.forks,
|
||||
label: "Forks",
|
||||
icon: <ForkIcon />,
|
||||
},
|
||||
{
|
||||
raw: stats.openIssues,
|
||||
label: "Open Issues",
|
||||
icon: <IssueIcon />,
|
||||
},
|
||||
{
|
||||
raw: stats.watchers,
|
||||
label: "Watchers",
|
||||
icon: <EyeIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 px-6 max-w-[72rem] mx-auto">
|
||||
<section ref={sectionRef} className="py-20 px-6 max-w-[72rem] mx-auto">
|
||||
{/* ── Stat cards grid ── */}
|
||||
<div className="landing-reveal grid grid-cols-2 md:grid-cols-4 gap-5">
|
||||
{cards.map((stat) => (
|
||||
<div
|
||||
{cards.map((stat, i) => (
|
||||
<StatCard
|
||||
key={stat.label}
|
||||
className="landing-card rounded-2xl py-8 px-6 text-center"
|
||||
>
|
||||
<div className="font-sans font-[680] tracking-tight text-[clamp(2rem,4vw,3rem)] tracking-tight mb-1">
|
||||
{stat.number}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--landing-muted)] opacity-60">
|
||||
{stat.label}
|
||||
</div>
|
||||
</div>
|
||||
raw={stat.raw}
|
||||
label={stat.label}
|
||||
icon={stat.icon}
|
||||
delay={i * 120}
|
||||
visible={visible}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="landing-reveal text-center mt-8">
|
||||
|
||||
{/* ── Bottom badges ── */}
|
||||
<div
|
||||
className="landing-reveal mt-10 flex flex-col sm:flex-row items-center justify-center gap-4"
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
transform: visible ? "translateY(0)" : "translateY(16px)",
|
||||
transition: "opacity 0.6s ease, transform 0.6s ease",
|
||||
transitionDelay: "600ms",
|
||||
}}
|
||||
>
|
||||
{/* GitHub stars link */}
|
||||
<a
|
||||
href="https://github.com/ComposioHQ/agent-orchestrator"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="landing-card inline-flex items-center gap-2 rounded-lg px-4 py-2 text-[0.8125rem] text-[var(--landing-muted)] no-underline transition-all hover:text-white mb-3"
|
||||
className="landing-card inline-flex items-center gap-2.5 rounded-xl px-5 py-2.5
|
||||
text-[0.8125rem] no-underline transition-all duration-300
|
||||
hover:scale-[1.03] hover:shadow-[0_0_24px_rgba(249,115,22,0.12)]"
|
||||
style={{
|
||||
color: "var(--landing-muted)",
|
||||
border: "1px solid var(--landing-border)",
|
||||
}}
|
||||
>
|
||||
<span className="text-[rgba(251,191,36,0.7)] text-sm">★</span>
|
||||
<span className="font-mono text-xs text-[var(--landing-fg)] opacity-80">{stats.stars.toLocaleString()}</span>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
style={{ color: "var(--landing-fg)", opacity: 0.7 }}
|
||||
>
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
|
||||
</svg>
|
||||
<span
|
||||
className="font-mono text-xs font-semibold"
|
||||
style={{ color: "var(--landing-fg)", opacity: 0.9 }}
|
||||
>
|
||||
{stats.stars.toLocaleString()}
|
||||
</span>
|
||||
<span>stars on GitHub</span>
|
||||
</a>
|
||||
<br />
|
||||
<div className="landing-card inline-flex items-center gap-2 rounded-lg px-4 py-2 text-[0.8125rem] text-[var(--landing-muted)]">
|
||||
<span className="w-2 h-2 rounded-full bg-[rgba(134,239,172,0.7)] animate-pulse" />
|
||||
Built with itself — this repo is managed by Agent Orchestrator
|
||||
|
||||
{/* Built with itself badge */}
|
||||
<div
|
||||
className="landing-card inline-flex items-center gap-2.5 rounded-xl px-5 py-2.5
|
||||
text-[0.8125rem] transition-all duration-300"
|
||||
style={{
|
||||
color: "var(--landing-muted)",
|
||||
border: "1px solid var(--landing-border)",
|
||||
}}
|
||||
>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span
|
||||
className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
|
||||
style={{ backgroundColor: "var(--accent-green)" }}
|
||||
/>
|
||||
<span
|
||||
className="relative inline-flex h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: "var(--accent-green)" }}
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Built with itself — this repo is managed by Agent Orchestrator
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Inline SVG icons ───────────────────────────────────────── */
|
||||
function StarIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ForkIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="18" r="3" />
|
||||
<circle cx="6" cy="6" r="3" />
|
||||
<circle cx="18" cy="6" r="3" />
|
||||
<path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" />
|
||||
<path d="M12 12v3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function IssueIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 8v4" />
|
||||
<path d="M12 16h.01" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function EyeIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,26 +26,29 @@ export function LandingTestimonials() {
|
|||
return (
|
||||
<section className="py-20 px-6 pb-[120px] max-w-[72rem] mx-auto">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted)] opacity-60 mb-6">
|
||||
<div className="text-xs tracking-[0.2em] uppercase text-[var(--accent-blue)] mb-4 font-mono font-medium">
|
||||
What engineers say
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] tracking-tight font-normal text-[clamp(1.375rem,3vw,2rem)] leading-[1.05] tracking-[-1.5px] mb-6">
|
||||
Trusted by <em className="italic text-[var(--landing-muted)]">builders</em>
|
||||
<h2 className="font-sans font-[680] tracking-tight text-[clamp(1.375rem,3vw,2rem)] leading-[1.05] tracking-[-1.5px] text-gradient-blue">
|
||||
Trusted by <em className="italic text-[var(--landing-fg-secondary)]">builders</em>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mt-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mt-12 stagger-children">
|
||||
{testimonials.map((t) => (
|
||||
<div key={t.initials} className="landing-reveal landing-card rounded-2xl p-8">
|
||||
<p className="text-[0.9375rem] text-[var(--landing-fg)]/80 leading-[1.7] mb-5 italic font-sans font-[680] tracking-tight">
|
||||
“{t.quote}”
|
||||
</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-9 h-9 rounded-full bg-[var(--landing-surface)] flex items-center justify-center text-xs font-semibold text-[var(--landing-muted)]">
|
||||
<div key={t.initials} className="landing-reveal glow-card rounded-2xl p-8 flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="text-[var(--accent-blue)] text-2xl mb-4 leading-none select-none">“</div>
|
||||
<p className="text-[0.9375rem] text-[var(--landing-fg-secondary)] leading-[1.7] mb-6 italic">
|
||||
{t.quote}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 pt-4 border-t border-[var(--landing-border)]">
|
||||
<div className="w-9 h-9 rounded-full bg-[rgba(59,130,246,0.08)] border border-[rgba(59,130,246,0.15)] flex items-center justify-center text-xs font-mono font-semibold text-[var(--accent-blue)]">
|
||||
{t.initials}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[0.8125rem] font-medium">{t.name}</div>
|
||||
<div className="text-[0.6875rem] text-[var(--landing-muted)] opacity-60">
|
||||
<div className="text-[0.8125rem] font-medium text-[var(--landing-fg)]">{t.name}</div>
|
||||
<div className="text-[0.6875rem] text-[var(--landing-muted)]">
|
||||
{t.role}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,45 +23,47 @@ export function LandingUseCases() {
|
|||
return (
|
||||
<section className="py-[100px] px-6 max-w-[72rem] mx-auto">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted-dim)] mb-6 font-mono">
|
||||
<div className="text-xs tracking-[0.2em] uppercase text-[var(--accent-orange)] mb-4 font-mono font-medium">
|
||||
Use cases
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] text-[clamp(1.375rem,3vw,2rem)] leading-[1.1] tracking-[-1.5px] mb-16">
|
||||
<h2 className="font-sans font-[680] text-[clamp(1.375rem,3vw,2rem)] leading-[1.1] tracking-[-1.5px] text-gradient-orange">
|
||||
What teams run with AO
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-6 mt-12 stagger-children">
|
||||
{cases.map((c) => (
|
||||
<div
|
||||
key={c.scenario}
|
||||
className="landing-reveal landing-card rounded-2xl p-8"
|
||||
className="landing-reveal glow-card rounded-2xl p-8 group"
|
||||
>
|
||||
<h3 className="font-sans font-[680] text-lg tracking-tight mb-4">
|
||||
<h3 className="font-sans font-[680] text-lg tracking-tight text-[var(--landing-fg)] mb-6">
|
||||
{c.scenario}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto_1fr_1fr] gap-4 md:gap-6 items-start">
|
||||
<div>
|
||||
<div className="font-mono text-[0.5625rem] tracking-[0.1em] uppercase text-[var(--landing-muted-dim)] mb-1.5">
|
||||
<div className="bg-[rgba(255,255,255,0.02)] rounded-xl p-4 border border-[var(--landing-border)]">
|
||||
<div className="font-mono text-[0.5625rem] tracking-[0.15em] uppercase text-[var(--landing-muted)] mb-2">
|
||||
Before
|
||||
</div>
|
||||
<p className="text-[0.8125rem] text-[var(--landing-muted)]">
|
||||
<p className="text-[0.8125rem] text-[var(--landing-muted)] leading-relaxed">
|
||||
{c.before}
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center text-[var(--landing-muted-dim)] text-lg">
|
||||
→
|
||||
<div className="hidden md:flex items-center justify-center">
|
||||
<div className="w-8 h-8 rounded-full bg-[var(--landing-border)] flex items-center justify-center text-[var(--landing-muted-dim)]">
|
||||
→
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-mono text-[0.5625rem] tracking-[0.1em] uppercase text-[rgba(134,239,172,0.7)] mb-1.5">
|
||||
<div className="bg-[rgba(34,197,94,0.04)] rounded-xl p-4 border border-[rgba(34,197,94,0.1)]">
|
||||
<div className="font-mono text-[0.5625rem] tracking-[0.15em] uppercase text-[var(--accent-green)] mb-2">
|
||||
After
|
||||
</div>
|
||||
<p className="text-[0.8125rem] text-[var(--landing-fg)]">
|
||||
<p className="text-[0.8125rem] text-[var(--landing-fg)] leading-relaxed">
|
||||
{c.after}
|
||||
</p>
|
||||
</div>
|
||||
<div className="font-mono text-[0.6875rem] text-[var(--landing-muted)] bg-black/30 px-3.5 py-2.5 rounded-lg self-center">
|
||||
<span className="text-[var(--landing-muted-dim)]">$</span>{" "}
|
||||
{c.command}
|
||||
<div className="font-mono text-[0.75rem] text-[var(--landing-fg-secondary)] bg-[rgba(0,0,0,0.4)] px-4 py-3 rounded-lg self-center border border-[var(--landing-border)]">
|
||||
<span className="text-[var(--accent-orange)] select-none">❯ </span>
|
||||
<span className="text-[var(--landing-fg)]">{c.command}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
export function LandingVideo() {
|
||||
return (
|
||||
<section className="landing-reveal px-6 pb-[120px] pt-10 max-w-[72rem] mx-auto">
|
||||
<div className="text-center mb-5">
|
||||
<span className="text-[0.6875rem] tracking-[0.12em] uppercase text-[var(--landing-muted)] opacity-50">
|
||||
<section className="landing-reveal px-6 pb-[140px] pt-10 max-w-[72rem] mx-auto">
|
||||
<div className="text-center mb-6">
|
||||
<span className="font-mono text-[0.75rem] tracking-[0.2em] uppercase text-[var(--accent-orange)]">
|
||||
See it in action
|
||||
</span>
|
||||
</div>
|
||||
<div className="landing-card rounded-2xl overflow-hidden aspect-video">
|
||||
<div className="glow-card rounded-2xl overflow-hidden aspect-video" style={{
|
||||
boxShadow: "0 0 80px -20px rgba(249,115,22,0.1)"
|
||||
}}>
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/QdwaeEXOmDs?autoplay=0&rel=0&modestbranding=1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const steps = [
|
||||
{ label: "Issue assigned", mono: "#42", color: "rgba(255,240,220,0.5)" },
|
||||
{ label: "Agent spawns", mono: "claude-code", color: "rgba(96,165,250,0.8)" },
|
||||
{ label: "Worktree created", mono: "feat/auth", color: "rgba(234,179,8,0.7)" },
|
||||
{ label: "PR opened", mono: "PR #312", color: "rgba(167,139,250,0.7)" },
|
||||
{ label: "CI passes", mono: "✓ 48/48", color: "rgba(134,239,172,0.7)" },
|
||||
{ label: "Merged", mono: "main", color: "rgba(34,197,94,0.9)" },
|
||||
{ label: "Issue assigned", mono: "#42", color: "var(--accent-orange)" },
|
||||
{ label: "Agent spawns", mono: "claude-code", color: "var(--accent-blue)" },
|
||||
{ label: "Worktree created", mono: "feat/auth", color: "var(--accent-purple)" },
|
||||
{ label: "PR opened", mono: "PR #312", color: "var(--accent-cyan)" },
|
||||
{ label: "CI passes", mono: "✓ 48/48", color: "var(--accent-green)" },
|
||||
{ label: "Merged", mono: "main", color: "var(--accent-green)" },
|
||||
];
|
||||
|
||||
export function LandingWorkflow() {
|
||||
|
|
@ -39,42 +39,49 @@ export function LandingWorkflow() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<section ref={ref} className="py-[100px] px-6 max-w-[72rem] mx-auto">
|
||||
<section ref={ref} className="py-[140px] px-6 max-w-[72rem] mx-auto">
|
||||
<div className="landing-reveal">
|
||||
<div className="text-xs tracking-[0.15em] uppercase text-[var(--landing-muted-dim)] mb-6 font-mono">
|
||||
<div className="font-mono text-[0.75rem] tracking-[0.2em] uppercase text-[var(--accent-purple)] mb-4">
|
||||
Lifecycle
|
||||
</div>
|
||||
<h2 className="font-sans font-[680] text-[clamp(1.375rem,3vw,2rem)] leading-[1.1] tracking-[-1.5px] mb-16">
|
||||
<h2 className="text-[clamp(1.5rem,3vw,2.25rem)] leading-[1.1] tracking-[-1px] font-bold">
|
||||
From issue to merged PR
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Pipeline */}
|
||||
<div className="relative">
|
||||
{/* Connection line */}
|
||||
<div className="absolute top-6 left-6 right-6 h-px bg-[var(--landing-border-subtle)] hidden md:block" />
|
||||
<div className="relative mt-16">
|
||||
{/* Connection line — background */}
|
||||
<div className="absolute top-6 left-6 right-6 h-[2px] hidden md:block" style={{
|
||||
background: "var(--landing-border)"
|
||||
}} />
|
||||
{/* Connection line — animated fill */}
|
||||
<div
|
||||
className="absolute top-6 left-6 right-6 h-px hidden md:block transition-all duration-700 ease-out origin-left"
|
||||
className="absolute top-6 left-6 right-6 h-[2px] hidden md:block transition-all duration-700 ease-out origin-left"
|
||||
style={{
|
||||
background: "var(--landing-accent)",
|
||||
background: "linear-gradient(90deg, var(--accent-orange), var(--accent-purple), var(--accent-green))",
|
||||
transform: `scaleX(${activeStep >= 0 ? Math.min(activeStep / (steps.length - 1), 1) : 0})`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Steps */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-6 md:gap-0">
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-8 md:gap-0">
|
||||
{steps.map((step, i) => {
|
||||
const isActive = i <= activeStep;
|
||||
return (
|
||||
<div key={step.label} className="flex flex-col items-center text-center relative">
|
||||
{/* Node */}
|
||||
<div
|
||||
className={`w-12 h-12 rounded-xl flex items-center justify-center mb-4 transition-all duration-500 ${
|
||||
className={`w-12 h-12 rounded-2xl flex items-center justify-center mb-4 transition-all duration-500 ${
|
||||
isActive
|
||||
? "landing-card border-[var(--landing-border-default)]"
|
||||
: "border border-[var(--landing-border-subtle)] bg-transparent"
|
||||
? "border-[1.5px]"
|
||||
: "border border-[var(--landing-border)]"
|
||||
}`}
|
||||
style={isActive ? { borderColor: step.color, boxShadow: `0 0 12px ${step.color.replace(/[\d.]+\)$/, "0.15)") }` } : undefined}
|
||||
style={isActive ? {
|
||||
borderColor: step.color,
|
||||
background: `color-mix(in srgb, ${step.color} 8%, transparent)`,
|
||||
boxShadow: `0 0 20px -4px ${step.color}`
|
||||
} : undefined}
|
||||
>
|
||||
<span
|
||||
className={`w-2.5 h-2.5 rounded-full transition-all duration-500 ${
|
||||
|
|
@ -86,24 +93,24 @@ export function LandingWorkflow() {
|
|||
|
||||
{/* Label */}
|
||||
<div
|
||||
className={`text-[0.6875rem] font-medium mb-1 transition-all duration-500 ${
|
||||
className={`text-[0.75rem] font-medium mb-1 transition-all duration-500 ${
|
||||
isActive ? "text-[var(--landing-fg)]" : "text-[var(--landing-muted-dim)]"
|
||||
}`}
|
||||
>
|
||||
{step.label}
|
||||
</div>
|
||||
<div
|
||||
className={`font-mono text-[0.5625rem] transition-all duration-500 ${
|
||||
className={`font-mono text-[0.625rem] transition-all duration-500 ${
|
||||
isActive ? "text-[var(--landing-muted)]" : "text-[var(--landing-muted-dim)] opacity-50"
|
||||
}`}
|
||||
>
|
||||
{step.mono}
|
||||
</div>
|
||||
|
||||
{/* Pulse on active */}
|
||||
{/* Pulse ring on active */}
|
||||
{i === activeStep && (
|
||||
<div
|
||||
className="absolute top-0 w-12 h-12 rounded-xl landing-node-pulse"
|
||||
className="absolute top-0 w-12 h-12 rounded-2xl landing-node-pulse"
|
||||
style={{ borderColor: step.color }}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ export function ScrollRevealProvider({ children }: { children: React.ReactNode }
|
|||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1, rootMargin: "-50px" }
|
||||
{ threshold: 0.1, rootMargin: "-40px" }
|
||||
);
|
||||
|
||||
document.querySelectorAll(".landing-reveal").forEach((el) => {
|
||||
// Observe both landing-reveal and stagger-children elements
|
||||
document.querySelectorAll(".landing-reveal, .stagger-children").forEach((el) => {
|
||||
observer.observe(el);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue