Rebuild landing design system on the adaline pebble/meadow palette

Foundation for the adaline-based landing rebuild:
- globals.css: warm-cream light theme + Material-3 dark theme mapped onto the
  existing CSS-var names; CSS-keyframe entrance + IntersectionObserver reveal
  motion (to replace framer-motion incrementally)
- layout.tsx: self-host Instrument Sans/Serif, Newsreader and JetBrains Mono via
  next/font/google; drop the external Google Fonts <link>
- _headers: tighten CSP style/font-src to 'self' now that fonts are local
- components/ui: Container, Reveal, Eyebrow, Badge, Button, SectionHeading
- lib/landing-content.ts: feature-section copy for the six product screens
- public/images: six product screenshots optimized to WebP
- add reicon-react/reicon-brands (bundled, CSP-safe SVG icons)

Business logic (D1 download counter, platform metadata, theme, SEO) unchanged.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 21:41:12 -04:00
1 parent 8265612206
commit b1e2374c7b
20 files changed
+433 -67

No files matched your search

+2 -1
View File
@@ -7,4 +7,5 @@ coverage/
*.tsbuildinfo
design-source/
.DS_Store
cred.txt
cred.txt
ailine
+1 -1
View File
@@ -1,5 +1,5 @@
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
Permissions-Policy: camera=(), microphone=(), geolocation=()
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
+142 -55
View File
@@ -1,63 +1,86 @@
@import "tailwindcss";
/*
* AgentCompany landing — design system.
* Light theme is a faithful rendering of the adaline pebble/meadow palette
* (warm cream + dark green). Dark theme uses the matching Material-3 dark
* surfaces + mint primary. Components consume the CSS variables below, so the
* whole palette is defined once here.
*/
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-serif: "Space Grotesk", "Inter", ui-sans-serif, system-ui, sans-serif;
--font-mono: "Space Mono", ui-monospace, SFMono-Regular, monospace;
--font-sans: var(--font-instrument-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
--font-serif: var(--font-newsreader), "Source Serif Pro", Georgia, "Times New Roman", serif;
--font-mono: var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
--font-display: var(--font-instrument-serif), "Cormorant Garamond", Georgia, serif;
}
/* ── Light Theme (Cohere) ── */
/* ── Light theme (pebble / meadow) ── */
:root {
--bg: #ffffff;
--bg-subtle: #fafafa;
--panel: #ffffff;
--panel-soft: #fafafa;
--text: #000000;
--text-secondary: #212121;
--muted: #93939f;
--muted-strong: #565650;
--accent: #1863dc;
--accent-hover: #1352b8;
--accent-soft: rgba(24, 99, 220, 0.06);
--text-on-accent: #ffffff;
--cta: #000000;
--cta-hover: #212121;
--cta-soft: rgba(0, 0, 0, 0.04);
--text-on-cta: #ffffff;
--text-on-cta-muted: rgba(255, 255, 255, 0.75);
--text-on-cta-faint: rgba(255, 255, 255, 0.60);
--border: rgba(0, 0, 0, 0.06);
--border-cool: #d9d9dd;
--glow-warm: rgba(24, 99, 220, 0.04);
--glow-accent: rgba(24, 99, 220, 0.03);
--section-alt: rgba(0, 0, 0, 0.015);
--bg: #fbfdf6;
--bg-subtle: #f8f9f5;
--panel: #fdfefb;
--panel-soft: #f1f3ef;
--panel-illustration: #eff2e8;
--text: #0a1d08;
--text-secondary: #2e3b28;
--muted: #6b7860;
--muted-strong: #505c47;
--accent: #203b14;
--accent-hover: #13290e;
--accent-soft: #ecf2df;
--text-on-accent: #fbfdf6;
--cta: #203b14;
--cta-hover: #13290e;
--cta-soft: rgba(32, 59, 20, 0.06);
--text-on-cta: #fbfdf6;
--text-on-cta-muted: rgba(251, 253, 246, 0.75);
--text-on-cta-faint: rgba(251, 253, 246, 0.6);
--border: #e1e6df;
--border-strong: #c4cbc2;
--border-cool: #c4cbc2;
--band: #2a332a;
--band-text: #fbfdf6;
--band-border: rgba(253, 254, 251, 0.24);
--footer-bg: #050e11;
--footer-text: #fbfdf6;
--glow-warm: rgba(32, 59, 20, 0.05);
--glow-accent: rgba(99, 143, 61, 0.06);
--section-alt: rgba(32, 59, 20, 0.02);
}
/* ── Dark Theme (Cohere) ── */
/* ── Dark theme (Material-3 dark surfaces + mint primary) ── */
.dark {
--bg: #17171c;
--bg-subtle: #1e1e24;
--panel: #212126;
--panel-soft: #2a2a30;
--text: #fafafa;
--text-secondary: #d9d9dd;
--muted: #93939f;
--muted-strong: #b8b8b2;
--accent: #1863dc;
--accent-hover: #2070e8;
--accent-soft: rgba(24, 99, 220, 0.12);
--text-on-accent: #ffffff;
--cta: #fafafa;
--cta-hover: #e5e5e5;
--cta-soft: rgba(250, 250, 250, 0.06);
--text-on-cta: #17171c;
--text-on-cta-muted: rgba(23, 23, 28, 0.75);
--text-on-cta-faint: rgba(23, 23, 28, 0.60);
--border: rgba(250, 250, 250, 0.08);
--border-cool: #333338;
--glow-warm: rgba(24, 99, 220, 0.06);
--glow-accent: rgba(24, 99, 220, 0.04);
--section-alt: rgba(24, 99, 220, 0.025);
--bg: #131515;
--bg-subtle: #171b1c;
--panel: #1d2226;
--panel-soft: #20262a;
--panel-illustration: #1d2226;
--text: #d8e3dd;
--text-secondary: #b2c3bf;
--muted: #93a49e;
--muted-strong: #b2c3bf;
--accent: #5eec97;
--accent-hover: #4af58f;
--accent-soft: #1c4534;
--text-on-accent: #124627;
--cta: #5eec97;
--cta-hover: #4af58f;
--cta-soft: rgba(94, 236, 151, 0.1);
--text-on-cta: #124627;
--text-on-cta-muted: rgba(18, 70, 39, 0.75);
--text-on-cta-faint: rgba(18, 70, 39, 0.55);
--border: #2c343b;
--border-strong: #3b454f;
--border-cool: #3b454f;
--band: #0b1510;
--band-text: #d8e3dd;
--band-border: rgba(216, 227, 221, 0.2);
--footer-bg: #05090b;
--footer-text: #d8e3dd;
--glow-warm: rgba(94, 236, 151, 0.05);
--glow-accent: rgba(94, 236, 151, 0.04);
--section-alt: rgba(94, 236, 151, 0.03);
}
/* ── Base ── */
@@ -68,10 +91,16 @@ html {
body {
background-color: var(--bg);
color: var(--text);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
line-height: 1.65;
line-height: 1.55;
}
.font-display {
font-family: var(--font-display);
font-variant-ligatures: common-ligatures discretionary-ligatures;
}
::selection {
@@ -88,6 +117,13 @@ body {
outline: none;
}
/* ── Eyebrow / small-caps mono label ── */
.eyebrow {
font-family: var(--font-mono);
font-variant-caps: all-small-caps;
letter-spacing: 0.06em;
}
/* ── Scrollbar ── */
::-webkit-scrollbar {
width: 6px;
@@ -99,7 +135,7 @@ body {
}
::-webkit-scrollbar-thumb {
background: var(--border);
background: var(--border-strong);
border-radius: 3px;
}
@@ -109,10 +145,56 @@ body {
* {
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
scrollbar-color: var(--border-strong) transparent;
}
/* ── Reduced Motion ── */
/* ── Entrance + reveal motion (faithful to adaline: ease-out-quint) ── */
@keyframes nav-enter {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes rise-enter {
from {
opacity: 0;
transform: translateY(14px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.enter-nav {
animation: nav-enter 0.52s cubic-bezier(0.22, 1, 0.36, 1) 0.1s both;
}
.enter-rise {
animation: rise-enter 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Scroll-reveal primitive: hidden until IntersectionObserver marks it in view. */
.reveal {
opacity: 0;
transform: translateY(16px);
transition:
opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
transition-delay: var(--reveal-delay, 0ms);
}
.reveal[data-in-view="true"] {
opacity: 1;
transform: translateY(0);
}
/* ── Reduced motion ── */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
@@ -125,4 +207,9 @@ body {
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.reveal {
opacity: 1;
transform: none;
}
}
+32 -7
View File
@@ -1,6 +1,37 @@
import type { Metadata } from "next";
import { Instrument_Sans, Instrument_Serif, Newsreader, JetBrains_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "../components/theme-provider";
const instrumentSans = Instrument_Sans({
subsets: ["latin"],
variable: "--font-instrument-sans",
display: "swap",
weight: ["400", "500", "600", "700"],
});
const instrumentSerif = Instrument_Serif({
subsets: ["latin"],
variable: "--font-instrument-serif",
display: "swap",
weight: "400",
});
const newsreader = Newsreader({
subsets: ["latin"],
variable: "--font-newsreader",
display: "swap",
weight: ["300", "400", "500"],
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
display: "swap",
weight: ["400", "500"],
});
const fontVariables = `${instrumentSans.variable} ${instrumentSerif.variable} ${newsreader.variable} ${jetbrainsMono.variable}`;
import {
landingHowItWorksNarrative,
landingStructuredDataNarrative,
@@ -180,15 +211,9 @@ const faqJsonLd = {
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en" className={fontVariables} suppressHydrationWarning>
<head>
<meta name="googlebot" content="index, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&family=Space+Mono:wght@400;700&display=swap"
rel="stylesheet"
/>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(softwareJsonLd) }} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationJsonLd) }} />
+24
View File
@@ -0,0 +1,24 @@
import type { ReactNode } from "react";
/** Rounded meadow pill, optionally a link. */
export function Badge({
children,
href,
className = "",
}: {
children: ReactNode;
href?: string;
className?: string;
}) {
const classes = `eyebrow inline-flex items-center gap-1.5 self-start rounded-full px-3 py-1.5 text-sm bg-[var(--accent-soft)] text-[var(--accent)] transition-colors ${className}`;
if (href) {
return (
<a href={href} className={classes}>
{children}
</a>
);
}
return <span className={classes}>{children}</span>;
}
+47
View File
@@ -0,0 +1,47 @@
import type { ReactNode } from "react";
type ButtonVariant = "primary" | "ghost";
type ButtonSize = "md" | "sm";
const base =
"eyebrow inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[20px] text-sm transition-colors";
const sizeClasses: Record<ButtonSize, string> = {
md: "h-11 px-6",
sm: "h-9 px-4",
};
const variantClasses: Record<ButtonVariant, string> = {
primary: "bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)]",
ghost: "border border-[var(--border-strong)] text-[var(--text-secondary)] hover:border-[var(--muted)]",
};
/** Link-styled call to action. Every landing CTA is an anchor. */
export function Button({
children,
href,
variant = "primary",
size = "md",
external = false,
className = "",
"aria-label": ariaLabel,
}: {
children: ReactNode;
href: string;
variant?: ButtonVariant;
size?: ButtonSize;
external?: boolean;
className?: string;
"aria-label"?: string;
}) {
return (
<a
href={href}
aria-label={ariaLabel}
{...(external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
className={`${base} ${sizeClasses[size]} ${variantClasses[variant]} ${className}`}
>
{children}
</a>
);
}
+6
View File
@@ -0,0 +1,6 @@
import type { ReactNode } from "react";
/** Page-width wrapper: 1280px max, adaline grid margins (16px → 48px). */
export function Container({ children, className = "" }: { children: ReactNode; className?: string }) {
return <div className={`mx-auto w-full max-w-[1280px] px-4 md:px-12 ${className}`}>{children}</div>;
}
+10
View File
@@ -0,0 +1,10 @@
import type { ReactNode } from "react";
/** Small-caps mono label used above section headings. */
export function Eyebrow({ children, className = "" }: { children: ReactNode; className?: string }) {
return (
<span className={`eyebrow inline-flex items-center gap-1.5 text-sm text-[var(--muted)] ${className}`}>
{children}
</span>
);
}
+59
View File
@@ -0,0 +1,59 @@
"use client";
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
/**
* Scroll-reveal wrapper. Starts hidden (via the `.reveal` class) and marks
* itself in view the first time it intersects the viewport, letting CSS handle
* the rise-and-fade. Honours reduced-motion through the stylesheet.
*/
export function Reveal({
children,
delay = 0,
className = "",
}: {
children: ReactNode;
delay?: number;
className?: string;
}) {
const ref = useRef<HTMLDivElement | null>(null);
const [inView, setInView] = useState(false);
useEffect(() => {
const node = ref.current;
if (!node) {
return;
}
if (typeof IntersectionObserver === "undefined") {
setInView(true);
return;
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
setInView(true);
observer.disconnect();
break;
}
}
},
{ threshold: 0.2, rootMargin: "0px 0px -10% 0px" },
);
observer.observe(node);
return () => observer.disconnect();
}, []);
return (
<div
ref={ref}
className={`reveal ${className}`}
data-in-view={inView}
style={delay ? ({ "--reveal-delay": `${delay}ms` } as CSSProperties) : undefined}
>
{children}
</div>
);
}
+32
View File
@@ -0,0 +1,32 @@
import type { ReactNode } from "react";
import { Eyebrow } from "./eyebrow";
/** Eyebrow + heading + subhead block shared by every section. */
export function SectionHeading({
eyebrow,
title,
description,
align = "left",
className = "",
}: {
eyebrow?: string;
title: ReactNode;
description?: ReactNode;
align?: "left" | "center";
className?: string;
}) {
const alignment =
align === "center" ? "items-center text-center mx-auto" : "items-start text-left";
return (
<div className={`flex max-w-[68ch] flex-col gap-3 md:gap-4 ${alignment} ${className}`}>
{eyebrow ? <Eyebrow>{eyebrow}</Eyebrow> : null}
<h2 className="text-balance text-[30px] font-medium leading-[1.12] tracking-[-0.02em] text-[var(--text)] md:text-[34px]">
{title}
</h2>
{description ? (
<p className="max-w-[62ch] text-pretty text-lg leading-relaxed text-[var(--muted)]">{description}</p>
) : null}
</div>
);
}
+56
View File
@@ -0,0 +1,56 @@
export type FeatureSection = {
id: string;
eyebrow: string;
title: string;
description: string;
image: string;
imageAlt: string;
};
export const featureSections: FeatureSection[] = [
{
id: "org-chart",
eyebrow: "Self-organizing",
title: "Your org chart builds itself",
description:
"Set one goal. The AI CEO decides which roles the mission needs, hires specialist agents, and draws the reporting lines — from your board seat down to every department.",
image: "/images/org-chart.webp",
imageAlt: "AgentCompany org chart with a board chair, an AI CEO, and specialist agents reporting into departments",
},
{
id: "office",
eyebrow: "Presence",
title: "A workplace you can watch",
description:
"Every agent has a desk. See who is heads-down at work, who is in the meeting room, and what the whole company is doing — at a single glance across the floor.",
image: "/images/office.webp",
imageAlt: "Top-down view of the AgentCompany virtual office with named agents at their desks",
},
{
id: "messages",
eyebrow: "Coordination",
title: "Agents that talk to each other",
description:
"Direct messages and department channels keep the team aligned. Read the reasoning behind every hand-off and decision, written out in plain language.",
image: "/images/messages.webp",
imageAlt: "AgentCompany messaging view with direct conversations between the CEO and specialist agents",
},
{
id: "connectors",
eyebrow: "Integrations",
title: "Plugged into your stack",
description:
"Connect GitHub, Slack, Notion, Google Workspace, Stripe, and dozens more. Your agents do the work inside the tools your company already runs on.",
image: "/images/connectors.webp",
imageAlt: "AgentCompany connector catalog with dozens of integrations ready to connect",
},
{
id: "cost-controls",
eyebrow: "Control",
title: "Spend stays under your cap",
description:
"Set a monthly budget and per-agent allocations. Track requests, tokens, and cost by agent — and hold the whole company inside the limit you set.",
image: "/images/cost-controls.webp",
imageAlt: "AgentCompany advanced settings showing budget caps and cost broken down by agent",
},
];
+18 -1
View File
@@ -11,7 +11,9 @@
"framer-motion": "12.38.0",
"next": "16.2.6",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"reicon-brands": "^1.0.2",
"reicon-react": "^1.1.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.2",
@@ -1614,6 +1616,21 @@
"react": "^19.2.4"
}
},
"node_modules/reicon-brands": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/reicon-brands/-/reicon-brands-1.0.2.tgz",
"integrity": "sha512-m+J4Ooa9vU1+Kg73GKni3eJgrFsoRmesLEgjkzHq4b3hkoJZj7so5kk8rYnkS8WZGfgH+ACb1u5wcjBRFUX4tw==",
"license": "MIT"
},
"node_modules/reicon-react": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/reicon-react/-/reicon-react-1.1.2.tgz",
"integrity": "sha512-dbp1CPEPKFjWfG41lGRMWi74YKfKJlb2JcB/TRBAUQF6jalZYW1dRZisRPCpze5TfQwRBD30p40qnoY+oYi7MQ==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+3 -1
View File
@@ -13,7 +13,9 @@
"framer-motion": "12.38.0",
"next": "16.2.6",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"reicon-brands": "^1.0.2",
"reicon-react": "^1.1.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.2",
+1 -1
View File
@@ -1,5 +1,5 @@
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
Permissions-Policy: camera=(), microphone=(), geolocation=()
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB