{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gradient-tiers",
  "title": "Gradient Tiers",
  "description": "Pastel gradient card headers with icons and color-coded inheritance labels.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/spectrumui/blocks/pricing/gradient-tiers.tsx",
      "content": "'use client';\n\nimport { ArrowRight, Building2, FileText, TrendingUp } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { REVEAL_KEYFRAMES, revealClass, revealDelay, useReveal } from './reveal';\nimport type { Plan } from './types';\n\nexport type GradientTiersVariant = 'Tinted' | 'Plain';\n\nexport interface GradientTierNote {\n  price?: string;\n  priceNote?: string;\n  footnote?: string;\n}\n\nexport interface GradientTiersProps {\n  plans: Plan[];\n  eyebrow?: string;\n  heading?: string;\n  subheading?: string;\n  notes?: GradientTierNote[];\n  variant?: GradientTiersVariant;\n  className?: string;\n}\n\nconst PALETTE = [\n  {\n    icon: FileText,\n    wash: 'from-[#e8efff] dark:from-[#1a2338]',\n    iconColor: 'text-[#4a7dfc] dark:text-[#8fadff]',\n    label: 'text-[#8a8580] dark:text-[#98938d]',\n  },\n  {\n    icon: TrendingUp,\n    wash: 'from-[#f3f7d4] dark:from-[#272b10]',\n    iconColor: 'text-[#7f931c] dark:text-[#bdd14b]',\n    label: 'text-[#7f931c] dark:text-[#bdd14b]',\n  },\n  {\n    icon: Building2,\n    wash: 'from-[#f5ecfe] dark:from-[#2a1c3c]',\n    iconColor: 'text-[#a34df0] dark:text-[#c894f7]',\n    label: 'text-[#4a7dfc] dark:text-[#8fadff]',\n  },\n];\n\nexport function GradientTiers({\n  plans,\n  eyebrow,\n  heading,\n  subheading,\n  notes = [],\n  variant = 'Tinted',\n  className,\n}: GradientTiersProps) {\n  const { ref, shown } = useReveal<HTMLElement>();\n\n  return (\n    <section ref={ref} className={cn('@container w-full', className)}>\n      <style dangerouslySetInnerHTML={{ __html: REVEAL_KEYFRAMES }} />\n\n      {heading && (\n        <div className={cn('mx-auto mb-10 max-w-[54ch] text-center', revealClass(shown))}>\n          {eyebrow && (\n            <span className=\"inline-flex items-center gap-2.5\">\n              <span aria-hidden className=\"flex gap-1\">\n                <span className=\"size-1.5 rounded-full bg-[#4a7dfc]\" />\n                <span className=\"size-1.5 rounded-full bg-[#7f931c]\" />\n                <span className=\"size-1.5 rounded-full bg-[#a34df0]\" />\n              </span>\n              <span className=\"text-[12px] font-semibold uppercase tracking-[0.12em] text-[#78716c] dark:text-[#98938d]\">\n                {eyebrow}\n              </span>\n            </span>\n          )}\n          <h2 className=\"mt-4 text-balance [font-family:var(--font-spectral,Georgia,serif)] text-[38px] font-normal leading-[1.08] tracking-[-0.5px] text-[#1c1b1a] dark:text-[#f5f5f4] @lg:text-[42px]\">\n            {heading}\n          </h2>\n          {subheading && (\n            <p className=\"mt-3 text-pretty text-[16px] leading-[1.55] text-[#57534e] dark:text-[#a8a29e]\">\n              {subheading}\n            </p>\n          )}\n        </div>\n      )}\n\n      <div className=\"grid w-full overflow-hidden border border-[#e2e0dd] bg-white @3xl:grid-cols-3 dark:border-[#2b2b30] dark:bg-[#0f0f11]\">\n        {plans.map((plan, position) => {\n          const palette = PALETTE[position % PALETTE.length];\n          const Icon = palette.icon;\n          const note = notes[position] ?? {};\n\n          return (\n            <div\n              key={plan.id}\n              style={revealDelay(shown, position)}\n              className={cn(\n                'relative flex flex-col border-t border-[#e2e0dd] px-8 pb-10 pt-9 first:border-t-0 @3xl:border-l @3xl:border-t-0 @3xl:first:border-l-0 dark:border-[#2b2b30]',\n                revealClass(shown),\n              )}\n            >\n              {variant === 'Tinted' && (\n                <span\n                  aria-hidden\n                  className={cn(\n                    'absolute inset-x-0 top-0 h-[46%] bg-gradient-to-b to-transparent',\n                    palette.wash,\n                  )}\n                />\n              )}\n\n              <div className=\"relative\">\n                <div className=\"flex items-start justify-between\">\n                  <Icon aria-hidden className={cn('size-6', palette.iconColor)} strokeWidth={1.8} />\n                  {plan.badge && (\n                    <span className=\"rounded-full bg-[#e7edc4] px-3.5 py-1.5 text-[14px] font-medium text-[#5b661c] dark:bg-[#2e3413] dark:text-[#cade5e]\">\n                      {plan.badge}\n                    </span>\n                  )}\n                </div>\n\n                <h3 className=\"mt-6 [font-family:var(--font-spectral,Georgia,serif)] text-[38px] font-normal leading-none tracking-[-0.5px] text-[#1c1b1a] dark:text-[#f5f5f4]\">\n                  {plan.name}\n                </h3>\n                <p className=\"mt-3 text-pretty text-[16.5px] leading-[1.5] text-[#57534e] dark:text-[#a8a29e]\">\n                  {plan.tagline}\n                </p>\n\n                <div className=\"mt-7 flex min-h-[42px] items-baseline justify-between gap-4\">\n                  <span className=\"whitespace-nowrap text-[27px] font-semibold tracking-[-0.5px] text-[#1c1b1a] dark:text-[#f5f5f4]\">\n                    {note.price ?? 'Custom pricing'}\n                  </span>\n                  {note.priceNote && (\n                    <span className=\"text-right text-[15px] leading-snug text-[#78716c] dark:text-[#8f8a85]\">\n                      {note.priceNote}\n                    </span>\n                  )}\n                </div>\n\n                <button\n                  type=\"button\"\n                  className={cn(\n                    'group mt-6 flex h-[52px] w-full items-center justify-center gap-2 rounded-[3px] text-[16px] font-medium',\n                    'transition-[transform,background-color,border-color] duration-150 active:scale-[0.98]',\n                    'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[#4a7dfc] focus-visible:ring-offset-2',\n                    position === 0\n                      ? 'border border-black/40 bg-[#3a3733] text-white shadow-[inset_0_0_0_1px_rgba(255,255,255,0.14)] hover:bg-[#292624] dark:border-white/20 dark:bg-[#f5f5f4] dark:text-[#1c1917] dark:shadow-[inset_0_0_0_1px_rgba(0,0,0,0.08)] dark:hover:bg-white'\n                      : 'border border-[#d9d6d2] bg-white text-[#453f54] hover:border-[#b9b5af] dark:border-white/[0.15] dark:bg-transparent dark:text-[#d3cde2] dark:hover:border-white/[0.3]',\n                  )}\n                >\n                  {plan.cta}\n                  {position > 0 && (\n                    <ArrowRight\n                      aria-hidden\n                      className=\"size-4 transition-transform duration-[180ms] ease-[cubic-bezier(0.23,1,0.32,1)] group-hover:translate-x-0.5 motion-reduce:transition-none\"\n                    />\n                  )}\n                </button>\n\n                <p\n                  className={cn(\n                    'mt-9 text-[13px] font-semibold uppercase tracking-[0.05em]',\n                    palette.label,\n                  )}\n                >\n                  {plan.inherits ? `Everything in ${plan.inherits}, plus:` : 'Core functionality'}\n                </p>\n\n                <ul className=\"mt-6 space-y-[18px]\">\n                  {plan.features.map((feature) => (\n                    <li key={feature} className=\"flex items-baseline gap-3.5\">\n                      <svg\n                        viewBox=\"0 0 16 16\"\n                        fill=\"none\"\n                        aria-hidden\n                        className=\"size-4 shrink-0 translate-y-0.5 text-[#1c1b1a] dark:text-[#f5f5f4]\"\n                      >\n                        <path\n                          d=\"m2.5 9 3.5 3.5L13.5 4\"\n                          stroke=\"currentColor\"\n                          strokeWidth=\"1.6\"\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                        />\n                      </svg>\n                      <span className=\"text-[16.5px] leading-[1.45] text-[#292524] dark:text-[#d6d3d1]\">\n                        {feature}\n                      </span>\n                    </li>\n                  ))}\n                </ul>\n\n                {note.footnote && (\n                  <p className=\"mt-10 text-center text-[15.5px] text-[#57534e] dark:text-[#a8a29e]\">\n                    {note.footnote}\n                  </p>\n                )}\n              </div>\n            </div>\n          );\n        })}\n      </div>\n    </section>\n  );\n}\n\nexport default GradientTiers;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/pricing/gradient-tiers.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"
}
