{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "type": "registry:ui",
  "title": "Chart",
  "description": "누적 막대 · 가로 막대 · 선 · 원형 네 형태를 같은 틀에 담은 차트입니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/lib-cn",
    "@atom/lib-use-controllable-state"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/chart.tsx",
      "content": "\"use client\";\n\nimport { type HTMLAttributes, type KeyboardEvent, type ReactNode, type Ref, useState } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../lib/use-controllable-state\";\n\n/* -------------------------------------------------------------------------- */\n/*  Chart (components/chart.md, styles/atom/elements/chart.css)               */\n/*    <Chart                                                                  */\n/*      type=\"bar\"                                                            */\n/*      title=\"월별 처리 건수\"                                                  */\n/*      subtitle=\"최근 7개월 · 단위 건\"                                         */\n/*      categories={[\"1월\", \"2월\", \"3월\"]}                                      */\n/*      series={[                                                             */\n/*        { key: \"done\", label: \"처리 완료\", data: [32, 41, 28] },              */\n/*        { key: \"doing\", label: \"처리 중\", data: [24, 18, 31] },               */\n/*      ]}                                                                    */\n/*    />                                                                      */\n/*  마크는 인라인 SVG 로 직접 그립니다(차트 라이브러리 없음). 좌표계는 원본 데모의 */\n/*  viewBox(620 × 300 · 620 × 290 · 300 × 260)와 여백을 그대로 씁니다.           */\n/* -------------------------------------------------------------------------- */\n\nexport type ChartType = \"bar\" | \"bar-horizontal\" | \"line\" | \"pie\";\nexport type ChartSeriesSlot = 1 | 2 | 3 | 4 | 5 | 6;\nexport type ChartSeries = {\n  /** 개체 식별자. 색 슬롯은 이 개체에 고정됩니다. */\n  key: string;\n  /** 범례·툴팁·표에 쓰는 이름 */\n  label: string;\n  /** categories 와 같은 순서의 값 */\n  data: number[];\n  /** 색 슬롯(1–6)을 고정합니다. 없으면 배열 순서대로 1 부터 */\n  slot?: ChartSeriesSlot;\n};\nexport type ChartValueLabels = \"none\" | \"end\" | \"total\" | \"all\";\nexport type ChartView = \"chart\" | \"table\";\nexport type ChartPoint = {\n  /** categories 의 인덱스 */\n  categoryIndex: number;\n  category: string;\n  /** 마크 하나를 가리킬 때(막대) 그 시리즈의 key */\n  seriesKey?: string;\n};\n\nexport type ChartProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"children\"> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 마크의 형태입니다. */\n  type?: ChartType;\n  /** 시리즈입니다. `slot` 을 주면 그 색을 고정합니다. */\n  series: ChartSeries[];\n  /** x축(또는 가로 막대의 y축) 라벨입니다. */\n  categories: string[];\n  /** `bar` 계열에서 누적할지 나란히 놓을지입니다. */\n  stacked?: boolean;\n  /** 무엇을 그린 차트인지입니다. 시리즈가 하나면 이것이 유일한 이름입니다. */\n  title: string;\n  /** 기간·단위 같은 보충입니다. */\n  subtitle?: string;\n  /** 직접 라벨의 범위입니다. `\"all\"` 은 안티패턴이라 문서화만 해 둡니다. */\n  valueLabels?: ChartValueLabels;\n  /** 표 뷰를 쓸 때만 씁니다(controlled). 선택 사항입니다. */\n  view?: ChartView;\n  /** uncontrolled 일 때 처음 보일 뷰 (구현 추가) */\n  defaultView?: ChartView;\n  /** 헤더의 차트/표 전환 칩으로 뷰가 바뀔 때 (구현 추가) */\n  onViewChange?: (view: ChartView) => void;\n  /** 헤더에 차트/표 전환 칩(atom-chart__toggle)을 둡니다 (구현 추가) */\n  viewToggle?: boolean;\n  /** 표 뷰 첫 열의 머리글입니다 (구현 추가) */\n  categoryLabel?: string;\n  /** y축 눈금입니다. 깔끔한 수로 떨어지게 잡습니다. 없으면 자동 */\n  yTicks?: number[];\n  /** 데이터가 없을 때 문구입니다. */\n  emptyText?: string;\n  /** 툴팁을 밖에서 제어할 때 씁니다. */\n  onPointHover?: (point: ChartPoint | null) => void;\n};\n\nconst B = \"atom-chart\";\n\n/* ------------------------------- 좌표계 ---------------------------------- */\n/* 원본 데모 SVG 에서 실측한 값 */\nconst W = 620;\nconst BAR = { left: 44, right: 596, top: 12, bottom: 236, height: 300 }; // 세로 막대\nconst LINE = { left: 44, right: 596, top: 12, bottom: 226, height: 290 }; // 선\nconst HBAR = { left: 96, right: 566, top: 12, slot: 38 }; // 가로 막대 (높이는 항목 수로)\nconst PIE = { width: 300, height: 260, cx: 130, cy: 124, r: 96 };\nconst BAR_W = 22; // 막대 두께 (상한 24)\nconst RADIUS = 4; // 데이터 끝 모서리\nconst GAP = 2; // 마크 사이 서피스 갭\n\nconst TYPE_NAME: Record<ChartType, string> = {\n  bar: \"막대 차트\",\n  \"bar-horizontal\": \"가로 막대 차트\",\n  line: \"선 차트\",\n  pie: \"원형 차트\",\n};\n\nconst f = (n: number) => n.toFixed(1);\n\n/** 1 · 2 · 2.5 · 5 × 10ⁿ 간격으로 4칸 남짓 — 원본 데모의 0·25·…·100, 0·20·40·60 과 같은 결과 */\nfunction autoTicks(max: number): number[] {\n  if (!(max > 0)) return [0, 1];\n  const raw = max / 4;\n  const mag = 10 ** Math.floor(Math.log10(raw));\n  const step = [1, 2, 2.5, 5, 10].map((m) => m * mag).find((s) => s >= raw - 1e-9) ?? 10 * mag;\n  const n = Math.ceil(max / step - 1e-9);\n  return Array.from({ length: n + 1 }, (_, i) => Number((i * step).toFixed(10)));\n}\n\nconst slotOf = (s: ChartSeries | undefined, index: number): ChartSeriesSlot =>\n  s?.slot ?? (((index % 6) + 1) as ChartSeriesSlot);\n\nconst val = (data: number[], i: number) => Math.max(0, data[i] ?? 0);\n\n/** 위쪽 두 모서리만 둥근 세로 막대 (기준선 쪽은 각) */\nfunction vBarPath(x: number, w: number, yBase: number, yEnd: number) {\n  const r = Math.min(RADIUS, yBase - yEnd, w / 2);\n  return `M${f(x)} ${f(yBase)} V${f(yEnd + r)} a${f(r)} ${f(r)} 0 0 1 ${f(r)} -${f(r)} H${f(x + w - r)} a${f(r)} ${f(r)} 0 0 1 ${f(r)} ${f(r)} V${f(yBase)} Z`;\n}\n\n/** 오른쪽 두 모서리만 둥근 가로 막대 */\nfunction hBarPath(xBase: number, xEnd: number, y: number, h: number) {\n  const r = Math.min(RADIUS, xEnd - xBase, h / 2);\n  return `M${f(xBase)} ${f(y)} H${f(xEnd - r)} a${f(r)} ${f(r)} 0 0 1 ${f(r)} ${f(r)} V${f(y + h - r)} a${f(r)} ${f(r)} 0 0 1 -${f(r)} ${f(r)} H${f(xBase)} Z`;\n}\n\ntype Active = { index: number; seriesIndex?: number } | null;\n\nfunction ChartRoot({\n  ref,\n  type = \"bar\",\n  series,\n  categories,\n  stacked = true,\n  title,\n  subtitle,\n  valueLabels = \"end\",\n  view: viewProp,\n  defaultView = \"chart\",\n  onViewChange,\n  viewToggle = false,\n  categoryLabel = \"항목\",\n  yTicks,\n  emptyText = \"조회된 데이터가 없습니다\",\n  onPointHover,\n  className,\n  ...rest\n}: ChartProps) {\n  const [view, setView] = useControllableState<ChartView>({\n    value: viewProp,\n    defaultValue: defaultView,\n    onChange: onViewChange,\n  });\n  const [active, setActiveState] = useState<Active>(null);\n\n  const isEmpty = categories.length === 0 || series.length === 0 || series.every((s) => s.data.length === 0);\n  const isPie = type === \"pie\";\n  const isBar = type === \"bar\" || type === \"bar-horizontal\";\n  const stack = isBar && stacked && series.length > 1;\n\n  /** 툴팁 대상을 바꾸고 onPointHover 로 알립니다 (이벤트 핸들러에서만 호출) */\n  const setActive = (next: Active) => {\n    if (active?.index === next?.index && active?.seriesIndex === next?.seriesIndex) return;\n    setActiveState(next);\n    const s = next?.seriesIndex !== undefined ? series[next.seriesIndex] : undefined;\n    onPointHover?.(next ? { categoryIndex: next.index, category: categories[next.index] ?? \"\", seriesKey: s?.key } : null);\n  };\n\n  /* ------------------------------ 범례 ------------------------------------ */\n  // 시리즈가 둘 이상이면 항상, 하나면 없음. 원형은 조각(categories)이 개체입니다.\n  const legendItems = isPie\n    ? categories.map((c, i) => ({ key: c, label: c, slot: slotOf(undefined, i) }))\n    : series.map((s, i) => ({ key: s.key, label: s.label, slot: slotOf(s, i) }));\n\n  const legend =\n    !isEmpty && legendItems.length >= 2 ? (\n      <ul className={`${B}__legend`}>\n        {legendItems.map((it) => (\n          <li key={it.key} className={`${B}__legend-item`}>\n            <span className={`${B}__legend-swatch`} data-series={it.slot} />\n            <span className={`${B}__legend-label`}>{it.label}</span>\n          </li>\n        ))}\n      </ul>\n    ) : null;\n\n  /* ------------------------------ 마크 ------------------------------------ */\n  const totals = categories.map((_, i) => series.reduce((sum, s) => sum + val(s.data, i), 0));\n  const dataMax = isPie\n    ? 0\n    : stack\n      ? Math.max(0, ...totals)\n      : Math.max(0, ...series.flatMap((s) => s.data.map((v) => Math.max(0, v))));\n  const ticks = yTicks && yTicks.length >= 2 ? [...yTicks].sort((a, b) => a - b) : autoTicks(dataMax);\n  const max = Math.max(ticks[ticks.length - 1] ?? 1, dataMax) || 1;\n\n  let viewBox = `0 0 ${W} ${BAR.height}`;\n  const marks: ReactNode[] = [];\n  /** 툴팁 위치(%) 계산용: 카테고리마다 기준 x 좌표 */\n  const anchorX: number[] = [];\n  let svgWidth = W;\n\n  const enter = (index: number, seriesIndex?: number) => () => setActive({ index, seriesIndex });\n\n  if (!isEmpty && type === \"bar\") {\n    const { left, right, top, bottom } = BAR;\n    const y = (v: number) => bottom - (v / max) * (bottom - top);\n    const slot = (right - left) / categories.length;\n    ticks.forEach((t) => {\n      if (t > 0) marks.push(<line key={`g${t}`} className={`${B}__grid-line`} x1={f(left)} y1={f(y(t))} x2={f(right)} y2={f(y(t))} />);\n    });\n    marks.push(<line key=\"axis\" className={`${B}__axis`} x1={f(left)} y1={f(bottom)} x2={f(right)} y2={f(bottom)} />);\n    ticks.forEach((t) =>\n      marks.push(\n        <text key={`t${t}`} className={`${B}__axis-label`} x={f(left - 8)} y={f(y(t) + 4)} textAnchor=\"end\">\n          {t}\n        </text>,\n      ),\n    );\n    categories.forEach((cat, i) => {\n      const cx = left + slot * (i + 0.5);\n      anchorX[i] = cx;\n      if (stack) {\n        const x = cx - BAR_W / 2;\n        const nonzero = series.map((s) => val(s.data, i));\n        const topIndex = nonzero.reduce((acc, v, s) => (v > 0 ? s : acc), -1);\n        let base = 0;\n        series.forEach((s, si) => {\n          const v = nonzero[si] ?? 0;\n          if (v <= 0) return;\n          const y0 = y(base);\n          const y1 = y(base + v);\n          base += v;\n          const slotNo = slotOf(s, si);\n          const common = {\n            className: `${B}__bar`,\n            \"data-series\": slotNo,\n            onPointerEnter: enter(i, si),\n          };\n          // 맨 위 세그먼트만 데이터 끝이 둥글고, 아래 세그먼트는 위로 2px 갭만큼 물러납니다\n          marks.push(\n            si === topIndex ? (\n              <path key={`b${i}-${si}`} {...common} d={vBarPath(x, BAR_W, y0, y1)} />\n            ) : (\n              <rect key={`b${i}-${si}`} {...common} x={f(x)} y={f(y1 + GAP)} width={f(BAR_W)} height={f(Math.max(0, y0 - y1 - GAP))} />\n            ),\n          );\n          if (valueLabels === \"all\" && y0 - y1 >= 14) {\n            marks.push(\n              <text key={`v${i}-${si}`} className={cn(`${B}__value`, `${B}__value--on-fill`)} data-series={slotNo} x={f(cx)} y={f((y0 + y1) / 2 + 4)} textAnchor=\"middle\">\n                {v}\n              </text>,\n            );\n          }\n        });\n        // 직접 라벨은 합계 하나. 세그먼트마다 얹지 않습니다\n        if (valueLabels === \"end\" || valueLabels === \"total\") {\n          marks.push(\n            <text key={`v${i}`} className={`${B}__value`} x={f(cx)} y={f(y(totals[i] ?? 0) - 8)} textAnchor=\"middle\">\n              {totals[i]}\n            </text>,\n          );\n        }\n      } else {\n        const n = series.length;\n        const bw = Math.min(BAR_W, (slot * 0.6 - (n - 1) * GAP) / n);\n        const gw = n * bw + (n - 1) * GAP;\n        series.forEach((s, si) => {\n          const v = val(s.data, i);\n          const x = cx - gw / 2 + si * (bw + GAP);\n          if (v > 0) {\n            marks.push(\n              <path key={`b${i}-${si}`} className={`${B}__bar`} data-series={slotOf(s, si)} onPointerEnter={enter(i, si)} d={vBarPath(x, bw, bottom, y(v))} />,\n            );\n          }\n          if (valueLabels === \"all\" || (valueLabels === \"end\" && si === 0)) {\n            marks.push(\n              <text key={`v${i}-${si}`} className={`${B}__value`} x={f(x + bw / 2)} y={f(y(v) - 8)} textAnchor=\"middle\">\n                {s.data[i] ?? 0}\n              </text>,\n            );\n          }\n        });\n        if (valueLabels === \"total\") {\n          const tip = Math.max(...series.map((s) => val(s.data, i)));\n          marks.push(\n            <text key={`v${i}`} className={`${B}__value`} x={f(cx)} y={f(y(tip) - 8)} textAnchor=\"middle\">\n              {totals[i]}\n            </text>,\n          );\n        }\n      }\n      marks.push(\n        <text key={`c${i}`} className={`${B}__axis-label`} x={f(cx)} y={f(bottom + 20)} textAnchor=\"middle\">\n          {cat}\n        </text>,\n      );\n    });\n  }\n\n  if (!isEmpty && type === \"bar-horizontal\") {\n    const { left, right, top, slot } = HBAR;\n    const bottom = top + slot * categories.length;\n    viewBox = `0 0 ${W} ${f(bottom + 52).replace(/\\.0$/, \"\")}`;\n    const x = (v: number) => left + (v / max) * (right - left);\n    ticks.forEach((t) => {\n      if (t > 0) marks.push(<line key={`g${t}`} className={`${B}__grid-line`} x1={f(x(t))} y1={f(top)} x2={f(x(t))} y2={f(bottom)} />);\n    });\n    marks.push(<line key=\"axis\" className={`${B}__axis`} x1={f(left)} y1={f(bottom)} x2={f(right)} y2={f(bottom)} />);\n    ticks.forEach((t) =>\n      marks.push(\n        <text key={`t${t}`} className={`${B}__axis-label`} x={f(x(t))} y={f(bottom + 16)} textAnchor=\"middle\">\n          {t}\n        </text>,\n      ),\n    );\n    categories.forEach((cat, i) => {\n      const cy = top + slot * (i + 0.5);\n      if (stack) {\n        const h = Math.min(BAR_W, slot - 10);\n        const y0 = cy - h / 2;\n        const values = series.map((s) => val(s.data, i));\n        const lastIndex = values.reduce((acc, v, s) => (v > 0 ? s : acc), -1);\n        let base = 0;\n        series.forEach((s, si) => {\n          const v = values[si] ?? 0;\n          if (v <= 0) return;\n          const x0 = x(base);\n          const x1 = x(base + v);\n          base += v;\n          const common = { className: `${B}__bar`, \"data-series\": slotOf(s, si), onPointerEnter: enter(i, si) };\n          marks.push(\n            si === lastIndex ? (\n              <path key={`b${i}-${si}`} {...common} d={hBarPath(x0, x1, y0, h)} />\n            ) : (\n              <rect key={`b${i}-${si}`} {...common} x={f(x0)} y={f(y0)} width={f(Math.max(0, x1 - x0 - GAP))} height={f(h)} />\n            ),\n          );\n        });\n        if (valueLabels !== \"none\") {\n          marks.push(\n            <text key={`v${i}`} className={`${B}__value`} x={f(x(totals[i] ?? 0) + 6)} y={f(cy + 4)}>\n              {totals[i]}\n            </text>,\n          );\n        }\n        anchorX[i] = x(totals[i] ?? 0);\n      } else {\n        const n = series.length;\n        const bt = Math.min(BAR_W, (slot - 10 - (n - 1) * GAP) / n);\n        const gh = n * bt + (n - 1) * GAP;\n        series.forEach((s, si) => {\n          const v = val(s.data, i);\n          const y0 = cy - gh / 2 + si * (bt + GAP);\n          if (v > 0) {\n            marks.push(\n              <path key={`b${i}-${si}`} className={`${B}__bar`} data-series={slotOf(s, si)} onPointerEnter={enter(i, si)} d={hBarPath(left, x(v), y0, bt)} />,\n            );\n          }\n          // 값은 첫 시리즈의 팁 바깥에만\n          if (valueLabels === \"all\" || ((valueLabels === \"end\" || valueLabels === \"total\") && si === 0)) {\n            marks.push(\n              <text key={`v${i}-${si}`} className={`${B}__value`} x={f(x(v) + 6)} y={f(y0 + bt / 2 + 4)}>\n                {s.data[i] ?? 0}\n              </text>,\n            );\n          }\n        });\n        anchorX[i] = x(Math.max(...series.map((s) => val(s.data, i))));\n      }\n      marks.push(\n        <text key={`c${i}`} className={`${B}__axis-label`} x={f(left - 10)} y={f(cy + 2)} textAnchor=\"end\">\n          {cat}\n        </text>,\n      );\n    });\n  }\n\n  if (!isEmpty && type === \"line\") {\n    const { left, right, top, bottom, height } = LINE;\n    viewBox = `0 0 ${W} ${height}`;\n    const y = (v: number) => bottom - (v / max) * (bottom - top);\n    const xs = categories.map((_, i) =>\n      categories.length === 1 ? (left + right) / 2 : left + (i * (right - left)) / (categories.length - 1),\n    );\n    xs.forEach((x, i) => (anchorX[i] = x));\n    ticks.forEach((t) => {\n      if (t > 0) marks.push(<line key={`g${t}`} className={`${B}__grid-line`} x1={f(left)} y1={f(y(t))} x2={f(right)} y2={f(y(t))} />);\n    });\n    marks.push(<line key=\"axis\" className={`${B}__axis`} x1={f(left)} y1={f(bottom)} x2={f(right)} y2={f(bottom)} />);\n    ticks.forEach((t) =>\n      marks.push(\n        <text key={`t${t}`} className={`${B}__axis-label`} x={f(left - 8)} y={f(y(t) + 4)} textAnchor=\"end\">\n          {t}\n        </text>,\n      ),\n    );\n    // 크로스헤어 — 선에만. 막대·조각은 마크 자체가 히트 영역입니다\n    if (active) {\n      const cx = xs[active.index] ?? left;\n      marks.push(<line key=\"crosshair\" className={`${B}__crosshair`} x1={f(cx)} y1={f(top)} x2={f(cx)} y2={f(bottom)} />);\n    }\n    series.forEach((s, si) => {\n      const pts = categories.map((_, i) => `${f(xs[i] ?? 0)} ${f(y(val(s.data, i)))}`);\n      marks.push(<path key={`l${si}`} className={`${B}__line`} data-series={slotOf(s, si)} d={`M${pts.join(\" L\")}`} />);\n    });\n    series.forEach((s, si) =>\n      categories.forEach((_, i) =>\n        marks.push(\n          <circle key={`m${si}-${i}`} className={`${B}__marker`} data-series={slotOf(s, si)} cx={f(xs[i] ?? 0)} cy={f(y(val(s.data, i)))} r=\"4\" onPointerEnter={enter(i)} />,\n        ),\n      ),\n    );\n    // 직접 라벨은 끝점에만 — 점마다 숫자를 얹지 않습니다\n    if (valueLabels === \"end\" || valueLabels === \"total\") {\n      const last = categories.length - 1;\n      series.forEach((s, si) =>\n        marks.push(\n          <text key={`v${si}`} className={`${B}__value`} x={f((xs[last] ?? 0) + 8)} y={f(y(val(s.data, last)) + 4)}>\n            {s.data[last] ?? 0}\n          </text>,\n        ),\n      );\n    }\n    categories.forEach((cat, i) =>\n      marks.push(\n        <text key={`c${i}`} className={`${B}__axis-label`} x={f(xs[i] ?? 0)} y={f(bottom + 20)} textAnchor=\"middle\">\n          {cat}\n        </text>,\n      ),\n    );\n    if (valueLabels === \"all\") {\n      series.forEach((s, si) =>\n        categories.forEach((_, i) =>\n          marks.push(\n            <text key={`a${si}-${i}`} className={`${B}__value`} x={f(xs[i] ?? 0)} y={f(y(val(s.data, i)) - 9)} textAnchor=\"middle\">\n              {s.data[i] ?? 0}\n            </text>,\n          ),\n        ),\n      );\n    }\n  }\n\n  if (!isEmpty && isPie) {\n    const { width, height, cx, cy, r } = PIE;\n    svgWidth = width;\n    viewBox = `0 0 ${width} ${height}`;\n    const data = series[0]?.data ?? [];\n    const total = categories.reduce((sum, _, i) => sum + val(data, i), 0);\n    const rad = (deg: number) => (deg * Math.PI) / 180;\n    const at = (deg: number, radius: number) => [cx + radius * Math.cos(rad(deg)), cy + radius * Math.sin(rad(deg))] as const;\n    let a0 = -90;\n    const visible = categories.filter((_, i) => val(data, i) > 0).length;\n    categories.forEach((_, i) => {\n      const v = val(data, i);\n      if (v <= 0 || total <= 0) return;\n      const sweep = (v / total) * 360;\n      const a1 = a0 + sweep;\n      const mid = a0 + sweep / 2;\n      const slotNo = slotOf(undefined, i);\n      if (visible === 1) {\n        marks.push(<circle key={`p${i}`} className={`${B}__slice`} data-series={slotNo} cx={cx} cy={cy} r={r} onPointerEnter={enter(i)} />);\n      } else {\n        // 조각 사이 2px 갭: 시작·끝 점을 접선 방향으로 1px 씩 물립니다\n        const [sx0, sy0] = at(a0, r);\n        const [ex0, ey0] = at(a1, r);\n        const sx = sx0 - Math.sin(rad(a0));\n        const sy = sy0 + Math.cos(rad(a0));\n        const ex = ex0 + Math.sin(rad(a1));\n        const ey = ey0 - Math.cos(rad(a1));\n        marks.push(\n          <path\n            key={`p${i}`}\n            className={`${B}__slice`}\n            data-series={slotNo}\n            onPointerEnter={enter(i)}\n            d={`M${f(cx)} ${f(cy)} L${f(sx)} ${f(sy)} A${f(r)} ${f(r)} 0 ${sweep > 180 ? 1 : 0} 1 ${f(ex)} ${f(ey)} Z`}\n          />,\n        );\n      }\n      const [lx, ly] = at(mid, r * 0.62);\n      anchorX[i] = lx;\n      // 비율을 조각 안에. 채움 위 라벨은 슬롯별로 흰색/잉크를 고릅니다(data-series)\n      if (valueLabels !== \"none\" && sweep >= 18) {\n        marks.push(\n          <text key={`v${i}`} className={cn(`${B}__value`, `${B}__value--on-fill`)} data-series={slotNo} x={f(lx)} y={f(ly + 4)} textAnchor=\"middle\">\n            {`${Math.round((v / total) * 100)}%`}\n          </text>,\n        );\n      }\n      a0 = a1;\n    });\n  }\n\n  /* ------------------------------ 툴팁 ------------------------------------ */\n  const tooltip = (() => {\n    if (isEmpty || !active) return null;\n    const i = active.index;\n    const cat = categories[i];\n    if (cat === undefined) return null;\n    let rows: { key: string; slot: ChartSeriesSlot; label: string; value: ReactNode }[];\n    if (isPie) {\n      const data = series[0]?.data ?? [];\n      const total = categories.reduce((sum, _, k) => sum + val(data, k), 0);\n      const v = data[i] ?? 0;\n      rows = [\n        {\n          key: cat,\n          slot: slotOf(undefined, i),\n          label: series[0]?.label ?? cat,\n          value: total > 0 ? `${v} (${Math.round((val(data, i) / total) * 100)}%)` : v,\n        },\n      ];\n    } else {\n      const pick = active.seriesIndex !== undefined ? [active.seriesIndex] : series.map((_, si) => si);\n      rows = pick.flatMap((si) => {\n        const s = series[si];\n        return s ? [{ key: s.key, slot: slotOf(s, si), label: s.label, value: s.data[i] ?? 0 }] : [];\n      });\n    }\n    const pct = ((anchorX[i] ?? 0) / svgWidth) * 100;\n    const left = pct > 55 ? Math.max(0, pct - 26) : pct + 2;\n    return (\n      <div className={`${B}__tooltip`} data-state=\"open\" role=\"status\" style={{ left: `${left.toFixed(1)}%`, top: 18 }}>\n        <p className={`${B}__tooltip-title`}>{cat}</p>\n        {rows.map((row) => (\n          <div key={row.key} className={`${B}__tooltip-row`} data-series={row.slot}>\n            <span className={`${B}__tooltip-swatch`} />\n            <span className={`${B}__tooltip-label`}>{row.label}</span>\n            <span className={`${B}__tooltip-value`}>{row.value}</span>\n          </div>\n        ))}\n      </div>\n    );\n  })();\n\n  /* ------------------------------ 키보드 ----------------------------------- */\n  // 키보드 초점도 hover 와 같은 내용을 보여줍니다\n  const onPlotKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n    const n = categories.length;\n    if (!n) return;\n    const cur = active?.index ?? -1;\n    let next: number | null = null;\n    if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") next = cur < 0 ? 0 : Math.min(n - 1, cur + 1);\n    else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") next = cur < 0 ? 0 : Math.max(0, cur - 1);\n    else if (e.key === \"Home\") next = 0;\n    else if (e.key === \"End\") next = n - 1;\n    else if (e.key === \"Escape\") {\n      if (active) {\n        e.preventDefault();\n        setActive(null);\n      }\n      return;\n    } else return;\n    e.preventDefault();\n    setActive({ index: next });\n  };\n\n  const svgLabel = `${title} ${stack && type === \"bar\" ? \"누적 막대 차트\" : TYPE_NAME[type]}`;\n\n  /* ------------------------------ 표 뷰 ------------------------------------ */\n  const showTotal = stack;\n  const table =\n    !isEmpty && view === \"table\" ? (\n      <div className={`${B}__table`}>\n        <table>\n          <thead>\n            <tr>\n              <th scope=\"col\">{categoryLabel}</th>\n              {(isPie ? series.slice(0, 1) : series).map((s) => (\n                <th key={s.key} scope=\"col\">\n                  {s.label}\n                </th>\n              ))}\n              {showTotal && <th scope=\"col\">합계</th>}\n            </tr>\n          </thead>\n          <tbody>\n            {categories.map((cat, i) => (\n              <tr key={cat + i}>\n                <th scope=\"row\">{cat}</th>\n                {(isPie ? series.slice(0, 1) : series).map((s) => (\n                  <td key={s.key}>{s.data[i] ?? 0}</td>\n                ))}\n                {showTotal && <td>{totals[i]}</td>}\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n    ) : null;\n\n  return (\n    <div {...rest} ref={ref} className={cn(B, className)} data-type={type} data-view={view}>\n      <div className={`${B}__header`}>\n        <div>\n          <p className={`${B}__title`}>{title}</p>\n          {subtitle && <p className={`${B}__subtitle`}>{subtitle}</p>}\n        </div>\n        {viewToggle && !isEmpty && (\n          <span className={`${B}__toggle`}>\n            {([\"chart\", \"table\"] as const).map((v) => {\n              const selected = view === v;\n              return (\n                <button\n                  key={v}\n                  type=\"button\"\n                  className={cn(\"atom-chip\", \"atom-chip--size_small\", selected && \"atom-chip--variant_neutral-solid\")}\n                  data-selected={selected ? \"\" : undefined}\n                  aria-pressed={selected}\n                  onClick={() => setView(v)}\n                >\n                  <span className=\"atom-chip__label\">{v === \"chart\" ? \"차트\" : \"표\"}</span>\n                </button>\n              );\n            })}\n          </span>\n        )}\n      </div>\n\n      {isEmpty ? (\n        // 데이터가 없으면 축과 격자만 남긴 빈 판을 그리지 않습니다\n        <p className={`${B}__empty`}>{emptyText}</p>\n      ) : (\n        <>\n          {legend}\n          <div\n            className={`${B}__plot`}\n            tabIndex={view === \"chart\" ? 0 : undefined}\n            onKeyDown={onPlotKeyDown}\n            onFocus={(e) => {\n              if (e.target === e.currentTarget && !active) setActive({ index: 0 });\n            }}\n            onBlur={() => setActive(null)}\n          >\n            <svg\n              viewBox={viewBox}\n              role=\"img\"\n              aria-label={svgLabel}\n              onPointerLeave={() => setActive(null)}\n              onPointerMove={\n                type === \"line\"\n                  ? (e) => {\n                      const rect = e.currentTarget.getBoundingClientRect();\n                      if (!rect.width || categories.length < 2) return;\n                      const vx = ((e.clientX - rect.left) / rect.width) * W;\n                      const step = (LINE.right - LINE.left) / (categories.length - 1);\n                      const idx = Math.round((vx - LINE.left) / step);\n                      setActive({ index: Math.min(categories.length - 1, Math.max(0, idx)) });\n                    }\n                  : undefined\n              }\n            >\n              {marks}\n            </svg>\n            {tooltip}\n          </div>\n          {table}\n        </>\n      )}\n    </div>\n  );\n}\n\nexport const Chart = Object.assign(ChartRoot, {});\n",
      "type": "registry:ui",
      "target": "components/ui/chart.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/chart.css",
      "content": "/* ============================================================================\n   Element — Chart\n   ----------------------------------------------------------------------------\n   Four forms on one frame: stacked bar, horizontal bar, line, pie. The frame is\n   the same every time — header, legend, plot — because the thing that changes\n   between them is the shape of the data's job, not the furniture around it.\n\n   The marks are inline SVG. There is no chart library here: this catalogue\n   documents what the marks must measure, and a <svg> is the only way to put a\n   real 2px line and a real 4px data-end on the page next to the numbers that\n   specify them.\n\n   Series colour comes off `data-series` and nothing else. `--atom-chart-series-1`\n   … `-6` are a validated set — see Foundations · Color — and the order is the\n   colour-blind-safety mechanism, so a mark asks for slot 3 by name rather than\n   by its position in the DOM. That is also what keeps a filter from repainting\n   the survivors: the entity owns the slot.\n\n   Two pieces of negative space do the separating, and neither is a stroke:\n     · a 2px surface gap between touching fills (stack segments, adjacent bars)\n     · a 2px surface ring on markers, so they stay legible where lines cross\n\n   atom-chart\n     [data-type=\"bar|bar-horizontal|line|pie\"]\n                                            says which form this is. A data\n                                            attribute, not a modifier class: for\n                                            three of the four it changes nothing\n                                            visually — the SVG inside is what\n                                            differs — and a modifier that draws\n                                            nothing is a promise the stylesheet\n                                            does not keep\n     [data-view=\"chart|table\"]              chart, or the optional table view\n     __header  __title  __subtitle  __toggle\n     __legend  __legend-item  __legend-swatch  __legend-label\n     __plot                                 the SVG box\n     __grid-line  __axis  __axis-label      chrome, recessive\n     __bar  __line  __area  __marker  __slice\n     __value                                a direct label. Selective, never\n                                            one per point\n     __tooltip  __tooltip-title  __tooltip-row  __tooltip-swatch\n     __tooltip-label  __tooltip-value\n     __table                                optional. The same numbers as a\n                                            table, for screens that want one\n     __empty\n   ========================================================================== */\n\n.atom-chart {\n  display: flex;\n  flex-direction: column;\n  gap: var(--atom-x4);\n  min-width: 0;\n  color: var(--atom-fg-neutral);\n}\n\n/* --- header --------------------------------------------------------------- */\n.atom-chart__header {\n  display: flex;\n  align-items: flex-start;\n  justify-content: space-between;\n  gap: var(--atom-x4);\n}\n\n/* 15px, not a heading size. The chart sits under a Section Title most of the\n   time, and two headings stacked read as two sections. */\n.atom-chart__title {\n  margin: 0;\n  font-size: 15px;\n  line-height: 1.5;\n  font-weight: 500;\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chart__subtitle {\n  margin: 0;\n  font-size: 13px;\n  line-height: 1.5;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-chart__toggle {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  flex-shrink: 0;\n}\n\n/* --- legend --------------------------------------------------------------- */\n/* Always present from two series up, and absent for one — with a single colour\n   the title already names what is plotted, and a one-swatch box restates it.\n   This is the dependable identity channel; the direct labels on the marks\n   supplement it rather than replace it. */\n.atom-chart__legend {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n  gap: var(--atom-x2) var(--atom-x4);\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n\n.atom-chart__legend-item {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  font-size: 13px;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* A long series name wraps rather than pushing its own swatch off the row. */\n.atom-chart__legend-label {\n  min-width: 0;\n}\n\n/* The swatch carries the identity so the label can stay in text ink. A legend\n   label painted in the series colour is illegible at slot 4 and reads as a link\n   everywhere else. */\n.atom-chart__legend-swatch {\n  width: 10px;\n  height: 10px;\n  flex-shrink: 0;\n  border-radius: 3px;\n  background: var(--atom-chart-series-1);\n}\n\n/* --- plot ---------------------------------------------------------------- */\n/* The box grows with its content. A fixed height that fits the plot but not the\n   x-axis band is how a chart card ends up with its own tiny scrollbar. */\n.atom-chart__plot {\n  position: relative;\n  min-width: 0;\n}\n\n.atom-chart__plot svg {\n  display: block;\n  width: 100%;\n  height: auto;\n  overflow: visible;\n}\n\n/* A pie has no axis band to fill, so its plot centres instead of stretching.\n   This is the one place the form changes the frame. */\n.atom-chart[data-type=\"pie\"] .atom-chart__plot {\n  display: flex;\n  justify-content: center;\n}\n\n.atom-chart[data-type=\"pie\"] .atom-chart__plot svg {\n  width: auto;\n  max-width: 100%;\n  max-height: 260px;\n}\n\n/* --- chrome -------------------------------------------------------------- */\n/* Hairline and solid. Dashing a gridline adds ink that isn't data and makes the\n   reader ask whether the line means a threshold. */\n.atom-chart__grid-line {\n  stroke: var(--atom-border-subtle);\n  stroke-width: 1;\n}\n\n.atom-chart__axis {\n  stroke: var(--atom-border-default);\n  stroke-width: 1;\n}\n\n.atom-chart__axis-label {\n  fill: var(--atom-fg-neutral-muted);\n  font-size: 11px;\n  font-variant-numeric: tabular-nums;\n}\n\n/* --- marks --------------------------------------------------------------- */\n/* `fill` and `stroke` both read the slot, so one attribute paints a bar, a line\n   or a slice. `--atom-chart-ink` is internal plumbing, not a token: it exists so\n   the six mark rules below can be written once instead of six times. */\n[data-series=\"1\"] { --atom-chart-ink: var(--atom-chart-series-1); }\n[data-series=\"2\"] { --atom-chart-ink: var(--atom-chart-series-2); }\n[data-series=\"3\"] { --atom-chart-ink: var(--atom-chart-series-3); }\n[data-series=\"4\"] { --atom-chart-ink: var(--atom-chart-series-4); }\n[data-series=\"5\"] { --atom-chart-ink: var(--atom-chart-series-5); }\n[data-series=\"6\"] { --atom-chart-ink: var(--atom-chart-series-6); }\n\n.atom-chart__legend-swatch[data-series] {\n  background: var(--atom-chart-ink);\n}\n\n.atom-chart__bar,\n.atom-chart__slice {\n  fill: var(--atom-chart-ink);\n}\n\n.atom-chart__line {\n  fill: none;\n  stroke: var(--atom-chart-ink);\n  stroke-width: 2;\n  stroke-linejoin: round;\n  stroke-linecap: round;\n}\n\n/* A wash, not a block. Only ever under a single series — two translucent areas\n   stacked stop being either colour. */\n.atom-chart__area {\n  fill: var(--atom-chart-ink);\n  opacity: 0.1;\n}\n\n/* r=4 is the 8px floor. The ring is the surface colour, so a marker stays\n   legible where it crosses its own line or a neighbour's, and it counts toward\n   the hit target rather than just spacing. */\n.atom-chart__marker {\n  fill: var(--atom-chart-ink);\n  stroke: var(--atom-bg-layer);\n  stroke-width: 2;\n}\n\n/* Values wear text ink, never the series colour. The mark beside them carries\n   identity. */\n.atom-chart__value {\n  fill: var(--atom-fg-neutral);\n  font-size: 11px;\n  font-weight: 500;\n  font-variant-numeric: tabular-nums;\n}\n\n/* The one exception: a label set inside a filled segment picks white or ink by\n   the fill's luminance, because the surface is no longer behind it. The pick is\n   measured, not guessed — see --atom-chart-on-fill-ink in tokens.css — and it is\n   per slot, so an in-fill label carries its own data-series to know which. */\n.atom-chart__value--on-fill {\n  fill: var(--atom-fg-on-solid);\n}\n\n.atom-chart__value--on-fill[data-series=\"2\"],\n.atom-chart__value--on-fill[data-series=\"3\"],\n.atom-chart__value--on-fill[data-series=\"4\"] {\n  fill: var(--atom-chart-on-fill-ink);\n}\n\n/* --- tooltip ------------------------------------------------------------- */\n/* Enhances, never gates. Every value here is also in the table view, so a\n   reader who cannot hover is not locked out. */\n.atom-chart__tooltip {\n  position: absolute;\n  z-index: 10;\n  min-width: 132px;\n  padding: var(--atom-x2) var(--atom-x3);\n  border-radius: var(--atom-radius-r2);\n  background: var(--atom-bg-tooltip);\n  box-shadow: var(--atom-shadow-s2);\n  color: var(--atom-fg-tooltip);\n  font-size: 12px;\n  pointer-events: none;\n}\n\n.atom-chart__tooltip[data-state=\"closed\"] {\n  display: none;\n}\n\n.atom-chart__tooltip-title {\n  margin-bottom: 4px;\n  font-weight: 600;\n}\n\n.atom-chart__tooltip-row {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n}\n\n.atom-chart__tooltip-swatch {\n  width: 8px;\n  height: 8px;\n  flex-shrink: 0;\n  border-radius: 2px;\n  background: var(--atom-chart-ink);\n}\n\n.atom-chart__tooltip-label {\n  flex: 1;\n  min-width: 0;\n}\n\n.atom-chart__tooltip-value {\n  font-variant-numeric: tabular-nums;\n  font-weight: 600;\n}\n\n/* --- crosshair ----------------------------------------------------------- */\n/* Line and area only. On a bar or a slice the mark itself is the hit target, so\n   a vertical rule would just be another line to read. */\n.atom-chart__crosshair {\n  stroke: var(--atom-border-default);\n  stroke-width: 1;\n}\n\n/* --- table view ---------------------------------------------------------- */\n/* The twin, not a fallback. It holds the same numbers the plot draws, which is\n   what lets the direct labels stay sparing and the tooltip stay an enhancement. */\n.atom-chart__table {\n  overflow-x: auto;\n}\n\n.atom-chart[data-view=\"chart\"] .atom-chart__table,\n.atom-chart[data-view=\"table\"] .atom-chart__plot,\n.atom-chart[data-view=\"table\"] .atom-chart__legend {\n  display: none;\n}\n\n.atom-chart__table table {\n  width: 100%;\n  border-collapse: collapse;\n  font-size: 13px;\n}\n\n.atom-chart__table th,\n.atom-chart__table td {\n  padding: var(--atom-x2) var(--atom-x3);\n  border-bottom: 1px solid var(--atom-border-subtle);\n  text-align: right;\n  font-variant-numeric: tabular-nums;\n}\n\n.atom-chart__table th:first-child,\n.atom-chart__table td:first-child {\n  text-align: left;\n  font-variant-numeric: normal;\n}\n\n.atom-chart__table thead th {\n  font-weight: 600;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* --- empty --------------------------------------------------------------- */\n.atom-chart__empty {\n  display: grid;\n  place-items: center;\n  min-height: 160px;\n  font-size: 14px;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n/* --- forced colors ------------------------------------------------------- */\n/* Where hue is taken away, the legend and the table are what is left. Marks get\n   a system outline so the shapes still read. */\n@media (forced-colors: active) {\n  .atom-chart__bar,\n  .atom-chart__slice {\n    stroke: CanvasText;\n    stroke-width: 1;\n  }\n\n  .atom-chart__line {\n    stroke: CanvasText;\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/chart.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/chip.css",
      "content": "/* ============================================================================\n   Element — Chip\n   ----------------------------------------------------------------------------\n   One pill, four behaviours. What changes between them is not the look but the\n   contract: whether the chip holds a state at all, and if so whether that state\n   is exclusive within its group.\n\n     button     상태 없음. 누르면 액션이 일어난다\n     toggle     혼자서 켜고 끈다\n     radio      그룹 안에서 하나만\n     checkbox   그룹 안에서 여러 개\n\n   So `--type_*` is a documentation marker for the author, and the real\n   difference lives in the markup each type demands — see the Specification\n   table. The selected treatment is shared: whatever the unselected variant was,\n   a selected chip fills solid.\n\n   atom-chip\n     --type_{button|toggle|radio|checkbox}   button is the default\n     --variant_{neutral-weak|neutral-outline|neutral-solid}\n                                             neutral-weak is the default\n     --size_{small|medium}                   medium is the default\n     [data-selected]  [aria-pressed=\"true\"]  __input:checked\n     [disabled]  [aria-disabled=\"true\"]\n     __input     visually-hidden radio/checkbox behind a <label>\n     __prefix    leading icon\n     __label\n     __check     선택 표시. 넣으면 선택됐을 때만 보인다\n     __caret     메뉴를 여는 button 타입에서\n     __count     뒤에 붙는 개수\n\n   atom-chip-group  (+ --wrap  --scroll)\n   ========================================================================== */\n\n.atom-chip {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  height: 33px;\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n  line-height: 1;\n  white-space: nowrap;\n  cursor: pointer;\n  user-select: none;\n  transition: background-color var(--atom-duration-fast) ease,\n    box-shadow var(--atom-duration-fast) ease,\n    color var(--atom-duration-fast) ease;\n}\n\n/* radio and checkbox chips are a real input behind a label, so the control is\n   removed from view but not from the accessibility tree or the tab order. */\n.atom-chip__input {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  opacity: 0;\n  pointer-events: none;\n}\n\n.atom-chip__label {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.atom-chip__prefix {\n  width: 14px;\n  height: 14px;\n  flex-shrink: 0;\n  margin-inline-start: -2px;\n}\n\n.atom-chip__caret {\n  width: 12px;\n  height: 12px;\n  flex-shrink: 0;\n  margin-inline-end: -2px;\n  opacity: 0.7;\n  transition: transform var(--atom-duration-base) var(--atom-ease-out);\n}\n\n/* The check is opt-in and costs width, so it collapses instead of merely fading\n   — a row of chips that reserves space for a mark none of them shows reads as\n   badly aligned rather than as unselected. */\n.atom-chip__check {\n  width: 0;\n  height: 14px;\n  flex-shrink: 0;\n  opacity: 0;\n  transition: width var(--atom-duration-fast) ease,\n    opacity var(--atom-duration-fast) ease;\n}\n\n.atom-chip__count {\n  flex-shrink: 0;\n  font-variant-numeric: tabular-nums;\n  opacity: 0.7;\n}\n\n/* --- types — guardrails, not decoration ---------------------------------- */\n/* These modifiers mostly document which contract a chip follows; the real\n   difference is the markup each type demands. The two rules below are the\n   exceptions, and both exist to stop wrong markup from shipping a wrong signal.\n\n   A button chip holds no state, so a check on one can only mislead. */\n.atom-chip--type_button .atom-chip__check {\n  display: none;\n}\n\n/* A caret promises \"this opens a menu\". The stateful three change a value in\n   place, so a caret on them is a promise the chip does not keep. */\n.atom-chip--type_toggle .atom-chip__caret,\n.atom-chip--type_radio .atom-chip__caret,\n.atom-chip--type_checkbox .atom-chip__caret {\n  display: none;\n}\n\n/* --- variants — the unselected look -------------------------------------- */\n.atom-chip,\n.atom-chip--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chip--variant_neutral-outline {\n  background: transparent;\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chip--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n  font-weight: 600;\n}\n\n/* --- selected — shared by toggle, radio and checkbox ---------------------- */\n/* One rule for three types: whatever the chip looked like unselected, selecting\n   it fills solid. Fill is the only difference a chip can carry that survives\n   being read at a glance in a scrolling row. */\n.atom-chip[data-selected],\n.atom-chip[aria-pressed=\"true\"],\n.atom-chip:has(.atom-chip__input:checked) {\n  background: var(--atom-bg-neutral-solid);\n  box-shadow: none;\n  color: var(--atom-fg-on-solid);\n  font-weight: 600;\n}\n\n.atom-chip[data-selected] .atom-chip__check,\n.atom-chip[aria-pressed=\"true\"] .atom-chip__check,\n.atom-chip:has(.atom-chip__input:checked) .atom-chip__check {\n  width: 14px;\n  opacity: 1;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-chip--size_small {\n  height: 28px;\n  padding-inline: var(--atom-x2);\n  font-size: 12px;\n}\n\n.atom-chip--size_small .atom-chip__prefix,\n.atom-chip--size_small .atom-chip__check {\n  height: 12px;\n}\n\n.atom-chip--size_small[data-selected] .atom-chip__check,\n.atom-chip--size_small[aria-pressed=\"true\"] .atom-chip__check,\n.atom-chip--size_small:has(.atom-chip__input:checked) .atom-chip__check {\n  width: 12px;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-chip:hover:not([disabled]):not([aria-disabled=\"true\"]) {\n  filter: brightness(0.96);\n}\n\n.atom-chip[data-state=\"open\"] .atom-chip__caret {\n  transform: rotate(180deg);\n}\n\n.atom-chip:focus-visible,\n.atom-chip:has(.atom-chip__input:focus-visible) {\n  outline: 2px solid var(--atom-fg-brand);\n  outline-offset: 2px;\n}\n\n.atom-chip[disabled],\n.atom-chip[aria-disabled=\"true\"] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* --- group --------------------------------------------------------------- */\n.atom-chip-group {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  min-width: 0;\n}\n\n.atom-chip-group--wrap {\n  flex-wrap: wrap;\n  row-gap: var(--atom-x2);\n}\n\n/* A single-line rail. The row keeps its height when it scrolls, which a wrapping\n   group cannot promise, so filter bars pinned under a header use this one. */\n.atom-chip-group--scroll {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n  scrollbar-width: none;\n}\n\n.atom-chip-group--scroll::-webkit-scrollbar {\n  display: none;\n}\n\n.atom-chip-group--scroll > .atom-chip {\n  flex-shrink: 0;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/chip.css"
    }
  ],
  "categories": [
    "display"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "component",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "Chart"
    ],
    "docs": "poc/design-system/components/chart.md"
  }
}
