{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cta-band",
  "title": "CTA Band",
  "description": "A closing call-to-action strip — headline, supporting line and a primary/secondary CTA pair, in four surfaces (plain, card, muted, accent) and row or stacked layouts.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/blocks/cta-band.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * What a CTA does. The type forces one of the two: either `href` (rendered as an `<a>`) or\n * `onClick` (rendered as a `<button type=\"button\">`). Supplying neither is a compile error —\n * this component can never draw a dead button with no behaviour behind it.\n */\nexport type CtaBandAction =\n  | { label: string; href: string; onClick?: () => void }\n  | { label: string; href?: undefined; onClick: () => void }\n\n/** plain flows with the page, no surface; card is a bordered card; muted is a soft surface; accent is primary with inverted text. */\nexport type CtaBandVariant = \"plain\" | \"card\" | \"muted\" | \"accent\"\n\n/** row stacks on narrow screens and puts copy left / actions right from md up; stacked is always a centred column. */\nexport type CtaBandLayout = \"row\" | \"stacked\"\n\nexport interface CtaBandProps extends Omit<React.ComponentPropsWithoutRef<\"section\">, \"title\"> {\n  /** The headline, which also becomes the accessible name of the section through aria-labelledby. */\n  title: React.ReactNode\n  /** Optional label above the headline; coloured with primary on every variant except accent. */\n  eyebrow?: React.ReactNode\n  /** Optional one-sentence supporting line. */\n  description?: React.ReactNode\n  /** Optional small print under the actions — usually the reassurance (\"no credit card required\"). */\n  footnote?: React.ReactNode\n  /** The primary CTA. Required: a CTA band without a primary action has no reason to exist. */\n  primaryAction: CtaBandAction\n  /** Optional secondary CTA, rendered as an outlined button that always reads weaker than the primary. */\n  secondaryAction?: CtaBandAction\n  /** Visual variant, default \"card\". */\n  variant?: CtaBandVariant\n  /** Layout, default \"row\". */\n  layout?: CtaBandLayout\n  /** Heading level, default 2. On a landing page a CTA usually sits under an h2, so pass 3 or 4 to keep the outline honest. */\n  headingLevel?: 2 | 3 | 4\n}\n\n// Surfaces: semantic tokens only — accent pairs bg-primary with its matching text-primary-foreground\nconst SURFACE: Record<CtaBandVariant, string> = {\n  accent: \"rounded-xl bg-primary p-6 text-primary-foreground sm:p-8\",\n  card: \"rounded-xl border bg-card p-6 text-card-foreground sm:p-8\",\n  muted: \"rounded-xl bg-muted p-6 sm:p-8\",\n  plain: \"py-10\",\n}\n\n// Secondary text: text-muted-foreground is tuned for light surfaces, so on accent use the paired colour at lower opacity\nconst SUBDUED: Record<CtaBandVariant, string> = {\n  accent: \"text-primary-foreground/80\",\n  card: \"text-muted-foreground\",\n  muted: \"text-muted-foreground\",\n  plain: \"text-muted-foreground\",\n}\n\nconst EYEBROW: Record<CtaBandVariant, string> = {\n  accent: \"text-primary-foreground/80\",\n  card: \"text-primary\",\n  muted: \"text-primary\",\n  plain: \"text-primary\",\n}\n\nconst ACTION_BASE =\n  \"inline-flex w-full items-center justify-center rounded-lg px-5 py-2.5 text-sm font-medium \" +\n  \"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 \" +\n  \"motion-reduce:transition-none sm:w-auto\"\n\n// Primary CTA: every surface swaps its own ring-offset colour, or the gap inside the focus ring is painted the wrong colour\nconst PRIMARY_ACTION: Record<CtaBandVariant, string> = {\n  accent:\n    \"bg-primary-foreground text-primary hover:bg-primary-foreground/90 \" +\n    \"focus-visible:ring-primary-foreground focus-visible:ring-offset-primary\",\n  card: \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring focus-visible:ring-offset-card\",\n  muted: \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring focus-visible:ring-offset-muted\",\n  plain:\n    \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring focus-visible:ring-offset-background\",\n}\n\n// Secondary CTA: hover is a foreground/10 alpha layer. In the light theme muted, accent and\n// secondary resolve to the same value, so hover:bg-muted over a muted surface changes nothing at all.\nconst SECONDARY_ACTION: Record<CtaBandVariant, string> = {\n  accent:\n    \"border border-primary-foreground/50 text-primary-foreground hover:bg-primary-foreground/15 \" +\n    \"focus-visible:ring-primary-foreground focus-visible:ring-offset-primary\",\n  card: \"border hover:bg-foreground/10 focus-visible:ring-ring focus-visible:ring-offset-card\",\n  muted: \"border hover:bg-foreground/10 focus-visible:ring-ring focus-visible:ring-offset-muted\",\n  plain: \"border hover:bg-foreground/10 focus-visible:ring-ring focus-visible:ring-offset-background\",\n}\n\nfunction CtaAction({ action, className }: { action: CtaBandAction; className: string }) {\n  if (action.href !== undefined) {\n    return (\n      <a className={className} href={action.href} onClick={action.onClick}>\n        {action.label}\n      </a>\n    )\n  }\n  return (\n    <button className={cn(className, \"cursor-pointer\")} onClick={action.onClick} type=\"button\">\n      {action.label}\n    </button>\n  )\n}\n\nexport function CtaBand({\n  title,\n  eyebrow,\n  description,\n  footnote,\n  primaryAction,\n  secondaryAction,\n  variant = \"card\",\n  layout = \"row\",\n  headingLevel = 2,\n  className,\n  ...rest\n}: CtaBandProps) {\n  const titleId = React.useId()\n  const stacked = layout === \"stacked\"\n  const Heading = `h${headingLevel}` as \"h2\" | \"h3\" | \"h4\"\n\n  return (\n    <section aria-labelledby={titleId} className={cn(\"w-full\", SURFACE[variant], className)} {...rest}>\n      <div\n        className={cn(\n          \"flex flex-col gap-6\",\n          stacked ? \"items-center text-center\" : \"md:flex-row md:items-center md:justify-between md:gap-10\",\n        )}\n      >\n        <div className={cn(\"flex flex-col gap-2\", stacked ? \"items-center\" : \"md:max-w-2xl\")}>\n          {eyebrow && (\n            <p className={cn(\"text-xs font-medium tracking-wide uppercase\", EYEBROW[variant])}>{eyebrow}</p>\n          )}\n          <Heading className=\"text-2xl font-semibold tracking-tight text-balance sm:text-3xl\" id={titleId}>\n            {title}\n          </Heading>\n          {description && <p className={cn(\"text-base text-pretty\", SUBDUED[variant])}>{description}</p>}\n        </div>\n\n        <div className={cn(\"flex w-full flex-col gap-2 sm:w-auto\", stacked ? \"items-center\" : \"md:shrink-0\")}>\n          <div className=\"flex w-full flex-col gap-3 sm:w-auto sm:flex-row sm:items-center\">\n            <CtaAction action={primaryAction} className={cn(ACTION_BASE, PRIMARY_ACTION[variant])} />\n            {secondaryAction && (\n              <CtaAction action={secondaryAction} className={cn(ACTION_BASE, SECONDARY_ACTION[variant])} />\n            )}\n          </div>\n          {footnote && <p className={cn(\"text-xs\", SUBDUED[variant])}>{footnote}</p>}\n        </div>\n      </div>\n    </section>\n  )\n}\n\nexport default CtaBand\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}