feat(web): marketing landing page with Anthropic design system
Landing page at / matching claude.com/product/claude-code structure: hero, surfaces, pricing, laptop-to-laptop, features, meets-you, faq, cta, + floating "Latest news" toaster. Motion-based scroll reveals. Design system extracted from claude.com via playwriter reverse-engineering: - Self-hosted Anthropic Sans/Serif/Mono fonts (6 woff2 files) - --cm-* tokens in globals.css (clay #d97757, gray-050..900, fluid clamps) - Serif display, Sans UI, Mono terminals & section markers - Italic clay phrases for emphasis Header rewritten for design consistency: claudemesh wordmark (mesh glyph + serif), dark bg, nav (Docs · Pricing · Changelog · GitHub), "Start free" CTA. Free-first messaging: hero subhead "Free and open-source. Forever.", primary CTA "Start free", pricing defaults to Solo=Free. Fixes: - packages/api: comment out aiRouter (module removed in1f094c4) - packages/db/schema/mesh.ts: rename memberRelations → meshMemberRelations (missed inbeeaa3brename pass, caught via web build — ack'd by BotMou) - credits/{api,server,index}: stub out @turbostarter/ai/credits/utils - remove (marketing)/legal/[slug] route and common/mdx.tsx (cms-backed) - sitemap: drop blog/legal enumeration (cms removed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,89 +1,103 @@
|
||||
import { getTranslation } from "@turbostarter/i18n/server";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@turbostarter/ui-web/accordion";
|
||||
import { buttonVariants } from "@turbostarter/ui-web/button";
|
||||
import { Icons } from "@turbostarter/ui-web/icons";
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { Reveal } from "./_reveal";
|
||||
|
||||
import { pathsConfig } from "~/config/paths";
|
||||
import { TurboLink } from "~/modules/common/turbo-link";
|
||||
import {
|
||||
Section,
|
||||
SectionHeader,
|
||||
SectionBadge,
|
||||
SectionTitle,
|
||||
SectionDescription,
|
||||
} from "~/modules/marketing/layout/section";
|
||||
|
||||
const questions = [
|
||||
{
|
||||
question: "faq.question.whatDoesOurPlatformDo.question",
|
||||
answer: "faq.question.whatDoesOurPlatformDo.answer",
|
||||
},
|
||||
{
|
||||
question: "faq.question.howWillThisBenefitMyBusiness.question",
|
||||
answer: "faq.question.howWillThisBenefitMyBusiness.answer",
|
||||
},
|
||||
{
|
||||
question: "faq.question.isMyDataSafe.question",
|
||||
answer: "faq.question.isMyDataSafe.answer",
|
||||
},
|
||||
{
|
||||
question: "faq.question.whatKindOfIntegrationsAreAvailable.question",
|
||||
answer: "faq.question.whatKindOfIntegrationsAreAvailable.answer",
|
||||
},
|
||||
{
|
||||
question: "faq.question.howEasyIsItToOnboardMyTeam.question",
|
||||
answer: "faq.question.howEasyIsItToOnboardMyTeam.answer",
|
||||
},
|
||||
{
|
||||
question: "faq.question.whatTypesOfBusinessesCanUseThis.question",
|
||||
answer: "faq.question.whatTypesOfBusinessesCanUseThis.answer",
|
||||
},
|
||||
{
|
||||
question: "faq.question.canICustomizeThisToFitMyBusinessNeeds.question",
|
||||
answer: "faq.question.canICustomizeThisToFitMyBusinessNeeds.answer",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const Faq = async () => {
|
||||
const { t } = await getTranslation({ ns: "marketing" });
|
||||
const ITEMS = [
|
||||
{
|
||||
q: "Is claudemesh free?",
|
||||
a: "Yes — the broker, CLI, dashboard, and SDK are MIT-licensed and free forever. Solo developers and small teams can self-host at no cost. Paid tiers add hosted brokers, SSO, audit retention, and support.",
|
||||
},
|
||||
{
|
||||
q: "How do I get started?",
|
||||
a: "Install the broker with one curl command. Add one env var to your Claude Code config. Your session joins the mesh. `npx claudemesh init` does both in 60 seconds.",
|
||||
},
|
||||
{
|
||||
q: "Does claudemesh send my code or prompts to the cloud?",
|
||||
a: "No. The broker is a local WebSocket server. Messages stay on your network. The only data that leaves your machines is what your Claude Code already sends to Anthropic — we don't touch it.",
|
||||
},
|
||||
{
|
||||
q: "Do I need to run a server?",
|
||||
a: "Yes — one machine on your network runs the broker. That can be your laptop, a shared dev box, a Raspberry Pi, or a container in your cluster. It's one binary, SQLite-backed, ~15 MB.",
|
||||
},
|
||||
{
|
||||
q: "Does it work across offices / continents?",
|
||||
a: "Yes. Put the broker on a VPS, or expose it through Tailscale / WireGuard. Every Claude Code session that can reach the broker joins the mesh.",
|
||||
},
|
||||
{
|
||||
q: "How does it route messages?",
|
||||
a: "By peer name (alex, jordan), by repo, or by priority level (now / next / low). Messages are queued until the target peer is idle, then delivered. You can also set a peer to DND to block all but priority:now.",
|
||||
},
|
||||
{
|
||||
q: "Which Claude Code versions work with claudemesh?",
|
||||
a: "Claude Code 2.0 and above. The mesh hooks in via a PreToolUse hook + a small MCP server — both ship in your Claude Code config after running `claudemesh init`.",
|
||||
},
|
||||
];
|
||||
|
||||
export const FAQ = () => {
|
||||
const [open, setOpen] = useState<number | null>(0);
|
||||
return (
|
||||
<Section id="faq" className="lg:flex-row lg:items-start">
|
||||
<SectionHeader className="grow basis-0 lg:items-start">
|
||||
<SectionBadge>{t("faq.label")}</SectionBadge>
|
||||
<SectionTitle className="lg:text-left">{t("faq.title")}</SectionTitle>
|
||||
<SectionDescription className="lg:text-left">
|
||||
{t("faq.description")}
|
||||
</SectionDescription>
|
||||
|
||||
<TurboLink
|
||||
href={pathsConfig.marketing.contact}
|
||||
className={buttonVariants({
|
||||
variant: "outline",
|
||||
<section className="border-b border-[var(--cm-border)] bg-[var(--cm-bg-elevated)] px-6 py-24 md:px-12 md:py-32">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<Reveal>
|
||||
<h2
|
||||
className="mb-16 text-center text-[clamp(2rem,4.5vw,3.25rem)] font-medium leading-[1.1] text-[var(--cm-fg)]"
|
||||
style={{ fontFamily: "var(--cm-font-serif)" }}
|
||||
>
|
||||
FAQ
|
||||
</h2>
|
||||
</Reveal>
|
||||
<div className="divide-y divide-[var(--cm-border)] border-y border-[var(--cm-border)]">
|
||||
{ITEMS.map((item, i) => {
|
||||
const isOpen = open === i;
|
||||
return (
|
||||
<Reveal key={i} delay={i * 0.5}>
|
||||
<button
|
||||
onClick={() => setOpen(isOpen ? null : i)}
|
||||
className="group flex w-full items-start justify-between gap-8 py-6 text-left transition-colors"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<h3
|
||||
className={
|
||||
"text-xl font-medium leading-snug transition-colors " +
|
||||
(isOpen
|
||||
? "text-[var(--cm-fg)]"
|
||||
: "text-[var(--cm-fg-secondary)] group-hover:text-[var(--cm-fg)]")
|
||||
}
|
||||
style={{ fontFamily: "var(--cm-font-serif)" }}
|
||||
>
|
||||
{item.q}
|
||||
</h3>
|
||||
<span
|
||||
className={
|
||||
"flex-shrink-0 text-2xl leading-none text-[var(--cm-clay)] transition-transform duration-300 " +
|
||||
(isOpen ? "rotate-45" : "rotate-0")
|
||||
}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className={
|
||||
"grid overflow-hidden transition-all duration-500 " +
|
||||
(isOpen
|
||||
? "grid-rows-[1fr] pb-6 opacity-100"
|
||||
: "grid-rows-[0fr] opacity-0")
|
||||
}
|
||||
>
|
||||
<div className="min-h-0">
|
||||
<p
|
||||
className="max-w-3xl text-base leading-[1.7] text-[var(--cm-fg-secondary)]"
|
||||
style={{ fontFamily: "var(--cm-font-serif)" }}
|
||||
>
|
||||
{item.a}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
>
|
||||
{t("faq.cta")}
|
||||
<Icons.ArrowRight className="ml-1 size-4" />
|
||||
</TurboLink>
|
||||
</SectionHeader>
|
||||
|
||||
<Accordion type="multiple" className="grow basis-0">
|
||||
{questions.map((question) => (
|
||||
<AccordionItem key={question.question} value={question.question}>
|
||||
<AccordionTrigger className="text-base">
|
||||
{t(question.question)}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="text-base">
|
||||
{t(question.answer)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user