Workspace / block / pricing
Minimal Pricing
paxfx add blocks/block-pricing-minimal-05local onlyTier: low. Reduced motion: Fully static.
"use client";
import { cn, useInView, useReducedMotion, type BlockCopy } from "@pax-sh/blocks";
export interface PricingMinimalProps {
copy?: BlockCopy;
price?: string;
cadence?: string;
includes?: string[];
className?: string;
}
const DEFAULT_COPY: BlockCopy = {
eyebrow: "Pricing",
title: "One plan. Everything in it.",
description: "No tiers to compare, no feature matrix to squint at. This is the whole offer.",
primaryCta: { label: "Start today", href: "/docs/installation" },
};
const DEFAULT_INCLUDES = [
"Every block, component, and template",
"Unlimited projects, commercial included",
"MCP registry for coding agents",
"All future releases while subscribed",
"Support answered by the people who built it",
];
/**
* A single-answer pricing section: one price stated very large, one measured
* list of what it includes, one CTA. The entrance is a quiet settle, skipped
* entirely under reduced motion.
*/
export function PricingMinimal({
copy = DEFAULT_COPY,
price = "$29",
cadence = "per editor, per month",
includes = DEFAULT_INCLUDES,
className,
}: PricingMinimalProps) {
const reduced = useReducedMotion();
const [ref, inView] = useInView<HTMLDivElement>({ threshold: 0.25 });
const settled = reduced || inView;
const enter = (step: number) =>
reduced
? undefined
: ({
opacity: settled ? 1 : 0,
transform: settled ? "translateY(0)" : "translateY(12px)",
transition: `opacity 0.55s ease ${step * 0.09}s, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1) ${step * 0.09}s`,
} as const);
return (
<section
className={cn(
"bg-[var(--pax-paper,#f6f2ea)] px-6 py-20 text-[var(--pax-ink,#12110f)] md:px-10 md:py-28",
className,
)}
data-pax-asset="block-pricing-minimal-05"
>
<div ref={ref} className="mx-auto flex max-w-6xl flex-col items-center text-center">
{copy.eyebrow ? (
<p
className="m-0 text-xs font-medium uppercase tracking-[0.22em] text-[var(--pax-mute,#6b645c)]"
style={enter(0)}
>
{copy.eyebrow}
</p>
) : null}
<h2
className="mt-3 text-3xl font-semibold tracking-tight [text-wrap:balance] md:text-4xl"
style={enter(1)}
>
{copy.title}
</h2>
{copy.description ? (
<p
className="mt-4 max-w-md text-base leading-relaxed text-[var(--pax-mute,#6b645c)]"
style={enter(2)}
>
{copy.description}
</p>
) : null}
<p className="mt-12 flex items-baseline gap-3" style={enter(3)}>
<span className="text-7xl font-semibold tracking-tight [font-variant-numeric:tabular-nums] md:text-8xl">
{price}
</span>
<span className="text-sm text-[var(--pax-mute,#6b645c)]">{cadence}</span>
</p>
<ul className="m-0 mt-10 list-none space-y-3 p-0 text-left" style={enter(4)}>
{includes.map((item) => (
<li key={item} className="flex items-start gap-3 text-sm md:text-base">
<span className="sr-only">Included:</span>
<svg
viewBox="0 0 16 16"
width="16"
height="16"
aria-hidden="true"
className="mt-1 shrink-0"
>
<path
d="m3 8.5 3.2 3L13 4.5"
fill="none"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
{item}
</li>
))}
</ul>
{copy.primaryCta ? (
<a
href={copy.primaryCta.href}
className="mt-12 rounded-full bg-[var(--pax-ink,#12110f)] px-8 py-3.5 text-sm font-semibold text-[var(--pax-paper,#f6f2ea)] outline-offset-4 transition-colors hover:bg-[var(--pax-night,#0b0d10)] focus-visible:outline focus-visible:outline-2"
style={enter(5)}
{...(copy.primaryCta.goal ? { "data-fast-goal": copy.primaryCta.goal } : {})}
>
{copy.primaryCta.label}
</a>
) : null}
</div>
</section>
);
}