Team seat slider with animated total — $147 base plus per-seat pricing with tabular-nums price handoff.
@nusaiba
registry/blocks/pricing/7/pricing-section.tsx
"use client";
import { useMemo, useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { Button } from "@/components/ui/button";
import { defaultSpring, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";
const BASE_PRICE = 147;
const SEAT_PRICE = 39;
const MIN_SEATS = 1;
const MAX_SEATS = 20;
function formatPrice(amount: number) {
return `$${amount.toLocaleString("en-US")}`;
}
export function PricingSeatsSection() {
const reduceMotion = useReducedMotion();
const [seats, setSeats] = useState(3);
const total = useMemo(
() => BASE_PRICE + Math.max(0, seats - 1) * SEAT_PRICE,
[seats],
);
return (
<SectionShell spacingMode="section" className="bg-background">
<ContentRail maxWidth="max-w-2xl" className="space-y-8 md:space-y-10">
<div className="max-w-xl space-y-2">
<p className="font-medium text-primary text-xs uppercase tracking-[0.22em]">
Team
</p>
<h2 className="text-balance font-medium text-2xl tracking-tight md:text-4xl">
Scale by seats
</h2>
<p className="text-pretty text-muted-foreground text-sm md:text-base">
Pro starts at $147 once — add seats at $39 each for shared registry access
across your team.
</p>
</div>
<motion.div
className="space-y-8 rounded-[var(--radius-md)] border border-border bg-background p-6 shadow-[0_1px_2px_rgba(0,0,0,0.04)] md:p-8 dark:shadow-[0_1px_2px_rgba(0,0,0,0.2)]"
initial={reduceMotion ? false : { opacity: 0, y: 12, filter: "blur(4px)" }}
transition={defaultSpring}
viewport={motionViewport}
whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
>
<div className="flex flex-wrap items-end justify-between gap-4">
<div>
<p className="font-mono text-[10px] text-muted-foreground uppercase tracking-[0.14em]">
Total
</p>
<AnimatePresence initial={false} mode="popLayout">
<motion.p
animate={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
className="font-medium text-4xl tabular-nums tracking-tight md:text-5xl"
exit={
reduceMotion
? undefined
: {
opacity: 0,
y: -6,
filter: "blur(4px)",
transition: { duration: 0.15, ease: "easeIn" },
}
}
initial={reduceMotion ? false : { opacity: 0, y: 8, filter: "blur(4px)" }}
key={total}
transition={{ type: "spring", duration: 0.3, bounce: 0 }}
>
{formatPrice(total)}
</motion.p>
</AnimatePresence>
<p className="mt-1 text-muted-foreground text-sm">once · includes Pro catalog</p>
</div>
<div className="text-right">
<p className="font-medium text-2xl tabular-nums">{seats}</p>
<p className="text-muted-foreground text-xs">
seat{seats === 1 ? "" : "s"}
</p>
</div>
</div>
<div className="space-y-3">
<label className="font-medium text-sm" htmlFor="pricing-seats">
Team seats
</label>
<input
aria-valuemax={MAX_SEATS}
aria-valuemin={MIN_SEATS}
aria-valuenow={seats}
className={cn(
"h-2 w-full cursor-pointer appearance-none rounded-full bg-muted",
"[&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:shadow-[0_1px_2px_rgba(0,0,0,0.2)]",
"[&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-primary",
)}
id="pricing-seats"
max={MAX_SEATS}
min={MIN_SEATS}
onChange={(event) => setSeats(Number(event.target.value))}
type="range"
value={seats}
/>
<div className="flex justify-between font-mono text-[10px] text-muted-foreground uppercase tracking-[0.12em]">
<span>{MIN_SEATS} seat</span>
<span>{MAX_SEATS} seats</span>
</div>
</div>
<ul className="space-y-2 text-pretty text-muted-foreground text-sm">
<li>
<span className="text-foreground">{formatPrice(BASE_PRICE)}</span> base Pro
license
</li>
<li>
<span className="text-foreground">+{formatPrice(SEAT_PRICE)}</span> per
additional seat
</li>
</ul>
<Button className="w-full active:scale-[0.96] sm:w-auto">
Get team access
</Button>
</motion.div>
</ContentRail>
</SectionShell>
);
}



























