A form builder's hero. Two notes write themselves in, one curling its arrow up to the call to action, the other pointing at the live reply count.
registry/demos/jot-demo.tsx
"use client";
import * as React from "react";
import { Jot } from "@/components/ui/jot";
// The last twelve hours of replies, oldest first; the newest bar grows as replies come in.
const HOURS = [18, 26, 22, 35, 41, 38, 52, 61, 48, 57, 66, 44];
export function JotDemo() {
const ctaRef = React.useRef<HTMLAnchorElement>(null);
const statsRef = React.useRef<HTMLDivElement>(null);
const [replies, setReplies] = React.useState(1284);
// A reply or two every couple of seconds, so "live" is true.
React.useEffect(() => {
const timer = window.setInterval(() => setReplies((n) => n + 1 + Math.floor(Math.random() * 2)), 2200);
return () => window.clearInterval(timer);
}, []);
const latest = HOURS[HOURS.length - 1] + (replies - 1284) * 2;
const top = Math.max(...HOURS, latest);
return (
<div data-jot-scope className="@container/jot relative w-full max-w-[56rem] px-2 pt-24 pb-32 @lg/jot:px-6 @3xl/jot:pt-28 @3xl/jot:pb-40">
<div className="mx-auto grid max-w-[38rem] gap-8 rounded-[1.75rem] border border-zinc-950/10 bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-12px_rgb(0_0_0/0.12)] @lg/jot:grid-cols-[1fr_12rem] @lg/jot:p-8 dark:border-white/10 dark:bg-zinc-900 dark:shadow-none">
<div className="flex flex-col">
<p className="flex items-center gap-2 text-[13px] font-medium text-zinc-500 dark:text-zinc-400">
<svg viewBox="0 0 20 20" aria-hidden className="size-5">
<rect width="20" height="20" rx="6" className="fill-zinc-950 dark:fill-white" />
<path
d="M5.5 7.5 7.6 13l2.4-5 2.4 5 2.1-5.5"
className="fill-none stroke-white dark:stroke-zinc-950"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Wren Forms
</p>
<h2 className="mt-5 text-[2rem]/[1.05] font-semibold tracking-[-0.03em] text-balance text-zinc-950 dark:text-white">
Forms people actually finish.
</h2>
<p className="mt-3 max-w-[30ch] text-[15px]/6 text-pretty text-zinc-600 dark:text-zinc-400">
One question at a time, saved as they type, easy to answer on a phone.
</p>
<div className="mt-7 flex flex-wrap items-center gap-x-5 gap-y-3">
<a
ref={ctaRef}
href="#build"
className="inline-flex h-10 items-center gap-1.5 rounded-full bg-zinc-950 px-5 text-sm font-medium text-white outline-none hover:bg-zinc-800 focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 dark:focus-visible:ring-white dark:focus-visible:ring-offset-zinc-900"
>
Build a form
<svg viewBox="0 0 16 16" aria-hidden className="size-4">
<path d="M6 4l4 4-4 4" className="fill-none stroke-current" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
<a
href="#templates"
className="rounded-sm text-sm font-medium text-zinc-600 underline decoration-zinc-300 underline-offset-4 outline-none hover:text-zinc-950 hover:decoration-zinc-950 focus-visible:ring-2 focus-visible:ring-zinc-950 dark:text-zinc-400 dark:decoration-zinc-700 dark:hover:text-white dark:hover:decoration-white dark:focus-visible:ring-white"
>
See templates
</a>
</div>
</div>
<div
ref={statsRef}
className="order-first self-end rounded-2xl @lg/jot:order-none border border-zinc-950/[0.07] bg-zinc-50 p-4 dark:border-white/[0.07] dark:bg-zinc-950/60"
>
<div className="flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400">
<span>Replies today</span>
<span className="flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-emerald-500" />
Live
</span>
</div>
<p className="mt-1.5 text-[1.75rem]/none font-semibold tracking-tight text-zinc-950 tabular-nums dark:text-white">
{replies.toLocaleString("en-US")}
</p>
<div className="mt-4 flex h-12 items-end gap-[3px]" aria-hidden>
{[...HOURS.slice(0, -1), latest].map((h, i) => (
<span
key={i}
className={
i === HOURS.length - 1
? "flex-1 rounded-[2px] bg-zinc-950 transition-[height] duration-500 ease-out dark:bg-white"
: "flex-1 rounded-[2px] bg-zinc-300 dark:bg-zinc-700"
}
style={{ height: `${(h / top) * 100}%` }}
/>
))}
</div>
<p className="mt-3 flex justify-between text-xs text-zinc-500 tabular-nums dark:text-zinc-400">
<span>Finish rate</span>
<span className="font-medium text-zinc-950 dark:text-white">71%</span>
</p>
</div>
</div>
<Jot
to={ctaRef}
loop
bend={-0.35}
tilt={-3}
className="absolute bottom-5 left-4 max-w-44 text-xl/snug text-blue-600 @lg/jot:left-6 @3xl/jot:bottom-7 @3xl/jot:left-4 dark:text-blue-400"
>
free for your first 1,000 replies
</Jot>
<Jot
to={statsRef}
bend={0.3}
tilt={2}
delay={0.7}
className="absolute top-4 right-4 max-w-none text-xl/snug text-blue-600 @lg/jot:right-6 @3xl/jot:top-6 @3xl/jot:right-4 dark:text-blue-400"
>
real replies,
<br />
counted as they land
</Jot>
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/jot#jot-demo
