{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cta-section",
  "type": "registry:block",
  "title": "CTA section",
  "description": "A call to action as a centered closing section, a split beside a setup card that completes itself, or a dismissible banner.",
  "categories": [
    "blocks"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/button.json",
    "https://irisui.dev/r/segmented-control.json",
    "https://irisui.dev/r/motion-tokens.json",
    "https://irisui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/registry/blocks/cta-section/cta-section-data.ts",
      "content": "export interface CtaAction {\n  label: string;\n  /** Renders a link. */\n  href?: string;\n  onClick?: () => void;\n  /** With no href or onClick, the button confirms in place with this label, which suits previews. */\n  confirmedLabel?: string;\n}\n\n/** Sample copy for notdocs, a workspace of markdown pages, tasks and decisions that lives in your git repository. */\nexport const ctaCopy = {\n  centered: {\n    title: \"Keep your docs where your code is\",\n    description:\n      \"Connect a repository, write the first spec, and let your agents edit the same files. Free for 1 seat.\",\n    primary: { label: \"Start for free\", confirmedLabel: \"Organization created\" },\n    secondary: { label: \"Compare plans\", confirmedLabel: \"Plans opened\" },\n    note: \"Solo €12/month, Team €39/month\",\n  },\n  split: {\n    title: \"Your project, set up before lunch\",\n    description: \"Connect a repository, bring your team in, and let agents work on the same markdown files you do.\",\n    primary: { label: \"Create an organization\", confirmedLabel: \"Organization created\" },\n    secondary: { label: \"Read the docs\", confirmedLabel: \"Docs opened\" },\n    points: [\n      \"Pages, tasks and decisions are markdown files\",\n      \"Agents edit over MCP and a CLI\",\n      \"One history, nothing to sync\",\n    ],\n  },\n  banner: {\n    title: \"The workspace fits a phone\",\n    description: \"Version 1.5.0 also adds print preview.\",\n    primary: { label: \"See the changelog\", confirmedLabel: \"Opened\" },\n  },\n} satisfies Record<\n  string,\n  { title: string; description: string; primary: CtaAction; secondary?: CtaAction; note?: string; points?: string[] }\n>;\n\n/** A flat initials avatar as an inline SVG, so the samples need no portrait files. */\nfunction initials(name: string) {\n  const letters = name\n    .split(\" \")\n    .map((part) => part[0])\n    .join(\"\");\n  const svg = `<svg class='iris-placeholder' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'><rect width='40' height='40' fill='#d3d9e3'/><text x='20' y='25' text-anchor='middle' font-family='system-ui,sans-serif' font-size='14' font-weight='600' fill='#4a5263'>${letters}</text></svg>`;\n  return `data:image/svg+xml,${encodeURIComponent(svg)}`;\n}\n\n/** Faces for the social proof line in the centered variant. */\nexport const ctaFaces = [\"Katerina Vlachou\", \"Stelios Antoniou\", \"Nefeli Kontou\", \"Tomasz Wrona\"].map(initials);\n\n/** Sample content for the split variant's setup card: a notdocs project being set up. */\nexport const ctaSetup = {\n  workspace: \"Launch site\",\n  steps: [\"Write the first spec\", \"Connect an agent\", \"Invite 4 teammates\"],\n  faces: [\"Nefeli Kontou\", \"Tomasz Wrona\", \"Clara Weiss\", \"Henrik Aas\"].map(initials),\n};\n",
      "type": "registry:component",
      "target": "@components/iris/blocks/cta-section/cta-section-data.ts"
    },
    {
      "path": "src/registry/blocks/cta-section/cta-section.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { AnimatePresence, animate, motion, useInView, useMotionValue, useReducedMotion } from \"motion/react\";\nimport { ArrowRight, Check, X } from \"lucide-react\";\nimport { Button } from \"../../button/button\";\nimport SegmentedControl from \"../../segmented-control/segmented-control\";\nimport { motionTokens } from \"../../lib/motion-tokens\";\nimport { cn } from \"../../lib/utils\";\nimport { ctaCopy, ctaFaces, ctaSetup, type CtaAction } from \"./cta-section-data\";\n\nexport type { CtaAction } from \"./cta-section-data\";\nexport type CtaVariant = \"centered\" | \"split\" | \"banner\";\n\nexport interface CtaSectionProps {\n  /** `centered` is a closing section, `split` sits beside a product visual, `banner` is one dismissible line. */\n  variant?: CtaVariant;\n  title?: string;\n  description?: string;\n  primaryAction?: CtaAction;\n  /** Not shown in the banner variant. Pass null to hide it. */\n  secondaryAction?: CtaAction | null;\n  /** Small print under the actions in the centered variant, such as \"No credit card required\". */\n  note?: string;\n  /** Faces beside the note in the centered variant. Pass an empty array to hide them. */\n  faces?: string[];\n  /** Short benefit lines in the split variant. */\n  points?: string[];\n  /** Replaces the split variant's setup card. */\n  visual?: ReactNode;\n  /** Banner only: shows a dismiss button and calls this after it closes. */\n  onDismiss?: () => void;\n  className?: string;\n}\n\ntype Bezier = [number, number, number, number];\nconst enter = [...motionTokens.ease.enter] as Bezier;\nconst standard = [...motionTokens.ease.standard] as Bezier;\n\n/* Container breakpoints of the `cta` container, matching the old 860 px and 560 px steps. */\nconst title =\n  \"m-0 font-display text-[length:clamp(1.875rem,1rem_+_3.4cqi,var(--text-4xl))] font-medium leading-display tracking-display text-balance\";\nconst description = \"m-0 text-lg leading-body text-ink-2 text-pretty @max-[560px]/cta:text-base\";\nconst actions = \"flex flex-wrap gap-3 @max-[560px]/cta:w-full @max-[560px]/cta:flex-col @max-[560px]/cta:*:w-full\";\n/** The arrow leans forward while its action is hovered. */\nconst arrowClass =\n  \"transition-transform duration-fast ease-standard group-hover/action:translate-x-[3px] motion-reduce:transition-none\";\n/** Faces overlap a little and sit on a ring of the surface they rest on. */\nconst faceImage = \"-ml-1.5 block rounded-full border-2 border-surface object-cover\";\n\n/** A link, a callback, or (with neither) a button that confirms in place, so previews always answer a click. */\nfunction Action({\n  action,\n  variant,\n  size = \"lg\",\n  arrow,\n}: {\n  action: CtaAction;\n  variant: \"primary\" | \"secondary\";\n  size?: \"sm\" | \"md\" | \"lg\";\n  arrow?: boolean;\n}) {\n  const [confirmed, setConfirmed] = useState(false);\n  if (action.href) {\n    return (\n      <a\n        className={cn(\n          \"group/action inline-flex h-control-lg items-center justify-center gap-2 rounded-button border border-transparent px-5 text-sm/[inherit] font-medium no-underline\",\n          \"[transition:background-color_var(--duration-fast)_var(--ease-standard),border-color_var(--duration-fast)_var(--ease-standard),transform_var(--duration-instant)_var(--ease-standard)] active:[transform:scale(.97)]\",\n          size === \"sm\" && \"h-control-sm px-3.5\",\n          variant === \"primary\" ? \"bg-ink text-page\" : \"border-line bg-surface text-ink\",\n        )}\n        data-variant={variant}\n        data-size={size}\n        href={action.href}\n      >\n        {action.label}\n        {arrow && <ArrowRight className={arrowClass} size={16} strokeWidth={2} aria-hidden=\"true\" />}\n      </a>\n    );\n  }\n  const simulated = !action.onClick;\n  return (\n    <Button\n      className=\"group/action\"\n      variant={variant}\n      size={size}\n      aria-live={simulated ? \"polite\" : undefined}\n      onClick={() => {\n        if (action.onClick) action.onClick();\n        else setConfirmed((value) => !value);\n      }}\n    >\n      {confirmed ? (\n        <>\n          <Check size={16} strokeWidth={2.25} aria-hidden=\"true\" />\n          {action.confirmedLabel ?? action.label}\n        </>\n      ) : (\n        <>\n          {action.label}\n          {arrow && <ArrowRight className={arrowClass} size={16} strokeWidth={2} aria-hidden=\"true\" />}\n        </>\n      )}\n    </Button>\n  );\n}\n\n/** The setup card's two status words share one grid cell and cross-fade with a small rise. */\nconst status = (on: boolean) =>\n  cn(\n    \"[grid-area:1/1] [transition:opacity_var(--duration-fast)_var(--ease-standard),transform_var(--duration-standard)_var(--ease-enter),filter_var(--duration-fast)_var(--ease-standard)] motion-reduce:transition-none\",\n    on ? \"opacity-100 [filter:blur(0)] [transform:none]\" : \"opacity-0 [filter:blur(2px)] [transform:translateY(4px)]\",\n  );\n\n/** A setup card that ticks through its steps once it scrolls into view. */\nfunction SetupVisual({ reduced }: { reduced: boolean }) {\n  const ref = useRef<HTMLDivElement>(null);\n  const inView = useInView(ref, { once: true, amount: 0.5 });\n  const total = ctaSetup.steps.length;\n  const [step, setStep] = useState(0);\n  const shown = reduced ? total : step;\n  const ready = shown === total;\n\n  useEffect(() => {\n    if (!inView || reduced) return;\n    const timers = ctaSetup.steps.map((_, index) => window.setTimeout(() => setStep(index + 1), 520 + index * 620));\n    return () => timers.forEach(window.clearTimeout);\n  }, [inView, reduced]);\n\n  return (\n    <div\n      ref={ref}\n      className=\"w-[min(100%,360px)] overflow-hidden rounded-panel border border-line bg-surface shadow-raised\"\n      aria-hidden=\"true\"\n      data-done={ready ? \"\" : undefined}\n    >\n      <div className=\"flex items-center gap-2.5 px-[18px] pt-4 pb-3.5\">\n        <span className=\"grid size-7 flex-none place-items-center rounded-control bg-ink text-sm/[inherit] font-medium text-page\">\n          {ctaSetup.workspace.charAt(0)}\n        </span>\n        <span className=\"text-sm/[inherit] font-medium\">{ctaSetup.workspace}</span>\n        <span className=\"ml-auto inline-grid justify-items-end text-xs/[inherit] text-ink-3\">\n          <span className={status(!ready)} data-shown={!ready ? \"\" : undefined}>\n            Setting up\n          </span>\n          <span className={cn(status(ready), ready && \"font-medium text-green\")} data-shown={ready ? \"\" : undefined}>\n            Ready\n          </span>\n        </span>\n      </div>\n      <div className=\"mx-[18px] h-0.5 overflow-hidden rounded-[1px] bg-line\">\n        <span\n          className=\"block h-full origin-left bg-green transition-transform duration-considered ease-(--ease-in-out) motion-reduce:transition-none\"\n          style={{ transform: `scaleX(${shown / total})` }}\n        />\n      </div>\n      <ol className=\"m-0 grid list-none gap-0.5 px-2 pt-2.5 pb-3\">\n        {ctaSetup.steps.map((label, index) => {\n          const done = index < shown;\n          const active = index === shown;\n          return (\n            <li\n              key={label}\n              className={cn(\n                \"flex min-h-11 items-center gap-3 rounded-[14px] px-2.5 text-sm/[inherit] transition-[color,background-color] duration-standard ease-standard motion-reduce:transition-none\",\n                active ? \"bg-hover text-ink\" : done ? \"text-ink\" : \"text-ink-3\",\n              )}\n              data-done={done ? \"\" : undefined}\n              data-active={active ? \"\" : undefined}\n            >\n              <span\n                className={cn(\n                  \"relative grid size-5 flex-none place-items-center rounded-full border-[1.5px] transition-[border-color,background-color,color] duration-standard ease-standard motion-reduce:transition-none\",\n                  done\n                    ? \"border-solid border-green bg-green text-page\"\n                    : active\n                      ? \"border-solid border-ink-3 text-transparent\"\n                      : \"border-dashed border-line-strong text-transparent\",\n                )}\n              >\n                <Check\n                  className={cn(\n                    \"[transition:transform_var(--duration-spring)_var(--ease-spring),opacity_var(--duration-fast)_var(--ease-standard)] motion-reduce:transition-none\",\n                    done ? \"opacity-100 [transform:scale(1)]\" : \"opacity-0 [transform:scale(.5)]\",\n                  )}\n                  size={12}\n                  strokeWidth={2.75}\n                />\n              </span>\n              <span className=\"min-w-0 truncate\">{label}</span>\n              {index === total - 1 && (\n                <span className=\"ml-auto flex pl-1.5\">\n                  {ctaSetup.faces.map((src, face) => (\n                    // oxlint-disable-next-line nextjs/no-img-element -- tiny inline SVG avatars; next/image adds nothing for data URIs\n                    <img\n                      key={src}\n                      className={cn(\n                        faceImage,\n                        \"size-[22px] [transition:opacity_var(--duration-standard)_var(--ease-standard),transform_var(--duration-spring)_var(--ease-spring)] motion-reduce:transition-none\",\n                        done || active\n                          ? \"opacity-100 [transform:none]\"\n                          : \"opacity-0 [transform:translateX(-6px)_scale(.9)]\",\n                      )}\n                      src={src}\n                      alt=\"\"\n                      width={22}\n                      height={22}\n                      style={{ transitionDelay: `${face * 60}ms` }}\n                    />\n                  ))}\n                </span>\n              )}\n            </li>\n          );\n        })}\n      </ol>\n    </div>\n  );\n}\n\nfunction Faces({ faces }: { faces: string[] }) {\n  if (!faces.length) return null;\n  return (\n    <span className=\"flex pl-1.5\" aria-hidden=\"true\">\n      {faces.slice(0, 4).map((src) => (\n        // oxlint-disable-next-line nextjs/no-img-element -- avatar URLs come from the consumer and may be on any origin, which next/image would reject\n        <img key={src} className={cn(faceImage, \"size-[26px]\")} src={src} alt=\"\" width={26} height={26} />\n      ))}\n    </span>\n  );\n}\n\n/**\n * A call to action in three shapes: a centered closing section, a split layout beside a setup card that completes\n * itself in view, and a compact banner that closes its gap when dismissed.\n */\nexport const CtaSection = forwardRef<HTMLElement, CtaSectionProps>(function CtaSection(\n  {\n    variant = \"centered\",\n    title: titleProp,\n    description: descriptionProp,\n    primaryAction,\n    secondaryAction,\n    note,\n    faces = ctaFaces,\n    points,\n    visual,\n    onDismiss,\n    className,\n  },\n  ref,\n) {\n  const id = useId();\n  const reduced = !!useReducedMotion();\n  const [open, setOpen] = useState(true);\n  const copy = ctaCopy[variant];\n  const heading = titleProp ?? copy.title;\n  const text = descriptionProp ?? copy.description;\n  const primary = primaryAction ?? copy.primary;\n  const secondary =\n    secondaryAction === null ? null : (secondaryAction ?? (\"secondary\" in copy ? copy.secondary : undefined));\n  const classes = cn(\"@container/cta bg-page font-body tracking-body text-ink\", className);\n\n  if (variant === \"banner\") {\n    return (\n      <section ref={ref} className={classes} data-variant=\"banner\" aria-labelledby={`${id}-title`}>\n        <AnimatePresence initial={false} onExitComplete={onDismiss}>\n          {open && (\n            <motion.div\n              key=\"banner\"\n              className=\"overflow-hidden\"\n              exit={\n                reduced\n                  ? { opacity: 0, transition: { duration: motionTokens.duration.instant } }\n                  : {\n                      opacity: 0,\n                      height: 0,\n                      transition: {\n                        height: motionTokens.spring.smooth,\n                        opacity: { duration: motionTokens.duration.fast, ease: standard },\n                      },\n                    }\n              }\n            >\n              <div className=\"mx-auto max-w-[calc(1120px_+_2*var(--space-8))] px-8 py-6 @max-[560px]/cta:p-4\">\n                <div className=\"flex items-center justify-between gap-4 rounded-[20px] border border-line bg-surface py-2.5 pr-2.5 pl-5 @max-[560px]/cta:flex-col @max-[560px]/cta:items-start @max-[560px]/cta:p-4\">\n                  <p className=\"m-0 flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm leading-body\">\n                    <strong id={`${id}-title`} className=\"font-medium\">\n                      {heading}\n                    </strong>\n                    <span className=\"text-ink-2\">{text}</span>\n                  </p>\n                  <div className=\"flex flex-none items-center gap-1 @max-[560px]/cta:w-full @max-[560px]/cta:justify-between\">\n                    <Action action={primary} variant=\"primary\" size=\"sm\" arrow />\n                    {onDismiss !== undefined && (\n                      <button\n                        type=\"button\"\n                        className=\"grid size-control-sm cursor-pointer place-items-center rounded-button border-0 bg-transparent text-ink-3 [-webkit-tap-highlight-color:transparent] [transition:background-color_var(--duration-fast)_var(--ease-standard),color_var(--duration-fast)_var(--ease-standard),transform_var(--duration-instant)_var(--ease-standard)] hover:bg-hover hover:text-ink active:[transform:scale(.94)]\"\n                        aria-label=\"Dismiss\"\n                        onClick={() => setOpen(false)}\n                      >\n                        <X size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n                      </button>\n                    )}\n                  </div>\n                </div>\n              </div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </section>\n    );\n  }\n\n  if (variant === \"split\") {\n    const list = points ?? ctaCopy.split.points;\n    return (\n      <section ref={ref} className={classes} data-variant=\"split\" aria-labelledby={`${id}-title`}>\n        <div className=\"mx-auto grid max-w-[1120px] grid-cols-[minmax(0,1fr)_minmax(0,1fr)] items-center gap-16 px-8 py-24 @max-[860px]/cta:grid-cols-[minmax(0,1fr)] @max-[860px]/cta:gap-10 @max-[860px]/cta:py-16 @max-[560px]/cta:px-4 @max-[560px]/cta:py-12\">\n          <div className=\"grid justify-items-start gap-4\">\n            <h2 id={`${id}-title`} className={title}>\n              {heading}\n            </h2>\n            <p className={description}>{text}</p>\n            {list.length > 0 && (\n              <ul className=\"mt-2 mb-0 grid list-none gap-2 p-0\">\n                {list.map((point) => (\n                  <li key={point} className=\"flex items-center gap-3 text-sm/[inherit] text-ink-2\">\n                    <Check className=\"flex-none text-green\" size={16} strokeWidth={2} aria-hidden=\"true\" />\n                    {point}\n                  </li>\n                ))}\n              </ul>\n            )}\n            <div className={cn(actions, \"mt-4\")}>\n              <Action action={primary} variant=\"primary\" arrow />\n              {secondary && <Action action={secondary} variant=\"secondary\" />}\n            </div>\n          </div>\n          <motion.div\n            className=\"grid min-h-[380px] place-items-center rounded-surface border border-line bg-[color-mix(in_oklab,var(--hover)_55%,var(--surface))] p-8 @max-[860px]/cta:min-h-[320px] @max-[560px]/cta:rounded-[28px] @max-[560px]/cta:p-5\"\n            initial={reduced ? false : { opacity: 0, y: 24 }}\n            whileInView={{ opacity: 1, y: 0 }}\n            viewport={{ once: true, amount: 0.35 }}\n            transition={{ duration: motionTokens.duration.considered, ease: enter }}\n          >\n            {visual ?? <SetupVisual reduced={reduced} />}\n          </motion.div>\n        </div>\n      </section>\n    );\n  }\n\n  const small = note ?? ctaCopy.centered.note;\n  return (\n    <section ref={ref} className={classes} data-variant=\"centered\" aria-labelledby={`${id}-title`}>\n      <div className=\"mx-auto max-w-[1120px] px-8 py-16 @max-[560px]/cta:px-4 @max-[560px]/cta:py-10\">\n        <div className=\"grid justify-items-center gap-4 rounded-surface border border-line bg-[color-mix(in_oklab,var(--hover)_45%,var(--surface))] px-8 py-24 text-center @max-[560px]/cta:rounded-[28px] @max-[560px]/cta:px-5 @max-[560px]/cta:py-16\">\n          <h2 id={`${id}-title`} className={cn(title, \"max-w-[18ch]\")}>\n            {heading}\n          </h2>\n          <p className={cn(description, \"max-w-[34rem]\")}>{text}</p>\n          <div className={cn(actions, \"mt-4 justify-center\")}>\n            <Action action={primary} variant=\"primary\" arrow />\n            {secondary && <Action action={secondary} variant=\"secondary\" />}\n          </div>\n          {(small || faces.length > 0) && (\n            <p className=\"mt-3 mb-0 inline-flex flex-wrap items-center justify-center gap-3 text-sm/[inherit] text-ink-3\">\n              <Faces faces={faces} />\n              {small}\n            </p>\n          )}\n        </div>\n      </div>\n    </section>\n  );\n});\n\nCtaSection.displayName = \"CtaSection\";\n\nconst variantOptions = [\n  { value: \"centered\", label: \"Centered\" },\n  { value: \"split\", label: \"Split\" },\n  { value: \"banner\", label: \"Banner\" },\n];\n\n/** Springs the frame to its content's height after a discrete change (a new layout, a dismissed banner), so the preview never snaps.\n *  Other resizes, such as the banner's own collapse or a parent reflow, follow at once so nothing lags. */\nfunction useFrameHeight(key: string, reduced: boolean) {\n  const content = useRef<HTMLDivElement>(null);\n  const height = useMotionValue<number | \"auto\">(\"auto\");\n  const lastKey = useRef(key),\n    armedUntil = useRef(0);\n  useLayoutEffect(() => {\n    if (lastKey.current === key) return;\n    lastKey.current = key;\n    armedUntil.current = performance.now() + 700;\n  }, [key]);\n  useEffect(() => {\n    const node = content.current;\n    if (!node || typeof ResizeObserver === \"undefined\") return;\n    let measured = false;\n    const observer = new ResizeObserver(() => {\n      const next = node.offsetHeight;\n      if (!measured || reduced || performance.now() > armedUntil.current) {\n        measured = next > 0;\n        height.jump(next || \"auto\");\n        return;\n      }\n      animate(height, next, motionTokens.spring.smooth);\n    });\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, [height, reduced]);\n  return { content, height };\n}\n\n/** Preview: all three shapes. Buttons confirm in place; nothing is sent. */\nexport function CtaSectionBlock() {\n  const reduced = !!useReducedMotion();\n  const [variant, setVariant] = useState<CtaVariant>(\"centered\");\n  const [dismissed, setDismissed] = useState(false);\n  const showRestore = variant === \"banner\" && dismissed;\n  const view = showRestore ? \"restore\" : variant;\n  const { content, height } = useFrameHeight(view, reduced);\n  // The first layout appears as is; later layouts fade in while the frame springs to their height.\n  const [changed, setChanged] = useState(false);\n  return (\n    <div className=\"grid w-full justify-items-center gap-4\">\n      <SegmentedControl\n        label=\"Call to action layout\"\n        options={variantOptions}\n        value={variant}\n        onValueChange={(next) => {\n          setChanged(true);\n          setVariant(next as CtaVariant);\n          setDismissed(false);\n        }}\n      />\n      <motion.div className=\"w-full overflow-hidden rounded-[20px] border border-line bg-page\" style={{ height }}>\n        <div ref={content}>\n          <motion.div\n            key={view}\n            initial={reduced || !changed ? false : { opacity: 0 }}\n            animate={{ opacity: 1 }}\n            transition={{ duration: motionTokens.duration.standard, ease: standard }}\n          >\n            {showRestore ? (\n              <div className=\"grid min-h-[104px] place-items-center p-6\">\n                <Button variant=\"secondary\" size=\"sm\" onClick={() => setDismissed(false)}>\n                  Show the banner again\n                </Button>\n              </div>\n            ) : (\n              <CtaSection\n                variant={variant}\n                onDismiss={\n                  variant === \"banner\"\n                    ? () => {\n                        setChanged(true);\n                        setDismissed(true);\n                      }\n                    : undefined\n                }\n              />\n            )}\n          </motion.div>\n        </div>\n      </motion.div>\n    </div>\n  );\n}\n\nexport default CtaSectionBlock;\n",
      "type": "registry:component",
      "target": "@components/iris/blocks/cta-section/cta-section.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/cta-section",
  "meta": {
    "kind": "block",
    "group": "Blocks",
    "export": "CtaSection",
    "docs": "https://irisui.dev/components/cta-section"
  }
}
