{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "title": "Chart",
  "author": "AlgorizeTech",
  "description": "A dependency-free chart rendering bar, line, pie and donut from a single data array. Use it for simple dashboard visuals without pulling in a charting library.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://avaraui.com/r/popover.json",
    "https://avaraui.com/r/toggle-group.json",
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/ChartBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  ArrowDownRight,\n  ArrowUpRight,\n  ChartColumnBig,\n  ChartSpline,\n  Info,\n  Mail,\n  Minus,\n  PenLine,\n  UserPlus,\n  Wallet,\n} from \"lucide-react\";\nimport { useId, useMemo, useState } from \"react\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./PopoverBlock\";\nimport { ToggleGroup, ToggleGroupItem } from \"./ToggleGroupBlock\";\n\n// ==========================================\n// DYNAMIC THEME ENGINE\n// ==========================================\nexport type ChartColor =\n  | \"indigo\"\n  | \"rose\"\n  | \"emerald\"\n  | \"zinc\"\n  | \"amber\"\n  | \"blue\"\n  | string;\nexport type ChartType =\n  | \"bar\"\n  | \"line\"\n  | \"pie\"\n  | \"donut\"\n  /** A categorical donut with a legend beside it; hover either to read a share. */\n  | \"donut-legend\"\n  /** One horizontal bar split into shares, legend below. */\n  | \"stacked-bar\"\n  /** Semicircular tick gauge with categorized shares and conversion rate. */\n  | \"traffic-sources\"\n  /** Proportional barcode visualization with country ISO badges and share breakdown. */\n  | \"visitors-by-country\"\n  /** Multi-metric tabs overview with pill columns, dashed average line, and trend spline toggle. */\n  | \"metrics-overview\";\n\nconst colorMap: Record<string, string> = {\n  indigo: \"#4f46e5\",\n  rose: \"#e11d48\",\n  emerald: \"#10b981\",\n  zinc: \"#3f3f46\",\n  amber: \"#f59e0b\",\n  blue: \"#3b82f6\",\n};\n\nconst getThemeColor = (color: string) => {\n  if (color.startsWith(\"#\") || color.startsWith(\"var(\")) return color;\n  return colorMap[color] || \"var(--primary)\";\n};\n\nconst inkOn = (color: string) =>\n  color === \"var(--primary)\" ? \"text-primary-foreground\" : \"text-white\";\n\n/**\n * Categorical palette for the legend charts, where every share needs its own\n * hue rather than a shade of one. The theme accent leads; the grey at the end\n * is what an \"Other\" bucket lands on when there are five shares.\n */\nconst CATEGORICAL = [\n  \"var(--primary)\",\n  \"#0d9488\",\n  \"#d97706\",\n  \"#e11d48\",\n  \"#a1a1aa\",\n];\n\nconst DASH_COLORS = [\n  \"#6356d1\",\n  \"#e87ba4\",\n  \"#eda100\",\n  \"#1baf7a\",\n  \"#94a3b8\",\n];\n\n// ==========================================\n// TYPES & PROPS\n// ==========================================\nexport interface ChartDataPoint {\n  id?: string;\n  label: string;\n  value: number;\n  /** Own colour for this share in `donut-legend`, `stacked-bar`, `traffic-sources`. */\n  color?: string;\n  /** 2-letter ISO code for country breakdown charts, e.g. \"US\", \"DE\". */\n  code?: string;\n}\n\nexport interface MetricTabPoint {\n  label: string;\n  shortLabel?: string;\n  fullLabel?: string;\n  value: number;\n}\n\nexport interface MetricTabItem {\n  id: string;\n  label: string;\n  icon?: React.ReactNode;\n  currency?: string;\n  change?: number | null;\n  points: MetricTabPoint[];\n}\n\nexport interface ChartBlockProps {\n  data?: ChartDataPoint[];\n  type?: ChartType;\n  color?: ChartColor;\n  /**\n   * Fixed pixel height. Defaults to 300 for the axis charts; the legend and breakdown\n   * charts size to their content unless set.\n   */\n  height?: number;\n  className?: string;\n  valuePrefix?: string;\n  valueSuffix?: string;\n  showGrid?: boolean;\n  showArea?: boolean;\n  /**\n   * Palette for the legend charts, one colour per share in order. A point's\n   * own `color` wins over it. Defaults to the theme accent and four hues.\n   */\n  colors?: string[];\n  /** `donut-legend`: caption under the centre value at rest, e.g. \"top reason\". */\n  centerLabel?: string;\n  /** `donut-legend`: caption under the centre value while a share is hovered. */\n  hoverLabel?: string;\n  /** `traffic-sources`: number of sign-ups to calculate visit-to-sign-up rate. */\n  signUps?: number;\n  /** `visitors-by-country`: whether to display 2-letter ISO country badge. */\n  showCodes?: boolean;\n  /** Optional title for card-based breakdown charts. */\n  title?: string;\n  /** Optional subtitle/caption for card-based breakdown charts. */\n  caption?: string;\n  /** `metrics-overview`: array of metric tabs with series data. */\n  metrics?: MetricTabItem[];\n}\n\n// ==========================================\n// MAIN COMPONENT EXPORT\n// ==========================================\nexport function ChartBlock({\n  data = [],\n  type = \"bar\",\n  color = \"var(--primary)\",\n  height,\n  className,\n  valuePrefix = \"\",\n  valueSuffix = \"\",\n  showGrid = true,\n  showArea = true,\n  colors = CATEGORICAL,\n  centerLabel = \"largest\",\n  hoverLabel = \"share\",\n  signUps,\n  showCodes = true,\n  title,\n  caption,\n  metrics,\n}: ChartBlockProps) {\n  const themeColor = getThemeColor(color);\n  const isSelfSizing =\n    type === \"donut-legend\" ||\n    type === \"stacked-bar\" ||\n    type === \"traffic-sources\" ||\n    type === \"visitors-by-country\" ||\n    type === \"metrics-overview\";\n\n  // Every share gets a colour: its own, else the palette in order, wrapping.\n  const shareColor = (d: ChartDataPoint, i: number) =>\n    getThemeColor(d.color ?? colors[i % colors.length] ?? \"primary\");\n\n  if (type === \"metrics-overview\") {\n    const activeMetrics =\n      metrics ||\n      (data && data.length > 0\n        ? [\n            {\n              id: \"metric-1\",\n              label: title || \"Overview\",\n              points: data.map((d) => ({\n                label: d.label,\n                value: d.value,\n              })),\n            },\n          ]\n        : undefined);\n\n    return (\n      <MetricsOverviewChart\n        metrics={activeMetrics}\n        title={title}\n        compareCaption={caption}\n        className={className}\n      />\n    );\n  }\n\n  if (type === \"traffic-sources\") {\n    return (\n      <TrafficSourcesChart\n        data={data}\n        colors={colors}\n        signUps={signUps}\n        title={title}\n        caption={caption}\n        className={className}\n      />\n    );\n  }\n\n  if (type === \"visitors-by-country\") {\n    return (\n      <VisitorsByCountryChart\n        data={data}\n        colors={colors}\n        showCodes={showCodes}\n        title={title}\n        caption={caption}\n        className={className}\n      />\n    );\n  }\n\n  return (\n    <div\n      className={cn(\"relative w-full\", className)}\n      style={{ height: height ?? (isSelfSizing ? undefined : 300) }}\n    >\n      {/* NATIVE CSS ANIMATIONS */}\n      <style>{`\n        @keyframes drawLine { from { clip-path: inset(-8px 100% -8px -8px); } to { clip-path: inset(-8px -8px -8px -8px); } }\n        @keyframes growUp { from { transform: scaleY(0); opacity: 0; } to { transform: scaleY(1); opacity: 1; } }\n        @keyframes fadeUp { from { transform: translateY(10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n        .animate-draw { animation: drawLine 1.5s cubic-bezier(0.22, 1, 0.36, 1) forwards; }\n        .animate-grow { animation: growUp 1s cubic-bezier(0.22, 1, 0.36, 1) forwards; transform-origin: bottom; }\n        .animate-fade { animation: fadeUp 1s cubic-bezier(0.22, 1, 0.36, 1) forwards; }\n      `}</style>\n\n      {type === \"bar\" && (\n        <BarChart\n          data={data}\n          color={themeColor}\n          prefix={valuePrefix}\n          suffix={valueSuffix}\n          showGrid={showGrid}\n        />\n      )}\n\n      {type === \"line\" && (\n        <SmoothLineChart\n          data={data}\n          color={themeColor}\n          prefix={valuePrefix}\n          suffix={valueSuffix}\n          showGrid={showGrid}\n          showArea={showArea}\n        />\n      )}\n\n      {(type === \"pie\" || type === \"donut\") && (\n        <CircularChart\n          data={data}\n          type={type}\n          color={themeColor}\n          prefix={valuePrefix}\n          suffix={valueSuffix}\n        />\n      )}\n\n      {type === \"donut-legend\" && (\n        <DonutLegendChart\n          data={data}\n          shareColor={shareColor}\n          prefix={valuePrefix}\n          suffix={valueSuffix}\n          centerLabel={centerLabel}\n          hoverLabel={hoverLabel}\n        />\n      )}\n\n      {type === \"stacked-bar\" && (\n        <StackedBarChart\n          data={data}\n          shareColor={shareColor}\n          prefix={valuePrefix}\n          suffix={valueSuffix}\n        />\n      )}\n    </div>\n  );\n}\n\n// ==========================================\n// 1. HTML FLEXBOX BAR CHART (Flawless Scaling)\n// ==========================================\nfunction BarChart({\n  data,\n  color,\n  prefix,\n  suffix,\n  showGrid,\n}: {\n  data: ChartDataPoint[];\n  color: string;\n  prefix: string;\n  suffix: string;\n  showGrid: boolean;\n}) {\n  const maxVal = Math.max(...data.map((d) => d.value), 1);\n  const gridLines = [1, 0.75, 0.5, 0.25, 0];\n\n  return (\n    <div className=\"relative w-full h-full flex flex-col pt-6 pl-12 pr-4 animate-fade\">\n      {/* Background Grid & Y-Axis */}\n      <div className=\"absolute inset-0 top-6 bottom-8 left-12 right-4 flex flex-col justify-between pointer-events-none z-0\">\n        {gridLines.map((ratio, i) => (\n          <div key={i} className=\"flex items-center w-full relative\">\n            <span className=\"absolute -left-3 -translate-x-full text-[10px] font-medium text-muted-foreground\">\n              {prefix}\n              {Math.round(maxVal * ratio)}\n              {suffix}\n            </span>\n            {showGrid && (\n              <div className=\"w-full border-t border-dashed border-border\" />\n            )}\n          </div>\n        ))}\n      </div>\n\n      {/* Bars Container */}\n      <div className=\"relative z-10 flex-1 flex items-end justify-between gap-1 sm:gap-2 px-2 pb-8\">\n        {data.map((d, i) => (\n          <div\n            key={i}\n            className=\"group relative flex flex-col items-center justify-end w-full h-full\"\n          >\n            {/* Tooltip */}\n            <div className=\"absolute -top-12 opacity-0 scale-95 group-hover:opacity-100 group-hover:scale-100 transition-all duration-200 pointer-events-none z-50 flex flex-col items-center\">\n              <div\n                className={cn(inkOn(color), \"px-3 py-1.5 rounded-lg border border-border/20 text-xs font-semibold whitespace-nowrap text-center\")}\n                style={{ backgroundColor: color }}\n              >\n                <div className=\"text-[10px] opacity-80 uppercase tracking-wider font-semibold\">\n                  {d.label}\n                </div>\n                <div>\n                  {prefix}\n                  {d.value}\n                  {suffix}\n                </div>\n              </div>\n              <div className=\"w-2 h-2 rotate-45 -mt-1\" style={{ backgroundColor: color }} />\n            </div>\n\n            {/* Flat-Bottom Bar */}\n            <div\n              className=\"w-full max-w-[48px] rounded-t-md opacity-90 group-hover:opacity-100 transition-opacity animate-grow cursor-pointer\"\n              style={{\n                height: `${(d.value / maxVal) * 100}%`,\n                backgroundColor: color,\n                animationDelay: `${i * 0.04}s`,\n              }}\n            >\n              <div className=\"w-full h-1.5 bg-white/30 rounded-t-md\" />\n            </div>\n\n            {/* X-Axis Label */}\n            <span className=\"absolute -bottom-6 text-[10px] font-medium text-muted-foreground group-hover:text-foreground transition-colors whitespace-nowrap overflow-hidden text-ellipsis max-w-full\">\n              {d.label}\n            </span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n\n// ==========================================\n// 2. SVG SMOOTH CURVE CHART (Line + Area Merge)\n// ==========================================\nfunction SmoothLineChart({\n  data,\n  color,\n  prefix,\n  suffix,\n  showGrid,\n  showArea,\n}: {\n  data: ChartDataPoint[];\n  color: string;\n  prefix: string;\n  suffix: string;\n  showGrid: boolean;\n  showArea: boolean;\n}) {\n  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n  const maxVal = Math.max(...data.map((d) => d.value), 1);\n  // An id that is valid whatever the colour is: `var(--primary)` (the\n  // default) is not a legal fragment for `url(#…)`, which left the area black.\n  const cleanColorId = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n  // Calculate X/Y percentages for the data\n  const points = data.map((d, i) => ({\n    x: (i / Math.max(data.length - 1, 1)) * 100,\n    y: 100 - (d.value / maxVal) * 100,\n    label: d.label,\n    value: d.value,\n  }));\n\n  // Mathematical cubic bezier curve generator for a premium SaaS look\n  const createSmoothPath = (pts: { x: number; y: number }[]) => {\n    const first = pts[0];\n    if (!first) return \"\";\n    let d = `M ${first.x} ${first.y}`;\n    for (let i = 1; i < pts.length; i++) {\n      const prev = pts[i - 1];\n      const curr = pts[i];\n      if (!prev || !curr) continue;\n      const cpX = (prev.x + curr.x) / 2; // Midpoint easing\n      d += ` C ${cpX} ${prev.y}, ${cpX} ${curr.y}, ${curr.x} ${curr.y}`;\n    }\n    return d;\n  };\n\n  const smoothPath = createSmoothPath(points);\n  const areaPath = `${smoothPath} L 100 100 L 0 100 Z`;\n\n  return (\n    <div className=\"relative w-full h-full pt-6 pb-8 pl-12 pr-4 animate-fade\">\n      {/* Background Grid */}\n      <div className=\"absolute inset-0 top-6 bottom-8 left-12 right-4 flex flex-col justify-between pointer-events-none z-0\">\n        {[1, 0.5, 0].map((ratio, i) => (\n          <div key={i} className=\"flex items-center w-full relative h-0\">\n            <span className=\"absolute -left-3 -translate-x-full text-[10px] font-medium text-muted-foreground\">\n              {prefix}\n              {Math.round(maxVal * ratio)}\n              {suffix}\n            </span>\n            {showGrid && (\n              <div className=\"w-full border-t border-dashed border-border\" />\n            )}\n          </div>\n        ))}\n      </div>\n\n      {/* Vector Graphics. The draw-in is a left-to-right clip reveal rather\n          than a stroke-dasharray sweep: with a non-scaling stroke the dash\n          pattern is measured in screen pixels, not pathLength units, so a\n          dashed draw cut the line into pieces whose count changed with width. */}\n      <svg\n        viewBox=\"0 0 100 100\"\n        preserveAspectRatio=\"none\"\n        className=\"absolute inset-0 top-6 bottom-8 left-12 right-4 w-[calc(100%-64px)] h-[calc(100%-56px)] overflow-visible z-10 animate-draw\"\n      >\n        <defs>\n          <linearGradient\n            id={`area-grad-${cleanColorId}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"0\"\n            y2=\"1\"\n          >\n            <stop offset=\"0%\" stopColor={color} stopOpacity=\"0.35\" />\n            <stop offset=\"100%\" stopColor={color} stopOpacity=\"0.0\" />\n          </linearGradient>\n        </defs>\n\n        {/* Optional Area Fill */}\n        {showArea && (\n          <path\n            d={areaPath}\n            fill={`url(#area-grad-${cleanColorId})`}\n            className=\"animate-fade\"\n          />\n        )}\n\n        {/* Animated Line Stroke */}\n        <path\n          d={smoothPath}\n          fill=\"none\"\n          stroke={color}\n          strokeWidth=\"2.5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          style={{ vectorEffect: \"non-scaling-stroke\" }}\n        />\n      </svg>\n\n      {/* HTML Interaction Overlay (Perfectly round dots that never squash) */}\n      <div className=\"absolute inset-0 top-6 bottom-8 left-12 right-4 z-20\">\n        {points.map((p, i) => (\n          <div\n            key={i}\n            className=\"absolute top-0 bottom-0 cursor-crosshair group\"\n            style={{\n              left: `${p.x}%`,\n              width: `${100 / Math.max(points.length - 1, 1)}%`,\n              transform: \"translateX(-50%)\",\n            }}\n            onMouseEnter={() => setHoveredIndex(i)}\n            onMouseLeave={() => setHoveredIndex(null)}\n          >\n            {/* HTML Dot positioned accurately over the SVG path */}\n            <div\n              className={cn(\n                \"absolute w-3 h-3 rounded-full bg-background border-2 transition-all duration-200 pointer-events-none\",\n                hoveredIndex === i\n                  ? \"scale-100 opacity-100\"\n                  : \"scale-50 opacity-0\",\n              )}\n              style={{\n                top: `${p.y}%`,\n                left: \"50%\",\n                transform: \"translate(-50%, -50%)\",\n                borderColor: color,\n              }}\n            >\n              {/* Tooltip */}\n              <div\n                className={cn(\n                  \"absolute -top-10 left-1/2 -translate-x-1/2 flex flex-col items-center transition-all duration-200\",\n                  hoveredIndex === i\n                    ? \"opacity-100 scale-100\"\n                    : \"opacity-0 scale-95\",\n                )}\n              >\n                <div\n                  className={cn(inkOn(color), \"px-3 py-1.5 rounded-lg border border-border/20 text-xs font-semibold whitespace-nowrap text-center\")}\n                  style={{ backgroundColor: color }}\n                >\n                  <div className=\"text-[10px] opacity-80 uppercase tracking-wider font-semibold\">\n                    {p.label}\n                  </div>\n                  <div>\n                    {prefix}\n                    {p.value}\n                    {suffix}\n                  </div>\n                </div>\n                <div className=\"w-2 h-2 rotate-45 -mt-1\" style={{ backgroundColor: color }} />\n              </div>\n            </div>\n\n            {/* X-Axis Label */}\n            <span\n              className={cn(\n                \"absolute -bottom-6 left-1/2 -translate-x-1/2 text-[10px] font-medium whitespace-nowrap transition-colors\",\n                hoveredIndex === i\n                  ? \"text-foreground font-semibold\"\n                  : \"text-muted-foreground\",\n              )}\n            >\n              {p.label}\n            </span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n\n// ==========================================\n// 3. SVG STROKE-DASHARRAY CHART (Pie/Donut)\n// ==========================================\nfunction CircularChart({\n  data,\n  type,\n  color,\n  prefix,\n  suffix,\n}: {\n  data: ChartDataPoint[];\n  type: \"pie\" | \"donut\";\n  color: string;\n  prefix: string;\n  suffix: string;\n}) {\n  const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n  const total = data.reduce((sum, d) => sum + d.value, 0);\n  const radius = type === \"pie\" ? 25 : 40;\n  const strokeWidth = type === \"pie\" ? 50 : 20;\n  const circumference = 2 * Math.PI * radius;\n\n  // Each slice starts where the previous one ended. Precomputed rather than\n  // accumulated in a mutable variable inside map(), so the mapper stays pure.\n  const sliceRatios = data.map((d) => d.value / (total || 1));\n  const startRatios: number[] = [];\n  sliceRatios.reduce((acc, ratio) => {\n    startRatios.push(acc);\n    return acc + ratio;\n  }, 0);\n\n  // Map data to stroke dashes\n  const slices = data.map((d, i) => {\n    const sliceRatio = sliceRatios[i] ?? 0;\n    const startRatio = startRatios[i] ?? 0;\n    const strokeDasharray = `${sliceRatio * circumference} ${circumference}`;\n    const strokeDashoffset = -(startRatio * circumference);\n    const endOffset = (startRatio + sliceRatio) * circumference;\n\n    const midAngle =\n      ((endOffset - (sliceRatio * circumference) / 2) / circumference) *\n        Math.PI *\n        2 -\n      Math.PI / 2;\n    const tooltipRadius = type === \"pie\" ? 30 : 40;\n\n    // Convert polar to cartesian for the tooltip\n    const tx = 50 + tooltipRadius * Math.cos(midAngle);\n    const ty = 50 + tooltipRadius * Math.sin(midAngle);\n\n    return {\n      ...d,\n      strokeDasharray,\n      strokeDashoffset,\n      opacity: 1 - i * 0.15,\n      tx,\n      ty,\n    };\n  });\n  const hovered = hoveredIndex === null ? undefined : slices[hoveredIndex];\n\n  return (\n    <div className=\"relative w-full h-full flex items-center justify-center animate-fade\">\n      {/* 1:1 Aspect Ratio Container forces perfect circles */}\n      <div className=\"relative aspect-square h-full max-h-[300px]\">\n        <svg\n          viewBox=\"0 0 100 100\"\n          className=\"w-full h-full overflow-visible -rotate-90\"\n        >\n          {slices.map((slice, i) => (\n            <circle\n              key={i}\n              cx=\"50\"\n              cy=\"50\"\n              r={radius}\n              fill=\"none\"\n              stroke={color}\n              strokeWidth={strokeWidth}\n              strokeDasharray={slice.strokeDasharray}\n              strokeDashoffset={slice.strokeDashoffset}\n              className={cn(\n                \"transition-all duration-300 cursor-crosshair\",\n                hoveredIndex !== null && hoveredIndex !== i\n                  ? \"opacity-30\"\n                  : \"opacity-100\",\n              )}\n              style={{ opacity: slice.opacity }}\n              onMouseEnter={() => setHoveredIndex(i)}\n              onMouseLeave={() => setHoveredIndex(null)}\n            />\n          ))}\n\n          {/* Inner ring to give Donut a sharp edge */}\n          {type === \"donut\" && (\n            <circle\n              cx=\"50\"\n              cy=\"50\"\n              r={radius - strokeWidth / 2}\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"0.5\"\n              className=\"text-border pointer-events-none\"\n            />\n          )}\n        </svg>\n\n        {/* Tooltip Overlay */}\n        {hovered && (\n          <div\n            className=\"absolute z-50 pointer-events-none animate-in fade-in zoom-in-95 duration-200 flex flex-col items-center\"\n            style={{\n              left: `${hovered.tx}%`,\n              top: `${hovered.ty}%`,\n              transform: \"translate(-50%, -50%)\",\n            }}\n          >\n            <div\n              className={cn(inkOn(color), \"px-3 py-1.5 rounded-lg border border-border/20 text-xs font-semibold whitespace-nowrap text-center\")}\n              style={{ backgroundColor: color }}\n            >\n              <div className=\"text-[10px] opacity-80 uppercase tracking-wider font-semibold\">\n                {hovered.label}\n              </div>\n              <div>\n                {prefix}\n                {hovered.value}\n                {suffix}\n              </div>\n            </div>\n          </div>\n        )}\n\n        {/* Donut Center Total Label */}\n        {type === \"donut\" && (\n          <div className=\"absolute inset-0 flex flex-col items-center justify-center pointer-events-none\">\n            <span className=\"text-xs font-medium text-muted-foreground\">Total</span>\n            <span className=\"text-xl font-semibold text-foreground\">\n              {prefix}\n              {total}\n              {suffix}\n            </span>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\n// ==========================================\n// 4. DONUT + LEGEND (categorical shares, hover either side)\n// ==========================================\nfunction DonutLegendChart({\n  data,\n  shareColor,\n  prefix,\n  suffix,\n  centerLabel,\n  hoverLabel,\n}: {\n  data: ChartDataPoint[];\n  shareColor: (d: ChartDataPoint, i: number) => string;\n  prefix: string;\n  suffix: string;\n  centerLabel: string;\n  hoverLabel: string;\n}) {\n  const [active, setActive] = useState<number | null>(null);\n  const total = data.reduce((sum, d) => sum + d.value, 0) || 1;\n  const R = 38;\n  const C = 2 * Math.PI * R;\n  const GAP = 2; // surface gap between segments, in viewBox units\n\n  // Where each segment starts, precomputed so the mapper stays pure.\n  const starts: number[] = [];\n  data.reduce((acc, d) => {\n    starts.push(acc);\n    return acc + (C * d.value) / total;\n  }, 0);\n\n  // The centre reads the hovered share; at rest, the largest one.\n  const largest = data.reduce((best, d, i) => (d.value > (data[best]?.value ?? -Infinity) ? i : best), 0);\n  const shown = data[active ?? largest];\n\n  return (\n    <div className=\"flex flex-col items-center gap-5 animate-fade sm:flex-row\">\n      <div className=\"relative size-36 shrink-0\">\n        <svg viewBox=\"0 0 100 100\" className=\"size-full -rotate-90\" role=\"img\" aria-label=\"Share of the whole by category\">\n          {data.map((d, i) => {\n            const length = (C * d.value) / total;\n            return (\n              <circle\n                key={d.label}\n                cx=\"50\"\n                cy=\"50\"\n                r={R}\n                fill=\"none\"\n                stroke={shareColor(d, i)}\n                strokeWidth={active === i ? 16 : 14}\n                strokeDasharray={`${Math.max(0, length - GAP)} ${C}`}\n                strokeDashoffset={-(starts[i] ?? 0)}\n                onMouseEnter={() => setActive(i)}\n                onMouseLeave={() => setActive(null)}\n                className={cn(\n                  \"cursor-pointer transition-[stroke-width,opacity] duration-200\",\n                  active !== null && active !== i && \"opacity-40\",\n                )}\n              />\n            );\n          })}\n        </svg>\n        {shown && (\n          <div className=\"absolute inset-0 grid place-items-center text-center\">\n            <div>\n              <p className=\"text-xl font-semibold tabular-nums text-foreground\">\n                {prefix}\n                {shown.value}\n                {suffix}\n              </p>\n              <p className=\"text-[10px] text-muted-foreground\">{active === null ? centerLabel : hoverLabel}</p>\n            </div>\n          </div>\n        )}\n      </div>\n      <ul className=\"min-w-0 flex-1 space-y-2 text-sm\">\n        {data.map((d, i) => (\n          <li\n            key={d.label}\n            onMouseEnter={() => setActive(i)}\n            onMouseLeave={() => setActive(null)}\n            className={cn(\n              \"flex items-start gap-2.5 transition-opacity\",\n              active !== null && active !== i && \"opacity-50\",\n            )}\n          >\n            <span\n              className=\"mt-1.5 size-2.5 shrink-0 rounded-full\"\n              style={{ backgroundColor: shareColor(d, i) }}\n            />\n            <span className=\"min-w-0 flex-1 leading-snug\">{d.label}</span>\n            <span className=\"font-medium tabular-nums\">\n              {prefix}\n              {d.value}\n              {suffix}\n            </span>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\n// ==========================================\n// 5. STACKED BAR (one bar split into shares, legend below)\n// ==========================================\nfunction StackedBarChart({\n  data,\n  shareColor,\n  prefix,\n  suffix,\n}: {\n  data: ChartDataPoint[];\n  shareColor: (d: ChartDataPoint, i: number) => string;\n  prefix: string;\n  suffix: string;\n}) {\n  const total = data.reduce((sum, d) => sum + d.value, 0) || 1;\n  return (\n    <div className=\"animate-fade\">\n      <div className=\"flex h-8 gap-0.5 overflow-hidden rounded-lg\" role=\"img\" aria-label=\"Share of the whole by category\">\n        {data.map((d, i) => (\n          <div\n            key={d.label}\n            style={{ width: `${(d.value / total) * 100}%`, backgroundColor: shareColor(d, i) }}\n            title={`${d.label} ${prefix}${d.value}${suffix}`}\n            className=\"h-full first:rounded-l-lg last:rounded-r-lg\"\n          />\n        ))}\n      </div>\n      <ul className=\"mt-3 flex flex-wrap gap-x-4 gap-y-1.5 text-xs\">\n        {data.map((d, i) => (\n          <li key={d.label} className=\"flex items-center gap-1.5\">\n            <span className=\"size-2 rounded-full\" style={{ backgroundColor: shareColor(d, i) }} />\n            <span className=\"text-muted-foreground\">{d.label}</span>\n            <span className=\"font-medium tabular-nums\">\n              {prefix}\n              {d.value}\n              {suffix}\n            </span>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\n// ==========================================\n// 6. TRAFFIC SOURCES & VISITORS BY COUNTRY (Dashboard Breakdown Visualizations)\n// ==========================================\nconst integerFormat = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 0 });\nconst compactFormat = new Intl.NumberFormat(\"en-US\", {\n  notation: \"compact\",\n  maximumFractionDigits: 1,\n});\n\nconst formatNumber = (value: number) => integerFormat.format(value);\nconst formatCompact = (value: number) =>\n  Math.abs(value) < 10000 ? formatNumber(value) : compactFormat.format(value);\nconst formatShare = (share: number) =>\n  share <= 0 ? \"0%\" : share < 0.01 ? \"<1%\" : `${Math.round(share * 100)}%`;\nconst formatRate = (rate: number) =>\n  rate === 0 ? \"0%\" : `${(rate * 100).toFixed(rate < 0.1 ? 1 : 0)}%`;\nconst plural = (count: number, [one, many]: [string, string]) =>\n  count === 1 ? one : many;\nconst sumValues = (values: number[]) => values.reduce((total, value) => total + value, 0);\n\ninterface BreakdownSlice {\n  key: string;\n  name: string;\n  value: number;\n  share: number;\n  color: string;\n  code?: string;\n}\n\nfunction composeBreakdownShares(\n  shares: ChartDataPoint[],\n  palette: string[] = DASH_COLORS\n): { slices: BreakdownSlice[]; total: number } {\n  const rows = [...shares].filter((s) => s.value > 0).sort((a, b) => b.value - a.value);\n  const total = sumValues(rows.map((r) => r.value));\n  if (!total) return { slices: [], total: 0 };\n  const top = rows.slice(0, rows.length > 4 ? 3 : 4);\n  const rest = rows.slice(top.length);\n  const slices: BreakdownSlice[] = top.map((row, index) => ({\n    key: row.id || row.label,\n    name: row.label,\n    value: row.value,\n    share: row.value / total,\n    color: row.color || palette[index % palette.length] || \"#94a3b8\",\n    code: row.code,\n  }));\n  if (rest.length) {\n    const value = sumValues(rest.map((r) => r.value));\n    slices.push({\n      key: \"__other\",\n      name: `Other (${rest.length})`,\n      value,\n      share: value / total,\n      color: \"#94a3b8\",\n    });\n  }\n  return { slices, total };\n}\n\nfunction allocateGaugeUnits(slices: BreakdownSlice[], units: number): (BreakdownSlice | null)[] {\n  const total = sumValues(slices.map((slice) => slice.value));\n  if (!total) return Array.from({ length: units }, () => null);\n  const exact = slices.map((slice) => (slice.value / total) * units);\n  const counts = exact.map(Math.floor);\n  let left = units - sumValues(counts);\n  exact\n    .map((value, index) => ({ index, rest: value - Math.floor(value) }))\n    .sort((a, b) => b.rest - a.rest)\n    .forEach(({ index }) => {\n      if (left > 0) {\n        counts[index] = (counts[index] ?? 0) + 1;\n        left -= 1;\n      }\n    });\n  return slices.flatMap((slice, index) => Array.from({ length: counts[index] ?? 0 }, () => slice));\n}\n\nconst GAUGE_TICKS = 44;\n\nfunction TickGauge({\n  slices,\n  focus,\n  onFocus,\n  label,\n  children,\n}: {\n  slices: BreakdownSlice[];\n  focus: string | null;\n  onFocus: (key: string | null) => void;\n  label: string;\n  children: React.ReactNode;\n}) {\n  const units = allocateGaugeUnits(slices, GAUGE_TICKS);\n  return (\n    <div className=\"relative mx-auto w-full max-w-[20rem] sm:max-w-[24rem] md:max-w-[26rem]\">\n      <svg viewBox=\"0 0 300 156\" className=\"block w-full\" role=\"img\" aria-label={label}>\n        {units.map((unit, index) => {\n          const angle = Math.PI - (index / (GAUGE_TICKS - 1)) * Math.PI;\n          const cos = Math.cos(angle);\n          const sin = Math.sin(angle);\n          return (\n            <line\n              key={index}\n              x1={150 + 104 * cos}\n              y1={150 - 104 * sin}\n              x2={150 + 136 * cos}\n              y2={150 - 136 * sin}\n              stroke={unit ? unit.color : \"currentColor\"}\n              className={cn(\n                unit ? \"cursor-pointer\" : \"text-muted-foreground/20\",\n                \"transition-all duration-150\"\n              )}\n              strokeWidth={focus && unit && unit.key === focus ? 6 : 5}\n              strokeLinecap=\"round\"\n              opacity={focus && unit && unit.key !== focus ? 0.22 : 1}\n              style={{ transition: \"opacity 150ms ease-out, stroke-width 150ms ease-out\" }}\n              onPointerEnter={unit ? () => onFocus(unit.key) : undefined}\n              onPointerLeave={unit ? () => onFocus(null) : undefined}\n            />\n          );\n        })}\n      </svg>\n      <div className=\"pointer-events-none absolute inset-x-0 bottom-0 flex flex-col items-center\">\n        {children}\n      </div>\n    </div>\n  );\n}\n\nfunction Barcode({\n  slices,\n  focus,\n  onFocus,\n}: {\n  slices: BreakdownSlice[];\n  focus: string | null;\n  onFocus: (key: string | null) => void;\n}) {\n  const units = allocateGaugeUnits(slices, 48);\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"mt-5 flex h-12 items-stretch gap-[3px]\"\n      onPointerLeave={() => onFocus(null)}\n    >\n      {units.map((unit, index) => (\n        <span\n          key={index}\n          className={cn(\n            \"min-w-0 flex-1 rounded-full transition-opacity duration-150 cursor-pointer\",\n            index > 0 && units[index - 1]?.key !== unit?.key && \"ml-1.5\",\n            focus && focus !== unit?.key && \"opacity-25\",\n            !unit && \"bg-muted\"\n          )}\n          style={{ backgroundColor: unit?.color }}\n          onPointerEnter={() => onFocus(unit?.key ?? null)}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction BreakdownList({\n  slices,\n  focus,\n  onFocus,\n  showCodes = false,\n  className,\n}: {\n  slices: BreakdownSlice[];\n  focus: string | null;\n  onFocus: (key: string | null) => void;\n  showCodes?: boolean;\n  className?: string;\n}) {\n  return (\n    <ul className={cn(\"space-y-1\", className)} onPointerLeave={() => onFocus(null)}>\n      {slices.map((slice) => (\n        <li\n          key={slice.key}\n          onPointerEnter={() => onFocus(slice.key)}\n          onClick={() => onFocus(focus === slice.key ? null : slice.key)}\n          className={cn(\n            \"flex items-center gap-3 rounded-xl px-3 py-2.5 transition-all duration-150 cursor-pointer select-none\",\n            focus === slice.key\n              ? \"bg-muted/80 translate-x-1\"\n              : \"hover:bg-muted/50 hover:translate-x-0.5\"\n          )}\n        >\n          <span\n            aria-hidden=\"true\"\n            className=\"h-4 w-1.5 shrink-0 rounded-full transition-transform duration-150\"\n            style={{ backgroundColor: slice.color }}\n          />\n          {showCodes && (\n            <span\n              aria-hidden=\"true\"\n              className=\"grid h-4 w-6 shrink-0 place-items-center rounded-[4px] bg-muted text-[9px] font-semibold tracking-wide text-muted-foreground\"\n            >\n              {slice.code ?? \"··\"}\n            </span>\n          )}\n          <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-foreground\">{slice.name}</span>\n          <span className=\"text-sm font-medium tabular-nums text-foreground\">{formatNumber(slice.value)}</span>\n          <span className=\"w-14 shrink-0 rounded-full bg-muted/80 py-0.5 text-center text-xs tabular-nums text-muted-foreground font-semibold\">\n            {formatShare(slice.share)}\n          </span>\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nfunction ConversionRate({ signUps, visits }: { signUps: number; visits: number }) {\n  const rate = signUps / (visits || 1);\n  const valid = rate <= 1;\n  return (\n    <div className=\"mt-auto pt-5\">\n      <div className=\"border-t border-border pt-4\">\n        <div className=\"flex items-center justify-between gap-3\">\n          <div className=\"flex items-center gap-1 text-[13px] text-muted-foreground\">\n            Visit to sign-up rate\n            <Popover>\n              <PopoverTrigger\n                aria-label=\"How this rate is calculated\"\n                className=\"grid size-6 cursor-pointer place-items-center rounded-full text-muted-foreground transition-colors hover:text-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n              >\n                <Info className=\"size-3.5\" aria-hidden=\"true\" />\n              </PopoverTrigger>\n              <PopoverContent\n                side=\"top\"\n                className=\"w-64 rounded-lg border-0 bg-foreground p-3 text-xs leading-5 text-background lg:p-3 lg:text-xs\"\n              >\n                New sign-ups divided by the visits in the same period.\n              </PopoverContent>\n            </Popover>\n          </div>\n          <p className=\"text-sm font-medium tabular-nums text-foreground\">{valid ? formatRate(rate) : \"—\"}</p>\n        </div>\n        <div aria-hidden=\"true\" className=\"mt-2 h-1.5 overflow-hidden rounded-full bg-muted\">\n          <div className=\"h-full rounded-full bg-primary\" style={{ width: `${valid ? rate * 100 : 0}%` }} />\n        </div>\n        <p className=\"mt-2 text-xs text-muted-foreground\">\n          {valid\n            ? `${formatNumber(signUps)} ${plural(signUps, [\"sign-up\", \"sign-ups\"])} from ${formatNumber(visits)} ${plural(visits, [\"visit\", \"visits\"])}`\n            : `${formatNumber(signUps)} sign-ups outnumber the ${formatNumber(visits)} tracked visits.`}\n        </p>\n      </div>\n    </div>\n  );\n}\n\nexport interface TrafficSourcesChartProps {\n  data?: ChartDataPoint[];\n  title?: string;\n  caption?: string;\n  signUps?: number;\n  showConversionRate?: boolean;\n  className?: string;\n  colors?: string[];\n  unitLabel?: string;\n  pluralUnitLabel?: string;\n  href?: string;\n}\n\nexport function TrafficSourcesChart({\n  data = [\n    { label: \"Direct\", value: 3420 },\n    { label: \"Google Search\", value: 2180 },\n    { label: \"GitHub\", value: 1450 },\n    { label: \"Twitter / X\", value: 890 },\n    { label: \"LinkedIn\", value: 430 },\n  ],\n  title = \"Traffic sources\",\n  caption = \"Today\",\n  signUps = 412,\n  showConversionRate = true,\n  className,\n  colors = DASH_COLORS,\n  unitLabel = \"visit\",\n  pluralUnitLabel = \"visits\",\n  href,\n}: TrafficSourcesChartProps) {\n  const id = useId();\n  const [focus, setFocus] = useState<string | null>(null);\n  const { slices, total } = useMemo(() => composeBreakdownShares(data, colors), [data, colors]);\n  const gaugeLabel = total\n    ? `Visits by source: ${slices.map((slice) => `${slice.name} ${formatShare(slice.share)}`).join(\", \")}`\n    : `No visits yet`;\n\n  return (\n    <div\n      aria-labelledby={`${id}-title`}\n      className={cn(\n        \"flex flex-col rounded-3xl border border-border/70 bg-card p-5 sm:p-6 text-card-foreground\",\n        className\n      )}\n    >\n      <div className=\"flex items-start justify-between gap-4\">\n        <div className=\"min-w-0\">\n          <h3 id={`${id}-title`} className=\"text-base font-semibold text-foreground\">\n            {title}\n          </h3>\n          {caption && <p className=\"text-[13px] text-muted-foreground\">{caption}</p>}\n        </div>\n        {href && (\n          <a\n            href={href}\n            aria-label=\"Open visitor analytics\"\n            className=\"grid size-8 shrink-0 place-items-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground border border-border/70\"\n          >\n            <ArrowUpRight className=\"size-4\" aria-hidden=\"true\" />\n          </a>\n        )}\n      </div>\n\n      <div className=\"mt-5 flex flex-1 flex-col\">\n        <TickGauge slices={slices} focus={focus} onFocus={setFocus} label={gaugeLabel}>\n          <span className=\"text-[2rem] font-light leading-none tracking-[-0.02em] text-foreground\">\n            {formatCompact(total)}\n          </span>\n          <span className=\"mt-1.5 text-xs text-muted-foreground\">{plural(total, [unitLabel, pluralUnitLabel])}</span>\n        </TickGauge>\n\n        {total ? (\n          <>\n            <BreakdownList slices={slices} focus={focus} onFocus={setFocus} className=\"mt-5\" />\n            {showConversionRate && signUps !== undefined && (\n              <ConversionRate signUps={signUps} visits={total} />\n            )}\n          </>\n        ) : (\n          <p className=\"mt-5 text-center text-[13px] text-muted-foreground\">\n            No visits recorded yet.\n          </p>\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport interface VisitorsByCountryChartProps {\n  data?: ChartDataPoint[];\n  title?: string;\n  caption?: string;\n  className?: string;\n  colors?: string[];\n  showCodes?: boolean;\n  href?: string;\n}\n\nexport function VisitorsByCountryChart({\n  data = [\n    { label: \"United States\", value: 4250, code: \"US\" },\n    { label: \"Germany\", value: 1820, code: \"DE\" },\n    { label: \"United Kingdom\", value: 1340, code: \"GB\" },\n    { label: \"India\", value: 980, code: \"IN\" },\n    { label: \"Canada\", value: 650, code: \"CA\" },\n    { label: \"France\", value: 420, code: \"FR\" },\n  ],\n  title = \"Visitors by country\",\n  caption = \"Today\",\n  className,\n  colors = DASH_COLORS,\n  showCodes = true,\n  href,\n}: VisitorsByCountryChartProps) {\n  const id = useId();\n  const [focus, setFocus] = useState<string | null>(null);\n  const { slices, total } = useMemo(() => composeBreakdownShares(data, colors), [data, colors]);\n  const countryCount = data.filter((item) => item.value > 0).length;\n  const leader = slices[0];\n\n  return (\n    <div\n      aria-labelledby={`${id}-title`}\n      className={cn(\n        \"flex flex-col rounded-3xl border border-border/70 bg-card p-5 sm:p-6 text-card-foreground\",\n        className\n      )}\n    >\n      <div className=\"flex items-start justify-between gap-4\">\n        <div className=\"min-w-0\">\n          <h3 id={`${id}-title`} className=\"text-base font-semibold text-foreground\">\n            {title}\n          </h3>\n          {caption && <p className=\"text-[13px] text-muted-foreground\">{caption}</p>}\n        </div>\n        {href && (\n          <a\n            href={href}\n            aria-label=\"Open country analytics\"\n            className=\"grid size-8 shrink-0 place-items-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground border border-border\"\n          >\n            <ArrowUpRight className=\"size-4\" aria-hidden=\"true\" />\n          </a>\n        )}\n      </div>\n\n      {!total || !leader ? (\n        <p className=\"mt-5 text-center text-[13px] text-muted-foreground py-8\">\n          Countries appear here once visitors arrive.\n        </p>\n      ) : (\n        <>\n          <div className=\"mt-5\">\n            <p className=\"flex items-baseline gap-2\">\n              <span className=\"text-[2rem] font-light leading-none tracking-[-0.02em] text-foreground\">\n                {formatNumber(countryCount)}\n              </span>\n              <span className=\"text-sm text-muted-foreground\">{plural(countryCount, [\"country\", \"countries\"])}</span>\n            </p>\n            <p className=\"mt-2 text-[13px] leading-5 text-muted-foreground\">\n              {leader.name} accounts for {formatShare(leader.share)} of visits.\n            </p>\n          </div>\n          <Barcode slices={slices} focus={focus} onFocus={setFocus} />\n          <BreakdownList slices={slices} focus={focus} onFocus={setFocus} showCodes={showCodes} className=\"mt-4\" />\n        </>\n      )}\n    </div>\n  );\n}\n\n// ==========================================\n// 7. METRICS OVERVIEW CHART (Multi-Tab Columns & Trend Spline)\n// ==========================================\nfunction DeltaBadge({ change, className }: { change: number; className?: string }) {\n  const rounded = Math.abs(change) < 0.05 ? 0 : change;\n  const direction = rounded > 0 ? \"up\" : rounded < 0 ? \"down\" : \"flat\";\n  const Icon = direction === \"up\" ? ArrowUpRight : direction === \"down\" ? ArrowDownRight : Minus;\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center gap-0.5 rounded-full py-0.5 pl-1 pr-2 text-xs font-medium tabular-nums\",\n        direction === \"up\" && \"bg-emerald-500/10 text-emerald-600 dark:text-emerald-400\",\n        direction === \"down\" && \"bg-rose-500/10 text-rose-600 dark:text-rose-400\",\n        direction === \"flat\" && \"bg-muted text-muted-foreground\",\n        className,\n      )}\n    >\n      <Icon className=\"size-3.5\" aria-hidden=\"true\" />\n      <span className=\"sr-only\">\n        {direction === \"up\" ? \"Up \" : direction === \"down\" ? \"Down \" : \"No change, \"}\n      </span>\n      {formatChange(rounded)}\n    </span>\n  );\n}\n\nconst formatMoney = (value: number, currency: string, compact = false) =>\n  new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency,\n    notation: compact && Math.abs(value) >= 10000 ? \"compact\" : \"standard\",\n    maximumFractionDigits: compact || Math.abs(value) >= 1000 ? 0 : 2,\n  }).format(value);\n\nconst formatChange = (change: number) => {\n  const size = Math.abs(change);\n  const digits = size < 10 ? size.toFixed(1) : String(Math.round(size));\n  return `${change > 0 ? \"+\" : change < 0 ? \"−\" : \"\"}${digits}%`;\n};\n\nconst currencySymbol = (currency: string) =>\n  new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency })\n    .formatToParts(0)\n    .find((part) => part.type === \"currency\")?.value ?? \"\";\n\nfunction niceCeiling(value: number) {\n  if (value <= 0) return 1;\n  const power = 10 ** Math.floor(Math.log10(value));\n  const fraction = value / power;\n  const nice = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 2.5 ? 2.5 : fraction <= 5 ? 5 : 10;\n  return nice * power;\n}\n\nfunction axisTicks(ceiling: number, money: boolean, empty: boolean) {\n  if (empty) return [0];\n  const half = ceiling / 2;\n  return !money && !Number.isInteger(half) ? [0, ceiling] : [0, half, ceiling];\n}\n\ninterface ResolvedOverviewMetric {\n  metric: MetricTabItem;\n  points: MetricTabPoint[];\n  current: number;\n  change: number | null;\n  total: number;\n  average: number;\n  format: (value: number) => string;\n  formatShort: (value: number) => string;\n}\n\nfunction ColumnsChart({ entry }: { entry: ResolvedOverviewMetric }) {\n  const { points } = entry;\n  const [inspected, setInspected] = useState<number | null>(null);\n  const lastIndex = points.length - 1;\n  const ceiling = niceCeiling(Math.max(0, ...points.map((point) => point.value)));\n  const empty = entry.total === 0;\n  const ticks = axisTicks(ceiling, Boolean(entry.metric.currency), empty);\n  const shown = empty ? inspected : (inspected ?? lastIndex);\n\n  const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    const from = inspected ?? lastIndex;\n    const moves: Record<string, number> = {\n      ArrowLeft: Math.max(0, from - 1),\n      ArrowRight: Math.min(lastIndex, from + 1),\n      Home: 0,\n      End: lastIndex,\n    };\n    const move = moves[event.key];\n    if (move !== undefined) {\n      event.preventDefault();\n      setInspected(move);\n    } else if (event.key === \"Escape\") {\n      setInspected(null);\n    }\n  };\n\n  return (\n    <div>\n      <div\n        role=\"group\"\n        aria-roledescription=\"chart\"\n        aria-label={`${entry.metric.label}. Use the arrow keys to read each value.`}\n        tabIndex={0}\n        onKeyDown={onKeyDown}\n        onBlur={() => setInspected(null)}\n        onPointerLeave={(event) => {\n          if (event.pointerType !== \"touch\") setInspected(null);\n        }}\n        className=\"relative rounded-2xl outline-none focus-visible:ring-2 focus-visible:ring-primary/60\"\n      >\n        <div className=\"h-[16.5rem] pt-10\">\n          <div className=\"relative h-full\">\n            <div aria-hidden=\"true\" className=\"absolute inset-y-0 left-0 w-9\">\n              {ticks.map((tick) => (\n                <span\n                  key={tick}\n                  className=\"absolute right-0 translate-y-1/2 text-[11px] leading-none tabular-nums text-muted-foreground font-mono\"\n                  style={{ bottom: `${(tick / ceiling) * 100}%` }}\n                >\n                  {entry.formatShort(tick)}\n                </span>\n              ))}\n            </div>\n            <div className=\"absolute inset-y-0 left-12 right-0\">\n              {entry.average > 0 && !empty && (\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute inset-x-0 border-t border-dashed border-muted-foreground/40 pointer-events-none\"\n                  style={{ bottom: `${(entry.average / ceiling) * 100}%` }}\n                />\n              )}\n              <div className=\"absolute inset-0 flex items-end gap-1 sm:gap-2 md:gap-3\">\n                {points.map((point, index) => {\n                  const percent = (point.value / ceiling) * 100;\n                  const isCurrent = index === lastIndex;\n                  const isInspected = index === inspected;\n                  return (\n                    <div\n                      key={`${point.label}-${index}`}\n                      className=\"relative flex h-full min-w-0 flex-1 justify-center cursor-pointer\"\n                      onPointerEnter={() => setInspected(index)}\n                      onPointerDown={() => setInspected(index)}\n                    >\n                      <div className=\"relative h-full w-full max-w-7 rounded-full bg-muted/60 transition-colors\">\n                        {point.value > 0 && (\n                          <div\n                            className={cn(\n                              \"absolute inset-x-0 bottom-0 rounded-full transition-all duration-200\",\n                              isCurrent\n                                ? \"bg-primary\"\n                                : isInspected\n                                  ? \"bg-primary/70\"\n                                  : \"bg-primary/25 hover:bg-primary/40\",\n                            )}\n                            style={{ height: `max(${percent}%, 0.625rem)` }}\n                          />\n                        )}\n                        {index === shown && (\n                          <div\n                            className={cn(\n                              \"pointer-events-none absolute z-20 whitespace-nowrap\",\n                              index === 0 ? \"left-0\" : index === lastIndex ? \"right-0\" : \"left-1/2 -translate-x-1/2\",\n                            )}\n                            style={{ bottom: `calc(max(${percent}%, 0.625rem) + 0.5rem)` }}\n                          >\n                            {isInspected ? (\n                              <div className=\"rounded-xl bg-card border border-border px-3 py-1.5 text-card-foreground\">\n                                <p className=\"text-sm font-semibold leading-5 text-foreground\">{entry.format(point.value)}</p>\n                                <p className=\"text-[11px] leading-4 text-muted-foreground\">{point.fullLabel ?? point.label}</p>\n                              </div>\n                            ) : (\n                              <span className=\"block rounded-full bg-primary px-2.5 py-1 text-xs font-semibold leading-none text-primary-foreground\">\n                                {entry.formatShort(point.value)}\n                              </span>\n                            )}\n                          </div>\n                        )}\n                      </div>\n                    </div>\n                  );\n                })}\n              </div>\n            </div>\n          </div>\n        </div>\n        <div aria-hidden=\"true\" className=\"ml-12 mt-3 flex gap-1 sm:gap-2 md:gap-3\">\n          {points.map((point, index) => (\n            <span\n              key={`${point.label}-${index}`}\n              className={cn(\n                \"min-w-0 flex-1 truncate text-center text-xs\",\n                index === lastIndex ? \"font-semibold text-foreground\" : \"text-muted-foreground\",\n              )}\n            >\n              <span className=\"sm:hidden\">{point.shortLabel ?? point.label}</span>\n              <span className=\"hidden sm:inline\">{point.label}</span>\n            </span>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport const defaultOverviewMetrics: MetricTabItem[] = [\n  {\n    id: \"users\",\n    label: \"New users\",\n    icon: <UserPlus className=\"size-4\" />,\n    change: 9.1,\n    points: [\n      { label: \"Jan\", shortLabel: \"J\", fullLabel: \"January\", value: 850 },\n      { label: \"Feb\", shortLabel: \"F\", fullLabel: \"February\", value: 940 },\n      { label: \"Mar\", shortLabel: \"M\", fullLabel: \"March\", value: 1050 },\n      { label: \"Apr\", shortLabel: \"A\", fullLabel: \"April\", value: 1000 },\n      { label: \"May\", shortLabel: \"M\", fullLabel: \"May\", value: 1120 },\n      { label: \"Jun\", shortLabel: \"J\", fullLabel: \"June\", value: 1200 },\n      { label: \"Jul\", shortLabel: \"J\", fullLabel: \"July\", value: 1180 },\n      { label: \"Aug\", shortLabel: \"A\", fullLabel: \"August\", value: 1280 },\n      { label: \"Sep\", shortLabel: \"S\", fullLabel: \"September\", value: 1094 },\n    ],\n  },\n  {\n    id: \"posts\",\n    label: \"Posts\",\n    icon: <PenLine className=\"size-4\" />,\n    change: 5.6,\n    points: [\n      { label: \"Jan\", shortLabel: \"J\", fullLabel: \"January\", value: 110 },\n      { label: \"Feb\", shortLabel: \"F\", fullLabel: \"February\", value: 125 },\n      { label: \"Mar\", shortLabel: \"M\", fullLabel: \"March\", value: 130 },\n      { label: \"Apr\", shortLabel: \"A\", fullLabel: \"April\", value: 120 },\n      { label: \"May\", shortLabel: \"M\", fullLabel: \"May\", value: 140 },\n      { label: \"Jun\", shortLabel: \"J\", fullLabel: \"June\", value: 150 },\n      { label: \"Jul\", shortLabel: \"J\", fullLabel: \"July\", value: 145 },\n      { label: \"Aug\", shortLabel: \"A\", fullLabel: \"August\", value: 160 },\n      { label: \"Sep\", shortLabel: \"S\", fullLabel: \"September\", value: 149 },\n    ],\n  },\n  {\n    id: \"messages\",\n    label: \"Messages\",\n    icon: <Mail className=\"size-4\" />,\n    change: 4.4,\n    points: [\n      { label: \"Jan\", shortLabel: \"J\", fullLabel: \"January\", value: 420 },\n      { label: \"Feb\", shortLabel: \"F\", fullLabel: \"February\", value: 460 },\n      { label: \"Mar\", shortLabel: \"M\", fullLabel: \"March\", value: 490 },\n      { label: \"Apr\", shortLabel: \"A\", fullLabel: \"April\", value: 470 },\n      { label: \"May\", shortLabel: \"M\", fullLabel: \"May\", value: 520 },\n      { label: \"Jun\", shortLabel: \"J\", fullLabel: \"June\", value: 580 },\n      { label: \"Jul\", shortLabel: \"J\", fullLabel: \"July\", value: 550 },\n      { label: \"Aug\", shortLabel: \"A\", fullLabel: \"August\", value: 610 },\n      { label: \"Sep\", shortLabel: \"S\", fullLabel: \"September\", value: 571 },\n    ],\n  },\n  {\n    id: \"revenue\",\n    label: \"Revenue\",\n    icon: <Wallet className=\"size-4\" />,\n    currency: \"USD\",\n    change: 7.7,\n    points: [\n      { label: \"Jan\", shortLabel: \"J\", fullLabel: \"January\", value: 52000 },\n      { label: \"Feb\", shortLabel: \"F\", fullLabel: \"February\", value: 58000 },\n      { label: \"Mar\", shortLabel: \"M\", fullLabel: \"March\", value: 64000 },\n      { label: \"Apr\", shortLabel: \"A\", fullLabel: \"April\", value: 61000 },\n      { label: \"May\", shortLabel: \"M\", fullLabel: \"May\", value: 69000 },\n      { label: \"Jun\", shortLabel: \"J\", fullLabel: \"June\", value: 74000 },\n      { label: \"Jul\", shortLabel: \"J\", fullLabel: \"July\", value: 71000 },\n      { label: \"Aug\", shortLabel: \"A\", fullLabel: \"August\", value: 82000 },\n      { label: \"Sep\", shortLabel: \"S\", fullLabel: \"September\", value: 77400 },\n    ],\n  },\n];\n\nexport interface MetricsOverviewChartProps {\n  metrics?: MetricTabItem[];\n  title?: string;\n  compareCaption?: string;\n  periodLabel?: string;\n  className?: string;\n  defaultMetricId?: string;\n}\n\nexport function MetricsOverviewChart({\n  metrics = defaultOverviewMetrics,\n  title = \"This year by month\",\n  compareCaption = \"vs last month\",\n  periodLabel = \"a month\",\n  className,\n  defaultMetricId,\n}: MetricsOverviewChartProps) {\n  const [activeId, setActiveId] = useState(defaultMetricId || metrics[0]?.id || \"\");\n  const [form, setForm] = useState<\"columns\" | \"line\">(\"columns\");\n\n  const resolved: ResolvedOverviewMetric[] = useMemo(\n    () =>\n      metrics.map((metric) => {\n        const points = metric.points;\n        const total = sumValues(points.map((p) => p.value));\n        const currency = metric.currency;\n        return {\n          metric,\n          points,\n          current: points[points.length - 1]?.value ?? 0,\n          change: metric.change ?? null,\n          total,\n          average: points.length ? total / points.length : 0,\n          format: currency ? (value: number) => formatMoney(value, currency) : formatNumber,\n          formatShort: currency ? (value: number) => formatMoney(value, currency, true) : formatNumber,\n        };\n      }),\n    [metrics],\n  );\n\n  const active = resolved.find((entry) => entry.metric.id === activeId) ?? resolved[0];\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-col rounded-3xl border border-border/70 bg-card text-card-foreground overflow-hidden\",\n        className\n      )}\n    >\n      {/* Metric Tabs */}\n      <div\n        role=\"tablist\"\n        aria-label=\"Metrics\"\n        className=\"flex gap-px border-b border-border bg-border/40\"\n      >\n        {resolved.map((entry) => {\n          const selected = entry.metric.id === active?.metric.id;\n          return (\n            <button\n              key={entry.metric.id}\n              type=\"button\"\n              role=\"tab\"\n              aria-selected={selected}\n              tabIndex={selected ? 0 : -1}\n              onClick={() => setActiveId(entry.metric.id)}\n              className={cn(\n                \"group relative flex min-w-0 flex-1 basis-0 flex-col items-start gap-1 bg-card px-2.5 py-3 text-left transition-all duration-200 hover:bg-muted/40 cursor-pointer sm:flex-auto sm:gap-1.5 sm:px-4 sm:py-3.5 select-none\",\n                \"outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary/60\",\n              )}\n            >\n              <span\n                aria-hidden=\"true\"\n                className={cn(\n                  \"absolute inset-x-0 bottom-0 h-[3px] origin-center bg-primary transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]\",\n                  selected ? \"scale-x-100\" : \"scale-x-0\",\n                )}\n              />\n              <span\n                className={cn(\n                  \"flex max-w-full items-center gap-2 text-xs leading-4 transition-colors sm:text-[13px] sm:leading-5\",\n                  selected ? \"text-foreground font-medium\" : \"text-muted-foreground\",\n                )}\n              >\n                {entry.metric.icon && (\n                  <span aria-hidden=\"true\" className=\"hidden shrink-0 sm:block [&_svg]:size-4\">\n                    {entry.metric.icon}\n                  </span>\n                )}\n                <span className=\"truncate\">{entry.metric.label}</span>\n              </span>\n              <span className=\"flex max-w-full flex-col items-start gap-1 sm:flex-row sm:flex-wrap sm:items-center sm:gap-x-1.5\">\n                <span className=\"text-[1.0625rem] font-normal leading-6 tracking-[-0.02em] tabular-nums text-foreground sm:text-[1.3125rem] sm:leading-7\">\n                  {entry.formatShort(entry.current)}\n                </span>\n                {entry.change !== null && (\n                  <DeltaBadge change={entry.change} className=\"text-[11px] sm:text-xs\" />\n                )}\n              </span>\n            </button>\n          );\n        })}\n      </div>\n\n      {active && (\n        <div className=\"px-5 pb-5 pt-4 sm:px-6 sm:pb-6\">\n          <figure className=\"m-0\">\n            <div className=\"flex flex-wrap items-center justify-between gap-x-4 gap-y-3\">\n              <figcaption className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-[13px] leading-5\">\n                <span className=\"font-semibold text-foreground\">{title}</span>\n                {active.change !== null && compareCaption && (\n                  <>\n                    <span aria-hidden=\"true\" className=\"h-3.5 w-px bg-muted-foreground/30\" />\n                    <span className=\"text-muted-foreground\">{compareCaption}</span>\n                  </>\n                )}\n              </figcaption>\n              <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2\">\n                {active.points.length > 0 && (\n                  <dl className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-[13px] leading-5\">\n                    <div className=\"flex items-baseline gap-1.5\">\n                      <dt className=\"text-muted-foreground\">Total</dt>\n                      <dd className=\"font-semibold tabular-nums text-foreground\">{active.formatShort(active.total)}</dd>\n                    </div>\n                    <div className=\"flex items-baseline gap-1.5\">\n                      <dt className=\"flex items-center gap-1.5 self-center text-muted-foreground\">\n                        <span aria-hidden=\"true\" className=\"w-3 border-t border-dashed border-current\" />\n                        <span aria-hidden=\"true\">Avg</span>\n                        <span className=\"sr-only\">Average</span>\n                      </dt>\n                      <dd className=\"font-semibold tabular-nums text-foreground\">\n                        {active.formatShort(active.average)}{\" \"}\n                        <span className=\"font-normal text-muted-foreground\">{periodLabel}</span>\n                      </dd>\n                    </div>\n                  </dl>\n                )}\n                <ToggleGroup\n                  type=\"single\"\n                  variant=\"segmented\"\n                  size=\"sm\"\n                  value={form}\n                  onValueChange={(next) => {\n                    if (next === \"columns\" || next === \"line\") setForm(next);\n                  }}\n                  aria-label=\"Chart style\"\n                >\n                  <ToggleGroupItem value=\"columns\" aria-label=\"Columns\" title=\"Columns\" className=\"w-8 px-0\">\n                    <ChartColumnBig className=\"size-4\" aria-hidden=\"true\" />\n                  </ToggleGroupItem>\n                  <ToggleGroupItem value=\"line\" aria-label=\"Trend line\" title=\"Trend line\" className=\"w-8 px-0\">\n                    <ChartSpline className=\"size-4\" aria-hidden=\"true\" />\n                  </ToggleGroupItem>\n                </ToggleGroup>\n              </div>\n            </div>\n\n            <div className=\"mt-2\">\n              {form === \"line\" ? (\n                <div className=\"h-[18rem] pt-4\">\n                  <ChartBlock\n                    type=\"line\"\n                    data={active.points.map((point) => ({ label: point.label, value: point.value }))}\n                    color=\"var(--primary)\"\n                    height={270}\n                    valuePrefix={active.metric.currency ? currencySymbol(active.metric.currency) : \"\"}\n                  />\n                </div>\n              ) : (\n                <ColumnsChart entry={active} />\n              )}\n            </div>\n          </figure>\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/ChartBlock.tsx"
    }
  ],
  "docs": "Named exports: ChartBlock. `data` takes { label, value, color? }; switch presentation with `type` (bar | line | pie | donut | donut-legend | stacked-bar) and set `height` in pixels for the axis charts. The two legend types colour each share from `colors` (theme accent first) and take `centerLabel` / `hoverLabel`. Hand-drawn SVG with no chart library, so bundle cost is nil — but there are no axes or scales beyond what is drawn. Use a real charting library for analytical work.",
  "categories": [
    "data-display"
  ],
  "type": "registry:component"
}
