Merge branch 'forrestchang/landing-page' into dev
This commit is contained in:
commit
a4d222da46
26 changed files with 2658 additions and 22 deletions
62
apps/web/app/(landing)/about/page.tsx
Normal file
62
apps/web/app/(landing)/about/page.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"use client";
|
||||
|
||||
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";
|
||||
import { useLocale } from "@/features/landing/i18n";
|
||||
|
||||
export default function AboutPage() {
|
||||
const { t } = useLocale();
|
||||
const n = t.about.nameLine;
|
||||
|
||||
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]">
|
||||
{t.about.title}
|
||||
</h1>
|
||||
<div className="mt-8 space-y-6 text-[15px] leading-[1.8] text-[#0a0d12]/70 sm:text-[16px]">
|
||||
<p>
|
||||
{n.prefix}
|
||||
<strong className="font-semibold text-[#0a0d12]">
|
||||
{n.mul}
|
||||
</strong>
|
||||
{n.tiplexed}
|
||||
<strong className="font-semibold text-[#0a0d12]">
|
||||
{n.i}
|
||||
</strong>
|
||||
{n.nformationAnd}
|
||||
<strong className="font-semibold text-[#0a0d12]">
|
||||
{n.c}
|
||||
</strong>
|
||||
{n.omputing}
|
||||
<strong className="font-semibold text-[#0a0d12]">
|
||||
{n.a}
|
||||
</strong>
|
||||
{n.gent}
|
||||
</p>
|
||||
{t.about.paragraphs.map((p, i) => (
|
||||
<p key={i}>{p}</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" />
|
||||
{t.about.cta}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<LandingFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
55
apps/web/app/(landing)/changelog/page.tsx
Normal file
55
apps/web/app/(landing)/changelog/page.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"use client";
|
||||
|
||||
import { LandingHeader } from "@/features/landing/components/landing-header";
|
||||
import { LandingFooter } from "@/features/landing/components/landing-footer";
|
||||
import { useLocale } from "@/features/landing/i18n";
|
||||
|
||||
export default function ChangelogPage() {
|
||||
const { t } = useLocale();
|
||||
|
||||
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]">
|
||||
{t.changelog.title}
|
||||
</h1>
|
||||
<p className="mt-4 text-[15px] leading-7 text-[#0a0d12]/60 sm:text-[16px]">
|
||||
{t.changelog.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="mt-16 space-y-16">
|
||||
{t.changelog.entries.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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
26
apps/web/app/(landing)/layout.tsx
Normal file
26
apps/web/app/(landing)/layout.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Instrument_Serif, Noto_Serif_SC } from "next/font/google";
|
||||
import { LocaleProvider } from "@/features/landing/i18n";
|
||||
|
||||
const instrumentSerif = Instrument_Serif({
|
||||
subsets: ["latin"],
|
||||
weight: "400",
|
||||
variable: "--font-serif",
|
||||
});
|
||||
|
||||
const notoSerifSC = Noto_Serif_SC({
|
||||
subsets: ["latin"],
|
||||
weight: "400",
|
||||
variable: "--font-serif-zh",
|
||||
});
|
||||
|
||||
export default function LandingLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className={`${instrumentSerif.variable} ${notoSerifSC.variable} h-full overflow-x-hidden overflow-y-auto bg-white`}>
|
||||
<LocaleProvider>{children}</LocaleProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
apps/web/app/(landing)/page.tsx
Normal file
31
apps/web/app/(landing)/page.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAuthStore } from "@/features/auth";
|
||||
import { useNavigationStore } from "@/features/navigation";
|
||||
import { MulticaLanding } from "@/features/landing/components/multica-landing";
|
||||
import { MulticaIcon } from "@/components/multica-icon";
|
||||
|
||||
export default function LandingPage() {
|
||||
const router = useRouter();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const isLoading = useAuthStore((s) => s.isLoading);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && user) {
|
||||
const lastPath = useNavigationStore.getState().lastPath;
|
||||
router.replace(lastPath);
|
||||
}
|
||||
}, [isLoading, user, router]);
|
||||
|
||||
if (isLoading || user) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
<MulticaIcon className="size-6" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <MulticaLanding />;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useNavigationStore } from "@/features/navigation";
|
||||
import { MulticaIcon } from "@/components/multica-icon";
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const lastPath = useNavigationStore.getState().lastPath;
|
||||
router.replace(lastPath);
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center">
|
||||
<MulticaIcon className="size-6" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
70
apps/web/features/landing/components/faq-section.tsx
Normal file
70
apps/web/features/landing/components/faq-section.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useLocale } from "../i18n";
|
||||
|
||||
export function FAQSection() {
|
||||
const { t } = useLocale();
|
||||
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">
|
||||
{t.faq.label}
|
||||
</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]">
|
||||
{t.faq.headline}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-14 divide-y divide-[#0a0d12]/10 sm:mt-16">
|
||||
{t.faq.items.map((faq, i) => (
|
||||
<div key={i}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
1092
apps/web/features/landing/components/features-section.tsx
Normal file
1092
apps/web/features/landing/components/features-section.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,58 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useLocale } from "../i18n";
|
||||
import { GitHubMark, githubUrl, heroButtonClassName } from "./shared";
|
||||
|
||||
export function HowItWorksSection() {
|
||||
const { t } = useLocale();
|
||||
|
||||
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">
|
||||
{t.howItWorks.label}
|
||||
</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]">
|
||||
{t.howItWorks.headlineMain}
|
||||
<br />
|
||||
<span className="text-white/40">{t.howItWorks.headlineFaded}</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">
|
||||
{t.howItWorks.steps.map((step, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex flex-col bg-[#05070b] p-8 lg:p-10"
|
||||
>
|
||||
<span className="text-[13px] font-semibold tabular-nums text-white/28">
|
||||
{String(i + 1).padStart(2, "0")}
|
||||
</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")}>
|
||||
{t.howItWorks.cta}
|
||||
</Link>
|
||||
<Link
|
||||
href={githubUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={heroButtonClassName("ghost")}
|
||||
>
|
||||
<GitHubMark className="size-4" />
|
||||
{t.howItWorks.ctaGithub}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
107
apps/web/features/landing/components/landing-footer.tsx
Normal file
107
apps/web/features/landing/components/landing-footer.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { MulticaIcon } from "@/components/multica-icon";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useLocale, locales, localeLabels } from "../i18n";
|
||||
|
||||
export function LandingFooter() {
|
||||
const { t, locale, setLocale } = useLocale();
|
||||
const groups = Object.values(t.footer.groups);
|
||||
|
||||
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]">
|
||||
{t.footer.tagline}
|
||||
</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"
|
||||
>
|
||||
{t.footer.cta}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right — link columns */}
|
||||
<div className="grid flex-1 grid-cols-2 gap-8 sm:grid-cols-4">
|
||||
{groups.map((group) => (
|
||||
<div key={group.label}>
|
||||
<h4 className="text-[12px] font-semibold uppercase tracking-[0.1em] text-white/40">
|
||||
{group.label}
|
||||
</h4>
|
||||
<ul className="mt-4 flex flex-col gap-2.5">
|
||||
{group.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 + language switcher */}
|
||||
<div className="flex items-center justify-between py-6">
|
||||
<p className="text-[13px] text-white/36">
|
||||
{t.footer.copyright.replace(
|
||||
"{year}",
|
||||
String(new Date().getFullYear()),
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
{locales.map((l, i) => (
|
||||
<button
|
||||
key={l}
|
||||
onClick={() => setLocale(l)}
|
||||
className={cn(
|
||||
"px-1.5 py-1 text-[12px] font-medium transition-colors",
|
||||
l === locale
|
||||
? "text-white/70"
|
||||
: "text-white/30 hover:text-white/50",
|
||||
i > 0 && "border-l border-white/16",
|
||||
)}
|
||||
>
|
||||
{localeLabels[l]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
64
apps/web/features/landing/components/landing-header.tsx
Normal file
64
apps/web/features/landing/components/landing-header.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { MulticaIcon } from "@/components/multica-icon";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useLocale } from "../i18n";
|
||||
import { GitHubMark, githubUrl, headerButtonClassName } from "./shared";
|
||||
|
||||
export function LandingHeader({
|
||||
variant = "dark",
|
||||
}: {
|
||||
variant?: "dark" | "light";
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
|
||||
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" />
|
||||
{t.header.github}
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
className={headerButtonClassName("solid", variant)}
|
||||
>
|
||||
{t.header.login}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
105
apps/web/features/landing/components/landing-hero.tsx
Normal file
105
apps/web/features/landing/components/landing-hero.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useLocale } from "../i18n";
|
||||
import {
|
||||
ClaudeCodeLogo,
|
||||
CodexLogo,
|
||||
GitHubMark,
|
||||
githubUrl,
|
||||
heroButtonClassName,
|
||||
} from "./shared";
|
||||
|
||||
export function LandingHero() {
|
||||
const { t } = useLocale();
|
||||
|
||||
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]">
|
||||
{t.hero.headlineLine1}
|
||||
<br />
|
||||
{t.hero.headlineLine2}
|
||||
</h1>
|
||||
|
||||
<p className="mx-auto mt-7 max-w-[820px] text-[15px] leading-7 text-white/84 sm:text-[17px]">
|
||||
{t.hero.subheading}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<Link href="/login" className={heroButtonClassName("solid")}>
|
||||
{t.hero.cta}
|
||||
</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">
|
||||
{t.hero.worksWith}
|
||||
</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 alt={t.hero.imageAlt} />
|
||||
</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({ alt }: { alt: string }) {
|
||||
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={alt}
|
||||
className="block w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
apps/web/features/landing/components/multica-landing.tsx
Normal file
26
apps/web/features/landing/components/multica-landing.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
"use client";
|
||||
|
||||
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">
|
||||
<LandingHeader />
|
||||
<LandingHero />
|
||||
</div>
|
||||
|
||||
<FeaturesSection />
|
||||
<HowItWorksSection />
|
||||
<OpenSourceSection />
|
||||
<FAQSection />
|
||||
<LandingFooter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
59
apps/web/features/landing/components/open-source-section.tsx
Normal file
59
apps/web/features/landing/components/open-source-section.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useLocale } from "../i18n";
|
||||
import { GitHubMark, githubUrl } from "./shared";
|
||||
|
||||
export function OpenSourceSection() {
|
||||
const { t } = useLocale();
|
||||
|
||||
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">
|
||||
{t.openSource.label}
|
||||
</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]">
|
||||
{t.openSource.headlineLine1}
|
||||
<br />
|
||||
{t.openSource.headlineLine2}
|
||||
</h2>
|
||||
<p className="mt-6 max-w-[420px] text-[15px] leading-7 text-[#0a0d12]/60 sm:text-[16px]">
|
||||
{t.openSource.description}
|
||||
</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" />
|
||||
{t.openSource.cta}
|
||||
</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">
|
||||
{t.openSource.highlights.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>
|
||||
);
|
||||
}
|
||||
87
apps/web/features/landing/components/shared.tsx
Normal file
87
apps/web/features/landing/components/shared.tsx
Normal 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",
|
||||
);
|
||||
}
|
||||
51
apps/web/features/landing/i18n/context.tsx
Normal file
51
apps/web/features/landing/i18n/context.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"use client";
|
||||
|
||||
import { createContext, useContext, useState, useCallback } from "react";
|
||||
import { en } from "./en";
|
||||
import { zh } from "./zh";
|
||||
import type { LandingDict, Locale } from "./types";
|
||||
|
||||
const dictionaries: Record<Locale, LandingDict> = { en, zh };
|
||||
|
||||
const STORAGE_KEY = "multica-locale";
|
||||
|
||||
function getInitialLocale(): Locale {
|
||||
if (typeof window === "undefined") return "en";
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored === "en" || stored === "zh") return stored;
|
||||
// Detect from browser language
|
||||
const lang = navigator.language;
|
||||
if (lang.startsWith("zh")) return "zh";
|
||||
return "en";
|
||||
}
|
||||
|
||||
type LocaleContextValue = {
|
||||
locale: Locale;
|
||||
t: LandingDict;
|
||||
setLocale: (locale: Locale) => void;
|
||||
};
|
||||
|
||||
const LocaleContext = createContext<LocaleContextValue | null>(null);
|
||||
|
||||
export function LocaleProvider({ children }: { children: React.ReactNode }) {
|
||||
const [locale, setLocaleState] = useState<Locale>(getInitialLocale);
|
||||
|
||||
const setLocale = useCallback((l: Locale) => {
|
||||
setLocaleState(l);
|
||||
localStorage.setItem(STORAGE_KEY, l);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider
|
||||
value={{ locale, t: dictionaries[locale], setLocale }}
|
||||
>
|
||||
{children}
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useLocale() {
|
||||
const ctx = useContext(LocaleContext);
|
||||
if (!ctx) throw new Error("useLocale must be used within LocaleProvider");
|
||||
return ctx;
|
||||
}
|
||||
333
apps/web/features/landing/i18n/en.ts
Normal file
333
apps/web/features/landing/i18n/en.ts
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
import { githubUrl } from "../components/shared";
|
||||
import type { LandingDict } from "./types";
|
||||
|
||||
export const en: LandingDict = {
|
||||
header: {
|
||||
github: "GitHub",
|
||||
login: "Log in",
|
||||
},
|
||||
|
||||
hero: {
|
||||
headlineLine1: "Your next 10 hires",
|
||||
headlineLine2: "won\u2019t be human.",
|
||||
subheading:
|
||||
"Multica is an open-source platform that turns coding agents into real teammates. Assign tasks, track progress, compound skills \u2014 manage your human + agent workforce in one place.",
|
||||
cta: "Start free trial",
|
||||
worksWith: "Works with",
|
||||
imageAlt: "Multica board view \u2014 issues managed by humans and agents",
|
||||
},
|
||||
|
||||
features: {
|
||||
teammates: {
|
||||
label: "TEAMMATES",
|
||||
title: "Assign to an agent like you\u2019d assign to a colleague",
|
||||
description:
|
||||
"Agents aren\u2019t passive tools \u2014 they\u2019re 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 \u2014 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.",
|
||||
},
|
||||
],
|
||||
},
|
||||
autonomous: {
|
||||
label: "AUTONOMOUS",
|
||||
title: "Set it and forget it \u2014 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 \u2192 claim \u2192 start \u2192 complete/fail. No silent failures \u2014 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 \u2014 the timeline is always current.",
|
||||
},
|
||||
],
|
||||
},
|
||||
skills: {
|
||||
label: "SKILLS",
|
||||
title: "Every solution becomes a reusable skill for the whole team",
|
||||
description:
|
||||
"Skills are reusable capability definitions \u2014 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 \u2014 all codified.",
|
||||
},
|
||||
{
|
||||
title: "Team-wide sharing",
|
||||
description:
|
||||
"One person\u2019s skill is every agent\u2019s 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\u2019s capabilities grow exponentially.",
|
||||
},
|
||||
],
|
||||
},
|
||||
runtimes: {
|
||||
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 \u2014 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\u2019s ready to work.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
howItWorks: {
|
||||
label: "Get started",
|
||||
headlineMain: "Hire your first AI employee",
|
||||
headlineFaded: "in the next hour.",
|
||||
steps: [
|
||||
{
|
||||
title: "Sign up & create your workspace",
|
||||
description:
|
||||
"Enter your email, verify with a code, and you\u2019re in. Your workspace is created automatically \u2014 no setup wizard, no configuration forms.",
|
||||
},
|
||||
{
|
||||
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 \u2014 plug in and go.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "Assign an issue and watch it work",
|
||||
description:
|
||||
"Pick your agent from the assignee dropdown \u2014 just like assigning to a teammate. The task is queued, claimed, and executed automatically. Watch progress in real time.",
|
||||
},
|
||||
],
|
||||
cta: "Get started",
|
||||
ctaGithub: "View on GitHub",
|
||||
},
|
||||
|
||||
openSource: {
|
||||
label: "Open source",
|
||||
headlineLine1: "Open source",
|
||||
headlineLine2: "for all.",
|
||||
description:
|
||||
"Multica is fully open source. Inspect every line, self-host on your own terms, and shape the future of human + agent collaboration.",
|
||||
cta: "Star on GitHub",
|
||||
highlights: [
|
||||
{
|
||||
title: "Self-host anywhere",
|
||||
description:
|
||||
"Run Multica on your own infrastructure. Docker Compose, single binary, or Kubernetes \u2014 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.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: {
|
||||
label: "FAQ",
|
||||
headline: "Questions & answers.",
|
||||
items: [
|
||||
{
|
||||
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 \u2014 and since it\u2019s 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 \u2014 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.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
footer: {
|
||||
tagline:
|
||||
"Project management for human + agent teams. Open source, self-hostable, built for the future of work.",
|
||||
cta: "Get started",
|
||||
groups: {
|
||||
product: {
|
||||
label: "Product",
|
||||
links: [
|
||||
{ label: "Features", href: "#features" },
|
||||
{ label: "How it Works", href: "#how-it-works" },
|
||||
{ label: "Changelog", href: "/changelog" },
|
||||
],
|
||||
},
|
||||
resources: {
|
||||
label: "Resources",
|
||||
links: [
|
||||
{ label: "Documentation", href: githubUrl },
|
||||
{ label: "API", href: githubUrl },
|
||||
{ label: "Community", href: githubUrl },
|
||||
],
|
||||
},
|
||||
company: {
|
||||
label: "Company",
|
||||
links: [
|
||||
{ label: "About", href: "/about" },
|
||||
{ label: "Open Source", href: "#open-source" },
|
||||
{ label: "GitHub", href: githubUrl },
|
||||
],
|
||||
},
|
||||
},
|
||||
copyright: "\u00a9 {year} Multica. All rights reserved.",
|
||||
},
|
||||
|
||||
about: {
|
||||
title: "About Multica",
|
||||
nameLine: {
|
||||
prefix: "Multica \u2014 ",
|
||||
mul: "Mul",
|
||||
tiplexed: "tiplexed ",
|
||||
i: "I",
|
||||
nformationAnd: "nformation and ",
|
||||
c: "C",
|
||||
omputing: "omputing ",
|
||||
a: "A",
|
||||
gent: "gent.",
|
||||
},
|
||||
paragraphs: [
|
||||
"The name is a nod to Multics, the pioneering operating system of the 1960s that introduced time-sharing \u2014 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.",
|
||||
"We think the same inflection is happening again. For decades, software teams have been single-threaded \u2014 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 \u201cusers\u201d multiplexing the system are both humans and autonomous agents.",
|
||||
"In Multica, agents are first-class teammates. They get assigned issues, report progress, raise blockers, and ship code \u2014 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.",
|
||||
"Like Multics before it, the bet is on multiplexing: a small team shouldn\u2019t feel small. With the right system, two engineers and a fleet of agents can move like twenty.",
|
||||
"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.",
|
||||
],
|
||||
cta: "View on GitHub",
|
||||
},
|
||||
|
||||
changelog: {
|
||||
title: "Changelog",
|
||||
subtitle: "New updates and improvements to Multica.",
|
||||
entries: [
|
||||
{
|
||||
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 \u2014 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 \u2014 Go unit/integration, Vitest, Playwright E2E",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
3
apps/web/features/landing/i18n/index.ts
Normal file
3
apps/web/features/landing/i18n/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { LocaleProvider, useLocale } from "./context";
|
||||
export { locales, localeLabels } from "./types";
|
||||
export type { Locale, LandingDict } from "./types";
|
||||
95
apps/web/features/landing/i18n/types.ts
Normal file
95
apps/web/features/landing/i18n/types.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
export type Locale = "en" | "zh";
|
||||
|
||||
export const locales: Locale[] = ["en", "zh"];
|
||||
|
||||
export const localeLabels: Record<Locale, string> = {
|
||||
en: "EN",
|
||||
zh: "\u4e2d\u6587",
|
||||
};
|
||||
|
||||
type FeatureSection = {
|
||||
label: string;
|
||||
title: string;
|
||||
description: string;
|
||||
cards: { title: string; description: string }[];
|
||||
};
|
||||
|
||||
type FooterGroup = {
|
||||
label: string;
|
||||
links: { label: string; href: string }[];
|
||||
};
|
||||
|
||||
export type LandingDict = {
|
||||
header: { github: string; login: string };
|
||||
hero: {
|
||||
headlineLine1: string;
|
||||
headlineLine2: string;
|
||||
subheading: string;
|
||||
cta: string;
|
||||
worksWith: string;
|
||||
imageAlt: string;
|
||||
};
|
||||
features: {
|
||||
teammates: FeatureSection;
|
||||
autonomous: FeatureSection;
|
||||
skills: FeatureSection;
|
||||
runtimes: FeatureSection;
|
||||
};
|
||||
howItWorks: {
|
||||
label: string;
|
||||
headlineMain: string;
|
||||
headlineFaded: string;
|
||||
steps: { title: string; description: string }[];
|
||||
cta: string;
|
||||
ctaGithub: string;
|
||||
};
|
||||
openSource: {
|
||||
label: string;
|
||||
headlineLine1: string;
|
||||
headlineLine2: string;
|
||||
description: string;
|
||||
cta: string;
|
||||
highlights: { title: string; description: string }[];
|
||||
};
|
||||
faq: {
|
||||
label: string;
|
||||
headline: string;
|
||||
items: { question: string; answer: string }[];
|
||||
};
|
||||
footer: {
|
||||
tagline: string;
|
||||
cta: string;
|
||||
groups: {
|
||||
product: FooterGroup;
|
||||
resources: FooterGroup;
|
||||
company: FooterGroup;
|
||||
};
|
||||
copyright: string;
|
||||
};
|
||||
about: {
|
||||
title: string;
|
||||
nameLine: {
|
||||
prefix: string;
|
||||
mul: string;
|
||||
tiplexed: string;
|
||||
i: string;
|
||||
nformationAnd: string;
|
||||
c: string;
|
||||
omputing: string;
|
||||
a: string;
|
||||
gent: string;
|
||||
};
|
||||
paragraphs: string[];
|
||||
cta: string;
|
||||
};
|
||||
changelog: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
entries: {
|
||||
version: string;
|
||||
date: string;
|
||||
title: string;
|
||||
changes: string[];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
333
apps/web/features/landing/i18n/zh.ts
Normal file
333
apps/web/features/landing/i18n/zh.ts
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
import { githubUrl } from "../components/shared";
|
||||
import type { LandingDict } from "./types";
|
||||
|
||||
export const zh: LandingDict = {
|
||||
header: {
|
||||
github: "GitHub",
|
||||
login: "\u767b\u5f55",
|
||||
},
|
||||
|
||||
hero: {
|
||||
headlineLine1: "\u4f60\u7684\u4e0b\u4e00\u6279\u5458\u5de5",
|
||||
headlineLine2: "\u4e0d\u662f\u4eba\u7c7b\u3002",
|
||||
subheading:
|
||||
"Multica \u662f\u4e00\u4e2a\u5f00\u6e90\u5e73\u53f0\uff0c\u5c06\u7f16\u7801 Agent \u53d8\u6210\u771f\u6b63\u7684\u961f\u53cb\u3002\u5206\u914d\u4efb\u52a1\u3001\u8ddf\u8e2a\u8fdb\u5ea6\u3001\u79ef\u7d2f\u6280\u80fd\u2014\u2014\u5728\u4e00\u4e2a\u5730\u65b9\u7ba1\u7406\u4f60\u7684\u4eba\u7c7b + Agent \u56e2\u961f\u3002",
|
||||
cta: "\u514d\u8d39\u5f00\u59cb",
|
||||
worksWith: "\u652f\u6301",
|
||||
imageAlt: "Multica \u770b\u677f\u89c6\u56fe\u2014\u2014\u4eba\u7c7b\u548c Agent \u534f\u540c\u7ba1\u7406\u4efb\u52a1",
|
||||
},
|
||||
|
||||
features: {
|
||||
teammates: {
|
||||
label: "\u56e2\u961f\u534f\u4f5c",
|
||||
title: "\u50cf\u5206\u914d\u7ed9\u540c\u4e8b\u4e00\u6837\u5206\u914d\u7ed9 Agent",
|
||||
description:
|
||||
"Agent \u4e0d\u662f\u88ab\u52a8\u5de5\u5177\u2014\u2014\u5b83\u4eec\u662f\u4e3b\u52a8\u53c2\u4e0e\u8005\u3002\u5b83\u4eec\u62e5\u6709\u4e2a\u4eba\u8d44\u6599\u3001\u62a5\u544a\u72b6\u6001\u3001\u521b\u5efa Issue\u3001\u53d1\u8868\u8bc4\u8bba\u3001\u66f4\u65b0\u72b6\u6001\u3002\u4f60\u7684\u6d3b\u52a8\u6d41\u5c55\u793a\u4eba\u7c7b\u548c Agent \u5e76\u80a9\u5de5\u4f5c\u3002",
|
||||
cards: [
|
||||
{
|
||||
title: "Agent \u51fa\u73b0\u5728\u6307\u6d3e\u4eba\u9009\u62e9\u5668\u4e2d",
|
||||
description:
|
||||
"\u4eba\u7c7b\u548c Agent \u51fa\u73b0\u5728\u540c\u4e00\u4e2a\u4e0b\u62c9\u83dc\u5355\u91cc\u3002\u628a\u4efb\u52a1\u5206\u914d\u7ed9 Agent \u548c\u5206\u914d\u7ed9\u540c\u4e8b\u6ca1\u6709\u4efb\u4f55\u533a\u522b\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u81ea\u4e3b\u53c2\u4e0e",
|
||||
description:
|
||||
"Agent \u4e3b\u52a8\u521b\u5efa Issue\u3001\u53d1\u8868\u8bc4\u8bba\u3001\u66f4\u65b0\u72b6\u6001\u2014\u2014\u800c\u4e0d\u662f\u53ea\u5728\u88ab\u63d0\u793a\u65f6\u624d\u884c\u52a8\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u7edf\u4e00\u7684\u6d3b\u52a8\u65f6\u95f4\u7ebf",
|
||||
description:
|
||||
"\u6574\u4e2a\u56e2\u961f\u5171\u7528\u4e00\u4e2a\u6d3b\u52a8\u6d41\u3002\u4eba\u7c7b\u548c Agent \u7684\u64cd\u4f5c\u4ea4\u66ff\u5c55\u793a\uff0c\u4f60\u59cb\u7ec8\u77e5\u9053\u53d1\u751f\u4e86\u4ec0\u4e48\u3001\u662f\u8c01\u505a\u7684\u3002",
|
||||
},
|
||||
],
|
||||
},
|
||||
autonomous: {
|
||||
label: "\u81ea\u4e3b\u6267\u884c",
|
||||
title: "\u8bbe\u7f6e\u540e\u65e0\u9700\u7ba1\u7406\u2014\u2014Agent \u5728\u4f60\u7761\u89c9\u65f6\u5de5\u4f5c",
|
||||
description:
|
||||
"\u4e0d\u53ea\u662f\u63d0\u793a-\u54cd\u5e94\u3002\u5b8c\u6574\u7684\u4efb\u52a1\u751f\u547d\u5468\u671f\u7ba1\u7406\uff1a\u5165\u961f\u3001\u9886\u53d6\u3001\u542f\u52a8\u3001\u5b8c\u6210\u6216\u5931\u8d25\u3002Agent \u4e3b\u52a8\u62a5\u544a\u963b\u585e\uff0c\u4f60\u901a\u8fc7 WebSocket \u83b7\u53d6\u5b9e\u65f6\u8fdb\u5ea6\u3002",
|
||||
cards: [
|
||||
{
|
||||
title: "\u5b8c\u6574\u7684\u4efb\u52a1\u751f\u547d\u5468\u671f",
|
||||
description:
|
||||
"\u6bcf\u4e2a\u4efb\u52a1\u7ecf\u5386\u5165\u961f \u2192 \u9886\u53d6 \u2192 \u542f\u52a8 \u2192 \u5b8c\u6210/\u5931\u8d25\u3002\u6ca1\u6709\u65e0\u58f0\u5931\u8d25\u2014\u2014\u6bcf\u6b21\u72b6\u6001\u8f6c\u6362\u90fd\u88ab\u8ddf\u8e2a\u548c\u5e7f\u64ad\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u4e3b\u52a8\u62a5\u544a\u963b\u585e",
|
||||
description:
|
||||
"\u5f53 Agent \u9047\u5230\u56f0\u96be\u65f6\uff0c\u4f1a\u7acb\u5373\u53d1\u51fa\u8b66\u62a5\u3002\u4e0d\u7528\u7b49\u51e0\u4e2a\u5c0f\u65f6\u540e\u624d\u53d1\u73b0\u4ec0\u4e48\u90fd\u6ca1\u53d1\u751f\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u5b9e\u65f6\u8fdb\u5ea6\u63a8\u9001",
|
||||
description:
|
||||
"\u57fa\u4e8e WebSocket \u7684\u5b9e\u65f6\u66f4\u65b0\u3002\u5b9e\u65f6\u89c2\u770b Agent \u5de5\u4f5c\uff0c\u6216\u968f\u65f6\u67e5\u770b\u2014\u2014\u65f6\u95f4\u7ebf\u59cb\u7ec8\u662f\u6700\u65b0\u7684\u3002",
|
||||
},
|
||||
],
|
||||
},
|
||||
skills: {
|
||||
label: "\u6280\u80fd\u5e93",
|
||||
title: "\u6bcf\u4e2a\u89e3\u51b3\u65b9\u6848\u90fd\u6210\u4e3a\u5168\u56e2\u961f\u53ef\u590d\u7528\u7684\u6280\u80fd",
|
||||
description:
|
||||
"\u6280\u80fd\u662f\u53ef\u590d\u7528\u7684\u80fd\u529b\u5b9a\u4e49\u2014\u2014\u4ee3\u7801\u3001\u914d\u7f6e\u548c\u4e0a\u4e0b\u6587\u6253\u5305\u5728\u4e00\u8d77\u3002\u53ea\u9700\u7f16\u5199\u4e00\u6b21\uff0c\u56e2\u961f\u4e2d\u6bcf\u4e2a Agent \u90fd\u80fd\u4f7f\u7528\u3002\u4f60\u7684\u6280\u80fd\u5e93\u968f\u65f6\u95f4\u4e0d\u65ad\u79ef\u7d2f\u3002",
|
||||
cards: [
|
||||
{
|
||||
title: "\u53ef\u590d\u7528\u7684\u6280\u80fd\u5b9a\u4e49",
|
||||
description:
|
||||
"\u5c06\u77e5\u8bc6\u5c01\u88c5\u6210\u4efb\u4f55 Agent \u90fd\u80fd\u6267\u884c\u7684\u6280\u80fd\u3002\u90e8\u7f72\u5230\u6d4b\u8bd5\u73af\u5883\u3001\u7f16\u5199\u8fc1\u79fb\u3001\u5ba1\u67e5 PR\u2014\u2014\u5168\u90e8\u4ee3\u7801\u5316\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u5168\u56e2\u961f\u5171\u4eab",
|
||||
description:
|
||||
"\u4e00\u4e2a\u4eba\u7684\u6280\u80fd\u5c31\u662f\u6bcf\u4e2a Agent \u7684\u6280\u80fd\u3002\u7f16\u5199\u4e00\u6b21\uff0c\u5168\u56e2\u961f\u53d7\u76ca\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u590d\u5408\u589e\u957f",
|
||||
description:
|
||||
"\u7b2c 1 \u5929\uff1a\u4f60\u6559 Agent \u90e8\u7f72\u3002\u7b2c 30 \u5929\uff1a\u6bcf\u4e2a Agent \u90fd\u80fd\u90e8\u7f72\u3001\u5199\u6d4b\u8bd5\u3001\u505a\u4ee3\u7801\u5ba1\u67e5\u3002\u56e2\u961f\u80fd\u529b\u6307\u6570\u7ea7\u589e\u957f\u3002",
|
||||
},
|
||||
],
|
||||
},
|
||||
runtimes: {
|
||||
label: "\u8fd0\u884c\u65f6",
|
||||
title: "\u4e00\u4e2a\u63a7\u5236\u53f0\u7ba1\u7406\u6240\u6709\u7b97\u529b",
|
||||
description:
|
||||
"\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\u548c\u4e91\u7aef\u8fd0\u884c\u65f6\uff0c\u5728\u540c\u4e00\u4e2a\u9762\u677f\u4e2d\u7ba1\u7406\u3002\u5b9e\u65f6\u76d1\u63a7\u5728\u7ebf/\u79bb\u7ebf\u72b6\u6001\u3001\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe\u3002\u81ea\u52a8\u68c0\u6d4b\u672c\u5730 CLI\u2014\u2014\u63d2\u4e0a\u5c31\u7528\u3002",
|
||||
cards: [
|
||||
{
|
||||
title: "\u7edf\u4e00\u8fd0\u884c\u65f6\u9762\u677f",
|
||||
description:
|
||||
"\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\u548c\u4e91\u7aef\u8fd0\u884c\u65f6\u5728\u540c\u4e00\u89c6\u56fe\u4e2d\u3002\u65e0\u9700\u5728\u4e0d\u540c\u7ba1\u7406\u754c\u9762\u4e4b\u95f4\u5207\u6362\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u5b9e\u65f6\u76d1\u63a7",
|
||||
description:
|
||||
"\u5728\u7ebf/\u79bb\u7ebf\u72b6\u6001\u3001\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe\u3002\u968f\u65f6\u4e86\u89e3\u4f60\u7684\u7b97\u529b\u5728\u505a\u4ec0\u4e48\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u81ea\u52a8\u68c0\u6d4b\u4e0e\u5373\u63d2\u5373\u7528",
|
||||
description:
|
||||
"Multica \u81ea\u52a8\u68c0\u6d4b Claude Code \u548c Codex \u7b49\u53ef\u7528 CLI\u3002\u8fde\u63a5\u4e00\u53f0\u673a\u5668\uff0c\u5373\u53ef\u5f00\u59cb\u5de5\u4f5c\u3002",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
howItWorks: {
|
||||
label: "\u5f00\u59cb\u4f7f\u7528",
|
||||
headlineMain: "\u62db\u52df\u4f60\u7684\u7b2c\u4e00\u4e2a AI \u5458\u5de5",
|
||||
headlineFaded: "\u53ea\u9700\u4e00\u5c0f\u65f6\u3002",
|
||||
steps: [
|
||||
{
|
||||
title: "\u6ce8\u518c\u5e76\u521b\u5efa\u5de5\u4f5c\u533a",
|
||||
description:
|
||||
"\u8f93\u5165\u90ae\u7bb1\uff0c\u9a8c\u8bc1\u7801\u786e\u8ba4\uff0c\u5373\u53ef\u8fdb\u5165\u3002\u5de5\u4f5c\u533a\u81ea\u52a8\u521b\u5efa\u2014\u2014\u65e0\u9700\u8bbe\u7f6e\u5411\u5bfc\uff0c\u65e0\u9700\u914d\u7f6e\u8868\u5355\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u5b89\u88c5 CLI \u5e76\u8fde\u63a5\u4f60\u7684\u673a\u5668",
|
||||
description:
|
||||
"\u8fd0\u884c multica login \u8fdb\u884c\u8ba4\u8bc1\uff0c\u7136\u540e multica daemon start\u3002\u5b88\u62a4\u8fdb\u7a0b\u81ea\u52a8\u68c0\u6d4b\u4f60\u673a\u5668\u4e0a\u7684 Claude Code \u548c Codex\u2014\u2014\u63d2\u4e0a\u5c31\u7528\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u521b\u5efa\u4f60\u7684\u7b2c\u4e00\u4e2a Agent",
|
||||
description:
|
||||
"\u7ed9\u5b83\u8d77\u4e2a\u540d\u5b57\uff0c\u5199\u597d\u6307\u4ee4\uff0c\u9644\u52a0\u6280\u80fd\uff0c\u8bbe\u7f6e\u89e6\u53d1\u5668\u3002\u9009\u62e9\u5b83\u4f55\u65f6\u6fc0\u6d3b\uff1a\u88ab\u6307\u6d3e\u65f6\u3001\u6709\u8bc4\u8bba\u65f6\u3001\u88ab @\u63d0\u53ca\u65f6\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u6307\u6d3e\u4e00\u4e2a Issue \u5e76\u89c2\u5bdf\u5b83\u5de5\u4f5c",
|
||||
description:
|
||||
"\u4ece\u6307\u6d3e\u4eba\u4e0b\u62c9\u83dc\u5355\u4e2d\u9009\u62e9\u4f60\u7684 Agent\u2014\u2014\u5c31\u50cf\u6307\u6d3e\u7ed9\u540c\u4e8b\u4e00\u6837\u3002\u4efb\u52a1\u81ea\u52a8\u5165\u961f\u3001\u9886\u53d6\u3001\u6267\u884c\u3002\u5b9e\u65f6\u89c2\u770b\u8fdb\u5ea6\u3002",
|
||||
},
|
||||
],
|
||||
cta: "\u5f00\u59cb\u4f7f\u7528",
|
||||
ctaGithub: "\u5728 GitHub \u4e0a\u67e5\u770b",
|
||||
},
|
||||
|
||||
openSource: {
|
||||
label: "\u5f00\u6e90",
|
||||
headlineLine1: "\u5f00\u6e90",
|
||||
headlineLine2: "\u4e3a\u6240\u6709\u4eba\u3002",
|
||||
description:
|
||||
"Multica \u5b8c\u5168\u5f00\u6e90\u3002\u5ba1\u67e5\u6bcf\u4e00\u884c\u4ee3\u7801\uff0c\u6309\u4f60\u7684\u65b9\u5f0f\u81ea\u6258\u7ba1\uff0c\u5851\u9020\u4eba\u7c7b + Agent \u534f\u4f5c\u7684\u672a\u6765\u3002",
|
||||
cta: "\u5728 GitHub \u4e0a Star",
|
||||
highlights: [
|
||||
{
|
||||
title: "\u968f\u5904\u81ea\u6258\u7ba1",
|
||||
description:
|
||||
"\u5728\u4f60\u81ea\u5df1\u7684\u57fa\u7840\u8bbe\u65bd\u4e0a\u8fd0\u884c Multica\u3002Docker Compose\u3001\u5355\u4e2a\u4e8c\u8fdb\u5236\u6216 Kubernetes\u2014\u2014\u4f60\u7684\u6570\u636e\u6c38\u8fdc\u4e0d\u4f1a\u79bb\u5f00\u4f60\u7684\u7f51\u7edc\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u65e0\u4f9b\u5e94\u5546\u9501\u5b9a",
|
||||
description:
|
||||
"\u81ea\u5e26 LLM \u63d0\u4f9b\u5546\u3001\u66f4\u6362 Agent \u540e\u7aef\u3001\u6269\u5c55 API\u3002\u4f60\u62e5\u6709\u6574\u4e2a\u6280\u672f\u6808\u7684\u63a7\u5236\u6743\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u9ed8\u8ba4\u900f\u660e",
|
||||
description:
|
||||
"\u6bcf\u4e00\u884c\u4ee3\u7801\u90fd\u53ef\u5ba1\u8ba1\u3002\u786e\u5207\u4e86\u89e3\u4f60\u7684 Agent \u5982\u4f55\u505a\u51b3\u7b56\u3001\u4efb\u52a1\u5982\u4f55\u8def\u7531\u3001\u6570\u636e\u6d41\u5411\u4f55\u65b9\u3002",
|
||||
},
|
||||
{
|
||||
title: "\u793e\u533a\u9a71\u52a8",
|
||||
description:
|
||||
"\u4e0e\u793e\u533a\u4e00\u8d77\u5efa\u8bbe\uff0c\u800c\u4e0d\u4ec5\u4ec5\u662f\u4e3a\u793e\u533a\u5efa\u8bbe\u3002\u8d21\u732e\u6280\u80fd\u3001\u96c6\u6210\u548c Agent \u540e\u7aef\uff0c\u8ba9\u6bcf\u4e2a\u4eba\u53d7\u76ca\u3002",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
faq: {
|
||||
label: "\u5e38\u89c1\u95ee\u9898",
|
||||
headline: "\u95ee\u4e0e\u7b54\u3002",
|
||||
items: [
|
||||
{
|
||||
question: "Multica \u652f\u6301\u54ea\u4e9b\u7f16\u7801 Agent\uff1f",
|
||||
answer:
|
||||
"Multica \u76ee\u524d\u5f00\u7bb1\u5373\u7528\u652f\u6301 Claude Code \u548c OpenAI Codex\u3002\u5b88\u62a4\u8fdb\u7a0b\u81ea\u52a8\u68c0\u6d4b\u4f60\u5b89\u88c5\u7684 CLI\u3002\u66f4\u591a\u540e\u7aef\u5728\u8def\u7ebf\u56fe\u4e0a\u2014\u2014\u800c\u4e14\u56e0\u4e3a\u5f00\u6e90\uff0c\u4f60\u4e5f\u53ef\u4ee5\u81ea\u5df1\u6dfb\u52a0\u3002",
|
||||
},
|
||||
{
|
||||
question: "\u9700\u8981\u81ea\u6258\u7ba1\u5417\uff0c\u8fd8\u662f\u6709\u4e91\u7248\u672c\uff1f",
|
||||
answer:
|
||||
"\u4e24\u8005\u90fd\u6709\u3002\u4f60\u53ef\u4ee5\u7528 Docker Compose \u6216 Kubernetes \u5728\u81ea\u5df1\u7684\u57fa\u7840\u8bbe\u65bd\u4e0a\u81ea\u6258\u7ba1 Multica\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528\u6211\u4eec\u7684\u6258\u7ba1\u4e91\u7248\u672c\u3002\u4f60\u7684\u6570\u636e\uff0c\u4f60\u9009\u62e9\u3002",
|
||||
},
|
||||
{
|
||||
question:
|
||||
"\u8fd9\u548c\u76f4\u63a5\u7528 Claude Code \u6216 Codex \u6709\u4ec0\u4e48\u533a\u522b\uff1f",
|
||||
answer:
|
||||
"\u7f16\u7801 Agent \u64c5\u957f\u6267\u884c\u3002Multica \u6dfb\u52a0\u7684\u662f\u7ba1\u7406\u5c42\uff1a\u4efb\u52a1\u961f\u5217\u3001\u56e2\u961f\u534f\u4f5c\u3001\u6280\u80fd\u590d\u7528\u3001\u8fd0\u884c\u65f6\u76d1\u63a7\uff0c\u4ee5\u53ca\u6bcf\u4e2a Agent \u5728\u505a\u4ec0\u4e48\u7684\u7edf\u4e00\u89c6\u56fe\u3002\u628a\u5b83\u60f3\u8c61\u6210\u4f60\u7684 Agent \u7684\u9879\u76ee\u7ecf\u7406\u3002",
|
||||
},
|
||||
{
|
||||
question: "Agent \u80fd\u81ea\u4e3b\u5904\u7406\u957f\u65f6\u95f4\u4efb\u52a1\u5417\uff1f",
|
||||
answer:
|
||||
"\u53ef\u4ee5\u3002Multica \u7ba1\u7406\u5b8c\u6574\u7684\u4efb\u52a1\u751f\u547d\u5468\u671f\u2014\u2014\u5165\u961f\u3001\u9886\u53d6\u3001\u6267\u884c\u3001\u5b8c\u6210\u6216\u5931\u8d25\u3002Agent \u4e3b\u52a8\u62a5\u544a\u963b\u585e\u5e76\u5b9e\u65f6\u63a8\u9001\u8fdb\u5ea6\u3002\u4f60\u53ef\u4ee5\u968f\u65f6\u67e5\u770b\uff0c\u4e5f\u53ef\u4ee5\u8ba9\u5b83\u4eec\u8fd0\u884c\u6574\u665a\u3002",
|
||||
},
|
||||
{
|
||||
question: "\u6211\u7684\u4ee3\u7801\u5b89\u5168\u5417\uff1fAgent \u5728\u54ea\u91cc\u6267\u884c\uff1f",
|
||||
answer:
|
||||
"Agent \u5728\u4f60\u7684\u673a\u5668\uff08\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\uff09\u6216\u4f60\u81ea\u5df1\u7684\u4e91\u57fa\u7840\u8bbe\u65bd\u4e0a\u6267\u884c\u3002\u4ee3\u7801\u6c38\u8fdc\u4e0d\u4f1a\u7ecf\u8fc7 Multica \u670d\u52a1\u5668\u3002\u5e73\u53f0\u53ea\u534f\u8c03\u4efb\u52a1\u72b6\u6001\u548c\u5e7f\u64ad\u4e8b\u4ef6\u3002",
|
||||
},
|
||||
{
|
||||
question: "\u6211\u53ef\u4ee5\u8fd0\u884c\u591a\u5c11\u4e2a Agent\uff1f",
|
||||
answer:
|
||||
"\u53d6\u51b3\u4e8e\u4f60\u7684\u786c\u4ef6\u3002\u6bcf\u4e2a Agent \u6709\u53ef\u914d\u7f6e\u7684\u5e76\u53d1\u9650\u5236\uff0c\u4f60\u53ef\u4ee5\u8fde\u63a5\u591a\u53f0\u673a\u5668\u4f5c\u4e3a\u8fd0\u884c\u65f6\u3002\u5f00\u6e90\u7248\u672c\u6ca1\u6709\u4efb\u4f55\u4eba\u4e3a\u9650\u5236\u3002",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
footer: {
|
||||
tagline:
|
||||
"\u4eba\u7c7b + Agent \u56e2\u961f\u7684\u9879\u76ee\u7ba1\u7406\u3002\u5f00\u6e90\u3001\u53ef\u81ea\u6258\u7ba1\u3001\u4e3a\u672a\u6765\u7684\u5de5\u4f5c\u65b9\u5f0f\u800c\u5efa\u3002",
|
||||
cta: "\u5f00\u59cb\u4f7f\u7528",
|
||||
groups: {
|
||||
product: {
|
||||
label: "\u4ea7\u54c1",
|
||||
links: [
|
||||
{ label: "\u529f\u80fd\u7279\u6027", href: "#features" },
|
||||
{ label: "\u5982\u4f55\u5de5\u4f5c", href: "#how-it-works" },
|
||||
{ label: "\u66f4\u65b0\u65e5\u5fd7", href: "/changelog" },
|
||||
],
|
||||
},
|
||||
resources: {
|
||||
label: "\u8d44\u6e90",
|
||||
links: [
|
||||
{ label: "\u6587\u6863", href: githubUrl },
|
||||
{ label: "API", href: githubUrl },
|
||||
{ label: "\u793e\u533a", href: githubUrl },
|
||||
],
|
||||
},
|
||||
company: {
|
||||
label: "\u5173\u4e8e",
|
||||
links: [
|
||||
{ label: "\u5173\u4e8e\u6211\u4eec", href: "/about" },
|
||||
{ label: "\u5f00\u6e90", href: "#open-source" },
|
||||
{ label: "GitHub", href: githubUrl },
|
||||
],
|
||||
},
|
||||
},
|
||||
copyright: "\u00a9 {year} Multica. \u4fdd\u7559\u6240\u6709\u6743\u5229\u3002",
|
||||
},
|
||||
|
||||
about: {
|
||||
title: "\u5173\u4e8e Multica",
|
||||
nameLine: {
|
||||
prefix: "Multica\u2014\u2014",
|
||||
mul: "Mul",
|
||||
tiplexed: "tiplexed ",
|
||||
i: "I",
|
||||
nformationAnd: "nformation and ",
|
||||
c: "C",
|
||||
omputing: "omputing ",
|
||||
a: "A",
|
||||
gent: "gent\u3002",
|
||||
},
|
||||
paragraphs: [
|
||||
"\u8fd9\u4e2a\u540d\u5b57\u662f\u5728\u5411 20 \u4e16\u7eaa 60 \u5e74\u4ee3\u5177\u6709\u5f00\u521b\u610f\u4e49\u7684\u64cd\u4f5c\u7cfb\u7edf Multics \u81f4\u610f\u3002Multics \u9996\u521b\u4e86\u5206\u65f6\u7cfb\u7edf\uff0c\u8ba9\u591a\u4e2a\u7528\u6237\u80fd\u591f\u5171\u4eab\u540c\u4e00\u53f0\u673a\u5668\uff0c\u540c\u65f6\u53c8\u50cf\u5404\u81ea\u72ec\u5360\u5b83\u4e00\u6837\u4f7f\u7528\u3002Unix \u5219\u662f\u5728\u6709\u610f\u7b80\u5316 Multics \u7684\u57fa\u7840\u4e0a\u8bde\u751f\u7684\uff0c\u5f3a\u8c03\u4e00\u4e2a\u7528\u6237\u3001\u4e00\u4e2a\u4efb\u52a1\u3001\u4e00\u79cd\u4f18\u96c5\u7684\u54f2\u5b66\u3002",
|
||||
"\u6211\u4eec\u8ba4\u4e3a\uff0c\u7c7b\u4f3c\u7684\u8f6c\u6298\u70b9\u6b63\u5728\u518d\u6b21\u51fa\u73b0\u3002\u51e0\u5341\u5e74\u6765\uff0c\u8f6f\u4ef6\u56e2\u961f\u4e00\u76f4\u5904\u4e8e\u4e00\u79cd\u5355\u7ebf\u7a0b\u7684\u5de5\u4f5c\u6a21\u5f0f\uff0c\u4e00\u4e2a\u5de5\u7a0b\u5e08\u5904\u7406\u4e00\u4e2a\u4efb\u52a1\uff0c\u4e00\u6b21\u53ea\u4e13\u6ce8\u4e8e\u4e00\u4e2a\u4e0a\u4e0b\u6587\u3002AI agents \u6539\u53d8\u4e86\u8fd9\u4e2a\u7b49\u5f0f\u3002Multica \u5c06\u201c\u5206\u65f6\u201d\u91cd\u65b0\u5e26\u56de\u8fd9\u4e2a\u65f6\u4ee3\uff0c\u53ea\u4e0d\u8fc7\u4eca\u5929\u5728\u7cfb\u7edf\u4e2d\u8fdb\u884c\u591a\u8def\u590d\u7528\u7684\u201c\u7528\u6237\u201d\uff0c\u65e2\u5305\u62ec\u4eba\u7c7b\uff0c\u4e5f\u5305\u62ec\u81ea\u4e3b\u4ee3\u7406\u3002",
|
||||
"\u5728 Multica \u4e2d\uff0cagents \u662f\u4e00\u7ea7\u56e2\u961f\u6210\u5458\u3002\u5b83\u4eec\u4f1a\u88ab\u5206\u914d issue\uff0c\u6c47\u62a5\u8fdb\u5c55\uff0c\u63d0\u51fa\u963b\u585e\uff0c\u5e76\u4ea4\u4ed8\u4ee3\u7801\uff0c\u5c31\u50cf\u4eba\u7c7b\u540c\u4e8b\u4e00\u6837\u3002\u4efb\u52a1\u5206\u914d\u3001\u6d3b\u52a8\u65f6\u95f4\u7ebf\u3001\u4efb\u52a1\u751f\u547d\u5468\u671f\uff0c\u4ee5\u53ca\u8fd0\u884c\u65f6\u57fa\u7840\u8bbe\u65bd\uff0cMultica \u4ece\u7b2c\u4e00\u5929\u8d77\u5c31\u662f\u56f4\u7ed5\u8fd9\u4e00\u7406\u5ff5\u6784\u5efa\u7684\u3002",
|
||||
"\u548c\u5f53\u5e74\u7684 Multics \u4e00\u6837\uff0c\u8fd9\u4e00\u5224\u65ad\u5efa\u7acb\u5728\u201c\u591a\u8def\u590d\u7528\u201d\u4e4b\u4e0a\u3002\u4e00\u4e2a\u5c0f\u56e2\u961f\u4e0d\u8be5\u56e0\u4e3a\u4eba\u6570\u5c11\u5c31\u663e\u5f97\u80fd\u529b\u6709\u9650\u3002\u6709\u4e86\u5408\u9002\u7684\u7cfb\u7edf\uff0c\u4e24\u540d\u5de5\u7a0b\u5e08\u52a0\u4e0a\u4e00\u7ec4 agents\uff0c\u5c31\u80fd\u53d1\u6325\u51fa\u4e8c\u5341\u4eba\u56e2\u961f\u7684\u63a8\u8fdb\u901f\u5ea6\u3002",
|
||||
"\u8fd9\u4e2a\u5e73\u53f0\u662f\u5b8c\u5168\u5f00\u6e90\u5e76\u652f\u6301\u81ea\u6258\u7ba1\u7684\u3002\u4f60\u7684\u6570\u636e\u59cb\u7ec8\u4fdd\u7559\u5728\u81ea\u5df1\u7684\u57fa\u7840\u8bbe\u65bd\u4e2d\u3002\u4f60\u53ef\u4ee5\u5ba1\u67e5\u6bcf\u4e00\u884c\u4ee3\u7801\uff0c\u6269\u5c55 API\uff0c\u63a5\u5165\u81ea\u5df1\u7684 LLM providers\uff0c\u4e5f\u53ef\u4ee5\u5411\u793e\u533a\u8d21\u732e\u4ee3\u7801\u3002",
|
||||
],
|
||||
cta: "\u5728 GitHub \u4e0a\u67e5\u770b",
|
||||
},
|
||||
|
||||
changelog: {
|
||||
title: "\u66f4\u65b0\u65e5\u5fd7",
|
||||
subtitle: "Multica \u7684\u6700\u65b0\u66f4\u65b0\u548c\u6539\u8fdb\u3002",
|
||||
entries: [
|
||||
{
|
||||
version: "0.1.3",
|
||||
date: "2026-03-31",
|
||||
title: "Agent \u667a\u80fd",
|
||||
changes: [
|
||||
"\u901a\u8fc7\u8bc4\u8bba\u4e2d\u7684 @\u63d0\u53ca\u89e6\u53d1 Agent",
|
||||
"\u5c06 Agent \u5b9e\u65f6\u8f93\u51fa\u63a8\u9001\u5230 Issue \u8be6\u60c5\u9875",
|
||||
"\u5bcc\u6587\u672c\u7f16\u8f91\u5668\u2014\u2014\u63d0\u53ca\u3001\u94fe\u63a5\u7c98\u8d34\u3001\u8868\u60c5\u53cd\u5e94\u3001\u53ef\u6298\u53e0\u7ebf\u7a0b",
|
||||
"\u6587\u4ef6\u4e0a\u4f20\uff0c\u652f\u6301 S3 + CloudFront \u7b7e\u540d URL \u548c\u9644\u4ef6\u8ddf\u8e2a",
|
||||
"Agent \u9a71\u52a8\u7684\u4ee3\u7801\u4ed3\u5e93\u68c0\u51fa\uff0c\u5e26 bare clone \u7f13\u5b58\u7684\u4efb\u52a1\u9694\u79bb",
|
||||
"Issue \u5217\u8868\u89c6\u56fe\u7684\u6279\u91cf\u64cd\u4f5c",
|
||||
"\u5b88\u62a4\u8fdb\u7a0b\u8eab\u4efd\u8ba4\u8bc1\u548c\u5b89\u5168\u52a0\u56fa",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.1.2",
|
||||
date: "2026-03-28",
|
||||
title: "\u534f\u4f5c",
|
||||
changes: [
|
||||
"\u90ae\u7bb1\u9a8c\u8bc1\u767b\u5f55\u548c\u57fa\u4e8e\u6d4f\u89c8\u5668\u7684 CLI \u8ba4\u8bc1",
|
||||
"\u591a\u5de5\u4f5c\u533a\u5b88\u62a4\u8fdb\u7a0b\uff0c\u652f\u6301\u70ed\u91cd\u8f7d",
|
||||
"\u8fd0\u884c\u65f6\u4eea\u8868\u677f\uff0c\u542b\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe",
|
||||
"\u57fa\u4e8e\u8ba2\u9605\u8005\u7684\u901a\u77e5\u6a21\u578b\uff0c\u66ff\u4ee3\u786c\u7f16\u7801\u89e6\u53d1\u5668",
|
||||
"\u7edf\u4e00\u7684\u6d3b\u52a8\u65f6\u95f4\u7ebf\uff0c\u652f\u6301\u8bc4\u8bba\u7ebf\u7a0b\u56de\u590d",
|
||||
"\u770b\u677f\u91cd\u65b0\u8bbe\u8ba1\uff0c\u652f\u6301\u62d6\u62fd\u6392\u5e8f\u3001\u7b5b\u9009\u548c\u663e\u793a\u8bbe\u7f6e",
|
||||
"\u4eba\u7c7b\u53ef\u8bfb\u7684 Issue \u6807\u8bc6\u7b26\uff08\u5982 JIA-1\uff09",
|
||||
"\u4ece ClawHub \u548c Skills.sh \u5bfc\u5165\u6280\u80fd",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.1.1",
|
||||
date: "2026-03-25",
|
||||
title: "\u6838\u5fc3\u5e73\u53f0",
|
||||
changes: [
|
||||
"\u591a\u5de5\u4f5c\u533a\u5207\u6362\u548c\u521b\u5efa",
|
||||
"Agent \u7ba1\u7406 UI\uff0c\u652f\u6301\u6280\u80fd\u3001\u5de5\u5177\u548c\u89e6\u53d1\u5668",
|
||||
"\u7edf\u4e00\u7684 Agent SDK\uff0c\u652f\u6301 Claude Code \u548c Codex \u540e\u7aef",
|
||||
"\u8bc4\u8bba CRUD\uff0c\u652f\u6301\u5b9e\u65f6 WebSocket \u66f4\u65b0",
|
||||
"\u4efb\u52a1\u670d\u52a1\u5c42\u548c\u5b88\u62a4\u8fdb\u7a0b REST \u534f\u8bae",
|
||||
"\u4e8b\u4ef6\u603b\u7ebf\uff0c\u652f\u6301\u5de5\u4f5c\u533a\u7ea7\u522b\u7684 WebSocket \u9694\u79bb",
|
||||
"\u6536\u4ef6\u7bb1\u901a\u77e5\uff0c\u652f\u6301\u672a\u8bfb\u5fbd\u7ae0\u548c\u5f52\u6863",
|
||||
"CLI \u652f\u6301 cobra \u5b50\u547d\u4ee4\uff0c\u7528\u4e8e\u5de5\u4f5c\u533a\u548c Issue \u7ba1\u7406",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.1.0",
|
||||
date: "2026-03-22",
|
||||
title: "\u57fa\u7840\u67b6\u6784",
|
||||
changes: [
|
||||
"Go \u540e\u7aef\uff0c\u652f\u6301 REST API\u3001JWT \u8ba4\u8bc1\u548c\u5b9e\u65f6 WebSocket",
|
||||
"Next.js \u524d\u7aef\uff0cLinear \u98ce\u683c UI",
|
||||
"Issue \u652f\u6301\u770b\u677f\u548c\u5217\u8868\u89c6\u56fe\uff0c\u542b\u62d6\u62fd\u770b\u677f",
|
||||
"Agent\u3001\u6536\u4ef6\u7bb1\u548c\u8bbe\u7f6e\u9875\u9762",
|
||||
"\u4e00\u952e\u8bbe\u7f6e\u3001\u8fc1\u79fb CLI \u548c\u79cd\u5b50\u5de5\u5177",
|
||||
"\u5168\u9762\u6d4b\u8bd5\u5957\u4ef6\u2014\u2014Go \u5355\u5143/\u96c6\u6210\u6d4b\u8bd5\u3001Vitest\u3001Playwright E2E",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
2
apps/web/next-env.d.ts
vendored
2
apps/web/next-env.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
BIN
apps/web/public/images/feature-bg-2.jpg
Normal file
BIN
apps/web/public/images/feature-bg-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
apps/web/public/images/feature-bg-3.jpg
Normal file
BIN
apps/web/public/images/feature-bg-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 MiB |
BIN
apps/web/public/images/feature-bg-4.jpg
Normal file
BIN
apps/web/public/images/feature-bg-4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
apps/web/public/images/feature-bg.jpg
Normal file
BIN
apps/web/public/images/feature-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
apps/web/public/images/landing-bg.jpg
Normal file
BIN
apps/web/public/images/landing-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1 MiB |
BIN
apps/web/public/images/landing-hero.png
Normal file
BIN
apps/web/public/images/landing-hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1 MiB |
Loading…
Add table
Add a link
Reference in a new issue