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:
1 parent
b1e2374c7b
commit
c0f677ccc9
12 files changed
+261
-487
No files matched your search
+12
-5
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user