# Agent Session Hero

> A hero for a developer tool that coding agents use. Under the headline an Agent Session plays: the agent adds a signup funnel to a live dashboard, each bar filling as its tracking call is written. Your mark's orbit is printed in glyphs around the headline and inks under the pointer, a Release Pill sits above it, and the nav docks into a pill that catches the hero's button as you scroll.

A Hero Section block from KeyframeUI: a whole landing-page section built around the Agent Session, Glyph Field, Handoff Nav and Release Pill components, free to install as source with the shadcn CLI. Built with React, Tailwind CSS and Motion.

- Live preview: https://keyframeui.com/blocks/hero-sections/agent-session-hero
- Category: Hero Sections
- Built with: [Agent Session](https://keyframeui.com/components/agent-session.md), [Glyph Field](https://keyframeui.com/components/glyph-field.md), [Handoff Nav](https://keyframeui.com/components/handoff-nav.md), [Release Pill](https://keyframeui.com/components/release-pill.md)
- npm dependencies: motion, clsx, tailwind-merge

## Installation

Register the @keyframeui registry in `components.json` once per project:

```json
{
  "registries": {
    "@keyframeui": "https://keyframeui.com/r/{name}.json"
  }
}
```

Then add the block. It lands in `components/agent-session-hero.tsx`, and the CLI also adds the components it's built with (`components/ui/agent-session.tsx`, `components/ui/glyph-field.tsx`, `components/ui/handoff-nav.tsx`, `components/ui/release-pill.tsx`) and `lib/utils.ts`, and installs the npm dependencies:

```bash
npx shadcn@latest add @keyframeui/agent-session-hero
```

Or install by URL, without registering the namespace:

```bash
npx shadcn@latest add https://keyframeui.com/r/agent-session-hero.json
```

## Usage

Put it where the section goes. The copy, links and data sit in constants at the top of the file; edit them there.

```tsx
import { AgentSessionHero } from "@/components/agent-session-hero";

export default function Page() {
  return <AgentSessionHero />;
}
```

## Source

```tsx title="components/agent-session-hero.tsx"
"use client";

import * as React from "react";
import { motion, useReducedMotion } from "motion/react";

import {
  AgentSession,
  AgentSessionTarget,
  useAgentSessionEffect,
  type AgentSessionStep,
} from "@/components/ui/agent-session";
import { GlyphField } from "@/components/ui/glyph-field";
import { HandoffNav, HandoffNavCta, HandoffNavProvider } from "@/components/ui/handoff-nav";
import { ReleasePill, type Release } from "@/components/ui/release-pill";

/* ── Content ─────────────────────────────────────────────────
 * Everything you'd change to make the hero yours: the nav, the
 * release notes in the pill, the copy, and the script the agent
 * runs under the headline. The dashboard it changes is plain
 * markup further down; its numbers are here too.
 * ─────────────────────────────────────────────────────────── */

/** The one accent: glyph ink, the session's glow and rings, and the dashboard's highlights. */
const ACCENT = { light: "#ea580c", dark: "#fb923c" };

const BRAND = { name: "Tern", href: "#" };

const NAV_LINKS = [
  { label: "Product", href: "#product" },
  { label: "Docs", href: "#docs" },
  { label: "Pricing", href: "#pricing" },
  { label: "Changelog", href: "#changelog" },
];
const SIGN_IN = { label: "Log in", href: "#login" };

const RELEASES: Release[] = [
  {
    version: "v2.4",
    title: "Funnels from a prompt",
    date: "Oct 28",
    href: "#changelog-v2-4",
    summary: "Ask your agent for a funnel and it writes the chart and every tracking call behind it.",
  },
  { version: "v2.3", title: "Slack alerts on any event", date: "Oct 14", href: "#changelog-v2-3" },
  { version: "v2.2", title: "Weekly retention cohorts", date: "Sep 30", href: "#changelog-v2-2" },
  { version: "v2.1", title: "Keys stay in your terminal", date: "Sep 16", href: "#changelog-v2-1" },
];
const CHANGELOG_HREF = "#changelog";

const HEADLINE = "Ship the feature.";
const HEADLINE_QUIET = "Your agent tracks it.";
const SUBLINE =
  "Tern is product analytics for teams that build with coding agents. Ask in a sentence: the agent finds the events, writes the tracking code and puts the chart on your dashboard.";
const PRIMARY_CTA = { label: "Start free", href: "#start" };
const SECONDARY_CTA = { label: "Set up your agent", href: "#agent-setup" };
const FOOTNOTE = "Free up to 10,000 events a month. Works with any agent that can run a command.";

/** Under the session: who uses it. Wordmarks set in type; swap in your customers' logos. */
const CUSTOMERS_LABEL = "Teams that ship with their agents";
const CUSTOMERS: { name: string; className: string }[] = [
  { name: "Kiln", className: "font-semibold tracking-tight" },
  { name: "Ferngrove", className: "font-serif italic" },
  { name: "LUMENHAUS", className: "font-mono text-[13px] tracking-[0.2em]" },
  { name: "Okto", className: "font-bold tracking-[-0.04em]" },
  { name: "Field & Fold", className: "font-serif" },
  { name: "harbor", className: "font-medium lowercase tracking-wide" },
];

/* The session under the headline, and the dashboard it changes. */

const TERMINAL_TITLE = "web — agent";
const APP_URL = "localhost:3000/dashboard";

const SCRIPT: AgentSessionStep[] = [
  { prompt: "Add a signup funnel to the dashboard and track each step" },
  {
    run: "npx tern events list --match signup",
    ms: 900,
    output: [
      "signup_viewed      page     /signup",
      "signup_submitted   form     #signup-form",
      "email_verified     server   POST /api/verify",
    ],
    more: 4,
  },
  {
    run: "npx tern add funnel signup --to app/dashboard",
    ms: 1100,
    output: [
      { text: "+ app/dashboard/signup-funnel.tsx   SignupFunnel", tone: "added", effect: "funnel" },
      { text: "~ app/dashboard/layout.tsx          Funnels (1)", effect: "nav" },
    ],
  },
  {
    run: "npx tern track signup_viewed signup_submitted email_verified",
    ms: 1200,
    pace: 420,
    output: [
      { text: '+ app/signup/page.tsx       track("signup_viewed")', tone: "added", effect: "step-1" },
      { text: '+ app/signup/form.tsx       track("signup_submitted")', tone: "added", effect: "step-2" },
      { text: '+ app/api/verify/route.ts   track("email_verified")', tone: "added", effect: "step-3" },
    ],
  },
  {
    say: "Added a three-step signup funnel. Last week 1,204 of 12,480 visitors finished, and most of the drop happens before the form is sent.",
  },
];

const DASHBOARD_NAV = ["Overview", "Funnels", "Events", "Retention", "Alerts"];
const STATS = [
  { label: "Visitors", value: "12,480", delta: "+8.2%" },
  { label: "Signups", value: "1,204", delta: "+3.1%" },
  { label: "Active today", value: "3,906", delta: "+1.4%" },
];
const FUNNEL = [
  { effect: "step-1", label: "Viewed signup", count: "12,480", share: 1 },
  { effect: "step-2", label: "Sent the form", count: "2,911", share: 0.233 },
  { effect: "step-3", label: "Verified email", count: "1,204", share: 0.096 },
];

/* ── Hero ────────────────────────────────────────────────── */

const focusRing =
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-950 dark:focus-visible:outline-white";

export function AgentSessionHero() {
  return (
    <section
      style={{ "--accent-light": ACCENT.light, "--accent-dark": ACCENT.dark } as React.CSSProperties}
      // The Release Pill draws with the shadcn theme colors; point them at this palette.
      className="relative isolate overflow-x-clip bg-zinc-50 text-zinc-950 [--accent:var(--accent-light)] [--background:#fafafa] [--foreground:#09090b] [--muted-foreground:#71717a] dark:bg-zinc-950 dark:text-white dark:[--accent:var(--accent-dark)] dark:[--background:#09090b] dark:[--foreground:#fafafa] dark:[--muted-foreground:#a1a1aa]"
    >
      <HandoffNavProvider>
        <HandoffNav
          logo={
            <a href={BRAND.href} className={`flex items-center gap-2 rounded-md text-[15px] font-semibold tracking-tight ${focusRing}`}>
              <TernMark className="size-5 text-(--accent)" />
              {BRAND.name}
            </a>
          }
          links={NAV_LINKS}
          action={SIGN_IN}
          className="[--handoff-nav-width:72rem]"
        />

        {/* "cover" sizes the orbit to the width, so its band sweeps around the copy, not through it. */}
        <GlyphField
          fit="cover"
          glyphClassName="text-zinc-300 dark:text-zinc-700"
          accentClassName="text-(--accent)"
          canvasClassName="opacity-60 sm:opacity-100"
          className="px-6 pt-10 pb-12 text-center sm:pt-14"
        >
          <ReleasePill releases={RELEASES} changelogHref={CHANGELOG_HREF} />
          <h1 className="mx-auto mt-6 max-w-[15ch] text-[2.75rem]/[1.02] font-semibold tracking-[-0.04em] text-balance sm:text-6xl/[1] lg:text-7xl/[0.98]">
            {HEADLINE} <span className="block text-zinc-400 dark:text-zinc-500">{HEADLINE_QUIET}</span>
          </h1>
          <p className="mx-auto mt-6 max-w-xl text-base/7 text-pretty text-zinc-600 sm:text-lg/8 dark:text-zinc-400">
            {SUBLINE}
          </p>
          <div className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-3">
            <a
              href={SECONDARY_CTA.href}
              className={`group inline-flex items-center gap-1 rounded-md text-sm font-medium text-zinc-700 transition-colors hover:text-zinc-950 dark:text-zinc-300 dark:hover:text-white ${focusRing}`}
            >
              {SECONDARY_CTA.label}
              <ArrowIcon className="size-3.5 transition-transform duration-200 group-hover:translate-x-0.5" />
            </a>
            <HandoffNavCta href={PRIMARY_CTA.href}>
              {PRIMARY_CTA.label}
              <ArrowIcon className="size-3.5" />
            </HandoffNavCta>
          </div>
          <p className="mt-5 text-sm text-zinc-500">{FOOTNOTE}</p>
        </GlyphField>

        <div className="mx-auto max-w-6xl px-6 pb-20 sm:pb-24">
          <AgentSession
            script={SCRIPT}
            title={TERMINAL_TITLE}
            url={APP_URL}
            className="[--agent-session-accent:var(--accent)] [--agent-session-height:22rem] [--agent-session-preview-height:20.5rem] sm:[--agent-session-height:26rem] dark:[--agent-session-accent:var(--accent)]"
          >
            <Dashboard />
          </AgentSession>

          <div className="mt-14 text-center sm:mt-16">
            <p className="text-sm text-zinc-500">{CUSTOMERS_LABEL}</p>
            <ul className="mt-5 flex flex-wrap items-center justify-center gap-x-10 gap-y-4 text-lg text-zinc-400 dark:text-zinc-500">
              {CUSTOMERS.map((customer) => (
                <li key={customer.name} className={customer.className}>
                  {customer.name}
                </li>
              ))}
            </ul>
          </div>
        </div>
      </HandoffNavProvider>
    </section>
  );
}

/* ── The dashboard the agent changes ─────────────────────── */

function Dashboard() {
  return (
    <div className="@container/app flex h-full text-zinc-900 dark:text-zinc-100">
      <aside className="hidden w-36 shrink-0 flex-col gap-0.5 border-r border-zinc-950/[0.06] bg-zinc-50/70 p-2.5 @[28rem]/app:flex dark:border-white/[0.06] dark:bg-white/[0.02]">
        <div className="mb-3 flex items-center gap-1.5 px-1.5 pt-1 text-[13px] font-semibold tracking-tight">
          <TernMark className="size-3.5 text-(--accent)" />
          {BRAND.name}
        </div>
        {DASHBOARD_NAV.map((item, i) => (
          <div
            key={item}
            className={
              i === 0
                ? "flex items-center justify-between rounded-md bg-white px-2 py-1.5 text-xs font-medium shadow-sm ring-1 ring-zinc-950/5 dark:bg-white/10 dark:ring-white/5"
                : "flex items-center justify-between rounded-md px-2 py-1.5 text-xs text-zinc-500 dark:text-zinc-400"
            }
          >
            {item}
            {item === "Funnels" && (
              <AgentSessionTarget effect="nav" className="[--agent-session-ring-radius:999px]">
                <span className="rounded-full bg-(--accent) px-1.5 text-[10px] leading-4 font-semibold text-white tabular-nums dark:text-zinc-950">
                  1
                </span>
              </AgentSessionTarget>
            )}
          </div>
        ))}
      </aside>

      <div className="flex min-w-0 flex-1 flex-col gap-3 p-4">
        <div className="flex items-baseline justify-between">
          <h3 className="text-sm font-semibold tracking-tight">Overview</h3>
          <span className="text-[11px] text-zinc-400">Last 7 days</span>
        </div>
        <div className="grid grid-cols-2 gap-2 @[34rem]/app:grid-cols-3">
          {STATS.map((stat, i) => (
            <div
              key={stat.label}
              className={`rounded-xl bg-white p-2.5 ring-1 ring-zinc-950/[0.08] dark:bg-white/[0.04] dark:ring-white/10 ${i === 2 ? "hidden @[34rem]/app:block" : ""}`}
            >
              <p className="text-[11px] text-zinc-500 dark:text-zinc-400">{stat.label}</p>
              <p className="mt-0.5 flex items-baseline gap-1.5">
                <span className="text-base font-semibold tracking-tight tabular-nums">{stat.value}</span>
                <span className="text-[10px] font-medium text-emerald-600 dark:text-emerald-400">{stat.delta}</span>
              </p>
            </div>
          ))}
        </div>

        <AgentSessionTarget
          effect="funnel"
          className="[--agent-session-ring-radius:14px]"
          fallback={
            <div className="flex h-full min-h-36 flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-zinc-950/15 text-center dark:border-white/15">
              <p className="text-xs font-medium">No funnels yet</p>
              <p className="text-[11px] text-zinc-400">Ask your agent for one.</p>
            </div>
          }
        >
          <div className="rounded-xl bg-white p-3 shadow-sm ring-1 ring-zinc-950/[0.08] dark:bg-white/[0.04] dark:ring-white/10">
            <div className="mb-2.5 flex items-baseline justify-between">
              <p className="text-xs font-semibold">Signup funnel</p>
              <p className="text-[11px] text-zinc-400 tabular-nums">9.6% convert</p>
            </div>
            <div className="flex flex-col gap-2">
              {FUNNEL.map((step) => (
                <FunnelRow key={step.effect} {...step} />
              ))}
            </div>
          </div>
        </AgentSessionTarget>
      </div>
    </div>
  );
}

// Each bar fills once the tracking call for its step is written.
function FunnelRow({ effect, label, count, share }: (typeof FUNNEL)[number]) {
  const landed = useAgentSessionEffect(effect);
  const reduced = useReducedMotion();
  return (
    <AgentSessionTarget effect={effect} enter="none" className="[--agent-session-ring-radius:8px]">
      <div className="flex flex-col gap-1">
        <div className="flex items-baseline justify-between text-[11px]">
          <span className="text-zinc-600 dark:text-zinc-300">{label}</span>
          <span className={`font-medium tabular-nums ${landed ? "" : "text-zinc-300 dark:text-zinc-600"}`}>
            {landed ? count : "—"}
          </span>
        </div>
        <div className="h-1.5 overflow-hidden rounded-full bg-zinc-100 dark:bg-white/[0.07]">
          <motion.div
            initial={false}
            animate={{ transform: `scaleX(${landed ? share : 0})` }}
            transition={reduced ? { duration: 0 } : { type: "spring", visualDuration: 0.6, bounce: 0 }}
            className="h-full origin-left rounded-full bg-(--accent)"
          />
        </div>
      </div>
    </AgentSessionTarget>
  );
}

/* ── Icons ───────────────────────────────────────────────── */

function TernMark({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 24 24" className={className} aria-hidden>
      <path d="M1 7.5c4.2-.9 8 1.2 11 5 3-3.8 6.8-5.9 11-5-4.4 1.4-8.1 5-11 10.5C9.1 12.5 5.4 8.9 1 7.5Z" fill="currentColor" />
    </svg>
  );
}

function ArrowIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth={1.6} aria-hidden>
      <path d="M3.5 8h9m-3.5-3.5L12.5 8 9 11.5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}
```
