feat(web): add about, changelog pages and fix landing header for light backgrounds

- Rewrite about page with Multica name origin (Multics → multiplexed
  agents) and project philosophy
- Replace placeholder changelog with real entries from git history
  (v0.1.0–v0.1.3)
- Add variant prop to LandingHeader (dark/light) so it renders
  correctly on white-background subpages
- Extract landing page into separate component files
This commit is contained in:
Jiayuan 2026-04-01 05:16:24 +08:00
parent e9ce376b96
commit 0b4c6b3910
17 changed files with 1980 additions and 929 deletions

View file

@ -0,0 +1,72 @@
import Link from "next/link";
import { LandingHeader } from "@/features/landing/components/landing-header";
import { LandingFooter } from "@/features/landing/components/landing-footer";
import { GitHubMark, githubUrl } from "@/features/landing/components/shared";
export default function AboutPage() {
return (
<>
<LandingHeader variant="light" />
<main className="bg-white text-[#0a0d12]">
<div className="mx-auto max-w-[720px] px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
<h1 className="font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem]">
About Multica
</h1>
<div className="mt-8 space-y-6 text-[15px] leading-[1.8] text-[#0a0d12]/70 sm:text-[16px]">
<p>
Multica <strong className="font-semibold text-[#0a0d12]">Mul</strong>tiplexed
Information and{" "}
<strong className="font-semibold text-[#0a0d12]">C</strong>omputing{" "}
<strong className="font-semibold text-[#0a0d12]">A</strong>gent.
</p>
<p>
The name is a nod to Multics, the pioneering operating system of
the 1960s that introduced time-sharing letting multiple users
share a single machine as if each had it to themselves. Unix was
born as a deliberate simplification of Multics: one user, one task,
one elegant philosophy.
</p>
<p>
We think the same inflection is happening again. For decades,
software teams have been single-threaded one engineer, one task,
one context switch at a time. AI agents change that equation.
Multica brings time-sharing back, but for an era where the
&ldquo;users&rdquo; multiplexing the system are both humans and
autonomous agents.
</p>
<p>
In Multica, agents are first-class teammates. They get assigned
issues, report progress, raise blockers, and ship code just like
their human colleagues. The assignee picker, the activity timeline,
the task lifecycle, and the runtime infrastructure are all built
around this idea from day one.
</p>
<p>
Like Multics before it, the bet is on multiplexing: a small team
shouldn&apos;t feel small. With the right system, two engineers and
a fleet of agents can move like twenty.
</p>
<p>
The platform is fully open source and self-hostable. Your data
stays on your infrastructure. Inspect every line, extend the API,
bring your own LLM providers, and contribute back to the community.
</p>
</div>
<div className="mt-12">
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2.5 rounded-[12px] bg-[#0a0d12] px-5 py-3 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0d12]/88"
>
<GitHubMark className="size-4" />
View on GitHub
</Link>
</div>
</div>
</main>
<LandingFooter />
</>
);
}

View file

@ -0,0 +1,110 @@
import { LandingHeader } from "@/features/landing/components/landing-header";
import { LandingFooter } from "@/features/landing/components/landing-footer";
const changelog = [
{
version: "0.1.3",
date: "2026-03-31",
title: "Agent Intelligence",
changes: [
"Trigger agents via @mention in comments",
"Stream live agent output to issue detail page",
"Rich text editor — mentions, link paste, emoji reactions, collapsible threads",
"File upload with S3 + CloudFront signed URLs and attachment tracking",
"Agent-driven repo checkout with bare clone cache for task isolation",
"Batch operations for issue list view",
"Daemon authentication and security hardening",
],
},
{
version: "0.1.2",
date: "2026-03-28",
title: "Collaboration",
changes: [
"Email verification login and browser-based CLI auth",
"Multi-workspace daemon with hot-reload",
"Runtime dashboard with usage charts and activity heatmaps",
"Subscriber-driven notification model replacing hardcoded triggers",
"Unified activity timeline with threaded comment replies",
"Kanban board redesign with drag sorting, filters, and display settings",
"Human-readable issue identifiers (e.g. JIA-1)",
"Skill import from ClawHub and Skills.sh",
],
},
{
version: "0.1.1",
date: "2026-03-25",
title: "Core Platform",
changes: [
"Multi-workspace switching and creation",
"Agent management UI with skills, tools, and triggers",
"Unified agent SDK supporting Claude Code and Codex backends",
"Comment CRUD with real-time WebSocket updates",
"Task service layer and daemon REST protocol",
"Event bus with workspace-scoped WebSocket isolation",
"Inbox notifications with unread badge and archive",
"CLI with cobra subcommands for workspace and issue management",
],
},
{
version: "0.1.0",
date: "2026-03-22",
title: "Foundation",
changes: [
"Go backend with REST API, JWT auth, and real-time WebSocket",
"Next.js frontend with Linear-inspired UI",
"Issues with board and list views and drag-and-drop kanban",
"Agents, Inbox, and Settings pages",
"One-click setup, migration CLI, and seed tool",
"Comprehensive test suite — Go unit/integration, Vitest, Playwright E2E",
],
},
];
export default function ChangelogPage() {
return (
<>
<LandingHeader variant="light" />
<main className="bg-white text-[#0a0d12]">
<div className="mx-auto max-w-[720px] px-4 py-16 sm:px-6 sm:py-20 lg:py-24">
<h1 className="font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem]">
Changelog
</h1>
<p className="mt-4 text-[15px] leading-7 text-[#0a0d12]/60 sm:text-[16px]">
New updates and improvements to Multica.
</p>
<div className="mt-16 space-y-16">
{changelog.map((release) => (
<div key={release.version} className="relative">
<div className="flex items-baseline gap-3">
<span className="text-[13px] font-semibold tabular-nums">
v{release.version}
</span>
<span className="text-[13px] text-[#0a0d12]/40">
{release.date}
</span>
</div>
<h2 className="mt-2 text-[20px] font-semibold leading-snug sm:text-[22px]">
{release.title}
</h2>
<ul className="mt-4 space-y-2">
{release.changes.map((change) => (
<li
key={change}
className="flex items-start gap-2.5 text-[14px] leading-[1.7] text-[#0a0d12]/60 sm:text-[15px]"
>
<span className="mt-2.5 h-1 w-1 shrink-0 rounded-full bg-[#0a0d12]/30" />
{change}
</li>
))}
</ul>
</div>
))}
</div>
</div>
</main>
<LandingFooter />
</>
);
}

View file

@ -0,0 +1,102 @@
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";
const faqs = [
{
question: "What coding agents does Multica support?",
answer:
"Multica currently supports Claude Code and OpenAI Codex out of the box. The daemon auto-detects whichever CLIs you have installed. More backends are on the roadmap — and since it's open source, you can add your own.",
},
{
question: "Do I need to self-host, or is there a cloud version?",
answer:
"Both. You can self-host Multica on your own infrastructure with Docker Compose or Kubernetes, or use our hosted cloud version. Your data, your choice.",
},
{
question:
"How is this different from just using Claude Code or Codex directly?",
answer:
"Coding agents are great at executing. Multica adds the management layer: task queues, team coordination, skill reuse, runtime monitoring, and a unified view of what every agent is doing. Think of it as the project manager for your agents.",
},
{
question: "Can agents work on long-running tasks autonomously?",
answer:
"Yes. Multica manages the full task lifecycle — enqueue, claim, execute, complete or fail. Agents report blockers proactively and stream progress in real time. You can check in whenever you want or let them run overnight.",
},
{
question: "Is my code safe? Where does agent execution happen?",
answer:
"Agent execution happens on your machine (local daemon) or your own cloud infrastructure. Code never passes through Multica servers. The platform only coordinates task state and broadcasts events.",
},
{
question: "How many agents can I run?",
answer:
"As many as your hardware supports. Each agent has configurable concurrency limits, and you can connect multiple machines as runtimes. There are no artificial caps in the open source version.",
},
];
export function FAQSection() {
const [openIndex, setOpenIndex] = useState<number | null>(null);
return (
<section id="faq" className="bg-[#f8f8f8] text-[#0a0d12]">
<div className="mx-auto max-w-[860px] px-4 py-24 sm:px-6 sm:py-32 lg:py-40">
<div className="text-center">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#0a0d12]/40">
FAQ
</p>
<h2 className="mt-4 font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem] lg:text-[4.2rem]">
Questions & answers.
</h2>
</div>
<div className="mt-14 divide-y divide-[#0a0d12]/10 sm:mt-16">
{faqs.map((faq, i) => (
<div key={faq.question}>
<button
onClick={() => setOpenIndex(openIndex === i ? null : i)}
className="flex w-full items-start justify-between gap-4 py-6 text-left"
>
<span className="text-[16px] font-semibold leading-snug text-[#0a0d12] sm:text-[17px]">
{faq.question}
</span>
<span
className={cn(
"mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border border-[#0a0d12]/12 text-[#0a0d12]/40 transition-transform",
openIndex === i && "rotate-45",
)}
>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
>
<path d="M6 1v10M1 6h10" />
</svg>
</span>
</button>
<div
className={cn(
"grid transition-[grid-template-rows] duration-200 ease-out",
openIndex === i ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
)}
>
<div className="overflow-hidden">
<p className="pb-6 pr-12 text-[14px] leading-[1.7] text-[#0a0d12]/56 sm:text-[15px]">
{faq.answer}
</p>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,80 @@
import Link from "next/link";
import { GitHubMark, githubUrl, heroButtonClassName } from "./shared";
const steps = [
{
number: "01",
title: "Sign up & create your workspace",
description:
"Enter your email, verify with a code, and you're in. Your workspace is created automatically — no setup wizard, no configuration forms.",
},
{
number: "02",
title: "Install the CLI & connect your machine",
description:
"Run multica login to authenticate, then multica daemon start. The daemon auto-detects Claude Code and Codex on your machine — plug in and go.",
},
{
number: "03",
title: "Create your first agent",
description:
"Give it a name, write instructions, attach skills, and set triggers. Choose when it activates: on assignment, on comment, or on mention.",
},
{
number: "04",
title: "Assign an issue and watch it work",
description:
"Pick your agent from the assignee dropdown — just like assigning to a teammate. The task is queued, claimed, and executed automatically. Watch progress in real time.",
},
];
export function HowItWorksSection() {
return (
<section id="how-it-works" className="bg-[#05070b] text-white">
<div className="mx-auto max-w-[1320px] px-4 py-24 sm:px-6 sm:py-32 lg:px-8 lg:py-40">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-white/40">
Get started
</p>
<h2 className="mt-4 font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem] lg:text-[4.2rem]">
Hire your first AI employee
<br />
<span className="text-white/40">in the next hour.</span>
</h2>
<div className="mt-20 grid gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10 sm:grid-cols-2 lg:grid-cols-4">
{steps.map((step) => (
<div
key={step.number}
className="flex flex-col bg-[#05070b] p-8 lg:p-10"
>
<span className="text-[13px] font-semibold tabular-nums text-white/28">
{step.number}
</span>
<h3 className="mt-4 text-[17px] font-semibold leading-snug text-white sm:text-[18px]">
{step.title}
</h3>
<p className="mt-3 text-[14px] leading-[1.7] text-white/50 sm:text-[15px]">
{step.description}
</p>
</div>
))}
</div>
<div className="mt-14 flex flex-wrap items-center gap-4">
<Link href="/login" className={heroButtonClassName("solid")}>
Get started
</Link>
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={heroButtonClassName("ghost")}
>
<GitHubMark className="size-4" />
View on GitHub
</Link>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,103 @@
"use client";
import Link from "next/link";
import { MulticaIcon } from "@/components/multica-icon";
import { githubUrl } from "./shared";
const footerLinks = {
Product: [
{ label: "Features", href: "#features" },
{ label: "How it Works", href: "#how-it-works" },
{ label: "Changelog", href: "/changelog" },
],
Resources: [
{ label: "Documentation", href: githubUrl },
{ label: "API", href: githubUrl },
{ label: "Community", href: githubUrl },
],
Company: [
{ label: "About", href: "/about" },
{ label: "Open Source", href: "#open-source" },
{ label: "GitHub", href: githubUrl },
],
};
export function LandingFooter() {
return (
<footer className="bg-[#0a0d12] text-white">
<div className="mx-auto max-w-[1320px] px-4 sm:px-6 lg:px-8">
{/* Top: CTA + link columns */}
<div className="flex flex-col gap-12 border-b border-white/10 py-16 sm:py-20 lg:flex-row lg:gap-20">
{/* Left — newsletter / CTA */}
<div className="lg:w-[340px] lg:shrink-0">
<Link href="#product" className="flex items-center gap-3">
<MulticaIcon className="size-5 text-white" noSpin />
<span className="text-[18px] font-semibold tracking-[0.04em] lowercase">
multica
</span>
</Link>
<p className="mt-4 max-w-[300px] text-[14px] leading-[1.7] text-white/50 sm:text-[15px]">
Project management for human + agent teams. Open source,
self-hostable, built for the future of work.
</p>
<div className="mt-6">
<Link
href="/login"
className="inline-flex items-center justify-center rounded-[11px] bg-white px-5 py-2.5 text-[13px] font-semibold text-[#0a0d12] transition-colors hover:bg-white/88"
>
Get started
</Link>
</div>
</div>
{/* Right — link columns */}
<div className="grid flex-1 grid-cols-2 gap-8 sm:grid-cols-4">
{Object.entries(footerLinks).map(([group, links]) => (
<div key={group}>
<h4 className="text-[12px] font-semibold uppercase tracking-[0.1em] text-white/40">
{group}
</h4>
<ul className="mt-4 flex flex-col gap-2.5">
{links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
{...(link.href.startsWith("http")
? { target: "_blank", rel: "noreferrer" }
: {})}
className="text-[14px] text-white/50 transition-colors hover:text-white"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
</div>
{/* Bottom: copyright */}
<div className="flex items-center justify-between py-6">
<p className="text-[13px] text-white/36">
&copy; {new Date().getFullYear()} Multica. All rights reserved.
</p>
</div>
{/* Giant logo */}
<div className="relative overflow-hidden pb-4">
<div className="flex items-end gap-6 sm:gap-8">
<MulticaIcon
className="size-[clamp(4rem,12vw,10rem)] shrink-0 text-white"
noSpin
/>
<span className="font-[family-name:var(--font-serif)] text-[clamp(6rem,22vw,16rem)] font-normal leading-[0.82] tracking-[-0.04em] text-white lowercase">
multica
</span>
</div>
</div>
</div>
</footer>
);
}

View file

@ -0,0 +1,59 @@
"use client";
import Link from "next/link";
import { MulticaIcon } from "@/components/multica-icon";
import { cn } from "@/lib/utils";
import { GitHubMark, githubUrl, headerButtonClassName } from "./shared";
export function LandingHeader({
variant = "dark",
}: {
variant?: "dark" | "light";
}) {
return (
<header
className={cn(
"inset-x-0 top-0 z-30",
variant === "dark" ? "absolute bg-transparent" : "border-b border-[#0a0d12]/8 bg-white",
)}
>
<div className="mx-auto flex h-[76px] max-w-[1320px] items-center justify-between px-4 sm:px-6 lg:px-8">
<Link href="/" className="flex items-center gap-3">
<MulticaIcon
className={cn(
"size-5",
variant === "dark" ? "text-white" : "text-[#0a0d12]",
)}
noSpin
/>
<span
className={cn(
"text-[18px] font-semibold tracking-[0.04em] lowercase sm:text-[20px]",
variant === "dark" ? "text-white/92" : "text-[#0a0d12]",
)}
>
multica
</span>
</Link>
<div className="flex items-center gap-2.5 sm:gap-3">
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={headerButtonClassName("ghost", variant)}
>
<GitHubMark className="size-3.5" />
GitHub
</Link>
<Link
href="/login"
className={headerButtonClassName("solid", variant)}
>
Log in
</Link>
</div>
</div>
</header>
);
}

View file

@ -0,0 +1,100 @@
import Image from "next/image";
import Link from "next/link";
import {
ClaudeCodeLogo,
CodexLogo,
GitHubMark,
githubUrl,
heroButtonClassName,
} from "./shared";
export function LandingHero() {
return (
<div className="relative min-h-full overflow-hidden bg-[#05070b] text-white">
<LandingBackdrop />
<main className="relative z-10">
<section
id="product"
className="mx-auto max-w-[1320px] px-4 pb-16 pt-28 sm:px-6 sm:pt-32 lg:px-8 lg:pb-24 lg:pt-36"
>
<div className="mx-auto max-w-[1120px] text-center">
<h1 className="font-[family-name:var(--font-serif)] text-[3.65rem] leading-[0.93] tracking-[-0.038em] text-white drop-shadow-[0_10px_34px_rgba(0,0,0,0.32)] sm:text-[4.85rem] lg:text-[6.4rem]">
Your next 10 hires
<br />
won&apos;t be human.
</h1>
<p className="mx-auto mt-7 max-w-[820px] text-[15px] leading-7 text-white/84 sm:text-[17px]">
Multica is an open-source platform that turns coding agents into
real teammates. Assign tasks, track progress, compound skills
manage your human + agent workforce in one place.
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link href="/login" className={heroButtonClassName("solid")}>
Start free trial
</Link>
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={heroButtonClassName("ghost")}
>
<GitHubMark className="size-4" />
GitHub
</Link>
</div>
</div>
<div className="mt-10 flex items-center justify-center gap-8">
<span className="text-[15px] text-white/50">Works with</span>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2.5 text-white/80">
<ClaudeCodeLogo className="size-5" />
<span className="text-[15px] font-medium">Claude Code</span>
</div>
<div className="flex items-center gap-2.5 text-white/80">
<CodexLogo className="size-5" />
<span className="text-[15px] font-medium">Codex</span>
</div>
</div>
</div>
<div id="preview" className="mt-10 sm:mt-12">
<ProductImage />
</div>
</section>
</main>
</div>
);
}
function LandingBackdrop() {
return (
<div className="pointer-events-none absolute inset-0">
<Image
src="/images/landing-bg.jpg"
alt=""
fill
priority
className="object-cover object-center"
/>
</div>
);
}
function ProductImage() {
return (
<div>
<div className="relative overflow-hidden border border-white/14">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/images/landing-hero.png"
alt="Multica board view — issues managed by humans and agents"
className="block w-full"
/>
</div>
</div>
);
}

View file

@ -1,98 +1,19 @@
"use client";
import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { MulticaIcon } from "@/components/multica-icon";
import { cn } from "@/lib/utils";
const githubUrl = "https://github.com/multica-ai/multica";
import { LandingHeader } from "./landing-header";
import { LandingHero } from "./landing-hero";
import { FeaturesSection } from "./features-section";
import { HowItWorksSection } from "./how-it-works-section";
import { OpenSourceSection } from "./open-source-section";
import { FAQSection } from "./faq-section";
import { LandingFooter } from "./landing-footer";
export function MulticaLanding() {
return (
<>
<div className="relative min-h-full overflow-hidden bg-[#05070b] text-white">
<LandingBackdrop />
<header className="absolute inset-x-0 top-0 z-30 bg-transparent">
<div className="mx-auto flex h-[76px] max-w-[1320px] items-center justify-between px-4 sm:px-6 lg:px-8">
<Link href="#product" className="flex items-center gap-3">
<MulticaIcon className="size-5 text-white" noSpin />
<span className="text-[18px] font-semibold tracking-[0.04em] text-white/92 lowercase sm:text-[20px]">
multica
</span>
</Link>
<div className="flex items-center gap-2.5 sm:gap-3">
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={headerButtonClassName("ghost")}
>
<GitHubMark className="size-3.5" />
GitHub
</Link>
<Link href="/login" className={headerButtonClassName("solid")}>
Log in
</Link>
</div>
</div>
</header>
<main className="relative z-10">
<section
id="product"
className="mx-auto max-w-[1320px] px-4 pb-16 pt-28 sm:px-6 sm:pt-32 lg:px-8 lg:pb-24 lg:pt-36"
>
<div className="mx-auto max-w-[1120px] text-center">
<h1 className="font-[family-name:var(--font-serif)] text-[3.65rem] leading-[0.93] tracking-[-0.038em] text-white drop-shadow-[0_10px_34px_rgba(0,0,0,0.32)] sm:text-[4.85rem] lg:text-[6.4rem]">
Your next 10 hires
<br />
won&apos;t be human.
</h1>
<p className="mx-auto mt-7 max-w-[820px] text-[15px] leading-7 text-white/84 sm:text-[17px]">
Multica is project management for human + agent teams. Assign
tasks, manage runtimes, compound skills, all in one place.
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link href="/login" className={heroButtonClassName("solid")}>
Start free trial
</Link>
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={heroButtonClassName("ghost")}
>
<GitHubMark className="size-4" />
GitHub
</Link>
</div>
</div>
<div className="mt-10 flex items-center justify-center gap-8">
<span className="text-[15px] text-white/50">Works with</span>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2.5 text-white/80">
<ClaudeCodeLogo className="size-5" />
<span className="text-[15px] font-medium">Claude Code</span>
</div>
<div className="flex items-center gap-2.5 text-white/80">
<CodexLogo className="size-5" />
<span className="text-[15px] font-medium">Codex</span>
</div>
</div>
</div>
<div id="preview" className="mt-10 sm:mt-12">
<ProductImage />
</div>
</section>
</main>
<div className="relative">
<LandingHeader />
<LandingHero />
</div>
<FeaturesSection />
@ -103,843 +24,3 @@ export function MulticaLanding() {
</>
);
}
/* -------------------------------------------------------------------------- */
/* Features Section */
/* -------------------------------------------------------------------------- */
const features = [
{
label: "TEAMMATES",
title: "Assign to an agent like you'd assign to a colleague",
description:
"Agents aren't passive tools — they're active participants. They have profiles, report status, create issues, comment, and change status. Your activity feed shows humans and agents working side by side.",
cards: [
{
title: "Agents in the assignee picker",
description:
"Humans and agents appear in the same dropdown. Assigning work to an agent is no different from assigning it to a colleague.",
},
{
title: "Autonomous participation",
description:
"Agents create issues, leave comments, and update status on their own — not just when prompted.",
},
{
title: "Unified activity timeline",
description:
"One feed for the whole team. Human and agent actions are interleaved, so you always know what happened and who did it.",
},
],
},
{
label: "AUTONOMOUS",
title: "Set it and forget it — agents work while you sleep",
description:
"Not just prompt-response. Full task lifecycle management: enqueue, claim, start, complete or fail. Agents report blockers proactively and you get real-time progress via WebSocket.",
cards: [
{
title: "Complete task lifecycle",
description:
"Every task flows through enqueue → claim → start → complete/fail. No silent failures — every transition is tracked and broadcast.",
},
{
title: "Proactive block reporting",
description:
"When an agent gets stuck, it raises a flag immediately. No more checking back hours later to find nothing happened.",
},
{
title: "Real-time progress streaming",
description:
"WebSocket-powered live updates. Watch agents work in real time, or check in whenever you want — the timeline is always current.",
},
],
},
{
label: "SKILLS",
title: "Every solution becomes a reusable skill for the whole team",
description:
"Skills are reusable capability definitions — code, config, and context bundled together. Write a skill once, and every agent on your team can use it. Your skill library compounds over time.",
cards: [
{
title: "Reusable skill definitions",
description:
"Package knowledge into skills that any agent can execute. Deploy to staging, write migrations, review PRs — all codified.",
},
{
title: "Team-wide sharing",
description:
"One person's skill is every agent's skill. Build once, benefit everywhere across your team.",
},
{
title: "Compound growth",
description:
"Day 1: you teach an agent to deploy. Day 30: every agent deploys, writes tests, and does code review. Your team's capabilities grow exponentially.",
},
],
},
{
label: "RUNTIMES",
title: "One dashboard for all your compute",
description:
"Local daemons and cloud runtimes, managed from a single panel. Real-time monitoring of online/offline status, usage charts, and activity heatmaps. Auto-detects local CLIs — plug in and go.",
cards: [
{
title: "Unified runtime panel",
description:
"Local daemons and cloud runtimes in one view. No context switching between different management interfaces.",
},
{
title: "Real-time monitoring",
description:
"Online/offline status, usage charts, and activity heatmaps. Know exactly what your compute is doing at any moment.",
},
{
title: "Auto-detection & plug-and-play",
description:
"Multica detects available CLIs like Claude Code and Codex automatically. Connect a machine, and it's ready to work.",
},
],
},
];
function FeaturesSection() {
const [activeIndex, setActiveIndex] = useState(0);
const panelRefs = useRef<(HTMLDivElement | null)[]>([]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const idx = Number(entry.target.getAttribute("data-index"));
if (!isNaN(idx)) setActiveIndex(idx);
}
}
},
{ rootMargin: "-20% 0px -60% 0px", threshold: 0 },
);
panelRefs.current.forEach((el) => {
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, []);
const scrollToPanel = (index: number) => {
panelRefs.current[index]?.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
return (
<section id="features" className="bg-white text-[#0a0d12]">
<div className="mx-auto max-w-[1320px] px-4 sm:px-6 lg:px-8">
<div className="relative lg:flex lg:gap-20">
{/* Sticky left nav */}
<nav className="hidden lg:block lg:w-[180px] lg:shrink-0">
<div className="sticky top-28 flex flex-col gap-0 py-28">
{features.map((f, i) => (
<button
key={f.label}
onClick={() => scrollToPanel(i)}
className={cn(
"group flex items-center gap-3 rounded-lg px-4 py-3 text-left text-[11px] font-semibold tracking-[0.12em] transition-colors",
i === activeIndex
? "text-[#0a0d12]"
: "text-[#0a0d12]/36 hover:text-[#0a0d12]/60",
)}
>
<span
className={cn(
"size-2 shrink-0 rounded-full transition-colors",
i === activeIndex ? "bg-[#0a0d12]" : "bg-transparent",
)}
/>
{f.label}
</button>
))}
</div>
</nav>
{/* Scrollable feature panels */}
<div className="flex-1">
{features.map((feature, i) => (
<div
key={feature.label}
ref={(el) => {
panelRefs.current[i] = el;
}}
data-index={i}
className={cn(
"py-20 lg:py-28",
i < features.length - 1 && "border-b border-[#0a0d12]/8",
)}
>
{/* Title + description */}
<h2 className="font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] text-[#0a0d12] sm:text-[3.4rem] lg:text-[4.2rem]">
{feature.title}
</h2>
<p className="mt-5 max-w-[640px] text-[15px] leading-7 text-[#0a0d12]/60 sm:text-[16px]">
{feature.description}
</p>
{/* Image placeholder */}
<div className="mt-14 sm:mt-18">
<div className="relative overflow-hidden rounded-[20px] border border-[#0a0d12]/8 bg-[#f5f5f5]">
<div className="aspect-[16/9] w-full" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="flex flex-col items-center gap-4 text-center">
<div className="grid size-14 place-items-center rounded-2xl border border-[#0a0d12]/8 bg-white shadow-sm">
<ImageIcon className="size-6 text-[#0a0d12]/30" />
</div>
<p className="text-[13px] text-[#0a0d12]/36">
{feature.label.toLowerCase()} visual
</p>
</div>
</div>
</div>
</div>
{/* Feature cards */}
<div className="mt-14 grid gap-8 sm:mt-18 md:grid-cols-3 md:gap-10">
{feature.cards.map((card) => (
<div key={card.title}>
<h3 className="text-[15px] font-semibold leading-snug text-[#0a0d12] sm:text-[16px]">
{card.title}
</h3>
<p className="mt-2.5 text-[14px] leading-[1.7] text-[#0a0d12]/56 sm:text-[15px]">
{card.description}
</p>
<button className="mt-4 text-[13px] font-semibold text-[#0a0d12] underline decoration-[#0a0d12]/24 underline-offset-[3px] transition-colors hover:decoration-[#0a0d12]/60">
Learn more
</button>
</div>
))}
</div>
</div>
))}
</div>
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* How it Works Section */
/* -------------------------------------------------------------------------- */
const steps = [
{
number: "01",
title: "Sign up & create your workspace",
description:
"Enter your email, verify with a code, and you're in. Your workspace is created automatically — no setup wizard, no configuration forms.",
},
{
number: "02",
title: "Install the CLI & connect your machine",
description:
"Run multica login to authenticate, then multica daemon start. The daemon auto-detects Claude Code and Codex on your machine — plug in and go.",
},
{
number: "03",
title: "Create your first agent",
description:
"Give it a name, write instructions, attach skills, and set triggers. Choose when it activates: on assignment, on comment, or on mention.",
},
{
number: "04",
title: "Assign an issue and watch it work",
description:
"Pick your agent from the assignee dropdown — just like assigning to a teammate. The task is queued, claimed, and executed automatically. Watch progress in real time.",
},
];
function HowItWorksSection() {
return (
<section id="how-it-works" className="bg-[#05070b] text-white">
<div className="mx-auto max-w-[1320px] px-4 py-24 sm:px-6 sm:py-32 lg:px-8 lg:py-40">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-white/40">
Get started
</p>
<h2 className="mt-4 font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem] lg:text-[4.2rem]">
Hire your first AI employee
<br />
<span className="text-white/40">in the next hour.</span>
</h2>
<div className="mt-20 grid gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10 sm:grid-cols-2 lg:grid-cols-4">
{steps.map((step) => (
<div
key={step.number}
className="flex flex-col bg-[#05070b] p-8 lg:p-10"
>
<span className="text-[13px] font-semibold tabular-nums text-white/28">
{step.number}
</span>
<h3 className="mt-4 text-[17px] font-semibold leading-snug text-white sm:text-[18px]">
{step.title}
</h3>
<p className="mt-3 text-[14px] leading-[1.7] text-white/50 sm:text-[15px]">
{step.description}
</p>
</div>
))}
</div>
<div className="mt-14 flex flex-wrap items-center gap-4">
<Link href="/login" className={heroButtonClassName("solid")}>
Get started
</Link>
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className={heroButtonClassName("ghost")}
>
<GitHubMark className="size-4" />
View on GitHub
</Link>
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* Open Source Section */
/* -------------------------------------------------------------------------- */
const openSourceHighlights = [
{
title: "Self-host anywhere",
description:
"Run Multica on your own infrastructure. Docker Compose, single binary, or Kubernetes — your data never leaves your network.",
},
{
title: "No vendor lock-in",
description:
"Bring your own LLM provider, swap agent backends, extend the API. You own the stack, top to bottom.",
},
{
title: "Transparent by default",
description:
"Every line of code is auditable. See exactly how your agents make decisions, how tasks are routed, and where your data flows.",
},
{
title: "Community-driven",
description:
"Built with the community, not just for it. Contribute skills, integrations, and agent backends that benefit everyone.",
},
];
function OpenSourceSection() {
return (
<section id="open-source" className="bg-white text-[#0a0d12]">
<div className="mx-auto max-w-[1320px] px-4 py-24 sm:px-6 sm:py-32 lg:px-8 lg:py-40">
<div className="flex flex-col gap-16 lg:flex-row lg:items-start lg:gap-24">
{/* Left column — heading + CTA */}
<div className="lg:w-[480px] lg:shrink-0">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#0a0d12]/40">
Open source
</p>
<h2 className="mt-4 font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem] lg:text-[4.2rem]">
Open source
<br />
for all.
</h2>
<p className="mt-6 max-w-[420px] text-[15px] leading-7 text-[#0a0d12]/60 sm:text-[16px]">
Multica is fully open source under the MIT license. Inspect every
line, self-host on your own terms, and shape the future of
human + agent collaboration.
</p>
<div className="mt-8 flex flex-wrap items-center gap-3">
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center gap-2.5 rounded-[12px] bg-[#0a0d12] px-5 py-3 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0d12]/88"
>
<GitHubMark className="size-4" />
Star on GitHub
</Link>
</div>
</div>
{/* Right column — highlight grid */}
<div className="flex-1">
<div className="grid gap-px overflow-hidden rounded-2xl border border-[#0a0d12]/8 bg-[#0a0d12]/8 sm:grid-cols-2">
{openSourceHighlights.map((item) => (
<div key={item.title} className="bg-white p-8 lg:p-10">
<h3 className="text-[17px] font-semibold leading-snug text-[#0a0d12] sm:text-[18px]">
{item.title}
</h3>
<p className="mt-3 text-[14px] leading-[1.7] text-[#0a0d12]/56 sm:text-[15px]">
{item.description}
</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* FAQ Section */
/* -------------------------------------------------------------------------- */
const faqs = [
{
question: "What coding agents does Multica support?",
answer:
"Multica currently supports Claude Code and OpenAI Codex out of the box. The daemon auto-detects whichever CLIs you have installed. More backends are on the roadmap — and since it's open source, you can add your own.",
},
{
question: "Do I need to self-host, or is there a cloud version?",
answer:
"Both. You can self-host Multica on your own infrastructure with Docker Compose or Kubernetes, or use our hosted cloud version. Your data, your choice.",
},
{
question: "How is this different from just using Claude Code or Codex directly?",
answer:
"Coding agents are great at executing. Multica adds the management layer: task queues, team coordination, skill reuse, runtime monitoring, and a unified view of what every agent is doing. Think of it as the project manager for your agents.",
},
{
question: "Can agents work on long-running tasks autonomously?",
answer:
"Yes. Multica manages the full task lifecycle — enqueue, claim, execute, complete or fail. Agents report blockers proactively and stream progress in real time. You can check in whenever you want or let them run overnight.",
},
{
question: "Is my code safe? Where does agent execution happen?",
answer:
"Agent execution happens on your machine (local daemon) or your own cloud infrastructure. Code never passes through Multica servers. The platform only coordinates task state and broadcasts events.",
},
{
question: "How many agents can I run?",
answer:
"As many as your hardware supports. Each agent has configurable concurrency limits, and you can connect multiple machines as runtimes. There are no artificial caps in the open source version.",
},
];
function FAQSection() {
const [openIndex, setOpenIndex] = useState<number | null>(null);
return (
<section id="faq" className="bg-[#f8f8f8] text-[#0a0d12]">
<div className="mx-auto max-w-[860px] px-4 py-24 sm:px-6 sm:py-32 lg:py-40">
<div className="text-center">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#0a0d12]/40">
FAQ
</p>
<h2 className="mt-4 font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem] lg:text-[4.2rem]">
Questions & answers.
</h2>
</div>
<div className="mt-14 divide-y divide-[#0a0d12]/10 sm:mt-16">
{faqs.map((faq, i) => (
<div key={faq.question}>
<button
onClick={() =>
setOpenIndex(openIndex === i ? null : i)
}
className="flex w-full items-start justify-between gap-4 py-6 text-left"
>
<span className="text-[16px] font-semibold leading-snug text-[#0a0d12] sm:text-[17px]">
{faq.question}
</span>
<span
className={cn(
"mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border border-[#0a0d12]/12 text-[#0a0d12]/40 transition-transform",
openIndex === i && "rotate-45",
)}
>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
>
<path d="M6 1v10M1 6h10" />
</svg>
</span>
</button>
<div
className={cn(
"grid transition-[grid-template-rows] duration-200 ease-out",
openIndex === i
? "grid-rows-[1fr]"
: "grid-rows-[0fr]",
)}
>
<div className="overflow-hidden">
<p className="pb-6 pr-12 text-[14px] leading-[1.7] text-[#0a0d12]/56 sm:text-[15px]">
{faq.answer}
</p>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* Footer */
/* -------------------------------------------------------------------------- */
const footerLinks = {
Product: [
{ label: "Features", href: "#features" },
{ label: "How it Works", href: "#how-it-works" },
{ label: "Pricing", href: "#" },
{ label: "Changelog", href: "#" },
],
Resources: [
{ label: "Documentation", href: "#" },
{ label: "API Reference", href: "#" },
{ label: "Blog", href: "#" },
{ label: "Community", href: "#" },
],
Company: [
{ label: "About", href: "#" },
{ label: "Open Source", href: "#open-source" },
{ label: "GitHub", href: githubUrl },
{ label: "Contact", href: "#" },
],
Legal: [
{ label: "Privacy Policy", href: "#" },
{ label: "Terms of Service", href: "#" },
{ label: "MIT License", href: `${githubUrl}/blob/main/LICENSE` },
],
};
function LandingFooter() {
return (
<footer className="bg-white text-[#0a0d12]">
<div className="mx-auto max-w-[1320px] px-4 sm:px-6 lg:px-8">
{/* Top: CTA + link columns */}
<div className="flex flex-col gap-12 border-b border-[#0a0d12]/8 py-16 sm:py-20 lg:flex-row lg:gap-20">
{/* Left — newsletter / CTA */}
<div className="lg:w-[340px] lg:shrink-0">
<Link href="#product" className="flex items-center gap-3">
<MulticaIcon className="size-5 text-[#0a0d12]" noSpin />
<span className="text-[18px] font-semibold tracking-[0.04em] lowercase">
multica
</span>
</Link>
<p className="mt-4 max-w-[300px] text-[14px] leading-[1.7] text-[#0a0d12]/56 sm:text-[15px]">
Project management for human + agent teams. Open source, self-hostable, built for the future of work.
</p>
<div className="mt-6">
<Link
href="/login"
className="inline-flex items-center justify-center rounded-[11px] bg-[#0a0d12] px-5 py-2.5 text-[13px] font-semibold text-white transition-colors hover:bg-[#0a0d12]/88"
>
Get started
</Link>
</div>
</div>
{/* Right — link columns */}
<div className="grid flex-1 grid-cols-2 gap-8 sm:grid-cols-4">
{Object.entries(footerLinks).map(([group, links]) => (
<div key={group}>
<h4 className="text-[12px] font-semibold uppercase tracking-[0.1em] text-[#0a0d12]/40">
{group}
</h4>
<ul className="mt-4 flex flex-col gap-2.5">
{links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
{...(link.href.startsWith("http")
? { target: "_blank", rel: "noreferrer" }
: {})}
className="text-[14px] text-[#0a0d12]/60 transition-colors hover:text-[#0a0d12]"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
</div>
{/* Bottom: copyright + legal */}
<div className="flex items-center justify-between py-6">
<p className="text-[13px] text-[#0a0d12]/36">
&copy; {new Date().getFullYear()} Multica. All rights reserved.
</p>
</div>
{/* Giant logo + Game of Life */}
<div className="relative overflow-hidden pb-4">
<div className="flex items-end gap-6 sm:gap-8">
<MulticaIcon
className="size-[clamp(4rem,12vw,10rem)] shrink-0 text-[#0a0d12]"
noSpin
/>
<span className="font-[family-name:var(--font-serif)] text-[clamp(6rem,22vw,16rem)] font-normal leading-[0.82] tracking-[-0.04em] text-[#0a0d12] lowercase">
multica
</span>
</div>
<div className="pointer-events-none absolute bottom-0 right-0 top-0 w-[40%]">
<GameOfLife />
</div>
</div>
</div>
</footer>
);
}
/* -------------------------------------------------------------------------- */
/* Game of Life */
/* -------------------------------------------------------------------------- */
function GameOfLife() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const cellSize = 10;
let cols = 0;
let rows = 0;
let grid: boolean[][] = [];
function resize() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas!.getBoundingClientRect();
canvas!.width = rect.width * dpr;
canvas!.height = rect.height * dpr;
ctx!.setTransform(dpr, 0, 0, dpr, 0, 0);
const newCols = Math.ceil(rect.width / cellSize);
const newRows = Math.ceil(rect.height / cellSize);
if (newCols !== cols || newRows !== rows) {
cols = newCols;
rows = newRows;
grid = initGrid(cols, rows);
}
}
function initGrid(c: number, r: number): boolean[][] {
return Array.from({ length: c }, () =>
Array.from({ length: r }, () => Math.random() < 0.3),
);
}
function step() {
const next: boolean[][] = Array.from({ length: cols }, () =>
Array(rows).fill(false),
);
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
let neighbors = 0;
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && grid[nx]?.[ny]) {
neighbors++;
}
}
}
if (grid[x]?.[y]) {
next[x]![y] = neighbors === 2 || neighbors === 3;
} else {
next[x]![y] = neighbors === 3;
}
}
}
grid = next;
}
function draw() {
const rect = canvas!.getBoundingClientRect();
ctx!.clearRect(0, 0, rect.width, rect.height);
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
if (grid[x]?.[y]) {
ctx!.fillStyle = "rgba(10, 13, 18, 0.12)";
ctx!.beginPath();
ctx!.arc(
x * cellSize + cellSize / 2,
y * cellSize + cellSize / 2,
3,
0,
Math.PI * 2,
);
ctx!.fill();
}
}
}
}
resize();
let frame: number;
let lastStep = 0;
const interval = 300;
function loop(time: number) {
if (time - lastStep > interval) {
step();
draw();
lastStep = time;
}
frame = requestAnimationFrame(loop);
}
draw();
frame = requestAnimationFrame(loop);
const onResize = () => resize();
window.addEventListener("resize", onResize);
return () => {
cancelAnimationFrame(frame);
window.removeEventListener("resize", onResize);
};
}, []);
return (
<canvas
ref={canvasRef}
className="size-full"
style={{ maskImage: "linear-gradient(to right, transparent, black 30%)" }}
/>
);
}
/* -------------------------------------------------------------------------- */
/* Shared components */
/* -------------------------------------------------------------------------- */
function LandingBackdrop() {
return (
<div className="pointer-events-none absolute inset-0">
<Image
src="/images/landing-bg.jpg"
alt=""
fill
priority
className="object-cover object-center"
/>
</div>
);
}
function GitHubMark({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 16 16"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2 .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.82A7.65 7.65 0 0 1 8 4.84c.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.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" />
</svg>
);
}
function ImageIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3.5" y="5" width="17" height="14" rx="2.5" />
<circle cx="9" cy="10" r="1.6" />
<path d="m20.5 16-4.8-4.8a1 1 0 0 0-1.4 0L8 17.5" />
<path d="m11.5 14.5 1.8-1.8a1 1 0 0 1 1.4 0l2.8 2.8" />
</svg>
);
}
function ClaudeCodeLogo({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M15.31 3.99A11.2 11.2 0 0 0 12 3.6c-1.14 0-2.24.14-3.31.39C5.45 4.93 3 7.98 3 12c0 4.02 2.45 7.07 5.69 8.01A11.2 11.2 0 0 0 12 20.4c1.14 0 2.24-.14 3.31-.39C18.55 19.07 21 16.02 21 12c0-4.02-2.45-7.07-5.69-8.01ZM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm2.75 13.5a.75.75 0 0 1-1.06.06l-1.5-1.33-1.5 1.33a.75.75 0 1 1-1-1.12l1.75-1.56V9.5a.75.75 0 0 1 1.5 0v3.38l1.75 1.56a.75.75 0 0 1 .06 1.06Z" />
</svg>
);
}
function CodexLogo({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073ZM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494ZM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646ZM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872v.024Zm16.597 3.855-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667Zm2.01-3.023-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66v.018ZM8.318 12.898l-2.024-1.168a.074.074 0 0 1-.038-.052V6.095a4.494 4.494 0 0 1 7.37-3.456l-.14.081-4.78 2.758a.795.795 0 0 0-.392.681l-.014 6.739h.018Zm1.1-2.36 2.602-1.5 2.595 1.5v2.999l-2.595 1.5-2.602-1.5v-3Z" />
</svg>
);
}
function ProductImage() {
return (
<div>
<div className="relative overflow-hidden rounded-xl border border-white/14">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/images/landing-hero.png"
alt="Multica board view — issues managed by humans and agents"
className="block w-full"
/>
</div>
</div>
);
}
function headerButtonClassName(tone: "ghost" | "solid") {
return cn(
"inline-flex items-center justify-center gap-2 rounded-[11px] px-4 py-2.5 text-[13px] font-semibold transition-colors",
tone === "solid"
? "bg-white text-[#0a0d12] hover:bg-white/92"
: "border border-white/18 bg-black/16 text-white backdrop-blur-sm hover:bg-black/24",
);
}
function heroButtonClassName(tone: "ghost" | "solid") {
return cn(
"inline-flex items-center justify-center gap-2 rounded-[12px] px-5 py-3 text-[14px] font-semibold transition-colors",
tone === "solid"
? "bg-white text-[#0a0d12] hover:bg-white/92"
: "border border-white/18 bg-black/16 text-white backdrop-blur-sm hover:bg-black/24",
);
}

View file

@ -0,0 +1,79 @@
import Link from "next/link";
import { GitHubMark, githubUrl } from "./shared";
const openSourceHighlights = [
{
title: "Self-host anywhere",
description:
"Run Multica on your own infrastructure. Docker Compose, single binary, or Kubernetes — your data never leaves your network.",
},
{
title: "No vendor lock-in",
description:
"Bring your own LLM provider, swap agent backends, extend the API. You own the stack, top to bottom.",
},
{
title: "Transparent by default",
description:
"Every line of code is auditable. See exactly how your agents make decisions, how tasks are routed, and where your data flows.",
},
{
title: "Community-driven",
description:
"Built with the community, not just for it. Contribute skills, integrations, and agent backends that benefit everyone.",
},
];
export function OpenSourceSection() {
return (
<section id="open-source" className="bg-white text-[#0a0d12]">
<div className="mx-auto max-w-[1320px] px-4 py-24 sm:px-6 sm:py-32 lg:px-8 lg:py-40">
<div className="flex flex-col gap-16 lg:flex-row lg:items-start lg:gap-24">
{/* Left column — heading + CTA */}
<div className="lg:w-[480px] lg:shrink-0">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#0a0d12]/40">
Open source
</p>
<h2 className="mt-4 font-[family-name:var(--font-serif)] text-[2.6rem] leading-[1.05] tracking-[-0.03em] sm:text-[3.4rem] lg:text-[4.2rem]">
Open source
<br />
for all.
</h2>
<p className="mt-6 max-w-[420px] text-[15px] leading-7 text-[#0a0d12]/60 sm:text-[16px]">
Multica is fully open source. Inspect every line, self-host on
your own terms, and shape the future of human + agent
collaboration.
</p>
<div className="mt-8 flex flex-wrap items-center gap-3">
<Link
href={githubUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center gap-2.5 rounded-[12px] bg-[#0a0d12] px-5 py-3 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0d12]/88"
>
<GitHubMark className="size-4" />
Star on GitHub
</Link>
</div>
</div>
{/* Right column — highlight grid */}
<div className="flex-1">
<div className="grid gap-px overflow-hidden rounded-2xl border border-[#0a0d12]/8 bg-[#0a0d12]/8 sm:grid-cols-2">
{openSourceHighlights.map((item) => (
<div key={item.title} className="bg-white p-8 lg:p-10">
<h3 className="text-[17px] font-semibold leading-snug text-[#0a0d12] sm:text-[18px]">
{item.title}
</h3>
<p className="mt-3 text-[14px] leading-[1.7] text-[#0a0d12]/56 sm:text-[15px]">
{item.description}
</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,87 @@
import { cn } from "@/lib/utils";
export const githubUrl = "https://github.com/multica-ai/multica";
export function GitHubMark({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 16 16"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2 .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.82A7.65 7.65 0 0 1 8 4.84c.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.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" />
</svg>
);
}
export function ImageIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3.5" y="5" width="17" height="14" rx="2.5" />
<circle cx="9" cy="10" r="1.6" />
<path d="m20.5 16-4.8-4.8a1 1 0 0 0-1.4 0L8 17.5" />
<path d="m11.5 14.5 1.8-1.8a1 1 0 0 1 1.4 0l2.8 2.8" />
</svg>
);
}
export function ClaudeCodeLogo({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 248 248"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M52.4285 162.873L98.7844 136.879L99.5485 134.602L98.7844 133.334H96.4921L88.7237 132.862L62.2346 132.153L39.3113 131.207L17.0249 130.026L11.4214 128.844L6.2 121.873L6.7094 118.447L11.4214 115.257L18.171 115.847L33.0711 116.911L55.485 118.447L71.6586 119.392L95.728 121.873H99.5485L100.058 120.337L98.7844 119.392L97.7656 118.447L74.5877 102.732L49.4995 86.1905L36.3823 76.62L29.3779 71.7757L25.8121 67.2858L24.2839 57.3608L30.6515 50.2716L39.3113 50.8623L41.4763 51.4531L50.2636 58.1879L68.9842 72.7209L93.4357 90.6804L97.0015 93.6343L98.4374 92.6652L98.6571 91.9801L97.0015 89.2625L83.757 65.2772L69.621 40.8192L63.2534 30.6579L61.5978 24.632C60.9565 22.1032 60.579 20.0111 60.579 17.4246L67.8381 7.49965L71.9133 6.19995L81.7193 7.49965L85.7946 11.0443L91.9074 24.9865L101.714 46.8451L116.996 76.62L121.453 85.4816L123.873 93.6343L124.764 96.1155H126.292V94.6976L127.566 77.9197L129.858 57.3608L132.15 30.8942L132.915 23.4505L136.608 14.4708L143.994 9.62643L149.725 12.344L154.437 19.0788L153.8 23.4505L150.998 41.6463L145.522 70.1215L141.957 89.2625H143.994L146.414 86.7813L156.093 74.0206L172.266 53.698L179.398 45.6635L187.803 36.802L193.152 32.5484H203.34L210.726 43.6549L207.415 55.1159L196.972 68.3492L188.312 79.5739L175.896 96.2095L168.191 109.585L168.882 110.689L170.738 110.53L198.755 104.504L213.91 101.787L231.994 98.7149L240.144 102.496L241.036 106.395L237.852 114.311L218.495 119.037L195.826 123.645L162.07 131.592L161.696 131.893L162.137 132.547L177.36 133.925L183.855 134.279H199.774L229.447 136.524L237.215 141.605L241.8 147.867L241.036 152.711L229.065 158.737L213.019 154.956L175.45 145.977L162.587 142.787H160.805V143.85L171.502 154.366L191.242 172.089L215.82 195.011L217.094 200.682L213.91 205.172L210.599 204.699L188.949 188.394L180.544 181.069L161.696 165.118H160.422V166.772L164.752 173.152L187.803 207.771L188.949 218.405L187.294 221.832L181.308 223.959L174.813 222.777L161.187 203.754L147.305 182.486L136.098 163.345L134.745 164.2L128.075 235.42L125.019 239.082L117.887 241.8L111.902 237.31L108.718 229.984L111.902 215.452L115.722 196.547L118.779 181.541L121.58 162.873L123.291 156.636L123.14 156.219L121.773 156.449L107.699 175.752L86.304 204.699L69.3663 222.777L65.291 224.431L58.2867 220.768L58.9235 214.27L62.8713 208.48L86.304 178.705L100.44 160.155L109.551 149.507L109.462 147.967L108.959 147.924L46.6977 188.512L35.6182 189.93L30.7788 185.44L31.4156 178.115L33.7079 175.752L52.4285 162.873Z" />
</svg>
);
}
export function CodexLogo({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={className}
fill="currentColor"
>
<path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073ZM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494ZM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646ZM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872v.024Zm16.597 3.855-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667Zm2.01-3.023-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66v.018ZM8.318 12.898l-2.024-1.168a.074.074 0 0 1-.038-.052V6.095a4.494 4.494 0 0 1 7.37-3.456l-.14.081-4.78 2.758a.795.795 0 0 0-.392.681l-.014 6.739h.018Zm1.1-2.36 2.602-1.5 2.595 1.5v2.999l-2.595 1.5-2.602-1.5v-3Z" />
</svg>
);
}
export function headerButtonClassName(
tone: "ghost" | "solid",
variant: "dark" | "light" = "dark",
) {
return cn(
"inline-flex items-center justify-center gap-2 rounded-[11px] px-4 py-2.5 text-[13px] font-semibold transition-colors",
variant === "dark"
? tone === "solid"
? "bg-white text-[#0a0d12] hover:bg-white/92"
: "border border-white/18 bg-black/16 text-white backdrop-blur-sm hover:bg-black/24"
: tone === "solid"
? "bg-[#0a0d12] text-white hover:bg-[#0a0d12]/88"
: "border border-[#0a0d12]/12 bg-white text-[#0a0d12] hover:bg-[#0a0d12]/5",
);
}
export function heroButtonClassName(tone: "ghost" | "solid") {
return cn(
"inline-flex items-center justify-center gap-2 rounded-[12px] px-5 py-3 text-[14px] font-semibold transition-colors",
tone === "solid"
? "bg-white text-[#0a0d12] hover:bg-white/92"
: "border border-white/18 bg-black/16 text-white backdrop-blur-sm hover:bg-black/24",
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 1 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1 MiB

Before After
Before After