{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dark-matrix",
  "title": "Dark Matrix",
  "description": "A dark comparison table with dashed rows, mint checks, and per-plan CTA pills.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/spectrumui/blocks/pricing/dark-matrix.tsx",
      "content": "'use client';\n\nimport { Check, Layers, Sparkles, X, Zap } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { REVEAL_KEYFRAMES, revealClass, revealDelay, useReveal } from './reveal';\nimport type { FeatureGroup, FeatureValue, Plan } from './types';\n\nexport type DarkMatrixVariant = 'Sections' | 'Flat';\n\nexport interface DarkMatrixProps {\n  plans: Plan[];\n  eyebrow?: string;\n  heading?: string;\n  subheading?: string;\n  groups: FeatureGroup[];\n  hints?: Record<string, string>;\n  variant?: DarkMatrixVariant;\n  className?: string;\n}\n\nconst ICONS = [Layers, Zap, Sparkles];\n\nfunction Value({ value }: { value: FeatureValue }) {\n  if (value === true) {\n    return (\n      <>\n        <Check\n          aria-hidden\n          strokeWidth={2.4}\n          className=\"mx-auto size-4 text-[#0e9f7e] dark:text-[#4be0c0]\"\n        />\n        <span className=\"sr-only\">Included</span>\n      </>\n    );\n  }\n  if (value === false) {\n    return (\n      <>\n        <X\n          aria-hidden\n          strokeWidth={2}\n          className=\"mx-auto size-4 text-[#b4b4bb] dark:text-[#55555d]\"\n        />\n        <span className=\"sr-only\">Not included</span>\n      </>\n    );\n  }\n  return <span className=\"tabular-nums\">{value}</span>;\n}\n\nexport function DarkMatrix({\n  plans,\n  groups,\n  eyebrow,\n  heading,\n  subheading,\n  hints = {},\n  variant = 'Sections',\n  className,\n}: DarkMatrixProps) {\n  const { ref, shown } = useReveal<HTMLElement>();\n  const rows = groups.flatMap((group) => group.rows);\n\n  const body = (rowsToRender: typeof rows) =>\n    rowsToRender.map((row) => (\n      <tr key={row.label}>\n        <th\n          scope=\"row\"\n          className=\"bg-white py-4 pl-5 pr-4 text-left text-[13.5px] font-normal text-[#5c5c64] [border-bottom:1px_dashed_#dcdce1] dark:bg-[#131316] dark:text-[#a5a5ad] dark:[border-bottom:1px_dashed_#2b2b30]\"\n        >\n          <span className=\"inline-flex items-center gap-2.5\">\n            {row.label}\n            {(row.hint ?? hints[row.label]) && (\n              <span\n                title={row.hint ?? hints[row.label]}\n                className=\"grid size-4 place-items-center rounded-full border border-[#c4c4cb] text-[9px] text-[#6d6d75] dark:border-[#3f3f46] dark:text-[#8f8f96]\"\n              >\n                ?<span className=\"sr-only\">{row.hint ?? hints[row.label]}</span>\n              </span>\n            )}\n          </span>\n        </th>\n        {row.values.map((value, index) => (\n          <td\n            key={plans[index]?.id ?? index}\n            className=\"bg-white px-4 py-4 text-center text-[13.5px] text-[#2a2a2f] [border-bottom:1px_dashed_#dcdce1] [border-left:1px_solid_#e7e7ea] dark:bg-[#131316] dark:text-[#d4d4da] dark:[border-bottom:1px_dashed_#2b2b30] dark:[border-left:1px_solid_#232327]\"\n          >\n            <Value value={value} />\n          </td>\n        ))}\n      </tr>\n    ));\n\n  return (\n    <section\n      ref={ref}\n      className={cn(\n        '@container w-full rounded-3xl bg-[#f7f7f8] p-4 dark:bg-[#09090b] sm:p-8',\n        className,\n      )}\n    >\n      <style dangerouslySetInnerHTML={{ __html: REVEAL_KEYFRAMES }} />\n\n      <div className={cn('overflow-x-auto', revealClass(shown))} style={revealDelay(shown, 0)}>\n        <table className=\"w-full min-w-[720px] border-collapse text-left\">\n          <caption className=\"sr-only\">Plan comparison</caption>\n          <thead>\n            <tr>\n              <th scope=\"col\" className=\"w-[34%] pb-5 pr-6 text-left align-bottom\">\n                {heading && (\n                  <span className=\"block\">\n                    {eyebrow && (\n                      <span className=\"block font-mono text-[10.5px] font-medium uppercase tracking-[0.16em] text-[#0e9f7e] dark:text-[#4be0c0]\">\n                        {eyebrow}\n                      </span>\n                    )}\n                    <span className=\"mt-2 block text-balance text-[22px] font-semibold leading-[1.15] tracking-[-0.4px] text-[#0f0f11] dark:text-white\">\n                      {heading}\n                    </span>\n                    {subheading && (\n                      <span className=\"mt-1.5 block text-pretty text-[12.5px] font-normal leading-[1.5] text-[#5c5c64] dark:text-[#a5a5ad]\">\n                        {subheading}\n                      </span>\n                    )}\n                  </span>\n                )}\n              </th>\n              {plans.map((plan, index) => {\n                const Icon = ICONS[index % ICONS.length];\n                return (\n                  <th\n                    key={plan.id}\n                    scope=\"col\"\n                    className=\"border-l border-[#e2e2e6] px-4 pb-5 dark:border-[#232327]\"\n                  >\n                    <span className=\"flex items-center justify-between gap-3\">\n                      <span className=\"flex items-center gap-2\">\n                        <Icon\n                          aria-hidden\n                          strokeWidth={1.8}\n                          className=\"size-4 text-[#0f0f11] dark:text-white\"\n                        />\n                        <span className=\"text-[14.5px] font-semibold text-[#0f0f11] dark:text-white\">\n                          {plan.name}\n                        </span>\n                      </span>\n                      <button\n                        type=\"button\"\n                        className={cn(\n                          'whitespace-nowrap rounded-full px-3.5 py-1.5 text-[11.5px] font-medium',\n                          'transition-[transform,background-color] duration-150 active:scale-[0.96]',\n                          'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[#0e9f7e] dark:focus-visible:ring-[#4be0c0]',\n                          plan.featured\n                            ? 'bg-[#0f0f11] text-white hover:bg-black dark:bg-white dark:text-[#09090b] dark:hover:bg-[#e7e7ea]'\n                            : 'bg-[#e7e7ea] text-[#0f0f11] hover:bg-[#dcdce0] dark:bg-[#232328] dark:text-white dark:hover:bg-[#2e2e34]',\n                        )}\n                      >\n                        {plan.cta}\n                      </button>\n                    </span>\n                  </th>\n                );\n              })}\n            </tr>\n          </thead>\n\n          {variant === 'Sections' ? (\n            groups.map((group) => (\n              <tbody key={group.label}>\n                <tr>\n                  <th\n                    scope=\"colgroup\"\n                    colSpan={plans.length + 1}\n                    className=\"pb-4 pt-7 text-left text-[14px] font-medium text-[#1c1c20] dark:text-[#e7e7ec]\"\n                  >\n                    {group.label}\n                  </th>\n                </tr>\n                {body(group.rows)}\n              </tbody>\n            ))\n          ) : (\n            <tbody>{body(rows)}</tbody>\n          )}\n        </table>\n      </div>\n    </section>\n  );\n}\n\nexport default DarkMatrix;\n",
      "type": "registry:block",
      "target": "components/spectrumui/blocks/pricing/dark-matrix.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"
}
