diff --git a/landing/app/layout.tsx b/landing/app/layout.tsx index c8cf55de..f2089931 100644 --- a/landing/app/layout.tsx +++ b/landing/app/layout.tsx @@ -1,7 +1,7 @@ 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"; +import { ThemeProvider } from "../components/theme/theme-provider"; const instrumentSans = Instrument_Sans({ subsets: ["latin"], @@ -74,10 +74,10 @@ export const metadata: Metadata = { locale: "en_US", images: [ { - url: "/screenshots/Dashboard.jpeg", - width: 1920, - height: 1080, - alt: "AgentCompany dashboard showing autonomous AI agents managing a company", + url: "/images/og.jpg", + width: 1200, + height: 630, + alt: "AgentCompany desktop dashboard — quarter goal, budget, agent workload, and recent runs", }, ], type: "website", @@ -86,7 +86,7 @@ export const metadata: Metadata = { card: "summary_large_image", title: "AgentCompany - Launch an Autonomous AI Company from Your Desktop", description, - images: ["/screenshots/Dashboard.jpeg"], + images: ["/images/og.jpg"], }, robots: { index: true, @@ -110,7 +110,7 @@ const softwareJsonLd = { operatingSystem: "macOS, Windows, Linux", url: "https://agent-company.dev", downloadUrl: "https://agent-company.dev/#download", - screenshot: "https://agent-company.dev/screenshots/Dashboard.jpeg", + screenshot: "https://agent-company.dev/images/dashboard.webp", softwareVersion: landingStructuredDataNarrative.softwareVersion, description, featureList: landingStructuredDataNarrative.featureList, diff --git a/landing/app/page.tsx b/landing/app/page.tsx index a35c9b51..4d7db44b 100644 --- a/landing/app/page.tsx +++ b/landing/app/page.tsx @@ -1,7 +1,7 @@ import { DownloadSection } from "../components/sections/download-section"; import { FAQ } from "../components/sections/faq"; import { Features } from "../components/sections/features"; -import { Footer } from "../components/footer"; +import { Footer } from "../components/layout/footer"; import { Hero } from "../components/sections/hero"; import { HowItWorks } from "../components/sections/how-it-works"; import { Navbar } from "../components/layout/navbar"; diff --git a/landing/components/ascii-background.tsx b/landing/components/ascii-background.tsx deleted file mode 100644 index 633fde70..00000000 --- a/landing/components/ascii-background.tsx +++ /dev/null @@ -1,158 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; - -const glyphs = " .'`^,:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"; - -function parseRgbHex(hex: string): [number, number, number] { - const value = hex.replace("#", "").trim(); - if (value.length !== 6) return [128, 128, 128]; - return [ - Number.parseInt(value.slice(0, 2), 16), - Number.parseInt(value.slice(2, 4), 16), - Number.parseInt(value.slice(4, 6), 16), - ]; -} - -export function AsciiBackground({ - cssVar = "--accent", - baseOpacity = 0.08, - speed = 1, -}: { - cssVar?: string; - baseOpacity?: number; - speed?: number; -}) { - const canvasRef = useRef(null); - const containerRef = useRef(null); - - useEffect(() => { - if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; - - const canvas = canvasRef.current; - const container = containerRef.current; - if (!canvas || !container) return; - - const context = canvas.getContext("2d"); - if (!context) return; - - let width = 0; - let height = 0; - let rgb: [number, number, number] = [128, 128, 128]; - let phase = 0; - let frame = 0; - let isVisible = true; - const pointer = { x: -9999, y: -9999 }; - - const updateColor = () => { - const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim(); - if (value.startsWith("#")) { - rgb = parseRgbHex(value); - } - }; - - updateColor(); - - const mutationObserver = new MutationObserver(updateColor); - mutationObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ["class"], - }); - - const intersectionObserver = new IntersectionObserver( - (entries) => { - const [entry] = entries; - isVisible = entry?.isIntersecting ?? true; - }, - { threshold: 0 }, - ); - intersectionObserver.observe(container); - - const resize = () => { - width = container.clientWidth; - height = container.clientHeight; - const devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2); - canvas.width = width * devicePixelRatio; - canvas.height = height * devicePixelRatio; - context.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0); - }; - - const handlePointer = (event: MouseEvent) => { - const rect = container.getBoundingClientRect(); - pointer.x = event.clientX - rect.left; - pointer.y = event.clientY - rect.top; - }; - - const render = () => { - frame = requestAnimationFrame(render); - if (!isVisible) return; - - context.clearRect(0, 0, width, height); - context.font = "14px monospace"; - context.textAlign = "center"; - context.textBaseline = "middle"; - - const columns = Math.ceil(width / 14); - const rows = Math.ceil(height / 14); - const [red, green, blue] = rgb; - const pointerX = pointer.x; - const pointerY = pointer.y; - - for (let row = 0; row < rows; row += 1) { - for (let column = 0; column < columns; column += 1) { - const centerX = column * 14 + 7; - const centerY = row * 14 + 7; - const value = - Math.sin(0.05 * column + phase) * Math.cos(0.05 * row + phase) + - Math.sin(0.012 * column - 0.7 * phase) * Math.cos(0.1 * row) * 0.5; - - if (Math.abs(value) < 0.3) continue; - - const index = Math.floor(Math.abs(value) * glyphs.length); - let glyph = glyphs[index % glyphs.length]; - if (!glyph.trim()) continue; - - let alpha = Math.abs(value) * baseOpacity; - const deltaX = centerX - pointerX; - const deltaY = centerY - pointerY; - const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - - if (distance < 110) { - const intensity = 1 - distance / 110; - glyph = Math.random() > 0.5 ? "0" : "1"; - alpha = 0.35 * intensity; - const drift = distance > 1 ? 7 * intensity : 0; - const offsetX = (deltaX / distance) * drift; - const offsetY = (deltaY / distance) * drift; - context.fillStyle = `rgba(${red},${green},${blue},${alpha})`; - context.fillText(glyph, centerX - offsetX, centerY - offsetY); - } else { - context.fillStyle = `rgba(${red},${green},${blue},${alpha})`; - context.fillText(glyph, centerX, centerY); - } - } - } - - phase += 0.005 * speed; - }; - - resize(); - window.addEventListener("resize", resize); - window.addEventListener("mousemove", handlePointer); - render(); - - return () => { - cancelAnimationFrame(frame); - window.removeEventListener("resize", resize); - window.removeEventListener("mousemove", handlePointer); - mutationObserver.disconnect(); - intersectionObserver.disconnect(); - }; - }, [baseOpacity, cssVar, speed]); - - return ( - - ); -} diff --git a/landing/components/icon-mark.tsx b/landing/components/brand/icon-mark.tsx similarity index 100% rename from landing/components/icon-mark.tsx rename to landing/components/brand/icon-mark.tsx diff --git a/landing/components/footer.tsx b/landing/components/footer.tsx deleted file mode 100644 index dd681a61..00000000 --- a/landing/components/footer.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { AsciiBackground } from "./ascii-background"; -import { IconMark } from "./icon-mark"; -import { CONTACT_EMAIL, WEBSITE_URL } from "./site-constants"; -import { ThemeToggle } from "./theme-toggle"; -import { getTrackedDownloadHref } from "../lib/downloads"; - -const footerGroups = [ - { - title: "Product", - links: [ - { label: "Features", href: "#features" }, - { label: "How It Works", href: "#how-it-works" }, - { label: "FAQ", href: "#faq" }, - { label: "Download", href: "#download" }, - ], - }, - { - title: "Downloads", - links: [ - { label: "Apple Silicon", href: getTrackedDownloadHref("macos-arm64-dmg"), external: true }, - { label: "Intel Mac", href: getTrackedDownloadHref("macos-x64-dmg"), external: true }, - { label: "Windows Installer", href: getTrackedDownloadHref("windows-universal-installer"), external: true }, - { label: "Linux x64 AppImage", href: getTrackedDownloadHref("linux-x64-appimage"), external: true }, - ], - }, - { - title: "Company", - links: [ - { label: "Website", href: WEBSITE_URL, external: true }, - { label: "Contact", href: `mailto:${CONTACT_EMAIL}`, external: true }, - { label: "Download Center", href: "#download" }, - ], - }, -]; - -export function Footer() { - const year = new Date().getFullYear(); - - return ( -
- - -
-
-
- - - AgentCompany - -

- Local-first desktop control plane for autonomous AI companies. -

-
- -
- {footerGroups.map((group) => ( -
-

{group.title}

- -
- ))} -
-
- -
- -
-
- © {year} AgentCompany -
- -
- -
-
-
-
- ); -} diff --git a/landing/components/layout/footer.tsx b/landing/components/layout/footer.tsx new file mode 100644 index 00000000..0c2ba5ae --- /dev/null +++ b/landing/components/layout/footer.tsx @@ -0,0 +1,95 @@ +import { Container } from "../ui/container"; +import { Reveal } from "../ui/reveal"; +import { Button } from "../ui/button"; +import { IconMark } from "../brand/icon-mark"; +import { getTrackedDownloadHref } from "../../lib/downloads"; +import { CONTACT_EMAIL, WEBSITE_URL } from "../../lib/site-constants"; + +type FooterLink = { label: string; href: string; external?: boolean }; + +const footerGroups: { title: string; links: FooterLink[] }[] = [ + { + title: "Product", + links: [ + { label: "Features", href: "#features" }, + { label: "How it works", href: "#how-it-works" }, + { label: "FAQ", href: "#faq" }, + { label: "Download", href: "#download" }, + ], + }, + { + title: "Downloads", + links: [ + { label: "Apple Silicon", href: getTrackedDownloadHref("macos-arm64-dmg"), external: true }, + { label: "Intel Mac", href: getTrackedDownloadHref("macos-x64-dmg"), external: true }, + { label: "Windows Installer", href: getTrackedDownloadHref("windows-universal-installer"), external: true }, + { label: "Linux x64 AppImage", href: getTrackedDownloadHref("linux-x64-appimage"), external: true }, + ], + }, + { + title: "Company", + links: [ + { label: "Website", href: WEBSITE_URL, external: true }, + { label: "Contact", href: `mailto:${CONTACT_EMAIL}`, external: true }, + { label: "Download center", href: "#download" }, + ], + }, +]; + +export function Footer() { + const year = new Date().getFullYear(); + + return ( +
+ +
+ +

+ Launch your company now. +

+
+ +
+
+
+ +
+
+ + + AgentCompany + +

+ Local-first desktop control plane for autonomous AI companies. +

+
+ + {footerGroups.map((group) => ( +
+

{group.title}

+ +
+ ))} +
+ +
+

© {year} AgentCompany

+
+
+
+ ); +} diff --git a/landing/components/layout/navbar.tsx b/landing/components/layout/navbar.tsx index 80cd72ae..ee36084c 100644 --- a/landing/components/layout/navbar.tsx +++ b/landing/components/layout/navbar.tsx @@ -5,8 +5,8 @@ import { Menu } from "reicon-react/icons/Menu"; import { Xmark } from "reicon-react/icons/Xmark"; import { Container } from "../ui/container"; import { Button } from "../ui/button"; -import { IconMark } from "../icon-mark"; -import { ThemeToggle } from "../theme-toggle"; +import { IconMark } from "../brand/icon-mark"; +import { ThemeToggle } from "../theme/theme-toggle"; const navLinks = [ { label: "Features", href: "#features" }, diff --git a/landing/components/theme-provider.tsx b/landing/components/theme/theme-provider.tsx similarity index 100% rename from landing/components/theme-provider.tsx rename to landing/components/theme/theme-provider.tsx diff --git a/landing/components/theme-toggle.tsx b/landing/components/theme/theme-toggle.tsx similarity index 100% rename from landing/components/theme-toggle.tsx rename to landing/components/theme/theme-toggle.tsx diff --git a/landing/components/ui/button.tsx b/landing/components/ui/button.tsx index cfa5a563..d5327fcf 100644 --- a/landing/components/ui/button.tsx +++ b/landing/components/ui/button.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; -type ButtonVariant = "primary" | "ghost"; +type ButtonVariant = "primary" | "ghost" | "inverted"; type ButtonSize = "md" | "sm"; const base = @@ -14,6 +14,7 @@ const sizeClasses: Record = { const variantClasses: Record = { 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)]", + inverted: "bg-[#fbfdf6] text-[#0a1d08] hover:bg-[#fbfdf6]/90", }; /** Link-styled call to action. Every landing CTA is an anchor. */ diff --git a/landing/components/site-constants.ts b/landing/lib/site-constants.ts similarity index 100% rename from landing/components/site-constants.ts rename to landing/lib/site-constants.ts diff --git a/landing/package-lock.json b/landing/package-lock.json index 3dddf722..ebc6fec4 100644 --- a/landing/package-lock.json +++ b/landing/package-lock.json @@ -8,7 +8,6 @@ "name": "agent-company-landing", "version": "1.0.0", "dependencies": { - "framer-motion": "12.38.0", "next": "16.2.6", "react": "19.2.4", "react-dom": "19.2.4", @@ -16,6 +15,7 @@ "reicon-react": "^1.1.2" }, "devDependencies": { + "@playwright/test": "^1.58.2", "@tailwindcss/postcss": "^4.2.2", "@types/node": "^24.9.1", "@types/react": "^19.2.2", @@ -757,6 +757,22 @@ "node": ">= 10" } }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1148,31 +1164,18 @@ "node": ">=10.13.0" } }, - "node_modules/framer-motion": { - "version": "12.38.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", - "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "motion-dom": "^12.38.0", - "motion-utils": "^12.36.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/graceful-fs": { @@ -1475,21 +1478,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/motion-dom": { - "version": "12.38.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", - "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.36.0" - } - }, - "node_modules/motion-utils": { - "version": "12.36.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", - "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", - "license": "MIT" - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1567,6 +1555,38 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/postcss": { "version": "8.5.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", diff --git a/landing/package.json b/landing/package.json index fe19f114..dc174518 100644 --- a/landing/package.json +++ b/landing/package.json @@ -10,7 +10,6 @@ "preview": "wrangler pages dev out" }, "dependencies": { - "framer-motion": "12.38.0", "next": "16.2.6", "react": "19.2.4", "react-dom": "19.2.4", @@ -18,6 +17,7 @@ "reicon-react": "^1.1.2" }, "devDependencies": { + "@playwright/test": "^1.58.2", "@tailwindcss/postcss": "^4.2.2", "@types/node": "^24.9.1", "@types/react": "^19.2.2", diff --git a/landing/public/images/og.jpg b/landing/public/images/og.jpg new file mode 100644 index 00000000..ba53d5db Binary files /dev/null and b/landing/public/images/og.jpg differ diff --git a/landing/public/screenshots/AI-Internal-Realtime-Message.jpeg b/landing/public/screenshots/AI-Internal-Realtime-Message.jpeg deleted file mode 100644 index bd8235e9..00000000 Binary files a/landing/public/screenshots/AI-Internal-Realtime-Message.jpeg and /dev/null differ diff --git a/landing/public/screenshots/Approval-by-Human.jpeg b/landing/public/screenshots/Approval-by-Human.jpeg deleted file mode 100644 index d5e459ff..00000000 Binary files a/landing/public/screenshots/Approval-by-Human.jpeg and /dev/null differ diff --git a/landing/public/screenshots/Auto-OrgChart-Team.jpeg b/landing/public/screenshots/Auto-OrgChart-Team.jpeg deleted file mode 100644 index 156ed2ad..00000000 Binary files a/landing/public/screenshots/Auto-OrgChart-Team.jpeg and /dev/null differ diff --git a/landing/public/screenshots/Auto-Task.jpeg b/landing/public/screenshots/Auto-Task.jpeg deleted file mode 100644 index df5096cd..00000000 Binary files a/landing/public/screenshots/Auto-Task.jpeg and /dev/null differ diff --git a/landing/public/screenshots/Dashboard.jpeg b/landing/public/screenshots/Dashboard.jpeg deleted file mode 100644 index cf4f53e0..00000000 Binary files a/landing/public/screenshots/Dashboard.jpeg and /dev/null differ diff --git a/landing/public/screenshots/Document-Center.jpeg b/landing/public/screenshots/Document-Center.jpeg deleted file mode 100644 index a40bfb89..00000000 Binary files a/landing/public/screenshots/Document-Center.jpeg and /dev/null differ diff --git a/landing/public/screenshots/Onboarding.jpeg b/landing/public/screenshots/Onboarding.jpeg deleted file mode 100644 index d80dee0d..00000000 Binary files a/landing/public/screenshots/Onboarding.jpeg and /dev/null differ