{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "funnel-chart-card",
  "title": "Funnel Chart",
  "description": "Tapered conversion funnel. Widths and drop-off come from the stage values.",
  "registryDependencies": [
    "https://components.exe.xyz/r/chart-card.json"
  ],
  "files": [
    {
      "path": "src/components/charts/funnel-chart-card.tsx",
      "content": "import {\n  ChartCard,\n  chartColor,\n  formatNumber,\n  useActiveRange,\n} from \"@/components/charts/chart-card\"\n\nexport type FunnelStage = { label: string; value: number; color?: string }\n\nconst DEFAULT_STAGES: FunnelStage[] = [\n  { label: \"Link opened\", value: 197 },\n  { label: \"Started\", value: 110 },\n  { label: \"Completed\", value: 77 },\n  { label: \"Converted\", value: 38 },\n]\n\nconst DEFAULT_RANGES = [\n  {\n    id: \"signup\",\n    label: \"Last 30 days\",\n    stages: DEFAULT_STAGES,\n    headline: 197,\n    delta: 5.2,\n  },\n  {\n    id: \"hiring\",\n    label: \"This quarter\",\n    stages: [\n      { label: \"Applied\", value: 1240 },\n      { label: \"Screened\", value: 620 },\n      { label: \"Interview\", value: 248 },\n      { label: \"Offer\", value: 74 },\n      { label: \"Hired\", value: 31 },\n    ],\n    headline: 1240,\n  },\n  {\n    id: \"checkout\",\n    label: \"Checkout\",\n    stages: [\n      { label: \"Viewed\", value: 41800 },\n      { label: \"Cart\", value: 12540 },\n      { label: \"Shipping\", value: 8420 },\n      { label: \"Paid\", value: 3904 },\n    ],\n    headline: 41800,\n    delta: -1.8,\n  },\n]\n\nfunction bandPath(\n  y0: number,\n  y1: number,\n  topW: number,\n  botW: number,\n  width: number,\n  smooth: boolean\n) {\n  const xTop0 = (width - topW) / 2\n  const xTop1 = xTop0 + topW\n  const xBot0 = (width - botW) / 2\n  const xBot1 = xBot0 + botW\n  if (!smooth) {\n    return `M ${xTop0} ${y0} L ${xTop1} ${y0} L ${xBot1} ${y1} L ${xBot0} ${y1} Z`\n  }\n  const cy = (y1 - y0) * 0.45\n  return [\n    `M ${xTop0} ${y0}`,\n    `L ${xTop1} ${y0}`,\n    `C ${xTop1} ${y0 + cy}, ${xBot1} ${y1 - cy}, ${xBot1} ${y1}`,\n    `L ${xBot0} ${y1}`,\n    `C ${xBot0} ${y1 - cy}, ${xTop0} ${y0 + cy}, ${xTop0} ${y0}`,\n    \"Z\",\n  ].join(\" \")\n}\n\nexport function FunnelChartCard({\n  title = \"Sign-up funnel\",\n  shape = \"smooth\",\n  mono = false,\n  stages,\n  headline,\n  delta,\n  range,\n  ranges = DEFAULT_RANGES,\n}: {\n  title?: string\n  shape?: \"smooth\" | \"sharp\"\n  mono?: boolean\n  stages?: FunnelStage[]\n  headline?: number\n  delta?: number\n  range?: string\n  ranges?: {\n    id: string\n    label: string\n    stages: FunnelStage[]\n    delta?: number\n    headline?: number\n  }[]\n}) {\n  const { id, setId, active } = useActiveRange(ranges, range)\n  const plotStages =\n    active?.stages ?? (stages && stages.length > 0 ? stages : DEFAULT_STAGES)\n  const firstStage = plotStages[0]\n  const plotHeadline = active?.headline ?? headline ?? firstStage.value\n  const plotDelta = active?.delta ?? delta\n  const max = Math.max(firstStage.value, 1)\n  const width = 320\n  const height = 200\n  const rowH = height / plotStages.length\n  const dropoff = plotStages\n    .map((stage, index) => {\n      if (index === 0) return `${stage.label} ${formatNumber(stage.value)}`\n      const prev = plotStages[index - 1]?.value ?? stage.value\n      const pct = prev === 0 ? 0 : Math.round((stage.value / prev) * 100)\n      return `${stage.label} ${formatNumber(stage.value)} (${pct}%)`\n    })\n    .join(\", \")\n\n  return (\n    <ChartCard\n      title={title}\n      headline={plotHeadline}\n      delta={plotDelta}\n      ranges={ranges}\n      range={id}\n      onRangeChange={setId}\n      summary={`${title} ${formatNumber(plotHeadline)}. ${dropoff}`}\n    >\n      <svg\n        role=\"img\"\n        aria-label={dropoff}\n        viewBox={`0 0 ${width} ${height}`}\n        className=\"h-52 w-full\"\n      >\n        {plotStages.map((stage, index) => {\n          const nextValue =\n            index < plotStages.length - 1\n              ? plotStages[index + 1].value\n              : stage.value * 0.62\n          const topW = Math.max(28, (stage.value / max) * (width - 24))\n          const botW = Math.max(22, (nextValue / max) * (width - 24))\n          const y0 = index * rowH + 2\n          const y1 = y0 + rowH - 6\n          const fill = mono\n            ? `color-mix(in srgb, var(--foreground) ${Math.round(\n                88 - index * 16\n              )}%, transparent)`\n            : chartColor(index, stage.color)\n          return (\n            <g key={stage.label}>\n              <path\n                d={bandPath(y0, y1, topW, botW, width, shape === \"smooth\")}\n                fill={fill}\n                stroke=\"var(--border)\"\n                strokeWidth={1}\n              />\n              <text\n                x={width / 2}\n                y={(y0 + y1) / 2 + 4}\n                textAnchor=\"middle\"\n                fill=\"var(--primary-foreground)\"\n                fontSize={11}\n                fontFamily=\"var(--font-mono)\"\n              >\n                {formatNumber(stage.value)}\n              </text>\n            </g>\n          )\n        })}\n      </svg>\n      <ul className=\"mt-2 flex flex-col gap-1.5\">\n        {plotStages.map((stage, index) => {\n          const prev = index === 0 ? undefined : plotStages[index - 1]\n          const pct =\n            prev == null || prev.value === 0\n              ? null\n              : Math.round((stage.value / prev.value) * 100)\n          return (\n            <li key={stage.label} className=\"flex items-center gap-2 text-sm\">\n              <span\n                className=\"size-1.5 rounded-[1px]\"\n                style={{\n                  background: mono\n                    ? \"var(--foreground)\"\n                    : chartColor(index, stage.color),\n                }}\n              />\n              <span className=\"text-muted-foreground\">{stage.label}</span>\n              <span className=\"ml-auto font-mono text-[11px] tabular-nums\">\n                {formatNumber(stage.value)}\n                {pct != null ? `  ${pct}%` : \"\"}\n              </span>\n            </li>\n          )\n        })}\n      </ul>\n    </ChartCard>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}