{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "line-chart",
  "title": "Line Chart",
  "description": "Multi-series lines with solid, dashed, bump, step, gradient, and glow strokes.",
  "dependencies": [
    "recharts",
    "framer-motion"
  ],
  "files": [
    {
      "path": "app/registry/charts/chart-kit.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { Rectangle } from 'recharts';\nimport type { TooltipProps } from 'recharts';\nimport { CartesianGrid, XAxis, YAxis } from 'recharts';\nimport { cn } from '@/lib/utils';\n\nexport const MONTHLY_TRAFFIC = [\n  { month: 'Jan', desktop: 186, mobile: 80 },\n  { month: 'Feb', desktop: 305, mobile: 200 },\n  { month: 'Mar', desktop: 237, mobile: 120 },\n  { month: 'Apr', desktop: 273, mobile: 190 },\n  { month: 'May', desktop: 209, mobile: 130 },\n  { month: 'Jun', desktop: 314, mobile: 140 },\n  { month: 'Jul', desktop: 280, mobile: 180 },\n  { month: 'Aug', desktop: 198, mobile: 110 },\n  { month: 'Sep', desktop: 249, mobile: 160 },\n  { month: 'Oct', desktop: 331, mobile: 210 },\n  { month: 'Nov', desktop: 256, mobile: 170 },\n  { month: 'Dec', desktop: 294, mobile: 200 },\n];\n\nexport const BROWSER_SHARE = [\n  { name: 'Chrome', value: 275 },\n  { name: 'Safari', value: 200 },\n  { name: 'Firefox', value: 187 },\n  { name: 'Edge', value: 173 },\n  { name: 'Other', value: 90 },\n];\n\nexport const RADAR_METRICS = [\n  { metric: 'Speed', desktop: 186, mobile: 80 },\n  { metric: 'Reliability', desktop: 305, mobile: 200 },\n  { metric: 'Usability', desktop: 237, mobile: 120 },\n  { metric: 'Security', desktop: 273, mobile: 190 },\n  { metric: 'Scale', desktop: 209, mobile: 130 },\n  { metric: 'Cost', desktop: 214, mobile: 140 },\n];\n\nexport type CandlePoint = {\n  date: string;\n  open: number;\n  high: number;\n  low: number;\n  close: number;\n  volume: number;\n};\n\nexport type PricePoint = {\n  time: string;\n  price: number;\n};\n\nexport type SparkPoint = {\n  i: number;\n  value: number;\n};\n\nexport type WatchlistRow = {\n  symbol: string;\n  name: string;\n  price: number;\n  change: number;\n  series: SparkPoint[];\n};\n\nexport const SOL_CANDLES: CandlePoint[] = [\n  { date: 'Jul 1', open: 128.4, high: 132.1, low: 126.8, close: 131.2, volume: 38_400_000 },\n  { date: 'Jul 2', open: 131.2, high: 134.6, low: 129.9, close: 130.4, volume: 41_200_000 },\n  { date: 'Jul 3', open: 130.4, high: 133.8, low: 127.1, close: 128.6, volume: 52_800_000 },\n  { date: 'Jul 6', open: 128.6, high: 129.4, low: 121.2, close: 123.8, volume: 61_500_000 },\n  { date: 'Jul 7', open: 123.8, high: 126.9, low: 122.4, close: 125.1, volume: 44_100_000 },\n  { date: 'Jul 8', open: 125.1, high: 131.7, low: 124.8, close: 130.9, volume: 48_600_000 },\n  { date: 'Jul 9', open: 130.9, high: 136.4, low: 130.2, close: 135.6, volume: 55_200_000 },\n  { date: 'Jul 10', open: 135.6, high: 137.2, low: 132.8, close: 133.4, volume: 39_800_000 },\n  { date: 'Jul 13', open: 133.4, high: 139.8, low: 133.1, close: 138.9, volume: 47_300_000 },\n  { date: 'Jul 14', open: 138.9, high: 142.6, low: 137.4, close: 141.2, volume: 51_900_000 },\n  { date: 'Jul 15', open: 141.2, high: 143.1, low: 136.5, close: 137.8, volume: 58_400_000 },\n  { date: 'Jul 16', open: 137.8, high: 140.4, low: 135.9, close: 139.6, volume: 36_700_000 },\n  { date: 'Jul 17', open: 139.6, high: 146.2, low: 139.1, close: 145.4, volume: 62_100_000 },\n  { date: 'Jul 20', open: 145.4, high: 147.8, low: 141.6, close: 142.3, volume: 49_500_000 },\n  { date: 'Jul 21', open: 142.3, high: 144.9, low: 140.8, close: 144.1, volume: 33_200_000 },\n  { date: 'Jul 22', open: 144.1, high: 148.6, low: 143.7, close: 147.9, volume: 54_800_000 },\n  { date: 'Jul 23', open: 147.9, high: 149.4, low: 144.2, close: 145.6, volume: 42_600_000 },\n  { date: 'Jul 24', open: 145.6, high: 151.2, low: 145.1, close: 150.4, volume: 57_300_000 },\n];\n\nexport const AAPL_CANDLES: CandlePoint[] = [\n  { date: 'Jul 1', open: 214.2, high: 216.8, low: 213.1, close: 215.6, volume: 48_200_000 },\n  { date: 'Jul 2', open: 215.6, high: 217.4, low: 214.8, close: 216.9, volume: 41_600_000 },\n  { date: 'Jul 3', open: 216.9, high: 218.1, low: 212.4, close: 213.2, volume: 55_400_000 },\n  { date: 'Jul 6', open: 213.2, high: 214.6, low: 208.9, close: 210.4, volume: 62_800_000 },\n  { date: 'Jul 7', open: 210.4, high: 212.8, low: 209.6, close: 211.9, volume: 39_100_000 },\n  { date: 'Jul 8', open: 211.9, high: 217.3, low: 211.4, close: 216.8, volume: 51_700_000 },\n  { date: 'Jul 9', open: 216.8, high: 219.6, low: 216.1, close: 218.4, volume: 44_300_000 },\n  { date: 'Jul 10', open: 218.4, high: 219.2, low: 214.7, close: 215.1, volume: 47_900_000 },\n  { date: 'Jul 13', open: 215.1, high: 221.4, low: 214.8, close: 220.6, volume: 53_200_000 },\n  { date: 'Jul 14', open: 220.6, high: 223.8, low: 219.9, close: 222.1, volume: 49_800_000 },\n  { date: 'Jul 15', open: 222.1, high: 222.9, low: 217.6, close: 218.4, volume: 58_100_000 },\n  { date: 'Jul 16', open: 218.4, high: 221.2, low: 217.9, close: 220.8, volume: 36_400_000 },\n  { date: 'Jul 17', open: 220.8, high: 226.4, low: 220.3, close: 225.7, volume: 61_500_000 },\n  { date: 'Jul 20', open: 225.7, high: 227.1, low: 222.8, close: 223.4, volume: 42_700_000 },\n  { date: 'Jul 21', open: 223.4, high: 225.6, low: 222.1, close: 224.9, volume: 33_900_000 },\n  { date: 'Jul 22', open: 224.9, high: 229.3, low: 224.4, close: 228.6, volume: 56_200_000 },\n  { date: 'Jul 23', open: 228.6, high: 229.8, low: 225.2, close: 226.1, volume: 40_800_000 },\n  { date: 'Jul 24', open: 226.1, high: 231.4, low: 225.8, close: 230.2, volume: 52_600_000 },\n];\n\nconst SOL_PRICES = [\n  128.4, 129.1, 127.6, 130.8, 132.4, 131.2, 129.8, 133.6, 136.2, 134.9, 132.1, 135.8, 138.4, 141.2,\n  139.6, 137.8, 140.4, 144.1, 146.8, 145.2, 142.6, 147.4, 149.1, 150.4,\n];\nconst NVDA_PRICES = [\n  118.2, 119.4, 117.8, 121.6, 124.1, 123.2, 126.8, 129.4, 128.1, 131.6, 134.2, 132.8, 136.4, 139.1,\n  137.6, 141.2, 144.8, 143.2, 147.6, 151.4, 149.8, 153.2, 156.1, 158.4,\n];\nconst ETH_PRICES = [\n  3420, 3398, 3444, 3482, 3461, 3510, 3548, 3522, 3494, 3556, 3602, 3580, 3624, 3668, 3640, 3612,\n  3674, 3718, 3690, 3742, 3788, 3760, 3812, 3846,\n];\nconst TVL_VALUES = [\n  6.12, 6.18, 6.09, 6.24, 6.41, 6.36, 6.28, 6.52, 6.71, 6.64, 6.48, 6.69, 6.88, 7.04, 6.96, 6.82,\n  7.11, 7.28, 7.19, 7.42, 7.58, 7.51, 7.69, 7.84,\n];\n\nfunction toPriceSeries(values: number[], prefix = 't'): PricePoint[] {\n  return values.map((price, index) => ({ time: `${prefix}${index + 1}`, price }));\n}\n\nfunction toSpark(values: number[]): SparkPoint[] {\n  return values.map((value, i) => ({ i, value }));\n}\n\nexport const SOL_PRICE = toPriceSeries(SOL_PRICES);\nexport const NVDA_PRICE = toPriceSeries(NVDA_PRICES);\nexport const ETH_PRICE = toPriceSeries(ETH_PRICES);\nexport const SOLANA_TVL = toPriceSeries(TVL_VALUES.map((value) => value * 1_000_000_000));\n\nexport const WATCHLIST: WatchlistRow[] = [\n  {\n    symbol: 'SOL',\n    name: 'Solana',\n    price: 150.4,\n    change: 17.13,\n    series: toSpark(SOL_PRICES),\n  },\n  {\n    symbol: 'ETH',\n    name: 'Ethereum',\n    price: 3846,\n    change: 12.46,\n    series: toSpark(ETH_PRICES),\n  },\n  {\n    symbol: 'JUP',\n    name: 'Jupiter',\n    price: 0.84,\n    change: -6.21,\n    series: toSpark([0.91, 0.89, 0.9, 0.87, 0.86, 0.88, 0.85, 0.83, 0.84, 0.82, 0.81, 0.84]),\n  },\n  {\n    symbol: 'AAPL',\n    name: 'Apple',\n    price: 230.2,\n    change: 7.47,\n    series: toSpark(AAPL_CANDLES.map((row) => row.close)),\n  },\n  {\n    symbol: 'NVDA',\n    name: 'NVIDIA',\n    price: 158.4,\n    change: 34.01,\n    series: toSpark(NVDA_PRICES),\n  },\n];\n\nexport function formatUsd(value: number, compact = false) {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD',\n    notation: compact ? 'compact' : 'standard',\n    maximumFractionDigits: compact ? 2 : value >= 100 ? 2 : value >= 1 ? 2 : 4,\n  }).format(value);\n}\n\nexport function formatPct(value: number) {\n  const sign = value > 0 ? '+' : '';\n  return `${sign}${value.toFixed(2)}%`;\n}\n\nexport function seriesDelta(values: number[]) {\n  if (values.length < 2) return 0;\n  const first = values[0];\n  const last = values[values.length - 1];\n  if (first === 0) return 0;\n  return ((last - first) / first) * 100;\n}\n\nexport const SERIES = {\n  desktop: { label: 'Desktop', color: 'var(--spectrum-chart-1)' },\n  mobile: { label: 'Mobile', color: 'var(--spectrum-chart-2)' },\n} as const;\n\nexport const CHART_COLORS = [\n  'var(--spectrum-chart-1)',\n  'var(--spectrum-chart-2)',\n  'var(--spectrum-chart-3)',\n  'var(--spectrum-chart-4)',\n  'var(--spectrum-chart-5)',\n] as const;\n\nexport const chartVarsClassName =\n  '[--spectrum-chart-1:#2563eb] [--spectrum-chart-2:#f59e0b] [--spectrum-chart-3:#0d9488] [--spectrum-chart-4:#7c3aed] [--spectrum-chart-5:#64748b] [--spectrum-chart-surface:#fff] [--spectrum-chart-up:#059669] [--spectrum-chart-down:#e11d48] dark:[--spectrum-chart-1:#60a5fa] dark:[--spectrum-chart-2:#fbbf24] dark:[--spectrum-chart-3:#2dd4bf] dark:[--spectrum-chart-4:#a78bfa] dark:[--spectrum-chart-5:#94a3b8] dark:[--spectrum-chart-surface:#0a0a0a] dark:[--spectrum-chart-up:#34d399] dark:[--spectrum-chart-down:#fb7185]';\n\nexport const chartVarsNeutral =\n  '[--spectrum-chart-1:#171717] [--spectrum-chart-2:#737373] [--spectrum-chart-3:#52525b] [--spectrum-chart-4:#a3a3a3] [--spectrum-chart-5:#d4d4d8] [--spectrum-chart-surface:#fff] [--spectrum-chart-up:#059669] [--spectrum-chart-down:#e11d48] dark:[--spectrum-chart-1:#f5f5f5] dark:[--spectrum-chart-2:#a3a3a3] dark:[--spectrum-chart-3:#d4d4d8] dark:[--spectrum-chart-4:#737373] dark:[--spectrum-chart-5:#52525b] dark:[--spectrum-chart-surface:#0a0a0a] dark:[--spectrum-chart-up:#34d399] dark:[--spectrum-chart-down:#fb7185]';\n\nexport const EASE_OUT = [0.23, 1, 0.32, 1] as const;\nexport const BAR_STAGGER = 0.04;\nexport const BAR_GROW = 0.22;\nexport const REVEAL_DURATION = 0.28;\nexport const HOVER_MS = 160;\nexport const HOVER_OPACITY = 0.28;\nexport const HOVER_TRANSITION = `opacity ${HOVER_MS}ms cubic-bezier(0.23, 1, 0.32, 1)`;\n\nexport type BarFillVariant =\n  | 'default'\n  | 'hatched'\n  | 'duotone'\n  | 'duotone-reverse'\n  | 'gradient'\n  | 'stripped';\n\nexport type AreaFillVariant = 'gradient' | 'gradient-reverse' | 'solid' | 'hatched' | 'dotted';\n\nexport type StrokeVariant = 'solid' | 'dashed' | 'animated-dashed';\n\nexport function useChartId(prefix = 'chart') {\n  const reactId = React.useId().replace(/:/g, '');\n  return `${prefix}-${reactId}`;\n}\n\nexport function useChartMotion() {\n  const reduce = Boolean(useReducedMotion());\n  return {\n    reduce,\n    isAnimationActive: !reduce,\n    animationDuration: reduce ? 0 : 220,\n    ease: EASE_OUT,\n  };\n}\n\nexport function useIntroStartedAt() {\n  const [introStartedAt] = React.useState(() => Date.now());\n  return introStartedAt;\n}\n\nconst HoverIndexContext = React.createContext<number | null>(null);\n\nexport function HoverIndexProvider({\n  value,\n  children,\n}: {\n  value: number | null;\n  children: React.ReactNode;\n}) {\n  return <HoverIndexContext.Provider value={value}>{children}</HoverIndexContext.Provider>;\n}\n\nexport function readActiveTooltipIndex(state: { activeTooltipIndex?: number | string | null }) {\n  return typeof state.activeTooltipIndex === 'number' ? state.activeTooltipIndex : null;\n}\n\nexport function strokeDasharray(variant: StrokeVariant) {\n  return variant === 'solid' ? undefined : '5 5';\n}\n\nexport function ChartFrame({\n  className,\n  children,\n}: {\n  className?: string;\n  children: React.ReactNode;\n}) {\n  return (\n    <div className={cn('h-[280px] w-full text-neutral-400 dark:text-neutral-500', chartVarsClassName, className)}>\n      {children}\n    </div>\n  );\n}\n\nexport function ChartPlotSurface({\n  children,\n  className,\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) {\n  return (\n    <div className={cn('relative min-h-0 flex-1', className)}>\n      <div\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 opacity-[0.22] [background-image:radial-gradient(circle,currentColor_0.55px,transparent_0.55px)] [background-size:12px_12px]\"\n      />\n      <div className=\"relative h-full min-h-0\">{children}</div>\n    </div>\n  );\n}\n\nexport function ChartLegend({\n  items = [\n    { label: SERIES.desktop.label, color: SERIES.desktop.color },\n    { label: SERIES.mobile.label, color: SERIES.mobile.color },\n  ],\n  className,\n}: {\n  items?: { label: string; color: string }[];\n  className?: string;\n}) {\n  return (\n    <ul className={cn('mb-2 flex justify-end gap-3 text-[11px] tracking-wide text-neutral-500 dark:text-neutral-400', className)}>\n      {items.map((item) => (\n        <li key={item.label} className=\"flex items-center gap-1.5\">\n          <span\n            className=\"size-2 shrink-0 rounded-[3px] ring-1 ring-black/5 dark:ring-white/10\"\n            style={{ background: item.color }}\n          />\n          {item.label}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport function ChartTooltipContent({\n  active,\n  payload,\n  label,\n}: TooltipProps<number, string>) {\n  const reduce = Boolean(useReducedMotion());\n\n  if (!active || !payload?.length) {\n    return <span className=\"invisible block h-10 w-28\" aria-hidden />;\n  }\n\n  return (\n    <motion.div\n      initial={reduce ? { opacity: 0 } : { opacity: 0, transform: 'scale(0.97)' }}\n      animate={reduce ? { opacity: 1 } : { opacity: 1, transform: 'scale(1)' }}\n      transition={{ duration: 0.16, ease: EASE_OUT }}\n      className=\"min-w-[7.5rem] rounded-lg border border-neutral-200/70 bg-white/75 px-3 py-2 text-xs shadow-xl backdrop-blur-md dark:border-neutral-800/80 dark:bg-neutral-950/70\"\n    >\n      {label != null ? (\n        <p className=\"mb-1.5 font-medium text-neutral-900 dark:text-neutral-100\">{String(label)}</p>\n      ) : null}\n      <ul className=\"flex flex-col gap-1\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item) => (\n            <li key={String(item.dataKey ?? item.name)} className=\"flex items-center gap-2\">\n              <span\n                className=\"size-2 shrink-0 rounded-[3px] ring-1 ring-black/5 dark:ring-white/10\"\n                style={{ background: String(item.color ?? item.payload?.fill ?? SERIES.desktop.color) }}\n              />\n              <span className=\"text-neutral-500 dark:text-neutral-400\">{item.name}</span>\n              <span className=\"ml-auto font-mono tabular-nums text-neutral-900 dark:text-neutral-100\">\n                {typeof item.value === 'number' ? item.value.toLocaleString() : String(item.value ?? '')}\n              </span>\n            </li>\n          ))}\n      </ul>\n    </motion.div>\n  );\n}\n\nexport const axisTick = {\n  fill: 'currentColor',\n  fontSize: 11,\n  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',\n} as const;\n\nexport const chartGrid = {\n  vertical: false,\n  stroke: 'currentColor',\n  strokeOpacity: 0.14,\n  strokeDasharray: '3 3',\n} satisfies Partial<React.ComponentProps<typeof CartesianGrid>>;\n\nexport const chartXAxis = {\n  axisLine: false,\n  tickLine: false,\n  tickMargin: 8,\n  tick: axisTick,\n  minTickGap: 8,\n} satisfies Partial<React.ComponentProps<typeof XAxis>>;\n\nexport const chartYAxis = {\n  axisLine: false,\n  tickLine: false,\n  tickMargin: 8,\n  tick: axisTick,\n  width: 44,\n} satisfies Partial<React.ComponentProps<typeof YAxis>>;\n\nexport function ChartGlowFilter({ id }: { id: string }) {\n  return (\n    <filter id={id} x=\"-80%\" y=\"-80%\" width=\"260%\" height=\"260%\">\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"8\" result=\"blur\" />\n      <feColorMatrix\n        in=\"blur\"\n        type=\"matrix\"\n        values=\"1 0 0 0 0\n                0 1 0 0 0\n                0 0 1 0 0\n                0 0 0 0.5 0\"\n        result=\"glow\"\n      />\n      <feMerge>\n        <feMergeNode in=\"glow\" />\n        <feMergeNode in=\"SourceGraphic\" />\n      </feMerge>\n    </filter>\n  );\n}\n\nexport function RevealMask({\n  id,\n  introStartedAt,\n  reduce = false,\n}: {\n  id: string;\n  introStartedAt: number;\n  reduce?: boolean;\n}) {\n  // eslint-disable-next-line react-hooks/purity -- one-shot intro is anchored to mount time\n  const elapsed = Date.now() - introStartedAt;\n  const durationMs = REVEAL_DURATION * 1000;\n  const finished = reduce || elapsed >= durationMs;\n  const from = elapsed <= 0 ? 0 : Math.min(1, elapsed / durationMs);\n\n  return (\n    <mask\n      id={id}\n      maskUnits=\"userSpaceOnUse\"\n      maskContentUnits=\"userSpaceOnUse\"\n      x=\"0\"\n      y=\"0\"\n      width=\"100%\"\n      height=\"100%\"\n    >\n      {finished ? (\n        <rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" fill=\"white\" />\n      ) : (\n        <motion.rect\n          x=\"0\"\n          y=\"0\"\n          width=\"100%\"\n          height=\"100%\"\n          fill=\"white\"\n          initial={{ transform: `scaleX(${from})` }}\n          animate={{ transform: 'scaleX(1)' }}\n          transition={{\n            duration: (durationMs - elapsed) / 1000,\n            ease: EASE_OUT,\n          }}\n          style={{ transformOrigin: 'left center' }}\n        />\n      )}\n    </mask>\n  );\n}\n\nexport function AnimatedDashedStroke() {\n  const reduce = Boolean(useReducedMotion());\n  if (reduce) return null;\n\n  return (\n    <>\n      <animate\n        key=\"dasharray\"\n        attributeName=\"stroke-dasharray\"\n        values=\"5 5; 0 5; 5 5\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        calcMode=\"linear\"\n      />\n      <animate\n        key=\"dashoffset\"\n        attributeName=\"stroke-dashoffset\"\n        values=\"0; -10\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        calcMode=\"linear\"\n      />\n    </>\n  );\n}\n\nexport type ChartDotRenderProps = {\n  cx?: number;\n  cy?: number;\n  fill?: string;\n  index?: number;\n};\n\nexport function ChartRestingDot({\n  cx,\n  cy,\n  color,\n  maskId,\n  r = 3,\n}: {\n  cx?: number;\n  cy?: number;\n  color: string;\n  maskId?: string;\n  r?: number;\n}) {\n  if (cx == null || cy == null) return null;\n  return (\n    <circle\n      cx={cx}\n      cy={cy}\n      r={r}\n      fill={color}\n      stroke=\"var(--spectrum-chart-surface)\"\n      strokeWidth={1.5}\n      mask={maskId ? `url(#${maskId})` : undefined}\n    />\n  );\n}\n\nexport function ChartActiveDot({\n  cx,\n  cy,\n  color,\n}: {\n  cx?: number;\n  cy?: number;\n  color?: string;\n}) {\n  if (cx == null || cy == null) return null;\n  return (\n    <g>\n      <circle cx={cx} cy={cy} r={6.5} fill=\"var(--spectrum-chart-surface)\" />\n      <circle cx={cx} cy={cy} r={3.25} fill={color ?? SERIES.desktop.color} />\n    </g>\n  );\n}\n\ntype GrowAxis = {\n  index?: number;\n  x?: number;\n  y?: number;\n  width?: number;\n  height?: number;\n  fill?: string;\n  background?: { x?: number; y?: number; width?: number; height?: number };\n};\n\nexport type GrowBarOptions = {\n  horizontal?: boolean;\n  introStartedAt: number;\n  dataLength: number;\n  reduce?: boolean;\n  radius?: number | [number, number, number, number];\n  stripped?: boolean;\n  glowId?: string;\n};\n\nfunction getBarGrowAnimation({\n  index,\n  dataLength,\n  horizontal,\n  introStartedAt,\n  reduce,\n}: {\n  index: number;\n  dataLength: number;\n  horizontal: boolean;\n  introStartedAt: number;\n  reduce: boolean;\n}) {\n  if (reduce || index < 0 || dataLength <= 0) return null;\n\n  const startMs = index * BAR_STAGGER * 1000;\n  const durationMs = BAR_GROW * 1000;\n  const endMs = startMs + durationMs;\n  const elapsed = Date.now() - introStartedAt;\n\n  if (elapsed >= endMs) return null;\n\n  const from = elapsed <= startMs ? 0 : (elapsed - startMs) / durationMs;\n  const axis = horizontal ? 'X' : 'Y';\n\n  return {\n    initial: { transform: `scale${axis}(${from})` },\n    animate: { transform: `scale${axis}(1)` },\n    transition: {\n      duration: (endMs - Math.max(elapsed, startMs)) / 1000,\n      ease: EASE_OUT,\n      delay: Math.max(0, startMs - elapsed) / 1000,\n    },\n    style: {\n      transformOrigin: horizontal ? 'left center' : 'center bottom',\n    } as React.CSSProperties,\n  };\n}\n\nexport function GrowBar({\n  x = 0,\n  y = 0,\n  width = 0,\n  height = 0,\n  fill,\n  index = 0,\n  background,\n  horizontal = false,\n  introStartedAt,\n  dataLength,\n  reduce = false,\n  radius = 4,\n  stripped = false,\n  glowId,\n}: GrowAxis & GrowBarOptions) {\n  const grow = getBarGrowAnimation({\n    index,\n    dataLength,\n    horizontal,\n    introStartedAt,\n    reduce,\n  });\n  const hoverIndex = React.useContext(HoverIndexContext);\n  const dimmed = hoverIndex != null && hoverIndex !== index;\n  const paintedHeight = Math.max(0, height - (stripped && !horizontal ? 3 : 0));\n  const paintedWidth = Math.max(0, width - (stripped && horizontal ? 3 : 0));\n  const hit = background ?? { x, y, width, height };\n\n  const painted = (\n    <>\n      <Rectangle\n        x={x}\n        y={y}\n        width={paintedWidth}\n        height={paintedHeight}\n        radius={radius}\n        fill={fill}\n        filter={glowId ? `url(#${glowId})` : undefined}\n        style={{\n          opacity: dimmed ? HOVER_OPACITY : 1,\n          transition: HOVER_TRANSITION,\n        }}\n      />\n      {stripped && !horizontal ? (\n        <Rectangle\n          x={x}\n          y={y - 3}\n          width={width}\n          height={2}\n          radius={1}\n          fill={fill}\n          style={{\n            opacity: dimmed ? HOVER_OPACITY : 1,\n            transition: HOVER_TRANSITION,\n          }}\n        />\n      ) : null}\n      {stripped && horizontal ? (\n        <Rectangle\n          x={x + width - 2}\n          y={y}\n          width={2}\n          height={height}\n          radius={1}\n          fill={fill}\n          style={{\n            opacity: dimmed ? HOVER_OPACITY : 1,\n            transition: HOVER_TRANSITION,\n          }}\n        />\n      ) : null}\n    </>\n  );\n\n  return (\n    <g style={{ cursor: 'pointer' }}>\n      <rect\n        x={hit.x ?? x}\n        y={hit.y ?? y}\n        width={hit.width ?? width}\n        height={hit.height ?? height}\n        fill=\"transparent\"\n      />\n      {grow ? (\n        <motion.g\n          initial={grow.initial}\n          animate={grow.animate}\n          transition={grow.transition}\n          style={grow.style}\n        >\n          {painted}\n        </motion.g>\n      ) : (\n        painted\n      )}\n    </g>\n  );\n}\n\nexport function createGrowBarShape(options: GrowBarOptions) {\n  function Shape(props: unknown) {\n    return <GrowBar {...(props as GrowAxis)} {...options} />;\n  }\n  Shape.displayName = 'SpectrumGrowBar';\n  return Shape;\n}\n\nexport function markOpacity(active: string | number | null, key: string | number) {\n  if (active == null) return 1;\n  return active === key ? 1 : HOVER_OPACITY;\n}\n\nexport function BarFillDefs({\n  id,\n  color,\n  variant,\n}: {\n  id: string;\n  color: string;\n  variant: BarFillVariant;\n}) {\n  return (\n    <>\n      {variant === 'hatched' ? (\n        <pattern\n          id={`${id}-hatched`}\n          width=\"7\"\n          height=\"7\"\n          patternUnits=\"userSpaceOnUse\"\n          patternTransform=\"rotate(45)\"\n        >\n          <rect width=\"7\" height=\"7\" fill={color} opacity={0.16} />\n          <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"7\" stroke={color} strokeWidth=\"2.4\" />\n        </pattern>\n      ) : null}\n      {variant === 'duotone' || variant === 'duotone-reverse' ? (\n        <linearGradient id={`${id}-${variant}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor={color} stopOpacity={variant === 'duotone' ? 1 : 0.28} />\n          <stop offset=\"50%\" stopColor={color} stopOpacity={variant === 'duotone' ? 1 : 0.28} />\n          <stop offset=\"50%\" stopColor={color} stopOpacity={variant === 'duotone' ? 0.28 : 1} />\n          <stop offset=\"100%\" stopColor={color} stopOpacity={variant === 'duotone' ? 0.28 : 1} />\n        </linearGradient>\n      ) : null}\n      {variant === 'gradient' ? (\n        <linearGradient id={`${id}-gradient`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor={color} stopOpacity={1} />\n          <stop offset=\"100%\" stopColor={color} stopOpacity={0.18} />\n        </linearGradient>\n      ) : null}\n      {variant === 'stripped' ? (\n        <pattern id={`${id}-stripped`} width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">\n          <rect width=\"8\" height=\"8\" fill={color} opacity={0.14} />\n          <rect width=\"4\" height=\"8\" fill={color} />\n        </pattern>\n      ) : null}\n    </>\n  );\n}\n\nexport function barFillUrl(id: string, variant: BarFillVariant, color: string) {\n  if (variant === 'default') return color;\n  return `url(#${id}-${variant})`;\n}\n\nexport function AreaFillDefs({\n  id,\n  color,\n  variant,\n}: {\n  id: string;\n  color: string;\n  variant: AreaFillVariant;\n}) {\n  return (\n    <>\n      {variant === 'gradient' || variant === 'gradient-reverse' ? (\n        <linearGradient id={`${id}-${variant}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"5%\" stopColor={color} stopOpacity={variant === 'gradient' ? 0.55 : 0.08} />\n          <stop offset=\"95%\" stopColor={color} stopOpacity={variant === 'gradient' ? 0.08 : 0.55} />\n        </linearGradient>\n      ) : null}\n      {variant === 'hatched' ? (\n        <pattern\n          id={`${id}-hatched`}\n          width=\"8\"\n          height=\"8\"\n          patternUnits=\"userSpaceOnUse\"\n          patternTransform=\"rotate(45)\"\n        >\n          <rect width=\"8\" height=\"8\" fill={color} opacity={0.1} />\n          <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"8\" stroke={color} strokeWidth=\"2\" />\n        </pattern>\n      ) : null}\n      {variant === 'dotted' ? (\n        <pattern id={`${id}-dotted`} width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\">\n          <circle cx=\"1.5\" cy=\"1.5\" r=\"1.15\" fill={color} />\n        </pattern>\n      ) : null}\n    </>\n  );\n}\n\nexport function areaFillUrl(id: string, variant: AreaFillVariant, color: string) {\n  if (variant === 'solid') return color;\n  return `url(#${id}-${variant})`;\n}\n\nconst SHIMMER_HEIGHTS = [42, 68, 51, 79, 46, 88, 57, 73, 39, 84, 62, 71];\n\nexport function ChartLoadingBars({ count = 12 }: { count?: number }) {\n  const reduce = Boolean(useReducedMotion());\n\n  return (\n    <div className=\"relative h-full overflow-hidden\">\n      <div className=\"flex h-full items-end gap-2 px-2 pb-6 pt-8\">\n        {Array.from({ length: count }, (_, index) => (\n          <div\n            key={index}\n            className=\"flex-1 rounded-sm bg-neutral-200 dark:bg-neutral-800\"\n            style={{ height: `${SHIMMER_HEIGHTS[index % SHIMMER_HEIGHTS.length]}%` }}\n          />\n        ))}\n      </div>\n      {reduce ? null : (\n        <motion.div\n          aria-hidden\n          className=\"pointer-events-none absolute inset-y-0 w-1/3 bg-linear-to-r from-transparent via-white/55 to-transparent dark:via-white/12\"\n          initial={{ transform: 'translateX(-120%)' }}\n          animate={{ transform: 'translateX(420%)' }}\n          transition={{ duration: 1.15, ease: 'linear', repeat: Infinity }}\n        />\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/spectrumui/charts/chart-kit.tsx"
    },
    {
      "path": "app/registry/charts/line-chart.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  CartesianGrid,\n  Line,\n  LineChart as RechartsLineChart,\n  ResponsiveContainer,\n  Tooltip,\n  XAxis,\n  YAxis,\n} from 'recharts';\nimport { cn } from '@/lib/utils';\nimport {\n  AnimatedDashedStroke,\n  ChartActiveDot,\n  ChartFrame,\n  ChartGlowFilter,\n  chartGrid,\n  ChartLegend,\n  ChartLoadingBars,\n  ChartPlotSurface,\n  ChartRestingDot,\n  ChartTooltipContent,\n  chartXAxis,\n  chartYAxis,\n  type ChartDotRenderProps,\n  MONTHLY_TRAFFIC,\n  RevealMask,\n  SERIES,\n  type StrokeVariant,\n  strokeDasharray,\n  useChartId,\n  useChartMotion,\n  useIntroStartedAt,\n} from './chart-kit';\n\nexport type LineCurve = 'monotone' | 'bump' | 'step' | 'linear';\n\nexport interface SpectrumLineChartProps {\n  className?: string;\n  data?: typeof MONTHLY_TRAFFIC;\n  curveType?: LineCurve;\n  strokeVariant?: StrokeVariant;\n  desktopStroke?: StrokeVariant;\n  mobileStroke?: StrokeVariant;\n  glowing?: boolean;\n  gradientStroke?: boolean;\n  isLoading?: boolean;\n  showLegend?: boolean;\n  showDots?: boolean;\n}\n\nexport function LineChart({\n  className,\n  data = MONTHLY_TRAFFIC,\n  curveType = 'monotone',\n  strokeVariant = 'solid',\n  desktopStroke,\n  mobileStroke,\n  glowing = false,\n  gradientStroke = false,\n  isLoading = false,\n  showLegend = true,\n  showDots = true,\n}: SpectrumLineChartProps) {\n  const id = useChartId('line');\n  const { reduce } = useChartMotion();\n  const introStartedAt = useIntroStartedAt();\n  const glowId = `${id}-glow`;\n  const maskId = `${id}-reveal`;\n  const desktopKind = desktopStroke ?? strokeVariant;\n  const mobileKind = mobileStroke ?? strokeVariant;\n  const desktopColor = gradientStroke ? `url(#${id}-desktop-stroke)` : SERIES.desktop.color;\n  const mobileColor = gradientStroke ? `url(#${id}-mobile-stroke)` : SERIES.mobile.color;\n  const maskStyle = reduce ? undefined : { mask: `url(#${maskId})` };\n\n  return (\n    <ChartFrame className={cn('flex flex-col', className)}>\n      {showLegend ? <ChartLegend /> : null}\n      {isLoading ? (\n        <ChartLoadingBars />\n      ) : (\n        <ChartPlotSurface>\n          <ResponsiveContainer width=\"100%\" height=\"100%\">\n            <RechartsLineChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>\n              <defs>\n                {gradientStroke ? (\n                  <>\n                    <linearGradient id={`${id}-desktop-stroke`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n                      <stop offset=\"0%\" stopColor={SERIES.desktop.color} />\n                      <stop offset=\"100%\" stopColor={SERIES.mobile.color} />\n                    </linearGradient>\n                    <linearGradient id={`${id}-mobile-stroke`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n                      <stop offset=\"0%\" stopColor={SERIES.mobile.color} />\n                      <stop offset=\"100%\" stopColor={SERIES.desktop.color} />\n                    </linearGradient>\n                  </>\n                ) : null}\n                {glowing ? <ChartGlowFilter id={glowId} /> : null}\n                <RevealMask id={maskId} introStartedAt={introStartedAt} reduce={reduce} />\n              </defs>\n              <CartesianGrid {...chartGrid} />\n              <XAxis {...chartXAxis} dataKey=\"month\" />\n              <YAxis {...chartYAxis} />\n              <Tooltip\n                cursor={{ stroke: 'currentColor', strokeOpacity: 0.22, strokeDasharray: '4 4' }}\n                content={<ChartTooltipContent />}\n              />\n              <Line\n                type={curveType}\n                dataKey=\"desktop\"\n                name={SERIES.desktop.label}\n                stroke={desktopColor}\n                strokeWidth={2.25}\n                strokeDasharray={strokeDasharray(desktopKind)}\n                dot={\n                  showDots\n                    ? (props: ChartDotRenderProps) => (\n                        <ChartRestingDot\n                          key={`dot-${props.index}`}\n                          cx={props.cx}\n                          cy={props.cy}\n                          color={SERIES.desktop.color}\n                          maskId={reduce ? undefined : maskId}\n                        />\n                      )\n                    : false\n                }\n                activeDot={(props: ChartDotRenderProps) => (\n                  <ChartActiveDot key={`active-${props.index}`} cx={props.cx} cy={props.cy} color={SERIES.desktop.color} />\n                )}\n                isAnimationActive={false}\n                filter={glowing ? `url(#${glowId})` : undefined}\n                style={maskStyle}\n              >\n                {desktopKind === 'animated-dashed' ? <AnimatedDashedStroke /> : null}\n              </Line>\n              <Line\n                type={curveType}\n                dataKey=\"mobile\"\n                name={SERIES.mobile.label}\n                stroke={mobileColor}\n                strokeWidth={2.25}\n                strokeDasharray={strokeDasharray(mobileKind)}\n                dot={\n                  showDots\n                    ? (props: ChartDotRenderProps) => (\n                        <ChartRestingDot\n                          key={`dot-${props.index}`}\n                          cx={props.cx}\n                          cy={props.cy}\n                          color={SERIES.mobile.color}\n                          maskId={reduce ? undefined : maskId}\n                        />\n                      )\n                    : false\n                }\n                activeDot={(props: ChartDotRenderProps) => (\n                  <ChartActiveDot key={`active-${props.index}`} cx={props.cx} cy={props.cy} color={SERIES.mobile.color} />\n                )}\n                isAnimationActive={false}\n                style={maskStyle}\n              >\n                {mobileKind === 'animated-dashed' ? <AnimatedDashedStroke /> : null}\n              </Line>\n            </RechartsLineChart>\n          </ResponsiveContainer>\n        </ChartPlotSurface>\n      )}\n    </ChartFrame>\n  );\n}\n\nexport function DefaultLineChart(props: SpectrumLineChartProps) {\n  return <LineChart {...props} />;\n}\n\nexport function DashedLineChart(props: SpectrumLineChartProps) {\n  return <LineChart strokeVariant=\"animated-dashed\" {...props} />;\n}\n\nexport function BumpLineChart(props: SpectrumLineChartProps) {\n  return <LineChart curveType=\"bump\" {...props} />;\n}\n\nexport function StepLineChart(props: SpectrumLineChartProps) {\n  return <LineChart curveType=\"step\" {...props} />;\n}\n\nexport function GlowingLineChart(props: SpectrumLineChartProps) {\n  return <LineChart glowing {...props} />;\n}\n\nexport function GradientLineChart(props: SpectrumLineChartProps) {\n  return <LineChart gradientStroke {...props} />;\n}\n",
      "type": "registry:component",
      "target": "components/spectrumui/charts/line-chart.tsx"
    }
  ],
  "type": "registry:component"
}
