{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "area-chart",
  "title": "Area Chart",
  "description": "A styled EvilCharts area chart for showing trends with filled ranges, gradients, brushes, and selection states.",
  "dependencies": [
    "motion",
    "recharts"
  ],
  "registryDependencies": [
    "https://ui.corr.sh/r/chart-empty-loading-error.json"
  ],
  "files": [
    {
      "path": "src/components/evilcharts/charts/area-chart.tsx",
      "content": "\"use client\"\n\nimport {\n  axisValueToPercentFormatter,\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  getLoadingData,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\"\nimport {\n  EvilBrush,\n  useEvilBrush,\n  type EvilBrushRange,\n} from \"@/components/evilcharts/ui/evil-brush\"\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\"\nimport {\n  useCallback,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n} from \"react\"\nimport {\n  Area,\n  AreaChart,\n  CartesianGrid,\n  LabelList,\n  ReferenceLine,\n  XAxis,\n  YAxis,\n} from \"recharts\"\nimport {\n  ChartBackground,\n  type BackgroundVariant,\n} from \"@/components/evilcharts/ui/background\"\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\"\nimport { ChartDot, type DotVariant } from \"@/components/evilcharts/ui/dot\"\nimport { motion } from \"motion/react\"\n\n// Constants\nconst STROKE_WIDTH = 0.8\nconst LOADING_AREA_DATA_KEY = \"loading\"\nconst LOADING_ANIMATION_DURATION = 2000 // in milliseconds\n\ntype ChartProps = ComponentProps<typeof AreaChart>\ntype XAxisProps = ComponentProps<typeof XAxis>\ntype YAxisProps = ComponentProps<typeof YAxis>\ntype LabelListProps = ComponentProps<typeof LabelList>\ntype AreaType = ComponentProps<typeof Area>[\"type\"]\ntype AreaVariant =\n  | \"gradient\"\n  | \"gradient-reverse\"\n  | \"solid\"\n  | \"dotted\"\n  | \"lines\"\n  | \"hatched\"\ntype StrokeVariant = \"solid\" | \"dashed\" | \"animated-dashed\"\ntype StackType = \"default\" | \"expanded\" | \"stacked\"\n\n// Validating Tyes to make sure user have provided valid data according to chartConfig\ntype ValidateConfigKeys<TData, TConfig> = {\n  [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never\n}\n\ntype BaseEvilAreaChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  chartConfig: TConfig & ValidateConfigKeys<TData, TConfig>\n  data: TData[]\n  xDataKey?: keyof TData & string\n  yDataKey?: keyof TData & string\n  className?: string\n  chartProps?: ChartProps\n  xAxisProps?: XAxisProps\n  yAxisProps?: YAxisProps\n  defaultSelectedDataKey?: string | null\n  curveType?: AreaType\n  areaVariant?: AreaVariant\n  strokeVariant?: StrokeVariant\n  stackType?: StackType\n  dotVariant?: DotVariant\n  activeDotVariant?: DotVariant\n  showLabels?: boolean\n  labelListProps?: Omit<LabelListProps, \"dataKey\">\n  legendVariant?: ChartLegendVariant\n  connectNulls?: boolean\n  tickGap?: number\n  // Hide Stuffs\n  hideTooltip?: boolean\n  hideCartesianGrid?: boolean\n  hideLegend?: boolean\n  hideCursorLine?: boolean\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness\n  tooltipVariant?: TooltipVariant\n  tooltipDefaultIndex?: number\n  isLoading?: boolean\n  loadingPoints?: number\n  // Brush\n  showBrush?: boolean\n  brushHeight?: number\n  brushFormatLabel?: (value: unknown, index: number) => string\n  onBrushChange?: (range: EvilBrushRange) => void\n  // Background\n  backgroundVariant?: BackgroundVariant\n}\n\ntype EvilAreaChartClickable = {\n  isClickable: true\n  onSelectionChange?: (selectedDataKey: string | null) => void\n}\n\ntype EvilAreaChartNotClickable = {\n  isClickable?: false\n  onSelectionChange?: never\n}\n\ntype EvilAreaChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = BaseEvilAreaChartProps<TData, TConfig> &\n  (EvilAreaChartClickable | EvilAreaChartNotClickable)\n\nexport function EvilAreaChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  chartConfig,\n  data,\n  xDataKey,\n  yDataKey,\n  className,\n  chartProps,\n  xAxisProps,\n  yAxisProps,\n  defaultSelectedDataKey = null,\n  curveType = \"linear\",\n  areaVariant = \"gradient\",\n  strokeVariant = \"dashed\",\n  stackType = \"default\",\n  dotVariant,\n  activeDotVariant,\n  showLabels = false,\n  labelListProps,\n  legendVariant,\n  connectNulls = false,\n  tickGap = 8,\n  hideTooltip = false,\n  hideCartesianGrid = false,\n  hideLegend = false,\n  hideCursorLine = false,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  isLoading = false,\n  loadingPoints,\n  showBrush = false,\n  brushHeight,\n  brushFormatLabel,\n  onBrushChange,\n  onSelectionChange,\n  backgroundVariant,\n}: EvilAreaChartProps<TData, TConfig>) {\n  const [selectedDataKey, setSelectedDataKey] = useState<string | null>(\n    defaultSelectedDataKey\n  )\n  const { loadingData, onShimmerExit } = useLoadingData(\n    isLoading,\n    loadingPoints\n  )\n  const chartId = useId().replace(/:/g, \"\") // Remove colons for valid CSS selectors\n\n  // ── Zoom state ──────────────────────────────────────────────────────────\n  const { visibleData, brushProps } = useEvilBrush({ data })\n  const displayData = showBrush && !isLoading ? visibleData : data\n\n  // Wrapper function to update state and call parent callback\n  // Only call callback when isClickable is true\n  const handleSelectionChange = useCallback(\n    (newSelectedDataKey: string | null) => {\n      setSelectedDataKey(newSelectedDataKey)\n      if (isClickable && onSelectionChange) {\n        onSelectionChange(newSelectedDataKey)\n      }\n    },\n    [onSelectionChange, isClickable]\n  )\n\n  const isExpanded = stackType === \"expanded\"\n  const isStacked = stackType === \"stacked\" || stackType === \"expanded\"\n\n  return (\n    <ChartContainer\n      className={className}\n      config={chartConfig}\n      footer={\n        showBrush &&\n        !isLoading && (\n          <EvilBrush\n            data={data}\n            chartConfig={chartConfig}\n            xDataKey={xDataKey}\n            variant=\"area\"\n            curveType={curveType}\n            strokeVariant={strokeVariant}\n            connectNulls={connectNulls}\n            height={brushHeight}\n            formatLabel={brushFormatLabel}\n            stacked={isStacked}\n            skipStyle\n            className=\"mt-1\"\n            {...brushProps}\n            onChange={(range) => {\n              brushProps.onChange(range)\n              onBrushChange?.(range)\n            }}\n          />\n        )\n      }\n    >\n      <LoadingIndicator isLoading={isLoading} />\n      <AreaChart\n        id=\"evil-charts-area-chart\"\n        accessibilityLayer\n        stackOffset={isExpanded ? \"expand\" : undefined}\n        data={isLoading ? loadingData : displayData}\n        {...chartProps}\n      >\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        <ReferenceLine color=\"white\" />\n        {!hideCartesianGrid && !backgroundVariant && (\n          <CartesianGrid vertical={false} strokeDasharray=\"3 3\" />\n        )}\n        {!hideLegend && (\n          <ChartLegend\n            verticalAlign=\"top\"\n            align=\"right\"\n            content={\n              <ChartLegendContent\n                selected={selectedDataKey}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {xDataKey && !isLoading && (\n          <XAxis\n            dataKey={xDataKey}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            {...xAxisProps}\n          />\n        )}\n        {yDataKey && !isLoading && (\n          <YAxis\n            dataKey={yDataKey}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            width=\"auto\"\n            tickFormatter={\n              stackType === \"expanded\"\n                ? axisValueToPercentFormatter\n                : yAxisProps?.tickFormatter\n            }\n            {...yAxisProps}\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            cursor={\n              hideCursorLine\n                ? false\n                : {\n                    strokeDasharray:\n                      strokeVariant === \"dashed\" ||\n                      strokeVariant === \"animated-dashed\"\n                        ? \"3 3\"\n                        : undefined,\n                    strokeWidth: STROKE_WIDTH,\n                  }\n            }\n            content={\n              <ChartTooltipContent\n                selected={selectedDataKey}\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n        {!isLoading &&\n          Object.keys(chartConfig).map((dataKey) => {\n            const _opacity = getOpacity(isClickable, selectedDataKey, dataKey)\n            const isSelected = selectedDataKey === dataKey\n            const hasSelection = selectedDataKey !== null\n\n            // Get fill pattern based on variant and selection state\n            const fillPattern = getFillPattern(\n              areaVariant,\n              isClickable,\n              hasSelection,\n              isSelected,\n              dataKey,\n              chartId\n            )\n\n            const dot = dotVariant ? (\n              <ChartDot\n                fillOpacity={_opacity.dot}\n                type={dotVariant}\n                dataKey={dataKey}\n                chartId={chartId}\n              />\n            ) : (\n              false\n            )\n            const activeDot = activeDotVariant ? (\n              <ChartDot\n                fillOpacity={_opacity.dot}\n                type={activeDotVariant}\n                dataKey={dataKey}\n                chartId={chartId}\n              />\n            ) : (\n              false\n            )\n\n            return (\n              <Area\n                type={curveType}\n                key={dataKey}\n                dataKey={dataKey}\n                connectNulls={connectNulls}\n                fillOpacity={_opacity.fill}\n                strokeOpacity={_opacity.stroke}\n                fill={fillPattern}\n                stroke={`url(#${chartId}-colors-${dataKey})`}\n                stackId={isStacked ? \"evil-stacked\" : undefined}\n                dot={dot}\n                activeDot={activeDot}\n                strokeWidth={STROKE_WIDTH}\n                strokeDasharray={\n                  strokeVariant === \"dashed\"\n                    ? \"3 3\"\n                    : strokeVariant === \"animated-dashed\"\n                      ? \"3 3\"\n                      : undefined\n                }\n                style={isClickable ? { cursor: \"pointer\" } : undefined}\n                onClick={() => {\n                  if (!isClickable) return\n                  // Toggle: if already selected, unselect; otherwise select\n                  handleSelectionChange(\n                    selectedDataKey === dataKey ? null : dataKey\n                  )\n                }}\n              >\n                {showLabels && (\n                  <LabelList\n                    dataKey={dataKey}\n                    position=\"top\"\n                    className=\"fill-muted-foreground text-[10px]\"\n                    {...labelListProps}\n                  />\n                )}\n                {strokeVariant === \"animated-dashed\" && !hasSelection && (\n                  <AnimatedDashedStyle />\n                )}\n              </Area>\n            )\n          })}\n        {/* ======== LOADING AREA ======== */}\n        {isLoading && (\n          <Area\n            type={curveType}\n            dataKey={LOADING_AREA_DATA_KEY}\n            fillOpacity={0.05}\n            min={0}\n            max={100}\n            fill=\"currentColor\"\n            stroke=\"currentColor\"\n            strokeOpacity={0.5}\n            isAnimationActive={false}\n            legendType=\"none\"\n            tooltipType=\"none\"\n            activeDot={false}\n            dot={false}\n            style={{ mask: `url(#${chartId}-loading-mask)` }}\n          />\n        )}\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {isLoading && (\n            <LoadingAreaPatternStyle\n              chartId={chartId}\n              onShimmerExit={onShimmerExit}\n            />\n          )}\n          {/* Shared horizontal color gradient - always rendered for stroke and all variants */}\n          <HorizontalColorGradientStyle\n            chartConfig={chartConfig}\n            chartId={chartId}\n            isExpanded={isExpanded}\n          />\n          {/* Variant-specific styles */}\n          {areaVariant === \"gradient\" && (\n            <LinearGradientStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"gradient-reverse\" && (\n            <ReverseGradientStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"lines\" && (\n            <LinesPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"solid\" && (\n            <SolidPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"dotted\" && (\n            <DottedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"hatched\" && (\n            <HatchedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          <UnselectedDiagonalPatternStyle\n            chartConfig={chartConfig}\n            chartId={chartId}\n            selectedDataKey={selectedDataKey}\n            isClickable={isClickable}\n          />\n        </defs>\n      </AreaChart>\n    </ChartContainer>\n  )\n}\n\n// Returns opacity object for both fill and stroke, same values for both\nconst getOpacity = (\n  isClickable: boolean,\n  selectedDataKey: string | null,\n  dataKey: string\n) => {\n  if (!isClickable || selectedDataKey === null) {\n    return { fill: 0.8, stroke: 0.8, dot: 1 }\n  }\n  return selectedDataKey === dataKey\n    ? { fill: 0.8, stroke: 0.8, dot: 1 }\n    : { fill: 0.2, stroke: 0.3, dot: 0.3 }\n}\n\n// Returns the appropriate fill pattern based on variant and selection state\nconst getFillPattern = (\n  variant: AreaVariant,\n  isClickable: boolean,\n  hasSelection: boolean,\n  isSelected: boolean,\n  dataKey: string,\n  chartId: string\n): string => {\n  // If clickable and there's a selection but this item is not selected, use unselected diagonal pattern\n  if (isClickable && hasSelection && !isSelected) {\n    return `url(#${chartId}-unselected-${dataKey})`\n  }\n\n  // Otherwise, use the variant-specific pattern\n  switch (variant) {\n    case \"gradient\":\n      return `url(#${chartId}-gradient-${dataKey})`\n    case \"gradient-reverse\":\n      return `url(#${chartId}-gradient-reverse-${dataKey})`\n    case \"solid\":\n      return `url(#${chartId}-solid-${dataKey})`\n    case \"dotted\":\n      return `url(#${chartId}-dotted-${dataKey})`\n    case \"lines\":\n      return `url(#${chartId}-lines-${dataKey})`\n    case \"hatched\":\n      return `url(#${chartId}-hatched-pattern-${dataKey})`\n    default:\n      return `url(#${chartId}-${dataKey})`\n  }\n}\n\n// Animated dashed-stroke style for the area chart\nconst AnimatedDashedStyle = () => {\n  return (\n    <>\n      <animate\n        attributeName=\"stroke-dasharray\"\n        values=\"3 3; 0 3; 3 3\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        keyTimes=\"0;0.5;1\"\n      />\n      <animate\n        attributeName=\"stroke-dashoffset\"\n        values=\"0; -6\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        keyTimes=\"0;1\"\n      />\n    </>\n  )\n}\n\n// Shared horizontal color gradient (left to right) - used by all variants and stroke\n// This is ALWAYS rendered so colors are available for any variant\nconst HorizontalColorGradientStyle = ({\n  chartConfig,\n  chartId,\n  isExpanded = false,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n  isExpanded?: boolean\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config)\n\n        return (\n          <linearGradient\n            key={`${chartId}-colors-${dataKey}`}\n            id={`${chartId}-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"0\"\n            gradientUnits={isExpanded ? \"userSpaceOnUse\" : \"objectBoundingBox\"}\n          >\n            {colorsCount === 1 ? (\n              // Single color: same color at start and end\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              // Multiple colors: distribute evenly\n              // Fallback to first color if index doesn't exist in current theme\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// Linear gradient variant - adds vertical fade mask on top of the shared color gradient\nconst LinearGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n}) => {\n  return (\n    <>\n      {/* Vertical fade gradient for mask */}\n      <linearGradient\n        id={`${chartId}-vertical-fade`}\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"0\"\n        y2=\"1\"\n      >\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.1} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-gradient-group-${dataKey}`}>\n          {/* Mask for vertical fade (top visible, bottom transparent) */}\n          <mask id={`${chartId}-gradient-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-vertical-fade)`}\n            />\n          </mask>\n\n          {/* Pattern combining shared color gradient + vertical mask */}\n          <pattern\n            id={`${chartId}-gradient-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-gradient-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  )\n}\n\n// Reverse gradient for the area chart - vertical fade (top transparent, bottom visible)\nconst ReverseGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n}) => {\n  return (\n    <>\n      {/* Vertical reverse fade gradient for mask */}\n      <linearGradient\n        id={`${chartId}-vertical-fade-reverse`}\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"0\"\n        y2=\"1\"\n      >\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.1} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-gradient-reverse-group-${dataKey}`}>\n          {/* Mask for reverse vertical fade */}\n          <mask id={`${chartId}-gradient-reverse-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-vertical-fade-reverse)`}\n            />\n          </mask>\n\n          {/* Pattern: horizontal gradient + reverse vertical mask */}\n          <pattern\n            id={`${chartId}-gradient-reverse-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-gradient-reverse-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  )\n}\n\n// Lines pattern for the area chart - diagonal lines with gradient\nconst LinesPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n}) => {\n  return (\n    <>\n      {/* Shared diagonal lines pattern for mask */}\n      <pattern\n        id={`${chartId}-lines-mask-pattern`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"5\"\n        height=\"5\"\n        patternTransform=\"rotate(45)\"\n      >\n        <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke=\"white\" strokeWidth=\"1\" />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-lines-group-${dataKey}`}>\n          {/* Mask using diagonal lines */}\n          <mask id={`${chartId}-lines-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-lines-mask-pattern)`}\n              fillOpacity=\"0.3\"\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by diagonal lines */}\n          <pattern\n            id={`${chartId}-lines-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-lines-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  )\n}\n\n// Solid pattern for the area chart - uniform opacity with gradient\nconst SolidPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n}) => {\n  return (\n    <>\n      {/* Uniform opacity mask for solid fill */}\n      <linearGradient\n        id={`${chartId}-solid-mask-gradient`}\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"0\"\n        y2=\"1\"\n      >\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.1} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.1} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-solid-group-${dataKey}`}>\n          {/* Mask for uniform opacity */}\n          <mask id={`${chartId}-solid-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-solid-mask-gradient)`}\n            />\n          </mask>\n\n          {/* Pattern: gradient fill with uniform opacity mask */}\n          <pattern\n            id={`${chartId}-solid-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-solid-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  )\n}\n\n// Dotted pattern for the area chart - dots with gradient\nconst DottedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n}) => {\n  return (\n    <>\n      {/* Shared dots pattern for mask */}\n      <pattern\n        id={`${chartId}-dotted-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"6\"\n        height=\"6\"\n        patternUnits=\"userSpaceOnUse\"\n      >\n        <circle cx=\"4\" cy=\"4\" r=\"0.5\" fill=\"white\" />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-dotted-group-${dataKey}`}>\n          {/* Mask using dots pattern */}\n          <mask id={`${chartId}-dotted-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-dotted-mask-pattern)`}\n              fillOpacity=\"0.5\"\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by dots */}\n          <pattern\n            id={`${chartId}-dotted-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-dotted-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  )\n}\n\n// Diagonal lines pattern for non-selected areas\nconst UnselectedDiagonalPatternStyle = ({\n  chartConfig,\n  chartId,\n  selectedDataKey,\n  isClickable,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n  selectedDataKey: string | null\n  isClickable: boolean\n}) => {\n  if (!isClickable || selectedDataKey === null) return null\n\n  return (\n    <>\n      {/* Shared diagonal lines pattern for mask (white lines) */}\n      <pattern\n        id={`${chartId}-unselected-lines-mask-pattern`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"5\"\n        height=\"5\"\n        patternTransform=\"rotate(45)\"\n      >\n        <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke=\"white\" strokeWidth=\"1\" />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => {\n        const isSelected = selectedDataKey === dataKey\n        if (isSelected) return null\n\n        return (\n          <g key={`${chartId}-unselected-group-${dataKey}`}>\n            {/* Mask using diagonal lines pattern */}\n            <mask id={`${chartId}-unselected-mask-${dataKey}`}>\n              <rect\n                width=\"100%\"\n                height=\"100%\"\n                fill={`url(#${chartId}-unselected-lines-mask-pattern)`}\n                fillOpacity=\"0.3\"\n              />\n            </mask>\n\n            {/* Pattern: gradient fill masked by diagonal lines */}\n            <pattern\n              id={`${chartId}-unselected-${dataKey}`}\n              patternUnits=\"userSpaceOnUse\"\n              width=\"100%\"\n              height=\"100%\"\n            >\n              <rect\n                width=\"100%\"\n                height=\"100%\"\n                fill={`url(#${chartId}-colors-${dataKey})`}\n                mask={`url(#${chartId}-unselected-mask-${dataKey})`}\n              />\n            </pattern>\n          </g>\n        )\n      })}\n    </>\n  )\n}\n\n// Hatched pattern with striped gradient effect\nconst HatchedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig\n  chartId: string\n}) => {\n  return (\n    <>\n      {/* Shared hatched stripes mask pattern */}\n      <linearGradient\n        id={`${chartId}-hatched-stripe-gradient`}\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"1\"\n        y2=\"0\"\n      >\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.2} />\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n      </linearGradient>\n      <pattern\n        id={`${chartId}-hatched-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"20\"\n        height=\"10\"\n        patternUnits=\"userSpaceOnUse\"\n        overflow=\"visible\"\n        patternTransform=\"rotate(20)\"\n      >\n        <rect\n          width=\"20\"\n          height=\"10\"\n          fill={`url(#${chartId}-hatched-stripe-gradient)`}\n        />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-hatched-group-${dataKey}`}>\n          {/* Mask using hatched stripes */}\n          <mask id={`${chartId}-hatched-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-hatched-mask-pattern)`}\n              fillOpacity=\"0.2\"\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by hatched stripes */}\n          <pattern\n            id={`${chartId}-hatched-pattern-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-hatched-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  )\n}\n\n// Generate gradient stops with smooth easing for loading animation\nconst generateEasedGradientStops = (\n  steps: number = 17,\n  minOpacity: number = 0.05,\n  maxOpacity: number = 0.9\n) => {\n  return Array.from({ length: steps }, (_, i) => {\n    const t = i / (steps - 1) // 0 to 1\n    // Sine-based bell curve easing: peaks at center (t=0.5), smooth falloff at edges\n    const eased = Math.sin(t * Math.PI) ** 2\n    const opacity = minOpacity + eased * (maxOpacity - minOpacity)\n    return {\n      offset: `${(t * 100).toFixed(0)}%`,\n      opacity: Number(opacity.toFixed(3)),\n    }\n  })\n}\n\n/**\n * Hook to manage loading data with pixel-perfect shimmer synchronization.\n *\n * Uses motion.dev's onAnimationComplete callback to ensure chart data\n * is only regenerated when the shimmer has completely exited the visible area.\n * This eliminates timing drift issues from setTimeout/setInterval.\n *\n * The shimmer pattern has 200-300% width so that when the visible shimmer\n * exits the chart container (at the 100% point), we can safely swap data\n * while the invisible portion continues animating.\n */\nexport function useLoadingData(isLoading: boolean, loadingPoints: number = 14) {\n  const [loadingDataKey, setLoadingDataKey] = useState(false)\n\n  // Callback fired by motion.dev when shimmer exits visible area\n  const onShimmerExit = useCallback(() => {\n    if (isLoading) {\n      setLoadingDataKey((prev) => !prev)\n    }\n  }, [isLoading])\n\n  const loadingData = useMemo(\n    () => getLoadingData(loadingPoints),\n    // loadingDataKey toggle triggers re-computation when shimmer exits\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [loadingPoints, loadingDataKey]\n  )\n\n  return { loadingData, onShimmerExit }\n}\n\n/**\n * Loading area pattern with animated skeleton effect using motion.dev\n *\n * Key design for pixel-perfect sync:\n * - Visible chart area is normalized to 0-1 in objectBoundingBox units\n * - Shimmer gradient has width=1 (same as visible area)\n * - Pattern width is 3x (300%) to provide buffer on both sides\n * - Animation: x goes from -1 (off-screen left) to 2 (off-screen right)\n * - At x=-1: shimmer is completely outside left edge\n * - At x=0: shimmer starts entering from left\n * - At x=1: shimmer has fully exited right edge\n * - At x=2: shimmer is in the right buffer zone\n * - onShimmerExit fires when x crosses 1 (shimmer fully exited visible area)\n * - Data swaps happen while shimmer is outside visible area (x >= 1)\n * - Loop continues infinitely\n */\nconst LoadingAreaPatternStyle = ({\n  chartId,\n  onShimmerExit,\n}: {\n  chartId: string\n  onShimmerExit: () => void\n}) => {\n  const gradientStops = generateEasedGradientStops()\n\n  // Pattern width needs to accommodate: 1 (left buffer) + 1 (visible) + 1 (right buffer) = 3\n  const patternWidth = 3\n\n  // Animation goes from -1 (left of visible) to 2 (right of visible)\n  // Total travel distance = 3, matching pattern width\n  const startX = -1\n  const endX = 2\n\n  // Track last x value to detect threshold crossing\n  const lastXRef = useRef(startX)\n\n  return (\n    <>\n      {/* Gradient for smooth fade: edges dim, middle bright for sweep effect */}\n      <linearGradient\n        id={`${chartId}-loading-mask-gradient`}\n        x1=\"0\"\n        y1=\"0\"\n        x2=\"1\"\n        y2=\"0\"\n      >\n        {gradientStops.map(({ offset, opacity }) => (\n          <stop\n            key={offset}\n            offset={offset}\n            stopColor=\"white\"\n            stopOpacity={opacity}\n          />\n        ))}\n      </linearGradient>\n      <pattern\n        id={`${chartId}-loading-mask-pattern`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        patternTransform=\"rotate(25)\"\n        width={patternWidth}\n        height=\"1\"\n        x=\"0\"\n        y=\"0\"\n      >\n        {/* Use motion.rect with keyframe animation for precise timing */}\n        <motion.rect\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${chartId}-loading-mask-gradient)`}\n          initial={{ x: startX }}\n          animate={{ x: endX }}\n          transition={{\n            duration: LOADING_ANIMATION_DURATION / 1000,\n            ease: \"linear\",\n            repeat: Infinity,\n            repeatType: \"loop\",\n          }}\n          // Use onUpdate to fire callback at precise exit point\n          onUpdate={(latest) => {\n            const xValue = typeof latest.x === \"number\" ? latest.x : startX\n            const lastX = lastXRef.current\n\n            // Fire when crossing the exit threshold (x >= 1 means shimmer fully exited right)\n            if (xValue >= 1 && lastX < 1) {\n              onShimmerExit()\n            }\n\n            // Update tracked value\n            lastXRef.current = xValue\n          }}\n        />\n      </pattern>\n      {/* Masking */}\n      <mask id={`${chartId}-loading-mask`} maskUnits=\"userSpaceOnUse\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${chartId}-loading-mask-pattern)`}\n        />\n      </mask>\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"
}