chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import type { HTMLAttributes, ReactNode } from "react";
|
||||
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||
import rehypePrettyCode from "rehype-pretty-code";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { docs, getAdjacentDocs } from "@/lib/docs";
|
||||
import { extractHeadings, readArticleByPath } from "@/lib/articles";
|
||||
import { pageMetadata } from "@/lib/page-metadata";
|
||||
import { DocsToc } from "@/components/docs-toc";
|
||||
import { CopyCodeButton } from "@/components/copy-button";
|
||||
import { HeadingAnchor } from "@/components/heading-anchor";
|
||||
import { rehypeZeroHighlight } from "@/lib/rehype-zero-highlight";
|
||||
import { rehypeJsonRender } from "@/lib/rehype-json-render";
|
||||
import { AgentChat } from "@/components/agent-chat";
|
||||
import { FlowChart } from "@/components/flow-chart";
|
||||
|
||||
type DocsPageParams = { slug?: string[] };
|
||||
type DocsPageProps = { params: Promise<DocsPageParams> };
|
||||
|
||||
export function generateStaticParams(): DocsPageParams[] {
|
||||
return docs.map((doc) => ({
|
||||
slug: doc.path.replace(/^\//, "").split("/"),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: DocsPageProps): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
return pageMetadata((slug ?? []).join("/"));
|
||||
}
|
||||
|
||||
const rehypePrettyCodeOptions = {
|
||||
theme: { light: "github-light", dark: "github-dark" },
|
||||
keepBackground: false,
|
||||
};
|
||||
|
||||
type HeadingProps = HTMLAttributes<HTMLHeadingElement> & {
|
||||
id?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
function makeHeading(Tag: "h2" | "h3" | "h4") {
|
||||
return function Heading({ id, children, ...rest }: HeadingProps) {
|
||||
return (
|
||||
<Tag id={id} {...rest} className="group">
|
||||
{children}
|
||||
<HeadingAnchor id={id} />
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const mdxComponents = {
|
||||
pre: ({ children, ...props }: HTMLAttributes<HTMLPreElement>) => (
|
||||
<div data-code-block className="relative">
|
||||
<CopyCodeButton />
|
||||
<pre {...props}>{children}</pre>
|
||||
</div>
|
||||
),
|
||||
h2: makeHeading("h2"),
|
||||
h3: makeHeading("h3"),
|
||||
h4: makeHeading("h4"),
|
||||
agentchat: ({ value }: { value?: string }) => {
|
||||
if (!value) return null;
|
||||
try {
|
||||
const spec = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
||||
return <AgentChat spec={spec} />;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
flowchart: ({ value }: { value?: string }) => {
|
||||
if (!value) return null;
|
||||
try {
|
||||
const spec = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
||||
return <FlowChart spec={spec} />;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default async function DocsPage({ params }: DocsPageProps) {
|
||||
const { slug } = await params;
|
||||
const routePath = "/" + (slug ?? []).join("/");
|
||||
const result = await readArticleByPath(routePath);
|
||||
if (!result) notFound();
|
||||
|
||||
const { doc, source } = result;
|
||||
const headings = extractHeadings(source);
|
||||
const { prev, next } = getAdjacentDocs(doc.slug);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="mx-auto w-full max-w-[54rem] px-4 pb-[50vh] pt-6 md:px-12">
|
||||
<header className="mb-8 border-b border-border pb-6">
|
||||
<p className="mb-2 text-[0.8125rem] font-medium tracking-wide text-muted">
|
||||
{doc.section ?? "Documentation"}
|
||||
</p>
|
||||
<h1 className="m-0 text-[clamp(1.75rem,5vw,2.5rem)] font-bold leading-[1.15] tracking-[-0.04em]">
|
||||
{doc.title}
|
||||
</h1>
|
||||
<p className="m-0 mt-3 max-w-[42rem] leading-[1.7] text-muted">{doc.description}</p>
|
||||
</header>
|
||||
|
||||
<article className="prose prose-zinc dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-headings:tracking-[-0.03em] prose-headings:font-semibold prose-a:text-blue prose-a:font-medium prose-a:no-underline prose-a:hover:underline prose-code:font-normal">
|
||||
<MDXRemote
|
||||
source={source}
|
||||
components={mdxComponents}
|
||||
options={{
|
||||
mdxOptions: {
|
||||
format: "md",
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [
|
||||
rehypeJsonRender,
|
||||
rehypeSlug,
|
||||
[rehypePrettyCode, rehypePrettyCodeOptions],
|
||||
rehypeZeroHighlight,
|
||||
],
|
||||
},
|
||||
parseFrontmatter: false,
|
||||
}}
|
||||
/>
|
||||
</article>
|
||||
|
||||
<nav aria-label="Pagination" className="mt-16 grid grid-cols-1 gap-4 border-t border-border pt-8 md:grid-cols-2">
|
||||
{prev ? (
|
||||
<Link
|
||||
href={prev.path}
|
||||
className="flex flex-col gap-1 rounded-lg border border-border p-4 no-underline transition hover:border-muted"
|
||||
>
|
||||
<span className="text-xs font-medium uppercase tracking-[0.04em] text-muted">Previous</span>
|
||||
<span className="text-[0.9375rem] font-semibold text-blue">{prev.title}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{next ? (
|
||||
<Link
|
||||
href={next.path}
|
||||
className="flex flex-col items-end gap-1 rounded-lg border border-border p-4 text-right no-underline transition hover:border-muted"
|
||||
>
|
||||
<span className="text-xs font-medium uppercase tracking-[0.04em] text-muted">Next</span>
|
||||
<span className="text-[0.9375rem] font-semibold text-blue">{next.title}</span>
|
||||
</Link>
|
||||
) : null}
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<DocsToc headings={headings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { DocsSidebarShell } from "@/components/docs-sidebar";
|
||||
import { docs, groupBySection } from "@/lib/docs";
|
||||
|
||||
export default function DocsLayout({ children }: { children: ReactNode }) {
|
||||
const groups = groupBySection(docs);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-screen grid-cols-1 md:grid-cols-[15rem_minmax(0,1fr)] lg:grid-cols-[15rem_minmax(0,1fr)_14rem]">
|
||||
<DocsSidebarShell groups={groups} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { convertToModelMessages, stepCountIs, streamText } from "ai";
|
||||
import type { ModelMessage, UIMessage } from "ai";
|
||||
import { createBashTool } from "bash-tool";
|
||||
import { docs } from "@/lib/docs";
|
||||
import { readArticleBySlug } from "@/lib/articles";
|
||||
import {
|
||||
docsChatDailyRateLimit,
|
||||
docsChatMinuteRateLimit,
|
||||
isDocsChatRateLimitConfigured,
|
||||
} from "@/lib/rate-limit";
|
||||
|
||||
export const maxDuration = 60;
|
||||
|
||||
const DEFAULT_MODEL = "anthropic/claude-haiku-4.5";
|
||||
|
||||
const SYSTEM_PROMPT = `You are a helpful documentation assistant for Zero, a pre-1 experimental programming language project exploring agent-first design. Zero intentionally makes breaking changes while searching for the language, library, and tooling patterns that work best for agents. Security vulnerabilities should be expected; Zero should be run or developed only in safe, non-production environments.
|
||||
|
||||
GitHub repository: https://github.com/vercel-labs/zerolang
|
||||
Documentation: https://zerolang.ai
|
||||
|
||||
You have access to the full Zero documentation via the bash and readFile tools. The docs are available as markdown files in the /workspace/ directory. The file layout matches each page's URL slug — e.g. /workspace/index.md is the home, /workspace/getting-started.md is /getting-started, /workspace/modules/parse.md is /modules/parse.
|
||||
|
||||
When answering questions:
|
||||
- Use the bash tool to list files (ls /workspace/) or search for content (grep -r "keyword" /workspace/)
|
||||
- Use the readFile tool to read specific documentation pages (e.g. readFile with path "/workspace/getting-started.md")
|
||||
- Do NOT use bash to write, create, modify, or delete files (no tee, cat >, sed -i, echo >, cp, mv, rm, mkdir, touch, etc.) — you are read-only
|
||||
- Always base your answers on the actual documentation content
|
||||
- Be concise and accurate
|
||||
- If the docs don't cover a topic, say so honestly
|
||||
- Do NOT include source references or file paths in your response
|
||||
- Do NOT use emojis in your responses
|
||||
- When showing Zero projection syntax, use \`\`\`zero fenced blocks and say it is a human-readable projection of the graph. When showing shell commands, use \`\`\`sh.`;
|
||||
|
||||
type DocsFiles = Record<string, string>;
|
||||
|
||||
let cachedDocsFiles: DocsFiles | null = null;
|
||||
|
||||
async function loadDocsFiles(): Promise<DocsFiles> {
|
||||
if (cachedDocsFiles) return cachedDocsFiles;
|
||||
|
||||
const files: DocsFiles = {};
|
||||
const results = await Promise.allSettled(
|
||||
docs.map(async (doc) => {
|
||||
const md = await readArticleBySlug(doc.slug);
|
||||
if (!md) return null;
|
||||
const fileName = doc.path === "/" ? "/index.md" : `${doc.path}.md`;
|
||||
return { fileName: fileName.replace(/^\//, ""), md };
|
||||
}),
|
||||
);
|
||||
for (const result of results) {
|
||||
if (result.status === "fulfilled" && result.value) {
|
||||
files[result.value.fileName] = result.value.md;
|
||||
}
|
||||
}
|
||||
cachedDocsFiles = files;
|
||||
return files;
|
||||
}
|
||||
|
||||
function addCacheControl(messages: ModelMessage[]): ModelMessage[] {
|
||||
if (messages.length === 0) return messages;
|
||||
return messages.map((message, index) => {
|
||||
if (index === messages.length - 1) {
|
||||
return {
|
||||
...message,
|
||||
providerOptions: {
|
||||
...message.providerOptions,
|
||||
anthropic: { cacheControl: { type: "ephemeral" } },
|
||||
},
|
||||
};
|
||||
}
|
||||
return message;
|
||||
});
|
||||
}
|
||||
|
||||
function getRateLimitIdentifier(req: Request): string {
|
||||
const forwardedFor = req.headers.get("x-forwarded-for");
|
||||
const realIp = req.headers.get("x-real-ip");
|
||||
return forwardedFor?.split(",")[0]?.trim() || realIp?.trim() || "anonymous";
|
||||
}
|
||||
|
||||
async function rateLimitRequest(req: Request): Promise<NextResponse | null> {
|
||||
if (!isDocsChatRateLimitConfigured()) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Chat rate limiting is not configured",
|
||||
message: "Set KV_REST_API_URL and KV_REST_API_TOKEN to enable docs chat.",
|
||||
},
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
const identifier = getRateLimitIdentifier(req);
|
||||
let minuteResult;
|
||||
let dailyResult;
|
||||
try {
|
||||
[minuteResult, dailyResult] = await Promise.all([
|
||||
docsChatMinuteRateLimit.limit(identifier),
|
||||
docsChatDailyRateLimit.limit(identifier),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Docs chat rate limiting failed", error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Chat rate limiting unavailable",
|
||||
message: "Docs chat is temporarily unavailable because rate limiting could not be applied.",
|
||||
},
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
if (minuteResult.success && dailyResult.success) return null;
|
||||
|
||||
const isMinuteLimit = !minuteResult.success;
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Rate limit exceeded",
|
||||
message: isMinuteLimit
|
||||
? "Too many requests. Please wait a moment before trying again."
|
||||
: "Daily limit reached. Please try again tomorrow.",
|
||||
},
|
||||
{ status: 429 },
|
||||
);
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
if (!process.env.AI_GATEWAY_API_KEY && !process.env.VERCEL_OIDC_TOKEN) {
|
||||
return NextResponse.json(
|
||||
{ error: "Chat is not configured. Set AI_GATEWAY_API_KEY or configure deployment OIDC." },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
const rateLimitResponse = await rateLimitRequest(req);
|
||||
if (rateLimitResponse) return rateLimitResponse;
|
||||
|
||||
let body: { messages?: UIMessage[] } | null;
|
||||
try {
|
||||
body = (await req.json()) as { messages?: UIMessage[] };
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const messages = Array.isArray(body?.messages) ? body.messages : [];
|
||||
|
||||
const docsFiles = await loadDocsFiles();
|
||||
const {
|
||||
tools: { bash, readFile },
|
||||
} = await createBashTool({ files: docsFiles });
|
||||
|
||||
const result = streamText({
|
||||
model: DEFAULT_MODEL,
|
||||
system: SYSTEM_PROMPT,
|
||||
messages: await convertToModelMessages(messages),
|
||||
stopWhen: stepCountIs(5),
|
||||
tools: { bash, readFile },
|
||||
prepareStep: ({ messages: stepMessages }) => ({
|
||||
messages: addCacheControl(stepMessages),
|
||||
}),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getSearchIndex } from "@/lib/search-index";
|
||||
import type { SearchResult } from "@/lib/types";
|
||||
|
||||
type ScoredSearchResult = SearchResult & { score: number };
|
||||
|
||||
function isScoredResult(result: ScoredSearchResult | null): result is ScoredSearchResult {
|
||||
return result !== null;
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const q = req.nextUrl.searchParams.get("q")?.trim().toLowerCase();
|
||||
if (!q) return NextResponse.json({ results: [] });
|
||||
|
||||
const index = await getSearchIndex();
|
||||
const terms = q.split(/\s+/).filter(Boolean);
|
||||
|
||||
const results = index
|
||||
.map((entry) => {
|
||||
const titleLower = entry.title.toLowerCase();
|
||||
const contentLower = entry.content.toLowerCase();
|
||||
|
||||
const titleMatch = terms.every((t) => titleLower.includes(t));
|
||||
const contentMatch = terms.every((t) => contentLower.includes(t));
|
||||
|
||||
if (!titleMatch && !contentMatch) return null;
|
||||
|
||||
let snippet = "";
|
||||
if (contentMatch) {
|
||||
const firstTermIdx = Math.min(
|
||||
...terms.map((t) => {
|
||||
const idx = contentLower.indexOf(t);
|
||||
return idx === -1 ? Infinity : idx;
|
||||
}),
|
||||
);
|
||||
if (firstTermIdx !== Infinity) {
|
||||
const start = Math.max(0, firstTermIdx - 40);
|
||||
const end = Math.min(entry.content.length, firstTermIdx + 120);
|
||||
snippet =
|
||||
(start > 0 ? "..." : "") +
|
||||
entry.content.slice(start, end).replace(/\n/g, " ") +
|
||||
(end < entry.content.length ? "..." : "");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: entry.title,
|
||||
href: entry.href,
|
||||
section: entry.section,
|
||||
snippet,
|
||||
score: titleMatch ? 2 : 1,
|
||||
};
|
||||
})
|
||||
.filter(isScoredResult)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 20)
|
||||
.map(({ score: _, ...rest }) => rest);
|
||||
|
||||
return NextResponse.json({ results }, { headers: { "Cache-Control": "public, max-age=60" } });
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,312 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "@tailwindcss/typography";
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
@custom-variant dark (&:is(.dark *, .dark));
|
||||
|
||||
@theme {
|
||||
--color-bg: #ffffff;
|
||||
--color-fg: #171717;
|
||||
--color-muted: #666666;
|
||||
--color-border: #eaeaea;
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-muted: #fafafa;
|
||||
--color-accent: #000000;
|
||||
--color-accent-fg: #ffffff;
|
||||
--color-blue: #0070f3;
|
||||
--color-blue-hover: #0060df;
|
||||
--color-success: #0070f3;
|
||||
--color-danger: #ee0000;
|
||||
--color-danger-bg: #fff0f0;
|
||||
--color-code-bg: #f5f5f5;
|
||||
--color-code-fg: #171717;
|
||||
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
|
||||
--container-content: 72rem;
|
||||
|
||||
--shadow-card: 0 4px 14px 0 rgb(0 0 0 / 0.1);
|
||||
--shadow-soft: 0 2px 8px rgb(0 0 0 / 0.08);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-bg: #000000;
|
||||
--color-fg: #ededed;
|
||||
--color-muted: #888888;
|
||||
--color-border: #333333;
|
||||
--color-surface: #111111;
|
||||
--color-surface-muted: #0a0a0a;
|
||||
--color-accent: #ffffff;
|
||||
--color-accent-fg: #000000;
|
||||
--color-blue: #3291ff;
|
||||
--color-blue-hover: #52a9ff;
|
||||
--color-danger: #ff4444;
|
||||
--color-danger-bg: #2a0000;
|
||||
--color-code-bg: #111111;
|
||||
--color-code-fg: #ededed;
|
||||
--shadow-card: 0 4px 14px 0 rgb(0 0 0 / 0.25);
|
||||
--shadow-soft: 0 2px 8px rgb(0 0 0 / 0.2);
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-fg);
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: color-mix(in srgb, var(--color-blue) 25%, transparent);
|
||||
}
|
||||
|
||||
/* ─── Shiki (rehype-pretty-code) ───────────────────── */
|
||||
|
||||
[data-rehype-pretty-code-figure] {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-figure] pre {
|
||||
overflow-x: auto;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--color-code-bg);
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-figure] pre > code {
|
||||
display: grid;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-figure] pre > code [data-line] {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-title] {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-muted);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-bottom: 0;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-title] + pre {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.prose :where(h1, h2, h3, h4, h5, h6) a,
|
||||
.prose :where(h1, h2, h3, h4, h5, h6) a:hover {
|
||||
color: inherit;
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-figure] [data-theme] code {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-figure] span {
|
||||
color: var(--shiki-light);
|
||||
}
|
||||
|
||||
.dark [data-rehype-pretty-code-figure] span {
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
|
||||
/* ─── Inline syntax highlight (home page) ──────────── */
|
||||
|
||||
.hl-comment { color: #6b7280; font-style: italic; }
|
||||
.hl-string { color: #067a6e; }
|
||||
.hl-keyword { color: #d6409f; }
|
||||
.hl-type { color: #0070c0; }
|
||||
.hl-function { color: #6f42c1; }
|
||||
.hl-number { color: #0070c0; }
|
||||
.hl-variable { color: var(--color-fg); }
|
||||
.hl-id { color: #6f42c1; }
|
||||
.hl-key { color: #d6409f; }
|
||||
.hl-boolean { color: #0070c0; }
|
||||
.hl-punctuation { color: var(--color-fg); }
|
||||
.hl-operator { color: #d6409f; }
|
||||
|
||||
.dark .hl-comment { color: #a1a1a1; }
|
||||
.dark .hl-string { color: #00ca50; }
|
||||
.dark .hl-keyword { color: #ff4d8d; }
|
||||
.dark .hl-type { color: #c472fb; }
|
||||
.dark .hl-function { color: #c472fb; }
|
||||
.dark .hl-number { color: #47a8ff; }
|
||||
.dark .hl-variable { color: #47a8ff; }
|
||||
.dark .hl-id { color: #c472fb; }
|
||||
.dark .hl-key { color: #ff4d8d; }
|
||||
.dark .hl-boolean { color: #47a8ff; }
|
||||
.dark .hl-punctuation { color: #ededed; }
|
||||
.dark .hl-operator { color: #ff4d8d; }
|
||||
|
||||
.hl-bash-function {
|
||||
color: var(--color-fg);
|
||||
font-weight: 600;
|
||||
}
|
||||
.hl-bash-key { color: #0070f3; }
|
||||
.hl-bash-string { color: #067a6e; }
|
||||
.hl-bash-keyword { color: #d6409f; }
|
||||
.hl-bash-variable { color: #6f42c1; }
|
||||
.hl-bash-operator,
|
||||
.hl-bash-punctuation {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.hl-bash-number { color: var(--color-fg); }
|
||||
|
||||
.dark .hl-bash-function { color: #ffffff; }
|
||||
.dark .hl-bash-key { color: #52a9ff; }
|
||||
.dark .hl-bash-string { color: #00ca50; }
|
||||
.dark .hl-bash-keyword { color: #ff4d8d; }
|
||||
.dark .hl-bash-variable { color: #c472fb; }
|
||||
.dark .hl-bash-operator,
|
||||
.dark .hl-bash-punctuation {
|
||||
color: #888888;
|
||||
}
|
||||
.dark .hl-bash-number { color: #ffffff; }
|
||||
|
||||
/* ─── Home chat motion ────────────────────────────── */
|
||||
|
||||
@keyframes home-chat-reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(0.875rem) scale(0.985);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes home-command-sheen {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-120%);
|
||||
}
|
||||
28% {
|
||||
opacity: 0.18;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(120%);
|
||||
}
|
||||
}
|
||||
|
||||
.home-chat-shell {
|
||||
transform-origin: center top;
|
||||
}
|
||||
|
||||
.home-chat-code {
|
||||
background: color-mix(in srgb, var(--color-fg) 9%, var(--color-bg));
|
||||
border: 1px solid color-mix(in srgb, var(--color-fg) 14%, transparent);
|
||||
border-radius: 0.375rem;
|
||||
color: var(--color-fg);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.78125rem;
|
||||
padding: 0.125rem 0.45rem;
|
||||
}
|
||||
|
||||
.home-chat-reveal,
|
||||
.home-tool-row {
|
||||
opacity: 0;
|
||||
transform: translateY(0.875rem) scale(0.985);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.home-chat-viewport[data-chat-visible="true"] .home-chat-reveal,
|
||||
.home-chat-viewport[data-chat-visible="true"] .home-tool-row {
|
||||
animation: home-chat-reveal 620ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
animation-delay: var(--chat-delay, 0ms);
|
||||
}
|
||||
|
||||
.home-tool-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.home-tool-row::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.home-chat-viewport[data-chat-visible="true"] .home-tool-row::after {
|
||||
animation: home-command-sheen 1150ms ease-out forwards;
|
||||
animation-delay: calc(var(--chat-delay, 0ms) + 140ms);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
color-mix(in srgb, var(--color-fg) 18%, transparent),
|
||||
transparent
|
||||
);
|
||||
content: "";
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
transform: translateX(-120%);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.home-chat-reveal,
|
||||
.home-tool-row {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.home-tool-row::after {
|
||||
animation: none;
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Prose adjustments ────────────────────────────── */
|
||||
|
||||
.prose pre {
|
||||
background: var(--color-code-bg) !important;
|
||||
color: var(--color-code-fg) !important;
|
||||
}
|
||||
|
||||
.prose :where(code):not(:where([class~="not-prose"] *)) {
|
||||
background: var(--color-surface-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.15rem 0.4rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::before,
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.prose :where(pre code):not(:where([class~="not-prose"] *)) {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import "./globals.css";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { Analytics } from "@vercel/analytics/next";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { SiteHeader } from "@/components/site-header";
|
||||
import { DocsChat } from "@/components/docs-chat";
|
||||
import { getStarCount } from "@/lib/github";
|
||||
|
||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://zerolang.ai";
|
||||
const SITE_DESCRIPTION =
|
||||
"Zero is a pre-1 agent-first language experiment. Expect breaking changes and run it only in safe, non-production environments.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(SITE_URL),
|
||||
title: {
|
||||
default: "Zero - Agent-first language experiment.",
|
||||
template: "%s | Zero",
|
||||
},
|
||||
description: SITE_DESCRIPTION,
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: SITE_URL,
|
||||
siteName: "Zero",
|
||||
title: "Zero - Agent-first language experiment.",
|
||||
description: SITE_DESCRIPTION,
|
||||
images: [{ url: "/og", width: 1200, height: 630, alt: "Zero" }],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Zero - Agent-first language experiment.",
|
||||
description: SITE_DESCRIPTION,
|
||||
images: ["/og"],
|
||||
},
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
export default async function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
|
||||
const stars = await getStarCount();
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className="bg-bg text-fg antialiased">
|
||||
<ThemeProvider>
|
||||
<SiteHeader stars={stars} />
|
||||
{children}
|
||||
<DocsChat />
|
||||
</ThemeProvider>
|
||||
<Analytics />
|
||||
<SpeedInsights />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ButtonLink } from "@/components/button";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main className="mx-auto w-[min(100%-3rem,42rem)] py-16">
|
||||
<p className="mb-2 text-[0.8125rem] font-medium uppercase tracking-[0.04em] text-muted">404</p>
|
||||
<h1 className="mb-4 text-3xl font-bold tracking-tight">Not found</h1>
|
||||
<p className="mb-6 text-muted">The page you requested does not exist.</p>
|
||||
<ButtonLink href="/">Go home</ButtonLink>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getPageTitle, renderOgImage } from "../og-image";
|
||||
|
||||
type OgRouteContext = { params: Promise<{ slug: string[] }> };
|
||||
|
||||
export async function GET(_request: Request, { params }: OgRouteContext) {
|
||||
const { slug } = await params;
|
||||
const title = getPageTitle(slug.join("/"));
|
||||
|
||||
if (!title) {
|
||||
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
return renderOgImage(title);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { ImageResponse } from "next/og";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
export { getPageTitle } from "@/lib/page-titles";
|
||||
|
||||
type FontCache = {
|
||||
geistRegular: Buffer;
|
||||
geistPixelSquare: Buffer;
|
||||
};
|
||||
|
||||
let fontCache: FontCache | null = null;
|
||||
|
||||
async function loadFonts(): Promise<FontCache> {
|
||||
if (fontCache) return fontCache;
|
||||
const [geistRegular, geistPixelSquare] = await Promise.all([
|
||||
readFile(join(process.cwd(), "public/Geist-Regular.ttf")),
|
||||
readFile(join(process.cwd(), "public/GeistPixel-Square.ttf")),
|
||||
]);
|
||||
fontCache = { geistRegular, geistPixelSquare };
|
||||
return fontCache;
|
||||
}
|
||||
|
||||
export async function renderOgImage(title: string): Promise<ImageResponse> {
|
||||
const { geistRegular, geistPixelSquare } = await loadFonts();
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "black",
|
||||
padding: "60px 80px",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
|
||||
<svg width="36" height="32" viewBox="0 0 76 65" fill="white">
|
||||
<path d="M37.5274 0L75.0548 65H0L37.5274 0Z" />
|
||||
</svg>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 36,
|
||||
color: "#666",
|
||||
fontFamily: "Geist",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
/
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 36,
|
||||
fontFamily: "GeistPixelSquare",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
zero
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{title.split("\n").map((line, i) => (
|
||||
<span
|
||||
key={i}
|
||||
style={{
|
||||
fontSize: 72,
|
||||
fontFamily: "Geist",
|
||||
fontWeight: 400,
|
||||
color: "white",
|
||||
letterSpacing: "-0.02em",
|
||||
textAlign: "center",
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{line}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
fonts: [
|
||||
{
|
||||
name: "Geist",
|
||||
data: geistRegular,
|
||||
style: "normal",
|
||||
weight: 400,
|
||||
},
|
||||
{
|
||||
name: "GeistPixelSquare",
|
||||
data: geistPixelSquare,
|
||||
style: "normal",
|
||||
weight: 400,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getPageTitle, renderOgImage } from "./og-image";
|
||||
|
||||
export async function GET() {
|
||||
const title = getPageTitle("") ?? "Zero";
|
||||
return renderOgImage(title);
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
import { ArrowRightIcon, LogoIcon } from "@/components/icons";
|
||||
import type { CSSProperties, ReactNode } from "react";
|
||||
import { ButtonLink } from "@/components/button";
|
||||
import { InstallCopy } from "@/components/install-copy";
|
||||
import { HomeChatViewport } from "@/components/home-chat-viewport";
|
||||
import { ChatToolRuns } from "@/components/home-chat-tools";
|
||||
import { LoopDiagram } from "@/components/loop-diagram";
|
||||
import { highlight } from "@/lib/highlight";
|
||||
import { pageMetadata } from "@/lib/page-metadata";
|
||||
|
||||
export const metadata = pageMetadata("");
|
||||
|
||||
const CODE_EXAMPLE = `<span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span> <span class="hl-variable">main</span>(world: <span class="hl-type">World</span>) -> <span class="hl-type">Void</span> <span class="hl-keyword">raises</span> {
|
||||
<span class="hl-keyword">check</span> world.out.<span class="hl-variable">write</span>(<span class="hl-string">"hello from zero\\n"</span>)
|
||||
}`;
|
||||
|
||||
const GRAPH_EXAMPLE = `zero-graph v1
|
||||
origin source-text
|
||||
module "hello"
|
||||
hash "graph:a7f7e6899a73f3b4"
|
||||
|
||||
node #decl_ad8d9028 Function name:"main" type:"Void" public:true fallible:true
|
||||
node #param_4610ae76 Param name:"world" type:"World"
|
||||
node #expr_c403020c MethodCall name:"write" type:"Void"
|
||||
node #expr_653eeb6e Literal type:"String" value:"hello from zero\\n"
|
||||
edge #expr_c403020c arg #expr_653eeb6e order:0`;
|
||||
|
||||
const PATCH_EXAMPLE = `zero patch \\
|
||||
--expect-graph-hash graph:a7f7e6899a73f3b4 \\
|
||||
--op 'set node="#expr_653eeb6e" \\
|
||||
field="value" \\
|
||||
expect="hello from zero\\n" \\
|
||||
value="hello graph\\n"'`;
|
||||
|
||||
const CONSTRAINTS = [
|
||||
"Token efficiency",
|
||||
"Low memory",
|
||||
"Fast startup",
|
||||
"Fast builds",
|
||||
"Low latency",
|
||||
"Zero dependencies",
|
||||
];
|
||||
|
||||
const ARCHITECTURE_STEPS = [
|
||||
{
|
||||
label: "Human",
|
||||
title: "Asks for an outcome",
|
||||
body: "Build a CRM API, add auth, or fix a failing route",
|
||||
},
|
||||
{
|
||||
label: "Agent",
|
||||
title: "Uses Graph",
|
||||
body: "Symbols, calls, types, effects, node IDs, and graph hashes",
|
||||
},
|
||||
{
|
||||
label: "Compiler",
|
||||
title: "Checks patches",
|
||||
body: "Shape, type, stale-state, and repository metadata checks",
|
||||
},
|
||||
{
|
||||
label: "Human",
|
||||
title: "Reviews projection",
|
||||
body: "Readable .0 projections stay available for review and rare manual edits",
|
||||
},
|
||||
];
|
||||
|
||||
/* ─── Primitives ──────────────────────────────────── */
|
||||
|
||||
function GridGlow() {
|
||||
return (
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.4] dark:opacity-[0.25]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(to right, var(--color-border) 1px, transparent 1px), linear-gradient(to bottom, var(--color-border) 1px, transparent 1px)",
|
||||
backgroundSize: "64px 64px",
|
||||
maskImage:
|
||||
"radial-gradient(ellipse 80% 60% at 50% 0%, #000 30%, transparent 75%)",
|
||||
WebkitMaskImage:
|
||||
"radial-gradient(ellipse 80% 60% at 50% 0%, #000 30%, transparent 75%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionHeader({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-12 max-w-[44rem]">
|
||||
<h2 className="m-0 max-w-[14ch] text-[clamp(2.25rem,6vw,4.25rem)] font-semibold leading-[0.98] tracking-[-0.045em]">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="mt-6 max-w-[34rem] text-pretty text-[1.0625rem] leading-[1.65] text-muted">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VisualGlow({ className = "", children }: { className?: string; children: ReactNode }) {
|
||||
return <div className={className}>{children}</div>;
|
||||
}
|
||||
|
||||
function Panel({ className = "", children }: { className?: string; children: ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className={`relative overflow-hidden rounded-2xl border border-fg/30 ${className}`}
|
||||
style={{ boxShadow: "0 1px 0 0 color-mix(in srgb, var(--color-fg) 4%, transparent)" }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WindowBar({ title }: { title: string }) {
|
||||
return (
|
||||
<div className="relative flex items-center border-b border-fg/30 px-4 py-3">
|
||||
<div className="flex gap-1.5">
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-border" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-border" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-border" />
|
||||
</div>
|
||||
<span className="absolute left-1/2 -translate-x-1/2 font-mono text-xs font-medium text-muted">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeWindow({
|
||||
title,
|
||||
html,
|
||||
className = "",
|
||||
}: {
|
||||
title: string;
|
||||
html: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Panel className={`bg-bg ${className}`}>
|
||||
<WindowBar title={title} />
|
||||
<pre className="m-0 overflow-x-auto bg-bg p-5 text-[0.8125rem] leading-[1.7]">
|
||||
<code className="text-code-fg" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
</pre>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function ArchitectureDiagram() {
|
||||
return (
|
||||
<div className="mt-12 w-full text-left">
|
||||
<div className="grid grid-cols-1 gap-px overflow-hidden rounded-2xl border border-border bg-border sm:grid-cols-2 lg:grid-cols-4">
|
||||
{ARCHITECTURE_STEPS.map((item, index) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="group relative flex flex-col bg-bg p-7 transition-colors duration-200 hover:bg-surface-muted"
|
||||
>
|
||||
<div className="mb-7 flex items-center gap-2.5">
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-md border border-border font-mono text-[0.6875rem] tabular-nums text-muted transition-colors group-hover:border-fg/40 group-hover:text-fg">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="font-mono text-[0.6875rem] font-semibold uppercase tracking-[0.16em] text-muted">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="m-0 text-[1.0625rem] font-semibold leading-snug tracking-[-0.02em] text-fg">
|
||||
{item.title}
|
||||
</h2>
|
||||
<p className="m-0 mt-2.5 text-[0.8125rem] leading-[1.65] text-muted">
|
||||
{item.body}
|
||||
</p>
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute right-0 top-1/2 z-10 hidden -translate-y-1/2 translate-x-[calc(50%+1px)] lg:flex"
|
||||
>
|
||||
{index < ARCHITECTURE_STEPS.length - 1 ? (
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded-full border border-border bg-bg text-muted">
|
||||
<ArrowRightIcon width={12} height={12} />
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Chat mockup ─────────────────────────────────── */
|
||||
|
||||
function chatRevealStyle(delay: string): CSSProperties {
|
||||
return { "--chat-delay": delay } as CSSProperties;
|
||||
}
|
||||
|
||||
function ChatMockup() {
|
||||
return (
|
||||
<HomeChatViewport>
|
||||
<Panel className="home-chat-shell bg-bg shadow-card">
|
||||
<WindowBar title="your agent" />
|
||||
|
||||
<div className="flex flex-col gap-5 p-5 sm:p-6">
|
||||
<div className="home-chat-reveal flex justify-end" style={chatRevealStyle("80ms")}>
|
||||
<div className="max-w-[82%] rounded-2xl rounded-br-md bg-fg px-4 py-2.5 text-[0.875rem] leading-relaxed text-bg">
|
||||
build hello world in zerolang
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="home-chat-reveal max-w-[92%] text-[0.875rem] leading-[1.65] text-fg"
|
||||
style={chatRevealStyle("360ms")}
|
||||
>
|
||||
I'll set up the package, patch the graph, and run it.
|
||||
</div>
|
||||
|
||||
<ChatToolRuns startDelayMs={640} />
|
||||
|
||||
<div
|
||||
className="home-chat-reveal max-w-[92%] text-[0.875rem] leading-[1.65] text-fg"
|
||||
style={chatRevealStyle("1320ms")}
|
||||
>
|
||||
Done. <code className="home-chat-code">zero.graph</code> validated at{" "}
|
||||
<code className="home-chat-code">graph:a7f7e689</code> with symbol{" "}
|
||||
<code className="home-chat-code">main</code>. It prints:
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="home-chat-reveal home-chat-output rounded-lg border border-border bg-code-bg px-4 py-3 font-mono text-[0.8125rem] text-code-fg"
|
||||
style={chatRevealStyle("1520ms")}
|
||||
>
|
||||
hello from zero
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</HomeChatViewport>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Section wrapper ─────────────────────────────── */
|
||||
|
||||
function Section({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<section className="relative z-10 mx-auto w-[min(100%-3rem,var(--container-content))] border-t border-border py-[clamp(5rem,11vh,8rem)]">
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden">
|
||||
<main>
|
||||
{/* Hero */}
|
||||
<section className="relative overflow-hidden px-6 pb-24 pt-[clamp(4rem,11vh,7rem)]">
|
||||
<GridGlow />
|
||||
<div className="relative z-10 mx-auto flex max-w-[54rem] flex-col items-center text-center">
|
||||
<div className="mb-7 inline-flex items-center rounded-full border border-border bg-surface/70 px-3.5 py-1 font-mono text-[0.6875rem] uppercase tracking-[0.16em] text-muted backdrop-blur">
|
||||
Experimental
|
||||
</div>
|
||||
<h1 className="m-0 text-[clamp(2.5rem,7vw,5rem)] font-semibold leading-[0.98] tracking-[-0.05em]">
|
||||
The Programming
|
||||
<br />
|
||||
Language for Agents
|
||||
</h1>
|
||||
<p className="mt-7 max-w-[40rem] text-pretty text-[clamp(1.0625rem,2vw,1.25rem)] leading-[1.6] text-muted">
|
||||
A programming language where the graph is the program. Humans ask
|
||||
for outcomes. Agents query the program graph, submit checked
|
||||
edits, and prove the result.
|
||||
</p>
|
||||
<InstallCopy />
|
||||
<ArchitectureDiagram />
|
||||
<p className="mt-5 max-w-[34rem] text-[0.8125rem] leading-relaxed text-muted">
|
||||
Expect breaking changes. Run it in a safe environment, not against
|
||||
production systems.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 01 — Chat */}
|
||||
<Section>
|
||||
<SectionHeader
|
||||
title="Start with a request."
|
||||
description="The expected workflow is a normal conversation. The graph discipline lives in the agent skills and compiler commands, not in stiff human prompts."
|
||||
/>
|
||||
<VisualGlow className="mx-auto max-w-[40rem]">
|
||||
<ChatMockup />
|
||||
</VisualGlow>
|
||||
</Section>
|
||||
|
||||
{/* 02 — Loop */}
|
||||
<Section>
|
||||
<SectionHeader
|
||||
title="Tighter agent loop."
|
||||
description="A traditional loop writes text, then runs tools to learn what the edit meant. Zerolang puts the compiler in the loop, so an edit is a checked change to the graph."
|
||||
/>
|
||||
<LoopDiagram />
|
||||
</Section>
|
||||
|
||||
{/* 03 — Patch */}
|
||||
<Section>
|
||||
<SectionHeader
|
||||
title="Checked by default."
|
||||
description="Graph patches target semantic nodes and fields, guarded by graph hashes and expected values. Stale or invalid edits fail before they touch the store."
|
||||
/>
|
||||
<VisualGlow className="grid grid-cols-1 gap-5 lg:grid-cols-[1.4fr_1fr]">
|
||||
<CodeWindow title="zero patch" html={highlight(PATCH_EXAMPLE, "sh")} />
|
||||
<Panel className="bg-bg">
|
||||
<dl className="m-0 divide-y divide-border/50 font-mono text-[0.8125rem]">
|
||||
{[
|
||||
["graph hash", "graph:b3c1d04f"],
|
||||
["node", "#expr_653eeb6e"],
|
||||
["field", "value"],
|
||||
["symbols", "main"],
|
||||
["projection", "src/main.0"],
|
||||
].map(([k, v]) => (
|
||||
<div key={k} className="flex items-center justify-between gap-4 px-5 py-3">
|
||||
<dt className="text-muted">{k}</dt>
|
||||
<dd className="m-0 truncate text-code-fg">{v}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</Panel>
|
||||
</VisualGlow>
|
||||
</Section>
|
||||
|
||||
{/* 04 — Graph */}
|
||||
<Section>
|
||||
<SectionHeader
|
||||
title="The program database."
|
||||
description="Readable text stays useful for review. The compiler-owned graph is the program database agents edit and the compiler consumes."
|
||||
/>
|
||||
<VisualGlow className="grid grid-cols-1 gap-5 lg:grid-cols-2">
|
||||
<CodeWindow title="zero query · graph" html={highlight(GRAPH_EXAMPLE, "zero-graph")} />
|
||||
<CodeWindow title="src/main.0 · projection" html={CODE_EXAMPLE} />
|
||||
</VisualGlow>
|
||||
</Section>
|
||||
|
||||
{/* 04 — Constraints */}
|
||||
<Section>
|
||||
<SectionHeader
|
||||
title="Built for runtime constraints."
|
||||
description="The graph model should reduce guessing without relaxing the runtime goals. Zerolang still aims to stay small, fast, explicit, and dependency-free."
|
||||
/>
|
||||
<VisualGlow>
|
||||
<div className="grid grid-cols-2 gap-px overflow-hidden rounded-2xl border border-border bg-border md:grid-cols-3">
|
||||
{CONSTRAINTS.map((c) => (
|
||||
<div
|
||||
key={c}
|
||||
className="flex items-center gap-3 bg-bg px-6 py-7 text-[0.9375rem] font-medium tracking-[-0.01em] transition-colors hover:bg-surface-muted"
|
||||
>
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-fg/30" />
|
||||
{c}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</VisualGlow>
|
||||
</Section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="relative z-10 border-t border-border">
|
||||
<div className="mx-auto flex w-[min(100%-3rem,var(--container-content))] flex-col items-center px-6 py-[clamp(6rem,14vh,9rem)] text-center">
|
||||
<h2 className="m-0 text-[clamp(2rem,6vw,3.5rem)] font-semibold leading-[1.02] tracking-[-0.045em]">
|
||||
Explore with us.
|
||||
</h2>
|
||||
<p className="mx-auto mt-6 max-w-[38rem] text-pretty text-[1.0625rem] leading-[1.6] text-muted">
|
||||
Start with the getting started guide, then read the graph architecture
|
||||
and compile-path pages to see why the program database matters.
|
||||
</p>
|
||||
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
|
||||
<ButtonLink href="/getting-started" variant="primary" size="lg">
|
||||
Get started <ArrowRightIcon />
|
||||
</ButtonLink>
|
||||
<ButtonLink href="/concepts/graph-architecture" variant="default" size="lg">
|
||||
Graph architecture <ArrowRightIcon />
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className="relative z-10 border-t border-border py-8">
|
||||
<div className="mx-auto flex w-[min(100%-3rem,var(--container-content))] flex-col items-center gap-2 text-center md:flex-row md:justify-between md:text-left">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-fg">
|
||||
<LogoIcon width="14" height="12" />
|
||||
<span>zerolang</span>
|
||||
</div>
|
||||
<p className="m-0 text-[0.8125rem] text-muted">
|
||||
Experimental graph-first language design.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user