{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "serif-tiers",
  "title": "Serif Tiers",
  "description": "Cream serif cards with a black featured card lit by a crimson glow.",
  "files": [
    {
      "path": "components/spectrumui/blocks/pricing/serif-tiers.tsx",
      "content": "'use client';\n\nimport { useState } from 'react';\nimport { cn } from '@/lib/utils';\nimport { PriceFigure } from './price-figure';\nimport { REVEAL_KEYFRAMES, revealClass, revealDelay, useReveal } from './reveal';\nimport type { BillingPeriod, Plan } from './types';\n\nexport type SerifTiersVariant = 'Ember' | 'Ink';\n\nexport interface SerifTiersProps {\n  plans: Plan[];\n  eyebrow?: string;\n  heading: string;\n  subheading?: string;\n  variant?: SerifTiersVariant;\n  className?: string;\n}\n\nfunction Star({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden className={cn('size-3.5 shrink-0', className)}>\n      <path\n        d=\"M8 0c.6 4.4 3 6.8 8 8-5 1.2-7.4 3.6-8 8-.6-4.4-3-6.8-8-8 5-1.2 7.4-3.6 8-8Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport function SerifTiers({\n  plans,\n  eyebrow = 'Pricing',\n  heading,\n  subheading,\n  variant = 'Ember',\n  className,\n}: SerifTiersProps) {\n  const [period, setPeriod] = useState<BillingPeriod>('monthly');\n  const { ref, shown } = useReveal<HTMLElement>();\n\n  return (\n    <section\n      ref={ref}\n      className={cn(\n        '@container w-full rounded-[28px] bg-[#fbfaf7] px-5 py-12 dark:bg-[#161316] sm:px-8',\n        className,\n      )}\n    >\n      <style dangerouslySetInnerHTML={{ __html: REVEAL_KEYFRAMES }} />\n\n      <div className={cn('flex flex-col items-center text-center', revealClass(shown))}>\n        <span className=\"rounded-full bg-white px-4 py-2 text-[12px] font-medium uppercase tracking-[0.1em] text-[#57534e] shadow-[0_1px_2px_rgba(0,0,0,0.06),0_0_0_1px_rgba(0,0,0,0.03)] dark:bg-[#211d21] dark:text-[#b6afa8] dark:shadow-[0_1px_2px_rgba(0,0,0,0.3),0_0_0_1px_rgba(255,255,255,0.06)]\">\n          {eyebrow}\n        </span>\n        <h2 className=\"mt-6 max-w-[20ch] text-balance [font-family:var(--font-spectral,Georgia,serif)] text-[28px] font-normal leading-[1.12] tracking-[-0.5px] text-[#1c1917] dark:text-[#f5f2ee] @sm:text-[34px] @lg:text-[46px]\">\n          {heading}\n        </h2>\n        {subheading && (\n          <p className=\"mt-4 max-w-[44ch] text-pretty text-[16px] leading-[1.55] text-[#57534e] dark:text-[#a8a29e]\">\n            {subheading}\n          </p>\n        )}\n\n        <div className=\"mt-8 inline-flex rounded-full border border-black/[0.06] bg-white p-1 shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:border-white/[0.07] dark:bg-[#211d21]\">\n          {(['monthly', 'annual'] as const).map((value) => (\n            <button\n              key={value}\n              type=\"button\"\n              aria-pressed={period === value}\n              onClick={() => setPeriod(value)}\n              className={cn(\n                'rounded-full px-6 py-2 text-[14.5px] font-medium transition-colors duration-200',\n                'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[#e11d48]',\n                period === value\n                  ? 'bg-[#f5f2ec] text-[#1c1917] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.04)] dark:bg-[#2e2930] dark:text-[#f5f2ee] dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]'\n                  : 'text-[#78716c] hover:text-[#1c1917] dark:text-[#8f8a85] dark:hover:text-[#f5f2ee]',\n              )}\n            >\n              {value === 'monthly' ? 'Monthly' : 'Yearly'}\n            </button>\n          ))}\n        </div>\n      </div>\n\n      <div className=\"mt-10 grid grid-cols-1 gap-5 @3xl:grid-cols-3\">\n        {plans.map((plan, position) => {\n          const featured = plan.featured;\n          const amount = plan.price ? plan.price[period] : 0;\n\n          return (\n            <div\n              key={plan.id}\n              style={revealDelay(shown, position + 1)}\n              className={cn(\n                'relative flex flex-col overflow-hidden rounded-[24px] p-8',\n                revealClass(shown),\n                featured\n                  ? 'bg-[#0c0a09] text-white shadow-[0_28px_60px_-28px_rgba(0,0,0,0.5)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.09),0_28px_60px_-28px_rgba(0,0,0,0.8)]'\n                  : 'bg-[#f5f2ec] dark:bg-[#211d22] dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.05)]',\n              )}\n            >\n              {featured && variant === 'Ember' && (\n                <>\n                  <span\n                    aria-hidden\n                    className=\"pointer-events-none absolute -bottom-40 -right-32 size-[300px] rounded-full bg-[#e11d48] opacity-70 blur-[90px]\"\n                  />\n                  <span\n                    aria-hidden\n                    className=\"pointer-events-none absolute -bottom-24 -right-16 size-[140px] rounded-full bg-[#fb7185] opacity-60 blur-[70px]\"\n                  />\n                </>\n              )}\n\n              <div className=\"relative flex flex-1 flex-col\">\n                <div className=\"flex items-center gap-3\">\n                  <h3\n                    className={cn(\n                      '[font-family:var(--font-spectral,Georgia,serif)] text-[24px] font-normal leading-none tracking-[-0.3px] @sm:text-[30px]',\n                      featured ? 'text-white' : 'text-[#1c1917] dark:text-[#f5f2ee]',\n                    )}\n                  >\n                    {plan.name}\n                  </h3>\n                  {plan.badge && (\n                    <span className=\"rounded-full bg-gradient-to-r from-[#fb7185] to-[#e11d48] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-white\">\n                      {plan.badge}\n                    </span>\n                  )}\n                </div>\n\n                <p\n                  className={cn(\n                    'mt-3 min-h-[24px] text-pretty text-[15px] leading-[1.5]',\n                    featured ? 'text-[#d6d3d1]' : 'text-[#57534e] dark:text-[#a8a29e]',\n                  )}\n                >\n                  {plan.tagline}\n                </p>\n\n                <div className=\"mt-10 flex items-end gap-2\">\n                  <PriceFigure\n                    value={amount}\n                    className={cn(\n                      '[font-family:var(--font-spectral,Georgia,serif)] text-[34px] font-normal tracking-[-1px] @sm:text-[40px] @lg:text-[46px]',\n                      featured ? 'text-white' : 'text-[#1c1917] dark:text-[#f5f2ee]',\n                    )}\n                  />\n                  <span\n                    className={cn(\n                      'pb-1.5 text-[14px]',\n                      featured ? 'text-[#a8a29e]' : 'text-[#78716c] dark:text-[#8f8a85]',\n                    )}\n                  >\n                    /month, per user\n                  </span>\n                </div>\n\n                <button\n                  type=\"button\"\n                  className={cn(\n                    'mt-7 h-[52px] w-full rounded-full bg-white text-[15px] font-medium text-[#1c1917]',\n                    'shadow-[0_1px_2px_rgba(0,0,0,0.08),0_0_0_1px_rgba(0,0,0,0.03)]',\n                    'transition-[transform,background-color] duration-150 hover:bg-[#faf9f6] active:scale-[0.98]',\n                    'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[#e11d48] focus-visible:ring-offset-2',\n                    featured && 'focus-visible:ring-offset-[#0c0a09]',\n                  )}\n                >\n                  {plan.cta}\n                </button>\n\n                <p\n                  className={cn(\n                    'mt-8 text-[14px]',\n                    featured ? 'text-[#a8a29e]' : 'text-[#78716c] dark:text-[#8f8a85]',\n                  )}\n                >\n                  What&rsquo;s included\n                </p>\n                <ul className=\"mt-4 space-y-3.5\">\n                  {plan.features.map((feature) => (\n                    <li key={feature} className=\"flex items-center gap-3\">\n                      <Star\n                        className={featured ? 'text-white' : 'text-[#1c1917] dark:text-[#f5f2ee]'}\n                      />\n                      <span\n                        className={cn(\n                          'text-[15px] leading-[1.45]',\n                          featured ? 'text-[#e7e5e4]' : 'text-[#292524] dark:text-[#d6d0c9]',\n                        )}\n                      >\n                        {feature}\n                      </span>\n                    </li>\n                  ))}\n                </ul>\n              </div>\n            </div>\n          );\n        })}\n      </div>\n    </section>\n  );\n}\n\nexport default SerifTiers;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/pricing/serif-tiers.tsx"
    },
    {
      "path": "components/spectrumui/blocks/pricing/price-figure.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\n\nconst DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];\n\nexport interface PriceFigureProps {\n  value: number;\n  currency?: string;\n  decimals?: number;\n  className?: string;\n}\n\nfunction Digit({ digit }: { digit: number }) {\n  return (\n    <span className=\"relative block h-[1em] w-[1ch] overflow-hidden\">\n      <span\n        className=\"absolute inset-x-0 top-0 flex flex-col transition-transform duration-[420ms] ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\"\n        style={{ transform: `translateY(-${digit * 10}%)` }}\n      >\n        {DIGITS.map((d) => (\n          <span key={d} className=\"flex h-[1em] items-center justify-center leading-none\">\n            {d}\n          </span>\n        ))}\n      </span>\n    </span>\n  );\n}\n\nexport function PriceFigure({ value, currency = '$', decimals = 0, className }: PriceFigureProps) {\n  const negative = value < 0;\n  const body = Math.abs(value).toFixed(decimals);\n  const grouped = body.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n  const label = `${negative ? '−' : ''}${currency}${grouped}`;\n\n  return (\n    <span className={cn('inline-flex leading-none tabular-nums', className)}>\n      <span className=\"sr-only\">{label}</span>\n      <span aria-hidden className=\"inline-flex\">\n        {negative && <span className=\"flex h-[1em] items-center leading-none\">−</span>}\n        {currency && <span className=\"flex h-[1em] items-center leading-none\">{currency}</span>}\n        {grouped.split('').map((character, index) =>\n          /\\d/.test(character) ? (\n            <Digit key={`${index}-digit`} digit={Number(character)} />\n          ) : (\n            <span key={`${index}-${character}`} className=\"flex h-[1em] items-center leading-none\">\n              {character}\n            </span>\n          ),\n        )}\n      </span>\n    </span>\n  );\n}\n\nexport default PriceFigure;\n",
      "type": "registry:file",
      "target": "components/spectrumui/blocks/pricing/price-figure.tsx"
    },
    {
      "path": "components/spectrumui/blocks/pricing/types.ts",
      "content": "export type BillingPeriod = 'monthly' | 'annual';\n\nexport interface PlanPrice {\n  monthly: number;\n  annual: number;\n}\n\nexport interface Plan {\n  id: string;\n  name: string;\n  tagline: string;\n  price: PlanPrice | null;\n  unit?: string;\n  cta: string;\n  featured?: boolean;\n  badge?: string;\n  inherits?: string;\n  features: string[];\n  footnote?: string;\n}\n\nexport type FeatureValue = string | boolean;\n\nexport interface FeatureRow {\n  label: string;\n  hint?: string;\n  values: FeatureValue[];\n}\n\nexport interface FeatureGroup {\n  label: string;\n  rows: FeatureRow[];\n}\n\nexport interface RateTier {\n  from: number;\n  to?: number;\n  unitPrice: number;\n}\n\nexport interface MeteredProduct {\n  id: string;\n  name: string;\n  description?: string;\n  unit: string;\n  unitPlural: string;\n  included: number;\n  tiers: RateTier[];\n}\n\nexport interface CreditPack {\n  id: string;\n  name: string;\n  credits: number;\n  bonus: number;\n  price: number;\n  featured?: boolean;\n  note?: string;\n}\n\nexport interface CreditRate {\n  id: string;\n  label: string;\n  credits: number;\n  per: number;\n  unitLabel: string;\n}\n\nexport interface AddOn {\n  id: string;\n  name: string;\n  description: string;\n  price: number;\n  unit?: string;\n  perSeat?: boolean;\n  defaultOn?: boolean;\n}\n\nexport interface ModelRate {\n  id: string;\n  name: string;\n  context: string;\n  input: number;\n  output: number;\n  cacheWrite: number;\n  cacheRead: number;\n  badge?: string;\n}\n\nexport interface UsageDimension {\n  id: string;\n  label: string;\n  unit: string;\n  min: number;\n  max: number;\n  step: number;\n  defaultValue: number;\n}\n\nexport interface ComplianceItem {\n  id: string;\n  label: string;\n  detail: string;\n}\n\nexport interface RecommenderOption {\n  id: string;\n  label: string;\n  scores: Record<string, number>;\n  reason?: string;\n}\n\nexport interface RecommenderQuestion {\n  id: string;\n  label: string;\n  hint?: string;\n  options: RecommenderOption[];\n}\n\nexport interface PlanFamily {\n  id: string;\n  label: string;\n  description: string;\n  plans: Plan[];\n  footnote?: string;\n}\n",
      "type": "registry:file",
      "target": "components/spectrumui/blocks/pricing/types.ts"
    },
    {
      "path": "components/spectrumui/blocks/pricing/reveal.ts",
      "content": "'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport const REVEAL_KEYFRAMES = `\n@keyframes su-reveal { from { opacity: 0; transform: translateY(14px) } to { opacity: 1; transform: none } }\n@keyframes su-draw { from { stroke-dashoffset: 1 } to { stroke-dashoffset: 0 } }\n@keyframes su-sheen { 0%, 62% { transform: translateX(-110%) } 100% { transform: translateX(110%) } }\n`;\n\nexport function useReveal<T extends HTMLElement>() {\n  const ref = useRef<T>(null);\n  const [shown, setShown] = useState(false);\n\n  useEffect(() => {\n    const node = ref.current;\n    if (!node) return;\n    if (typeof IntersectionObserver === 'undefined') {\n      const frame = requestAnimationFrame(() => setShown(true));\n      return () => cancelAnimationFrame(frame);\n    }\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (entries.some((entry) => entry.isIntersecting)) {\n          setShown(true);\n          observer.disconnect();\n        }\n      },\n      { threshold: 0.18, rootMargin: '0px 0px -8% 0px' },\n    );\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, []);\n\n  return { ref, shown };\n}\n\nexport function revealClass(shown: boolean) {\n  return shown\n    ? '[animation:su-reveal_480ms_cubic-bezier(0.23,1,0.32,1)_both] motion-reduce:animate-none'\n    : '';\n}\n\nexport function revealDelay(shown: boolean, index: number) {\n  return shown ? { animationDelay: `${index * 70}ms` } : undefined;\n}\n",
      "type": "registry:file",
      "target": "components/spectrumui/blocks/pricing/reveal.ts"
    }
  ],
  "type": "registry:block"
}
