Rebuild navbar, hero, and feature sections in adaline style

- navbar: sticky, transparent to blur-on-scroll, wordmark + mono nav links +
  theme toggle + Download CTA, Reicon Menu/Xmark icons (components/layout)
- theme-toggle: Reicon Sun/Moon icons
- hero: meadow badge, tight Instrument Sans headline, Download + How-it-works
  CTAs, availability line, framed dashboard shot (components/sections)
- features: five stacked heading-over-panel sections mapping the org chart,
  office, messages, connectors and cost-control screens
- ui/media-frame: shared framed product-shot panel
- reveal: progressive enhancement — content is visible without JS and only
  hidden once the js class is present, then revealed on scroll
- remove the superseded flat navbar/hero/features components

Verified: next build green; desktop + mobile screenshots reviewed.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 22:08:31 -04:00
1 parent b1e2374c7b
commit c0f677ccc9
12 files changed
+261 -487

No files matched your search

+12 -5
View File
@@ -179,17 +179,24 @@ body {
animation: rise-enter 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Scroll-reveal primitive: hidden until IntersectionObserver marks it in view. */
/*
* Scroll-reveal primitive. Progressive enhancement: content is visible by
* default and only starts hidden once the `js` class is present, so the page
* stays fully readable if scripts never run.
*/
.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"] {
.js .reveal {
opacity: 0;
transform: translateY(16px);
}
.js .reveal[data-in-view="true"] {
opacity: 1;
transform: translateY(0);
}
@@ -208,7 +215,7 @@ body {
transition-duration: 0.01ms !important;
}
.reveal {
.js .reveal {
opacity: 1;
transform: none;
}
+1 -1
View File
@@ -99,7 +99,7 @@ export const metadata: Metadata = {
};
const themeScript = `
(function(){try{var t=localStorage.getItem("agentcompany-theme");if(t==="dark"||(!t&&window.matchMedia("(prefers-color-scheme:dark)").matches)){document.documentElement.classList.add("dark")}}catch(e){}})();
(function(){document.documentElement.classList.add("js");try{var t=localStorage.getItem("agentcompany-theme");if(t==="dark"||(!t&&window.matchMedia("(prefers-color-scheme:dark)").matches)){document.documentElement.classList.add("dark")}}catch(e){}})();
`;
const softwareJsonLd = {
+3 -3
View File
@@ -1,10 +1,10 @@
import { DownloadSection } from "../components/download-section";
import { FAQ } from "../components/faq";
import { Features } from "../components/features";
import { Features } from "../components/sections/features";
import { Footer } from "../components/footer";
import { Hero } from "../components/hero";
import { Hero } from "../components/sections/hero";
import { HowItWorks } from "../components/how-it-works";
import { Navbar } from "../components/navbar";
import { Navbar } from "../components/layout/navbar";
import { RuntimesBar } from "../components/runtimes-bar";
export default function Page() {
-191
View File
@@ -1,191 +0,0 @@
"use client";
import { motion } from "framer-motion";
const features = [
{
number: "01",
title: "One-Click Company Creation",
description:
"Name your company, describe your mission, connect your AI runtime, and launch. Your first agents start working in under a minute.",
image: "/screenshots/Onboarding.jpeg",
imageAlt: "Company creation onboarding flow with mission and runtime setup",
imageLeft: true,
},
{
number: "02",
title: "Self-Organizing Teams",
description:
"Your AI CEO builds the org chart. It identifies the roles needed, hires specialists, and creates reporting lines — all from a single goal.",
image: "/screenshots/Auto-OrgChart-Team.jpeg",
imageAlt: "AI-generated org chart showing self-organizing team structure",
imageLeft: false,
},
{
number: "03",
title: "Autonomous Task Management",
description:
"Goals break down into tasks automatically. Agents pick up work, report progress, and escalate blockers — you decide when to step in.",
image: "/screenshots/Auto-Task.jpeg",
imageAlt: "Autonomous task board with agents managing work items",
imageLeft: true,
},
{
number: "04",
title: "Real-Time Agent Communication",
description:
"Agents communicate through department channels and direct messages. Watch strategic decisions happen in real time, like a Slack workspace you built.",
image: "/screenshots/AI-Internal-Realtime-Message.jpeg",
imageAlt: "Real-time messaging between AI agents in department channels",
},
{
number: "05",
title: "Human-in-the-Loop Approvals",
description:
"Stay in control. Critical decisions surface for your review. Approve, reject, or redirect — your company respects the chain of command.",
image: "/screenshots/Approval-by-Human.jpeg",
imageAlt: "Approval interface showing human review of agent decisions",
},
{
number: "06",
title: "AI Document Generation",
description:
"Business plans, marketing strategies, technical specs — agents draft and iterate on real deliverables, stored in your document center.",
image: "/screenshots/Document-Center.jpeg",
imageAlt: "Document center with AI-generated business deliverables",
},
];
function FeaturePair({ feature }: { feature: (typeof features)[number] }) {
const { number, title, description, image, imageAlt, imageLeft } = feature;
const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];
const media = (
<motion.div
variants={{
hidden: { opacity: 0, x: imageLeft ? -60 : 60 },
visible: { opacity: 1, x: 0, transition: { duration: 0.7, ease } },
}}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
className={`order-2 ${imageLeft ? "lg:order-1" : "lg:order-2"}`}
>
<div className="rounded-[22px] overflow-hidden shadow-[0_4px_32px_-8px_rgba(0,0,0,0.06)] border border-[var(--border)] bg-[var(--panel)] transition-shadow duration-500 hover:shadow-[0_8px_48px_-12px_rgba(0,0,0,0.10)]">
<img src={image} alt={imageAlt} width="1920" height="1080" className="w-full h-auto block" loading="lazy" />
</div>
</motion.div>
);
const copy = (
<motion.div
variants={{
hidden: { opacity: 0, x: imageLeft ? 60 : -60 },
visible: { opacity: 1, x: 0, transition: { duration: 0.7, ease, delay: 0.1 } },
}}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
className={`flex flex-col justify-center order-1 ${imageLeft ? "lg:order-2" : "lg:order-1"}`}
>
<span className="font-mono text-[5rem] lg:text-[6rem] font-bold leading-none text-[var(--accent)] opacity-[0.07] select-none -mb-10 lg:-mb-12">
{number}
</span>
<div className="relative">
<div className="flex items-center gap-3 mb-4">
<span className="block w-2 h-2 rounded-full bg-[var(--accent)]" />
<span className="font-mono text-sm text-[var(--muted)]">{number}</span>
</div>
<h2 className="text-2xl lg:text-[1.875rem] font-bold text-[var(--text)] mb-4 tracking-tight">{title}</h2>
<p className="text-[var(--muted-strong)] leading-relaxed text-base lg:text-lg">{description}</p>
</div>
</motion.div>
);
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
{media}
{copy}
</div>
);
}
export function Features() {
return (
<section
id="features"
className="py-24 lg:py-40"
style={{ background: "linear-gradient(180deg, transparent 0%, var(--section-alt) 30%, var(--section-alt) 70%, transparent 100%)" }}
>
<div className="max-w-6xl mx-auto px-6 lg:px-8">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
className="text-center mb-24 lg:mb-36"
>
<span className="font-mono text-xs uppercase tracking-[0.25em] text-[var(--accent)] mb-5 block font-medium">
Features
</span>
<h2 className="font-serif text-[clamp(2rem,1.5rem+2.5vw,3.25rem)] tracking-[-0.01em] text-[var(--text)]">
Your Company, Under the Hood
</h2>
</motion.div>
<div className="space-y-24 lg:space-y-40">
{features.slice(0, 3).map((feature) => (
<FeaturePair key={feature.number} feature={feature} />
))}
</div>
<motion.div
className="mt-24 lg:mt-40 grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8"
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
variants={{
hidden: {},
visible: {
transition: { staggerChildren: 0.12 },
},
}}
>
{features.slice(3).map((feature) => (
<motion.div
key={feature.number}
variants={{
hidden: { opacity: 0, y: 32 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] },
},
}}
className="group bg-[var(--panel)] rounded-[22px] border border-[var(--border)] overflow-hidden transition-all duration-300 hover:shadow-[0_8px_40px_-12px_rgba(0,0,0,0.08)] hover:-translate-y-1.5"
>
<div className="overflow-hidden">
<img
src={feature.image}
alt={feature.imageAlt}
width="640"
height="360"
className="w-full h-auto block transition-transform duration-500 group-hover:scale-[1.03]"
loading="lazy"
/>
</div>
<div className="p-6 lg:p-7">
<div className="flex items-center gap-3 mb-3">
<span className="block w-2 h-2 rounded-full bg-[var(--accent)]" />
<span className="font-mono text-sm text-[var(--muted)]">{feature.number}</span>
</div>
<h2 className="text-lg font-bold text-[var(--text)] mb-2 tracking-tight">{feature.title}</h2>
<p className="text-sm text-[var(--muted-strong)] leading-relaxed">{feature.description}</p>
</div>
</motion.div>
))}
</motion.div>
</div>
</section>
);
}
-129
View File
@@ -1,129 +0,0 @@
"use client";
import { motion } from "framer-motion";
import { AsciiBackground } from "./ascii-background";
import { landingHeroNarrative } from "../lib/product-narrative";
const sharedEase: [number, number, number, number] = [0.16, 1, 0.3, 1];
function heroMotion(delay: number) {
return {
initial: { opacity: 0, y: 24 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.7, delay, ease: sharedEase },
};
}
function AppleIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
</svg>
);
}
function WindowsIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M3 12V6.5l8-1.1V12H3zm0 .5h8v6.6l-8-1.1V12.5zM11.5 5.3l9.5-1.3v8h-9.5V5.3zm0 7.2h9.5v8l-9.5-1.3V12.5z" />
</svg>
);
}
function LinuxIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12.5 2C10 2 8.2 4.2 8.2 7c0 1.5.3 2.2.7 3.2.3.8.7 1.7.7 3 0 .3 0 .6-.1.9-.2 1-.6 1.8-1.2 2.3-.8.7-1.8 1.1-2.8 1.6-.7.3-1.4.7-1.9 1.2-.4.4-.6.9-.6 1.4 0 .5.2.9.6 1.2.7.6 2 .7 3.1.7.8 0 1.6-.1 2.2-.2.6-.1 1.2-.3 1.9-.3.7 0 1.3.2 1.9.3.6.1 1.4.2 2.2.2 1.1 0 2.4-.1 3.1-.7.4-.3.6-.7.6-1.2 0-.5-.2-1-.6-1.4-.5-.5-1.2-.9-1.9-1.2-1-.5-2-1-2.8-1.6-.6-.5-1-1.3-1.2-2.3-.1-.3-.1-.6-.1-.9 0-1.3.4-2.2.7-3 .4-1 .7-1.7.7-3.2 0-2.8-1.8-5-4.3-5h-.2zm-1 5c0-.8.5-1.5 1.2-1.5s1.2.7 1.2 1.5-.5 1.5-1.2 1.5S11.5 7.8 11.5 7z" />
</svg>
);
}
export function Hero() {
return (
<section className="relative overflow-hidden pt-32 pb-24 lg:pt-44 lg:pb-36">
<AsciiBackground cssVar="--accent" baseOpacity={0.06} speed={1} />
<div
className="absolute inset-0 pointer-events-none"
aria-hidden="true"
style={{
background:
"radial-gradient(ellipse 80% 50% at 50% 0%, var(--glow-warm) 0%, transparent 70%), radial-gradient(ellipse 60% 40% at 50% 20%, var(--glow-accent) 0%, transparent 60%)",
}}
/>
<div className="relative mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="flex flex-col items-center text-center">
<motion.div {...heroMotion(0.05)}>
<span className="inline-flex items-center px-4 py-1.5 rounded-full text-xs font-semibold tracking-wide bg-[var(--accent-soft)] text-[var(--accent)] dark:text-[var(--muted-strong)] border border-[var(--border)]">
{landingHeroNarrative.eyebrow}
</span>
</motion.div>
<motion.h1
{...heroMotion(0.15)}
className="mt-8 font-serif text-[clamp(2.75rem,2rem+3.8vw,5rem)] font-normal leading-[1.0] tracking-[-0.03em] text-[var(--text)]"
>
{landingHeroNarrative.titleLine1} <br className="hidden sm:block" />
<span className="text-[var(--accent)]">{landingHeroNarrative.titleLine2}</span>
</motion.h1>
<motion.p
{...heroMotion(0.25)}
className="mt-7 max-w-2xl text-lg sm:text-xl leading-relaxed text-[var(--muted)]"
>
{landingHeroNarrative.description}
</motion.p>
<motion.div {...heroMotion(0.35)} className="mt-12">
<a
href="#download"
className="group inline-flex items-center justify-center px-8 py-4 rounded-full text-base font-medium bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)] hover:scale-[1.03] active:scale-[0.98] transition-all duration-200 will-change-transform"
>
See Downloads
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
className="ml-2 transition-transform duration-200 group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
>
<path d="m7 7 10 10" />
<path d="M17 7v10H7" />
</svg>
</a>
</motion.div>
<motion.div {...heroMotion(0.45)} className="mt-5 flex items-center gap-3 text-xs text-[var(--muted)]">
<div className="flex items-center gap-1.5 opacity-70">
<AppleIcon />
<WindowsIcon />
<LinuxIcon />
</div>
<span>{landingHeroNarrative.availability}</span>
</motion.div>
</div>
<motion.div
initial={{ opacity: 0, y: 40, scale: 0.94 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.9, delay: 0.5, ease: sharedEase }}
className="mt-20 lg:mt-24 mx-auto max-w-5xl"
>
<img
src="/screenshots/Dashboard.jpeg"
alt="AgentCompany dashboard showing autonomous AI agents managing a company"
width="1920"
height="1080"
className="w-full h-auto block rounded-[22px] border border-[var(--border)] shadow-[0_8px_40px_-8px_rgba(0,0,0,0.08)] transition-shadow duration-500"
loading="eager"
/>
</motion.div>
</div>
</section>
);
}
+114
View File
@@ -0,0 +1,114 @@
"use client";
import { useEffect, useState } from "react";
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";
const navLinks = [
{ label: "Features", href: "#features" },
{ label: "How it works", href: "#how-it-works" },
{ label: "FAQ", href: "#faq" },
];
export function Navbar() {
const [mobileOpen, setMobileOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
return () => window.removeEventListener("scroll", onScroll);
}, []);
useEffect(() => {
const onResize = () => {
if (window.innerWidth >= 768) {
setMobileOpen(false);
}
};
window.addEventListener("resize", onResize, { passive: true });
return () => window.removeEventListener("resize", onResize);
}, []);
return (
<nav
className={[
"enter-nav sticky top-0 z-50 transition-colors duration-300",
scrolled
? "border-b border-[var(--border)] bg-[var(--bg)]/85 backdrop-blur-xl"
: "border-b border-transparent bg-transparent",
].join(" ")}
>
<Container>
<div className="flex h-16 items-center justify-between">
<a href="#" className="flex shrink-0 items-center gap-2.5" aria-label="AgentCompany home">
<IconMark size={28} />
<span className="text-lg font-medium tracking-[-0.02em] text-[var(--text)]">AgentCompany</span>
</a>
<div className="hidden items-center gap-1 md:flex">
{navLinks.map(({ label, href }) => (
<a
key={href}
href={href}
className="eyebrow rounded-full px-3 py-2 text-sm text-[var(--muted)] transition-colors hover:text-[var(--text)]"
>
{label}
</a>
))}
</div>
<div className="hidden items-center gap-2 md:flex">
<ThemeToggle />
<Button href="#download" size="sm">
Download
</Button>
</div>
<div className="flex items-center gap-1 md:hidden">
<ThemeToggle />
<button
type="button"
onClick={() => setMobileOpen((value) => !value)}
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
aria-expanded={mobileOpen}
aria-controls="mobile-nav"
className="inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-full text-[var(--text)] transition-colors hover:bg-[var(--panel-soft)]"
>
{mobileOpen ? <Xmark size={22} /> : <Menu size={22} />}
</button>
</div>
</div>
</Container>
<div
id="mobile-nav"
className={[
"overflow-hidden border-b transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] md:hidden",
mobileOpen ? "max-h-72 border-[var(--border)] bg-[var(--bg)]/95 backdrop-blur-xl" : "max-h-0 border-transparent",
].join(" ")}
>
<Container className="flex flex-col gap-1 pb-4 pt-1">
{navLinks.map(({ label, href }) => (
<a
key={href}
href={href}
onClick={() => setMobileOpen(false)}
className="eyebrow rounded-lg px-3 py-2.5 text-sm text-[var(--muted)] transition-colors hover:bg-[var(--panel-soft)] hover:text-[var(--text)]"
>
{label}
</a>
))}
<Button href="#download" size="sm" className="mt-2 w-full">
Download
</Button>
</Container>
</div>
</nav>
);
}
-132
View File
@@ -1,132 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import { IconMark } from "./icon-mark";
import { ThemeToggle } from "./theme-toggle";
const navLinks = [
{ label: "Features", href: "#features" },
{ label: "How It Works", href: "#how-it-works" },
{ label: "FAQ", href: "#faq" },
{ label: "Download", href: "#download" },
];
function MenuIcon({ open }: { open: boolean }) {
return (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
{open ? (
<>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</>
) : (
<>
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</>
)}
</svg>
);
}
export function Navbar() {
const [mobileOpen, setMobileOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 8);
window.addEventListener("scroll", handleScroll, { passive: true });
handleScroll();
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 768) {
setMobileOpen(false);
}
};
window.addEventListener("resize", handleResize, { passive: true });
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<nav
className={[
"fixed top-0 left-0 right-0 z-50 transition-all duration-300 backdrop-blur-xl",
scrolled ? "bg-[var(--panel)]/80 border-b border-[var(--border)] shadow-sm" : "bg-transparent border-b border-transparent",
].join(" ")}
>
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
<a href="#" className="flex items-center gap-2.5 shrink-0" aria-label="AgentCompany home">
<IconMark />
<span className="text-[var(--text)] font-serif text-xl font-normal tracking-[-0.4px]">AgentCompany</span>
</a>
<div className="hidden md:flex items-center gap-8">
{navLinks.map(({ label, href }) => (
<a key={href} href={href} className="text-sm text-[var(--muted)] hover:text-[var(--text)] transition-colors duration-200">
{label}
</a>
))}
</div>
<div className="hidden md:flex items-center gap-3">
<ThemeToggle />
<a
href="#download"
className="inline-flex items-center px-5 py-2 rounded-full text-sm font-medium bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)] hover:scale-[1.03] active:scale-[0.98] transition-all duration-200"
>
Download
</a>
</div>
<div className="flex md:hidden items-center gap-2">
<ThemeToggle />
<button
type="button"
onClick={() => setMobileOpen((current) => !current)}
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
aria-expanded={mobileOpen}
aria-controls="mobile-nav-menu"
className="inline-flex items-center justify-center w-11 h-11 rounded-lg text-[var(--muted)] hover:text-[var(--text)] hover:bg-[var(--panel-soft)] transition-colors duration-200"
>
<MenuIcon open={mobileOpen} />
</button>
</div>
</div>
</div>
<div
id="mobile-nav-menu"
className={[
"md:hidden overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]",
mobileOpen ? "max-h-64 opacity-100" : "max-h-0 opacity-0",
].join(" ")}
>
<div className="px-4 pb-4 pt-1 space-y-1 bg-[var(--panel)]/90 backdrop-blur-xl border-b border-[var(--border)]">
{navLinks.map(({ label, href }) => (
<a
key={href}
href={href}
onClick={() => setMobileOpen(false)}
className="block px-3 py-2.5 rounded-lg text-sm text-[var(--muted)] hover:text-[var(--text)] hover:bg-[var(--panel-soft)] transition-colors duration-200"
>
{label}
</a>
))}
<a
href="#download"
onClick={() => setMobileOpen(false)}
className="block px-3 py-2.5 rounded-full text-sm font-medium text-center bg-[var(--cta)] text-[var(--text-on-cta)] hover:bg-[var(--cta-hover)] transition-colors duration-200 mt-2"
>
Download
</a>
</div>
</div>
</nav>
);
}
@@ -0,0 +1,18 @@
import { Reveal } from "../ui/reveal";
import { SectionHeading } from "../ui/section-heading";
import { MediaFrame } from "../ui/media-frame";
import type { FeatureSection as FeatureSectionData } from "../../lib/landing-content";
/** One feature: heading + subhead above a full-width product panel (adaline rhythm). */
export function FeatureSection({ feature }: { feature: FeatureSectionData }) {
return (
<div id={feature.id} className="flex scroll-mt-24 flex-col gap-8 md:gap-10">
<Reveal>
<SectionHeading eyebrow={feature.eyebrow} title={feature.title} description={feature.description} />
</Reveal>
<Reveal delay={90}>
<MediaFrame src={feature.image} alt={feature.imageAlt} />
</Reveal>
</div>
);
}
+15
View File
@@ -0,0 +1,15 @@
import { Container } from "../ui/container";
import { FeatureSection } from "./feature-section";
import { featureSections } from "../../lib/landing-content";
export function Features() {
return (
<section id="features" className="scroll-mt-20 py-20 md:py-28 lg:py-32">
<Container className="flex flex-col gap-24 md:gap-32">
{featureSections.map((feature) => (
<FeatureSection key={feature.id} feature={feature} />
))}
</Container>
</section>
);
}
+65
View File
@@ -0,0 +1,65 @@
import { Container } from "../ui/container";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { MediaFrame } from "../ui/media-frame";
import { landingHeroNarrative } from "../../lib/product-narrative";
export function Hero() {
return (
<section className="relative overflow-hidden">
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0"
style={{
background:
"radial-gradient(ellipse 70% 50% at 50% -5%, var(--glow-warm) 0%, transparent 70%), radial-gradient(ellipse 50% 40% at 50% 10%, var(--glow-accent) 0%, transparent 60%)",
}}
/>
<Container className="relative pb-16 pt-20 md:pb-24 md:pt-28 lg:pt-36">
<div className="flex flex-col items-center text-center">
<Badge className="enter-rise">{landingHeroNarrative.eyebrow}</Badge>
<h1
className="enter-rise mt-7 max-w-[17ch] text-balance text-[40px] font-medium leading-[1.04] tracking-[-0.03em] text-[var(--text)] md:text-[54px] lg:text-[64px]"
style={{ animationDelay: "0.06s" }}
>
{landingHeroNarrative.titleLine1} {landingHeroNarrative.titleLine2}
</h1>
<p
className="enter-rise mt-6 max-w-[60ch] text-lg leading-relaxed text-[var(--muted)] md:text-xl"
style={{ animationDelay: "0.12s" }}
>
{landingHeroNarrative.description}
</p>
<div
className="enter-rise mt-9 flex flex-wrap items-center justify-center gap-3"
style={{ animationDelay: "0.18s" }}
>
<Button href="#download">Download</Button>
<Button href="#how-it-works" variant="ghost">
How it works
</Button>
</div>
<p
className="eyebrow enter-rise mt-6 text-sm text-[var(--muted)]"
style={{ animationDelay: "0.24s" }}
>
{landingHeroNarrative.availability}
</p>
</div>
<div className="enter-rise mt-14 md:mt-20" style={{ animationDelay: "0.3s" }}>
<MediaFrame
src="/images/dashboard.webp"
alt="AgentCompany desktop dashboard showing the quarter goal, budget, agent workload, and recent runs"
priority
/>
</div>
</Container>
</section>
);
}
+4 -26
View File
@@ -1,31 +1,9 @@
"use client";
import { Sun } from "reicon-react/icons/Sun";
import { Moon } from "reicon-react/icons/Moon";
import { useTheme } from "./theme-provider";
function SunIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
);
}
function MoonIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
);
}
export function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
@@ -34,9 +12,9 @@ export function ThemeToggle() {
type="button"
onClick={toggleTheme}
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
className="relative inline-flex items-center justify-center w-11 h-11 rounded-lg transition-colors duration-200 cursor-pointer hover:bg-[var(--panel-soft)] text-[var(--muted)] hover:text-[var(--text)]"
className="inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-full text-[var(--muted)] transition-colors hover:bg-[var(--panel-soft)] hover:text-[var(--text)]"
>
{theme === "light" ? <MoonIcon /> : <SunIcon />}
{theme === "light" ? <Moon size={18} /> : <Sun size={18} />}
</button>
);
}
+29
View File
@@ -0,0 +1,29 @@
/** Framed product screenshot panel: rounded, bordered, soft shadow on the page. */
export function MediaFrame({
src,
alt,
priority = false,
className = "",
}: {
src: string;
alt: string;
priority?: boolean;
className?: string;
}) {
return (
<div
className={`overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--panel-illustration)] shadow-[0_24px_70px_-28px_rgba(10,29,8,0.3)] md:rounded-2xl ${className}`}
>
<img
src={src}
alt={alt}
width={2400}
height={1600}
loading={priority ? "eager" : "lazy"}
decoding="async"
fetchPriority={priority ? "high" : "auto"}
className="block h-auto w-full"
/>
</div>
);
}