{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-table",
  "title": "Pricing Table",
  "author": "ilinxa",
  "description": "Pricing tiers side by side — monthly and annual toggle, highlighted tier, per-feature tooltips, and a comparison layout. RTL-safe, full i18n.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/registry/components/marketing/pricing-table/pricing-table.tsx",
      "content": "\"use client\";\n\nimport { memo, useCallback, useEffect, useId, useMemo, useState } from \"react\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { BillingToggle } from \"./parts/billing-toggle\";\nimport { ComparisonTable } from \"./parts/comparison-table\";\nimport { TierCard } from \"./parts/tier-card\";\nimport { resolveTone } from \"./parts/tone\";\nimport {\n  DEFAULT_LABELS,\n  type BillingPeriod,\n  type PricingTableProps,\n} from \"./types\";\n\n/**\n * PricingTable — side-by-side pricing tiers with optional monthly/annual toggle,\n * highlighted-tier badge, per-feature included rows, and a comparison-table layout.\n *\n * Billing-period state is controlled-or-uncontrolled (mirrors React input convention).\n * CTAs accept ReactNode (load-bearing, consumer wraps with router primitive) OR a\n * CtaSpec convenience overload that renders a plain anchor/button.\n */\nfunction PricingTableImpl(props: PricingTableProps) {\n  const {\n    heading,\n    subheading,\n    headingAs = \"h2\",\n    tiers,\n    layout = \"cards\",\n    billingToggle = \"none\",\n    billing: controlledBilling,\n    defaultBilling = \"monthly\",\n    onBillingChange,\n    tone = \"primary\",\n    onTierCtaClick,\n    labels: labelsProp,\n    className,\n    tierCardClassName,\n    highlightedRingClassName,\n    id: idProp,\n  } = props;\n\n  const HeadingTag = headingAs;\n  const generatedId = useId();\n  const rootId = idProp ?? generatedId;\n  const headingId = `${rootId}-heading`;\n\n  const labels = useMemo(\n    () => ({ ...DEFAULT_LABELS, ...labelsProp }),\n    [labelsProp],\n  );\n  const toneClasses = useMemo(() => resolveTone(tone), [tone]);\n\n  const [internalBilling, setInternalBilling] =\n    useState<BillingPeriod>(defaultBilling);\n  const isControlled = controlledBilling !== undefined;\n  const billing = isControlled ? controlledBilling : internalBilling;\n\n  const handleBillingChange = useCallback(\n    (period: BillingPeriod) => {\n      if (!isControlled) setInternalBilling(period);\n      onBillingChange?.(period);\n    },\n    [isControlled, onBillingChange],\n  );\n\n  const handleTierCtaClick = useCallback(\n    (tierName: string) => {\n      onTierCtaClick?.(tierName);\n    },\n    [onTierCtaClick],\n  );\n\n  useEffect(() => {\n    if (process.env.NODE_ENV === \"production\") return;\n    if (tiers.length < 2 || tiers.length > 4) {\n      console.warn(\n        `[pricing-table] expected 2–4 tiers, received ${tiers.length}. Layout assumes 2–4 tiers.`,\n      );\n    }\n    const highlightedCount = tiers.filter((tier) => tier.highlighted).length;\n    if (highlightedCount > 1) {\n      console.warn(\n        `[pricing-table] ${highlightedCount} tiers marked highlighted; only one tier per table is the intended pattern.`,\n      );\n    }\n  }, [tiers]);\n\n  const showToggle = billingToggle === \"monthly-annual\";\n\n  // F-cross-13: dropped `delayDuration={150}` from <TooltipProvider> below —\n  // Radix-vs-Base-UI prop-name divergence (`delayDuration` vs `delay`). Per\n  // the locked defensive pattern, syntactically-divergent props are dropped\n  // at the producer side so consumer-side shadcn-add installs (which ship\n  // Base UI) don't carry a stale prop name. Default delay (~700ms Radix /\n  // ~600ms Base UI) is acceptable for a pricing-table tooltip surface.\n  return (\n    <TooltipProvider>\n      <section\n        id={rootId}\n        aria-labelledby={heading ? headingId : undefined}\n        className={cn(\"flex flex-col gap-8\", className)}\n      >\n        {(heading || subheading || showToggle) && (\n          <header className=\"flex flex-col items-center gap-4 text-center\">\n            {heading ? (\n              <HeadingTag\n                id={headingId}\n                className=\"text-2xl font-semibold text-foreground sm:text-3xl\"\n              >\n                {heading}\n              </HeadingTag>\n            ) : null}\n            {subheading ? (\n              <p className=\"max-w-2xl text-sm text-muted-foreground\">\n                {subheading}\n              </p>\n            ) : null}\n            {showToggle ? (\n              <BillingToggle\n                billing={billing}\n                onChange={handleBillingChange}\n                labels={labels}\n                toneClasses={toneClasses}\n              />\n            ) : null}\n          </header>\n        )}\n\n        {layout === \"table\" ? (\n          <ComparisonTable\n            tiers={tiers}\n            billing={billing}\n            labels={labels}\n            toneClasses={toneClasses}\n            onTierCtaClick={handleTierCtaClick}\n            highlightedRingClassName={highlightedRingClassName}\n          />\n        ) : (\n          <div\n            className={cn(\n              \"grid gap-6\",\n              \"grid-cols-1\",\n              tiers.length === 2 && \"md:grid-cols-2\",\n              tiers.length === 3 && \"md:grid-cols-2 lg:grid-cols-3\",\n              tiers.length >= 4 && \"md:grid-cols-2 lg:grid-cols-4\",\n            )}\n          >\n            {tiers.map((tier) => (\n              <TierCard\n                key={tier.name}\n                tier={tier}\n                billing={billing}\n                labels={labels}\n                toneClasses={toneClasses}\n                onTierCtaClick={handleTierCtaClick}\n                className={tierCardClassName}\n                highlightedRingClassName={highlightedRingClassName}\n              />\n            ))}\n          </div>\n        )}\n      </section>\n    </TooltipProvider>\n  );\n}\n\nexport const PricingTable = memo(PricingTableImpl);\nPricingTable.displayName = \"PricingTable\";\n\nexport default PricingTable;\n",
      "type": "registry:component",
      "target": "components/pricing-table/pricing-table.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/index.ts",
      "content": "export { PricingTable, default } from \"./pricing-table\";\nexport type {\n  BillingPeriod,\n  CtaSpec,\n  CtaVariant,\n  CurrencyDisplay,\n  PricingBillingToggle,\n  PricingFeature,\n  PricingHeadingLevel,\n  PricingLayout,\n  PricingTableProps,\n  PricingTableLabels,\n  PricingTier,\n  PricingTone,\n} from \"./types\";\n// No meta re-export: meta.ts never ships to consumers (locked registry\n// convention) — a barrel reference breaks consumer tsc (F-cross-13 smoke).\n",
      "type": "registry:component",
      "target": "components/pricing-table/index.ts"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/types.ts",
      "content": "import type { ReactNode } from \"react\";\n\nexport type PricingLayout = \"cards\" | \"table\";\n\nexport type PricingBillingToggle = \"none\" | \"monthly-annual\";\n\nexport type BillingPeriod = \"monthly\" | \"annual\";\n\nexport type PricingTone = \"primary\" | \"accent\" | \"muted\";\n\nexport type CurrencyDisplay = \"symbol\" | \"code\";\n\nexport type PricingHeadingLevel = \"h2\" | \"h3\" | \"h4\";\n\nexport type CtaVariant = \"primary\" | \"outline\";\n\nexport interface CtaSpec {\n  label: string;\n  href?: string;\n  onClick?: () => void;\n  variant?: CtaVariant;\n  ariaLabel?: string;\n}\n\nexport interface PricingFeature {\n  label: string;\n  included: boolean;\n  tooltip?: string;\n}\n\nexport interface PricingTier {\n  name: string;\n  description?: string;\n  /** Per-month price. Always required (annual mode shows the monthly equivalent). */\n  priceMonthly: number;\n  /** Per-month rate when billed annually. The optional yearly hint is derived as priceAnnual * 12. */\n  priceAnnual?: number;\n  /** ISO 4217 (e.g. \"USD\", \"EUR\", \"TRY\"). */\n  currencyCode: string;\n  /** Default: \"symbol\". */\n  currencyDisplay?: CurrencyDisplay;\n  /** Overrides default periodLabel from the labels bag (e.g. \"per user / month\"). */\n  periodLabel?: string;\n  features: ReadonlyArray<PricingFeature>;\n  /** Load-bearing: ReactNode. Pass a CtaSpec for the convenience case. */\n  cta: ReactNode | CtaSpec;\n  highlighted?: boolean;\n  /** Per-tier override of the \"Most popular\" badge text. */\n  badge?: string;\n}\n\nexport interface PricingTableLabels {\n  monthlyLabel?: string;\n  annualLabel?: string;\n  /** sr-only label for the toggle radiogroup. */\n  toggleGroupLabel?: string;\n  popularBadge?: string;\n  periodMonthly?: string;\n  periodAnnual?: string;\n  /** Opt-in: rendered when priceMonthly === 0 (instead of the formatted \"0\"). */\n  freeLabel?: string;\n  /**\n   * Small label next to the annual price. String form supports the \"{amount}\"\n   * placeholder; function form receives the formatted yearly total.\n   * Pass `null` to hide.\n   */\n  yearlyHint?: string | ((yearlyTotal: string) => string) | null;\n  /** sr-only state label for \"included\" features. */\n  featureIncluded?: string;\n  /** sr-only state label for \"not included\" features. */\n  featureExcluded?: string;\n}\n\nexport interface ResolvedLabels {\n  monthlyLabel: string;\n  annualLabel: string;\n  toggleGroupLabel: string;\n  popularBadge: string;\n  periodMonthly: string;\n  periodAnnual: string;\n  freeLabel: string | undefined;\n  yearlyHint: string | ((yearlyTotal: string) => string) | null;\n  featureIncluded: string;\n  featureExcluded: string;\n}\n\nexport const DEFAULT_LABELS: ResolvedLabels = {\n  monthlyLabel: \"Monthly\",\n  annualLabel: \"Annual\",\n  toggleGroupLabel: \"Billing period\",\n  popularBadge: \"Most popular\",\n  periodMonthly: \"per month\",\n  periodAnnual: \"per month, billed annually\",\n  freeLabel: undefined,\n  yearlyHint: \"{amount} / yr\",\n  featureIncluded: \"Included\",\n  featureExcluded: \"Not included\",\n};\n\nexport interface PricingTableProps {\n  heading?: string;\n  subheading?: string;\n  headingAs?: PricingHeadingLevel;\n\n  /** 2–4 tiers. Out-of-range length logs a dev-mode warn but still renders. */\n  tiers: ReadonlyArray<PricingTier>;\n\n  /** Layout variant. Default: \"cards\". */\n  layout?: PricingLayout;\n\n  /** Billing toggle mode. Default: \"none\". */\n  billingToggle?: PricingBillingToggle;\n\n  /** Controlled billing period. */\n  billing?: BillingPeriod;\n  /** Uncontrolled initial value. Default: \"monthly\". */\n  defaultBilling?: BillingPeriod;\n  onBillingChange?: (period: BillingPeriod) => void;\n\n  /** Color tone. Default: \"primary\". */\n  tone?: PricingTone;\n\n  /** Fires for CtaSpec tiers after the consumer's onClick. ReactNode CTAs wire their own. */\n  onTierCtaClick?: (tierName: string) => void;\n\n  labels?: PricingTableLabels;\n\n  className?: string;\n  /** Class on each tier card / column. */\n  tierCardClassName?: string;\n  /** Class on the highlighted-tier ring/border. */\n  highlightedRingClassName?: string;\n\n  /** Override the root id (drives aria-labelledby wiring). Default: useId(). */\n  id?: string;\n}\n\nexport interface ResolvedTone {\n  cardBorder: string;\n  highlightRing: string;\n  highlightBorder: string;\n  badgeBg: string;\n  badgeText: string;\n  toggleActiveBg: string;\n  toggleActiveText: string;\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/types.ts"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/billing-toggle.tsx",
      "content": "import { useRef } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport type { BillingPeriod, ResolvedLabels, ResolvedTone } from \"../types\";\n\ninterface BillingToggleProps {\n  billing: BillingPeriod;\n  onChange: (period: BillingPeriod) => void;\n  labels: ResolvedLabels;\n  toneClasses: ResolvedTone;\n  className?: string;\n}\n\nconst SEGMENTS: ReadonlyArray<BillingPeriod> = [\"monthly\", \"annual\"];\n\nexport function BillingToggle({\n  billing,\n  onChange,\n  labels,\n  toneClasses,\n  className,\n}: BillingToggleProps) {\n  const refs = useRef<Array<HTMLButtonElement | null>>([null, null]);\n\n  const focusSegment = (index: number) => {\n    const next = (index + SEGMENTS.length) % SEGMENTS.length;\n    const period = SEGMENTS[next];\n    refs.current[next]?.focus();\n    onChange(period);\n  };\n\n  const handleKeyDown = (\n    event: KeyboardEvent<HTMLButtonElement>,\n    index: number,\n  ) => {\n    if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n      event.preventDefault();\n      focusSegment(index + 1);\n    } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n      event.preventDefault();\n      focusSegment(index - 1);\n    } else if (event.key === \"Home\") {\n      event.preventDefault();\n      focusSegment(0);\n    } else if (event.key === \"End\") {\n      event.preventDefault();\n      focusSegment(SEGMENTS.length - 1);\n    }\n  };\n\n  return (\n    <div\n      role=\"radiogroup\"\n      aria-label={labels.toggleGroupLabel}\n      className={cn(\n        \"inline-flex items-center rounded-full border border-border bg-muted/40 p-1 text-sm\",\n        className,\n      )}\n    >\n      {SEGMENTS.map((period, index) => {\n        const active = billing === period;\n        const segmentLabel =\n          period === \"monthly\" ? labels.monthlyLabel : labels.annualLabel;\n        return (\n          <button\n            key={period}\n            ref={(node) => {\n              refs.current[index] = node;\n            }}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={active}\n            tabIndex={active ? 0 : -1}\n            onClick={() => onChange(period)}\n            onKeyDown={(event) => handleKeyDown(event, index)}\n            className={cn(\n              \"rounded-full px-4 py-1.5 font-medium transition-colors\",\n              \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n              active\n                ? cn(toneClasses.toggleActiveBg, toneClasses.toggleActiveText)\n                : \"text-muted-foreground hover:text-foreground\",\n            )}\n          >\n            {segmentLabel}\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/billing-toggle.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/comparison-table.tsx",
      "content": "import { Check, X } from \"lucide-react\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { PriceDisplay } from \"./price-display\";\nimport { TierCta } from \"./tier-cta\";\nimport type {\n  BillingPeriod,\n  PricingFeature,\n  PricingTier,\n  ResolvedLabels,\n  ResolvedTone,\n} from \"../types\";\n\ninterface ComparisonTableProps {\n  tiers: ReadonlyArray<PricingTier>;\n  billing: BillingPeriod;\n  labels: ResolvedLabels;\n  toneClasses: ResolvedTone;\n  onTierCtaClick: (tierName: string) => void;\n  className?: string;\n  highlightedRingClassName?: string;\n}\n\ninterface FeatureRow {\n  label: string;\n  tooltip: string | undefined;\n  perTier: Array<PricingFeature | undefined>;\n}\n\nfunction buildFeatureRows(\n  tiers: ReadonlyArray<PricingTier>,\n): ReadonlyArray<FeatureRow> {\n  const seen = new Map<string, FeatureRow>();\n  for (let tierIndex = 0; tierIndex < tiers.length; tierIndex++) {\n    const tier = tiers[tierIndex];\n    for (const feature of tier.features) {\n      const existing = seen.get(feature.label);\n      if (existing) {\n        existing.perTier[tierIndex] = feature;\n        if (!existing.tooltip && feature.tooltip) {\n          existing.tooltip = feature.tooltip;\n        }\n      } else {\n        const perTier: Array<PricingFeature | undefined> = new Array(\n          tiers.length,\n        ).fill(undefined);\n        perTier[tierIndex] = feature;\n        seen.set(feature.label, {\n          label: feature.label,\n          tooltip: feature.tooltip,\n          perTier,\n        });\n      }\n    }\n  }\n  return Array.from(seen.values());\n}\n\nexport function ComparisonTable({\n  tiers,\n  billing,\n  labels,\n  toneClasses,\n  onTierCtaClick,\n  className,\n  highlightedRingClassName,\n}: ComparisonTableProps) {\n  const rows = buildFeatureRows(tiers);\n\n  return (\n    <div className={cn(\"w-full overflow-x-auto\", className)}>\n      <table className=\"w-full min-w-[640px] border-collapse text-sm\">\n        <thead>\n          <tr>\n            <th scope=\"col\" className=\"w-1/4 p-3 text-start font-normal\" />\n            {tiers.map((tier) => {\n              const isHighlighted = !!tier.highlighted;\n              const badgeText = tier.badge ?? labels.popularBadge;\n              return (\n                <th\n                  key={tier.name}\n                  scope=\"col\"\n                  className={cn(\n                    \"p-3 text-start align-top\",\n                    isHighlighted\n                      ? cn(\n                          \"rounded-t-xl ring-2 ring-inset\",\n                          toneClasses.highlightRing,\n                          highlightedRingClassName,\n                        )\n                      : null,\n                  )}\n                >\n                  <div className=\"flex flex-col gap-1.5\">\n                    {isHighlighted ? (\n                      <span\n                        className={cn(\n                          \"inline-flex w-fit items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide\",\n                          toneClasses.badgeBg,\n                          toneClasses.badgeText,\n                        )}\n                      >\n                        {badgeText}\n                      </span>\n                    ) : null}\n                    <span className=\"text-base font-semibold text-foreground\">\n                      {tier.name}\n                    </span>\n                    {tier.description ? (\n                      <span className=\"text-xs text-muted-foreground\">\n                        {tier.description}\n                      </span>\n                    ) : null}\n                  </div>\n                </th>\n              );\n            })}\n          </tr>\n        </thead>\n        <tbody>\n          <tr className=\"border-t border-border/60\">\n            <th scope=\"row\" className=\"sticky start-0 bg-card p-3 text-start text-xs font-medium text-muted-foreground\">\n              {billing === \"annual\" ? labels.annualLabel : labels.monthlyLabel}\n            </th>\n            {tiers.map((tier) => (\n              <td key={tier.name} className=\"p-3 align-top\">\n                <PriceDisplay tier={tier} billing={billing} labels={labels} />\n              </td>\n            ))}\n          </tr>\n          <tr className=\"border-t border-border/60\">\n            <th\n              scope=\"row\"\n              className=\"sticky start-0 bg-card p-3 text-start text-xs font-medium text-muted-foreground\"\n            >\n              <span className=\"sr-only\">Call to action</span>\n            </th>\n            {tiers.map((tier) => (\n              <td key={tier.name} className=\"p-3 align-top\">\n                <TierCta\n                  cta={tier.cta}\n                  tierName={tier.name}\n                  onTierCtaClick={onTierCtaClick}\n                />\n              </td>\n            ))}\n          </tr>\n          {rows.map((row) => (\n            <tr key={row.label} className=\"border-t border-border/60\">\n              <th\n                scope=\"row\"\n                className=\"sticky start-0 bg-card p-3 text-start text-sm font-normal text-foreground\"\n              >\n                {row.tooltip ? (\n                  /* F-cross-13: no `asChild` — the trigger IS the label button. */\n                  <Tooltip>\n                    <TooltipTrigger\n                      type=\"button\"\n                      className=\"cursor-help text-start underline decoration-dotted decoration-muted-foreground/40 underline-offset-2\"\n                    >\n                      {row.label}\n                    </TooltipTrigger>\n                    <TooltipContent>{row.tooltip}</TooltipContent>\n                  </Tooltip>\n                ) : (\n                  row.label\n                )}\n              </th>\n              {row.perTier.map((cell, index) => {\n                const tier = tiers[index];\n                const included = cell?.included ?? false;\n                const stateLabel = included\n                  ? labels.featureIncluded\n                  : labels.featureExcluded;\n                const Icon = included ? Check : X;\n                return (\n                  <td\n                    key={tier.name}\n                    className=\"p-3 align-top text-muted-foreground\"\n                  >\n                    <span className=\"sr-only\">{stateLabel}</span>\n                    <Icon\n                      aria-hidden=\"true\"\n                      className={cn(\n                        \"h-4 w-4\",\n                        included ? \"text-primary\" : \"text-muted-foreground\",\n                      )}\n                    />\n                  </td>\n                );\n              })}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/comparison-table.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/price-display.tsx",
      "content": "import { cn } from \"@/lib/utils\";\nimport type { BillingPeriod, PricingTier, ResolvedLabels } from \"../types\";\nimport { formatPrice, formatYearly, resolveYearlyHint } from \"./format\";\n\ninterface PriceDisplayProps {\n  tier: PricingTier;\n  billing: BillingPeriod;\n  labels: ResolvedLabels;\n  className?: string;\n}\n\nexport function PriceDisplay({\n  tier,\n  billing,\n  labels,\n  className,\n}: PriceDisplayProps) {\n  const {\n    priceMonthly,\n    priceAnnual,\n    currencyCode,\n    currencyDisplay = \"symbol\",\n    periodLabel,\n  } = tier;\n\n  const showAnnual = billing === \"annual\" && priceAnnual !== undefined;\n  const activePrice = showAnnual ? (priceAnnual as number) : priceMonthly;\n\n  const isFree =\n    activePrice === 0 && labels.freeLabel !== undefined && billing === \"monthly\";\n\n  const formattedActive = formatPrice(activePrice, currencyCode, currencyDisplay);\n  const formattedMonthly = formatPrice(priceMonthly, currencyCode, currencyDisplay);\n\n  const periodCopy =\n    periodLabel ??\n    (billing === \"annual\" ? labels.periodAnnual : labels.periodMonthly);\n\n  const showStrikethrough =\n    showAnnual && (priceAnnual as number) < priceMonthly;\n\n  const yearlyHintText = showAnnual\n    ? resolveYearlyHint(\n        labels.yearlyHint,\n        formatYearly(priceAnnual as number, currencyCode, currencyDisplay),\n      )\n    : null;\n\n  return (\n    <div className={cn(\"flex flex-col gap-1\", className)}>\n      <div className=\"flex items-baseline gap-2\">\n        {isFree ? (\n          <span className=\"text-3xl font-bold tracking-tight text-foreground\">\n            {labels.freeLabel}\n          </span>\n        ) : (\n          <>\n            <span className=\"text-3xl font-bold tracking-tight text-foreground\">\n              {formattedActive}\n            </span>\n            {showStrikethrough ? (\n              <s\n                className=\"text-sm text-muted-foreground\"\n                aria-label={`Was ${formattedMonthly} per month`}\n              >\n                {formattedMonthly}\n              </s>\n            ) : null}\n          </>\n        )}\n      </div>\n      {!isFree ? (\n        <span className=\"text-xs text-muted-foreground\">{periodCopy}</span>\n      ) : null}\n      {yearlyHintText ? (\n        <span className=\"text-xs text-muted-foreground/80\">\n          {yearlyHintText}\n        </span>\n      ) : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/price-display.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/tier-card.tsx",
      "content": "import { useId } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { PriceDisplay } from \"./price-display\";\nimport { TierCta } from \"./tier-cta\";\nimport { TierFeatureRow } from \"./tier-feature-row\";\nimport type {\n  BillingPeriod,\n  PricingTier,\n  ResolvedLabels,\n  ResolvedTone,\n} from \"../types\";\n\ninterface TierCardProps {\n  tier: PricingTier;\n  billing: BillingPeriod;\n  labels: ResolvedLabels;\n  toneClasses: ResolvedTone;\n  onTierCtaClick: (tierName: string) => void;\n  className?: string;\n  highlightedRingClassName?: string;\n}\n\nexport function TierCard({\n  tier,\n  billing,\n  labels,\n  toneClasses,\n  onTierCtaClick,\n  className,\n  highlightedRingClassName,\n}: TierCardProps) {\n  const titleId = useId();\n  const isHighlighted = !!tier.highlighted;\n  const badgeText = tier.badge ?? labels.popularBadge;\n\n  return (\n    <article\n      role=\"region\"\n      aria-labelledby={titleId}\n      className={cn(\n        \"relative flex flex-col gap-6 rounded-2xl border bg-card p-6 text-card-foreground\",\n        isHighlighted\n          ? cn(\n              \"ring-2\",\n              toneClasses.highlightBorder,\n              toneClasses.highlightRing,\n              highlightedRingClassName,\n            )\n          : toneClasses.cardBorder,\n        className,\n      )}\n    >\n      {isHighlighted ? (\n        <span\n          className={cn(\n            \"absolute -top-3 start-6 inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-wide\",\n            toneClasses.badgeBg,\n            toneClasses.badgeText,\n          )}\n          aria-label={badgeText}\n        >\n          {badgeText}\n        </span>\n      ) : null}\n\n      <header className=\"flex flex-col gap-1.5\">\n        <h3 id={titleId} className=\"text-lg font-semibold text-foreground\">\n          {tier.name}\n        </h3>\n        {tier.description ? (\n          <p className=\"text-sm text-muted-foreground\">{tier.description}</p>\n        ) : null}\n      </header>\n\n      <PriceDisplay tier={tier} billing={billing} labels={labels} />\n\n      <TierCta\n        cta={tier.cta}\n        tierName={tier.name}\n        onTierCtaClick={onTierCtaClick}\n      />\n\n      <ul role=\"list\" className=\"flex flex-col gap-2\">\n        {tier.features.map((feature, index) => (\n          <TierFeatureRow\n            key={`${feature.label}-${index}`}\n            feature={feature}\n            labels={labels}\n          />\n        ))}\n      </ul>\n    </article>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/tier-card.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/tier-cta.tsx",
      "content": "import { isValidElement } from \"react\";\nimport type { MouseEvent, ReactNode } from \"react\";\nimport { Button, buttonVariants } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport type { CtaSpec, CtaVariant } from \"../types\";\n\ninterface TierCtaProps {\n  cta: ReactNode | CtaSpec;\n  tierName: string;\n  onTierCtaClick: (tierName: string) => void;\n  className?: string;\n}\n\nfunction isCtaSpec(cta: ReactNode | CtaSpec): cta is CtaSpec {\n  if (cta === null || typeof cta !== \"object\") return false;\n  if (isValidElement(cta)) return false;\n  const candidate = cta as Partial<CtaSpec>;\n  if (typeof candidate.label !== \"string\") return false;\n  return candidate.href !== undefined || candidate.onClick !== undefined;\n}\n\nfunction buttonVariantFor(variant: CtaVariant | undefined) {\n  return variant === \"outline\" ? \"outline\" : \"default\";\n}\n\nexport function TierCta({\n  cta,\n  tierName,\n  onTierCtaClick,\n  className,\n}: TierCtaProps) {\n  if (!isCtaSpec(cta)) {\n    return <>{cta}</>;\n  }\n\n  const { label, href, onClick, variant, ariaLabel } = cta;\n  const buttonVariant = buttonVariantFor(variant);\n\n  const handleClick = (event: MouseEvent<HTMLElement>) => {\n    onTierCtaClick(tierName);\n    onClick?.();\n    if (!href) event.preventDefault();\n  };\n\n  if (href) {\n    // F-cross-13: no `<Button asChild>` — style the anchor with buttonVariants directly.\n    return (\n      <a\n        href={href}\n        onClick={handleClick}\n        aria-label={ariaLabel}\n        className={cn(buttonVariants({ variant: buttonVariant }), \"w-full\", className)}\n      >\n        {label}\n      </a>\n    );\n  }\n\n  if (onClick) {\n    return (\n      <Button\n        type=\"button\"\n        variant={buttonVariant}\n        className={cn(\"w-full\", className)}\n        onClick={handleClick}\n        aria-label={ariaLabel}\n      >\n        {label}\n      </Button>\n    );\n  }\n\n  if (process.env.NODE_ENV !== \"production\") {\n    console.warn(\n      `[pricing-table] tier \"${tierName}\" CTA spec has neither href nor onClick — rendering disabled button.`,\n    );\n  }\n\n  return (\n    <Button\n      type=\"button\"\n      variant={buttonVariant}\n      className={cn(\"w-full\", className)}\n      disabled\n      aria-label={ariaLabel}\n    >\n      {label}\n    </Button>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/tier-cta.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/tier-feature-row.tsx",
      "content": "import { Check, X } from \"lucide-react\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport type { PricingFeature, ResolvedLabels } from \"../types\";\n\ninterface TierFeatureRowProps {\n  feature: PricingFeature;\n  labels: ResolvedLabels;\n  className?: string;\n}\n\nexport function TierFeatureRow({\n  feature,\n  labels,\n  className,\n}: TierFeatureRowProps) {\n  const Icon = feature.included ? Check : X;\n  const iconClass = feature.included\n    ? \"text-primary\"\n    : \"text-muted-foreground\";\n  const stateLabel = feature.included\n    ? labels.featureIncluded\n    : labels.featureExcluded;\n\n  // F-cross-13: no `asChild` — the trigger IS the label (a keyboard-reachable\n  // <button> in both backends; text-start counters the UA's centered button text).\n  const labelContent = feature.tooltip ? (\n    <Tooltip>\n      <TooltipTrigger\n        type=\"button\"\n        className=\"cursor-help text-start underline decoration-dotted decoration-muted-foreground/40 underline-offset-2\"\n      >\n        {feature.label}\n      </TooltipTrigger>\n      <TooltipContent>{feature.tooltip}</TooltipContent>\n    </Tooltip>\n  ) : (\n    feature.label\n  );\n\n  return (\n    <li\n      className={cn(\n        \"flex items-start gap-2 text-sm\",\n        feature.included ? \"text-foreground\" : \"text-muted-foreground\",\n        className,\n      )}\n    >\n      <Icon\n        aria-hidden=\"true\"\n        className={cn(\"mt-0.5 h-4 w-4 shrink-0\", iconClass)}\n      />\n      <span className=\"sr-only\">{stateLabel}:</span>\n      <span>{labelContent}</span>\n    </li>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/tier-feature-row.tsx"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/format.ts",
      "content": "import type { CurrencyDisplay } from \"../types\";\n\nconst FALLBACK_LOCALE = \"en-US\";\n\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\nfunction getFormatter(\n  currencyCode: string,\n  currencyDisplay: CurrencyDisplay,\n): Intl.NumberFormat | null {\n  const key = `${currencyCode}|${currencyDisplay}`;\n  const cached = formatterCache.get(key);\n  if (cached) return cached;\n\n  try {\n    const fmt = new Intl.NumberFormat(FALLBACK_LOCALE, {\n      style: \"currency\",\n      currency: currencyCode,\n      currencyDisplay,\n      minimumFractionDigits: 0,\n      maximumFractionDigits: 2,\n    });\n    formatterCache.set(key, fmt);\n    return fmt;\n  } catch {\n    return null;\n  }\n}\n\nexport function formatPrice(\n  value: number,\n  currencyCode: string,\n  currencyDisplay: CurrencyDisplay = \"symbol\",\n): string {\n  const fmt = getFormatter(currencyCode, currencyDisplay);\n  if (!fmt) return `${currencyCode} ${value}`;\n\n  const raw = fmt.format(value);\n  return raw.replace(/[.,]00(?=\\D|$)/, \"\");\n}\n\nexport function formatYearly(\n  priceAnnual: number,\n  currencyCode: string,\n  currencyDisplay: CurrencyDisplay = \"symbol\",\n): string {\n  return formatPrice(priceAnnual * 12, currencyCode, currencyDisplay);\n}\n\nexport function resolveYearlyHint(\n  template: string | ((yearlyTotal: string) => string) | null,\n  yearlyTotal: string,\n): string | null {\n  if (template === null) return null;\n  if (typeof template === \"function\") return template(yearlyTotal);\n  return template.replace(\"{amount}\", yearlyTotal);\n}\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/format.ts"
    },
    {
      "path": "src/registry/components/marketing/pricing-table/parts/tone.ts",
      "content": "import type { PricingTone, ResolvedTone } from \"../types\";\n\nconst TONE_MAP: Record<PricingTone, ResolvedTone> = {\n  primary: {\n    cardBorder: \"border-border/60\",\n    highlightRing: \"ring-primary/30\",\n    highlightBorder: \"border-primary\",\n    badgeBg: \"bg-primary\",\n    badgeText: \"text-primary-foreground\",\n    toggleActiveBg: \"bg-primary\",\n    toggleActiveText: \"text-primary-foreground\",\n  },\n  accent: {\n    cardBorder: \"border-accent/40\",\n    highlightRing: \"ring-accent/40\",\n    highlightBorder: \"border-accent\",\n    badgeBg: \"bg-accent\",\n    badgeText: \"text-accent-foreground\",\n    toggleActiveBg: \"bg-accent\",\n    toggleActiveText: \"text-accent-foreground\",\n  },\n  muted: {\n    cardBorder: \"border-border/50\",\n    highlightRing: \"ring-foreground/15\",\n    highlightBorder: \"border-foreground/50\",\n    badgeBg: \"bg-muted\",\n    badgeText: \"text-foreground\",\n    toggleActiveBg: \"bg-foreground\",\n    toggleActiveText: \"text-background\",\n  },\n};\n\nexport const resolveTone = (tone: PricingTone): ResolvedTone => TONE_MAP[tone];\n",
      "type": "registry:component",
      "target": "components/pricing-table/parts/tone.ts"
    }
  ],
  "categories": [
    "marketing"
  ],
  "type": "registry:block"
}