{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart-card",
  "title": "图表卡片",
  "description": "图表卡片布局（标题、指标行、图例、明细表）加三个图表：刻度式环形仪表、多序列折线 / 柱 / 面积图（支持堆叠与正负对比）、横向排名条。基于 shadcn chart，颜色全部取自主题变量。",
  "dependencies": [
    "recharts",
    "motion"
  ],
  "registryDependencies": [
    "chart"
  ],
  "files": [
    {
      "path": "src/registry/ui/chart-card/chart-card.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  conventionClass,\n  toneColor,\n  type DataConvention,\n  type DataTone,\n} from \"@/lib/data-display\"\n\nimport { ChartGauge, type ChartGaugeProps } from \"./chart-gauge\"\nimport {\n  ChartLegendGrid,\n  ChartLegendList,\n  type ChartLegendGridProps,\n  type ChartLegendItem,\n  type ChartLegendListProps,\n} from \"./chart-legend\"\nimport {\n  ChartRanking,\n  type ChartRankingItem,\n  type ChartRankingProps,\n} from \"./chart-ranking\"\nimport {\n  ChartSeries,\n  type ChartSeriesItem,\n  type ChartSeriesProps,\n} from \"./chart-series\"\n\ntype ChartCardProps = React.ComponentProps<\"div\"> & {\n  /** 涨跌配色约定，里面的图表与指标共用；不设置时沿用外层 */\n  convention?: DataConvention\n}\n\n/** 图表卡片的外壳：细边框、无阴影；用下面的 Header / Content / Footer 分区，分区之间用细线隔开。 */\nfunction ChartCard({ className, convention, ...props }: ChartCardProps) {\n  return (\n    <div\n      data-slot=\"chart-card\"\n      data-convention={convention}\n      className={cn(\n        \"flex min-w-0 flex-col overflow-hidden rounded-lg border border-edge bg-card text-sm text-card-foreground\",\n        conventionClass(convention),\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/** 卡片头部：左边标题与说明，右边放 `ChartCardAction`；窄屏上下堆叠。 */\nfunction ChartCardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"chart-card-header\"\n      className={cn(\n        \"flex flex-col gap-3 border-b border-edge p-4 sm:flex-row sm:items-start sm:justify-between\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n// 用 div 而不是 h3：标题层级由所在页面决定，需要时自己加 role=\"heading\" 与 aria-level\nfunction ChartCardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"chart-card-title\"\n      className={cn(\"text-sm leading-5 font-medium\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ChartCardDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"chart-card-description\"\n      className={cn(\"text-[13px] leading-5 text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\n/** 头部右侧的操作区：按钮、选择器、图例等。 */\nfunction ChartCardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"chart-card-action\"\n      className={cn(\"flex shrink-0 items-center gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ChartCardContent({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"chart-card-content\"\n      className={cn(\"min-w-0 p-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ChartCardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"chart-card-footer\"\n      className={cn(\n        \"flex items-center gap-3 border-t border-edge p-4 text-xs text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/** 指标行：几个并排的小指标，之间用竖线隔开，放在头部或图表上方。 */\nfunction ChartMetrics({ className, ...props }: React.ComponentProps<\"dl\">) {\n  return (\n    <dl\n      data-slot=\"chart-metrics\"\n      className={cn(\n        \"flex flex-wrap items-center gap-y-2 divide-x divide-edge\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\ntype ChartMetricProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** 指标名 */\n  label: React.ReactNode\n  /** 指标值 */\n  value: React.ReactNode\n  /** 数值前的色标，用来对应图表里的某条序列 */\n  tone?: DataTone\n}\n\nfunction ChartMetric({\n  className,\n  label,\n  value,\n  tone,\n  ...props\n}: ChartMetricProps) {\n  return (\n    <div\n      data-slot=\"chart-metric\"\n      className={cn(\n        \"flex flex-col gap-1 px-4 first:pl-0 last:pr-0 max-sm:px-3\",\n        className,\n      )}\n      {...props}\n    >\n      <dt className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n        {tone ? (\n          <span\n            aria-hidden=\"true\"\n            className=\"size-1.5 rounded-full\"\n            style={{ backgroundColor: toneColor(tone) }}\n          />\n        ) : null}\n        {label}\n      </dt>\n      <dd className=\"text-sm leading-none font-medium tabular-nums\">{value}</dd>\n    </div>\n  )\n}\n\ntype ChartTableColumn = {\n  /** 对应行数据里的字段名 */\n  key: string\n  /** 表头 */\n  header: React.ReactNode\n  /**\n   * 对齐方式；数字列用 `end`\n   * @default \"start\"\n   */\n  align?: \"start\" | \"center\" | \"end\"\n}\n\ntype ChartTableRow = {\n  /** 行首的色标，对应图表里的序列 */\n  tone?: DataTone\n  /** 各列的内容，字段名与 `columns[].key` 对应 */\n  [column: string]: React.ReactNode\n}\n\ntype ChartTableProps = Omit<React.ComponentProps<\"table\">, \"children\"> & {\n  columns: ChartTableColumn[]\n  rows: ChartTableRow[]\n}\n\nconst ALIGN = {\n  start: \"text-left\",\n  center: \"text-center\",\n  end: \"text-right\",\n} as const\n\n/** 图表下方的明细表：第一列前带一根与序列同色的竖条，数字列右对齐、等宽。 */\nfunction ChartTable({ className, columns, rows, ...props }: ChartTableProps) {\n  return (\n    <div data-slot=\"chart-table\" className=\"w-full overflow-x-auto\">\n      <table\n        data-slot=\"chart-table-element\"\n        className={cn(\"w-full min-w-max border-collapse text-sm\", className)}\n        {...props}\n      >\n        <thead>\n          <tr className=\"border-b border-edge text-xs text-muted-foreground\">\n            {columns.map((column) => (\n              <th\n                key={column.key}\n                scope=\"col\"\n                className={cn(\n                  \"px-3 py-2 font-normal first:pl-0 last:pr-0\",\n                  ALIGN[column.align ?? \"start\"],\n                )}\n              >\n                {column.header}\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody className=\"divide-y divide-edge\">\n          {rows.map((row, rowIndex) => (\n            <tr key={rowIndex}>\n              {columns.map((column, columnIndex) => (\n                <td\n                  key={column.key}\n                  className={cn(\n                    \"px-3 py-2.5 tabular-nums first:pl-0 last:pr-0\",\n                    ALIGN[column.align ?? \"start\"],\n                  )}\n                >\n                  {columnIndex === 0 && row.tone ? (\n                    <span className=\"flex items-center gap-2\">\n                      <span\n                        aria-hidden=\"true\"\n                        className=\"h-5 w-1 shrink-0 rounded-full\"\n                        style={{ backgroundColor: toneColor(row.tone) }}\n                      />\n                      {row[column.key]}\n                    </span>\n                  ) : (\n                    row[column.key]\n                  )}\n                </td>\n              ))}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  )\n}\n\nexport {\n  ChartCard,\n  ChartCardAction,\n  ChartCardContent,\n  ChartCardDescription,\n  ChartCardFooter,\n  ChartCardHeader,\n  ChartCardTitle,\n  ChartGauge,\n  ChartLegendGrid,\n  ChartLegendList,\n  ChartMetric,\n  ChartMetrics,\n  ChartRanking,\n  ChartSeries,\n  ChartTable,\n}\nexport type {\n  ChartCardProps,\n  ChartGaugeProps,\n  ChartLegendGridProps,\n  ChartLegendItem,\n  ChartLegendListProps,\n  ChartMetricProps,\n  ChartRankingItem,\n  ChartRankingProps,\n  ChartSeriesItem,\n  ChartSeriesProps,\n  ChartTableColumn,\n  ChartTableProps,\n  ChartTableRow,\n}\n",
      "type": "registry:ui",
      "target": "@ui/chart-card/chart-card.tsx"
    },
    {
      "path": "src/registry/ui/chart-card/chart-gauge.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  formatStat,\n  motionStyles,\n  toneColor,\n  type DataTone,\n  type StatFormatOptions,\n} from \"@/lib/data-display\"\n\ntype ChartGaugeProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** 当前值 */\n  value: number\n  /**\n   * 满值\n   * @default 100\n   */\n  max?: number\n  /**\n   * 刻度数量；刻度越多越像一圈连续的光带\n   * @default 60\n   */\n  ticks?: number\n  /**\n   * 刻度环的总角度：360 是整圆，270 是下方留口的仪表\n   * @default 270\n   */\n  arc?: number\n  /**\n   * 点亮刻度的色调\n   * @default \"primary\"\n   */\n  tone?: DataTone\n  /**\n   * 点亮的刻度由淡到浓渐变，最后一格最亮最长，像指针停在当前值上\n   * @default true\n   */\n  fade?: boolean\n  /** 中央大数字的格式，默认取整后的百分比 */\n  format?: StatFormatOptions\n  /** 中央数字下方的小字，如「达成率」 */\n  caption?: React.ReactNode\n  /** 中央内容，传入后替代默认的数字与小字 */\n  children?: React.ReactNode\n  /** 读屏名称 */\n  label?: string\n}\n\nconst CENTER = 100\nconst OUTER = 94\nconst TICK = 12\n\n/**\n * 刻度式环形仪表：一圈细刻度，按值依次点亮。\n * 点亮的刻度从起点向当前值逐格淡入；服务端直接输出最终状态，淡入是纯 CSS，没有脚本也完整显示；系统开启「减少动态效果」时不播放。\n */\n/** 三角函数在 Node 与浏览器里末位小数可能不同，取 3 位小数让服务端与客户端输出一致，避免水合告警。 */\nfunction round(value: number) {\n  return Math.round(value * 1000) / 1000\n}\n\nfunction ChartGauge({\n  className,\n  value,\n  max = 100,\n  ticks = 60,\n  arc = 270,\n  tone = \"primary\",\n  fade = true,\n  format,\n  caption,\n  children,\n  label,\n  ...props\n}: ChartGaugeProps) {\n  const ratio = max > 0 ? Math.min(1, Math.max(0, value / max)) : 0\n  const lit = Math.round(ratio * ticks)\n  const color = toneColor(tone)\n  const sweep = Math.min(360, Math.max(30, arc))\n  // 角度从 12 点钟顺时针量；开口仪表让缺口落在正下方，起点在左下\n  const start = sweep === 360 ? 0 : 180 + (360 - sweep) / 2\n  const step = sweep === 360 ? 360 / ticks : sweep / Math.max(1, ticks - 1)\n\n  const marks = Array.from({ length: ticks }, (_, index) => {\n    const angle = ((start + index * step) * Math.PI) / 180\n    const sin = Math.sin(angle)\n    const cos = Math.cos(angle)\n    const head = lit > 0 && index === lit - 1\n    // 最后一格点亮的刻度比其余长，作为「当前值」的标记\n    const length = head ? TICK + 6 : TICK\n    return {\n      index,\n      head,\n      on: index < lit,\n      x1: round(CENTER + (OUTER - length) * sin),\n      y1: round(CENTER - (OUTER - length) * cos),\n      x2: round(CENTER + OUTER * sin),\n      y2: round(CENTER - OUTER * cos),\n      opacity: head || !fade || lit < 2 ? 1 : 0.3 + 0.7 * (index / (lit - 1)),\n    }\n  })\n\n  return (\n    <div\n      data-slot=\"chart-gauge\"\n      role=\"meter\"\n      aria-label={label}\n      aria-valuemin={0}\n      aria-valuemax={max}\n      aria-valuenow={value}\n      className={cn(\n        \"relative aspect-square size-44 max-w-full shrink-0\",\n        className,\n      )}\n      {...props}\n    >\n      {motionStyles()}\n      <svg\n        viewBox=\"0 0 200 200\"\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 size-full\"\n      >\n        {/* 稳定的淡色底环不依赖入场动画，慢网与无脚本时仍有完整轮廓。 */}\n        <circle\n          data-slot=\"chart-gauge-placeholder\"\n          cx={CENTER}\n          cy={CENTER}\n          r={OUTER - TICK / 2}\n          fill=\"none\"\n          stroke=\"var(--muted)\"\n          strokeWidth={TICK}\n          pathLength={1}\n          strokeDasharray={`${sweep / 360} 1`}\n          transform={`rotate(${start - 90} 100 100)`}\n        />\n        {marks.map((mark) =>\n          mark.on ? (\n            <line\n              key={mark.index}\n              data-dd-anim=\"fade\"\n              x1={mark.x1}\n              y1={mark.y1}\n              x2={mark.x2}\n              y2={mark.y2}\n              stroke={color}\n              strokeWidth={mark.head ? 3.5 : 3}\n              strokeLinecap=\"round\"\n              opacity={mark.opacity}\n              style={{\n                animationDelay: `${Math.round((mark.index / ticks) * 700)}ms`,\n              }}\n            />\n          ) : (\n            <line\n              key={mark.index}\n              x1={mark.x1}\n              y1={mark.y1}\n              x2={mark.x2}\n              y2={mark.y2}\n              stroke=\"var(--edge)\"\n              strokeWidth={3}\n              strokeLinecap=\"round\"\n            />\n          ),\n        )}\n      </svg>\n      <div\n        data-slot=\"chart-gauge-content\"\n        className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 text-center tabular-nums\"\n      >\n        {children ?? (\n          <>\n            <span className=\"text-3xl leading-none font-semibold tracking-tight\">\n              {formatStat(ratio * 100, {\n                style: \"percent\",\n                digits: 0,\n                ...format,\n              })}\n            </span>\n            {caption ? (\n              <span className=\"text-xs text-muted-foreground\">{caption}</span>\n            ) : null}\n          </>\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport { ChartGauge }\nexport type { ChartGaugeProps }\n",
      "type": "registry:ui",
      "target": "@ui/chart-card/chart-gauge.tsx"
    },
    {
      "path": "src/registry/ui/chart-card/chart-series.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Area,\n  AreaChart,\n  Bar,\n  BarChart,\n  CartesianGrid,\n  Line,\n  LineChart,\n  XAxis,\n  YAxis,\n} from \"recharts\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  type ChartConfig,\n} from \"@/components/ui/chart\"\nimport {\n  SERIES_TONES,\n  conventionClass,\n  createAxisFormatter,\n  formatStat,\n  toneColor,\n  type DataConvention,\n  type DataTone,\n  type StatFormatOptions,\n} from \"@/lib/data-display\"\n\nimport { ChartLegendList } from \"./chart-legend\"\nimport { CHART_AXIS_TICK, CHART_GRID, useChartAnimation } from \"./chart-parts\"\n\ntype ChartSeriesItem = {\n  /** 数据里这条序列的字段名 */\n  key: string\n  /** 图例与提示框里的名称 */\n  label: string\n  /** 色调，不设置时按顺序取默认色板（蓝、琥珀、绿、紫、玫红、青） */\n  tone?: DataTone\n}\n\ntype ChartSeriesProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** 数据行，每行一个横轴位置 */\n  data: Record<string, string | number>[]\n  /** 横轴对应的字段名 */\n  xKey: string\n  /** 要画的序列 */\n  series: ChartSeriesItem[]\n  /**\n   * 图形：折线、柱、面积\n   * @default \"line\"\n   */\n  type?: \"line\" | \"bar\" | \"area\"\n  /**\n   * 堆叠柱 / 面积；`\"diverging\"` 让正负值分别向上、向下堆叠，用来对比两组相反方向的数据\n   * @default false\n   */\n  stacked?: boolean | \"diverging\"\n  /**\n   * 折线与面积的插值：平滑、折线、阶梯\n   * @default \"monotone\"\n   */\n  curve?: \"monotone\" | \"linear\" | \"step\"\n  /** 横轴刻度文字的格式化，如把 `\"2026-02-14\"` 变成 `\"2月14日\"` */\n  xTickFormatter?: (value: string, index: number) => string\n  /** 提示框标题的格式化，默认同横轴刻度 */\n  labelFormatter?: (value: string) => string\n  /** 提示框里数值的格式，默认 `zh-CN` 压缩；纵轴取其中的 `style`、`locale`、`currency`、`digits`，并按整条轴统一单位（万 / 亿或 K / M / B） */\n  format?: StatFormatOptions\n  /** 纵轴范围，不设置时自动 */\n  yDomain?: [number, number]\n  /** 纵轴刻度位置 */\n  yTicks?: number[]\n  /**\n   * 显示纵轴\n   * @default true\n   */\n  showYAxis?: boolean\n  /**\n   * 显示水平虚线网格\n   * @default true\n   */\n  showGrid?: boolean\n  /**\n   * 在图表上方显示图例；默认序列多于一条时显示\n   */\n  showLegend?: boolean\n  /** 柱宽上限（像素） */\n  barSize?: number\n  /** 涨跌配色约定，仅序列使用 `up` / `down` 色调时有用；不设置时沿用外层 */\n  convention?: DataConvention\n  /** 读屏名称，描述这张图表示什么 */\n  label?: string\n}\n\n/** 多序列图表：折线、柱、面积三合一，支持堆叠与正负对比。高度用 `className` 控制（默认 `h-64`）。 */\nfunction ChartSeries({\n  className,\n  data,\n  xKey,\n  series,\n  type = \"line\",\n  stacked = false,\n  curve = \"monotone\",\n  xTickFormatter,\n  labelFormatter,\n  format,\n  yDomain,\n  yTicks,\n  showYAxis = true,\n  showGrid = true,\n  showLegend = series.length > 1,\n  barSize,\n  convention,\n  label,\n  ...props\n}: ChartSeriesProps) {\n  const ready = React.useSyncExternalStore(\n    subscribe,\n    () => true,\n    () => false,\n  )\n  const animation = useChartAnimation()\n  const gradientBase = React.useId().replaceAll(\":\", \"\")\n  const resolved = series.map((item, index) => ({\n    ...item,\n    color: toneColor(\n      item.tone ?? SERIES_TONES[index % SERIES_TONES.length] ?? \"blue\",\n    ),\n  }))\n  const config: ChartConfig = {}\n  for (const item of resolved)\n    config[item.key] = { label: item.label, color: item.color }\n\n  // 纵轴整条轴共用一个单位：先找出数据（堆叠时是每列的合计）的最大绝对值，再选「万 / 亿」或 K / M\n  const keys = series.map((item) => item.key)\n  const peak =\n    yDomain && yDomain.length === 2\n      ? Math.max(Math.abs(yDomain[0]), Math.abs(yDomain[1]))\n      : Math.max(\n          0,\n          ...data.map((row) => {\n            const values = keys.map((key) => Number(row[key]) || 0)\n            return stacked\n              ? Math.max(\n                  values.filter((v) => v > 0).reduce((a, b) => a + b, 0),\n                  -values.filter((v) => v < 0).reduce((a, b) => a + b, 0),\n                )\n              : Math.max(...values.map(Math.abs))\n          }),\n        )\n  const formatTick = createAxisFormatter(peak, format)\n  const margin = { top: 8, right: 8, bottom: 0, left: 0 }\n  const grid = showGrid ? <CartesianGrid {...CHART_GRID} /> : null\n  const xAxis = (\n    <XAxis\n      dataKey={xKey}\n      tickLine={false}\n      axisLine={false}\n      tickMargin={8}\n      minTickGap={16}\n      tick={CHART_AXIS_TICK}\n      tickFormatter={xTickFormatter}\n    />\n  )\n  const yAxis = (\n    <YAxis\n      hide={!showYAxis}\n      width=\"auto\"\n      tickLine={false}\n      axisLine={false}\n      tickMargin={8}\n      tick={CHART_AXIS_TICK}\n      domain={yDomain}\n      ticks={yTicks}\n      tickFormatter={formatTick}\n    />\n  )\n  const tooltip = (\n    <ChartTooltip\n      cursor={type === \"bar\" ? false : { stroke: \"var(--edge)\" }}\n      content={\n        <ChartTooltipContent\n          className=\"min-w-36 shadow-sm\"\n          labelFormatter={\n            labelFormatter\n              ? (value) => labelFormatter(String(value))\n              : undefined\n          }\n          formatter={(value, name, item) => (\n            <>\n              <span\n                aria-hidden=\"true\"\n                className=\"size-2 shrink-0 rounded-[2px]\"\n                style={{ backgroundColor: item.color }}\n              />\n              <span className=\"text-muted-foreground\">{name}</span>\n              <span className=\"ml-auto font-mono font-medium text-foreground tabular-nums\">\n                {formatStat(Number(value), format)}\n              </span>\n            </>\n          )}\n        />\n      }\n    />\n  )\n  const stackId = stacked ? \"stack\" : undefined\n\n  return (\n    <div\n      data-slot=\"chart-series\"\n      data-ready={ready}\n      data-type={type}\n      className={cn(\n        \"relative flex h-64 min-w-0 flex-col gap-3\",\n        conventionClass(convention),\n        className,\n      )}\n      {...props}\n    >\n      {showLegend ? (\n        <ChartLegendList\n          marker={type === \"line\" ? \"line\" : \"dot\"}\n          items={resolved.map((item) => ({\n            label: item.label,\n            tone: item.color,\n          }))}\n        />\n      ) : null}\n      <ChartContainer\n        data-slot=\"chart-series-plot\"\n        data-ready={ready}\n        config={config}\n        role=\"img\"\n        aria-label={label}\n        initialDimension={{ width: 320, height: 200 }}\n        className=\"aspect-auto min-h-0 w-full flex-1 justify-start opacity-0 transition-opacity duration-200 data-[ready=true]:opacity-100 motion-reduce:transition-none\"\n      >\n        {type === \"bar\" ? (\n          <BarChart\n            data={data}\n            margin={margin}\n            barCategoryGap=\"28%\"\n            stackOffset={stacked === \"diverging\" ? \"sign\" : undefined}\n          >\n            {grid}\n            {xAxis}\n            {yAxis}\n            {tooltip}\n            {resolved.map((item, index) => (\n              <Bar\n                key={item.key}\n                dataKey={item.key}\n                name={item.label}\n                fill={`var(--color-${item.key})`}\n                stackId={stackId}\n                maxBarSize={barSize ?? 24}\n                radius={\n                  stacked &&\n                  stacked !== \"diverging\" &&\n                  index < resolved.length - 1\n                    ? 0\n                    : [3, 3, 0, 0]\n                }\n                {...animation}\n              />\n            ))}\n          </BarChart>\n        ) : type === \"area\" ? (\n          <AreaChart data={data} margin={margin}>\n            <defs>\n              {resolved.map((item) => (\n                <linearGradient\n                  key={item.key}\n                  id={`${gradientBase}-${item.key}`}\n                  x1=\"0\"\n                  y1=\"0\"\n                  x2=\"0\"\n                  y2=\"1\"\n                >\n                  <stop\n                    offset=\"0%\"\n                    stopColor={`var(--color-${item.key})`}\n                    stopOpacity={0.28}\n                  />\n                  <stop\n                    offset=\"100%\"\n                    stopColor={`var(--color-${item.key})`}\n                    stopOpacity={0}\n                  />\n                </linearGradient>\n              ))}\n            </defs>\n            {grid}\n            {xAxis}\n            {yAxis}\n            {tooltip}\n            {resolved.map((item) => (\n              <Area\n                key={item.key}\n                dataKey={item.key}\n                name={item.label}\n                type={curve === \"step\" ? \"stepAfter\" : curve}\n                stroke={`var(--color-${item.key})`}\n                strokeWidth={2}\n                fill={`url(#${gradientBase}-${item.key})`}\n                stackId={stackId}\n                activeDot={{ r: 3, strokeWidth: 0 }}\n                {...animation}\n              />\n            ))}\n          </AreaChart>\n        ) : (\n          <LineChart data={data} margin={margin}>\n            {grid}\n            {xAxis}\n            {yAxis}\n            {tooltip}\n            {resolved.map((item) => (\n              <Line\n                key={item.key}\n                dataKey={item.key}\n                name={item.label}\n                type={curve === \"step\" ? \"stepAfter\" : curve}\n                stroke={`var(--color-${item.key})`}\n                strokeWidth={2}\n                dot={false}\n                activeDot={{ r: 3, strokeWidth: 0 }}\n                {...animation}\n              />\n            ))}\n          </LineChart>\n        )}\n      </ChartContainer>\n      <div\n        data-slot=\"chart-series-placeholder\"\n        aria-hidden=\"true\"\n        className={cn(\n          \"pointer-events-none absolute inset-0 z-10 min-h-0 bg-muted/30 transition-opacity duration-200 motion-reduce:transition-none\",\n          ready && \"opacity-0\",\n        )}\n      >\n        <svg\n          className=\"absolute inset-0 size-full text-muted-foreground/20\"\n          preserveAspectRatio=\"none\"\n          viewBox=\"0 0 100 100\"\n        >\n          <path\n            d=\"M0 25H100 M0 50H100 M0 75H100\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeDasharray=\"3 3\"\n          />\n        </svg>\n      </div>\n    </div>\n  )\n}\n\nconst subscribe = () => () => {}\n\nexport { ChartSeries }\nexport type { ChartSeriesItem, ChartSeriesProps }\n",
      "type": "registry:ui",
      "target": "@ui/chart-card/chart-series.tsx"
    },
    {
      "path": "src/registry/ui/chart-card/chart-ranking.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Bar, BarChart, Cell, LabelList, XAxis, YAxis } from \"recharts\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChartContainer, type ChartConfig } from \"@/components/ui/chart\"\nimport {\n  SERIES_TONES,\n  formatStat,\n  toneColor,\n  type DataTone,\n  type StatFormatOptions,\n} from \"@/lib/data-display\"\n\nimport { ChartLegendGrid } from \"./chart-legend\"\nimport { CHART_AXIS_TICK, useChartAnimation } from \"./chart-parts\"\n\ntype ChartRankingItem = {\n  /** 条目名称，图例里显示 */\n  label: string\n  /** 数值，单位由 `format` 决定（默认按百分比显示） */\n  value: number\n  /** 色调，不设置时按顺序取默认色板 */\n  tone?: DataTone\n}\n\ntype ChartRankingProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** 排名条目，按从高到低排列 */\n  items: ChartRankingItem[]\n  /**\n   * 横轴满值；百分比数据用 100，其余数据默认取最大值\n   */\n  max?: number\n  /**\n   * 数值格式，默认是不压缩的百分比\n   * @default { style: \"percent\", digits: 0 }\n   */\n  format?: StatFormatOptions\n  /**\n   * 在图表旁显示图例格（名称 + 数值）；窄屏落到图表下方\n   * @default true\n   */\n  showLegend?: boolean\n  /** 读屏名称，描述这张图表示什么 */\n  label?: string\n}\n\nconst ROW_HEIGHT = 36\n\n/**\n * 横向排名条：左侧是名次，条末标出数值，右侧（窄屏在下方）是带名称的图例格。\n * 名次对应图例的顺序，所以图例同样按名次排列。\n */\nfunction ChartRanking({\n  className,\n  items,\n  max,\n  format = { style: \"percent\", digits: 0 },\n  showLegend = true,\n  label,\n  ...props\n}: ChartRankingProps) {\n  const ready = React.useSyncExternalStore(\n    subscribe,\n    () => true,\n    () => false,\n  )\n  const animation = useChartAnimation()\n  const data = items.map((item, index) => ({\n    rank: String(index + 1),\n    label: item.label,\n    value: item.value,\n    fill: toneColor(\n      item.tone ?? SERIES_TONES[index % SERIES_TONES.length] ?? \"blue\",\n    ),\n  }))\n  const config: ChartConfig = { value: { label: \"数值\" } }\n  const ceiling = max ?? Math.max(...items.map((item) => item.value), 1)\n\n  return (\n    <div\n      data-slot=\"chart-ranking\"\n      data-ready={ready}\n      className={cn(\n        \"relative grid min-w-0 items-center gap-6\",\n        showLegend && \"lg:grid-cols-2\",\n        className,\n      )}\n      {...props}\n    >\n      <ChartContainer\n        data-slot=\"chart-ranking-plot\"\n        data-ready={ready}\n        config={config}\n        role=\"img\"\n        aria-label={label}\n        initialDimension={{ width: 320, height: items.length * ROW_HEIGHT }}\n        className=\"aspect-auto w-full justify-start opacity-0 transition-opacity duration-200 data-[ready=true]:opacity-100 motion-reduce:transition-none\"\n        style={{ height: items.length * ROW_HEIGHT + 24 }}\n      >\n        <BarChart\n          data={data}\n          layout=\"vertical\"\n          barCategoryGap=\"30%\"\n          margin={{ top: 0, right: 40, bottom: 0, left: 0 }}\n        >\n          <XAxis type=\"number\" domain={[0, ceiling]} hide />\n          <YAxis\n            dataKey=\"rank\"\n            type=\"category\"\n            width={20}\n            tickLine={false}\n            axisLine={false}\n            tick={CHART_AXIS_TICK}\n          />\n          <Bar\n            dataKey=\"value\"\n            radius={[0, 4, 4, 0]}\n            background={{ fill: \"var(--muted)\", radius: 4 }}\n            {...animation}\n          >\n            {data.map((entry) => (\n              <Cell key={entry.label} fill={entry.fill} />\n            ))}\n            <LabelList\n              dataKey=\"value\"\n              position=\"right\"\n              offset={8}\n              className=\"fill-foreground text-xs tabular-nums\"\n              formatter={(value: unknown) => formatStat(Number(value), format)}\n            />\n          </Bar>\n        </BarChart>\n      </ChartContainer>\n      {showLegend ? (\n        <ChartLegendGrid\n          items={items.map((item, index) => ({\n            label: `${index + 1}. ${item.label}`,\n            tone:\n              item.tone ?? SERIES_TONES[index % SERIES_TONES.length] ?? \"blue\",\n            value: formatStat(item.value, format),\n          }))}\n        />\n      ) : null}\n      <div\n        data-slot=\"chart-ranking-placeholder\"\n        aria-hidden=\"true\"\n        className={cn(\n          \"pointer-events-none absolute inset-0 z-10 min-h-0 bg-muted/30 transition-opacity duration-200 motion-reduce:transition-none\",\n          ready && \"opacity-0\",\n        )}\n      >\n        <div className=\"absolute inset-0 flex flex-col justify-around gap-2 p-3\">\n          {items.map((item, index) => (\n            <div\n              key={index}\n              className=\"h-3 shrink rounded-sm bg-muted-foreground/15\"\n              style={{\n                width: `${Math.max(12, Math.min(90, (item.value / ceiling) * 90))}%`,\n              }}\n            />\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nconst subscribe = () => () => {}\n\nexport { ChartRanking }\nexport type { ChartRankingItem, ChartRankingProps }\n",
      "type": "registry:ui",
      "target": "@ui/chart-card/chart-ranking.tsx"
    },
    {
      "path": "src/registry/ui/chart-card/chart-legend.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { toneColor, type DataTone } from \"@/lib/data-display\"\n\ntype ChartLegendItem = {\n  label: string\n  tone: DataTone\n  /** 图例项右侧的数值，已格式化的文字 */\n  value?: React.ReactNode\n}\n\ntype ChartLegendListProps = Omit<React.ComponentProps<\"ul\">, \"children\"> & {\n  items: ChartLegendItem[]\n  /**\n   * 色标形状：`dot` 圆点（柱、面积），`line` 短横线（折线）\n   * @default \"dot\"\n   */\n  marker?: \"dot\" | \"line\"\n}\n\n/** 行内图例：色标 + 名称，放在图表上方，窄屏自动换行。 */\nfunction ChartLegendList({\n  className,\n  items,\n  marker = \"dot\",\n  ...props\n}: ChartLegendListProps) {\n  return (\n    <ul\n      data-slot=\"chart-legend-list\"\n      className={cn(\"flex flex-wrap items-center gap-x-4 gap-y-1\", className)}\n      {...props}\n    >\n      {items.map((item) => (\n        <li\n          key={item.label}\n          className=\"flex items-center gap-1.5 text-xs text-muted-foreground\"\n        >\n          <span\n            aria-hidden=\"true\"\n            className={cn(\n              \"shrink-0\",\n              marker === \"dot\"\n                ? \"size-2 rounded-full\"\n                : \"h-0.5 w-3 rounded-full\",\n            )}\n            style={{ backgroundColor: toneColor(item.tone) }}\n          />\n          {item.label}\n        </li>\n      ))}\n    </ul>\n  )\n}\n\ntype ChartLegendGridProps = Omit<React.ComponentProps<\"ul\">, \"children\"> & {\n  items: ChartLegendItem[]\n}\n\n/** 图例格：两列，每项是圆点、名称和下面一行大号数值（通常是百分比）。 */\nfunction ChartLegendGrid({ className, items, ...props }: ChartLegendGridProps) {\n  return (\n    <ul\n      data-slot=\"chart-legend-grid\"\n      className={cn(\"grid grid-cols-2 gap-x-6 gap-y-4\", className)}\n      {...props}\n    >\n      {items.map((item) => (\n        <li key={item.label} className=\"flex min-w-0 items-start gap-2.5\">\n          <span\n            aria-hidden=\"true\"\n            className=\"mt-1.5 size-2 shrink-0 rounded-full\"\n            style={{ backgroundColor: toneColor(item.tone) }}\n          />\n          <span className=\"flex min-w-0 flex-col gap-1\">\n            <span className=\"line-clamp-2 text-[13px] text-muted-foreground\">\n              {item.label}\n            </span>\n            <span className=\"text-lg leading-none font-semibold tabular-nums\">\n              {item.value}\n            </span>\n          </span>\n        </li>\n      ))}\n    </ul>\n  )\n}\n\nexport { ChartLegendGrid, ChartLegendList }\nexport type { ChartLegendGridProps, ChartLegendItem, ChartLegendListProps }\n",
      "type": "registry:ui",
      "target": "@ui/chart-card/chart-legend.tsx"
    },
    {
      "path": "src/registry/ui/chart-card/chart-parts.tsx",
      "content": "\"use client\"\n\nimport { useReducedMotion } from \"motion/react\"\n\n/** 坐标轴刻度：11px、次要文字色。 */\nconst CHART_AXIS_TICK = {\n  fontSize: 11,\n  fill: \"var(--muted-foreground)\",\n} as const\n\n/** 网格线：只画水平线，细、淡、虚线，颜色取线条网格的 `--edge`。 */\nconst CHART_GRID = {\n  vertical: false,\n  stroke: \"var(--edge)\",\n  strokeDasharray: \"3 4\",\n} as const\n\n/** recharts 的入场动画开关：系统开启「减少动态效果」时关闭。 */\nfunction useChartAnimation() {\n  const reduced = useReducedMotion()\n  return { isAnimationActive: !reduced, animationDuration: 700 } as const\n}\n\nexport { CHART_AXIS_TICK, CHART_GRID, useChartAnimation }\n",
      "type": "registry:ui",
      "target": "@ui/chart-card/chart-parts.tsx"
    },
    {
      "path": "src/registry/lib/data-display.ts",
      "content": "import * as React from \"react\"\n\n/**\n * 数据展示共用工具：涨跌配色约定、色调、数字与日期格式化。\n * stat-card、mini-chart、chart-card 三个条目共用这一份，所以涨跌色、数字格式只在这里定义。\n */\n\n/**\n * 涨跌配色约定：\n * - `intl`（默认）：绿涨红跌；\n * - `cn`：红涨绿跌，国内行情软件的习惯。\n */\ntype DataConvention = \"intl\" | \"cn\"\n\n// 约定只改写 `--stat-up` / `--stat-down` 两个 CSS 变量。变量沿 DOM 向下继承，\n// 所以给整块看板的外层设一次 `convention`，里面所有涨跌色（包括图表里的 SVG）都跟着变，\n// 不需要 context，服务端组件也能用。没有设置时回落到默认的绿涨红跌。\nconst CONVENTION_CLASSES: Record<DataConvention, string> = {\n  intl: \"[--stat-up:var(--flow-green,var(--primary))] [--stat-down:var(--flow-rose,var(--destructive))]\",\n  cn: \"[--stat-up:var(--flow-rose,var(--destructive))] [--stat-down:var(--flow-green,var(--primary))]\",\n}\n\n/** 把 `convention` 换成要加到根元素上的类名；不传返回 `undefined`，沿用外层的约定。 */\nfunction conventionClass(convention?: DataConvention) {\n  return convention ? CONVENTION_CLASSES[convention] : undefined\n}\n\ntype Trend = \"up\" | \"down\" | \"flat\"\n\nfunction trendOf(value: number): Trend {\n  return value > 0 ? \"up\" : value < 0 ? \"down\" : \"flat\"\n}\n\n/** 涨跌文字色（Tailwind 只认完整类名，所以三种写全）。 */\nconst TREND_TEXT: Record<Trend, string> = {\n  up: \"text-[color:var(--stat-up,var(--flow-green,var(--primary)))]\",\n  down: \"text-[color:var(--stat-down,var(--flow-rose,var(--destructive)))]\",\n  flat: \"text-muted-foreground\",\n}\n\n/**\n * 内置色调：\n * - `primary`、`foreground`、`muted`、`destructive` 直接引用主题变量，跟随颜色主题；\n * - `blue` 等多彩色调引用 `--flow-*` 变量，没有安装时回落到主题强调色；\n * - `chart-1` … `chart-5` 引用主题的图表色板；\n * - `up` / `down` 是涨跌色，随 `convention` 变化。\n */\ntype DataToneName =\n  | \"primary\"\n  | \"foreground\"\n  | \"muted\"\n  | \"destructive\"\n  | \"blue\"\n  | \"green\"\n  | \"amber\"\n  | \"rose\"\n  | \"violet\"\n  | \"cyan\"\n  | \"chart-1\"\n  | \"chart-2\"\n  | \"chart-3\"\n  | \"chart-4\"\n  | \"chart-5\"\n  | \"up\"\n  | \"down\"\n\n/** 色调：内置名，或任意 CSS 颜色（`\"var(--brand)\"`、`\"oklch(0.7 0.2 150)\"` 都可以）。 */\ntype DataTone = DataToneName | (string & {})\n\nconst TONES: Record<DataToneName, string> = {\n  primary: \"var(--primary)\",\n  foreground: \"var(--foreground)\",\n  muted: \"var(--muted-foreground)\",\n  destructive: \"var(--destructive)\",\n  blue: \"var(--flow-blue, var(--primary))\",\n  green: \"var(--flow-green, var(--primary))\",\n  amber: \"var(--flow-amber, var(--primary))\",\n  rose: \"var(--flow-rose, var(--destructive))\",\n  violet: \"var(--flow-violet, var(--primary))\",\n  cyan: \"var(--flow-cyan, var(--primary))\",\n  \"chart-1\": \"var(--chart-1)\",\n  \"chart-2\": \"var(--chart-2)\",\n  \"chart-3\": \"var(--chart-3)\",\n  \"chart-4\": \"var(--chart-4)\",\n  \"chart-5\": \"var(--chart-5)\",\n  up: \"var(--stat-up, var(--flow-green, var(--primary)))\",\n  down: \"var(--stat-down, var(--flow-rose, var(--destructive)))\",\n}\n\n/** 把色调换成可直接用于 `fill` / `stroke` / `color` 的 CSS 颜色。 */\nfunction toneColor(tone: DataTone): string {\n  return Object.hasOwn(TONES, tone) ? TONES[tone as DataToneName] : tone\n}\n\n/**\n * 多序列图表默认的色调轮换：六种互不相近的多彩色调。\n * 不把主题强调色放进来：强调色会落在蓝、绿、紫、琥珀任意一种附近，\n * 切到对应的颜色主题时会与其中一条序列撞色。单条序列想跟随主题，显式写 `tone=\"primary\"`。\n */\nconst SERIES_TONES: DataTone[] = [\n  \"blue\",\n  \"amber\",\n  \"green\",\n  \"violet\",\n  \"rose\",\n  \"cyan\",\n]\n\ntype StatFormatOptions = {\n  /**\n   * 数字样式：`number` 普通数字，`percent` 百分比（传入的 12.5 表示 12.5%），`currency` 货币\n   * @default \"number\"\n   */\n  style?: \"number\" | \"percent\" | \"currency\"\n  /**\n   * BCP 47 区域标识；`zh-CN` 压缩成「万 / 亿」，`en` 压缩成 K / M / B\n   * @default \"zh-CN\"\n   */\n  locale?: string\n  /**\n   * 压缩大数（1.2万、3.4亿）；百分比不压缩\n   * @default true\n   */\n  compact?: boolean\n  /**\n   * 货币代码，仅 `style=\"currency\"` 生效；人民币显示 ¥\n   * @default \"CNY\"\n   */\n  currency?: string\n  /**\n   * 最多保留的小数位\n   * @default 1（压缩与百分比），2（普通数字与货币）\n   */\n  digits?: number\n  /**\n   * 正数前加 `+`（零不加），用于涨跌幅\n   * @default false\n   */\n  sign?: boolean\n}\n\nconst formatters = new Map<string, Intl.NumberFormat>()\n\n/**\n * 格式化统计数字，默认 `zh-CN` 压缩：`12480` → `1.2万`，`345000000` → `3.5亿`；\n * `locale: \"en\"` 时为 `12K`、`345M`。同参数的 `Intl.NumberFormat` 会被缓存复用。\n */\nfunction formatStat(value: number, options: StatFormatOptions = {}): string {\n  const {\n    style = \"number\",\n    locale = \"zh-CN\",\n    compact = style !== \"percent\",\n    currency = \"CNY\",\n    sign = false,\n  } = options\n  const digits = options.digits ?? (compact || style === \"percent\" ? 1 : 2)\n  const key = [style, locale, compact, currency, digits, sign].join(\"|\")\n  let formatter = formatters.get(key)\n  if (!formatter) {\n    formatter = new Intl.NumberFormat(locale, {\n      style: style === \"number\" ? \"decimal\" : style,\n      currency: style === \"currency\" ? currency : undefined,\n      currencyDisplay: style === \"currency\" ? \"narrowSymbol\" : undefined,\n      notation: compact && style !== \"percent\" ? \"compact\" : \"standard\",\n      maximumFractionDigits: digits,\n      signDisplay: sign ? \"exceptZero\" : \"auto\",\n    })\n    formatters.set(key, formatter)\n  }\n  return formatter.format(style === \"percent\" ? value / 100 : value)\n}\n\ntype DayStyle = \"long\" | \"short\" | \"weekday\"\n\nconst dayFormatters = new Map<string, Intl.DateTimeFormat>()\n\n/**\n * 格式化日期轴文字：`long` → `2月14日`，`short` → `02/14`，`weekday` → `一`（单字星期）。\n * 日期按 UTC 解析与输出，`\"2026-02-14\"` 这类字符串在任何时区、任何环境下结果一致，不会引起水合差异。\n */\nfunction formatDay(\n  input: string | number | Date,\n  style: DayStyle = \"long\",\n  locale = \"zh-CN\",\n): string {\n  const key = `${style}|${locale}`\n  let formatter = dayFormatters.get(key)\n  if (!formatter) {\n    const options: Intl.DateTimeFormatOptions =\n      style === \"weekday\"\n        ? { weekday: \"narrow\" }\n        : style === \"short\"\n          ? { month: \"2-digit\", day: \"2-digit\" }\n          : { month: \"long\", day: \"numeric\" }\n    formatter = new Intl.DateTimeFormat(locale, { ...options, timeZone: \"UTC\" })\n    dayFormatters.set(key, formatter)\n  }\n  return formatter.format(new Date(input))\n}\n\ntype AxisFormatOptions = Pick<\n  StatFormatOptions,\n  \"style\" | \"locale\" | \"currency\" | \"digits\"\n>\n\n// 压缩单位表：从大到小，命中第一个不超过轴最大值的单位\nconst AXIS_UNITS = {\n  zh: [\n    [1e8, \"亿\"],\n    [1e4, \"万\"],\n  ],\n  \"zh-trad\": [\n    [1e8, \"億\"],\n    [1e4, \"萬\"],\n  ],\n  en: [\n    [1e9, \"B\"],\n    [1e6, \"M\"],\n    [1e3, \"K\"],\n  ],\n} as const\n\n/**\n * 按「整条轴」统一单位的刻度格式化：先用轴的最大值选定一个单位，所有刻度都换算成它，\n * 避免 `9000` 与 `1.2万` 混在同一根轴上。例如最大值 12000 时刻度是 `0`、`0.3万`、`0.6万`、`0.9万`、`1.2万`；\n * 最大值 8000 时全部是整数（`2000`、`4000`…）。`en` 同理换成 K / M / B。\n *\n * @param max 轴上数据的最大绝对值；留 15% 余量，因为图表会把坐标轴上沿取整到比数据更大的刻度\n */\nfunction createAxisFormatter(\n  max: number,\n  options: AxisFormatOptions = {},\n): (value: number) => string {\n  const { style = \"number\", locale = \"zh-CN\", currency = \"CNY\" } = options\n  if (style === \"percent\")\n    return (value) =>\n      formatStat(value, { style: \"percent\", locale, digits: options.digits })\n  const family = /^zh-(TW|HK|MO|Hant)/i.test(locale)\n    ? \"zh-trad\"\n    : /^zh/i.test(locale)\n      ? \"zh\"\n      : \"en\"\n  const reach = Math.abs(max) * 1.15\n  const unit = AXIS_UNITS[family].find(([size]) => reach >= size)\n  const divisor = unit?.[0] ?? 1\n  const suffix = unit?.[1] ?? \"\"\n  const formatter = new Intl.NumberFormat(locale, {\n    style: style === \"currency\" ? \"currency\" : \"decimal\",\n    currency: style === \"currency\" ? currency : undefined,\n    currencyDisplay: style === \"currency\" ? \"narrowSymbol\" : undefined,\n    notation: \"standard\",\n    maximumFractionDigits: options.digits ?? 2,\n  })\n  return (value) =>\n    value === 0\n      ? formatter.format(0)\n      : `${formatter.format(value / divisor)}${suffix}`\n}\n\n// 进度条、计量条、分段条、进度环、刻度仪表的入场动画：纯 CSS，服务端直接输出最终状态，\n// 所以没有脚本时也完整显示；系统开启「减少动态效果」时不播放。\nconst MOTION_CSS = `@media (prefers-reduced-motion: no-preference) {\n@keyframes dd-reveal { from { clip-path: inset(0 100% 0 0 round 9999px) } to { clip-path: inset(0 0 0 0 round 9999px) } }\n@keyframes dd-rise { from { opacity: 0; transform: scaleY(0.35) } }\n@keyframes dd-fade { from { opacity: 0 } }\n@keyframes dd-arc { from { stroke-dasharray: 0 1 } }\n[data-dd-anim=\"reveal\"] { animation: dd-reveal 0.8s cubic-bezier(0.22, 1, 0.36, 1) both }\n[data-dd-anim=\"rise\"] { animation: dd-rise 0.3s cubic-bezier(0.22, 1, 0.36, 1) both }\n[data-dd-anim=\"fade\"] { animation: dd-fade 0.35s cubic-bezier(0.22, 1, 0.36, 1) both }\n[data-dd-anim=\"arc\"] { animation: dd-arc 0.9s cubic-bezier(0.22, 1, 0.36, 1) both }\n}`\n\n/**\n * 渲染上面的动画样式。React 19 会按 `href` 去重并提升到 `<head>`，\n * 同一页放多少个组件都只有一份；放在服务端组件里也能用。\n */\nfunction motionStyles() {\n  return React.createElement(\n    \"style\",\n    { href: \"data-display-motion\", precedence: \"default\" },\n    MOTION_CSS,\n  )\n}\n\nexport {\n  TREND_TEXT,\n  SERIES_TONES,\n  conventionClass,\n  createAxisFormatter,\n  formatDay,\n  formatStat,\n  motionStyles,\n  toneColor,\n  trendOf,\n}\nexport type {\n  AxisFormatOptions,\n  DataConvention,\n  DataTone,\n  DataToneName,\n  DayStyle,\n  StatFormatOptions,\n  Trend,\n}\n",
      "type": "registry:ui",
      "target": "lib/data-display.ts"
    }
  ],
  "cssVars": {
    "theme": {
      "color-edge": "var(--edge)"
    },
    "light": {
      "flow-blue": "oklch(0.56 0.2 258)",
      "flow-green": "oklch(0.62 0.16 150)",
      "flow-amber": "oklch(0.7 0.16 70)",
      "flow-rose": "oklch(0.62 0.21 5)",
      "flow-violet": "oklch(0.56 0.22 292)",
      "flow-cyan": "oklch(0.66 0.12 215)",
      "edge": "color-mix(in oklch, var(--foreground) 12%, transparent)"
    },
    "dark": {
      "flow-blue": "oklch(0.72 0.15 255)",
      "flow-green": "oklch(0.77 0.17 152)",
      "flow-amber": "oklch(0.82 0.15 82)",
      "flow-rose": "oklch(0.74 0.16 8)",
      "flow-violet": "oklch(0.74 0.15 295)",
      "flow-cyan": "oklch(0.82 0.11 208)",
      "edge": "color-mix(in oklch, var(--foreground) 18%, transparent)"
    }
  },
  "type": "registry:ui"
}
