{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-02",
  "title": "Pricing 02",
  "description": "A compact pricing table with separated plan summaries, detailed feature lists, and an animated billing-cycle switch.",
  "type": "registry:block",
  "categories": [
    "pricing"
  ],
  "files": [
    {
      "path": "registry/blocks/pricing-02/pricing-02.tsx",
      "target": "components/pricing-02.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport {\n  type CSSProperties,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { ArrowRight, Check } from \"lucide-react\";\n\nconst plans = [\n  {\n    name: \"Starter\",\n    monthlyPrice: 0,\n    yearlyPrice: 0,\n    description: \"For personal projects and ideas taking their first shape.\",\n    features: [\n      \"3 active projects\",\n      \"Unlimited collaborators\",\n      \"Core analytics\",\n      \"Community support\",\n      \"7-day version history\",\n      \"Standard integrations\",\n    ],\n    includesLabel: \"Starter includes\",\n    cta: \"Start for free\",\n  },\n  {\n    name: \"Studio\",\n    monthlyPrice: 32,\n    yearlyPrice: 307,\n    description: \"For small teams building and shipping every week.\",\n    features: [\n      \"Unlimited projects\",\n      \"Custom domains\",\n      \"Advanced analytics\",\n      \"Priority support\",\n      \"Unlimited version history\",\n      \"Team permissions\",\n    ],\n    includesLabel: \"Everything in Starter, plus\",\n    cta: \"Choose Studio\",\n    featured: true,\n  },\n  {\n    name: \"Scale\",\n    monthlyPrice: 96,\n    yearlyPrice: 922,\n    description: \"For growing organizations that need control and support.\",\n    features: [\n      \"Everything in Studio\",\n      \"Single sign-on\",\n      \"Audit logs\",\n      \"Dedicated onboarding\",\n      \"Custom data retention\",\n      \"Enterprise integrations\",\n    ],\n    includesLabel: \"Everything in Studio, plus\",\n    cta: \"Choose Scale\",\n  },\n];\n\ntype BillingCycle = \"monthly\" | \"yearly\";\n\nfunction positionPill(\n  pill: HTMLSpanElement,\n  tab: HTMLButtonElement,\n  animate: boolean,\n) {\n  if (!animate) {\n    const previousTransition = pill.style.transition;\n    pill.style.transition = \"none\";\n    pill.style.transform = `translateX(${tab.offsetLeft}px)`;\n    pill.style.width = `${tab.offsetWidth}px`;\n    void pill.offsetWidth;\n    pill.style.transition = previousTransition;\n    return;\n  }\n\n  pill.style.transform = `translateX(${tab.offsetLeft}px)`;\n  pill.style.width = `${tab.offsetWidth}px`;\n}\n\nexport function Pricing02() {\n  const [billingCycle, setBillingCycle] = useState<BillingCycle>(\"monthly\");\n  const pillRef = useRef<HTMLSpanElement>(null);\n  const monthlyTabRef = useRef<HTMLButtonElement>(null);\n  const yearlyTabRef = useRef<HTMLButtonElement>(null);\n  const priceRefs = useRef<Array<HTMLSpanElement | null>>([]);\n  const hasMounted = useRef(false);\n\n  useLayoutEffect(() => {\n    const pill = pillRef.current;\n    const activeTab = monthlyTabRef.current;\n    if (!pill || !activeTab) return;\n\n    positionPill(pill, activeTab, false);\n\n    const handleResize = () => {\n      const currentTab =\n        yearlyTabRef.current?.getAttribute(\"aria-selected\") === \"true\"\n          ? yearlyTabRef.current\n          : monthlyTabRef.current;\n      if (pill && currentTab) positionPill(pill, currentTab, false);\n    };\n\n    window.addEventListener(\"resize\", handleResize);\n    return () => window.removeEventListener(\"resize\", handleResize);\n  }, []);\n\n  useEffect(() => {\n    if (!hasMounted.current) {\n      hasMounted.current = true;\n      return;\n    }\n\n    const pill = pillRef.current;\n    const activeTab =\n      billingCycle === \"monthly\" ? monthlyTabRef.current : yearlyTabRef.current;\n    if (pill && activeTab) positionPill(pill, activeTab, true);\n\n    priceRefs.current.forEach((group) => {\n      if (!group) return;\n      group.classList.remove(\"is-animating\");\n      void group.offsetHeight;\n      group.classList.add(\"is-animating\");\n    });\n  }, [billingCycle]);\n\n  return (\n    <section className=\"pricing-02 relative min-h-screen overflow-hidden bg-white px-4 py-20 text-[#172033] transition-colors duration-300 dark:bg-[#101010] dark:text-white sm:px-6 lg:px-8 lg:py-28\">\n      <style>{`\n        .pricing-02 {\n          --digit-dur: 500ms;\n          --digit-distance: 8px;\n          --digit-stagger: 70ms;\n          --digit-blur: 2px;\n          --digit-ease: cubic-bezier(0.34, 1.45, 0.64, 1);\n          --digit-dir-x: 0;\n          --digit-dir-y: 1;\n          --tabs-dur: 250ms;\n          --tabs-ease: cubic-bezier(0.22, 1, 0.36, 1);\n          --tabs-text-muted: rgba(71, 85, 105, 0.82);\n          --tabs-text-hover: #334155;\n          --tabs-text-active: #ffffff;\n          --tabs-bar-bg: #ffffff;\n          --tabs-pill-bg: #172033;\n        }\n\n        .dark .pricing-02 {\n          --tabs-text-muted: rgba(161, 161, 170, 0.76);\n          --tabs-text-hover: #f4f4f5;\n          --tabs-text-active: #18181b;\n          --tabs-bar-bg: #1a1a1a;\n          --tabs-pill-bg: #f4f4f5;\n        }\n\n        @keyframes t-digit-pop-in {\n          0% {\n            transform: translate(\n              calc(var(--digit-distance) * var(--digit-dir-x)),\n              calc(var(--digit-distance) * var(--digit-dir-y))\n            );\n            opacity: 0;\n            filter: blur(var(--digit-blur));\n          }\n          100% {\n            transform: translate(0, 0);\n            opacity: 1;\n            filter: blur(0);\n          }\n        }\n\n        .t-digit-group {\n          display: inline-flex;\n          align-items: baseline;\n        }\n        .t-digit {\n          display: inline-block;\n          will-change: transform, opacity, filter;\n        }\n        .t-digit-group.is-animating .t-digit {\n          animation: t-digit-pop-in var(--digit-dur) var(--digit-ease) both;\n        }\n        .t-digit-group.is-animating .t-digit[data-stagger=\"1\"] {\n          animation-delay: var(--digit-stagger);\n        }\n        .t-digit-group.is-animating .t-digit[data-stagger=\"2\"] {\n          animation-delay: calc(var(--digit-stagger) * 2);\n        }\n\n        .t-tabs {\n          position: relative;\n          display: inline-flex;\n          align-items: center;\n          gap: 3px;\n          padding: 3px;\n          border-radius: 48px;\n          background: var(--tabs-bar-bg);\n        }\n        .t-tab {\n          position: relative;\n          appearance: none;\n          border: 0;\n          background: transparent;\n          height: 30px;\n          padding: 4px 12px;\n          color: var(--tabs-text-muted);\n          cursor: pointer;\n          border-radius: 48px;\n          z-index: 1;\n          transition: color var(--tabs-dur) var(--tabs-ease);\n        }\n        .t-tab:not([aria-selected=\"true\"]):hover {\n          color: var(--tabs-text-hover);\n        }\n        .t-tab[aria-selected=\"true\"] {\n          color: var(--tabs-text-active);\n        }\n\n        .t-tabs-pill {\n          position: absolute;\n          top: 3px;\n          left: 0;\n          height: 30px;\n          width: 0;\n          background: var(--tabs-pill-bg);\n          border-radius: 48px;\n          transform: translateX(0);\n          transition:\n            transform var(--tabs-dur) var(--tabs-ease),\n            width var(--tabs-dur) var(--tabs-ease);\n          will-change: transform, width;\n          z-index: 0;\n          pointer-events: none;\n        }\n\n        @media (prefers-reduced-motion: reduce) {\n          .t-digit-group .t-digit {\n            animation: none !important;\n          }\n          .t-tabs-pill,\n          .t-tab {\n            transition: none !important;\n          }\n        }\n      `}</style>\n\n      <div className=\"mx-auto max-w-6xl\">\n        <div className=\"grid gap-6 md:grid-cols-12 md:items-end\">\n          <div className=\"md:col-span-6\">\n            <p className=\"text-xs font-medium uppercase tracking-[0.18em] text-slate-400 dark:text-zinc-500\">\n              Simple pricing\n            </p>\n            <h2 className=\"mt-4 max-w-xl text-4xl font-medium leading-[1.02] tracking-[-0.045em] sm:text-5xl\">\n              Start small.\n              <br />\n              Keep room to grow.\n            </h2>\n          </div>\n          <div className=\"md:col-span-4 md:col-start-9\">\n            <p className=\"max-w-md text-sm leading-6 text-slate-500 dark:text-zinc-400\">\n              Straightforward monthly plans with every essential included.\n              Upgrade, downgrade, or cancel whenever you like.\n            </p>\n            <div\n              className=\"t-tabs mt-5 border border-slate-200 text-xs shadow-sm dark:border-zinc-700 dark:shadow-none\"\n              role=\"tablist\"\n              aria-label=\"Billing cycle\"\n            >\n              <span ref={pillRef} className=\"t-tabs-pill\" aria-hidden=\"true\" />\n              <button\n                ref={monthlyTabRef}\n                type=\"button\"\n                role=\"tab\"\n                aria-selected={billingCycle === \"monthly\"}\n                className={`t-tab font-medium ${\n                  billingCycle === \"monthly\"\n                    ? \"text-white dark:text-zinc-950\"\n                    : \"\"\n                }`}\n                onClick={() => setBillingCycle(\"monthly\")}\n              >\n                Monthly\n              </button>\n              <button\n                ref={yearlyTabRef}\n                type=\"button\"\n                role=\"tab\"\n                aria-selected={billingCycle === \"yearly\"}\n                className={`t-tab font-medium ${\n                  billingCycle === \"yearly\"\n                    ? \"text-white dark:text-zinc-950\"\n                    : \"\"\n                }`}\n                onClick={() => setBillingCycle(\"yearly\")}\n              >\n                Yearly · save 20%\n              </button>\n            </div>\n          </div>\n        </div>\n\n        <div className=\"relative mt-12 grid gap-3 lg:grid-cols-3\">\n          {plans.map((plan, planIndex) => (\n            <article\n              key={plan.name}\n              className=\"group relative isolate flex flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white/70 p-2 shadow-[0_18px_55px_-46px_rgba(15,23,42,0.35)] transition-[border-color,box-shadow] duration-500 ease-out hover:border-slate-300 hover:shadow-[0_24px_70px_-50px_rgba(15,23,42,0.52)] dark:border-white/[0.08] dark:bg-[#151515] dark:shadow-[0_18px_55px_-46px_rgba(0,0,0,0.95)] dark:hover:border-white/[0.15] dark:hover:shadow-[0_24px_70px_-48px_rgba(0,0,0,1)] lg:min-h-[640px]\"\n            >\n              <div\n                aria-hidden=\"true\"\n                className=\"pointer-events-none absolute inset-x-0 top-0 z-0 h-72 bg-[radial-gradient(ellipse_at_50%_-20%,rgba(148,163,184,0.16),transparent_66%)] opacity-0 transition-opacity duration-500 ease-out group-hover:opacity-100 dark:bg-[radial-gradient(ellipse_at_50%_-20%,rgba(255,255,255,0.09),transparent_64%)]\"\n              />\n\n              <div\n                className={`relative z-10 flex min-h-[248px] flex-col rounded-xl p-5 shadow-[0_0_0_1px_rgba(0,0,0,0.06),inset_0_8px_18px_-20px_rgba(15,23,42,0.45),inset_0_-8px_18px_-22px_rgba(15,23,42,0.4)] transition-[background-color,box-shadow] duration-500 ease-out group-hover:shadow-[0_0_0_1px_rgba(0,0,0,0.075),inset_0_8px_18px_-20px_rgba(15,23,42,0.45),inset_0_-8px_18px_-22px_rgba(15,23,42,0.4)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.085),inset_0_8px_18px_-20px_rgba(255,255,255,0.45),inset_0_-8px_18px_-22px_rgba(255,255,255,0.4)] dark:group-hover:shadow-[0_0_0_1px_rgba(255,255,255,0.105),inset_0_8px_18px_-20px_rgba(255,255,255,0.45),inset_0_-8px_18px_-22px_rgba(255,255,255,0.4)] ${\n                  plan.featured\n                    ? \"bg-[radial-gradient(circle_at_50%_0%,#eef2f7_0%,#ffffff_62%)] dark:bg-[radial-gradient(circle_at_50%_0%,#292929_0%,#1a1a1a_62%)]\"\n                    : \"bg-white dark:bg-[#1a1a1a]\"\n                }`}\n              >\n                <div>\n                  <p className=\"text-base font-medium\">{plan.name}</p>\n                  <p className=\"mt-2 min-h-12 max-w-xs text-sm leading-6 text-slate-500 dark:text-zinc-400\">\n                    {plan.description}\n                  </p>\n                </div>\n\n                <div className=\"mt-4 flex min-h-10 items-end gap-1.5\">\n                  <span\n                    ref={(element) => {\n                      priceRefs.current[planIndex] = element;\n                    }}\n                    className=\"t-digit-group text-4xl font-medium leading-none tracking-[-0.045em]\"\n                    style={\n                      {\n                        \"--digit-dir-y\": billingCycle === \"yearly\" ? 1 : -1,\n                      } as CSSProperties\n                    }\n                  >\n                    {`$${\n                      billingCycle === \"monthly\"\n                        ? plan.monthlyPrice\n                        : plan.yearlyPrice\n                    }`\n                      .split(\"\")\n                      .map((character, index, characters) => (\n                        <span\n                          key={`${billingCycle}-${index}-${character}`}\n                          className=\"t-digit\"\n                          data-stagger={\n                            index === characters.length - 2\n                              ? \"1\"\n                              : index === characters.length - 1\n                                ? \"2\"\n                                : undefined\n                          }\n                        >\n                          {character}\n                        </span>\n                      ))}\n                  </span>\n                  <span className=\"pb-1 text-sm text-slate-400 dark:text-zinc-500\">\n                    / {billingCycle === \"monthly\" ? \"month\" : \"year\"}\n                  </span>\n                </div>\n\n                <a\n                  href=\"#\"\n                  className={`mt-auto flex h-10 items-center justify-between rounded-lg px-4 text-sm font-medium transition-[background-color,color,transform] duration-300 active:scale-[0.99] ${\n                    plan.featured\n                      ? \"bg-[#172033] text-white hover:bg-slate-700 dark:bg-zinc-100 dark:text-zinc-950 dark:hover:bg-white\"\n                      : \"border border-slate-200 bg-white text-[#172033] hover:border-slate-300 hover:bg-slate-50 dark:border-white/[0.1] dark:bg-transparent dark:text-zinc-100 dark:hover:border-white/[0.16] dark:hover:bg-white/[0.04]\"\n                  }`}\n                >\n                  {plan.cta}\n                  <ArrowRight className=\"size-4 transition-transform duration-300 group-hover:translate-x-0.5\" />\n                </a>\n              </div>\n\n              <div className=\"relative z-10 flex flex-1 flex-col px-4 pb-5 pt-8 sm:px-5\">\n                <p className=\"text-[11px] font-medium uppercase tracking-[0.15em] text-slate-400 dark:text-zinc-500\">\n                  {plan.includesLabel}\n                </p>\n\n                <ul className=\"mt-5 space-y-4\">\n                  {plan.features.map((feature) => (\n                    <li\n                      key={feature}\n                      className=\"flex items-center gap-2.5 text-sm text-slate-600 dark:text-zinc-300\"\n                    >\n                      <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-300\">\n                        <Check className=\"size-3\" />\n                      </span>\n                      {feature}\n                    </li>\n                  ))}\n                </ul>\n              </div>\n            </article>\n          ))}\n        </div>\n\n        <div className=\"mt-5 flex flex-col gap-2 text-xs text-slate-400 dark:text-zinc-600 sm:flex-row sm:items-center sm:justify-between\">\n          <p>No credit card required for Starter.</p>\n          <p>Prices exclude applicable taxes.</p>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "meta": {
    "createdAt": "2026-07-29",
    "iframeHeight": 1100,
    "previewClassName": "min-h-[1100px]"
  }
}
