{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "comparison-table",
  "type": "registry:block",
  "title": "Comparison table",
  "description": "An us versus them table with a sticky header, a highlighted column, and a stacked phone view.",
  "categories": [
    "blocks"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/motion-tokens.json",
    "https://irisui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/registry/blocks/comparison-table/comparison-table-data.ts",
      "content": "/** Sample comparison: CapyDB against generic kinds of Postgres hosting, not real products. */\n\n/** true is included, false is not, \"partial\" is limited, and a string prints as text (a limit, a price). */\nexport type ComparisonValue = boolean | string | { value: boolean | string; note?: string };\n\nexport type ComparisonColumn = {\n  id: string;\n  name: string;\n  /** A short line under the name, such as a price. */\n  caption?: string;\n  /** Your product. It gets the tinted band and stays visible on phones. */\n  highlight?: boolean;\n};\n\nexport type ComparisonRow = {\n  id: string;\n  feature: string;\n  /** Secondary text under the feature name. */\n  hint?: string;\n  values: Record<string, ComparisonValue>;\n};\n\nexport type ComparisonSection = { id: string; title: string; rows: ComparisonRow[] };\n\nexport const comparisonColumns: ComparisonColumn[] = [\n  { id: \"capydb\", name: \"CapyDB\", caption: \"$15.99/mo on Ship\", highlight: true },\n  { id: \"vps\", name: \"Self-managed VPS\", caption: \"You run it\" },\n  { id: \"shared\", name: \"Shared Postgres\", caption: \"Shared resources\" },\n  { id: \"cloud\", name: \"Big-cloud database\", caption: \"Priced per resource\" },\n];\n\nexport const comparisonSections: ComparisonSection[] = [\n  {\n    id: \"databases\",\n    title: \"Databases\",\n    rows: [\n      {\n        id: \"cell\",\n        feature: \"Own database per project\",\n        hint: \"Every project runs in its own isolated cell\",\n        values: { capydb: true, vps: true, shared: false, cloud: true },\n      },\n      {\n        id: \"versions\",\n        feature: \"Postgres versions\",\n        values: { capydb: \"16 to 19\", vps: \"Any\", shared: \"One\", cloud: \"Most\" },\n      },\n      {\n        id: \"pooled\",\n        feature: \"Pooled connection string\",\n        values: {\n          capydb: true,\n          vps: { value: \"partial\", note: \"Set it up yourself\" },\n          shared: false,\n          cloud: { value: \"partial\", note: \"Extra service\" },\n        },\n      },\n      {\n        id: \"kv\",\n        feature: \"Built-in K/V\",\n        values: { capydb: true, vps: false, shared: false, cloud: { value: \"partial\", note: \"Separate product\" } },\n      },\n    ],\n  },\n  {\n    id: \"building\",\n    title: \"Building\",\n    rows: [\n      {\n        id: \"previews\",\n        feature: \"Preview databases\",\n        hint: \"Clone production for a pull request\",\n        values: { capydb: true, vps: false, shared: false, cloud: { value: \"partial\", note: \"Manual snapshots\" } },\n      },\n      {\n        id: \"ephemeral\",\n        feature: \"Ephemeral databases\",\n        hint: \"Claim one within 72 h or it goes away\",\n        values: { capydb: true, vps: false, shared: false, cloud: false },\n      },\n      {\n        id: \"advisor\",\n        feature: \"Index advisor\",\n        values: { capydb: true, vps: false, shared: false, cloud: { value: \"partial\", note: \"Paid add on\" } },\n      },\n      {\n        id: \"agents\",\n        feature: \"Set up by a coding agent\",\n        hint: \"One approval, then the agent connects\",\n        values: { capydb: true, vps: false, shared: false, cloud: false },\n      },\n    ],\n  },\n  {\n    id: \"recovery\",\n    title: \"Recovery\",\n    rows: [\n      {\n        id: \"backups\",\n        feature: \"Nightly backups\",\n        values: { capydb: true, vps: { value: \"partial\", note: \"Your scripts\" }, shared: true, cloud: true },\n      },\n      {\n        id: \"pitr\",\n        feature: \"Point-in-time restore\",\n        values: { capydb: \"7 days\", vps: false, shared: false, cloud: true },\n      },\n      {\n        id: \"pause\",\n        feature: \"Pause when idle\",\n        hint: \"Wakes in about 308 ms\",\n        values: { capydb: true, vps: false, shared: false, cloud: { value: \"partial\", note: \"Some tiers\" } },\n      },\n    ],\n  },\n  {\n    id: \"safety\",\n    title: \"Safety\",\n    rows: [\n      {\n        id: \"tls\",\n        feature: \"Verified TLS\",\n        values: { capydb: true, vps: { value: \"partial\", note: \"Your certificates\" }, shared: true, cloud: true },\n      },\n      {\n        id: \"confirm\",\n        feature: \"Confirmation before destructive work\",\n        values: { capydb: true, vps: false, shared: false, cloud: false },\n      },\n      {\n        id: \"eu\",\n        feature: \"Hosted in the EU\",\n        values: {\n          capydb: true,\n          vps: true,\n          shared: { value: \"partial\", note: \"Depends on host\" },\n          cloud: { value: \"partial\", note: \"Pick a region\" },\n        },\n      },\n    ],\n  },\n];\n",
      "type": "registry:component",
      "target": "@components/iris/blocks/comparison-table/comparison-table-data.ts"
    },
    {
      "path": "src/registry/blocks/comparison-table/comparison-table.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport type { CSSProperties } from \"react\";\nimport { AnimatePresence, LayoutGroup, motion, useReducedMotion } from \"motion/react\";\nimport { Check, Minus } from \"lucide-react\";\nimport { motionTokens } from \"../../lib/motion-tokens\";\nimport { comparisonColumns, comparisonSections } from \"./comparison-table-data\";\nimport type { ComparisonColumn, ComparisonSection, ComparisonValue } from \"./comparison-table-data\";\nimport { cn } from \"../../lib/utils\";\n\nexport type { ComparisonColumn, ComparisonRow, ComparisonSection, ComparisonValue } from \"./comparison-table-data\";\n\nexport interface ComparisonTableProps {\n  title?: string;\n  description?: string;\n  columns?: ComparisonColumn[];\n  sections?: ComparisonSection[];\n  /** Hide rows where every visible column has the same value (controlled). */\n  differencesOnly?: boolean;\n  defaultDifferencesOnly?: boolean;\n  onDifferencesOnlyChange?: (value: boolean) => void;\n  /** Competitor shown beside yours in the stacked phone layout (controlled). */\n  compareWith?: string;\n  onCompareWithChange?: (id: string) => void;\n  /** Call to action in your column. */\n  cta?: { label: string; href?: string; onClick?: () => void; doneLabel?: string };\n  /** Offset for the sticky header, such as the height of a fixed site header. Defaults to 0. */\n  stickyTop?: number;\n  /** Caps the table height and scrolls it inside the block, with the header sticking to its top. */\n  maxHeight?: number | string;\n  /** Width below which the table stacks into a two column comparison. Defaults to 640. */\n  stackBelow?: number;\n  className?: string;\n}\n\nconst snappy = motionTokens.spring.snappy;\nconst smooth = motionTokens.spring.smooth;\nconst morph = motionTokens.spring.morph;\n\n/** Visually hidden but read by screen readers. */\nconst srOnly = \"absolute h-px w-px overflow-hidden whitespace-nowrap [clip:rect(0_0_0_0)]\";\n/** The included mark: a soft disc with a drawn tick, filled with the control colour in your column. */\nconst checkMark = (own?: boolean) =>\n  cn(\n    \"[&_circle]:fill-[color-mix(in_oklab,var(--ink)_8%,transparent)] [&_path]:fill-none [&_path]:stroke-ink [&_path]:[stroke-width:1.9] [&_path]:[stroke-linecap:round] [&_path]:[stroke-linejoin:round]\",\n    own && \"[&_circle]:fill-control-on [&_path]:stroke-control-glyph\",\n  );\nconst partialMark = \"[&_circle]:fill-none [&_circle]:stroke-ink-3 [&_circle]:[stroke-width:1.5] [&_path]:fill-ink-3\";\nconst crossMark = \"text-ink-3 opacity-70\";\n/** Grid tracks come from `--grid` on the table, which narrows at the container breakpoints. */\nconst gridRow = \"grid grid-cols-[var(--grid)]\";\n/** Every cell of a body row: a hairline under it and a faint wash while the row is hovered. */\nconst rowCell =\n  \"border-b border-line-soft transition-[background-color] duration-fast ease-standard group-hover/row:bg-[color-mix(in_oklab,var(--ink)_2.5%,transparent)]\";\n\nfunction useControllable<T>(value: T | undefined, initial: T, onChange?: (next: T) => void) {\n  const [inner, setInner] = useState(initial);\n  const current = value !== undefined ? value : inner;\n  const set = (next: T) => {\n    if (value === undefined) setInner(next);\n    onChange?.(next);\n  };\n  return [current, set] as const;\n}\n\nfunction normalize(value: ComparisonValue | undefined) {\n  if (value === undefined) return { value: false as const, note: undefined };\n  if (typeof value === \"object\") return value;\n  return { value, note: undefined };\n}\nconst ctaClass =\n  \"inline-flex h-control-sm max-w-[calc(100%-var(--space-2))] cursor-pointer items-center justify-center overflow-hidden rounded-button border-0 bg-ink px-4 text-sm/[inherit] font-medium whitespace-nowrap text-page no-underline [-webkit-tap-highlight-color:transparent]\";\nconst keyOf = (value: ComparisonValue | undefined) => {\n  const item = normalize(value);\n  return `${item.value}`;\n};\n\nfunction Mark({\n  value,\n  own,\n  index,\n  reduced,\n}: {\n  value: ComparisonValue | undefined;\n  own: boolean;\n  index: number;\n  reduced: boolean;\n}) {\n  const { value: v, note } = normalize(value);\n  if (typeof v === \"string\" && v !== \"partial\")\n    return (\n      <span className={cn(\"text-sm/[inherit] font-medium tabular-nums\", !own && \"font-normal text-ink-2\")}>{v}</span>\n    );\n  if (v === true)\n    return (\n      <span className=\"inline-grid justify-items-center gap-0.5\">\n        <svg\n          className={checkMark(own)}\n          data-own={own || undefined}\n          width=\"22\"\n          height=\"22\"\n          viewBox=\"0 0 22 22\"\n          aria-hidden=\"true\"\n        >\n          <circle cx=\"11\" cy=\"11\" r=\"10\" />\n          <motion.path\n            d=\"M6.6 11.3l3 3 5.9-6.4\"\n            initial={reduced ? false : { pathLength: 0 }}\n            whileInView={{ pathLength: 1 }}\n            viewport={{ once: true, amount: 1 }}\n            transition={{\n              duration: motionTokens.duration.considered,\n              ease: [...motionTokens.ease.enter],\n              delay: own ? 0.06 + Math.min(index, 12) * 0.035 : 0,\n            }}\n          />\n        </svg>\n        <span className={srOnly}>Included</span>\n      </span>\n    );\n  if (v === \"partial\")\n    return (\n      <span className=\"inline-grid justify-items-center gap-0.5\">\n        <svg className={partialMark} width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">\n          <circle cx=\"11\" cy=\"11\" r=\"9.25\" />\n          <path d=\"M11 1.75a9.25 9.25 0 0 1 0 18.5z\" />\n        </svg>\n        <span className={srOnly}>Partial</span>\n        {note && <span className=\"max-w-[12ch] text-xs leading-body text-ink-3\">{note}</span>}\n      </span>\n    );\n  return (\n    <span className=\"inline-grid justify-items-center gap-0.5\">\n      <Minus className={crossMark} size={18} strokeWidth={1.75} aria-hidden=\"true\" />\n      <span className={srOnly}>Not included</span>\n    </span>\n  );\n}\n\nexport function ComparisonTable({\n  title = \"How CapyDB compares\",\n  description = \"Every project gets its own database cell, with previews, backups and point-in-time restore built in.\",\n  columns = comparisonColumns,\n  sections = comparisonSections,\n  differencesOnly: differencesProp,\n  defaultDifferencesOnly = false,\n  onDifferencesOnlyChange,\n  compareWith: compareProp,\n  onCompareWithChange,\n  cta,\n  stickyTop = 0,\n  maxHeight,\n  stackBelow = 640,\n  className,\n}: ComparisonTableProps) {\n  const reduced = Boolean(useReducedMotion());\n  const uid = useId();\n  const rootRef = useRef<HTMLElement>(null);\n  const [narrow, setNarrow] = useState(false);\n  const [differencesOnly, setDifferencesOnly] = useControllable(\n    differencesProp,\n    defaultDifferencesOnly,\n    onDifferencesOnlyChange,\n  );\n  const own = columns.find((column) => column.highlight) ?? columns[0]!;\n  const others = columns.filter((column) => column !== own);\n  const [compareWith, setCompareWith] = useControllable(compareProp, others[0]?.id ?? own.id, onCompareWithChange);\n  const [ctaDone, setCtaDone] = useState(false);\n\n  useEffect(() => {\n    const node = rootRef.current;\n    if (!node) return;\n    const observer = new ResizeObserver(([entry]) => {\n      if (entry) setNarrow(entry.contentRect.width < stackBelow);\n    });\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, [stackBelow]);\n\n  const visible = narrow ? columns.filter((column) => column === own || column.id === compareWith) : columns;\n  const differs = (values: Record<string, ComparisonValue>) =>\n    new Set(visible.map((column) => keyOf(values[column.id]))).size > 1;\n  const gridStyle = { \"--columns\": visible.length, \"--sticky-top\": `${stickyTop}px` } as CSSProperties;\n  let rowIndex = 0;\n\n  const header = (\n    <div role=\"row\" className={cn(gridRow, \"border-b border-line\")}>\n      <div role=\"columnheader\" className=\"min-h-[84px] @max-[640px]/compare:min-h-[72px]\">\n        <span className={srOnly}>Feature</span>\n      </div>\n      {visible.map((column) => (\n        // oxlint-disable-next-line jsx-a11y/control-has-associated-label -- header text is rendered inside AnimatePresence below; ARIA table built from CSS grid rows that animate their height; table elements cannot take this layout\n        <div\n          key={column === own ? \"own\" : narrow ? \"other\" : column.id}\n          role=\"columnheader\"\n          className={cn(\n            \"relative grid min-h-[84px] items-end justify-items-center overflow-hidden px-2 py-4 text-center @max-[640px]/compare:min-h-[72px]\",\n            column === own && \"rounded-t-[20px] bg-(--band)\",\n          )}\n          data-own={column === own || undefined}\n        >\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            <motion.span\n              key={column.id}\n              className=\"grid gap-0.5\"\n              initial={reduced ? { opacity: 0 } : { opacity: 0, y: 8 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={reduced ? { opacity: 0 } : { opacity: 0, y: -8 }}\n              transition={smooth}\n            >\n              <span\n                className={cn(\"text-base leading-body font-medium\", column === own && \"text-accent-ink dark:text-ink\")}\n              >\n                {column.name}\n              </span>\n              {column.caption && <span className=\"text-xs leading-body text-ink-3 tabular-nums\">{column.caption}</span>}\n            </motion.span>\n          </AnimatePresence>\n        </div>\n      ))}\n    </div>\n  );\n\n  return (\n    <section\n      ref={rootRef}\n      className={cn(\"@container/compare w-full min-w-0 bg-page font-body tracking-body text-ink\", className)}\n      aria-labelledby={`${uid}-title`}\n      data-narrow={narrow || undefined}\n    >\n      <div className=\"mx-auto grid max-w-[1080px] gap-8 px-6 py-16 @max-[640px]/compare:gap-6 @max-[640px]/compare:px-4 @max-[640px]/compare:py-12\">\n        <header className=\"flex flex-wrap items-end justify-between gap-6\">\n          <div className=\"grid max-w-[560px] gap-3\">\n            <h2\n              id={`${uid}-title`}\n              className=\"m-0 font-display text-[length:clamp(var(--text-2xl),1rem_+_3cqi,var(--text-4xl))] leading-display font-medium tracking-display text-balance\"\n            >\n              {title}\n            </h2>\n            {description && <p className=\"m-0 text-base leading-body text-ink-2 text-pretty\">{description}</p>}\n          </div>\n          <label className=\"group/toggle relative inline-flex cursor-pointer items-center gap-3 text-sm/[inherit] font-medium text-ink-2 select-none [-webkit-tap-highlight-color:transparent] has-checked:text-ink\">\n            <span>Only differences</span>\n            <input\n              className=\"peer absolute inset-0 m-0 cursor-pointer opacity-0\"\n              type=\"checkbox\"\n              role=\"switch\"\n              aria-checked={differencesOnly}\n              checked={differencesOnly}\n              onChange={(event) => setDifferencesOnly(event.target.checked)}\n            />\n            <span\n              className=\"relative h-[22px] w-[38px] flex-none rounded-full bg-control-track transition-[background-color] duration-fast ease-standard peer-checked:bg-control-on group-hover/toggle:peer-not-checked:bg-control-track-hover\"\n              aria-hidden=\"true\"\n            >\n              <span className=\"absolute top-0.5 left-0.5 size-[18px] rounded-full bg-control-thumb shadow-thumb [transition:transform_var(--duration-spring)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-standard)] group-has-checked/toggle:bg-control-thumb-on group-has-checked/toggle:[transform:translateX(16px)] motion-reduce:transition-none\" />\n            </span>\n          </label>\n        </header>\n\n        {narrow && others.length > 1 && (\n          <div\n            className=\"-mx-4 flex gap-0.5 overflow-x-auto px-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n            role=\"group\"\n            aria-label={`Compare ${own.name} with`}\n          >\n            <LayoutGroup id={`${uid}-picker`}>\n              {others.map((column) => (\n                <button\n                  key={column.id}\n                  type=\"button\"\n                  className=\"relative isolate h-9 flex-none cursor-pointer rounded-button border-0 bg-transparent px-4 text-sm/[inherit] font-medium whitespace-nowrap text-ink-2 transition-colors duration-fast ease-standard [-webkit-tap-highlight-color:transparent] aria-pressed:text-ink\"\n                  aria-pressed={column.id === compareWith}\n                  onClick={() => setCompareWith(column.id)}\n                >\n                  {column.id === compareWith && (\n                    <motion.span\n                      layoutId=\"pick\"\n                      className=\"absolute inset-0 -z-1 rounded-[inherit] bg-hover\"\n                      transition={reduced ? { duration: 0 } : morph}\n                    />\n                  )}\n                  <span>{column.name}</span>\n                </button>\n              ))}\n            </LayoutGroup>\n          </div>\n        )}\n\n        <div\n          className=\"relative overscroll-contain [scrollbar-width:thin]\"\n          style={maxHeight !== undefined ? { maxHeight, overflowY: \"auto\" } : undefined}\n          tabIndex={maxHeight !== undefined ? 0 : undefined}\n          aria-label={maxHeight !== undefined ? title : undefined}\n          role={maxHeight !== undefined ? \"region\" : undefined}\n        >\n          <div\n            role=\"table\"\n            aria-labelledby={`${uid}-title`}\n            className=\"grid [--band:color-mix(in_oklab,var(--accent)_6%,var(--surface))] [--grid:minmax(0,1.7fr)_repeat(var(--columns),minmax(0,1fr))] @max-[820px]/compare:[--grid:minmax(0,1.4fr)_repeat(var(--columns),minmax(0,1fr))] @max-[640px]/compare:[--grid:minmax(0,1.3fr)_repeat(var(--columns),minmax(0,.9fr))]\"\n            style={gridStyle}\n          >\n            <div\n              role=\"rowgroup\"\n              className=\"sticky top-[var(--sticky-top,0)] z-3 bg-[color-mix(in_oklab,var(--page)_94%,transparent)] [backdrop-filter:blur(14px)_saturate(1.4)]\"\n            >\n              {header}\n            </div>\n            {sections.map((section) => {\n              const rows = differencesOnly ? section.rows.filter((row) => differs(row.values)) : section.rows;\n              return (\n                <div role=\"rowgroup\" key={section.id} className=\"contents\">\n                  <AnimatePresence initial={false}>\n                    {rows.length > 0 && (\n                      <motion.div\n                        key=\"title\"\n                        role=\"row\"\n                        className={cn(gridRow, \"overflow-hidden\")}\n                        initial={{ height: 0, opacity: 0 }}\n                        animate={{ height: \"auto\", opacity: 1 }}\n                        exit={{ height: 0, opacity: 0 }}\n                        transition={reduced ? { duration: 0 } : smooth}\n                      >\n                        <div role=\"rowheader\" className=\"pt-8 pr-2 pb-2 pl-0 text-xs/[inherit] font-medium text-ink-3\">\n                          {section.title}\n                        </div>\n                        {visible.map((column) => (\n                          <div\n                            key={column.id}\n                            role=\"cell\"\n                            className={cn(column === own && \"bg-(--band)\")}\n                            data-own={column === own || undefined}\n                          />\n                        ))}\n                      </motion.div>\n                    )}\n                    {rows.map((row) => {\n                      const index = rowIndex++;\n                      return (\n                        <motion.div\n                          key={row.id}\n                          role=\"row\"\n                          className={cn(gridRow, \"group/row overflow-hidden\")}\n                          initial={{ height: 0, opacity: 0 }}\n                          animate={{ height: \"auto\", opacity: 1 }}\n                          exit={{ height: 0, opacity: 0 }}\n                          transition={reduced ? { duration: 0 } : smooth}\n                        >\n                          <div\n                            role=\"rowheader\"\n                            className={cn(\n                              rowCell,\n                              \"grid min-h-14 content-center gap-0.5 py-3 pr-4 pl-0 text-sm leading-body font-medium @max-[640px]/compare:pr-3\",\n                            )}\n                          >\n                            <span>{row.feature}</span>\n                            {row.hint && <span className=\"text-xs/[inherit] font-normal text-ink-3\">{row.hint}</span>}\n                          </div>\n                          {visible.map((column) => (\n                            <div\n                              key={column === own ? \"own\" : narrow ? \"other\" : column.id}\n                              role=\"cell\"\n                              className={cn(\n                                rowCell,\n                                \"relative grid place-items-center p-2 text-center\",\n                                column === own &&\n                                  \"bg-(--band) group-hover/row:bg-[color-mix(in_oklab,var(--accent)_9%,var(--surface))]\",\n                              )}\n                              data-own={column === own || undefined}\n                            >\n                              <AnimatePresence mode=\"popLayout\" initial={false}>\n                                <motion.span\n                                  key={`${column.id}`}\n                                  className=\"grid place-items-center\"\n                                  initial={reduced ? { opacity: 0 } : { opacity: 0, scale: 0.8 }}\n                                  animate={{ opacity: 1, scale: 1 }}\n                                  exit={reduced ? { opacity: 0 } : { opacity: 0, scale: 0.8 }}\n                                  transition={snappy}\n                                >\n                                  <Mark\n                                    value={row.values[column.id]}\n                                    own={column === own}\n                                    index={index}\n                                    reduced={reduced}\n                                  />\n                                </motion.span>\n                              </AnimatePresence>\n                            </div>\n                          ))}\n                        </motion.div>\n                      );\n                    })}\n                  </AnimatePresence>\n                </div>\n              );\n            })}\n            <div role=\"rowgroup\">\n              <div role=\"row\" className={gridRow}>\n                <div\n                  role=\"cell\"\n                  className=\"flex flex-wrap content-start gap-x-4 gap-y-2 pt-5 pr-4 text-xs/[inherit] text-ink-3 @max-[640px]/compare:flex-col [&>span]:inline-flex [&>span]:items-center [&>span]:gap-1.5\"\n                >\n                  <span>\n                    <svg\n                      className={cn(checkMark(), \"[&_path]:[stroke-width:2.2]\")}\n                      width=\"16\"\n                      height=\"16\"\n                      viewBox=\"0 0 22 22\"\n                      aria-hidden=\"true\"\n                    >\n                      <circle cx=\"11\" cy=\"11\" r=\"10\" />\n                      <path d=\"M6.6 11.3l3 3 5.9-6.4\" />\n                    </svg>\n                    Included\n                  </span>\n                  <span>\n                    <svg className={partialMark} width=\"16\" height=\"16\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">\n                      <circle cx=\"11\" cy=\"11\" r=\"9.25\" />\n                      <path d=\"M11 1.75a9.25 9.25 0 0 1 0 18.5z\" />\n                    </svg>\n                    Partial\n                  </span>\n                  <span>\n                    <Minus className={crossMark} size={14} strokeWidth={1.75} aria-hidden=\"true\" />\n                    Not included\n                  </span>\n                </div>\n                {visible.map((column) => (\n                  <div\n                    key={column.id}\n                    role=\"cell\"\n                    className={cn(\n                      \"grid place-items-center pt-5 pb-5\",\n                      column === own && \"rounded-b-[20px] bg-(--band)\",\n                    )}\n                    data-own={column === own || undefined}\n                  >\n                    {column === own &&\n                      cta &&\n                      (cta.href && !cta.onClick ? (\n                        <a\n                          className={cn(\n                            ctaClass,\n                            \"[transition:transform_var(--duration-instant)_var(--ease-standard)] active:[transform:scale(.97)]\",\n                          )}\n                          href={cta.href}\n                        >\n                          {cta.label}\n                        </a>\n                      ) : (\n                        <motion.button\n                          type=\"button\"\n                          layout={!reduced}\n                          className={cn(\n                            ctaClass,\n                            \"transition-[background-color,color] duration-standard ease-standard\",\n                            ctaDone && \"bg-[color-mix(in_oklab,var(--green)_14%,transparent)] text-green\",\n                          )}\n                          style={{ borderRadius: 9999 }}\n                          data-done={ctaDone || undefined}\n                          whileTap={reduced ? undefined : { scale: 0.97 }}\n                          transition={morph}\n                          onClick={() => {\n                            cta.onClick?.();\n                            if (cta.doneLabel) setCtaDone(true);\n                          }}\n                        >\n                          <AnimatePresence mode=\"popLayout\" initial={false}>\n                            <motion.span\n                              key={ctaDone ? \"done\" : \"idle\"}\n                              layout={reduced ? false : \"position\"}\n                              className=\"inline-flex items-center gap-1.5\"\n                              initial={reduced ? { opacity: 0 } : { opacity: 0, y: 8, filter: \"blur(2px)\" }}\n                              animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                              exit={reduced ? { opacity: 0 } : { opacity: 0, y: -8, filter: \"blur(2px)\" }}\n                              transition={{\n                                duration: motionTokens.duration.standard,\n                                ease: [...motionTokens.ease.standard],\n                              }}\n                            >\n                              {ctaDone ? (\n                                <>\n                                  <Check size={14} strokeWidth={2} aria-hidden=\"true\" />\n                                  {cta.doneLabel}\n                                </>\n                              ) : (\n                                cta.label\n                              )}\n                            </motion.span>\n                          </AnimatePresence>\n                        </motion.button>\n                      ))}\n                  </div>\n                ))}\n              </div>\n            </div>\n          </div>\n        </div>\n        <p className={srOnly} aria-live=\"polite\">\n          {differencesOnly ? \"Showing only rows that differ\" : \"Showing all rows\"}\n        </p>\n      </div>\n    </section>\n  );\n}\n\n/** Preview: the comparison inside a capped height so the sticky header shows. */\nexport function ComparisonTableBlock() {\n  return (\n    <ComparisonTable\n      maxHeight=\"min(760px, 82vh)\"\n      cta={{ label: \"Start your free month\", doneLabel: \"Free month started\" }}\n    />\n  );\n}\n\nexport default ComparisonTableBlock;\n",
      "type": "registry:component",
      "target": "@components/iris/blocks/comparison-table/comparison-table.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/comparison-table",
  "meta": {
    "kind": "block",
    "group": "Blocks",
    "export": "ComparisonTable",
    "docs": "https://irisui.dev/components/comparison-table"
  }
}
