{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sleep-score-card",
  "title": "Sleep Score",
  "description": "Segmented radial score with a qualitative band and metric rows.",
  "registryDependencies": [
    "https://components.exe.xyz/r/chart-card.json",
    "https://components.exe.xyz/r/chip.json",
    "https://components.exe.xyz/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/charts/sleep-score-card.tsx",
      "content": "import { useState } from \"react\"\n\nimport { Chip } from \"@/components/ui/chip\"\nimport {\n  ChartCard,\n  chartColor,\n  describeArc,\n} from \"@/components/charts/chart-card\"\nimport { useCountUp } from \"@/lib/use-count-up\"\nimport { cn } from \"@/lib/utils\"\n\nexport type SleepMetric = {\n  id: string\n  label: string\n  detail: string\n  earned: number\n  max: number\n}\n\nconst DEFAULT_METRICS: SleepMetric[] = [\n  {\n    id: \"duration\",\n    label: \"Duration\",\n    detail: \"7 hours 50 minutes\",\n    earned: 49,\n    max: 50,\n  },\n  {\n    id: \"bedtime\",\n    label: \"Bedtime\",\n    detail: \"20 minutes earlier\",\n    earned: 29,\n    max: 30,\n  },\n  {\n    id: \"interruptions\",\n    label: \"Interruptions\",\n    detail: \"5 minutes wake up\",\n    earned: 20,\n    max: 20,\n  },\n]\n\nconst GAP = 10\nconst CX = 120\nconst CY = 120\nconst RADIUS = 78\nconst TRACK = 16\n\nexport function SleepScoreCard({\n  title = \"Sleep score\",\n  score = 98,\n  band = \"Excellent\",\n  rangeLabel = \"29 Jun–5 Jul\",\n  metrics = DEFAULT_METRICS,\n  className,\n}: {\n  title?: string\n  score?: number\n  band?: string\n  rangeLabel?: string\n  metrics?: SleepMetric[]\n  className?: string\n}) {\n  const [activeId, setActiveId] = useState<string | null>(null)\n  const displayed = useCountUp(score)\n  const totalMax = metrics.reduce((sum, row) => sum + row.max, 0) || 1\n  const sweepBudget = 360 - GAP * metrics.length\n  const summary = `Sleep score ${score}, ${band}`\n\n  let cursor = 0\n  const arcs = metrics.map((metric, index) => {\n    const sweep = (metric.max / totalMax) * sweepBudget\n    const earnedSweep =\n      metric.max === 0 ? 0 : (metric.earned / metric.max) * sweep\n    const start = cursor\n    cursor += sweep + GAP\n    return {\n      ...metric,\n      start,\n      sweep,\n      earnedSweep,\n      color: chartColor(index),\n    }\n  })\n\n  return (\n    <ChartCard\n      title={title}\n      period={rangeLabel}\n      summary={summary}\n      className={className}\n    >\n      <div className=\"relative mx-auto h-52 w-52\">\n        <svg\n          role=\"img\"\n          aria-label={summary}\n          viewBox=\"0 0 240 240\"\n          className=\"h-full w-full\"\n        >\n          {arcs.map((arc) => {\n            const dimmed = activeId != null && activeId !== arc.id\n            const active = activeId === arc.id\n            const width = active ? TRACK + 4 : TRACK\n            return (\n              <g\n                key={arc.id}\n                opacity={dimmed ? 0.28 : 1}\n                className=\"cursor-pointer\"\n                onMouseEnter={() => setActiveId(arc.id)}\n                onMouseLeave={() => setActiveId(null)}\n              >\n                <path\n                  d={describeArc(\n                    CX,\n                    CY,\n                    RADIUS,\n                    arc.start,\n                    arc.start + arc.sweep\n                  )}\n                  fill=\"none\"\n                  stroke=\"var(--muted)\"\n                  strokeWidth={width}\n                  strokeLinecap=\"butt\"\n                />\n                <path\n                  d={describeArc(\n                    CX,\n                    CY,\n                    RADIUS,\n                    arc.start,\n                    arc.start + Math.max(arc.earnedSweep, 0.01)\n                  )}\n                  fill=\"none\"\n                  stroke={arc.color}\n                  strokeWidth={width}\n                  strokeLinecap=\"butt\"\n                />\n              </g>\n            )\n          })}\n        </svg>\n        <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-1\">\n          <p\n            className=\"font-heading text-4xl font-semibold tracking-tight tabular-nums\"\n            aria-hidden=\"true\"\n          >\n            {displayed}\n          </p>\n          <Chip\n            variant=\"outline\"\n            className=\"h-5 border-transparent bg-[color-mix(in_srgb,var(--chart-2)_18%,transparent)] px-1.5 text-[var(--chart-2)]\"\n          >\n            {band}\n          </Chip>\n        </div>\n        <span className=\"sr-only\">{summary}</span>\n      </div>\n      <ul className=\"-mx-3 mt-2 -mb-3 flex flex-col border-t border-border\">\n        {metrics.map((metric, index) => {\n          const dimmed = activeId != null && activeId !== metric.id\n          return (\n            <li\n              key={metric.id}\n              className=\"border-b border-border last:border-b-0\"\n            >\n              <button\n                type=\"button\"\n                aria-pressed={activeId === metric.id}\n                aria-label={`${metric.label} ${metric.detail}, ${metric.earned} of ${metric.max}`}\n                className={cn(\n                  \"flex w-full items-center justify-between gap-3 px-4 py-2.5 text-left transition-opacity outline-none\",\n                  \"focus-visible:ring-2 focus-visible:ring-ring/50\",\n                  dimmed && \"opacity-40\"\n                )}\n                onMouseEnter={() => setActiveId(metric.id)}\n                onMouseLeave={() => setActiveId(null)}\n                onFocus={() => setActiveId(metric.id)}\n                onBlur={() => setActiveId(null)}\n              >\n                <span className=\"flex min-w-0 items-start gap-2\">\n                  <span\n                    className=\"mt-1.5 size-1.5 shrink-0 rounded-[1px]\"\n                    style={{ background: chartColor(index) }}\n                  />\n                  <span>\n                    <span className=\"block text-sm\">{metric.label}</span>\n                    <span className=\"font-mono text-[11px] text-muted-foreground\">\n                      {metric.detail}\n                    </span>\n                  </span>\n                </span>\n                <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums\">\n                  {metric.earned}/{metric.max}\n                </span>\n              </button>\n            </li>\n          )\n        })}\n      </ul>\n    </ChartCard>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}