{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "line-chart",
  "type": "registry:ui",
  "title": "Line Chart",
  "description": "SVG line chart with multi-series support, animated draw-on, area fill, grid lines, and legend.",
  "dependencies": [],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "ui/line-chart/line-chart.tsx",
      "type": "registry:ui",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type LineChartVariant = 'DEFAULT' | 'ACTIVE' | 'WARNING' | 'CRITICAL'\n\nexport interface LineChartSeries {\n  id: string\n  label: string\n  data: number[]\n  variant?: LineChartVariant\n}\n\nexport interface LineChartProps extends React.HTMLAttributes<HTMLDivElement> {\n  series: LineChartSeries[]\n  labels: string[]\n  title?: string\n  height?: number\n  min?: number\n  max?: number\n  showDots?: boolean\n  showArea?: boolean\n  showGrid?: boolean\n  showLegend?: boolean\n  animated?: boolean\n}\n\nconst LEFT   = 40\nconst RIGHT  = 8\nconst TOP    = 8\nconst BOTTOM = 24\n\nconst SERIES_COLORS = [\n  'var(--color-green)',\n  'var(--color-amber)',\n  'var(--color-red)',\n  'var(--color-blue)',\n]\n\nconst VARIANT_COLORS: Record<LineChartVariant, string> = {\n  DEFAULT:  'var(--text-secondary)',\n  ACTIVE:   'var(--color-green)',\n  WARNING:  'var(--color-amber)',\n  CRITICAL: 'var(--color-red)',\n}\n\nfunction seriesColor(s: LineChartSeries, idx: number): string {\n  if (s.variant) return VARIANT_COLORS[s.variant]\n  return SERIES_COLORS[idx % SERIES_COLORS.length]\n}\n\nconst LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(\n  (\n    {\n      className,\n      series,\n      labels,\n      title,\n      height    = 160,\n      min,\n      max,\n      showDots  = true,\n      showArea  = false,\n      showGrid  = true,\n      showLegend,\n      animated  = true,\n      style,\n      ...props\n    },\n    ref\n  ) => {\n    const containerRef = React.useRef<HTMLDivElement>(null)\n    const [svgW, setSvgW] = React.useState(500)\n    const [drawn, setDrawn] = React.useState(false)\n\n    React.useEffect(() => {\n      if (!containerRef.current) return\n      const ro = new ResizeObserver(entries => {\n        setSvgW(entries[0].contentRect.width)\n      })\n      ro.observe(containerRef.current)\n      return () => ro.disconnect()\n    }, [])\n\n    React.useEffect(() => {\n      const id = requestAnimationFrame(() => setDrawn(true))\n      return () => cancelAnimationFrame(id)\n    }, [])\n\n    const allValues = series.flatMap(s => s.data)\n    const dataMin = allValues.length > 0 ? Math.min(...allValues) : 0\n    const dataMax = allValues.length > 0 ? Math.max(...allValues) : 100\n    const minV = min ?? dataMin\n    const maxV = max ?? dataMax\n    const range = Math.max(maxV - minV, 1)\n\n    const plotW = svgW - LEFT - RIGHT\n    const plotH = height - TOP - BOTTOM\n    const n = labels.length\n\n    function xPos(i: number) {\n      return LEFT + (n <= 1 ? plotW / 2 : (i / (n - 1)) * plotW)\n    }\n\n    function yPos(v: number) {\n      return TOP + (1 - (v - minV) / range) * plotH\n    }\n\n    function buildLinePath(data: number[]): string {\n      return data\n        .map((v, i) => `${i === 0 ? 'M' : 'L'} ${xPos(i).toFixed(1)} ${yPos(v).toFixed(1)}`)\n        .join(' ')\n    }\n\n    function buildAreaPath(data: number[]): string {\n      if (data.length === 0) return ''\n      const line = buildLinePath(data)\n      const firstX = xPos(0).toFixed(1)\n      const lastX  = xPos(data.length - 1).toFixed(1)\n      const bottomY = (TOP + plotH).toFixed(1)\n      return `${line} L ${lastX} ${bottomY} L ${firstX} ${bottomY} Z`\n    }\n\n    const gridFracs = [0, 1 / 3, 2 / 3, 1]\n    const showLeg = showLegend ?? series.length > 1\n    const step = Math.max(1, Math.ceil(n / 8))\n\n    const shouldAnimate = animated\n\n    return (\n      <div\n        ref={ref}\n        className={cn(className)}\n        style={{\n          border:     '1px solid var(--border)',\n          background: 'var(--surface)',\n          fontFamily: 'var(--font-mono)',\n          overflow:   'hidden',\n          ...style,\n        }}\n        {...props}\n      >\n        {title && (\n          <div\n            style={{\n              padding:       '0.4rem 0.75rem',\n              borderBottom:  '1px solid var(--border)',\n              background:    'var(--surface-raised)',\n              fontSize:      '0.6rem',\n              color:         'var(--text-muted)',\n              letterSpacing: '0.12em',\n              textTransform: 'uppercase',\n            }}\n          >\n            {title}\n          </div>\n        )}\n\n        <div ref={containerRef} style={{ width: '100%', height: `${height}px` }}>\n          <svg width={svgW} height={height} style={{ display: 'block' }}>\n            {/* Grid */}\n            {showGrid && gridFracs.map((f, gi) => {\n              const y = TOP + f * plotH\n              const val = minV + (1 - f) * range\n              return (\n                <g key={gi}>\n                  <line\n                    x1={LEFT}\n                    y1={y.toFixed(1)}\n                    x2={(LEFT + plotW).toFixed(1)}\n                    y2={y.toFixed(1)}\n                    stroke=\"var(--border)\"\n                    strokeWidth={0.5}\n                  />\n                  <text\n                    x={LEFT - 4}\n                    y={y}\n                    textAnchor=\"end\"\n                    dominantBaseline=\"middle\"\n                    fontSize={9}\n                    fill=\"var(--text-muted)\"\n                    fontFamily=\"var(--font-mono)\"\n                  >\n                    {val.toFixed(0)}\n                  </text>\n                </g>\n              )\n            })}\n\n            {/* X-axis labels */}\n            {labels.map((label, i) => {\n              if (i % step !== 0 && i !== n - 1) return null\n              return (\n                <text\n                  key={i}\n                  x={xPos(i)}\n                  y={TOP + plotH + 14}\n                  textAnchor=\"middle\"\n                  fontSize={9}\n                  fill=\"var(--text-muted)\"\n                  fontFamily=\"var(--font-mono)\"\n                >\n                  {label}\n                </text>\n              )\n            })}\n\n            {/* Series */}\n            {series.map((s, si) => {\n              if (s.data.length === 0) return null\n              const color = seriesColor(s, si)\n              const linePath = buildLinePath(s.data)\n              const areaPath = buildAreaPath(s.data)\n\n              return (\n                <g key={s.id}>\n                  {/* Area fill */}\n                  {showArea && (\n                    <path d={areaPath} fill={color} opacity={0.1} />\n                  )}\n\n                  {/* Glow layer */}\n                  <path\n                    d={linePath}\n                    fill=\"none\"\n                    stroke={color}\n                    strokeWidth={6}\n                    opacity={0.12}\n                    pathLength={100}\n                    strokeDasharray={100}\n                    strokeDashoffset={shouldAnimate ? (drawn ? 0 : 100) : 0}\n                    style={shouldAnimate ? { transition: 'stroke-dashoffset 0.8s ease' } : undefined}\n                  />\n\n                  {/* Main line */}\n                  <path\n                    d={linePath}\n                    fill=\"none\"\n                    stroke={color}\n                    strokeWidth={1.5}\n                    pathLength={100}\n                    strokeDasharray={100}\n                    strokeDashoffset={shouldAnimate ? (drawn ? 0 : 100) : 0}\n                    style={shouldAnimate ? { transition: 'stroke-dashoffset 0.8s ease' } : undefined}\n                  />\n\n                  {/* Dots */}\n                  {showDots && s.data.map((v, i) => (\n                    <circle\n                      key={i}\n                      cx={xPos(i)}\n                      cy={yPos(v)}\n                      r={3}\n                      fill={color}\n                      opacity={shouldAnimate ? (drawn ? 1 : 0) : 1}\n                      style={shouldAnimate ? { transition: 'opacity 0.8s ease 0.4s' } : undefined}\n                    />\n                  ))}\n                </g>\n              )\n            })}\n          </svg>\n        </div>\n\n        {/* Legend */}\n        {showLeg && (\n          <div\n            style={{\n              display:    'flex',\n              flexWrap:   'wrap',\n              gap:        '0.75rem',\n              padding:    '0.5rem 0.75rem',\n              borderTop:  '1px solid var(--border)',\n              background: 'var(--surface-raised)',\n            }}\n          >\n            {series.map((s, si) => {\n              const color = seriesColor(s, si)\n              return (\n                <div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: '0.35rem' }}>\n                  <div\n                    style={{\n                      width:        '0.5rem',\n                      height:       '0.5rem',\n                      borderRadius: '50%',\n                      background:   color,\n                      flexShrink:   0,\n                    }}\n                  />\n                  <span\n                    style={{\n                      fontSize:      '0.6rem',\n                      color:         'var(--text-muted)',\n                      letterSpacing: '0.06em',\n                      textTransform: 'uppercase',\n                    }}\n                  >\n                    {s.label}\n                  </span>\n                </div>\n              )\n            })}\n          </div>\n        )}\n      </div>\n    )\n  }\n)\nLineChart.displayName = 'LineChart'\n\nexport { LineChart }\n"
    }
  ]
}