{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pie-chart",
  "title": "Pie Chart",
  "description": "A styled EvilCharts pie chart for showing parts of a whole with labels, legends, and donut variants.",
  "dependencies": [
    "motion",
    "recharts"
  ],
  "registryDependencies": [
    "https://ui.corr.sh/r/chart-empty-loading-error.json"
  ],
  "files": [
    {
      "path": "src/components/evilcharts/charts/pie-chart.tsx",
      "content": "\"use client\";\n\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport { ChartLegend, ChartLegendContent, type ChartLegendVariant } from \"@/components/evilcharts/ui/legend\";\nimport { LabelList, Pie, PieChart, Sector, type PieSectorShapeProps } from \"recharts\";\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport { useCallback, useId, useState, type ComponentProps } from \"react\";\nimport { motion } from \"motion/react\";\n\n// Loading animation constants\nconst LOADING_SECTORS = 5;\nconst LOADING_ANIMATION_DURATION = 2000; // Full cycle duration in ms\n\n// Constants\nconst DEFAULT_INNER_RADIUS = 0;\nconst DEFAULT_OUTER_RADIUS = \"80%\";\nconst DEFAULT_CORNER_RADIUS = 0;\nconst DEFAULT_PADDING_ANGLE = 0;\n\ntype ChartProps = ComponentProps<typeof PieChart>;\ntype PieProps = ComponentProps<typeof Pie>;\ntype LabelListProps = ComponentProps<typeof LabelList>;\n\ntype EvilPieChartProps<TData extends Record<string, unknown>> = {\n  // Data\n  data: TData[];\n  dataKey: keyof TData & string;\n  nameKey: keyof TData & string;\n  chartConfig: ChartConfig;\n  className?: string;\n  chartProps?: ChartProps;\n  pieProps?: Omit<PieProps, \"data\" | \"dataKey\" | \"nameKey\">;\n\n  // Pie Shape\n  innerRadius?: number | string;\n  outerRadius?: number | string;\n  cornerRadius?: number;\n  paddingAngle?: number;\n  startAngle?: number;\n  endAngle?: number;\n\n  // Labels\n  showLabels?: boolean;\n  labelKey?: keyof TData & string;\n  labelListProps?: Omit<LabelListProps, \"dataKey\">;\n\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideLegend?: boolean;\n  legendVariant?: ChartLegendVariant;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n\n  // Interactive Stuffs\n  isLoading?: boolean;\n\n  // Glow Effects\n  glowingSectors?: string[];\n  // Background\n  backgroundVariant?: BackgroundVariant;\n};\n\ntype EvilPieChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selection: { dataKey: string; value: number } | null) => void;\n};\n\ntype EvilPieChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilPieChartPropsWithCallback<TData extends Record<string, unknown>> =\n  EvilPieChartProps<TData> & (EvilPieChartClickable | EvilPieChartNotClickable);\n\nexport function EvilPieChart<TData extends Record<string, unknown>>({\n  data,\n  dataKey,\n  nameKey,\n  chartConfig,\n  className,\n  chartProps,\n  pieProps,\n  innerRadius = DEFAULT_INNER_RADIUS,\n  outerRadius = DEFAULT_OUTER_RADIUS,\n  cornerRadius = DEFAULT_CORNER_RADIUS,\n  paddingAngle = DEFAULT_PADDING_ANGLE,\n  startAngle = 0,\n  endAngle = 360,\n  showLabels = false,\n  labelKey,\n  labelListProps,\n  hideTooltip = false,\n  hideLegend = false,\n  legendVariant,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  isLoading = false,\n  glowingSectors = [],\n  onSelectionChange,\n  backgroundVariant,\n}: EvilPieChartPropsWithCallback<TData>) {\n  const [selectedSector, setSelectedSector] = useState<string | null>(null);\n  const chartId = useId().replace(/:/g, \"\");\n\n  // Handler to update selection and call callback\n  const handleSelectionChange = useCallback(\n    (sectorName: string | null) => {\n      setSelectedSector(sectorName);\n      if (isClickable && onSelectionChange) {\n        if (sectorName === null) {\n          onSelectionChange(null);\n        } else {\n          // Find the data item and get its value\n          const selectedItem = data.find((item) => (item[nameKey] as string) === sectorName);\n          if (selectedItem) {\n            const value = selectedItem[dataKey] as number;\n            onSelectionChange({ dataKey: sectorName, value });\n          }\n        }\n      }\n    },\n    [isClickable, onSelectionChange, data, nameKey, dataKey],\n  );\n\n  // Prepare data with fill colors referencing gradients\n  const preparedData = data.map((item) => {\n    const sectorName = item[nameKey] as string;\n    return {\n      ...item,\n      fill: `url(#${chartId}-pie-colors-${sectorName})`,\n    };\n  });\n\n  return (\n    <ChartContainer className={className} config={chartConfig}>\n      <LoadingIndicator isLoading={isLoading} />\n      <PieChart id=\"evil-charts-pie-chart\" accessibilityLayer {...chartProps}>\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        {!hideLegend && (\n          <ChartLegend\n            verticalAlign=\"bottom\"\n            align=\"center\"\n            content={\n              <ChartLegendContent\n                selected={selectedSector}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                nameKey={nameKey}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            content={\n              <ChartTooltipContent\n                nameKey={nameKey}\n                hideLabel\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n        {!isLoading && (\n          <Pie\n            data={preparedData}\n            dataKey={dataKey}\n            nameKey={nameKey}\n            innerRadius={innerRadius}\n            outerRadius={outerRadius}\n            cornerRadius={cornerRadius}\n            paddingAngle={paddingAngle}\n            startAngle={startAngle}\n            endAngle={endAngle}\n            strokeWidth={0}\n            isAnimationActive\n            style={isClickable ? { cursor: \"pointer\" } : undefined}\n            onClick={(_, index) => {\n              if (!isClickable) return;\n              const clickedName = data[index]?.[nameKey] as string;\n              handleSelectionChange(selectedSector === clickedName ? null : clickedName);\n            }}\n            shape={(props: PieSectorShapeProps) => {\n              const index = props.index ?? 0;\n              const sectorName = data[index]?.[nameKey] as string;\n              const isGlowing = glowingSectors.includes(sectorName);\n              const isSelected = selectedSector === null || selectedSector === sectorName;\n\n              const getFilter = () => {\n                if (isGlowing) return `url(#${chartId}-pie-glow-${sectorName})`;\n                return undefined;\n              };\n\n              return (\n                <Sector\n                  {...props}\n                  fill={`url(#${chartId}-pie-colors-${sectorName})`}\n                  filter={getFilter()}\n                  stroke={paddingAngle < 0 ? \"var(--background)\" : \"none\"}\n                  strokeWidth={paddingAngle < 0 ? 5 : 0}\n                  opacity={isClickable && !isSelected ? 0.3 : 1}\n                  className=\"transition-opacity duration-200\"\n                />\n              );\n            }}\n            {...pieProps}\n          >\n            {showLabels && (\n              <LabelList\n                dataKey={labelKey ?? dataKey}\n                stroke=\"none\"\n                fontSize={12}\n                fontWeight={500}\n                fill=\"currentColor\"\n                className=\"fill-background\"\n                {...labelListProps}\n              />\n            )}\n          </Pie>\n        )}\n\n        {/* Animated loading overlay using custom shape */}\n        {isLoading && (\n          <Pie\n            data={LOADING_PIE_DATA}\n            dataKey=\"value\"\n            nameKey=\"name\"\n            innerRadius={innerRadius}\n            outerRadius={outerRadius}\n            cornerRadius={cornerRadius}\n            paddingAngle={paddingAngle}\n            startAngle={startAngle}\n            endAngle={endAngle}\n            strokeWidth={0}\n            isAnimationActive={false}\n            shape={(props) => <AnimatedLoadingSector {...props} />}\n          />\n        )}\n\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {/* Radial color gradients for each sector */}\n          <RadialColorGradientStyle chartConfig={chartConfig} chartId={chartId} />\n\n          {/* Glow filters */}\n          {glowingSectors.length > 0 && (\n            <GlowFilterStyle chartId={chartId} glowingSectors={glowingSectors} />\n          )}\n        </defs>\n      </PieChart>\n    </ChartContainer>\n  );\n}\n\n// Generate fixed loading data with equal sectors for circular pulsing animation\nconst LOADING_PIE_DATA = Array.from({ length: LOADING_SECTORS }, (_, i) => ({\n  name: `loading${i}`,\n  value: 100 / LOADING_SECTORS,\n}));\n\n// Animated sector for loading state using motion.dev\nconst AnimatedLoadingSector = (props: ComponentProps<typeof Sector> & { index?: number }) => {\n  const { index = 0, ...sectorProps } = props;\n\n  // Calculate delay for circular wave effect\n  const delay = (index / LOADING_SECTORS) * (LOADING_ANIMATION_DURATION / 1000);\n\n  return (\n    <motion.g\n      initial={{ opacity: 0.15 }}\n      animate={{ opacity: [0.15, 0.5, 0.15] }}\n      transition={{\n        duration: LOADING_ANIMATION_DURATION / 1000,\n        delay,\n        repeat: Infinity,\n        ease: \"easeInOut\",\n      }}\n    >\n      <Sector {...sectorProps} fill=\"currentColor\" />\n    </motion.g>\n  );\n};\n\n// Create radial color gradient for pie sectors\nconst RadialColorGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-pie-colors-${dataKey}`}\n            id={`${chartId}-pie-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"1\"\n          >\n            {colorsCount === 1 ? (\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Apply soft glow filter effect to pie sectors using SVG filters\nconst GlowFilterStyle = ({\n  chartId,\n  glowingSectors,\n}: {\n  chartId: string;\n  glowingSectors: string[];\n}) => {\n  return (\n    <>\n      {glowingSectors.map((sectorName) => (\n        <filter\n          key={`${chartId}-pie-glow-${sectorName}`}\n          id={`${chartId}-pie-glow-${sectorName}`}\n          x=\"-100%\"\n          y=\"-100%\"\n          width=\"300%\"\n          height=\"300%\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"8\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.5 0\"\n            result=\"glow\"\n          />\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/evilcharts/ui/chart.tsx",
      "content": "\"use client\";\n\nimport * as RechartsPrimitive from \"recharts\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\ntype ThemeKey = keyof typeof THEMES;\n\n// All Keys are optional at first\ntype ThemeColorsBase = {\n  [K in ThemeKey]?: string[];\n};\n\n// Require at least one theme key\ntype AtLeastOneThemeColor = {\n  [K in ThemeKey]: Required<Pick<ThemeColorsBase, K>> & Partial<Omit<ThemeColorsBase, K>>;\n}[ThemeKey];\n\nconst VALID_THEME_KEYS = Object.keys(THEMES) as ThemeKey[];\n\n// Validation for chart config colors at runtime\nfunction validateChartConfigColors(config: ChartConfig): void {\n  for (const [key, value] of Object.entries(config)) {\n    if (value.colors) {\n      const hasValidThemeKey = VALID_THEME_KEYS.some(\n        (themeKey) => value.colors?.[themeKey] !== undefined,\n      );\n\n      if (!hasValidThemeKey) {\n        throw new Error(\n          `[EvilCharts] Invalid chart config for \"${key}\": colors object must have at least one theme key (${VALID_THEME_KEYS.join(\", \")}). Received empty object or invalid keys.`,\n        );\n      }\n    }\n  }\n}\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n    colors?: AtLeastOneThemeColor;\n  }\n>;\n\ninterface ChartContextProps {\n  config: ChartConfig;\n}\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nexport function useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error(\"useChart must be used within a <ChartContainer />\");\n  }\n\n  return context;\n}\n\ninterface ChartContainerProps\n  extends\n    Omit<React.ComponentProps<\"div\">, \"children\">,\n    Pick<\n      React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>,\n      | \"initialDimension\"\n      | \"aspect\"\n      | \"debounce\"\n      | \"minHeight\"\n      | \"minWidth\"\n      | \"maxHeight\"\n      | \"height\"\n      | \"width\"\n      | \"onResize\"\n      | \"children\"\n    > {\n  config: ChartConfig;\n  innerResponsiveContainerStyle?: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >[\"style\"];\n  /** Optional content rendered below the chart (e.g. EvilBrush) */\n  footer?: React.ReactNode;\n}\n\nfunction ChartContainer({\n  id,\n  config,\n  initialDimension = { width: 320, height: 200 },\n  className,\n  children,\n  footer,\n  ...props\n}: Readonly<ChartContainerProps>) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n\n  // Validate chart config at runtime\n  validateChartConfigColors(config);\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"min-h-0 w-full flex-1\",\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border relative flex flex-col justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          !footer && \"aspect-video\",\n          className,\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer\n          className=\"min-h-0 w-full flex-1\"\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n        {footer}\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nfunction LoadingIndicator({ isLoading }: { isLoading: boolean }) {\n  if (!isLoading) {\n    return null;\n  }\n\n  return (\n    <div className=\"pointer-events-none absolute inset-0 z-20 flex items-center justify-center\">\n      <div className=\"text-primary bg-background flex items-center justify-center gap-2 rounded-md border px-2 py-0.5 text-sm\">\n        <div className=\"border-border border-t-primary h-3 w-3 animate-spin rounded-full border\" />\n        <span>Loading</span>\n      </div>\n    </div>\n  );\n}\n\n// Distribute colors evenly across slots, extra slots go to last color(s)\n// Example: 2 colors for 4 slots → [red, red, pink, pink]\n// Example: 3 colors for 4 slots → [red, pink, blue, blue]\nfunction distributeColors(colorsArray: string[], maxCount: number): string[] {\n  const availableCount = colorsArray.length;\n  if (availableCount >= maxCount) {\n    return colorsArray.slice(0, maxCount);\n  }\n\n  const result: string[] = [];\n  const baseSlots = Math.floor(maxCount / availableCount);\n  const extraSlots = maxCount % availableCount;\n\n  // First (availableCount - extraSlots) colors get baseSlots each\n  // Last extraSlots colors get (baseSlots + 1) each\n  for (let colorIdx = 0; colorIdx < availableCount; colorIdx++) {\n    const isExtraColor = colorIdx >= availableCount - extraSlots;\n    const slotsForThisColor = baseSlots + (isExtraColor ? 1 : 0);\n    for (let j = 0; j < slotsForThisColor; j++) {\n      result.push(colorsArray[colorIdx]);\n    }\n  }\n\n  return result;\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(([, config]) => config.colors);\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  const generateCssVars = (theme: keyof typeof THEMES) =>\n    colorConfig\n      .flatMap(([key, itemConfig]) => {\n        const colorsArray = itemConfig.colors?.[theme];\n        if (!colorsArray || !Array.isArray(colorsArray) || colorsArray.length === 0) {\n          return [];\n        }\n\n        // Get max count across all themes for this key\n        const maxCount = getColorsCount(itemConfig);\n\n        // Distribute colors evenly across all required slots\n        const distributedColors = distributeColors(colorsArray, maxCount);\n\n        return distributedColors.map((color, index) => `  --color-${key}-${index}: ${color};`);\n      })\n      .filter(Boolean)\n      .join(\"\\n\");\n\n  const css = Object.entries(THEMES)\n    .map(\n      ([theme, prefix]) =>\n        `${prefix} [data-chart=${id}] {\\n${generateCssVars(theme as keyof typeof THEMES)}\\n}`,\n    )\n    .join(\"\\n\");\n\n  return <style dangerouslySetInnerHTML={{ __html: css }} />;\n};\n\n// Helper to extract item config from a payload.\nexport function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n  if (typeof payload !== \"object\" || payload === null) {\n    return undefined;\n  }\n\n  const payloadPayload =\n    \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n  ) {\n    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\n// Format values to percent for expanded charts\nfunction axisValueToPercentFormatter(value: number) {\n  return `${Math.round(value * 100).toFixed(0)}%`;\n}\n\n// Get max colors count across all themes for a config entry\nfunction getColorsCount(config: ChartConfig[string]): number {\n  if (!config.colors) return 1;\n  const counts = VALID_THEME_KEYS.map((theme) => config.colors?.[theme]?.length ?? 0);\n  return Math.max(...counts, 1);\n}\n\n// Generate random loading data for skeleton/loading state\n// min/max represent percentage of the range (0-100), defaults to 20-80 for realistic look\nexport const getLoadingData = (points: number = 10, min: number = 0, max: number = 70) => {\n  const range = max - min;\n  return Array.from({ length: points }, () => ({\n    loading: Math.floor(Math.random() * range) + min,\n  }));\n};\n\nexport {\n  ChartContainer,\n  ChartStyle,\n  axisValueToPercentFormatter,\n  LoadingIndicator,\n  getColorsCount,\n};\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/evilcharts/ui/tooltip.tsx",
      "content": "import { getPayloadConfigFromPayload, getColorsCount, useChart } from \"@/components/evilcharts/ui/chart\";\nimport type { NameType, ValueType } from \"recharts/types/component/DefaultTooltipContent\";\nimport * as RechartsPrimitive from \"recharts\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\ntype TooltipRoundness = \"sm\" | \"md\" | \"lg\" | \"xl\";\ntype TooltipVariant = \"default\" | \"frosted-glass\";\n\nconst roundnessMap: Record<TooltipRoundness, string> = {\n  sm: \"rounded-sm\",\n  md: \"rounded-md\",\n  lg: \"rounded-lg\",\n  xl: \"rounded-xl\",\n};\n\nconst variantMap: Record<TooltipVariant, string> = {\n  default: \"bg-background\",\n  \"frosted-glass\": \"bg-background/70 backdrop-blur-sm\",\n};\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = \"dot\",\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  nameKey,\n  labelKey,\n  selected,\n  roundness = \"lg\",\n  variant = \"default\",\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<\"div\"> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: \"line\" | \"dot\" | \"dashed\";\n    nameKey?: string;\n    labelKey?: string;\n    selected?: string | null;\n    roundness?: TooltipRoundness;\n    variant?: TooltipVariant;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<ValueType, NameType>,\n    \"accessibilityLayer\"\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === \"string\" ? (config[label]?.label ?? label) : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n  }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n  if (!active || !payload?.length) {\n    // Empty tooltip - to prevent position getting 0.0 so it doesnt animate tooltip every time from 0.0 origin\n    return <span className=\"p-4\" />;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n  return (\n    <div\n      className={cn(\n        \"border-border/50 grid min-w-32 items-start gap-1.5 border px-2.5 py-1.5 text-xs shadow-xl\",\n        roundnessMap[roundness],\n        variantMap[variant],\n        className,\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== \"none\")\n          .map((item, index) => {\n            // For pie charts, item.name contains the sector name (e.g., \"chrome\")\n            // For radial charts, the name is in item.payload[nameKey]\n            // For other charts, item.name or item.dataKey contains the series name\n            const payloadName =\n              nameKey && item.payload\n                ? (item.payload as Record<string, unknown>)[nameKey]\n                : undefined;\n            const key = `${payloadName ?? item.name ?? item.dataKey ?? \"value\"}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n            // Get colors count for this item to determine gradient vs solid\n            const colorsCount = itemConfig ? getColorsCount(itemConfig) : 1;\n\n            return (\n              <div\n                key={index}\n                className={cn(\n                  \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\n                  indicator === \"dot\" && \"items-center\",\n                  selected != null && selected !== item.dataKey && \"opacity-30\",\n                )}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\"shrink-0 rounded-[2px]\", {\n                            \"h-2.5 w-2.5\": indicator === \"dot\",\n                            \"w-1\": indicator === \"line\",\n                            \"w-0 border-[1.5px] border-dashed bg-transparent!\":\n                              indicator === \"dashed\",\n                            \"my-0.5\": nestLabel && indicator === \"dashed\",\n                          })}\n                          style={getIndicatorColorStyle(key, colorsCount)}\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        \"flex flex-1 justify-between gap-4 leading-none\",\n                        nestLabel ? \"items-end\" : \"items-center\",\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                          {typeof item.value === \"number\"\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nfunction getIndicatorColorStyle(dataKey: string, colorsCount: number): React.CSSProperties {\n  if (colorsCount <= 1) {\n    return { background: `var(--color-${dataKey}-0)` };\n  }\n\n  // Multiple colors: create linear gradient with evenly distributed stops\n  const stops = Array.from({ length: colorsCount }, (_, index) => {\n    const offset = (index / (colorsCount - 1)) * 100;\n    return `var(--color-${dataKey}-${index}) ${offset}%`;\n  }).join(\", \");\n\n  return { background: `linear-gradient(to right, ${stops})` };\n}\n\nconst ChartTooltip = ({\n  animationDuration = 200,\n  ...props\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip>) => (\n  <RechartsPrimitive.Tooltip animationDuration={animationDuration} {...props} />\n);\n\nexport { ChartTooltip, ChartTooltipContent };\nexport type { TooltipRoundness, TooltipVariant };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/evilcharts/ui/legend.tsx",
      "content": "import { getPayloadConfigFromPayload, getColorsCount, useChart } from \"@/components/evilcharts/ui/chart\";\nimport * as RechartsPrimitive from \"recharts\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\ntype ChartLegendVariant =\n  | \"square\"\n  | \"circle\"\n  | \"circle-outline\"\n  | \"rounded-square\"\n  | \"rounded-square-outline\"\n  | \"vertical-bar\"\n  | \"horizontal-bar\";\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  nameKey,\n  payload,\n  verticalAlign,\n  align = \"right\",\n  selected,\n  onSelectChange,\n  isClickable,\n  variant = \"rounded-square\",\n}: React.ComponentProps<\"div\"> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n  selected?: string | null;\n  isClickable?: boolean;\n  onSelectChange?: (selected: string | null) => void;\n  variant?: ChartLegendVariant;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-4 select-none\",\n        align === \"left\" && \"justify-start\",\n        align === \"center\" && \"justify-center\",\n        align === \"right\" && \"justify-end\",\n        verticalAlign === \"top\" ? \"pb-4\" : \"pt-4\",\n        className,\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== \"none\")\n        .map((item) => {\n          // For pie charts, item.value contains the sector name (e.g., \"chrome\")\n          // For radial charts, the name is in item.payload[nameKey]\n          // For other charts, item.dataKey contains the series name (e.g., \"desktop\")\n          const payloadName =\n            nameKey && item.payload\n              ? (item.payload as Record<string, unknown>)[nameKey]\n              : undefined;\n          const key = `${payloadName ?? item.value ?? item.dataKey ?? \"value\"}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n          const isSelected = selected === null || selected === key;\n\n          // Get colors count for this item to determine gradient vs solid\n          const colorsCount = itemConfig ? getColorsCount(itemConfig) : 1;\n\n          return (\n            <div\n              key={key}\n              className={cn(\n                \"[&>svg]:text-muted-foreground flex items-center gap-1.5 transition-opacity [&>svg]:h-3 [&>svg]:w-3\",\n                !isSelected && \"opacity-30\",\n                isClickable && \"cursor-pointer\",\n              )}\n              onClick={() => {\n                if (!isClickable) return;\n\n                onSelectChange?.(selected === key ? null : key);\n              }}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <LegendIndicator\n                  variant={variant}\n                  dataKey={key}\n                  colorsCount={colorsCount}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\n// ---------------------------------------------------------------------------\n// Legend indicator — each variant gets its own branch so future variants\n// can diverge freely in markup & style.\n// ---------------------------------------------------------------------------\n\nfunction LegendIndicator({\n  variant,\n  dataKey,\n  colorsCount,\n}: {\n  variant: ChartLegendVariant;\n  dataKey: string;\n  colorsCount: number;\n}) {\n  const fillStyle = getLegendFillStyle(dataKey, colorsCount);\n  const outlineStyle = getLegendOutlineStyle(dataKey, colorsCount);\n\n  switch (variant) {\n    case \"square\":\n      return <div className=\"h-2 w-2 shrink-0\" style={fillStyle} />;\n\n    case \"circle\":\n      return <div className=\"h-2 w-2 shrink-0 rounded-full\" style={fillStyle} />;\n\n    case \"circle-outline\":\n      return (\n        <div\n          className=\"h-2.5 w-2.5 shrink-0 rounded-full p-[1.5px]\"\n          style={outlineStyle}\n        />\n      );\n\n    case \"vertical-bar\":\n      return <div className=\"h-3 w-1 shrink-0 rounded-[2px]\" style={fillStyle} />;\n\n    case \"horizontal-bar\":\n      return <div className=\"h-1 w-3 shrink-0 rounded-[2px]\" style={fillStyle} />;\n\n    case \"rounded-square-outline\":\n      return (\n        <div\n          className=\"h-2.5 w-2.5 shrink-0 rounded-[3px] p-[1.5px]\"\n          style={outlineStyle}\n        />\n      );\n\n    case \"rounded-square\":\n    default:\n      return <div className=\"h-2 w-2 shrink-0 rounded-[2px]\" style={fillStyle} />;\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Style helpers\n// ---------------------------------------------------------------------------\n\n/** Solid fill / gradient background for filled variants. */\nfunction getLegendFillStyle(dataKey: string, colorsCount: number): React.CSSProperties {\n  if (colorsCount <= 1) {\n    return { backgroundColor: `var(--color-${dataKey}-0)` };\n  }\n\n  const stops = Array.from({ length: colorsCount }, (_, i) => {\n    const offset = (i / (colorsCount - 1)) * 100;\n    return `var(--color-${dataKey}-${i}) ${offset}%`;\n  }).join(\", \");\n\n  return { background: `linear-gradient(to right, ${stops})` };\n}\n\n/**\n * Outline style for stroke variants.\n * Uses background + mask-composite to punch out the center, leaving only the\n * \"border\" visible. Works with both solid colors and gradients, and respects\n * border-radius — unlike plain `border-color`.\n */\nfunction getLegendOutlineStyle(dataKey: string, colorsCount: number): React.CSSProperties {\n  const maskStyle: React.CSSProperties = {\n    WebkitMask:\n      \"linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)\",\n    WebkitMaskComposite: \"xor\",\n    mask: \"linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)\",\n    maskComposite: \"exclude\",\n  };\n\n  if (colorsCount <= 1) {\n    return {\n      backgroundColor: `var(--color-${dataKey}-0)`,\n      ...maskStyle,\n    };\n  }\n\n  const stops = Array.from({ length: colorsCount }, (_, i) => {\n    const offset = (i / (colorsCount - 1)) * 100;\n    return `var(--color-${dataKey}-${i}) ${offset}%`;\n  }).join(\", \");\n\n  return {\n    background: `linear-gradient(to right, ${stops})`,\n    ...maskStyle,\n  };\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nexport { ChartLegend, ChartLegendContent, type ChartLegendVariant };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/evilcharts/ui/background.tsx",
      "content": "\"use client\";\n\nimport { ZIndexLayer } from \"recharts\";\nimport { useId } from \"react\";\n\n// ── Background Variant Types ─────────────────────────────────────────────────\n// To add a new variant:\n// 1. Add its name to the BackgroundVariant union type below\n// 2. Create a pattern component with PatternProps\n// 3. Register it in PATTERN_MAP\n\nexport type BackgroundVariant =\n  | \"dots\"\n  | \"grid\"\n  | \"cross-hatch\"\n  | \"diagonal-lines\"\n  | \"plus\"\n  | \"falling-triangles\"\n  | \"4-pointed-star\"\n  | \"tiny-checkers\"\n  | \"overlapping-circles\"\n  | \"wiggle-lines\"\n  | \"bubbles\";\n\n// ── Pattern Components ───────────────────────────────────────────────────────\n\ntype PatternProps = { id: string };\n\nconst DotsPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\n    <circle className=\"text-border dark:text-border\" cx=\"2\" cy=\"2\" r=\"1\" fill=\"currentColor\" />\n  </pattern>\n);\n\nconst GridPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M 20 0 L 0 0 0 20\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n    />\n  </pattern>\n);\n\nconst CrossHatchPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border/60 dark:text-border/50\"\n      d=\"M 0 0 L 20 20 M 20 0 L 0 20\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n    />\n  </pattern>\n);\n\nconst DiagonalLinesPattern = ({ id }: PatternProps) => (\n  <pattern\n    id={id}\n    x=\"0\"\n    y=\"0\"\n    width=\"6\"\n    height=\"6\"\n    patternUnits=\"userSpaceOnUse\"\n    patternTransform=\"rotate(45)\"\n  >\n    <line\n      className=\"text-border dark:text-border\"\n      x1=\"0\"\n      y1=\"0\"\n      x2=\"0\"\n      y2=\"6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n    />\n  </pattern>\n);\n\nconst PlusPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M 8 4 L 8 12 M 4 8 L 12 8\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n      strokeLinecap=\"round\"\n    />\n  </pattern>\n);\n\nconst FallingTrianglesPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"18\" height=\"36\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M2 6h12L8 18 2 6zm18 36h12l-6 12-6-12z\"\n      transform=\"scale(0.5)\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst FourPointedStarPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">\n    <polygon\n      className=\"text-border dark:text-border\"\n      fillRule=\"evenodd\"\n      points=\"5 3 8 4 5 5 4 8 3 5 0 4 3 3 4 0 5 3\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst TinyCheckersPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      fillRule=\"evenodd\"\n      d=\"M0 0h4v4H0V0zm4 4h4v4H4V4z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.2\"\n    />\n  </pattern>\n);\n\nconst OverlappingCirclesPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"40\" height=\"40\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      fillRule=\"evenodd\"\n      d=\"M25 25c0-2.762 2.238-5 5-5s5 2.238 5 5-2.238 5-5 5c0 2.762-2.238 5-5 5s-5-2.238-5-5 2.238-5 5-5zM5 5c0-2.762 2.238-5 5-5s5 2.238 5 5-2.238 5-5 5c0 2.762-2.238 5-5 5S0 12.762 0 10s2.238-5 5-5zm5 4c2.209 0 4-1.791 4-4s-1.791-4-4-4-4 1.791-4 4 1.791 4 4 4zm20 20c2.209 0 4-1.791 4-4s-1.791-4-4-4-4 1.791-4 4 1.791 4 4 4z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst WiggleLinesPattern = ({ id }: PatternProps) => (\n  <pattern\n    id={id}\n    x=\"0\"\n    y=\"0\"\n    width=\"52\"\n    height=\"26\"\n    patternUnits=\"userSpaceOnUse\"\n    patternTransform=\"scale(0.6)\"\n  >\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst BubblesPattern = ({ id }: PatternProps) => (\n  <pattern\n    id={id}\n    x=\"0\"\n    y=\"0\"\n    width=\"100\"\n    height=\"100\"\n    patternUnits=\"userSpaceOnUse\"\n    patternTransform=\"scale(0.6667)\"\n  >\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n      fillRule=\"evenodd\"\n    />\n  </pattern>\n);\n\n// ── Pattern Registry ─────────────────────────────────────────────────────────\n// Map variant names to pattern components\n\nconst PATTERN_MAP: Record<BackgroundVariant, React.FC<PatternProps>> = {\n  dots: DotsPattern,\n  grid: GridPattern,\n  plus: PlusPattern,\n  bubbles: BubblesPattern,\n  \"cross-hatch\": CrossHatchPattern,\n  \"diagonal-lines\": DiagonalLinesPattern,\n  \"falling-triangles\": FallingTrianglesPattern,\n  \"4-pointed-star\": FourPointedStarPattern,\n  \"tiny-checkers\": TinyCheckersPattern,\n  \"overlapping-circles\": OverlappingCirclesPattern,\n  \"wiggle-lines\": WiggleLinesPattern,\n};\n\n// ── Main Component ───────────────────────────────────────────────────────────\n// Usage: Place <ChartBackground variant=\"dots\" /> inside any Recharts chart component.\n// ZIndexLayer with zIndex={-1} ensures the background renders behind all chart content.\n\ninterface ChartBackgroundProps {\n  variant: BackgroundVariant;\n}\n\nexport function ChartBackground({ variant }: ChartBackgroundProps) {\n  const baseId = useId().replace(/:/g, \"\");\n  const patternId = `${baseId}-bg-${variant}`;\n  const maskId = `${baseId}-bg-edge-fade`;\n  const filterId = `${baseId}-bg-blur`;\n  const PatternComponent = PATTERN_MAP[variant];\n\n  return (\n    <ZIndexLayer zIndex={-1}>\n      <defs>\n        <PatternComponent id={patternId} />\n        {/* Gaussian blur filter for soft edge fade */}\n        <filter id={filterId}>\n          <feGaussianBlur stdDeviation=\"25\" />\n        </filter>\n        {/* Mask: a slightly inset white rect with blur creates smooth transparent edges */}\n        <mask id={maskId} maskUnits=\"userSpaceOnUse\">\n          <rect x=\"8%\" y=\"20%\" width=\"85%\" height=\"60%\" fill=\"white\" filter={`url(#${filterId})`} />\n        </mask>\n      </defs>\n      <rect width=\"100%\" height=\"100%\" fill={`url(#${patternId})`} mask={`url(#${maskId})`} />\n    </ZIndexLayer>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/evilcharts/ui/dot.tsx",
      "content": "import { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\nexport type DotVariant = \"default\" | \"border\" | \"colored-border\";\n\ntype ChartDotProps = {\n  cx?: number;\n  cy?: number;\n  dataKey: string;\n  chartId: string;\n  className?: string;\n  fillOpacity?: number;\n  type?: DotVariant;\n};\n\nconst ChartDot = React.memo(function ChartDot({\n  cx,\n  cy,\n  dataKey,\n  chartId,\n  className,\n  fillOpacity = 1,\n  type = \"default\",\n}: ChartDotProps) {\n  const dotId = React.useId().replace(/:/g, \"\");\n  const gradientUrl = `url(#${chartId}-colors-${String(dataKey)})`;\n\n  if (cx === undefined || cy === undefined) return null;\n\n  switch (type) {\n    case \"border\":\n      return (\n        <PrimaryBorderDot\n          cx={cx}\n          cy={cy}\n          dotId={dotId}\n          fillOpacity={fillOpacity}\n          gradientUrl={gradientUrl}\n          className={className}\n        />\n      );\n    case \"colored-border\":\n      return (\n        <ColoredBorderDot\n          cx={cx}\n          cy={cy}\n          dotId={dotId}\n          fillOpacity={fillOpacity}\n          gradientUrl={gradientUrl}\n          className={className}\n        />\n      );\n    default:\n      return (\n        <DefaultDot\n          cx={cx}\n          cy={cy}\n          dotId={dotId}\n          fillOpacity={fillOpacity}\n          gradientUrl={gradientUrl}\n          className={className}\n        />\n      );\n  }\n});\n\ntype DotVariantProps = {\n  cx: number;\n  cy: number;\n  dotId: string;\n  fillOpacity: number;\n  gradientUrl: string;\n  className?: string;\n};\n\nconst DefaultDot = React.memo(\n  ({ cx, cy, dotId, fillOpacity, gradientUrl, className }: DotVariantProps) => {\n    const r = 3;\n    return (\n      <g className={className}>\n        <defs>\n          <clipPath id={`dot-clip-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r} />\n          </clipPath>\n        </defs>\n        {/* Full-width gradient rectangle clipped to dot shape */}\n        <rect\n          x=\"0\"\n          y={cy - r}\n          width=\"100%\"\n          height={r * 2}\n          fill={gradientUrl}\n          fillOpacity={fillOpacity}\n          clipPath={`url(#dot-clip-${dotId})`}\n        />\n      </g>\n    );\n  },\n);\n\nDefaultDot.displayName = \"DefaultDot\";\n\nconst PrimaryBorderDot = React.memo(\n  ({ cx, cy, dotId, fillOpacity, gradientUrl, className }: DotVariantProps) => {\n    const r = 6;\n    const strokeWidth = 5;\n    return (\n      <g className={cn(className, \"text-background\")}>\n        <defs>\n          <clipPath id={`dot-clip-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r} />\n          </clipPath>\n        </defs>\n        {/* Background stroke (border) */}\n        <circle cx={cx} cy={cy} r={r} fill=\"currentColor\" />\n        {/* Inner gradient circle clipped */}\n        <rect\n          x=\"0\"\n          y={cy - (r - strokeWidth / 2)}\n          width=\"100%\"\n          height={(r - strokeWidth / 2) * 2}\n          fill={gradientUrl}\n          fillOpacity={fillOpacity}\n          clipPath={`url(#dot-clip-inner-${dotId})`}\n        />\n        <defs>\n          <clipPath id={`dot-clip-inner-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r - strokeWidth / 2} />\n          </clipPath>\n        </defs>\n      </g>\n    );\n  },\n);\n\nPrimaryBorderDot.displayName = \"PrimaryBorderDot\";\n\nconst ColoredBorderDot = React.memo(\n  ({ cx, cy, dotId, fillOpacity, gradientUrl, className }: DotVariantProps) => {\n    const r = 3;\n    const strokeWidth = 1;\n    return (\n      <g className={cn(className, \"text-background\")}>\n        <defs>\n          <clipPath id={`dot-clip-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r + strokeWidth / 2} />\n          </clipPath>\n        </defs>\n        {/* Gradient stroke (border) via clipped rect */}\n        <rect\n          x=\"0\"\n          y={cy - r - strokeWidth / 2}\n          width=\"100%\"\n          height={(r + strokeWidth / 2) * 2}\n          fill={gradientUrl}\n          fillOpacity={fillOpacity}\n          clipPath={`url(#dot-clip-${dotId})`}\n        />\n        {/* Inner solid fill */}\n        <circle cx={cx} cy={cy} r={r - strokeWidth / 2} fill=\"currentColor\" />\n      </g>\n    );\n  },\n);\n\nColoredBorderDot.displayName = \"ColoredBorderDot\";\n\nexport { ChartDot };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/evilcharts/ui/evil-brush.tsx",
      "content": "\"use client\";\n\nimport { motion, useMotionValue, useMotionValueEvent, useSpring, useTransform } from \"motion/react\";\nimport { ResponsiveContainer, AreaChart, Area, LineChart, Line, BarChart, Bar } from \"recharts\";\nimport { ChartStyle, getColorsCount, type ChartConfig } from \"@/components/evilcharts/ui/chart\";\nimport type { MotionValue } from \"motion/react\";\nimport { useCallback, useEffect, type ComponentProps } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\ntype EvilBrushVariant = \"line\" | \"area\" | \"bar\";\ntype CurveType = ComponentProps<typeof Area>[\"type\"];\n\ninterface EvilBrushRange {\n  startIndex: number;\n  endIndex: number;\n}\n\ninterface EvilBrushProps {\n  /** Full dataset – always rendered in the miniature chart */\n  data: Record<string, unknown>[];\n  /** Chart config with colour definitions */\n  chartConfig: ChartConfig;\n  /** Data keys to plot (default: all keys from chartConfig) */\n  dataKeys?: string[];\n  /** X-axis data key – used for handle labels */\n  xDataKey?: string;\n  /** Visual variant of the mini chart */\n  variant?: EvilBrushVariant;\n  /** Pixel height of the brush */\n  height?: number;\n  /** Extra className */\n  className?: string;\n  /** Whether areas/bars should be stacked in the mini chart */\n  stacked?: boolean;\n  /** Stroke variant for line / area strokes in the mini chart */\n  strokeVariant?: \"solid\" | \"dashed\" | \"animated-dashed\";\n  /** Whether to connect null data points in line / area variants */\n  connectNulls?: boolean;\n  /** Radius for bar corners in the bar variant */\n  barRadius?: number;\n\n  // ── Controlled mode ──────────────────────────────────────────────────\n  /** Controlled start index */\n  startIndex?: number;\n  /** Controlled end index */\n  endIndex?: number;\n\n  // ── Uncontrolled mode ────────────────────────────────────────────────\n  /** Initial start index (uncontrolled) */\n  defaultStartIndex?: number;\n  /** Initial end index (uncontrolled) */\n  defaultEndIndex?: number;\n\n  /** Fired whenever the visible range changes */\n  onChange?: (range: EvilBrushRange) => void;\n  /** Format the handle label from the xDataKey value */\n  formatLabel?: (value: unknown, index: number) => string;\n  /** Curve type for line / area variants */\n  curveType?: CurveType;\n  /** Minimum number of data points that must remain selected */\n  minSpan?: number;\n  /** Whether to render labels on the handles */\n  showLabels?: boolean;\n  /** Skip rendering own ChartStyle (when inside a ChartContainer that already provides CSS vars) */\n  skipStyle?: boolean;\n}\n\n// ─── Spring config ──────────────────────────────────────────────────────────\n\nconst SPRING_CONFIG = { stiffness: 300, damping: 35, mass: 0.8 };\n\n// ─── Pointer-capture drag hook ──────────────────────────────────────────────\n// Replaces raw addEventListener with the modern Pointer Events API.\n// setPointerCapture routes all pointer events to the originating element,\n// so we get mouse + touch + pen support with zero global listeners.\n\ntype DragType = \"left\" | \"right\" | \"middle\";\n\ninterface DragState {\n  type: DragType;\n  originX: number;\n  originRange: EvilBrushRange;\n}\n\nfunction useBrushDrag({\n  range,\n  totalPoints,\n  containerRef,\n  commit,\n}: {\n  range: EvilBrushRange;\n  totalPoints: number;\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  commit: (next: EvilBrushRange, mode?: DragType) => void;\n}) {\n  const dragRef = React.useRef<DragState | null>(null);\n  const [isDragging, setIsDragging] = React.useState(false);\n\n  const toIndexDelta = useCallback(\n    (px: number) => {\n      if (!containerRef.current || totalPoints <= 1) return 0;\n      return Math.round(\n        (px / containerRef.current.getBoundingClientRect().width) * (totalPoints - 1),\n      );\n    },\n    [totalPoints, containerRef],\n  );\n\n  const onPointerDown = useCallback(\n    (e: React.PointerEvent, type: DragType) => {\n      e.preventDefault();\n      (e.target as HTMLElement).setPointerCapture(e.pointerId);\n      dragRef.current = { type, originX: e.clientX, originRange: { ...range } };\n      setIsDragging(true);\n    },\n    [range],\n  );\n\n  const onPointerMove = useCallback(\n    (e: React.PointerEvent) => {\n      const d = dragRef.current;\n      if (!d) return;\n\n      const delta = toIndexDelta(e.clientX - d.originX);\n      const { type, originRange: o } = d;\n\n      if (type === \"left\") {\n        commit({ startIndex: o.startIndex + delta, endIndex: o.endIndex }, \"left\");\n      } else if (type === \"right\") {\n        commit({ startIndex: o.startIndex, endIndex: o.endIndex + delta }, \"right\");\n      } else {\n        const span = o.endIndex - o.startIndex;\n        let s = o.startIndex + delta;\n        let e2 = s + span;\n        if (s < 0) {\n          s = 0;\n          e2 = span;\n        }\n        if (e2 > totalPoints - 1) {\n          e2 = totalPoints - 1;\n          s = Math.max(0, e2 - span);\n        }\n        commit({ startIndex: s, endIndex: e2 }, \"middle\");\n      }\n    },\n    [toIndexDelta, totalPoints, commit],\n  );\n\n  const onPointerUp = useCallback((e: React.PointerEvent) => {\n    (e.target as HTMLElement).releasePointerCapture(e.pointerId);\n    dragRef.current = null;\n    setIsDragging(false);\n  }, []);\n\n  // Helper to bind all three pointer handlers for a given drag type\n  const bind = useCallback(\n    (type: DragType) => ({\n      onPointerDown: (e: React.PointerEvent) => onPointerDown(e, type),\n      onPointerMove,\n      onPointerUp,\n    }),\n    [onPointerDown, onPointerMove, onPointerUp],\n  );\n\n  return { isDragging, bind };\n}\n\n// ─── EvilBrush ────────────────────────────────────────────────────────────\n\nfunction EvilBrush({\n  data,\n  chartConfig,\n  dataKeys,\n  xDataKey,\n  variant = \"area\",\n  height = 56,\n  className,\n  stacked = false,\n  strokeVariant = \"solid\",\n  connectNulls = false,\n  barRadius,\n  startIndex: controlledStart,\n  endIndex: controlledEnd,\n  defaultStartIndex = 0,\n  defaultEndIndex,\n  onChange,\n  formatLabel,\n  curveType = \"monotone\",\n  minSpan = 2,\n  showLabels = true,\n  skipStyle = false,\n}: EvilBrushProps) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const keys = React.useMemo(() => dataKeys ?? Object.keys(chartConfig), [dataKeys, chartConfig]);\n  const totalPoints = data.length;\n  const chartId = React.useId().replace(/:/g, \"\");\n\n  // ── Controlled vs uncontrolled ──────────────────────────────────────────\n\n  const isControlled = controlledStart !== undefined && controlledEnd !== undefined;\n\n  const [internalRange, setInternalRange] = React.useState<EvilBrushRange>(() => ({\n    startIndex: Math.max(0, Math.min(defaultStartIndex, totalPoints - 1)),\n    endIndex: Math.max(0, Math.min(defaultEndIndex ?? totalPoints - 1, totalPoints - 1)),\n  }));\n\n  // Track the last committed range to avoid duplicate updates when small\n  // mouse movements don't produce index changes (e.g., at boundaries)\n  const lastCommittedRef = React.useRef<EvilBrushRange>(internalRange);\n\n  useEffect(() => {\n    if (!isControlled) {\n      setInternalRange((prev) => {\n        const adjusted = {\n          startIndex: Math.min(prev.startIndex, Math.max(0, totalPoints - 1)),\n          endIndex: Math.min(prev.endIndex, Math.max(0, totalPoints - 1)),\n        };\n        lastCommittedRef.current = adjusted;\n        return adjusted;\n      });\n    }\n  }, [totalPoints, isControlled]);\n\n  // ── Clamping & committing ───────────────────────────────────────────────\n\n  const clampRange = useCallback(\n    (range: EvilBrushRange, mode?: DragType): EvilBrushRange => {\n      let { startIndex, endIndex } = range;\n      const maxIndex = Math.max(0, totalPoints - 1);\n\n      startIndex = Math.max(0, Math.min(startIndex, maxIndex));\n      endIndex = Math.max(0, Math.min(endIndex, maxIndex));\n\n      if (mode === \"left\") {\n        const maxStart = Math.max(0, endIndex - minSpan);\n        startIndex = Math.min(startIndex, maxStart);\n        return { startIndex, endIndex };\n      }\n\n      if (mode === \"right\") {\n        const minEnd = Math.min(maxIndex, startIndex + minSpan);\n        endIndex = Math.max(endIndex, minEnd);\n        return { startIndex, endIndex };\n      }\n\n      if (endIndex - startIndex < minSpan) {\n        endIndex = Math.min(startIndex + minSpan, maxIndex);\n        if (endIndex - startIndex < minSpan) {\n          startIndex = Math.max(0, endIndex - minSpan);\n        }\n      }\n      return { startIndex, endIndex };\n    },\n    [totalPoints, minSpan],\n  );\n\n  const commit = useCallback(\n    (next: EvilBrushRange, mode?: DragType) => {\n      const clamped = clampRange(next, mode);\n      const last = lastCommittedRef.current;\n\n      // Only update if the range has actually changed — avoids unnecessary\n      // re-renders when the brush is at a boundary and small mouse movements\n      // don't produce index changes\n      if (last.startIndex === clamped.startIndex && last.endIndex === clamped.endIndex) {\n        return;\n      }\n\n      lastCommittedRef.current = clamped;\n      setInternalRange(clamped);\n      // Defer the parent callback — chart re-render happens at lower priority,\n      // React can skip intermediate frames during fast drags\n      React.startTransition(() => {\n        onChange?.(clamped);\n      });\n    },\n    [clampRange, onChange],\n  );\n\n  // ── Drag ────────────────────────────────────────────────────────────────\n\n  const { isDragging, bind } = useBrushDrag({\n    range: internalRange,\n    totalPoints,\n    containerRef,\n    commit,\n  });\n\n  // Position always driven by internalRange (never lags behind controlled props)\n  const range = internalRange;\n\n  // Sync internalRange with controlled props when not dragging\n  useEffect(() => {\n    if (isControlled && !isDragging) {\n      const syncedRange = { startIndex: controlledStart, endIndex: controlledEnd };\n      // eslint-disable-next-line react-hooks/set-state-in-effect\n      setInternalRange(syncedRange);\n      lastCommittedRef.current = syncedRange;\n    }\n  }, [isControlled, controlledStart, controlledEnd, isDragging]);\n\n  // ── Computed positions (%) ──────────────────────────────────────────────\n\n  const leftPct = totalPoints > 1 ? (range.startIndex / (totalPoints - 1)) * 100 : 0;\n  const rightPct = totalPoints > 1 ? (range.endIndex / (totalPoints - 1)) * 100 : 100;\n\n  // Drive all moving brush UI from the same springed edge values.\n  const leftTarget = useMotionValue(leftPct);\n  const rightTarget = useMotionValue(rightPct);\n  if (leftTarget.get() !== leftPct) leftTarget.set(leftPct);\n  if (rightTarget.get() !== rightPct) rightTarget.set(rightPct);\n\n  const leftSpring = useSpring(leftTarget, SPRING_CONFIG);\n  const rightSpring = useSpring(rightTarget, SPRING_CONFIG);\n  const leftPosition = useTransform(leftSpring, (v) => `${v}%`);\n  const rightPosition = useTransform(rightSpring, (v) => `${v}%`);\n  const leftOverlayWidth = useTransform(leftSpring, (v) => `${v}%`);\n  const rightOverlayWidth = useTransform(rightSpring, (v) => `${Math.max(0, 100 - v)}%`);\n  const selectedWidth = useMotionValue(`${Math.max(0, rightPct - leftPct)}%`);\n\n  const updateSelectedWidth = useCallback(() => {\n    selectedWidth.set(`${Math.max(0, rightSpring.get() - leftSpring.get())}%`);\n  }, [leftSpring, rightSpring, selectedWidth]);\n\n  useMotionValueEvent(leftSpring, \"change\", updateSelectedWidth);\n  useMotionValueEvent(rightSpring, \"change\", updateSelectedWidth);\n\n  const getLabel = useCallback(\n    (idx: number) => {\n      if (!xDataKey) return String(idx);\n      const v = data[idx]?.[xDataKey];\n      return formatLabel ? formatLabel(v, idx) : String(v ?? idx);\n    },\n    [data, xDataKey, formatLabel],\n  );\n\n  // ── Render ──────────────────────────────────────────────────────────────\n\n  if (totalPoints === 0) return null;\n\n  return (\n    <div\n      ref={containerRef}\n      data-chart={skipStyle ? undefined : chartId}\n      className={cn(\"group relative select-none\", className)}\n      style={{ height }}\n    >\n      {!skipStyle && <ChartStyle id={chartId} config={chartConfig} />}\n\n      {/* Mini chart – always shows all data */}\n      <div className=\"absolute inset-0 overflow-hidden rounded-md\">\n        <MiniChart\n          data={data}\n          keys={keys}\n          chartConfig={chartConfig}\n          variant={variant}\n          curveType={curveType}\n          chartId={chartId}\n          stacked={stacked}\n          strokeVariant={strokeVariant === \"animated-dashed\" ? \"dashed\" : strokeVariant}\n          connectNulls={connectNulls}\n          barRadius={barRadius}\n        />\n      </div>\n\n      {/* Dim overlay – left */}\n      <motion.div\n        className=\"bg-background/70 pointer-events-none absolute inset-y-0 left-0 rounded-l-md\"\n        style={{ width: leftOverlayWidth }}\n      />\n      {/* Dim overlay – right */}\n      <motion.div\n        className=\"bg-background/70 pointer-events-none absolute inset-y-0 right-0 rounded-r-md\"\n        style={{ width: rightOverlayWidth }}\n      />\n\n      {/* Selected region – draggable to pan */}\n      <motion.div\n        className=\"absolute inset-y-0 cursor-grab touch-none rounded-sm border active:cursor-grabbing\"\n        style={{ left: leftPosition, width: selectedWidth }}\n        {...bind(\"middle\")}\n      />\n\n      {/* Left handle */}\n      <BrushHandle\n        side=\"left\"\n        position={leftPosition}\n        label={showLabels ? getLabel(range.startIndex) : undefined}\n        bind={bind(\"left\")}\n      />\n\n      {/* Right handle */}\n      <BrushHandle\n        side=\"right\"\n        position={rightPosition}\n        label={showLabels ? getLabel(range.endIndex) : undefined}\n        bind={bind(\"right\")}\n      />\n    </div>\n  );\n}\n\n// ─── Brush Handle ───────────────────────────────────────────────────────────\n\nfunction BrushHandle({\n  side,\n  position,\n  label,\n  bind,\n}: {\n  side: \"left\" | \"right\";\n  position: MotionValue<string>;\n  label?: string;\n  bind: {\n    onPointerDown: (e: React.PointerEvent) => void;\n    onPointerMove: (e: React.PointerEvent) => void;\n    onPointerUp: (e: React.PointerEvent) => void;\n  };\n}) {\n  const isLeft = side === \"left\";\n\n  return (\n    <motion.div className=\"absolute inset-y-0 z-10\" style={{ left: position }}>\n      <div\n        className={cn(\n          \"group absolute inset-y-0 flex w-3 cursor-ew-resize touch-none items-center justify-center after:absolute after:inset-y-0 after:-left-4 after:w-11 after:content-['']\",\n          isLeft ? \"\" : \"-translate-x-full\",\n        )}\n        {...bind}\n      >\n        <div\n          className={cn(\n            \"bg-muted-foreground group-hover:bg-foreground relative flex h-4 w-1.5 items-center justify-center rounded-md transition-colors\",\n            isLeft ? \"-left-[5.5px]\" : \"-right-[5.5px]\",\n          )}\n        >\n          <div className=\"flex flex-col gap-[2px]\">\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n          </div>\n        </div>\n      </div>\n\n      {label && (\n        <div\n          className={cn(\n            \"bg-foreground text-background pointer-events-none absolute -bottom-3 -translate-y-1/2 rounded-[3px] px-1 py-px text-[8px] leading-tight font-medium whitespace-nowrap opacity-0 group-hover:opacity-100\",\n            isLeft ? \"left-1.5\" : \"right-1.5\",\n          )}\n        >\n          {label}\n        </div>\n      )}\n    </motion.div>\n  );\n}\n\n// ─── Mini Chart ─────────────────────────────────────────────────────────────\n\nfunction MiniChart({\n  data,\n  keys,\n  chartConfig,\n  variant,\n  curveType,\n  chartId,\n  stacked,\n  strokeVariant = \"solid\",\n  connectNulls = false,\n  barRadius,\n}: {\n  data: Record<string, unknown>[];\n  keys: string[];\n  chartConfig: ChartConfig;\n  variant: EvilBrushVariant;\n  curveType: CurveType;\n  chartId: string;\n  stacked: boolean;\n  strokeVariant?: \"solid\" | \"dashed\" | \"animated-dashed\";\n  connectNulls?: boolean;\n  barRadius?: number;\n}) {\n  const gradients = React.useMemo(\n    () =>\n      Object.entries(chartConfig)\n        .filter(([key]) => keys.includes(key))\n        .map(([dataKey, config]) => ({\n          dataKey,\n          colorsCount: getColorsCount(config),\n        })),\n    [chartConfig, keys],\n  );\n\n  const dashArray =\n    strokeVariant === \"dashed\" || strokeVariant === \"animated-dashed\" ? \"4 4\" : undefined;\n\n  const defsContent = (\n    <>\n      {/* Vertical fade gradient for area fill mask */}\n      {variant === \"area\" && (\n        <linearGradient id={`${chartId}-zm-vertical-fade`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.15} />\n          <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n        </linearGradient>\n      )}\n      {gradients.map(({ dataKey, colorsCount }) => {\n        const colorStops =\n          colorsCount === 1 ? (\n            <>\n              <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n              <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n            </>\n          ) : (\n            Array.from({ length: colorsCount }, (_, i) => (\n              <stop\n                key={i}\n                offset={`${(i / (colorsCount - 1)) * 100}%`}\n                stopColor={`var(--color-${dataKey}-${i}, var(--color-${dataKey}-0))`}\n              />\n            ))\n          );\n\n        return (\n          <React.Fragment key={dataKey}>\n            {/* Vertical color gradient (stroke + bar fill) */}\n            <linearGradient id={`${chartId}-zm-${dataKey}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              {colorStops}\n            </linearGradient>\n\n            {/* Area fill: color gradient masked with vertical fade */}\n            {variant === \"area\" && (\n              <>\n                <mask id={`${chartId}-zm-fill-mask-${dataKey}`}>\n                  <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-zm-vertical-fade)`} />\n                </mask>\n                <pattern\n                  id={`${chartId}-zm-fill-${dataKey}`}\n                  patternUnits=\"userSpaceOnUse\"\n                  width=\"100%\"\n                  height=\"100%\"\n                >\n                  <rect\n                    width=\"100%\"\n                    height=\"100%\"\n                    fill={`url(#${chartId}-zm-${dataKey})`}\n                    mask={`url(#${chartId}-zm-fill-mask-${dataKey})`}\n                  />\n                </pattern>\n              </>\n            )}\n          </React.Fragment>\n        );\n      })}\n    </>\n  );\n\n  if (variant === \"line\") {\n    return (\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\n        <LineChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>\n          <defs>{defsContent}</defs>\n          {keys.map((dk) => (\n            <Line\n              key={dk}\n              type={curveType}\n              dataKey={dk}\n              stroke={`url(#${chartId}-zm-${dk})`}\n              strokeWidth={1}\n              strokeOpacity={0.5}\n              strokeDasharray={dashArray}\n              connectNulls={connectNulls}\n              dot={false}\n              activeDot={false}\n              isAnimationActive={false}\n            />\n          ))}\n        </LineChart>\n      </ResponsiveContainer>\n    );\n  }\n\n  if (variant === \"bar\") {\n    const r = barRadius ?? 3;\n    return (\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\n        <BarChart\n          data={data}\n          margin={{ top: 2, right: 0, bottom: 0, left: 0 }}\n          barGap={2}\n          barSize={14}\n        >\n          <defs>{defsContent}</defs>\n          {keys.map((dk) => (\n            <Bar\n              key={dk}\n              dataKey={dk}\n              fill={`url(#${chartId}-zm-${dk})`}\n              fillOpacity={0.35}\n              stackId={stacked ? \"zm-stack\" : undefined}\n              isAnimationActive={false}\n              radius={[r, r, r, r]}\n            />\n          ))}\n        </BarChart>\n      </ResponsiveContainer>\n    );\n  }\n\n  // Default: area\n  return (\n    <ResponsiveContainer width=\"100%\" height=\"100%\">\n      <AreaChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>\n        <defs>{defsContent}</defs>\n        {keys.map((dk) => (\n          <Area\n            key={dk}\n            type={curveType}\n            dataKey={dk}\n            stroke={`url(#${chartId}-zm-${dk})`}\n            fill={`url(#${chartId}-zm-fill-${dk})`}\n            strokeWidth={1}\n            strokeOpacity={0.5}\n            strokeDasharray={dashArray}\n            connectNulls={connectNulls}\n            fillOpacity={1}\n            stackId={stacked ? \"zm-stack\" : undefined}\n            dot={false}\n            activeDot={false}\n            isAnimationActive={false}\n          />\n        ))}\n      </AreaChart>\n    </ResponsiveContainer>\n  );\n}\n\n// ─── useEvilBrush Hook ──────────────────────────────────────────────────────\n\nfunction useEvilBrush<TData extends Record<string, unknown>>({\n  data,\n  defaultStartIndex = 0,\n  defaultEndIndex,\n}: {\n  data: TData[];\n  defaultStartIndex?: number;\n  defaultEndIndex?: number;\n}) {\n  const [range, setRange] = React.useState<EvilBrushRange>({\n    startIndex: defaultStartIndex,\n    endIndex: defaultEndIndex ?? Math.max(0, data.length - 1),\n  });\n\n  // Defer the range used for data slicing — the brush handles move at the\n\n  // immediate `range` cadence while the expensive chart re-render uses the\n  // deferred value.  React can skip intermediate slices during fast drags.\n  const deferredRange = React.useDeferredValue(range);\n\n  useEffect(() => {\n    // eslint-disable-next-line react-hooks/set-state-in-effect\n    setRange({\n      startIndex: 0,\n      endIndex: Math.max(0, data.length - 1),\n    });\n  }, [data.length]);\n\n  const visibleData = React.useMemo(\n    () => data.slice(deferredRange.startIndex, deferredRange.endIndex + 1),\n    [data, deferredRange.startIndex, deferredRange.endIndex],\n  );\n\n  return {\n    range,\n    visibleData,\n    brushProps: {\n      startIndex: range.startIndex,\n      endIndex: range.endIndex,\n      onChange: setRange,\n    } satisfies Pick<EvilBrushProps, \"startIndex\" | \"endIndex\" | \"onChange\">,\n  };\n}\n\nexport { EvilBrush, useEvilBrush, type EvilBrushProps, type EvilBrushRange, type EvilBrushVariant };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}