update
This commit is contained in:
1 parent
66158e222e
commit
67c757f481
314 files changed
+37945
-22962
No files matched your search
@@ -0,0 +1,158 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
const glyphs = " .'`^,:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
|
||||
|
||||
function parseRgbHex(hex: string): [number, number, number] {
|
||||
const value = hex.replace("#", "").trim();
|
||||
if (value.length !== 6) return [128, 128, 128];
|
||||
return [
|
||||
Number.parseInt(value.slice(0, 2), 16),
|
||||
Number.parseInt(value.slice(2, 4), 16),
|
||||
Number.parseInt(value.slice(4, 6), 16),
|
||||
];
|
||||
}
|
||||
|
||||
export function AsciiBackground({
|
||||
cssVar = "--accent",
|
||||
baseOpacity = 0.08,
|
||||
speed = 1,
|
||||
}: {
|
||||
cssVar?: string;
|
||||
baseOpacity?: number;
|
||||
speed?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
const container = containerRef.current;
|
||||
if (!canvas || !container) return;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let rgb: [number, number, number] = [128, 128, 128];
|
||||
let phase = 0;
|
||||
let frame = 0;
|
||||
let isVisible = true;
|
||||
const pointer = { x: -9999, y: -9999 };
|
||||
|
||||
const updateColor = () => {
|
||||
const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
|
||||
if (value.startsWith("#")) {
|
||||
rgb = parseRgbHex(value);
|
||||
}
|
||||
};
|
||||
|
||||
updateColor();
|
||||
|
||||
const mutationObserver = new MutationObserver(updateColor);
|
||||
mutationObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
isVisible = entry?.isIntersecting ?? true;
|
||||
},
|
||||
{ threshold: 0 },
|
||||
);
|
||||
intersectionObserver.observe(container);
|
||||
|
||||
const resize = () => {
|
||||
width = container.clientWidth;
|
||||
height = container.clientHeight;
|
||||
const devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
canvas.width = width * devicePixelRatio;
|
||||
canvas.height = height * devicePixelRatio;
|
||||
context.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
|
||||
};
|
||||
|
||||
const handlePointer = (event: MouseEvent) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
pointer.x = event.clientX - rect.left;
|
||||
pointer.y = event.clientY - rect.top;
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
frame = requestAnimationFrame(render);
|
||||
if (!isVisible) return;
|
||||
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.font = "14px monospace";
|
||||
context.textAlign = "center";
|
||||
context.textBaseline = "middle";
|
||||
|
||||
const columns = Math.ceil(width / 14);
|
||||
const rows = Math.ceil(height / 14);
|
||||
const [red, green, blue] = rgb;
|
||||
const pointerX = pointer.x;
|
||||
const pointerY = pointer.y;
|
||||
|
||||
for (let row = 0; row < rows; row += 1) {
|
||||
for (let column = 0; column < columns; column += 1) {
|
||||
const centerX = column * 14 + 7;
|
||||
const centerY = row * 14 + 7;
|
||||
const value =
|
||||
Math.sin(0.05 * column + phase) * Math.cos(0.05 * row + phase) +
|
||||
Math.sin(0.012 * column - 0.7 * phase) * Math.cos(0.1 * row) * 0.5;
|
||||
|
||||
if (Math.abs(value) < 0.3) continue;
|
||||
|
||||
const index = Math.floor(Math.abs(value) * glyphs.length);
|
||||
let glyph = glyphs[index % glyphs.length];
|
||||
if (!glyph.trim()) continue;
|
||||
|
||||
let alpha = Math.abs(value) * baseOpacity;
|
||||
const deltaX = centerX - pointerX;
|
||||
const deltaY = centerY - pointerY;
|
||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
if (distance < 110) {
|
||||
const intensity = 1 - distance / 110;
|
||||
glyph = Math.random() > 0.5 ? "0" : "1";
|
||||
alpha = 0.35 * intensity;
|
||||
const drift = distance > 1 ? 7 * intensity : 0;
|
||||
const offsetX = (deltaX / distance) * drift;
|
||||
const offsetY = (deltaY / distance) * drift;
|
||||
context.fillStyle = `rgba(${red},${green},${blue},${alpha})`;
|
||||
context.fillText(glyph, centerX - offsetX, centerY - offsetY);
|
||||
} else {
|
||||
context.fillStyle = `rgba(${red},${green},${blue},${alpha})`;
|
||||
context.fillText(glyph, centerX, centerY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phase += 0.005 * speed;
|
||||
};
|
||||
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
window.addEventListener("mousemove", handlePointer);
|
||||
render();
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
window.removeEventListener("resize", resize);
|
||||
window.removeEventListener("mousemove", handlePointer);
|
||||
mutationObserver.disconnect();
|
||||
intersectionObserver.disconnect();
|
||||
};
|
||||
}, [baseOpacity, cssVar, speed]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||
<canvas ref={canvasRef} className="block w-full h-full" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { AsciiBackground } from "./ascii-background";
|
||||
import { platformDownloads, releaseVersion } from "./release-links.generated";
|
||||
|
||||
export function DownloadSection() {
|
||||
return (
|
||||
<section id="download" className="py-24 lg:py-40 px-4 sm:px-6 lg:px-8 relative overflow-hidden">
|
||||
<motion.div
|
||||
className="relative mx-auto max-w-5xl rounded-3xl p-14 lg:p-24 overflow-hidden"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, var(--cta) 0%, color-mix(in srgb, var(--cta) 85%, #000) 100%)",
|
||||
}}
|
||||
initial={{ opacity: 0, scale: 0.92 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true, margin: "-80px" }}
|
||||
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<AsciiBackground cssVar="--text-on-cta" baseOpacity={0.1} speed={0.8} />
|
||||
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none opacity-[0.03]"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='256' height='256' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E\")",
|
||||
backgroundRepeat: "repeat",
|
||||
backgroundSize: "128px 128px",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse at top right, rgba(255,255,255,0.12) 0%, transparent 50%), radial-gradient(ellipse at bottom left, rgba(255,255,255,0.06) 0%, transparent 50%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 flex flex-col items-center text-center">
|
||||
<h2 className="font-serif italic text-[clamp(2rem,1.5rem+2.5vw,3.25rem)] tracking-[-0.01em] text-[var(--text-on-cta)] mb-6">
|
||||
Install the Right Build for Your Machine
|
||||
</h2>
|
||||
<p className="text-[var(--text-on-cta-muted)] text-lg max-w-xl mx-auto mb-12 leading-relaxed">
|
||||
Full release matrix hosted on our Cloudflare release lane. Version {releaseVersion}.
|
||||
</p>
|
||||
|
||||
<div className="w-full mt-2 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{platformDownloads.map((download, index) => (
|
||||
<motion.div
|
||||
key={download.id}
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
transition={{ duration: 0.5, delay: 0.08 * index, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="rounded-2xl border border-white/10 bg-white/6 backdrop-blur-xl shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] p-6 lg:p-7 text-left"
|
||||
>
|
||||
<p className="font-mono text-xs uppercase tracking-[0.25em] text-[var(--text-on-cta-faint)] mb-3">
|
||||
{download.title}
|
||||
</p>
|
||||
<p className="text-[var(--text-on-cta-muted)] text-sm leading-relaxed mb-4">{download.note}</p>
|
||||
<div className="space-y-2.5">
|
||||
{download.assets.map((asset) => (
|
||||
<a
|
||||
key={asset.id}
|
||||
href={asset.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-between gap-3 rounded-xl border border-white/12 bg-black/10 px-4 py-3 text-sm text-[var(--text-on-cta)] transition-colors duration-200 hover:bg-black/18"
|
||||
>
|
||||
<span className="font-semibold tracking-tight">{asset.label}</span>
|
||||
<span className="font-mono text-xs uppercase tracking-[0.18em] text-[var(--text-on-cta-faint)]">
|
||||
{asset.note}
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "What is AgentCompany?",
|
||||
answer:
|
||||
"AgentCompany is a local-first desktop application for launching and operating autonomous AI companies. You define the mission, an AI CEO organizes the team, delegates work, and coordinates delivery across engineering, design, research, and operations.",
|
||||
},
|
||||
{
|
||||
question: "How do I download AgentCompany?",
|
||||
answer:
|
||||
"Use the download section on this page to choose the build that matches your architecture and package format. macOS ships as signed and notarized DMGs, Windows ships as a universal installer covering ARM64, x64, and x86, and Linux ships as AppImage, DEB, and RPM builds from our Cloudflare release host.",
|
||||
},
|
||||
{
|
||||
question: "What platforms does AgentCompany support?",
|
||||
answer:
|
||||
"AgentCompany is available on macOS arm64 and x64, Windows ARM64/x64/x86, and Linux x64 and ARM64. It is built with Electron and the desktop feature set stays aligned across all supported targets.",
|
||||
},
|
||||
{
|
||||
question: "What AI models can AgentCompany use?",
|
||||
answer:
|
||||
"AgentCompany supports multiple AI runtimes as first-class connectors: Claude Code (Anthropic), Codex CLI (OpenAI), and Gemini CLI (Google). You can connect your preferred provider during setup.",
|
||||
},
|
||||
{
|
||||
question: "How does the AI CEO work?",
|
||||
answer:
|
||||
"When you create a company with a goal, the AI CEO agent analyzes the objective, creates the necessary departments (Engineering, Design, Marketing, etc.), hires specialized agents for each role, builds an org chart, and delegates tasks. The CEO monitors progress, handles blockers, and coordinates cross-department work — all autonomously.",
|
||||
},
|
||||
{
|
||||
question: "Is my data private? Does it leave my machine?",
|
||||
answer:
|
||||
"AgentCompany is local-first. All data is stored in a local SQLite database on your machine. Secrets are encrypted via an OS-level keychain. The only external communication is between your machine and your AI provider's API — no data is sent to AgentCompany servers.",
|
||||
},
|
||||
{
|
||||
question: "What can an AI company actually deliver?",
|
||||
answer:
|
||||
"AI companies built with AgentCompany can produce code (full applications, scripts, APIs), content (blog posts, marketing copy, social media), research (competitive analysis, market research), business documents (business plans, technical specs, strategies), and more. The deliverables depend on the goal you set and the AI runtime you connect.",
|
||||
},
|
||||
{
|
||||
question: "How are updates delivered?",
|
||||
answer:
|
||||
"Desktop updates are published to a Cloudflare-hosted release lane. macOS builds include the signed update metadata required for in-app update checks, while the Windows and Linux installers and packages are distributed from the same release host.",
|
||||
},
|
||||
];
|
||||
|
||||
function FaqItem({ question, answer, index }: { question: string; answer: string; index: number }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
transition={{ duration: 0.5, delay: 0.06 * index, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="border-b border-[var(--border)] last:border-b-0"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((value) => !value)}
|
||||
aria-expanded={open}
|
||||
className="flex w-full items-center justify-between gap-4 py-6 text-left transition-colors duration-200 hover:text-[var(--accent)]"
|
||||
>
|
||||
<h3 className="text-base lg:text-lg font-semibold text-[var(--text)] pr-4">{question}</h3>
|
||||
<span
|
||||
className={`shrink-0 text-[var(--muted)] transition-transform duration-300 ${open ? "rotate-45" : ""}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19" />
|
||||
<line x1="5" y1="12" x2="19" y2="12" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className={`overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] ${open ? "max-h-96 pb-6" : "max-h-0"}`}>
|
||||
<p className="text-[var(--muted-strong)] leading-relaxed text-base pr-12">{answer}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FAQ() {
|
||||
return (
|
||||
<section id="faq" className="py-24 lg:py-40 px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center mb-16 lg:mb-20"
|
||||
>
|
||||
<p className="font-mono text-xs font-medium uppercase tracking-[0.25em] text-[var(--accent)] mb-5">FAQ</p>
|
||||
<h2 className="font-serif text-[clamp(2rem,1.5rem+2.5vw,3.25rem)] tracking-[-0.01em] text-[var(--text)]">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="border-t border-[var(--border)]">
|
||||
{faqs.map((item, index) => (
|
||||
<FaqItem key={item.question} question={item.question} answer={item.answer} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const features = [
|
||||
{
|
||||
number: "01",
|
||||
title: "One-Click Company Creation",
|
||||
description:
|
||||
"Name your company, describe your mission, connect your AI runtime, and launch. Your first agents start working in under a minute.",
|
||||
image: "/screenshots/Onboarding.jpeg",
|
||||
imageAlt: "Company creation onboarding flow with mission and runtime setup",
|
||||
imageLeft: true,
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
title: "Self-Organizing Teams",
|
||||
description:
|
||||
"Your AI CEO builds the org chart. It identifies the roles needed, hires specialists, and creates reporting lines — all from a single goal.",
|
||||
image: "/screenshots/Auto-OrgChart-Team.jpeg",
|
||||
imageAlt: "AI-generated org chart showing self-organizing team structure",
|
||||
imageLeft: false,
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
title: "Autonomous Task Management",
|
||||
description:
|
||||
"Goals break down into tasks automatically. Agents pick up work, report progress, and escalate blockers — you decide when to step in.",
|
||||
image: "/screenshots/Auto-Task.jpeg",
|
||||
imageAlt: "Autonomous task board with agents managing work items",
|
||||
imageLeft: true,
|
||||
},
|
||||
{
|
||||
number: "04",
|
||||
title: "Real-Time Agent Communication",
|
||||
description:
|
||||
"Agents communicate through department channels and direct messages. Watch strategic decisions happen in real time, like a Slack workspace you built.",
|
||||
image: "/screenshots/AI-Internal-Realtime-Message.jpeg",
|
||||
imageAlt: "Real-time messaging between AI agents in department channels",
|
||||
},
|
||||
{
|
||||
number: "05",
|
||||
title: "Human-in-the-Loop Approvals",
|
||||
description:
|
||||
"Stay in control. Critical decisions surface for your review. Approve, reject, or redirect — your company respects the chain of command.",
|
||||
image: "/screenshots/Approval-by-Human.jpeg",
|
||||
imageAlt: "Approval interface showing human review of agent decisions",
|
||||
},
|
||||
{
|
||||
number: "06",
|
||||
title: "AI Document Generation",
|
||||
description:
|
||||
"Business plans, marketing strategies, technical specs — agents draft and iterate on real deliverables, stored in your document center.",
|
||||
image: "/screenshots/Document-Center.jpeg",
|
||||
imageAlt: "Document center with AI-generated business deliverables",
|
||||
},
|
||||
];
|
||||
|
||||
function FeaturePair({ feature }: { feature: (typeof features)[number] }) {
|
||||
const { number, title, description, image, imageAlt, imageLeft } = feature;
|
||||
const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
||||
|
||||
const media = (
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, x: imageLeft ? -60 : 60 },
|
||||
visible: { opacity: 1, x: 0, transition: { duration: 0.7, ease } },
|
||||
}}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
className={`order-2 ${imageLeft ? "lg:order-1" : "lg:order-2"}`}
|
||||
>
|
||||
<div className="rounded-2xl overflow-hidden shadow-[0_4px_32px_-8px_rgba(0,0,0,0.1)] border border-[var(--border)] bg-[var(--panel)] transition-shadow duration-500 hover:shadow-[0_8px_48px_-12px_rgba(0,0,0,0.15)]">
|
||||
<img src={image} alt={imageAlt} width="1920" height="1080" className="w-full h-auto block" loading="lazy" />
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
const copy = (
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, x: imageLeft ? 60 : -60 },
|
||||
visible: { opacity: 1, x: 0, transition: { duration: 0.7, ease, delay: 0.1 } },
|
||||
}}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
className={`flex flex-col justify-center order-1 ${imageLeft ? "lg:order-2" : "lg:order-1"}`}
|
||||
>
|
||||
<span className="font-mono text-[5rem] lg:text-[6rem] font-bold leading-none text-[var(--accent)] opacity-[0.07] select-none -mb-10 lg:-mb-12">
|
||||
{number}
|
||||
</span>
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<span className="block w-2 h-2 rounded-full bg-[var(--accent)]" />
|
||||
<span className="font-mono text-sm text-[var(--muted)]">{number}</span>
|
||||
</div>
|
||||
<h2 className="text-2xl lg:text-[1.875rem] font-bold text-[var(--text)] mb-4 tracking-tight">{title}</h2>
|
||||
<p className="text-[var(--muted-strong)] leading-relaxed text-base lg:text-lg">{description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
|
||||
{media}
|
||||
{copy}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Features() {
|
||||
return (
|
||||
<section
|
||||
id="features"
|
||||
className="py-24 lg:py-40"
|
||||
style={{ background: "linear-gradient(180deg, transparent 0%, var(--section-alt) 30%, var(--section-alt) 70%, transparent 100%)" }}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-center mb-24 lg:mb-36"
|
||||
>
|
||||
<span className="font-mono text-xs uppercase tracking-[0.25em] text-[var(--accent)] mb-5 block font-medium">
|
||||
Features
|
||||
</span>
|
||||
<h2 className="font-serif text-[clamp(2rem,1.5rem+2.5vw,3.25rem)] tracking-[-0.01em] text-[var(--text)]">
|
||||
Your Company, Under the Hood
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-24 lg:space-y-40">
|
||||
{features.slice(0, 3).map((feature) => (
|
||||
<FeaturePair key={feature.number} feature={feature} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="mt-24 lg:mt-40 grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
variants={{
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: { staggerChildren: 0.12 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{features.slice(3).map((feature) => (
|
||||
<motion.div
|
||||
key={feature.number}
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: 32 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] },
|
||||
},
|
||||
}}
|
||||
className="group bg-[var(--panel)] rounded-2xl border border-[var(--border)] overflow-hidden transition-all duration-300 hover:shadow-[0_8px_40px_-12px_rgba(0,0,0,0.12)] hover:-translate-y-1.5"
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<img
|
||||
src={feature.image}
|
||||
alt={feature.imageAlt}
|
||||
width="640"
|
||||
height="360"
|
||||
className="w-full h-auto block transition-transform duration-500 group-hover:scale-[1.03]"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6 lg:p-7">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className="block w-2 h-2 rounded-full bg-[var(--accent)]" />
|
||||
<span className="font-mono text-sm text-[var(--muted)]">{feature.number}</span>
|
||||
</div>
|
||||
<h2 className="text-lg font-bold text-[var(--text)] mb-2 tracking-tight">{feature.title}</h2>
|
||||
<p className="text-sm text-[var(--muted-strong)] leading-relaxed">{feature.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { AsciiBackground } from "./ascii-background";
|
||||
import { IconMark } from "./icon-mark";
|
||||
import { CONTACT_EMAIL, WEBSITE_URL } from "./site-constants";
|
||||
import { platformDownloads } from "./release-links.generated";
|
||||
import { ThemeToggle } from "./theme-toggle";
|
||||
|
||||
function findAsset(platformId: string, assetId: string) {
|
||||
const platform = platformDownloads.find((entry) => entry.id === platformId);
|
||||
if (!platform) {
|
||||
throw new Error(`Missing platform download entry for ${platformId}`);
|
||||
}
|
||||
const asset = platform.assets.find((entry) => entry.id === assetId);
|
||||
if (!asset) {
|
||||
throw new Error(`Missing asset ${assetId} for ${platformId}`);
|
||||
}
|
||||
return asset.href;
|
||||
}
|
||||
|
||||
const footerGroups = [
|
||||
{
|
||||
title: "Product",
|
||||
links: [
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "How It Works", href: "#how-it-works" },
|
||||
{ label: "FAQ", href: "#faq" },
|
||||
{ label: "Download", href: "#download" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Downloads",
|
||||
links: [
|
||||
{ label: "Apple Silicon", href: findAsset("macos", "macos-arm64-dmg"), external: true },
|
||||
{ label: "Intel Mac", href: findAsset("macos", "macos-x64-dmg"), external: true },
|
||||
{ label: "Windows Installer", href: findAsset("windows", "windows-universal-installer"), external: true },
|
||||
{ label: "Linux x64 AppImage", href: findAsset("linux", "linux-x64-appimage"), external: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
links: [
|
||||
{ label: "Website", href: WEBSITE_URL, external: true },
|
||||
{ label: "Contact", href: `mailto:${CONTACT_EMAIL}`, external: true },
|
||||
{ label: "Download Center", href: "#download" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function Footer() {
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="relative border-t border-[var(--border)] overflow-hidden">
|
||||
<AsciiBackground cssVar="--accent" baseOpacity={0.04} speed={0.5} />
|
||||
|
||||
<div className="relative z-10 mx-auto max-w-6xl px-6 lg:px-8 pt-16 lg:pt-20 pb-10 lg:pb-12">
|
||||
<div className="flex flex-col lg:flex-row gap-y-12 gap-x-8">
|
||||
<div className="shrink-0 lg:w-64 mb-4 lg:mb-0">
|
||||
<a href="#" className="inline-flex items-center gap-2.5" aria-label="AgentCompany home">
|
||||
<IconMark size={28} />
|
||||
<span className="text-[var(--text)] font-semibold text-lg tracking-tight">AgentCompany</span>
|
||||
</a>
|
||||
<p className="mt-4 text-sm leading-relaxed text-[var(--muted)] max-w-xs">
|
||||
Local-first desktop control plane for autonomous AI companies.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-wrap gap-y-12 gap-x-8 justify-between">
|
||||
{footerGroups.map((group) => (
|
||||
<div key={group.title} className="min-w-[120px]">
|
||||
<h3 className="text-sm font-semibold text-[var(--text)] tracking-tight mb-4">{group.title}</h3>
|
||||
<ul className="space-y-3">
|
||||
{group.links.map((link) => (
|
||||
<li key={link.label}>
|
||||
<a
|
||||
href={link.href}
|
||||
{...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
|
||||
className="text-sm text-[var(--muted)] hover:text-[var(--text)] transition-colors duration-200"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-14 lg:mt-16 pt-6 border-t border-[var(--border)]" />
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex items-center gap-4 text-xs text-[var(--muted)]">
|
||||
<span>© {year} AgentCompany</span>
|
||||
<span className="hidden sm:inline-flex items-center gap-1.5">
|
||||
Cloudflare-hosted desktop builds
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { AsciiBackground } from "./ascii-background";
|
||||
|
||||
const sharedEase: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
||||
|
||||
function heroMotion(delay: number) {
|
||||
return {
|
||||
initial: { opacity: 0, y: 24 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.7, delay, ease: sharedEase },
|
||||
};
|
||||
}
|
||||
|
||||
function AppleIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function WindowsIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M3 12V6.5l8-1.1V12H3zm0 .5h8v6.6l-8-1.1V12.5zM11.5 5.3l9.5-1.3v8h-9.5V5.3zm0 7.2h9.5v8l-9.5-1.3V12.5z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function LinuxIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12.5 2C10 2 8.2 4.2 8.2 7c0 1.5.3 2.2.7 3.2.3.8.7 1.7.7 3 0 .3 0 .6-.1.9-.2 1-.6 1.8-1.2 2.3-.8.7-1.8 1.1-2.8 1.6-.7.3-1.4.7-1.9 1.2-.4.4-.6.9-.6 1.4 0 .5.2.9.6 1.2.7.6 2 .7 3.1.7.8 0 1.6-.1 2.2-.2.6-.1 1.2-.3 1.9-.3.7 0 1.3.2 1.9.3.6.1 1.4.2 2.2.2 1.1 0 2.4-.1 3.1-.7.4-.3.6-.7.6-1.2 0-.5-.2-1-.6-1.4-.5-.5-1.2-.9-1.9-1.2-1-.5-2-1-2.8-1.6-.6-.5-1-1.3-1.2-2.3-.1-.3-.1-.6-.1-.9 0-1.3.4-2.2.7-3 .4-1 .7-1.7.7-3.2 0-2.8-1.8-5-4.3-5h-.2zm-1 5c0-.8.5-1.5 1.2-1.5s1.2.7 1.2 1.5-.5 1.5-1.2 1.5S11.5 7.8 11.5 7z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative overflow-hidden pt-32 pb-24 lg:pt-44 lg:pb-36">
|
||||
<AsciiBackground cssVar="--accent" baseOpacity={0.06} speed={1} />
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse 80% 50% at 50% 0%, var(--glow-warm) 0%, transparent 70%), radial-gradient(ellipse 60% 40% at 50% 20%, var(--glow-accent) 0%, transparent 60%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<motion.div {...heroMotion(0.05)}>
|
||||
<span className="inline-flex items-center px-4 py-1.5 rounded-full text-xs font-semibold tracking-wide bg-[var(--accent-soft)] text-[var(--accent)] dark:text-[var(--muted-strong)] border border-[var(--border)]">
|
||||
Local-First · Desktop App
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
{...heroMotion(0.15)}
|
||||
className="mt-8 font-serif italic text-[clamp(2.75rem,2rem+3.8vw,5rem)] tracking-[-0.02em] text-[var(--text)] leading-[1.08]"
|
||||
>
|
||||
Launch an AI Company <br className="hidden sm:block" />
|
||||
<span className="text-[var(--cta)]">in Minutes</span>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
{...heroMotion(0.25)}
|
||||
className="mt-7 max-w-2xl text-lg sm:text-xl leading-relaxed text-[var(--muted)]"
|
||||
>
|
||||
Describe your company's mission. A CEO agent recruits the team, delegates work, and delivers real
|
||||
results — code, content, research, and more. You're the founder.
|
||||
</motion.p>
|
||||
|
||||
<motion.div {...heroMotion(0.35)} className="mt-12">
|
||||
<a
|
||||
href="#download"
|
||||
className="group inline-flex items-center justify-center px-9 py-4 rounded-xl text-[17px] font-semibold bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)] hover:scale-[1.03] active:scale-[0.98] transition-all duration-200 will-change-transform shadow-[0_4px_24px_-4px_var(--cta),0_8px_40px_-12px_var(--cta)]"
|
||||
>
|
||||
See Downloads
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
className="ml-2 transition-transform duration-200 group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
|
||||
>
|
||||
<path d="m7 7 10 10" />
|
||||
<path d="M17 7v10H7" />
|
||||
</svg>
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
<motion.div {...heroMotion(0.45)} className="mt-5 flex items-center gap-3 text-xs text-[var(--muted)]">
|
||||
<div className="flex items-center gap-1.5 opacity-70">
|
||||
<AppleIcon />
|
||||
<WindowsIcon />
|
||||
<LinuxIcon />
|
||||
</div>
|
||||
<span>Available on macOS, Windows & Linux</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40, scale: 0.94 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{ duration: 0.9, delay: 0.5, ease: sharedEase }}
|
||||
className="mt-20 lg:mt-24 mx-auto max-w-5xl"
|
||||
>
|
||||
<img
|
||||
src="/screenshots/Dashboard.jpeg"
|
||||
alt="AgentCompany dashboard showing autonomous AI agents managing a company"
|
||||
width="1920"
|
||||
height="1080"
|
||||
className="w-full h-auto block rounded-2xl border border-[var(--border)] shadow-[0_8px_40px_-8px_rgba(0,0,0,0.12),0_20px_80px_-20px_var(--glow-warm)] transition-shadow duration-500"
|
||||
loading="eager"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const demoMessages = [
|
||||
{ agent: "You", role: "system", text: "Build a SaaS landing page with pricing, blog, and auth.", delay: 0 },
|
||||
{ agent: "CEO", role: "ceo", text: "Analyzing goal... Creating departments: Engineering, Design, Content.", delay: 800 },
|
||||
{ agent: "CEO", role: "ceo", text: "Hiring 4 specialists for this mission.", delay: 1600 },
|
||||
{ agent: "Lead Engineer", role: "engineer", text: "Picked up task: Set up Next.js project with auth routes.", delay: 2400 },
|
||||
{ agent: "UI Designer", role: "designer", text: "Picked up task: Design landing page and pricing components.", delay: 3000 },
|
||||
{ agent: "Researcher", role: "researcher", text: "Picked up task: Competitive analysis of SaaS pricing models.", delay: 3600 },
|
||||
{ agent: "Lead Engineer", role: "engineer", text: "Completed: Auth scaffolding with JWT + session middleware.", delay: 4400 },
|
||||
{ agent: "UI Designer", role: "designer", text: "Completed: Responsive landing page with 3-tier pricing grid.", delay: 5200 },
|
||||
{ agent: "CEO", role: "ceo", text: "All tasks delivered. 3 documents ready for review.", delay: 6000 },
|
||||
];
|
||||
|
||||
const logTone: Record<(typeof demoMessages)[number]["role"], string> = {
|
||||
system: "text-[var(--muted-strong)]",
|
||||
ceo: "text-[#ca8a04]",
|
||||
engineer: "text-[var(--accent)]",
|
||||
designer: "text-[#8b5cf6]",
|
||||
researcher: "text-[#059669]",
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{ number: 1, title: "Set Your Goal", description: "Describe the company, its mission, and what you want delivered." },
|
||||
{ number: 2, title: "Agents Self-Organize", description: "A CEO analyzes your goal, hires specialists, and delegates the work." },
|
||||
{ number: 3, title: "Collect Results", description: "Review deliverables, approve decisions, and download finished work." },
|
||||
];
|
||||
|
||||
function DemoPanel() {
|
||||
const anchorRef = useRef<HTMLDivElement | null>(null);
|
||||
const isVisible = useInView(anchorRef, { once: true, amount: 0.4 });
|
||||
const [visibleCount, setVisibleCount] = useState(0);
|
||||
const logRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisible) return;
|
||||
|
||||
const timers = demoMessages.map((message, index) =>
|
||||
window.setTimeout(() => setVisibleCount(index + 1), message.delay),
|
||||
);
|
||||
|
||||
return () => timers.forEach((timer) => window.clearTimeout(timer));
|
||||
}, [isVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logRef.current) {
|
||||
logRef.current.scrollTop = logRef.current.scrollHeight;
|
||||
}
|
||||
}, [visibleCount]);
|
||||
|
||||
return (
|
||||
<div ref={anchorRef} className="relative">
|
||||
<div className="rounded-2xl overflow-hidden border border-[var(--border)] bg-[var(--panel)] shadow-[0_8px_40px_-8px_rgba(0,0,0,0.1),0_24px_80px_-24px_var(--glow-warm)]">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-[var(--panel-soft)] border-b border-[var(--border)]">
|
||||
<span className="w-3 h-3 rounded-full bg-[#ff5f57]" aria-hidden="true" />
|
||||
<span className="w-3 h-3 rounded-full bg-[#febc2e]" aria-hidden="true" />
|
||||
<span className="w-3 h-3 rounded-full bg-[#28c840]" aria-hidden="true" />
|
||||
<span className="ml-2 text-xs text-[var(--muted)] font-mono">AgentCompany</span>
|
||||
</div>
|
||||
|
||||
<div ref={logRef} role="log" aria-label="Agent activity demonstration" className="p-5 lg:p-6 font-mono text-sm leading-relaxed h-[340px] overflow-y-auto">
|
||||
{demoMessages.slice(0, visibleCount).map((message, index) => (
|
||||
<motion.div
|
||||
key={`${message.agent}-${index}`}
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="flex gap-3 mb-3 last:mb-0"
|
||||
>
|
||||
<span className={`shrink-0 font-semibold inline-flex items-center gap-1.5 ${logTone[message.role]}`}>
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${
|
||||
message.role === "system"
|
||||
? "bg-[var(--muted-strong)]"
|
||||
: message.role === "ceo"
|
||||
? "bg-[#ca8a04]"
|
||||
: message.role === "engineer"
|
||||
? "bg-[var(--accent)]"
|
||||
: message.role === "designer"
|
||||
? "bg-[#8b5cf6]"
|
||||
: "bg-[#059669]"
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{message.agent}
|
||||
</span>
|
||||
<span className="text-[var(--text)]">{message.text}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{visibleCount < demoMessages.length && isVisible && (
|
||||
<span className="inline-block w-2 h-4 bg-[var(--accent)] animate-pulse rounded-sm" aria-hidden="true" />
|
||||
)}
|
||||
|
||||
{visibleCount >= demoMessages.length && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="mt-4 pt-4 border-t border-[var(--border)] flex items-center gap-2 text-[#059669]"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
</svg>
|
||||
<span className="font-semibold">Company run complete. All deliverables ready.</span>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function HowItWorks() {
|
||||
return (
|
||||
<section id="how-it-works" className="py-24 lg:py-40 px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="text-center mb-20 lg:mb-24">
|
||||
<p className="font-mono text-xs font-medium uppercase tracking-[0.25em] text-[var(--accent)] mb-5">
|
||||
How It Works
|
||||
</p>
|
||||
<h2 className="font-serif text-[clamp(2rem,1.5rem+2.5vw,3.25rem)] tracking-[-0.01em] text-[var(--text)]">
|
||||
Watch a Company Come to Life
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-12 lg:gap-16 items-start">
|
||||
<div className="lg:col-span-2 relative">
|
||||
<div className="absolute left-[15px] top-10 bottom-4 w-px bg-[var(--border)] hidden lg:block" aria-hidden="true" />
|
||||
|
||||
<div className="space-y-10">
|
||||
{steps.map((step, index) => (
|
||||
<motion.div
|
||||
key={step.number}
|
||||
initial={{ opacity: 0, x: -24 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, amount: 0.5 }}
|
||||
transition={{ duration: 0.5, delay: 0.12 * index, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="flex gap-5"
|
||||
>
|
||||
<div className="relative z-10 shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-[var(--accent)] text-[var(--text-on-accent)] text-sm font-bold shadow-[0_2px_12px_-2px_var(--accent)]">
|
||||
{step.number}
|
||||
</div>
|
||||
<div className="pt-0.5">
|
||||
<h3 className="text-base font-bold text-[var(--text)] mb-1.5 tracking-tight">{step.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-[var(--muted)]">{step.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="lg:col-span-3"
|
||||
initial={{ opacity: 0, y: 32 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<DemoPanel />
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export function IconMark({ size = 32 }: { size?: number }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 1024 1024"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="512" cy="512" r="480" fill="#191919" />
|
||||
<g transform="translate(268 255) scale(4)">
|
||||
<path
|
||||
fill="#FBFAF5"
|
||||
d="M 76.604813 111.661926 C 65.070183 111.662621 53.987053 111.662621 42.757744 111.662621 C 45.2239 101.998177 45.297546 101.949265 53.830105 102.036659 C 57.279823 102.071999 60.730202 102.04248 64.259201 102.04248 C 62.159458 93.316338 61.799103 93.009819 51.340881 91.341682 C 59.312901 73.847687 67.193756 56.553749 75.587578 38.134155 C 86.933441 62.965954 97.804642 86.758881 108.711754 110.630394 C 100.707413 111.871689 92.355965 107.246559 88.107536 98.852119 C 84.207748 91.146538 80.800591 83.191696 77.172813 75.348343 C 76.340195 73.54818 75.492958 71.754791 74.614624 69.877441 C 69.107384 75.691788 68.008286 81.475128 71.005882 88.088753 C 74.413452 95.606903 77.813919 103.128281 81.452705 111.167984 C 79.55114 111.381325 78.303734 111.521271 76.604813 111.661926 Z"
|
||||
/>
|
||||
<path
|
||||
fill="#FBFAF5"
|
||||
d="M 26.805992 87.743256 C 38.616821 61.830872 50.261688 36.260269 62.470131 9.452141 C 65.732315 17.818909 71.145126 23.524567 66.258224 33.086182 C 55.518253 54.099815 46.339973 75.911873 36.533691 97.402336 C 32.699284 105.805443 26.495394 110.823158 16.012421 111.410957 C 19.72117 103.270836 23.180601 95.67794 26.805992 87.743256 Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconMark } from "./icon-mark";
|
||||
import { ThemeToggle } from "./theme-toggle";
|
||||
|
||||
const navLinks = [
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "How It Works", href: "#how-it-works" },
|
||||
{ label: "FAQ", href: "#faq" },
|
||||
{ label: "Download", href: "#download" },
|
||||
];
|
||||
|
||||
function MenuIcon({ open }: { open: boolean }) {
|
||||
return (
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
{open ? (
|
||||
<>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<line x1="3" y1="6" x2="21" y2="6" />
|
||||
<line x1="3" y1="12" x2="21" y2="12" />
|
||||
<line x1="3" y1="18" x2="21" y2="18" />
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Navbar() {
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setScrolled(window.scrollY > 8);
|
||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||
handleScroll();
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (window.innerWidth >= 768) {
|
||||
setMobileOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize, { passive: true });
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={[
|
||||
"fixed top-0 left-0 right-0 z-50 transition-all duration-300 backdrop-blur-xl",
|
||||
scrolled ? "bg-[var(--panel)]/80 border-b border-[var(--border)] shadow-sm" : "bg-transparent border-b border-transparent",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex h-16 items-center justify-between">
|
||||
<a href="#" className="flex items-center gap-2.5 shrink-0" aria-label="AgentCompany home">
|
||||
<IconMark />
|
||||
<span className="text-[var(--text)] font-semibold text-lg tracking-tight">AgentCompany</span>
|
||||
</a>
|
||||
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map(({ label, href }) => (
|
||||
<a key={href} href={href} className="text-sm text-[var(--muted)] hover:text-[var(--text)] transition-colors duration-200">
|
||||
{label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex items-center gap-3">
|
||||
<ThemeToggle />
|
||||
<a
|
||||
href="#download"
|
||||
className="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)] hover:scale-[1.03] active:scale-[0.98] transition-all duration-200"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex md:hidden items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileOpen((current) => !current)}
|
||||
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
|
||||
aria-expanded={mobileOpen}
|
||||
aria-controls="mobile-nav-menu"
|
||||
className="inline-flex items-center justify-center w-11 h-11 rounded-lg text-[var(--muted)] hover:text-[var(--text)] hover:bg-[var(--panel-soft)] transition-colors duration-200"
|
||||
>
|
||||
<MenuIcon open={mobileOpen} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="mobile-nav-menu"
|
||||
className={[
|
||||
"md:hidden overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]",
|
||||
mobileOpen ? "max-h-64 opacity-100" : "max-h-0 opacity-0",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="px-4 pb-4 pt-1 space-y-1 bg-[var(--panel)]/90 backdrop-blur-xl border-b border-[var(--border)]">
|
||||
{navLinks.map(({ label, href }) => (
|
||||
<a
|
||||
key={href}
|
||||
href={href}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="block px-3 py-2.5 rounded-lg text-sm text-[var(--muted)] hover:text-[var(--text)] hover:bg-[var(--panel-soft)] transition-colors duration-200"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
))}
|
||||
<a
|
||||
href="#download"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="block px-3 py-2.5 rounded-lg text-sm font-medium text-center bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)] transition-colors duration-200 mt-2"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
export type PlatformDownloadAsset = {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
note: string;
|
||||
};
|
||||
|
||||
export type PlatformDownload = {
|
||||
id: "macos" | "windows" | "linux";
|
||||
title: string;
|
||||
note: string;
|
||||
assets: PlatformDownloadAsset[];
|
||||
};
|
||||
|
||||
export const releaseVersion = "0.4.150";
|
||||
|
||||
export const platformDownloads: PlatformDownload[] = [
|
||||
{
|
||||
id: "macos",
|
||||
title: "macOS",
|
||||
note: "Signed + notarized DMG builds",
|
||||
assets: [
|
||||
{
|
||||
id: "macos-arm64-dmg",
|
||||
label: "Apple Silicon DMG",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/AgentCompany-0.4.150-arm64.dmg",
|
||||
note: "arm64",
|
||||
},
|
||||
{
|
||||
id: "macos-x64-dmg",
|
||||
label: "Intel Mac DMG",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/AgentCompany-0.4.150.dmg",
|
||||
note: "x64",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "windows",
|
||||
title: "Windows",
|
||||
note: "NSIS installer builds",
|
||||
assets: [
|
||||
{
|
||||
id: "windows-universal-installer",
|
||||
label: "Windows Universal Installer",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/AgentCompany.Setup.0.4.150.exe",
|
||||
note: "arm64 + x64 + ia32",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "linux",
|
||||
title: "Linux",
|
||||
note: "AppImage, DEB, and RPM builds",
|
||||
assets: [
|
||||
{
|
||||
id: "linux-x64-appimage",
|
||||
label: "Linux x64 AppImage",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/AgentCompany-0.4.150.AppImage",
|
||||
note: "x64",
|
||||
},
|
||||
{
|
||||
id: "linux-x64-deb",
|
||||
label: "Linux x64 DEB",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/agent-company_0.4.150_amd64.deb",
|
||||
note: "x64",
|
||||
},
|
||||
{
|
||||
id: "linux-x64-rpm",
|
||||
label: "Linux x64 RPM",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/agent-company-0.4.150.x86_64.rpm",
|
||||
note: "x64",
|
||||
},
|
||||
{
|
||||
id: "linux-arm64-appimage",
|
||||
label: "Linux ARM64 AppImage",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/AgentCompany-0.4.150-arm64.AppImage",
|
||||
note: "arm64",
|
||||
},
|
||||
{
|
||||
id: "linux-arm64-deb",
|
||||
label: "Linux ARM64 DEB",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/agent-company_0.4.150_arm64.deb",
|
||||
note: "arm64",
|
||||
},
|
||||
{
|
||||
id: "linux-arm64-rpm",
|
||||
label: "Linux ARM64 RPM",
|
||||
href: "https://pub-a19a3a2c1d2e4fa3a9c120b184024448.r2.dev/AC/agent-company-0.4.150.aarch64.rpm",
|
||||
note: "arm64",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,2 @@
|
||||
export const WEBSITE_URL = "https://agent-company.dev";
|
||||
export const CONTACT_EMAIL = "dev@agentcompany.ai";
|
||||
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
|
||||
const storageKey = "agentcompany-theme";
|
||||
|
||||
type ThemeMode = "light" | "dark";
|
||||
|
||||
type ThemeContextValue = {
|
||||
theme: ThemeMode;
|
||||
setTheme: (theme: ThemeMode) => void;
|
||||
toggleTheme: () => void;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
||||
|
||||
function resolveTheme(): ThemeMode {
|
||||
if (typeof window === "undefined") {
|
||||
return "light";
|
||||
}
|
||||
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
if (stored === "light" || stored === "dark") {
|
||||
return stored;
|
||||
}
|
||||
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setThemeState] = useState<ThemeMode>(resolveTheme);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (theme === "dark") {
|
||||
root.classList.add("dark");
|
||||
} else {
|
||||
root.classList.remove("dark");
|
||||
}
|
||||
localStorage.setItem(storageKey, theme);
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const handleChange = (event: MediaQueryListEvent) => {
|
||||
if (!localStorage.getItem(storageKey)) {
|
||||
setThemeState(event.matches ? "dark" : "light");
|
||||
}
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener("change", handleChange);
|
||||
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||
}, []);
|
||||
|
||||
const setTheme = useCallback((nextTheme: ThemeMode) => {
|
||||
setThemeState(nextTheme);
|
||||
}, []);
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
setThemeState((currentTheme) => (currentTheme === "light" ? "dark" : "light"));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "./theme-provider";
|
||||
|
||||
function SunIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function MoonIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleTheme}
|
||||
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
|
||||
className="relative inline-flex items-center justify-center w-11 h-11 rounded-lg transition-colors duration-200 cursor-pointer hover:bg-[var(--panel-soft)] text-[var(--muted)] hover:text-[var(--text)]"
|
||||
>
|
||||
{theme === "light" ? <MoonIcon /> : <SunIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user