{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "recharts-area-chart",
  "description": "Area chart component",
  "dependencies": [
    "recharts",
    "motion"
  ],
  "registryDependencies": [
    "@evilcharts/recharts-chart",
    "@evilcharts/recharts-tooltip",
    "@evilcharts/recharts-legend",
    "@evilcharts/recharts-dot",
    "@evilcharts/recharts-brush",
    "@evilcharts/recharts-background"
  ],
  "files": [
    {
      "path": "src/registry/charts/recharts-area-chart.tsx",
      "content": "\"use client\";\n\nimport {\n  Children,\n  createContext,\n  isValidElement,\n  use,\n  useCallback,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n  type FC,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\nimport {\n  axisValueToPercentFormatter,\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  getLoadingData,\n  LoadingIndicator,\n} from \"@/registry/ui/recharts-chart\";\nimport {\n  Area as RechartsArea,\n  AreaChart as RechartsAreaChart,\n  CartesianGrid,\n  XAxis as RechartsXAxis,\n  YAxis as RechartsYAxis,\n} from \"recharts\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/registry/ui/recharts-tooltip\";\nimport {\n  Brush,\n  EvilBrush,\n  useEvilBrush,\n  type BrushProps,\n  type EvilBrushRange,\n} from \"@/registry/ui/recharts-brush\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/registry/ui/recharts-legend\";\nimport { ChartDot, type DotVariant } from \"@/registry/ui/recharts-dot\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\n// Constants\nconst STROKE_WIDTH = 0.8; // default series stroke — <Area strokeWidth> overrides it\nconst LOADING_AREA_DATA_KEY = \"loading\";\nconst LOADING_ANIMATION_DURATION = 2000; // in milliseconds\nconst STACK_ID = \"evil-stacked\";\nconst REVEAL_DURATION = 1; // intro wipe length, in seconds\nconst REVEAL_EASE: [number, number, number, number] = [0, 0.7, 0.5, 1]; // intro wipe easing\n\ntype CurveType = ComponentProps<typeof RechartsArea>[\"type\"];\ntype AreaDotProp = ComponentProps<typeof RechartsArea>[\"dot\"];\ntype AreaActiveDotProp = ComponentProps<typeof RechartsArea>[\"activeDot\"];\ntype AreaVariant = \"gradient\" | \"gradient-reverse\" | \"solid\" | \"dotted\" | \"lines\" | \"hatched\";\ntype StrokeVariant = \"solid\" | \"dashed\" | \"animated-dashed\";\ntype StackType = \"default\" | \"expanded\" | \"stacked\";\n\n/**\n * Direction of the custom motion.dev intro reveal. Recharts' own area animation\n * is permanently disabled (it drew the line after the dots had already popped\n * in) — these reveals replace it.\n *\n * NOTE: a reveal is a per-frame animated SVG mask, so it is heavier than a\n * static chart. `\"none\"` opts out entirely; it is also what a device with the\n * OS \"reduce motion\" preference falls back to automatically.\n */\ntype AreaAnimationType = \"none\" | \"left-to-right\" | \"right-to-left\" | \"center-out\" | \"edges-in\";\ntype RevealAnimationType = Exclude<AreaAnimationType, \"none\">;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared context\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Shared state for every part of the chart. Lifted into <EvilAreaChart /> so that\n * <Area />, <XAxis />, <Legend />, and friends can read it without prop drilling.\n * Sub-components are composed freely — the provider is the single source of truth.\n */\ntype AreaChartContextValue = {\n  config: ChartConfig; // colors + labels for every series\n  curveType: CurveType; // default curve interpolation each <Area /> inherits\n  animationType: AreaAnimationType; // default intro reveal each <Area /> inherits\n  isStacked: boolean; // whether areas stack on top of each other\n  isExpanded: boolean; // whether the stack is normalized to 100%\n  isLoading: boolean; // whether the chart shows its loading skeleton\n  selectedDataKey: string | null; // currently selected series, or null when none\n  selectDataKey: (dataKey: string | null) => void; // sets the selected series\n};\n\nconst AreaChartContext = createContext<AreaChartContextValue | null>(null);\n\n// Reads the chart context, throwing a helpful error when used outside <EvilAreaChart />\nfunction useAreaChart() {\n  const context = use(AreaChartContext);\n\n  if (!context) {\n    throw new Error(\n      \"Area chart parts (<Area />, <XAxis />, …) must be used within <EvilAreaChart />\",\n    );\n  }\n\n  return context;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Root container\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Validates that every config key also exists on the data row type\ntype ValidateConfigKeys<TData, TConfig> = {\n  [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never;\n};\n\ntype EvilAreaChartBaseProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  config: TConfig & ValidateConfigKeys<TData, TConfig>; // series colors + labels\n  data: TData[]; // rows rendered by the chart\n  children: ReactNode; // composed parts — <Area />, <XAxis />, <Legend />, …\n  className?: string; // extra classes for the chart container\n  chartProps?: ComponentProps<typeof RechartsAreaChart>; // escape hatch for the raw Recharts chart\n  curveType?: CurveType; // default curve interpolation for every <Area />\n  animationType?: AreaAnimationType; // default intro reveal for every <Area />\n  stackType?: StackType; // how multiple areas combine\n  defaultSelectedDataKey?: string | null; // series selected on first render\n  onSelectionChange?: (selectedDataKey: string | null) => void; // fires when the selected series changes\n  isLoading?: boolean; // shows the animated loading skeleton\n  loadingPoints?: number; // number of points in the loading skeleton\n  xDataKey?: keyof TData & string; // x-axis key — only needed for the <Brush /> footer\n};\n\ntype EvilAreaChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = EvilAreaChartBaseProps<TData, TConfig>;\n\n/**\n * Root of the composible area chart. Owns the data, the shared context, the\n * loading skeleton, and the optional zoom brush. Everything visual — axes,\n * grid, tooltip, legend, and the areas themselves — is composed as children,\n * so a consumer renders exactly the parts they need.\n */\nexport function EvilAreaChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  config,\n  data,\n  children,\n  className,\n  chartProps,\n  curveType = \"linear\",\n  animationType = \"left-to-right\",\n  stackType = \"default\",\n  defaultSelectedDataKey = null,\n  onSelectionChange,\n  isLoading = false,\n  loadingPoints,\n  xDataKey,\n}: EvilAreaChartProps<TData, TConfig>) {\n  const chartId = useId().replace(/:/g, \"\"); // colon-free id keeps CSS/SVG selectors valid\n  const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey);\n  const { loadingData, onShimmerExit } = useLoadingData(isLoading, loadingPoints);\n  const { visibleData, brushProps } = useEvilBrush({ data });\n\n  // Brush is a <Brush /> child now (not props): pull it out of the children so\n  // it never reaches the Recharts tree, and drive the footer from its props.\n  const brush = useMemo(() => {\n    // Pull the <Brush> element out of the children (config-only, never rendered\n    // into the Recharts tree); toArray also assigns stable keys to the rest.\n    const parts = Children.toArray(children);\n    const brushEl = parts.find((child) => isValidElement(child) && child.type === Brush);\n    const bp = (isValidElement(brushEl) ? brushEl.props : {}) as BrushProps;\n    return {\n      slot: {\n        present: isValidElement(brushEl),\n        height: bp.height,\n        formatLabel: bp.formatLabel,\n        onChange: bp.onChange,\n      },\n      chartChildren: parts.filter((child) => !(isValidElement(child) && child.type === Brush)),\n    };\n  }, [children]);\n  const showBrush = brush.slot.present;\n\n  const isExpanded = stackType === \"expanded\";\n  const isStacked = stackType === \"stacked\" || isExpanded;\n  const displayData = showBrush && !isLoading ? visibleData : data;\n\n  // Updates selection state and notifies the parent\n  const selectDataKey = useCallback(\n    (newSelectedDataKey: string | null) => {\n      setSelectedDataKey(newSelectedDataKey);\n      onSelectionChange?.(newSelectedDataKey);\n    },\n    [onSelectionChange],\n  );\n\n  const contextValue = useMemo<AreaChartContextValue>(\n    () => ({\n      config,\n      curveType,\n      animationType,\n      isStacked,\n      isExpanded,\n      isLoading,\n      selectedDataKey,\n      selectDataKey,\n    }),\n    [\n      config,\n      curveType,\n      animationType,\n      isStacked,\n      isExpanded,\n      isLoading,\n      selectedDataKey,\n      selectDataKey,\n    ],\n  );\n\n  return (\n    <AreaChartContext value={contextValue}>\n      <ChartContainer\n        className={className}\n        config={config}\n        footer={\n          showBrush &&\n          !isLoading && (\n            <EvilBrush\n              data={data}\n              chartConfig={config}\n              xDataKey={xDataKey}\n              variant=\"area\"\n              curveType={curveType}\n              height={brush.slot.height}\n              formatLabel={brush.slot.formatLabel}\n              stacked={isStacked}\n              skipStyle\n              className=\"mt-1\"\n              {...brushProps}\n              onChange={(range) => {\n                brushProps.onChange(range);\n                brush.slot.onChange?.(range);\n              }}\n            />\n          )\n        }\n      >\n        <LoadingIndicator isLoading={isLoading} />\n        <RechartsAreaChart\n          id={chartId}\n          accessibilityLayer\n          stackOffset={isExpanded ? \"expand\" : undefined}\n          data={isLoading ? loadingData : displayData}\n          {...chartProps}\n        >\n          {brush.chartChildren}\n          {isLoading && (\n            <LoadingArea chartId={chartId} curveType={curveType} onShimmerExit={onShimmerExit} />\n          )}\n        </RechartsAreaChart>\n      </ChartContainer>\n    </AreaChartContext>\n  );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Composible parts\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype AreaProps = {\n  dataKey: string; // series key — must exist on the data and config\n  variant?: AreaVariant; // fill style for this area only\n  strokeVariant?: StrokeVariant; // stroke style for this area\n  strokeWidth?: number; // stroke thickness in pixels for this area\n  curveType?: CurveType; // curve interpolation — falls back to the chart default\n  animationType?: AreaAnimationType; // intro reveal — falls back to the chart default\n  connectNulls?: boolean; // join segments across null/missing values\n  isClickable?: boolean; // lets this area be selected by clicking it\n  children?: ReactNode; // optional <Dot /> and <ActiveDot /> composition\n  areaProps?: ComponentProps<typeof RechartsArea>; // escape hatch for raw Recharts Area props\n};\n\n/**\n * A single area series. Each <Area /> is fully self-contained: it generates its\n * own gradient/pattern definitions under a unique id, so any number of areas —\n * each with its own variant, stroke, and clickability — can live in one chart\n * without style collisions. Compose <Dot /> and <ActiveDot /> inside it to add\n * point markers.\n */\nfunction Area({\n  dataKey,\n  variant = \"gradient\",\n  strokeVariant = \"dashed\",\n  strokeWidth = STROKE_WIDTH,\n  curveType,\n  animationType,\n  connectNulls = false,\n  isClickable = false,\n  children,\n  areaProps,\n}: AreaProps) {\n  const {\n    config,\n    curveType: defaultCurve,\n    animationType: defaultAnimation,\n    isStacked,\n    isExpanded,\n    isLoading,\n    selectedDataKey,\n    selectDataKey,\n  } = useAreaChart();\n  const id = useId().replace(/:/g, \"\"); // unique id scopes this area's style defs\n  // Devices set to \"reduce motion\" skip the intro reveal entirely\n  const shouldReduceMotion = useReducedMotion();\n\n  // The root renders the skeleton area while loading, so real areas step aside\n  if (isLoading) return null;\n\n  const resolvedCurve = curveType ?? defaultCurve;\n\n  // The reveal is an animated SVG mask — heavier than a static chart — so\n  // `\"none\"` and the OS reduce-motion preference both opt out of it.\n  const revealType: AreaAnimationType = shouldReduceMotion\n    ? \"none\"\n    : (animationType ?? defaultAnimation);\n  const maskId = revealType === \"none\" ? undefined : `${id}-reveal-mask`;\n\n  const isSelected = selectedDataKey === dataKey;\n  const hasSelection = selectedDataKey !== null;\n  const opacity = getOpacity(selectedDataKey, dataKey);\n  const showUnselected = hasSelection && !isSelected;\n\n  const { dot, activeDot } = resolveDots(children, id, dataKey, opacity.dot, maskId);\n\n  const isAnimatedDashed = strokeVariant === \"animated-dashed\";\n  const isDashed = strokeVariant === \"dashed\" || isAnimatedDashed;\n\n  return (\n    <>\n      <RechartsArea\n        type={resolvedCurve}\n        dataKey={dataKey}\n        connectNulls={connectNulls}\n        fillOpacity={opacity.fill}\n        strokeOpacity={opacity.stroke}\n        fill={getFillPattern(variant, showUnselected, id)}\n        stroke={`url(#${id}-colors-${dataKey})`}\n        stackId={isStacked ? STACK_ID : undefined}\n        dot={dot}\n        activeDot={activeDot}\n        strokeWidth={strokeWidth}\n        strokeDasharray={isDashed ? \"3 3\" : undefined}\n        // Recharts' built-in area animation is permanently disabled — it drew\n        // the line after the dots had already popped in. The motion.dev reveal\n        // mask drives the intro instead, wiping fill, stroke, and dots in together.\n        isAnimationActive={false}\n        style={{\n          ...(maskId ? { mask: `url(#${maskId})` } : {}),\n          ...(isClickable ? { cursor: \"pointer\" } : {}),\n        }}\n        onClick={() => {\n          if (!isClickable) return;\n          // Clicking the selected area clears the selection, otherwise selects it\n          selectDataKey(isSelected ? null : dataKey);\n        }}\n        {...areaProps}\n      >\n        {isAnimatedDashed && !hasSelection && <AnimatedDashedStroke />}\n      </RechartsArea>\n      <defs>\n        {revealType !== \"none\" && <RevealMask id={id} type={revealType} />}\n        <ColorGradient id={id} dataKey={dataKey} config={config} isExpanded={isExpanded} />\n        {variant === \"gradient\" && <GradientPattern id={id} dataKey={dataKey} />}\n        {variant === \"gradient-reverse\" && <ReverseGradientPattern id={id} dataKey={dataKey} />}\n        {variant === \"solid\" && <SolidPattern id={id} dataKey={dataKey} />}\n        {variant === \"dotted\" && <DottedPattern id={id} dataKey={dataKey} />}\n        {variant === \"lines\" && <LinesPattern id={id} dataKey={dataKey} />}\n        {variant === \"hatched\" && <HatchedPattern id={id} dataKey={dataKey} />}\n        {showUnselected && <UnselectedPattern id={id} dataKey={dataKey} />}\n      </defs>\n    </>\n  );\n}\n\ntype DotProps = {\n  variant?: DotVariant; // visual style of the point marker\n};\n\n/**\n * Declares a resting point marker for the <Area /> it is composed inside.\n * It renders nothing on its own — the parent <Area /> reads its variant and\n * wires it into the Recharts dot slot.\n */\nconst Dot: FC<DotProps> = () => null;\n\n/**\n * Declares the hovered/active point marker for the <Area /> it is composed\n * inside. Like <Dot />, it is a configuration slot and renders nothing itself.\n */\nconst ActiveDot: FC<DotProps> = () => null;\n\ntype XAxisProps = ComponentProps<typeof RechartsXAxis>;\n\n/**\n * The horizontal category axis. Ships with the chart's flat default styling and\n * forwards every Recharts XAxis prop, so `dataKey`, `tickFormatter`, etc. are\n * passed straight through. Hidden automatically while the chart is loading.\n */\nfunction XAxis({\n  tickLine = false,\n  axisLine = false,\n  tickMargin = 8,\n  minTickGap = 8,\n  ...props\n}: XAxisProps) {\n  const { isLoading } = useAreaChart();\n\n  if (isLoading) return null;\n\n  return (\n    <RechartsXAxis\n      tickLine={tickLine}\n      axisLine={axisLine}\n      tickMargin={tickMargin}\n      minTickGap={minTickGap}\n      {...props}\n    />\n  );\n}\n\ntype YAxisProps = ComponentProps<typeof RechartsYAxis>;\n\n/**\n * The vertical value axis. Forwards every Recharts YAxis prop and, when the\n * chart uses an expanded stack, formats ticks as percentages automatically.\n * Hidden automatically while the chart is loading.\n */\nfunction YAxis({\n  tickLine = false,\n  axisLine = false,\n  tickMargin = 8,\n  minTickGap = 8,\n  width = \"auto\",\n  tickFormatter,\n  ...props\n}: YAxisProps) {\n  const { isLoading, isExpanded } = useAreaChart();\n\n  if (isLoading) return null;\n\n  return (\n    <RechartsYAxis\n      tickLine={tickLine}\n      axisLine={axisLine}\n      tickMargin={tickMargin}\n      minTickGap={minTickGap}\n      width={width}\n      tickFormatter={isExpanded ? axisValueToPercentFormatter : tickFormatter}\n      {...props}\n    />\n  );\n}\n\ntype GridProps = ComponentProps<typeof CartesianGrid>;\n\n/**\n * The background grid lines. Defaults to horizontal-only dashed lines and\n * forwards every Recharts CartesianGrid prop for full control.\n */\nfunction Grid({ vertical = false, strokeDasharray = \"3 3\", ...props }: GridProps) {\n  return <CartesianGrid vertical={vertical} strokeDasharray={strokeDasharray} {...props} />;\n}\n\ntype TooltipProps = {\n  variant?: TooltipVariant; // visual style of the tooltip surface\n  roundness?: TooltipRoundness; // border-radius of the tooltip\n  defaultIndex?: number; // data index shown by default with no hover\n  cursor?: boolean; // whether the vertical cursor line follows the pointer\n};\n\n/**\n * The hover tooltip. Reads the chart's selection from context so its content\n * dims unselected series. Hidden automatically while the chart is loading.\n */\nfunction Tooltip({ variant, roundness, defaultIndex, cursor = true }: TooltipProps) {\n  const { isLoading, selectedDataKey } = useAreaChart();\n\n  if (isLoading) return null;\n\n  return (\n    <ChartTooltip\n      defaultIndex={defaultIndex}\n      cursor={cursor ? { strokeDasharray: \"3 3\", strokeWidth: STROKE_WIDTH } : false}\n      content={\n        <ChartTooltipContent selected={selectedDataKey} roundness={roundness} variant={variant} />\n      }\n    />\n  );\n}\n\ntype LegendProps = {\n  variant?: ChartLegendVariant; // visual style of the legend indicators\n  align?: \"left\" | \"center\" | \"right\"; // horizontal placement\n  verticalAlign?: \"top\" | \"middle\" | \"bottom\"; // vertical placement\n  isClickable?: boolean; // lets each entry toggle selection of its series\n};\n\n/**\n * The series legend. When `isClickable` is set, each entry toggles selection of\n * its series, driving the shared selection state read by every <Area />.\n */\nfunction Legend({\n  variant,\n  align = \"right\",\n  verticalAlign = \"top\",\n  isClickable = false,\n}: LegendProps) {\n  const { selectedDataKey, selectDataKey } = useAreaChart();\n\n  return (\n    <ChartLegend\n      verticalAlign={verticalAlign}\n      align={align}\n      content={\n        <ChartLegendContent\n          selected={selectedDataKey}\n          onSelectChange={selectDataKey}\n          isClickable={isClickable}\n          variant={variant}\n        />\n      }\n    />\n  );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Selection + dot helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Returns fill/stroke/dot opacity — dims a series only when another is selected\nconst getOpacity = (selectedDataKey: string | null, dataKey: string) => {\n  if (selectedDataKey === null) {\n    return { fill: 0.8, stroke: 1, dot: 1 };\n  }\n\n  return selectedDataKey === dataKey\n    ? { fill: 0.8, stroke: 1, dot: 1 }\n    : { fill: 0.1, stroke: 0.3, dot: 0.3 };\n};\n\n// Resolves the SVG paint reference for an area's fill based on its variant\nconst getFillPattern = (variant: AreaVariant, showUnselected: boolean, id: string): string => {\n  // A non-selected area in a clickable chart is striped to recede visually\n  if (showUnselected) return `url(#${id}-unselected)`;\n\n  return `url(#${id}-${variant})`;\n};\n\n// Pulls <Dot /> and <ActiveDot /> out of an area's children into Recharts dot slots.\n// When a `maskId` is given the resting dot is wired to the intro reveal mask so it\n// wipes in with the line; the active dot is always left unmasked since it only\n// appears on hover, after the intro has finished.\nconst resolveDots = (\n  children: ReactNode,\n  id: string,\n  dataKey: string,\n  dotOpacity: number,\n  maskId: string | undefined,\n): { dot: AreaDotProp; activeDot: AreaActiveDotProp } => {\n  let dot: AreaDotProp = false;\n  let activeDot: AreaActiveDotProp = false;\n\n  Children.forEach(children, (child) => {\n    if (!isValidElement(child)) return;\n\n    if (child.type === Dot) {\n      const { variant } = (child as ReactElement<DotProps>).props;\n      dot = (\n        <ChartDot\n          type={variant}\n          dataKey={dataKey}\n          chartId={id}\n          fillOpacity={dotOpacity}\n          maskId={maskId}\n        />\n      );\n    }\n\n    if (child.type === ActiveDot) {\n      const { variant } = (child as ReactElement<DotProps>).props;\n      activeDot = (\n        <ChartDot type={variant} dataKey={dataKey} chartId={id} fillOpacity={dotOpacity} />\n      );\n    }\n  });\n\n  return { dot, activeDot };\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Style definitions — one set per <Area />, scoped to its unique id\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype StyleProps = {\n  id: string; // unique id of the owning <Area />\n  dataKey: string; // series key the colors belong to\n};\n\n// Animated dashed-stroke effect, rendered as a child of the Recharts Area\nconst AnimatedDashedStroke = () => {\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// motion `originX` for each single-rect reveal — the edge the wipe grows from.\n// 0 = left edge, 1 = right edge, 0.5 = centre (grows outward to both edges).\nconst SINGLE_REVEAL_ORIGIN: Record<Exclude<RevealAnimationType, \"edges-in\">, number> = {\n  \"left-to-right\": 0,\n  \"right-to-left\": 1,\n  \"center-out\": 0.5,\n};\n\n/**\n * Wipe mask driven by motion.dev, played once when an <Area /> mounts. The same\n * mask is applied to the area's fill, stroke, and resting dots, so all three\n * reveal in lockstep — fixing Recharts' default, where the dots appeared before\n * the line had finished drawing.\n *\n * `maskUnits`/`maskContentUnits` are both userSpaceOnUse so every masked element\n * shares one coordinate space and the wipe edge lands at the same x on each.\n *\n * Each rect animates `scaleX` 0 → 1; `originX` decides which edge it grows from.\n * \"edges-in\" needs two rects — each half grows inward from an opposite edge.\n */\nconst RevealMask = ({ id, type }: { id: string; type: RevealAnimationType }) => {\n  const reveal = {\n    initial: { scaleX: 0 },\n    animate: { scaleX: 1 },\n    transition: { duration: REVEAL_DURATION, ease: REVEAL_EASE },\n  };\n\n  return (\n    <mask\n      id={`${id}-reveal-mask`}\n      maskUnits=\"userSpaceOnUse\"\n      maskContentUnits=\"userSpaceOnUse\"\n      x=\"0\"\n      y=\"0\"\n      width=\"100%\"\n      height=\"100%\"\n    >\n      {type === \"edges-in\" ? (\n        <>\n          {/* left half wipes inward from the left edge toward the centre */}\n          <motion.rect\n            {...reveal}\n            x=\"0\"\n            y=\"0\"\n            width=\"50%\"\n            height=\"100%\"\n            fill=\"white\"\n            style={{ originX: 0 }}\n          />\n          {/* right half wipes inward from the right edge toward the centre */}\n          <motion.rect\n            {...reveal}\n            x=\"50%\"\n            y=\"0\"\n            width=\"50%\"\n            height=\"100%\"\n            fill=\"white\"\n            style={{ originX: 1 }}\n          />\n        </>\n      ) : (\n        <motion.rect\n          {...reveal}\n          x=\"0\"\n          y=\"0\"\n          width=\"100%\"\n          height=\"100%\"\n          fill=\"white\"\n          style={{ originX: SINGLE_REVEAL_ORIGIN[type] }}\n        />\n      )}\n    </mask>\n  );\n};\n\n/**\n * Horizontal left-to-right color gradient for a series. Always rendered — every\n * fill variant, the stroke, and the dots all paint from this single gradient.\n */\nconst ColorGradient = ({\n  id,\n  dataKey,\n  config,\n  isExpanded,\n}: StyleProps & { config: ChartConfig; isExpanded: boolean }) => {\n  const colorsCount = getColorsCount(config[dataKey] ?? {});\n\n  return (\n    <linearGradient\n      id={`${id}-colors-${dataKey}`}\n      x1=\"0\"\n      y1=\"0\"\n      x2=\"1\"\n      y2=\"0\"\n      gradientUnits={isExpanded ? \"userSpaceOnUse\" : \"objectBoundingBox\"}\n    >\n      {colorsCount === 1 ? (\n        <>\n          <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n          <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n        </>\n      ) : (\n        Array.from({ length: colorsCount }, (_, index) => {\n          const offset = `${(index / (colorsCount - 1)) * 100}%`;\n          return (\n            <stop\n              key={offset}\n              offset={offset}\n              stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n            />\n          );\n        })\n      )}\n    </linearGradient>\n  );\n};\n\n/** Gradient fill that fades from visible at the top to transparent at the bottom. */\nconst GradientPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <linearGradient id={`${id}-vertical-fade`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.1} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n      </linearGradient>\n      <mask id={`${id}-gradient-mask`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-vertical-fade)`} />\n      </mask>\n      <pattern id={`${id}-gradient`} patternUnits=\"userSpaceOnUse\" width=\"100%\" height=\"100%\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-gradient-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Gradient fill that fades from transparent at the top to visible at the bottom. */\nconst ReverseGradientPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <linearGradient id={`${id}-vertical-fade-reverse`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.1} />\n      </linearGradient>\n      <mask id={`${id}-gradient-reverse-mask`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-vertical-fade-reverse)`} />\n      </mask>\n      <pattern\n        id={`${id}-gradient-reverse`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"100%\"\n        height=\"100%\"\n      >\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-gradient-reverse-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Uniform low-opacity gradient fill with no vertical fade. */\nconst SolidPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <linearGradient id={`${id}-solid-fade`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.1} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.1} />\n      </linearGradient>\n      <mask id={`${id}-solid-mask`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-solid-fade)`} />\n      </mask>\n      <pattern id={`${id}-solid`} patternUnits=\"userSpaceOnUse\" width=\"100%\" height=\"100%\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-solid-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Diagonal-line texture fill, masked from the series color gradient. */\nconst LinesPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <pattern\n        id={`${id}-lines-texture`}\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      <mask id={`${id}-lines-mask`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-lines-texture)`} fillOpacity=\"0.3\" />\n      </mask>\n      <pattern id={`${id}-lines`} patternUnits=\"userSpaceOnUse\" width=\"100%\" height=\"100%\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-lines-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Dotted texture fill, masked from the series color gradient. */\nconst DottedPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <pattern\n        id={`${id}-dotted-texture`}\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      <mask id={`${id}-dotted-mask`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-dotted-texture)`} fillOpacity=\"0.5\" />\n      </mask>\n      <pattern id={`${id}-dotted`} patternUnits=\"userSpaceOnUse\" width=\"100%\" height=\"100%\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-dotted-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Hatched striped fill with a soft gradient across each stripe. */\nconst HatchedPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <linearGradient id={`${id}-hatched-stripe`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.2} />\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n      </linearGradient>\n      <pattern\n        id={`${id}-hatched-texture`}\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 width=\"20\" height=\"10\" fill={`url(#${id}-hatched-stripe)`} />\n      </pattern>\n      <mask id={`${id}-hatched-mask`}>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id}-hatched-texture)`} fillOpacity=\"0.2\" />\n      </mask>\n      <pattern id={`${id}-hatched`} patternUnits=\"userSpaceOnUse\" width=\"100%\" height=\"100%\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-hatched-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n/** Diagonal-line fill used to push a non-selected area into the background. */\nconst UnselectedPattern = ({ id, dataKey }: StyleProps) => {\n  return (\n    <>\n      <pattern\n        id={`${id}-unselected-texture`}\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      <mask id={`${id}-unselected-mask`}>\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-unselected-texture)`}\n          fillOpacity=\"0.3\"\n        />\n      </mask>\n      <pattern id={`${id}-unselected`} patternUnits=\"userSpaceOnUse\" width=\"100%\" height=\"100%\">\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#${id}-colors-${dataKey})`}\n          mask={`url(#${id}-unselected-mask)`}\n        />\n      </pattern>\n    </>\n  );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Loading skeleton\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Builds bell-curve eased gradient stops for the loading shimmer\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 { offset: `${(t * 100).toFixed(0)}%`, opacity: Number(opacity.toFixed(3)) };\n  });\n};\n\n/**\n * Hook to manage loading data with pixel-perfect shimmer synchronization.\n *\n * Uses motion.dev's onUpdate callback to ensure chart data is only regenerated\n * when the shimmer has completely exited the visible area. This eliminates\n * timing drift issues from setTimeout/setInterval.\n */\nexport function useLoadingData(isLoading: boolean, loadingPoints: number = 14) {\n  const [loadingDataKey, setLoadingDataKey] = useState(false);\n\n  // Callback fired by motion.dev when the shimmer exits the 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 the shimmer exits\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [loadingPoints, loadingDataKey],\n  );\n\n  return { loadingData, onShimmerExit };\n}\n\n/**\n * The skeleton area shown while the chart is loading. Rendered by the root in\n * place of the real areas, paired with its own masked shimmer pattern.\n */\nconst LoadingArea = ({\n  chartId,\n  curveType,\n  onShimmerExit,\n}: {\n  chartId: string;\n  curveType: CurveType;\n  onShimmerExit: () => void;\n}) => {\n  return (\n    <>\n      <RechartsArea\n        type={curveType}\n        dataKey={LOADING_AREA_DATA_KEY}\n        fillOpacity={0.05}\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      <defs>\n        <LoadingPattern chartId={chartId} onShimmerExit={onShimmerExit} />\n      </defs>\n    </>\n  );\n};\n\n/**\n * Animated shimmer pattern for the loading skeleton.\n *\n * The visible chart area is normalized to 0-1, the shimmer gradient has width 1,\n * and the pattern is 3x wide so the shimmer has buffer on both sides. The motion\n * rect travels x from -1 to 2; onShimmerExit fires as it crosses x=1, letting the\n * data swap happen while the shimmer is off-screen for a seamless loop.\n */\nconst LoadingPattern = ({\n  chartId,\n  onShimmerExit,\n}: {\n  chartId: string;\n  onShimmerExit: () => void;\n}) => {\n  const gradientStops = generateEasedGradientStops();\n\n  // 1 (left buffer) + 1 (visible) + 1 (right buffer)\n  const patternWidth = 3;\n  const startX = -1;\n  const endX = 2;\n\n  // Tracks the last x value to detect the exit threshold crossing\n  const lastXRef = useRef(startX);\n\n  return (\n    <>\n      <linearGradient id={`${chartId}-loading-gradient`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        {gradientStops.map(({ offset, opacity }) => (\n          <stop key={offset} offset={offset} stopColor=\"white\" stopOpacity={opacity} />\n        ))}\n      </linearGradient>\n      <pattern\n        id={`${chartId}-loading-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        <motion.rect\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${chartId}-loading-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          onUpdate={(latest) => {\n            const xValue = typeof latest.x === \"number\" ? latest.x : startX;\n            const lastX = lastXRef.current;\n\n            // Fire once per loop, when the shimmer fully exits the visible area\n            if (xValue >= 1 && lastX < 1) {\n              onShimmerExit();\n            }\n\n            lastXRef.current = xValue;\n          }}\n        />\n      </pattern>\n      <mask id={`${chartId}-loading-mask`} maskUnits=\"userSpaceOnUse\">\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-loading-pattern)`} />\n      </mask>\n    </>\n  );\n};\n\n// Compound API: every part hangs off the root as a static member, so a consumer\n// writes <EvilAreaChart.Area/>, <EvilAreaChart.Tooltip/>, … from a single import\n// — no colliding named marker exports when several charts share one file.\nEvilAreaChart.Area = Area;\nEvilAreaChart.Dot = Dot;\nEvilAreaChart.ActiveDot = ActiveDot;\nEvilAreaChart.XAxis = XAxis;\nEvilAreaChart.YAxis = YAxis;\nEvilAreaChart.Grid = Grid;\nEvilAreaChart.Tooltip = Tooltip;\nEvilAreaChart.Legend = Legend;\nEvilAreaChart.Brush = Brush;\n",
      "type": "registry:component",
      "target": "components/evilcharts/charts/recharts-area-chart.tsx"
    }
  ],
  "type": "registry:component"
}