# Nimbus — Product Analytics — AI website prompt and React landing page template

> Airy light SaaS page with violet and sky gradients, a detailed drawn dashboard whose charts animate in and morph between date ranges, a bento feature grid and a pricing teaser.

- Style: Soft gradient · Light
- Tags: saas, light, minimal
- Live preview: https://pixelpapa.com/sites/site-nimbus-analytics

## The prompt

Paste this into Claude, Cursor, v0 or Lovable, then describe your product.

```text
Build a one-page landing site for "Nimbus", a product analytics tool, as a single React component.

## Art direction

Mood: a bright morning. Page background #faf9ff with white cards, lavender hairlines #e7e3f6 and ink text #17152b (secondary #5c5977). Brand colours are violet #7257ff and sky #4fb6ff, almost always used together as a soft gradient: blurred blobs behind the hero, gradient text, chart strokes, the highlighted plan border and the final CTA panel. Type is a clean semibold grotesk with tight tracking (-0.04em); each headline has one word in an italic display serif filled with the violet-to-sky gradient. Shapes are generous: 24px card radii, full-pill buttons, soft violet-tinted shadows.

## Layout

1. Floating pill nav, sticky near the top: cloud logo, five links, "Sign in", dark "Start free" button.
2. Hero, centred: "New" announcement pill, headline "See what your users actually do", one sentence, two buttons. Directly below, a large drawn dashboard window: left sidebar; main area with title, a 7d/30d/90d segmented control, four KPI tiles (value, green delta chip, sparkline), a wide area chart with a gradient line, soft fill and dashed previous-period line, and a signup funnel of four horizontal bars. Background: two blurred colour blobs and a faint masked grid.
3. Row of six muted fictional wordmarks.
4. Bento grid (3 columns): a wide funnel tile with drop-off chips; a retention heatmap triangle; an "ask in plain English" question-and-answer tile; a paths diagram of three curved flows; an alerts tile with two notification rows.
5. Proof band on white: four gradient count-up stats and a lavender quote card with initials avatar.
6. Pricing teaser: heading, monthly/annual switch, three plan cards with the middle one gradient-bordered and tagged "Most popular".
7. Gradient CTA panel with white button, then a one-line footer.

## Motion

- Hero copy staggers up (0.09s apart, 0.8s, ease [0.22,1,0.36,1]); the dashboard rises 60px.
- Charts animate in: sparklines draw, the area chart wipes left to right over 1.6s, funnel bars grow in sequence.
- Signature: switching the date range slides the pill and morphs the line, area and dashed paths to new data over 0.7s.
- Bento bars and path strokes animate once in view; heatmap cells pulse on hover.
- Stats count up; the pricing thumb springs across and the price swaps.

## Rules

React + Tailwind CSS v4 + Motion (motion/react), lucide-react icons. No external images, fonts or chart libraries — the dashboard is plain markup and inline SVG. Fixed light palette regardless of host theme. Responsive to 360px (sidebar hides, KPIs go two-up) with no horizontal overflow. Proper tab and switch roles, focus rings, chart aria-label. Respect prefers-reduced-motion.

Replace the brand, copy and palette with mine: <describe your product here>
```

## Or install the code

```bash
npx shadcn@latest add "https://pixelpapa.com/r/site-nimbus-analytics.json"
```

The whole page is one file (420 lines) with no external images: visuals are drawn in CSS and SVG.

## Source

```tsx
"use client";

import { ArrowRight, Bell, Check, Filter, GitFork, LayoutGrid, Sparkles, Users } from "lucide-react";
import { MotionConfig, animate, motion, useInView, useReducedMotion } from "motion/react";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { cn } from "@/lib/utils";

const ease = [0.22, 1, 0.36, 1] as const;
const rise = { hidden: { opacity: 0, y: 22 }, shown: { opacity: 1, y: 0, transition: { duration: 0.8, ease } } };
const card = "rounded-3xl border border-[#e7e3f6] bg-white";
const gradText = "bg-linear-to-r from-[#7257ff] to-[#3aa8f5] bg-clip-text text-transparent";

function Reveal({ children, className, delay = 0 }: { children: ReactNode; className?: string; delay?: number }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 24 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, amount: 0.2 }}
      transition={{ duration: 0.7, delay, ease }}
      className={className}
    >
      {children}
    </motion.div>
  );
}

function Logo() {
  return (
    <a href="#" className="flex items-center gap-2 text-lg font-semibold tracking-tight">
      <svg viewBox="0 0 28 20" className="h-5" aria-hidden>
        <defs>
          <linearGradient id="pp-site-nimbus-logo" x1="0" y1="0" x2="1" y2="1">
            <stop stopColor="#7257ff" />
            <stop offset="1" stopColor="#4fb6ff" />
          </linearGradient>
        </defs>
        <path d="M7 20a7 7 0 0 1-.6-13.97A9 9 0 0 1 23.5 8.5 5.8 5.8 0 0 1 22 20Z" fill="url(#pp-site-nimbus-logo)" />
      </svg>
      Nimbus
    </a>
  );
}

/* ── Dashboard ─────────────────────────────────────────────────────────── */

const W = 600;
const H = 190;
function curve(values: number[]) {
  const step = W / (values.length - 1);
  const pts = values.map((v, i) => [i * step, H - (v / 100) * H] as const);
  return pts.reduce((d, [x, y], i) => (i === 0 ? `M${x} ${y}` : `${d} C${pts[i - 1][0] + step / 2} ${pts[i - 1][1]} ${x - step / 2} ${y} ${x} ${y}`), "");
}

const ranges: Record<string, { now: number[]; prev: number[]; total: string }> = {
  "7d": { now: [32, 40, 36, 52, 48, 61, 58, 70, 66, 78, 74, 88], prev: [28, 30, 34, 38, 36, 44, 46, 50, 48, 56, 58, 62], total: "48,291" },
  "30d": { now: [22, 30, 44, 38, 50, 62, 54, 60, 72, 68, 80, 92], prev: [20, 26, 30, 36, 34, 40, 48, 46, 52, 58, 60, 66], total: "196,480" },
  "90d": { now: [14, 20, 26, 36, 34, 46, 58, 56, 66, 76, 84, 94], prev: [12, 16, 22, 24, 30, 34, 38, 44, 46, 52, 56, 60], total: "512,037" },
};

const kpis = [
  { label: "Weekly active", value: "48,291", delta: "+12.4%", spark: [30, 42, 38, 55, 50, 68, 80] },
  { label: "Activation rate", value: "63.8%", delta: "+4.1%", spark: [40, 44, 43, 52, 58, 60, 70] },
  { label: "Median session", value: "6m 42s", delta: "+0:38", spark: [50, 46, 54, 52, 60, 64, 62] },
  { label: "Churn", value: "1.9%", delta: "−0.3%", spark: [70, 62, 64, 50, 46, 38, 30] },
];

const funnel: [string, number, string][] = [["Visited", 100, "24.1k"], ["Signed up", 62, "14.9k"], ["Activated", 41, "9.8k"], ["Subscribed", 18, "4.3k"]];
const side: [typeof LayoutGrid, string][] = [[LayoutGrid, "Overview"], [Filter, "Funnels"], [Users, "Retention"], [GitFork, "Paths"], [Bell, "Alerts"]];

function Dashboard() {
  const [range, setRange] = useState("7d");
  const data = ranges[range];
  return (
    <div className="grid overflow-hidden rounded-[22px] border border-[#e1dcf5] bg-white text-left shadow-[0_50px_120px_-30px_rgba(114,87,255,0.35)] lg:grid-cols-[200px_1fr]">
      <aside className="hidden border-r border-[#efecfa] bg-[#fbfaff] p-4 lg:block">
        <p className="flex items-center gap-2 px-2 text-sm font-semibold"><span className="size-5 rounded-md bg-linear-to-br from-[#7257ff] to-[#4fb6ff]" />Acme Notes</p>
        <ul className="mt-6 space-y-1 text-sm text-[#6b6885]">
          {side.map(([Icon, name], i) => (
            <li key={name} className={cn("flex items-center gap-2.5 rounded-lg px-2.5 py-2", i === 0 && "bg-[#efeaff] font-medium text-[#5a3ff0]")}>
              <Icon className="size-4" />{name}
            </li>
          ))}
        </ul>
        <div className="mt-8 rounded-xl bg-white p-3 text-xs text-[#6b6885] ring-1 ring-[#e7e3f6]">
          <p className="font-medium text-[#17152b]">Events this month</p>
          <div className="mt-2 h-1.5 rounded-full bg-[#efeaff]"><div className="h-full w-[64%] rounded-full bg-[#7257ff]" /></div>
          <p className="mt-2">6.4M of 10M</p>
        </div>
      </aside>
      <div className="min-w-0 p-4 sm:p-6">
        <div className="flex flex-wrap items-center justify-between gap-3">
          <div>
            <p className="text-lg font-semibold tracking-tight">Overview</p>
            <p className="text-xs text-[#6b6885]">Updated 12 seconds ago</p>
          </div>
          <div role="tablist" aria-label="Date range" className="flex rounded-full bg-[#f3f1fb] p-1 text-xs font-medium">
            {Object.keys(ranges).map((r) => (
              <button key={r} type="button" role="tab" aria-selected={range === r} onClick={() => setRange(r)} className={cn("relative rounded-full px-3.5 py-1.5 transition-colors focus-visible:outline-2 focus-visible:outline-[#7257ff]", range === r ? "text-[#17152b]" : "text-[#6b6885]")}>
                {range === r && <motion.span layoutId="site-nimbus-analytics-range" className="absolute inset-0 rounded-full bg-white shadow-sm" />}
                <span className="relative">{r}</span>
              </button>
            ))}
          </div>
        </div>
        <div className="mt-5 grid grid-cols-2 gap-3 xl:grid-cols-4">
          {kpis.map((k, i) => (
            <div key={k.label} className="rounded-2xl border border-[#efecfa] p-3.5 sm:p-4">
              <p className="text-xs text-[#6b6885]">{k.label}</p>
              <p className="mt-1.5 text-xl font-semibold tracking-tight tabular-nums sm:text-2xl">{k.value}</p>
              <div className="mt-2 flex items-end justify-between gap-2">
                <span className="rounded-full bg-[#e8f8ef] px-1.5 py-0.5 text-[11px] font-medium text-[#12804a]">{k.delta}</span>
                <svg viewBox="0 0 60 24" className="h-6 w-16" fill="none" aria-hidden>
                  <motion.polyline
                    points={k.spark.map((v, j) => `${j * 10},${24 - (v / 100) * 24}`).join(" ")}
                    stroke={i % 2 ? "#4fb6ff" : "#7257ff"} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
                    initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} transition={{ duration: 1.2, delay: 0.9 + i * 0.1, ease }}
                  />
                </svg>
              </div>
            </div>
          ))}
        </div>
        <div className="mt-3 grid gap-3 md:grid-cols-[1.7fr_1fr]">
          <div className="rounded-2xl border border-[#efecfa] p-4">
            <div className="flex items-baseline justify-between">
              <p className="text-sm font-medium">Active users <span className="ml-2 text-xl font-semibold tracking-tight tabular-nums">{data.total}</span></p>
              <p className="flex gap-3 text-[11px] text-[#6b6885]">
                <span className="flex items-center gap-1.5"><i className="size-2 rounded-full bg-[#7257ff]" />This period</span>
                <span className="flex items-center gap-1.5"><i className="size-2 rounded-full bg-[#c9c4e3]" />Previous</span>
              </p>
            </div>
            <svg viewBox={`0 0 ${W} ${H + 4}`} className="mt-4 w-full" fill="none" role="img" aria-label={`Active users over ${range}`}>
              <defs>
                <linearGradient id="pp-site-nimbus-area" x1="0" y1="0" x2="0" y2="1">
                  <stop stopColor="#7257ff" stopOpacity="0.28" />
                  <stop offset="1" stopColor="#4fb6ff" stopOpacity="0" />
                </linearGradient>
                <linearGradient id="pp-site-nimbus-line" x1="0" y1="0" x2="1" y2="0">
                  <stop stopColor="#7257ff" />
                  <stop offset="1" stopColor="#3aa8f5" />
                </linearGradient>
                <clipPath id="pp-site-nimbus-wipe">
                  <motion.rect height={H + 4} initial={{ width: 0 }} animate={{ width: W }} transition={{ duration: 1.6, delay: 0.8, ease }} />
                </clipPath>
              </defs>
              {[0.25, 0.5, 0.75, 1].map((g) => <path key={g} d={`M0 ${H * g} H${W}`} stroke="#efecfa" />)}
              <g clipPath="url(#pp-site-nimbus-wipe)">
                <motion.path animate={{ d: curve(data.prev) }} initial={false} transition={{ duration: 0.7, ease }} stroke="#c9c4e3" strokeWidth="2" strokeDasharray="5 6" />
                <motion.path animate={{ d: `${curve(data.now)} L${W} ${H} L0 ${H}Z` }} initial={false} transition={{ duration: 0.7, ease }} fill="url(#pp-site-nimbus-area)" />
                <motion.path animate={{ d: curve(data.now) }} initial={false} transition={{ duration: 0.7, ease }} stroke="url(#pp-site-nimbus-line)" strokeWidth="3" strokeLinecap="round" />
              </g>
            </svg>
          </div>
          <div className="rounded-2xl border border-[#efecfa] p-4">
            <p className="text-sm font-medium">Signup funnel</p>
            <ul className="mt-4 space-y-3">
              {funnel.map(([name, pct, n], i) => (
                <li key={name} className="text-xs">
                  <p className="flex justify-between text-[#6b6885]"><span>{name}</span><span className="tabular-nums">{n} · {pct}%</span></p>
                  <div className="mt-1.5 h-2.5 rounded-full bg-[#f3f1fb]">
                    <motion.div initial={{ width: 0 }} animate={{ width: `${pct}%` }} transition={{ duration: 1, delay: 1.1 + i * 0.12, ease }} className="h-full rounded-full bg-linear-to-r from-[#7257ff] to-[#4fb6ff]" style={{ opacity: 1 - i * 0.16 }} />
                  </div>
                </li>
              ))}
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
}

function Hero() {
  return (
    <section className="relative overflow-hidden">
      <div aria-hidden className="pointer-events-none absolute inset-0">
        <div className="absolute top-[-180px] left-[8%] size-[560px] rounded-full bg-[#b9a8ff] opacity-50 blur-[120px]" />
        <div className="absolute top-[-120px] right-[6%] size-[520px] rounded-full bg-[#9bd8ff] opacity-60 blur-[120px]" />
        <div className="absolute inset-0 [background-image:linear-gradient(#17152b0a_1px,transparent_1px),linear-gradient(90deg,#17152b0a_1px,transparent_1px)] [background-size:56px_56px] [mask-image:radial-gradient(60%_50%_at_50%_20%,black,transparent)]" />
      </div>
      <motion.div initial="hidden" animate="shown" transition={{ staggerChildren: 0.09 }} className="relative mx-auto flex max-w-4xl flex-col items-center px-5 pt-14 text-center sm:pt-16">
        <motion.a variants={rise} href="#" className="inline-flex items-center gap-2 rounded-full border border-[#e1dcf5] bg-white/80 py-1 pr-3 pl-1 text-xs font-medium text-[#4b4866] backdrop-blur">
          <span className="flex items-center gap-1 rounded-full bg-[#efeaff] px-2 py-0.5 text-[#5a3ff0]"><Sparkles className="size-3" />New</span>
          Ask Nimbus anything in plain English <ArrowRight className="size-3" />
        </motion.a>
        <motion.h1 variants={rise} className="mt-6 text-[2.7rem] leading-[1.02] font-semibold tracking-[-0.045em] text-balance sm:text-6xl lg:text-[4.6rem]">
          See what your users <span className={cn(gradText, "font-serif font-normal italic")}>actually</span> do
        </motion.h1>
        <motion.p variants={rise} className="mt-5 max-w-xl text-lg text-pretty text-[#5c5977]">
          Funnels, retention and paths from one snippet. Nimbus turns raw events into answers your whole team can read before stand-up.
        </motion.p>
        <motion.div variants={rise} className="mt-7 flex flex-wrap justify-center gap-3">
          <a href="#" className="inline-flex h-12 items-center gap-2 rounded-full bg-[#17152b] px-6 text-sm font-medium text-white shadow-lg shadow-[#7257ff]/30 transition-transform hover:-translate-y-0.5">Start free <ArrowRight className="size-4" /></a>
          <a href="#" className="inline-flex h-12 items-center rounded-full border border-[#e1dcf5] bg-white px-6 text-sm font-medium transition-colors hover:border-[#7257ff]">Book a demo</a>
        </motion.div>
      </motion.div>
      <motion.div initial={{ opacity: 0, y: 60 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 1, delay: 0.4, ease }} className="relative mx-auto mt-12 max-w-6xl px-4 pb-20 sm:px-6">
        <Dashboard />
        <motion.div initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} transition={{ duration: 0.8, delay: 1.5, ease }} className="absolute bottom-8 -left-2 hidden w-64 rounded-2xl border border-[#e1dcf5] bg-white p-4 text-left shadow-xl shadow-[#7257ff]/15 xl:block">
          <p className="flex items-center gap-1.5 text-xs font-medium text-[#5a3ff0]"><Sparkles className="size-3.5" />Insight</p>
          <p className="mt-2 text-sm leading-snug">Activation rose <b>18%</b> for users who saw onboarding v3.</p>
        </motion.div>
      </motion.div>
    </section>
  );
}

/* ── Logos + bento ─────────────────────────────────────────────────────── */

const logos = ["Brightside", "kelp.io", "NORTHWIND", "Pico Labs", "ferry", "Tandem&Co"];

function Bento() {
  const tile = cn(card, "group overflow-hidden p-6 transition-shadow duration-300 hover:shadow-[0_30px_60px_-30px_rgba(114,87,255,0.45)] sm:p-7");
  return (
    <section className="mx-auto max-w-6xl px-5 pb-24 sm:px-6 lg:pb-32">
      <ul className="flex flex-wrap items-center justify-center gap-x-12 gap-y-4 text-lg font-semibold tracking-tight text-[#17152b]/35">
        {logos.map((l) => <li key={l}>{l}</li>)}
      </ul>
      <Reveal className="mx-auto mt-24 max-w-2xl text-center">
        <p className="text-sm font-medium text-[#5a3ff0]">Everything in one place</p>
        <h2 className="mt-3 text-4xl leading-[1.05] font-semibold tracking-[-0.04em] sm:text-5xl">Answers, not <span className={cn(gradText, "font-serif font-normal italic")}>another</span> chart builder</h2>
      </Reveal>
      <div className="mt-12 grid gap-4 md:grid-cols-3">
        <Reveal className={cn(tile, "md:col-span-2")}>
          <h3 className="text-xl font-semibold tracking-tight">Funnels that explain themselves</h3>
          <p className="mt-1.5 max-w-md text-sm text-[#5c5977]">Every drop-off comes with the segments and events that best predict it.</p>
          <div className="mt-8 flex h-44 items-end gap-3">
            {funnel.map(([name, pct], i) => (
              <div key={name} className="flex h-full flex-1 flex-col justify-end">
                {i > 0 && <span className="mb-2 self-start rounded-full bg-[#fff0f0] px-2 py-0.5 text-[11px] font-medium text-[#c2334a]">−{funnel[i - 1][1] - pct}%</span>}
                <motion.div initial={{ height: 0 }} whileInView={{ height: `${pct}%` }} viewport={{ once: true }} transition={{ duration: 0.9, delay: i * 0.12, ease }} className="rounded-t-xl bg-linear-to-b from-[#7257ff] to-[#b9a8ff]" style={{ opacity: 1 - i * 0.15 }} />
                <p className="mt-2 truncate text-xs text-[#6b6885]">{name}</p>
              </div>
            ))}
          </div>
        </Reveal>
        <Reveal delay={0.08} className={tile}>
          <h3 className="text-xl font-semibold tracking-tight">Retention at a glance</h3>
          <p className="mt-1.5 text-sm text-[#5c5977]">Cohorts that colour in as they stick.</p>
          <div className="mt-6 grid grid-cols-7 gap-1.5" aria-hidden>
            {Array.from({ length: 42 }, (_, i) => {
              const row = Math.floor(i / 7), col = i % 7;
              return col > 6 - row ? <span key={i} /> : <span key={i} className="aspect-square rounded-md bg-[#7257ff] transition-transform group-hover:scale-110" style={{ opacity: Math.max(0.08, 1 - col * 0.14 - row * 0.05), transitionDelay: `${col * 30}ms` }} />;
            })}
          </div>
        </Reveal>
        <Reveal className={tile}>
          <h3 className="text-xl font-semibold tracking-tight">Ask in plain English</h3>
          <p className="mt-1.5 text-sm text-[#5c5977]">No SQL, no ticket to the data team.</p>
          <p className="mt-6 rounded-2xl bg-[#f3f1fb] px-4 py-3 text-sm">Which plan upgrades fastest?<span className="ml-0.5 inline-block h-4 w-px translate-y-0.5 bg-[#17152b] [animation:pp-site-nimbus-blink_1s_steps(2)_infinite] motion-reduce:animate-none" /></p>
          <p className="mt-2 rounded-2xl bg-linear-to-br from-[#7257ff] to-[#4fa8ff] px-4 py-3 text-sm text-white">Team — median 9 days to upgrade, 2.3× faster than Solo.</p>
        </Reveal>
        <Reveal delay={0.08} className={tile}>
          <h3 className="text-xl font-semibold tracking-tight">Paths, untangled</h3>
          <p className="mt-1.5 text-sm text-[#5c5977]">See the routes people really take.</p>
          <svg viewBox="0 0 260 120" className="mt-5 w-full" fill="none" aria-hidden>
            {["M30 60 C100 60 110 22 230 22", "M30 60 C100 60 120 60 230 60", "M30 60 C100 60 110 98 230 98"].map((d, i) => (
              <motion.path key={d} d={d} stroke={i === 1 ? "#7257ff" : "#bfe3ff"} strokeWidth={i === 1 ? 14 : 8} strokeLinecap="round" initial={{ pathLength: 0 }} whileInView={{ pathLength: 1 }} viewport={{ once: true }} transition={{ duration: 1.2, delay: i * 0.15, ease }} />
            ))}
            <circle cx="30" cy="60" r="12" fill="#17152b" />
            {[22, 60, 98].map((y) => <circle key={y} cx="232" cy={y} r="8" fill="white" stroke="#7257ff" strokeWidth="3" />)}
          </svg>
        </Reveal>
        <Reveal delay={0.16} className={tile}>
          <h3 className="text-xl font-semibold tracking-tight">Alerts without the noise</h3>
          <p className="mt-1.5 text-sm text-[#5c5977]">Only pings when a metric leaves its usual band.</p>
          <ul className="mt-5 space-y-2 text-sm">
            {[["Checkout conversion", "back to normal", "#12804a"], ["Trial starts", "34% above forecast", "#5a3ff0"]].map(([m, s, c]) => (
              <li key={m} className="flex items-center gap-3 rounded-2xl border border-[#efecfa] px-3.5 py-2.5 transition-transform group-hover:translate-x-1">
                <Bell className="size-4 shrink-0" style={{ color: c }} />
                <span className="min-w-0"><b className="block truncate font-medium">{m}</b><span className="text-xs text-[#6b6885]">{s}</span></span>
              </li>
            ))}
          </ul>
        </Reveal>
      </div>
    </section>
  );
}

/* ── Numbers + quote ───────────────────────────────────────────────────── */

function CountUp({ to, decimals = 0, suffix = "" }: { to: number; decimals?: number; suffix?: string }) {
  const ref = useRef<HTMLSpanElement>(null);
  const inView = useInView(ref, { once: true, amount: 0.6 });
  const reduce = useReducedMotion();
  useEffect(() => {
    const node = ref.current;
    if (!inView || !node) return;
    const controls = animate(0, to, { duration: reduce ? 0 : 1.6, ease, onUpdate: (v) => (node.textContent = v.toFixed(decimals) + suffix) });
    return () => controls.stop();
  }, [inView, to, decimals, suffix, reduce]);
  return <span ref={ref} className="tabular-nums">{(0).toFixed(decimals) + suffix}</span>;
}

const stats = [
  { to: 2.4, decimals: 1, suffix: "B", label: "events processed daily" },
  { to: 140, suffix: "ms", label: "median query time" },
  { to: 5, suffix: " min", label: "from snippet to first chart" },
  { to: 99.99, decimals: 2, suffix: "%", label: "uptime over 12 months" },
];

function Proof() {
  return (
    <section className="border-y border-[#e7e3f6] bg-white">
      <div className="mx-auto max-w-6xl px-5 py-20 sm:px-6 lg:py-24">
        <dl className="grid grid-cols-2 gap-x-6 gap-y-10 lg:grid-cols-4">
          {stats.map((s) => (
            <div key={s.label}>
              <dd className={cn(gradText, "text-4xl font-semibold tracking-[-0.04em] sm:text-5xl")}><CountUp to={s.to} decimals={s.decimals} suffix={s.suffix} /></dd>
              <dt className="mt-2 text-sm text-[#5c5977]">{s.label}</dt>
            </div>
          ))}
        </dl>
        <Reveal className="mt-16 grid gap-8 rounded-3xl bg-[#f6f4ff] p-8 sm:p-12 lg:grid-cols-[1fr_auto] lg:items-end">
          <blockquote className="max-w-3xl font-serif text-2xl leading-snug tracking-[-0.01em] sm:text-4xl">
            “We cancelled the Monday metrics meeting. Everyone had already read the answer in Nimbus on <em>Friday</em>.”
          </blockquote>
          <p className="flex items-center gap-3 text-sm">
            <span className="grid size-11 place-items-center rounded-full bg-linear-to-br from-[#7257ff] to-[#4fb6ff] font-semibold text-white">DK</span>
            <span><b className="block font-semibold">Dana Kowalczyk</b><span className="text-[#5c5977]">Head of Product, Brightside</span></span>
          </p>
        </Reveal>
      </div>
    </section>
  );
}

/* ── Pricing teaser ────────────────────────────────────────────────────── */

const plans = [
  { name: "Starter", monthly: 0, annual: 0, note: "1M events / month", features: ["Funnels & retention", "3 seats", "30-day history"] },
  { name: "Growth", monthly: 149, annual: 119, note: "20M events / month", features: ["Everything in Starter", "Ask Nimbus AI", "Unlimited seats", "Anomaly alerts"] },
  { name: "Scale", monthly: null, annual: null, note: "Volume pricing", features: ["Warehouse sync", "SSO & audit log", "Dedicated region"] },
];

function Pricing() {
  const [annual, setAnnual] = useState(true);
  return (
    <section className="mx-auto max-w-6xl px-5 py-24 sm:px-6 lg:py-32">
      <Reveal className="flex flex-col items-center text-center">
        <h2 className="text-4xl leading-[1.05] font-semibold tracking-[-0.04em] sm:text-5xl">Free until it’s <span className={cn(gradText, "font-serif font-normal italic")}>obviously</span> worth it</h2>
        <button type="button" role="switch" aria-checked={annual} onClick={() => setAnnual(!annual)} className="mt-7 flex items-center gap-3 text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#7257ff]">
          <span className={cn(!annual ? "text-[#17152b]" : "text-[#6b6885]")}>Monthly</span>
          <span className={cn("flex h-7 w-12 rounded-full p-1 transition-colors", annual ? "justify-end bg-[#7257ff]" : "justify-start bg-[#d8d3ee]")}>
            <motion.span layout transition={{ type: "spring", stiffness: 500, damping: 32 }} className="size-5 rounded-full bg-white shadow" />
          </span>
          <span className={cn(annual ? "text-[#17152b]" : "text-[#6b6885]")}>Annual <span className="ml-1 rounded-full bg-[#e8f8ef] px-2 py-0.5 text-xs text-[#12804a]">−20%</span></span>
        </button>
      </Reveal>
      <div className="mt-12 grid gap-4 lg:grid-cols-3">
        {plans.map((p, i) => {
          const price = annual ? p.annual : p.monthly;
          const hot = i === 1;
          return (
            <Reveal key={p.name} delay={i * 0.08} className={cn("rounded-3xl p-px", hot ? "bg-linear-to-br from-[#7257ff] to-[#4fb6ff] shadow-[0_30px_70px_-30px_rgba(114,87,255,0.6)]" : "bg-[#e7e3f6]")}>
              <div className="flex h-full flex-col rounded-[23px] bg-white p-7">
                <p className="flex items-center justify-between font-semibold">{p.name}{hot && <span className="rounded-full bg-[#efeaff] px-2.5 py-0.5 text-xs font-medium text-[#5a3ff0]">Most popular</span>}</p>
                <p className="mt-5 text-5xl font-semibold tracking-[-0.04em] tabular-nums">
                  {price === null ? "Custom" : `$${price}`}{price !== null && <span className="ml-1 text-sm font-normal tracking-normal text-[#6b6885]">/ month</span>}
                </p>
                <p className="mt-1 text-sm text-[#6b6885]">{p.note}</p>
                <ul className="mt-6 flex-1 space-y-2.5 text-sm">
                  {p.features.map((f) => <li key={f} className="flex items-center gap-2.5"><Check className="size-4 text-[#7257ff]" />{f}</li>)}
                </ul>
                <a href="#" className={cn("mt-8 inline-flex h-11 items-center justify-center rounded-full text-sm font-medium transition-colors", hot ? "bg-[#17152b] text-white hover:bg-[#5a3ff0]" : "border border-[#e1dcf5] hover:border-[#7257ff]")}>
                  {price === null ? "Talk to sales" : price === 0 ? "Start free" : "Try Growth for 14 days"}
                </a>
              </div>
            </Reveal>
          );
        })}
      </div>
      <p className="mt-6 text-center text-sm text-[#5c5977]"><a href="#" className="font-medium text-[#5a3ff0] hover:underline">Compare every feature →</a></p>
    </section>
  );
}

export default function SiteNimbusAnalytics() {
  return (
    <MotionConfig reducedMotion="user">
      <div className="w-full overflow-x-clip bg-[#faf9ff] font-sans text-[#17152b] antialiased selection:bg-[#7257ff] selection:text-white">
        <style>{`@keyframes pp-site-nimbus-blink { 50% { opacity: 0; } }`}</style>
        <header className="sticky top-3 z-40 px-4">
          <nav aria-label="Main" className="mx-auto flex h-14 max-w-5xl items-center justify-between rounded-full border border-[#e7e3f6] bg-white/80 pr-2 pl-5 shadow-[0_8px_30px_-12px_rgba(114,87,255,0.25)] backdrop-blur-md">
            <Logo />
            <div className="hidden gap-7 text-sm text-[#4b4866] md:flex">
              {["Product", "Customers", "Pricing", "Docs", "Changelog"].map((l) => <a key={l} href="#" className="transition-colors hover:text-[#5a3ff0]">{l}</a>)}
            </div>
            <div className="flex items-center gap-1 text-sm font-medium">
              <a href="#" className="hidden px-3 sm:block">Sign in</a>
              <a href="#" className="inline-flex h-10 items-center rounded-full bg-[#17152b] px-4 text-white transition-colors hover:bg-[#5a3ff0]">Start free</a>
            </div>
          </nav>
        </header>
        <main>
          <Hero />
          <Bento />
          <Proof />
          <Pricing />
          <section className="px-4 pb-4">
            <Reveal className="relative mx-auto max-w-6xl overflow-hidden rounded-[32px] bg-linear-to-br from-[#6a4dff] via-[#7c6bff] to-[#4fb6ff] px-6 py-20 text-center text-white sm:py-24">
              <div aria-hidden className="absolute -bottom-24 left-1/2 h-48 w-[120%] -translate-x-1/2 rounded-[50%] bg-white/20 blur-2xl" />
              <h2 className="relative mx-auto max-w-2xl text-4xl leading-[1.03] font-semibold tracking-[-0.04em] sm:text-6xl">Your first answer is <span className="font-serif font-normal italic">five minutes</span> away</h2>
              <p className="relative mx-auto mt-5 max-w-md text-white/85">Paste one snippet, watch events land, ask a question. No card, no sales call.</p>
              <a href="#" className="relative mt-8 inline-flex h-12 items-center gap-2 rounded-full bg-white px-7 text-sm font-semibold text-[#17152b] transition-transform hover:-translate-y-0.5">Start free <ArrowRight className="size-4" /></a>
            </Reveal>
          </section>
        </main>
        <footer className="mx-auto flex max-w-6xl flex-col gap-6 px-5 py-10 text-sm sm:px-6 md:flex-row md:items-center md:justify-between">
          <Logo />
          <ul className="flex flex-wrap gap-x-7 gap-y-2 text-[#5c5977]">
            {["Product", "Pricing", "Docs", "Security", "Status", "Careers"].map((l) => <li key={l}><a href="#" className="hover:text-[#5a3ff0]">{l}</a></li>)}
          </ul>
          <p className="text-[#6b6885]">© 2026 Nimbus Analytics, Inc.</p>
        </footer>
      </div>
    </MotionConfig>
  );
}
```
