{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "area-chart-widget",
  "title": "Glass Area Chart Widget",
  "description": "A liquid glass styled area chart widget with glowing multi-layer gradient fills, smooth curves, and metric summary.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.eindev.ir/r/base-widget.json"
  ],
  "files": [
    {
      "path": "registry/widgets/area-chart-widget.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { GlassWidgetBase, type GlassWidgetBaseProps } from \"./base-widget\";\nimport { TrendingDown, TrendingUp } from \"lucide-react\";\n\ntype GlowColor = NonNullable<GlassWidgetBaseProps[\"glowColor\"]>;\n\nexport interface AreaChartDataPoint {\n  label: string;\n  value: number;\n}\n\nexport interface AreaChartWidgetProps {\n  title?: string;\n  subtitle?: string;\n  value?: string | number;\n  change?: string;\n  data?: AreaChartDataPoint[];\n  glowColor?: GlowColor;\n  className?: string;\n}\n\n// The chart is authored in a fixed viewBox so stroke widths stay crisp at any size.\nconst VIEW_WIDTH = 280;\nconst PLOT_LEFT = 10;\nconst PLOT_RIGHT = VIEW_WIDTH - 10;\nconst PLOT_TOP = 12;\nconst PLOT_BOTTOM = 78;\nconst LABEL_Y = 92;\n// Past this many points the x-axis labels collide, so we thin them out.\nconst MAX_LABELS = 8;\n\nconst chartPalettes: Record<GlowColor, { stops: [string, string, string]; fill: string }> = {\n  cyan: { stops: [\"#22d3ee\", \"#3b82f6\", \"#a855f7\"], fill: \"34, 211, 238\" },\n  purple: { stops: [\"#c084fc\", \"#d946ef\", \"#818cf8\"], fill: \"192, 132, 252\" },\n  blue: { stops: [\"#60a5fa\", \"#3b82f6\", \"#22d3ee\"], fill: \"96, 165, 250\" },\n  pink: { stops: [\"#f472b6\", \"#e879f9\", \"#c084fc\"], fill: \"244, 114, 182\" },\n  green: { stops: [\"#34d399\", \"#22d3ee\", \"#a3e635\"], fill: \"52, 211, 153\" },\n  amber: { stops: [\"#fbbf24\", \"#fb923c\", \"#f472b6\"], fill: \"251, 191, 36\" },\n  red: { stops: [\"#fb7185\", \"#f43f5e\", \"#c084fc\"], fill: \"251, 113, 133\" },\n};\n\n/**\n * Builds a smooth cubic path from points using Catmull-Rom control points, so the\n * curve eases between samples instead of drawing hard corners.\n */\nfunction buildSmoothPath(points: { x: number; y: number }[]): string {\n  if (points.length === 0) return \"\";\n  if (points.length === 1) return `M ${points[0].x} ${points[0].y}`;\n\n  let path = `M ${points[0].x} ${points[0].y}`;\n\n  for (let i = 0; i < points.length - 1; i++) {\n    const p0 = points[i - 1] ?? points[i];\n    const p1 = points[i];\n    const p2 = points[i + 1];\n    const p3 = points[i + 2] ?? p2;\n\n    const c1x = p1.x + (p2.x - p0.x) / 6;\n    const c1y = p1.y + (p2.y - p0.y) / 6;\n    const c2x = p2.x - (p3.x - p1.x) / 6;\n    const c2y = p2.y - (p3.y - p1.y) / 6;\n\n    path += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;\n  }\n\n  return path;\n}\n\nexport function AreaChartWidget({\n  title = \"Analytics\",\n  subtitle = \"Performance overview\",\n  value = \"$42,850\",\n  change = \"+12.4%\",\n  data = [],\n  glowColor = \"cyan\",\n  className,\n}: AreaChartWidgetProps) {\n  // Gradient ids must be unique per instance, otherwise the first widget on the\n  // page wins and every later chart paints with the wrong colors.\n  const uid = React.useId();\n  const areaGradientId = `area-grad-${uid}`;\n  const lineGradientId = `line-grad-${uid}`;\n  const palette = chartPalettes[glowColor];\n\n  const points = React.useMemo(() => {\n    if (data.length === 0) return [];\n\n    const values = data.map((point) => point.value);\n    const min = Math.min(...values);\n    const max = Math.max(...values);\n    // A flat series has no span, so fall back to 1 to keep the math finite.\n    const span = max - min || 1;\n    const paddedMin = min - span * 0.1;\n    const paddedMax = max + span * 0.1;\n    const plotSpan = paddedMax - paddedMin;\n\n    return data.map((point, index) => ({\n      label: point.label,\n      value: point.value,\n      x:\n        data.length === 1\n          ? (PLOT_LEFT + PLOT_RIGHT) / 2\n          : PLOT_LEFT + (index / (data.length - 1)) * (PLOT_RIGHT - PLOT_LEFT),\n      y: PLOT_BOTTOM - ((point.value - paddedMin) / plotSpan) * (PLOT_BOTTOM - PLOT_TOP),\n    }));\n  }, [data]);\n\n  const linePath = buildSmoothPath(points);\n  const areaPath =\n    points.length > 0\n      ? `${linePath} L ${points[points.length - 1].x} ${PLOT_BOTTOM} L ${points[0].x} ${PLOT_BOTTOM} Z`\n      : \"\";\n\n  const isNegative = change?.trim().startsWith(\"-\") ?? false;\n  const TrendIcon = isNegative ? TrendingDown : TrendingUp;\n  const labelStep = Math.max(1, Math.ceil(points.length / MAX_LABELS));\n\n  return (\n    <GlassWidgetBase className={cn(\"flex flex-col justify-between\", className)} glowColor={glowColor}>\n      <div className=\"flex items-start justify-between gap-3\">\n        <div className=\"min-w-0\">\n          <h3 className=\"truncate text-base font-medium text-white\">{title}</h3>\n          <p className=\"truncate text-xs text-white/50\">{subtitle}</p>\n        </div>\n        {value !== undefined && (\n          <div className=\"shrink-0 text-right\">\n            <div className=\"text-lg font-semibold tabular-nums text-white\">{value}</div>\n            {change && (\n              <div\n                className={cn(\n                  \"flex items-center justify-end gap-1 text-xs tabular-nums\",\n                  isNegative ? \"text-red-400\" : \"text-emerald-400\",\n                )}\n              >\n                <TrendIcon className=\"size-3\" aria-hidden=\"true\" />\n                <span>{change}</span>\n              </div>\n            )}\n          </div>\n        )}\n      </div>\n\n      {points.length === 0 ? (\n        <div className=\"my-4 flex h-32 items-center justify-center rounded-xl border border-white/10 bg-black/20 text-xs text-white/40\">\n          No data\n        </div>\n      ) : (\n        <svg\n          viewBox={`0 0 ${VIEW_WIDTH} 100`}\n          className=\"mt-4 h-32 w-full overflow-visible\"\n          role=\"img\"\n          aria-label={`${title} — ${subtitle}`}\n        >\n          <defs>\n            <linearGradient id={areaGradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stopColor={`rgba(${palette.fill}, 0.4)`} />\n              <stop offset=\"100%\" stopColor={`rgba(${palette.fill}, 0)`} />\n            </linearGradient>\n            <linearGradient id={lineGradientId} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n              <stop offset=\"0%\" stopColor={palette.stops[0]} />\n              <stop offset=\"50%\" stopColor={palette.stops[1]} />\n              <stop offset=\"100%\" stopColor={palette.stops[2]} />\n            </linearGradient>\n          </defs>\n\n          <g aria-hidden=\"true\">\n            <line\n              x1={PLOT_LEFT}\n              y1={PLOT_TOP}\n              x2={PLOT_RIGHT}\n              y2={PLOT_TOP}\n              stroke=\"rgba(255,255,255,0.07)\"\n              strokeDasharray=\"4 4\"\n            />\n            <line\n              x1={PLOT_LEFT}\n              y1={PLOT_BOTTOM}\n              x2={PLOT_RIGHT}\n              y2={PLOT_BOTTOM}\n              stroke=\"rgba(255,255,255,0.1)\"\n            />\n          </g>\n\n          {areaPath && <path d={areaPath} fill={`url(#${areaGradientId})`} />}\n\n          {linePath && (\n            <path\n              d={linePath}\n              fill=\"none\"\n              stroke={`url(#${lineGradientId})`}\n              strokeWidth=\"2.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          )}\n\n          {points.map((point, index) => (\n            <circle\n              key={`${point.label}-${index}`}\n              cx={point.x}\n              cy={point.y}\n              r=\"3\"\n              fill={palette.stops[0]}\n              stroke=\"white\"\n              strokeWidth=\"1.5\"\n              className=\"transition-all duration-300 hover:[r:4.5]\"\n            />\n          ))}\n\n          {/* Labels live in the SVG so each one stays locked to the point it describes. */}\n          <g fontSize=\"9\" fill=\"rgba(255,255,255,0.4)\" aria-hidden=\"true\">\n            {points.map((point, index) => {\n              if (index % labelStep !== 0 && index !== points.length - 1) return null;\n\n              const anchor =\n                index === 0 ? \"start\" : index === points.length - 1 ? \"end\" : \"middle\";\n\n              return (\n                <text\n                  key={`label-${point.label}-${index}`}\n                  x={point.x}\n                  y={LABEL_Y}\n                  textAnchor={anchor}\n                >\n                  {point.label}\n                </text>\n              );\n            })}\n          </g>\n        </svg>\n      )}\n    </GlassWidgetBase>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}