diff --git a/apps/marketing/app/globals.css b/apps/marketing/app/globals.css
index 5be67d608..e1f14def5 100644
--- a/apps/marketing/app/globals.css
+++ b/apps/marketing/app/globals.css
@@ -1,7 +1,7 @@
@import "tailwindcss";
@import "tw-animate-css";
-@custom-variant dark (&:is(.dark *));
+@custom-variant dark (&:is(.dark, .dark *));
/* Add scrollbar-hide utility */
@utility scrollbar-hide {
@@ -12,162 +12,62 @@
}
}
-/* Custom thin scrollbar styling */
-* {
- scrollbar-width: thin;
- scrollbar-color: var(--muted) var(--secondary);
-}
-
-::-webkit-scrollbar {
- width: 6px;
-}
-
-::-webkit-scrollbar-track {
- background: var(--secondary);
-}
-
-::-webkit-scrollbar-thumb {
- background: var(--muted);
- border-radius: 3px;
-}
-
:root {
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --destructive-foreground: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
+ color-scheme: dark;
--radius: 0.625rem;
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
-
- /* Semantic status and UI tokens */
- --status-progress: oklch(0.795 0.184 86.047);
- --status-success: oklch(0.765 0.177 163.223);
- --status-warning: oklch(0.769 0.188 70.08);
- --status-error: oklch(0.637 0.237 25.331);
- --brand: oklch(0.585 0.233 277.117);
- --brand-foreground: oklch(0.985 0 0);
- --surface: oklch(0.97 0 0);
- --surface-elevated: oklch(0.985 0 0);
- --highlight: oklch(0.488 0.243 264.376);
- --highlight-foreground: oklch(0.985 0 0);
-
- /* Semantic tokens for favorites/labels */
- --favorite-blue: oklch(0.646 0.222 41.116);
- --favorite-purple: oklch(0.627 0.265 303.9);
- --label-project: oklch(0.828 0.189 84.429);
-
- /* Code syntax highlighting tokens */
- --syntax-keyword: oklch(0.646 0.222 41.116);
- --syntax-string: oklch(0.6 0.118 184.704);
- --syntax-function: oklch(0.828 0.189 84.429);
- --syntax-variable: oklch(0.769 0.188 70.08);
-
- /* Added new semantic tokens for landing page sections */
- /* Section accent colors */
- --accent-ai: oklch(0.623 0.214 259.815);
- --accent-workflows: oklch(0.705 0.213 47.604);
- --accent-planning: oklch(0.723 0.219 149.579);
- /* Code block syntax colors (light mode) */
- --code-comment: oklch(0.556 0 0);
- --code-keyword: oklch(0.627 0.265 303.9);
- --code-string: oklch(0.723 0.219 149.579);
- --code-function: oklch(0.769 0.188 70.08);
- --code-variable: oklch(0.795 0.184 86.047);
- --code-type: oklch(0.6 0.118 184.704);
- --code-constant: oklch(0.705 0.213 47.604);
-}
-
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.145 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.145 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.985 0 0);
- --primary-foreground: oklch(0.205 0 0);
+ /* Dark-mode-only palette */
+ --background: color-mix(in srgb, #0a0a1a 95%, white);
+ --foreground: #f5f5f5;
+ --card: color-mix(in srgb, #0a0a1a 95%, white);
+ --card-foreground: #f5f5f5;
+ --popover: color-mix(in srgb, #0a0a1a 95%, white);
+ --popover-foreground: #f5f5f5;
+ --primary: oklch(0.588 0.217 264);
+ --primary-foreground: #f5f5f5;
--secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
+ --secondary-foreground: #f5f5f5;
--muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
+ --muted-foreground: color-mix(in srgb, #a3a3a3 90%, white);
--accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
+ --accent-foreground: #f5f5f5;
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
- --border: oklch(0.269 0 0);
- --input: oklch(0.269 0 0);
- --ring: oklch(0.439 0 0);
+ --border: oklch(1 0 0 / 0.06);
+ --input: oklch(1 0 0 / 0.06);
+ --ring: oklch(0.588 0.217 264 / 0.4);
+
+ /* Chart colors */
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
+
+ /* Sidebar */
--sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-foreground: #f5f5f5;
+ --sidebar-primary: oklch(0.588 0.217 264);
+ --sidebar-primary-foreground: #f5f5f5;
--sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(0.269 0 0);
- --sidebar-ring: oklch(0.439 0 0);
+ --sidebar-accent-foreground: #f5f5f5;
+ --sidebar-border: oklch(1 0 0 / 0.06);
+ --sidebar-ring: oklch(0.588 0.217 264 / 0.4);
- /* Dark mode semantic tokens */
- --status-progress: oklch(0.795 0.184 86.047);
- --status-success: oklch(0.765 0.177 163.223);
- --status-warning: oklch(0.769 0.188 70.08);
- --status-error: oklch(0.637 0.237 25.331);
+ /* Semantic tokens */
--brand: oklch(0.585 0.233 277.117);
- --brand-foreground: oklch(0.985 0 0);
+ --brand-foreground: #f5f5f5;
--surface: oklch(0.205 0 0);
--surface-elevated: oklch(0.269 0 0);
--highlight: oklch(0.585 0.233 277.117);
- --highlight-foreground: oklch(0.985 0 0);
-
- /* Dark mode favorites/labels */
- --favorite-blue: oklch(0.488 0.243 264.376);
- --favorite-purple: oklch(0.627 0.265 303.9);
- --label-project: oklch(0.627 0.265 303.9);
+ --highlight-foreground: #f5f5f5;
- /* Code syntax highlighting tokens - dark mode */
- --syntax-keyword: oklch(0.488 0.243 264.376);
- --syntax-string: oklch(0.696 0.17 162.48);
- --syntax-function: oklch(0.627 0.265 303.9);
- --syntax-variable: oklch(0.769 0.188 70.08);
-
- /* Dark mode landing page tokens */
+ /* Landing page section accents */
--accent-ai: oklch(0.623 0.214 259.815);
--accent-workflows: oklch(0.705 0.213 47.604);
--accent-planning: oklch(0.723 0.219 149.579);
- /* Code block syntax colors (dark mode) */
+ /* Code syntax */
--code-comment: oklch(0.439 0 0);
--code-keyword: oklch(0.627 0.265 303.9);
--code-string: oklch(0.723 0.219 149.579);
@@ -178,8 +78,10 @@
}
@theme inline {
- --font-sans: "Geist", "Geist Fallback";
- --font-mono: "Geist Mono", "Geist Mono Fallback";
+ --font-sans: var(--font-dm-sans), "DM Sans", -apple-system, BlinkMacSystemFont,
+ "Segoe UI", system-ui, sans-serif;
+ --font-mono: "SF Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo,
+ monospace;
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -216,29 +118,12 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
-
- /* Register semantic tokens in theme */
- --color-status-progress: var(--status-progress);
- --color-status-success: var(--status-success);
- --color-status-warning: var(--status-warning);
- --color-status-error: var(--status-error);
--color-brand: var(--brand);
--color-brand-foreground: var(--brand-foreground);
--color-surface: var(--surface);
--color-surface-elevated: var(--surface-elevated);
--color-highlight: var(--highlight);
--color-highlight-foreground: var(--highlight-foreground);
-
- /* Register favorites/labels tokens */
- --color-favorite-blue: var(--favorite-blue);
- --color-favorite-purple: var(--favorite-purple);
- --color-label-project: var(--label-project);
- --color-syntax-keyword: var(--syntax-keyword);
- --color-syntax-string: var(--syntax-string);
- --color-syntax-function: var(--syntax-function);
- --color-syntax-variable: var(--syntax-variable);
-
- /* Register new landing page semantic tokens */
--color-accent-ai: var(--accent-ai);
--color-accent-workflows: var(--accent-workflows);
--color-accent-planning: var(--accent-planning);
@@ -258,9 +143,53 @@
body {
@apply bg-background text-foreground;
}
- /* Add text selection styling */
::selection {
- background-color: var(--brand);
+ background-color: var(--primary);
color: white;
}
}
+
+/* Fractal noise texture overlay */
+body::after {
+ content: "";
+ position: fixed;
+ inset: 0;
+ z-index: 9999;
+ pointer-events: none;
+ opacity: 0.035;
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
+}
+
+/* Heading scale */
+h1 {
+ font-size: clamp(2.25rem, 5vw, 3.75rem);
+ line-height: 1.1;
+ font-weight: 600;
+ letter-spacing: -0.025em;
+}
+
+h2 {
+ font-size: clamp(1.5rem, 3.5vw, 2.25rem);
+ line-height: 1.2;
+ font-weight: 600;
+ letter-spacing: -0.02em;
+}
+
+/* Custom thin scrollbar styling */
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--muted) transparent;
+}
+
+::-webkit-scrollbar {
+ width: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--muted);
+ border-radius: 3px;
+}
diff --git a/apps/marketing/app/layout.tsx b/apps/marketing/app/layout.tsx
index dd6999770..3edafb467 100644
--- a/apps/marketing/app/layout.tsx
+++ b/apps/marketing/app/layout.tsx
@@ -1,37 +1,41 @@
import type React from "react";
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { DM_Sans } from "next/font/google";
import { Analytics } from "@vercel/analytics/next";
import "./globals.css";
-const _geist = Geist({ subsets: ["latin"] });
-const _geistMono = Geist_Mono({ subsets: ["latin"] });
+const dmSans = DM_Sans({
+ subsets: ["latin"],
+ variable: "--font-dm-sans",
+});
export const metadata: Metadata = {
- title: "Sprint - Purpose-built tool for planning and building products",
+ title: "OK Code — A Minimal Web GUI for Coding Agents",
description:
- "Meet the system for modern software development. Streamline issues, projects, and product roadmaps with Sprint.",
- generator: "v0.app",
+ "Chat with Codex and Claude in a modern web UI. Git worktree isolation, diff review, integrated terminal, and more. Run anywhere with npx okcodes.",
keywords: [
- "project management",
- "product development",
- "issue tracking",
- "roadmap planning",
- "team collaboration",
+ "coding agents",
+ "AI coding",
+ "web GUI",
+ "git worktree",
+ "diff review",
+ "Claude",
+ "Codex",
+ "terminal",
],
- authors: [{ name: "Sprint" }],
+ authors: [{ name: "OpenKnots" }],
openGraph: {
- title: "Sprint - Purpose-built tool for planning and building products",
+ title: "OK Code — A Minimal Web GUI for Coding Agents",
description:
- "Meet the system for modern software development. Streamline issues, projects, and product roadmaps.",
+ "Chat with Codex and Claude in a modern web UI. Git worktree isolation, diff review, integrated terminal, and more.",
type: "website",
locale: "en_US",
},
twitter: {
card: "summary_large_image",
- title: "Sprint - Purpose-built tool for planning and building products",
+ title: "OK Code — A Minimal Web GUI for Coding Agents",
description:
- "Meet the system for modern software development. Streamline issues, projects, and product roadmaps.",
+ "Chat with Codex and Claude in a modern web UI. Git worktree isolation, diff review, integrated terminal, and more.",
},
icons: {
icon: [
@@ -58,8 +62,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
+
+
{children}
diff --git a/apps/marketing/app/page.tsx b/apps/marketing/app/page.tsx
index 7f31aeb2d..588ce14b1 100644
--- a/apps/marketing/app/page.tsx
+++ b/apps/marketing/app/page.tsx
@@ -1,9 +1,19 @@
-import { Hero3DStage } from "@/components/hero-3d-stage";
+import { Nav } from "@/components/Nav";
+import { Hero } from "@/components/Hero";
+import { FeatureGrid } from "@/components/FeatureGrid";
+import { HowItWorks } from "@/components/HowItWorks";
+import { GetStarted } from "@/components/GetStarted";
+import { Footer } from "@/components/Footer";
export default function Home() {
return (
-
-
+
+
+
+
+
+
+
);
}
diff --git a/apps/marketing/components/CodeBlock.tsx b/apps/marketing/components/CodeBlock.tsx
new file mode 100644
index 000000000..558a1ab42
--- /dev/null
+++ b/apps/marketing/components/CodeBlock.tsx
@@ -0,0 +1,68 @@
+import { useState, useCallback, useRef, useEffect } from "react";
+
+const svgProps = {
+ width: 16,
+ height: 16,
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: 2,
+ strokeLinecap: "round" as const,
+ strokeLinejoin: "round" as const,
+};
+
+const CheckIcon = (
+
+
+
+);
+
+const CopyIcon = (
+
+
+
+
+);
+
+interface CodeBlockProps {
+ code: string;
+ label?: string;
+}
+
+export function CodeBlock({ code, label }: CodeBlockProps) {
+ const [copied, setCopied] = useState(false);
+ const timerRef = useRef>(null);
+
+ useEffect(() => {
+ return () => {
+ if (timerRef.current) clearTimeout(timerRef.current);
+ };
+ }, []);
+
+ const handleCopy = useCallback(async () => {
+ try {
+ await navigator.clipboard.writeText(code);
+ if (timerRef.current) clearTimeout(timerRef.current);
+ setCopied(true);
+ timerRef.current = setTimeout(() => setCopied(false), 2000);
+ } catch {
+ // Fallback: clipboard API unavailable
+ }
+ }, [code]);
+
+ return (
+
+ {label && (
+ {label}
+ )}
+ {code}
+
+ {copied ? CheckIcon : CopyIcon}
+
+
+ );
+}
diff --git a/apps/marketing/components/ExternalLink.tsx b/apps/marketing/components/ExternalLink.tsx
new file mode 100644
index 000000000..c8ff37370
--- /dev/null
+++ b/apps/marketing/components/ExternalLink.tsx
@@ -0,0 +1,22 @@
+import type { AnchorHTMLAttributes, ReactNode } from "react";
+
+interface ExternalLinkProps extends AnchorHTMLAttributes {
+ href: string;
+ children: ReactNode;
+}
+
+export function ExternalLink({ href, children, className, ...rest }: ExternalLinkProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/marketing/components/FeatureCard.tsx b/apps/marketing/components/FeatureCard.tsx
new file mode 100644
index 000000000..d49095655
--- /dev/null
+++ b/apps/marketing/components/FeatureCard.tsx
@@ -0,0 +1,27 @@
+import type { ReactNode } from "react";
+
+interface FeatureCardProps {
+ icon?: ReactNode;
+ title: string;
+ description: string;
+ compact?: boolean;
+}
+
+export function FeatureCard({ icon, title, description, compact }: FeatureCardProps) {
+ if (compact) {
+ return (
+
+
{title}
+
{description}
+
+ );
+ }
+
+ return (
+
+ {icon &&
{icon}
}
+
{title}
+
{description}
+
+ );
+}
diff --git a/apps/marketing/components/FeatureGrid.tsx b/apps/marketing/components/FeatureGrid.tsx
new file mode 100644
index 000000000..512f2b610
--- /dev/null
+++ b/apps/marketing/components/FeatureGrid.tsx
@@ -0,0 +1,149 @@
+import { FeatureCard } from "./FeatureCard";
+
+/* Shared props to avoid repeating on every inline SVG icon */
+const svgProps = {
+ width: 20,
+ height: 20,
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ strokeWidth: 2,
+ strokeLinecap: "round" as const,
+ strokeLinejoin: "round" as const,
+};
+
+const icons = {
+ messageSquare: (
+
+
+
+ ),
+ gitBranch: (
+
+
+
+
+
+
+ ),
+ fileDiff: (
+
+
+
+
+
+
+ ),
+ terminal: (
+
+
+
+
+ ),
+ listChecks: (
+
+
+
+
+
+
+
+ ),
+ shieldCheck: (
+
+
+
+
+ ),
+};
+
+const primaryFeatures = [
+ {
+ icon: icons.messageSquare,
+ title: "Codex and Claude, one interface",
+ description:
+ "Switch providers per thread. Stream responses in real time. Attach images, terminal output, or file context.",
+ },
+ {
+ icon: icons.gitBranch,
+ title: "Every thread, its own worktree",
+ description:
+ "Each conversation runs in an isolated git worktree. Your main branch stays clean. Merge when ready.",
+ },
+ {
+ icon: icons.fileDiff,
+ title: "Review every change",
+ description:
+ "Inline and side-by-side diffs with syntax highlighting. Accept or reject changes per file before committing.",
+ },
+ {
+ icon: icons.terminal,
+ title: "Built-in terminal",
+ description:
+ "Up to four terminal tabs per thread. Feed output directly to the agent. No window switching.",
+ },
+ {
+ icon: icons.listChecks,
+ title: "Structured implementation plans",
+ description:
+ "AI-generated step-by-step plans with status tracking. See the full scope before any code changes.",
+ },
+ {
+ icon: icons.shieldCheck,
+ title: "You stay in control",
+ description:
+ "Full-access or approval-required modes. Review every file write and command before execution.",
+ },
+];
+
+const secondaryFeatures = [
+ {
+ title: "GitHub PR review",
+ description: "Inline comments, conflict resolution, full review flow.",
+ },
+ {
+ title: "Reusable skills",
+ description: "Built-in and custom skill catalog for automation.",
+ },
+ {
+ title: "Restore any turn",
+ description: "Automatic snapshots. Roll back to any point in the conversation.",
+ },
+ {
+ title: "Desktop, web, mobile",
+ description: "npx, Electron, or Capacitor. Same experience everywhere.",
+ },
+];
+
+export function FeatureGrid() {
+ return (
+
+
+ Features
+
+ Everything you need
+
+
+ {primaryFeatures.map((feature) => (
+
+ ))}
+
+
+
+ {secondaryFeatures.map((feature) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/marketing/components/GetStarted.tsx b/apps/marketing/components/GetStarted.tsx
new file mode 100644
index 000000000..8ae6af6c0
--- /dev/null
+++ b/apps/marketing/components/GetStarted.tsx
@@ -0,0 +1,24 @@
+import { CodeBlock } from "./CodeBlock";
+import { ExternalLink } from "./ExternalLink";
+import { LINKS } from "./links";
+
+export function GetStarted() {
+ return (
+
+
+
Start building.
+
One command. Zero config.
+
+
+
+
+
+
+ View on GitHub
+ Download Desktop
+ Join Discord
+
+
+
+ );
+}
diff --git a/apps/marketing/components/Hero.tsx b/apps/marketing/components/Hero.tsx
new file mode 100644
index 000000000..2b043ea31
--- /dev/null
+++ b/apps/marketing/components/Hero.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import { motion } from "framer-motion";
+import { CodeBlock } from "./CodeBlock";
+import { ExternalLink } from "./ExternalLink";
+import { LINKS } from "./links";
+import { OKCodeMockup } from "./OKCodeMockup";
+
+export function Hero() {
+ return (
+
+ {/* Ambient glow */}
+
+
+
+ {/* Copy */}
+
+
+ A minimal web GUI for coding agents
+
+
+
+ Ship code with AI agents.
+
+
+
+ Chat with Codex and Claude in real time. Every thread runs in its own git worktree.
+ Review diffs, run terminals, and deploy — all from one interface.
+
+
+
+
+
+ Download Desktop
+
+
+
+
+ {/* Product render */}
+
+ {/* Bottom fade */}
+
+
+ {/* Mockup frame */}
+
+
+
+
+ {/* Subtle reflection glow under frame */}
+
+
+
+
+ );
+}
diff --git a/apps/marketing/components/HowItWorks.tsx b/apps/marketing/components/HowItWorks.tsx
new file mode 100644
index 000000000..0f19893f1
--- /dev/null
+++ b/apps/marketing/components/HowItWorks.tsx
@@ -0,0 +1,49 @@
+const steps = [
+ {
+ number: "01",
+ title: "Install a provider",
+ details: ["npm install -g @openai/codex", "npm install -g @anthropic-ai/claude-code"],
+ },
+ {
+ number: "02",
+ title: "Launch OK Code",
+ details: ["npx okcodes"],
+ },
+ {
+ number: "03",
+ title: "Start coding",
+ details: ["Open a thread, describe what you want, review the diff."],
+ },
+];
+
+export function HowItWorks() {
+ return (
+
+
+ Quick start
+
+ Get running in 60 seconds
+
+
+ {steps.map((step) => (
+
+
+ {step.number}
+
+
{step.title}
+
+ {step.details.map((detail) => (
+
+ {detail}
+
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/marketing/components/Nav.tsx b/apps/marketing/components/Nav.tsx
new file mode 100644
index 000000000..e15cd1906
--- /dev/null
+++ b/apps/marketing/components/Nav.tsx
@@ -0,0 +1,30 @@
+import { ExternalLink } from "./ExternalLink";
+import { LINKS } from "./links";
+
+export function Nav() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/marketing/components/OKCodeMockup.tsx b/apps/marketing/components/OKCodeMockup.tsx
new file mode 100644
index 000000000..ce93cf7aa
--- /dev/null
+++ b/apps/marketing/components/OKCodeMockup.tsx
@@ -0,0 +1,393 @@
+"use client";
+
+import type React from "react";
+import { motion } from "framer-motion";
+import {
+ ChevronDown,
+ ChevronRight,
+ Search,
+ Plus,
+ File,
+ Folder,
+ FolderOpen,
+ Terminal,
+ FileText,
+ Settings,
+ GitBranch,
+ MessageSquare,
+ Code,
+ Clock,
+ Monitor,
+} from "lucide-react";
+
+const containerVariants = {
+ hidden: {},
+ visible: {
+ transition: {
+ staggerChildren: 0.25,
+ delayChildren: 0.4,
+ },
+ },
+};
+
+const panelVariants = {
+ hidden: { opacity: 0, y: 12 },
+ visible: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ duration: 0.8,
+ ease: [0.22, 1, 0.36, 1] as const,
+ },
+ },
+};
+
+export function OKCodeMockup() {
+ return (
+
+ {/* Sidebar */}
+
+ {/* Project header */}
+
+
+ {/* Active project */}
+
+
+ {/* Active thread */}
+
+
+ refactor auth flow
+
+
+
+ {/* File tree — static, minimal */}
+
+
+ Files
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Bottom nav */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Center — Chat */}
+
+ {/* Thread header */}
+
+
+ refactor auth flow
+ acme-app
+
+
+
+ feat/auth-refactor
+
+
+
+ {/* Chat content — static, no scroll */}
+
+ {/* User message */}
+
+
+
+ Refactor the auth form to use server actions and add proper validation
+
+
+
+
+ {/* Agent response */}
+
+
+ {
+ "I'll refactor the auth form to use Next.js server actions with Zod validation. Let me start by updating the form component."
+ }
+
+
+
+
+
+ Edited 3 files
+ +47 -23
+
+
+
+ Done. The auth form now uses useActionState with a server action that
+ validates input through authSchema . Error messages render inline per
+ field.
+
+
+
+
+
+
+ {/* Input — static */}
+
+
+
+ Ask anything, @tag files, or / for commands
+
+
+
+
+
+ Claude
+
+
|
+
+
+ Chat
+
+
|
+
+
+ Full access
+
+
+
+
+
+
+
+
+ {/* Right — Diff panel */}
+
+ {/* Tabs */}
+
+
+ auth-form.tsx
+
+ api.ts
+ schema.ts
+
+
+ {/* Diff content — static */}
+
+ {`"use server";`}
+ {``}
+ {`import { z } from "zod";`}
+ {`import { authSchema } from "./schema";`}
+ {``}
+ {`export async function login(data) {`}
+ {`export async function login(`}
+ {` _prev: unknown,`}
+ {` formData: FormData`}
+ {`) {`}
+ {` const parsed = authSchema.safeParse({`}
+ {` email: formData.get("email"),`}
+ {` password: formData.get("pass"),`}
+ {` });`}
+ {``}
+ {` if (!parsed.success) {`}
+ {` return { errors: parsed.error.flatten() };`}
+ {` }`}
+
+
+ {/* Diff footer */}
+
+
+ +47
+ -23
+ 3 files
+
+
+
+ Reject
+
+
+ Accept
+
+
+
+
+
+ );
+}
+
+/* Helper components — purely presentational, no state */
+
+function TreeFolder({
+ name,
+ children,
+ indent = 0,
+ open = false,
+}: {
+ name: string;
+ children?: React.ReactNode;
+ indent?: number;
+ open?: boolean;
+}) {
+ return (
+
+
+ {open ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+ {name}
+
+ {open && children}
+
+ );
+}
+
+function TreeFile({
+ name,
+ indent = 0,
+ active = false,
+ dim = false,
+}: {
+ name: string;
+ indent?: number;
+ active?: boolean;
+ dim?: boolean;
+}) {
+ return (
+
+
+ {name}
+
+ );
+}
+
+function SidebarLink({ icon: Icon, label }: { icon: React.ElementType; label: string }) {
+ return (
+
+
+ {label}
+
+ );
+}
+
+function Pill({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+function Mono({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+function DiffLine({
+ type,
+ num,
+ children,
+}: {
+ type: "add" | "remove" | "context";
+ num: string;
+ children: React.ReactNode;
+}) {
+ const colors = {
+ add: "bg-emerald-500/[0.06] text-emerald-300/70",
+ remove: "bg-red-500/[0.06] text-red-300/60 line-through",
+ context: "text-white/30",
+ };
+ const prefix = { add: "+", remove: "-", context: " " };
+
+ return (
+
+ {num}
+ {prefix[type]}
+ {children}
+
+ );
+}
diff --git a/apps/marketing/components/footer.tsx b/apps/marketing/components/footer.tsx
index ffa1fac5b..b7b61ae83 100644
--- a/apps/marketing/components/footer.tsx
+++ b/apps/marketing/components/footer.tsx
@@ -1,78 +1,24 @@
-export function Footer() {
- const footerLinks = {
- Features: [
- "Plan",
- "Build",
- "Insights",
- "Customer Requests",
- "Sprint Asks",
- "Security",
- "Mobile",
- ],
- Product: [
- "Pricing",
- "Method",
- "Integrations",
- "Changelog",
- "Documentation",
- "Download",
- "Switch",
- ],
- Company: ["About", "Customers", "Careers", "Now", "README", "Quality", "Brand"],
- Resources: [
- "Developers",
- "Status",
- "Startups",
- "Report vulnerability",
- "DPA",
- "Privacy",
- "Terms",
- ],
- Connect: ["Contact us", "Community", "X (Twitter)", "GitHub", "YouTube"],
- };
+import { ExternalLink } from "./ExternalLink";
+import { LINKS } from "./links";
+export function Footer() {
return (
-
-
-
- {/* Logo - Use semantic token */}
-
+
diff --git a/apps/marketing/components/links.ts b/apps/marketing/components/links.ts
new file mode 100644
index 000000000..689332c2c
--- /dev/null
+++ b/apps/marketing/components/links.ts
@@ -0,0 +1,5 @@
+export const LINKS = {
+ github: "https://github.com/OpenKnots/okcode",
+ releases: "https://github.com/OpenKnots/okcode/releases",
+ discord: "https://discord.gg/openknot",
+} as const;