Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions website/public/icons/social-linkedin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 45 additions & 9 deletions website/src/components/HowItWorks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
* HowItWorks — "Do the work in chat. Then automate it."
* Ported 1:1 from the dark landing DC (hiw* scroll logic + markup).
*
* Desktop + motion allowed: a 500vh section with a position:sticky, 100vh inner
* Desktop + motion allowed: a tall (SECTION_VH) section with a position:sticky, 100vh inner
* stage. Scroll progress across the section maps to 6 stages (0..5) of ONE chat
* thread. Reduced motion OR viewport <=860px: NO sticky scroll — two stacked
* static panels ("Work in chat" / "Automate"), each showing its slice of the
Expand All @@ -24,6 +24,12 @@ import {
const GEIST = "'Geist',var(--font-sans)";
const AGENTA_SYMBOL = "/logos/Agenta-symbol-dark-accent.svg";

// Scroll length of the pinned stage. The 6 stages are mapped across
// (SECTION_VH − 100vh) of scroll, so each beat advances every
// (SECTION_VH − 100) / 6 vh. Smaller = snappier (less scrolling per beat).
// Was 500 (~67vh/beat); 320 is ~37vh/beat — tune here if beats fly by / drag.
const SECTION_VH = 320;

Comment on lines +27 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the wrapper’s scroll-height comment.

website/src/components/HowItWorks.astro still says the desktop layout owns a 500vh scroll layout (Lines 4-5), while this component now renders 320vh. Update that comment to 320vh or reference SECTION_VH.

// Stage at which each of the 7 message blocks first appears (dc: revealAt).
const REVEAL_AT = [0, 1, 2, 3, 4, 5, 5];

Expand Down Expand Up @@ -208,7 +214,7 @@ function renderBlock(i: number, wrap: CSSProperties): ReactNode {
}}
>
2m ago{" "}·{" "}5.3s{" "}·{" "}28.3K tokens
{" "}·{" "}$0.00
{" "}·{" "}$0.14
</span>
</div>
);
Expand Down Expand Up @@ -360,7 +366,7 @@ function renderBlock(i: number, wrap: CSSProperties): ReactNode {
}}
>
Mon 09:02{" "}·{" "}41s{" "}·{" "}12.1K tokens
{" "}·{" "}$0.00
{" "}·{" "}$0.06
</span>
</div>
);
Expand Down Expand Up @@ -634,6 +640,13 @@ function ChatCard({
height: string;
justify: CSSProperties["justifyContent"];
}) {
// Scroll layout only: the thread is bottom-anchored (justify:flex-end) and
// grows upward as stages reveal, so it overflows the top. Clip it here and
// fade the top edge so messages dissolve into the card instead of being cut
// by a hard overflow slice. The static layout (auto height, flex-start) has
// no overflow, so it needs no mask.
const scrolled = justify === "flex-end";
const TOP_FADE = "linear-gradient(to bottom, transparent 0, #000 52px)";
return (
<div
style={{
Expand All @@ -660,6 +673,13 @@ function ChatCard({
gap: 14,
padding: "20px 24px",
textAlign: "left",
...(scrolled
? {
overflow: "hidden",
WebkitMaskImage: TOP_FADE,
maskImage: TOP_FADE,
}
: {}),
}}
>
{blocks}
Expand Down Expand Up @@ -788,9 +808,15 @@ export default function HowItWorks() {
}, []);

// Scroll → stage, only while the sticky-scroll layout is active.
// Scroll/resize events fire faster than the display refreshes; we coalesce a
// burst into a single rAF so the one layout read (getBoundingClientRect) and
// stage check happen at most once per frame. That keeps the main thread free
// during fast scrolls instead of forcing a reflow on every raw event.
useEffect(() => {
if (!scroll) return;
const onScroll = () => {
let raf = 0;
const measure = () => {
raf = 0;
const el = sectionRef.current;
if (!el) return;
const rect = el.getBoundingClientRect();
Expand All @@ -799,14 +825,21 @@ export default function HowItWorks() {
const next = Math.min(5, Math.floor(p * 6));
setStage((prev) => (prev !== next ? next : prev));
};
const onResize = () => setShortViewport(window.innerHeight < 700);
window.addEventListener("scroll", onScroll, { passive: true });
const schedule = () => {
if (!raf) raf = requestAnimationFrame(measure);
};
const onResize = () => {
setShortViewport(window.innerHeight < 700);
schedule();
};
window.addEventListener("scroll", schedule, { passive: true });
window.addEventListener("resize", onResize);
onScroll();
measure();
onResize();
return () => {
window.removeEventListener("scroll", onScroll);
window.removeEventListener("scroll", schedule);
window.removeEventListener("resize", onResize);
if (raf) cancelAnimationFrame(raf);
};
}, [scroll]);

Expand Down Expand Up @@ -893,7 +926,10 @@ export default function HowItWorks() {
const beatActive = [stage <= 2, stage >= 3];

return (
<div ref={sectionRef} style={{ height: "500vh", boxSizing: "border-box" }}>
<div
ref={sectionRef}
style={{ height: `${SECTION_VH}vh`, boxSizing: "border-box" }}
>
<div
style={{
position: "sticky",
Expand Down
7 changes: 5 additions & 2 deletions website/src/components/SiteFooter.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
// internal routes point at their intended path (some 404 until those pages ship).
const blurb = "Fast-tracking LLM apps to production.";

// icon files: social-1 = X, social-2 = Slack, social-3 = GitHub,
// social-linkedin = LinkedIn. (social-4/social-5 are an OpenAI mark and a play
// glyph — not ours to show here, so they're left unreferenced.)
const socials = [
{ platform: "x", url: "https://x.com/agenta_ai", icon: "/icons/social-1.svg" },
{
platform: "linkedin",
url: "https://www.linkedin.com/company/agenta-ai",
icon: "/icons/social-2.svg",
icon: "/icons/social-linkedin.svg",
},
{
platform: "github",
Expand All @@ -19,7 +22,7 @@ const socials = [
{
platform: "slack",
url: "https://join.slack.com/t/agenta-hq/shared_invite/zt-37pnbp5s6-mbBrPL863d_oLB61GSNFjw",
icon: "/icons/social-4.svg",
icon: "/icons/social-2.svg",
},
];

Expand Down
27 changes: 18 additions & 9 deletions website/src/components/SiteNav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,30 @@ const triggerStyle = linkStyle + "border:none;background:transparent;";
toggle.setAttribute("aria-expanded", open ? "true" : "false");
});

// Nav-pill: landing only. The rAF loop toggles `.nav-scrolled` on <html> past
// 24px so the sticky bar floats into the glass pill (styles in global.css). It
// runs ONLY when a `nav[data-pill]` is present — inner pages render a static
// bar with no pill, so the loop never starts and `.nav-scrolled` never toggles.
// Nav-pill: landing only. Toggles `.nav-scrolled` on <html> past 24px so the
// sticky bar floats into the glass pill (styles in global.css). Wired ONLY when
// a `nav[data-pill]` is present — inner pages render a static bar with no pill,
// so nothing runs and `.nav-scrolled` never toggles.
// Driven by a passive scroll listener (not an always-on rAF loop), so it does
// no work while the page is idle; each scroll burst coalesces into one read per
// frame via rAF, with the state check gated on an actual crossing of the 24px
// threshold to avoid redundant classList writes.
const root = document.documentElement;
if (document.querySelector("nav[data-pill]") && !(window as any).__agNavPill) {
(window as any).__agNavPill = true;
const tick = () => {
const y = window.scrollY || root.scrollTop || 0;
const on = y > 24;
let raf = 0;
const measure = () => {
raf = 0;
const on = (window.scrollY || root.scrollTop || 0) > 24;
if (on !== root.classList.contains("nav-scrolled")) {
root.classList.toggle("nav-scrolled", on);
}
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
const schedule = () => {
if (!raf) raf = requestAnimationFrame(measure);
};
window.addEventListener("scroll", schedule, { passive: true });
window.addEventListener("resize", schedule, { passive: true });
measure();
}
</script>
9 changes: 5 additions & 4 deletions website/src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,11 @@
--section-pad-y-dark: 128px;

/* ── nav pill geometry (scrolled state) ───────────────────────
Matched to live agenta.ai: the bar keeps its full 68px height, floats
24px below the viewport, and rounds to 16px. Consumed by the
nav-scrolled rules in global.css. */
--nav-pill-inset: 24px;
The bar keeps its full 68px height, floats just below the viewport, and
rounds to 16px. Consumed by the nav-scrolled rules in global.css. The
inset is intentionally tighter than agenta.ai's 24px so the floating pill
reclaims top space instead of eating into the viewport. */
--nav-pill-inset: 12px;
--nav-pill-radius: 16px;
--nav-pill-max: 1256px;
--nav-pad-x: 32px;
Expand Down
Loading