{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "audio-visualizer",
  "type": "registry:component",
  "title": "AudioVisualizer",
  "description": "Levels-driven realtime audio visualizer offered in two usage versions - bars and line - sharing one export and one props contract.",
  "dependencies": [],
  "registryDependencies": [
    "https://control-ui.dev/r/core.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/audio-visualizer-knobs.ts",
      "target": "@components/control-ui/knob-contracts/audio-visualizer-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/audio-visualizer.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const audioVisualizerKnobs = [\n  \"--cui-audio-visualizer-radius\",\n  \"--cui-audio-visualizer-bar-background\",\n  \"--cui-audio-visualizer-line-fill\",\n  \"--cui-audio-visualizer-line-stroke\",\n] as const;\nexport type AudioVisualizerKnobStyle = Partial<Record<(typeof audioVisualizerKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/audio-visualizer.tsx",
      "target": "@components/control-ui/audio-visualizer.tsx",
      "type": "registry:component",
      "content": "import type { ComponentProps, CSSProperties } from \"react\";\nimport type { AudioVisualizerKnobStyle } from \"@/components/control-ui/knob-contracts/audio-visualizer-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\n\nexport type AudioVisualizerProps = Omit<ComponentProps<\"div\">, \"children\" | \"style\"> & {\n  levels: readonly number[];\n  active?: boolean;\n  points?: number;\n  style?: CSSProperties & AudioVisualizerKnobStyle;\n};\n\n// Bars reading. audio-visualizer-line.tsx exports same `AudioVisualizer` on same props, so swapping is import-path change.\n// Runtime-agnostic — feed it any rolling 0..1 window.\n\ntype AudioVisualizerLevelStyle = CSSProperties & Record<\"--_audio-visualizer-level-opacity\", string>;\n\nconst DEFAULT_POINTS = 28;\nconst MAX_POINTS = 128;\nconst MIN_VISIBLE_LEVEL = 0.12;\n\n// local so either visualizer installs as complete, independent choice\nfunction resolveAudioVisualizerLevels(levels: readonly number[], points?: number) {\n  const requestedPoints = Number.isFinite(points) ? Math.floor(points ?? DEFAULT_POINTS) : DEFAULT_POINTS;\n  const pointCount = Math.min(MAX_POINTS, Math.max(1, requestedPoints));\n  const visible = levels.slice(-pointCount).map((level) => (Number.isFinite(level) ? Math.min(1, Math.max(0, level)) : 0));\n  const padded = visible.length === pointCount ? visible : [...new Array<number>(pointCount - visible.length).fill(0), ...visible];\n  return padded.map((level, position) => ({ key: `bar-${position}`, level }));\n}\n\nexport function AudioVisualizer({ levels, points, active = true, className, style, ...props }: AudioVisualizerProps) {\n  const visible = resolveAudioVisualizerLevels(levels, points);\n\n  return (\n    <div\n      data-control-ui=\"audio-visualizer\"\n      data-control-family=\"audio-visualizer\"\n      data-slot=\"root\"\n      data-variant=\"bars\"\n      data-active={active ? \"true\" : undefined}\n      aria-hidden=\"true\"\n      className={cn(\"h-7 w-44 shrink-0 overflow-hidden px-1.5 py-1.5\", className)}\n      style={style}\n      {...props}\n    >\n      <span\n        data-control-ui=\"audio-visualizer\"\n        data-control-family=\"audio-visualizer\"\n        data-slot=\"track\"\n        data-active={active ? \"true\" : undefined}\n        className=\"flex size-full items-stretch justify-end gap-px mask-l-from-90%\"\n      >\n        {visible.map(({ key, level }) => {\n          const perceptualLevel = Math.sqrt(level);\n          const visibleLevel = MIN_VISIBLE_LEVEL + perceptualLevel * (1 - MIN_VISIBLE_LEVEL);\n          const levelStyle: AudioVisualizerLevelStyle = {\n            \"--_audio-visualizer-level-opacity\": `${0.48 + perceptualLevel * 0.52}`,\n            clipPath: `inset(${(1 - visibleLevel) * 50}% 0 round var(--radius-control))`,\n          };\n\n          return (\n            <span\n              key={key}\n              data-control-ui=\"audio-visualizer\"\n              data-control-family=\"audio-visualizer\"\n              data-slot=\"bar-track\"\n              className=\"flex w-1 shrink-0 items-center\"\n            >\n              <span\n                data-control-ui=\"audio-visualizer\"\n                data-control-family=\"audio-visualizer\"\n                data-slot=\"bar\"\n                className=\"block h-full w-full\"\n                style={levelStyle}\n              />\n            </span>\n          );\n        })}\n      </span>\n    </div>\n  );\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/audio-visualizer.css",
      "target": "@components/control-ui/styles/recipes/audio-visualizer.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"audio-visualizer\"][data-variant]) {\n    --cui-audio-visualizer-radius: var(--radius-control);\n    --cui-audio-visualizer-bar-background: oklch(from var(--primary) l c h / 0.8);\n    --cui-audio-visualizer-line-fill: oklch(from var(--primary) l c h / 0.18);\n    --cui-audio-visualizer-line-stroke: oklch(from var(--primary) l c h / 0.8);\n  }\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"root\"][data-variant]) {\n    --_audio-visualizer-bar-track-filter: none;\n    border-radius: var(--cui-audio-visualizer-radius);\n  }\n\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"track\"]) {\n    opacity: 0.55;\n    transition: opacity var(--duration-base) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"track\"][data-active=\"true\"]) {\n    opacity: 1;\n  }\n\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"bar-track\"]) {\n    transition:\n      opacity var(--duration-base) var(--ease-emphasized),\n      filter var(--duration-base) var(--ease-emphasized),\n      translate var(--duration-base) var(--ease-emphasized);\n    filter: var(--_audio-visualizer-bar-track-filter);\n  }\n\n  @starting-style {\n    :where([data-control-family=\"audio-visualizer\"][data-slot=\"bar-track\"]) {\n      opacity: 0;\n      --_audio-visualizer-bar-track-filter: blur(var(--blur-xs));\n      filter: var(--_audio-visualizer-bar-track-filter);\n      translate: var(--spacing) 0;\n    }\n  }\n\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"bar\"]) {\n    background: var(--cui-audio-visualizer-bar-background);\n    border-radius: var(--radius-control);\n    opacity: var(--_audio-visualizer-level-opacity);\n    transition:\n      clip-path var(--duration-base) var(--ease-standard),\n      opacity var(--duration-base) var(--ease-standard);\n  }\n\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"baseline\"]) {\n    stroke: oklch(from var(--border) l c h / 0.55);\n  }\n\n  :where([data-control-family=\"audio-visualizer\"][data-slot=\"waveform\"]) {\n    fill: var(--cui-audio-visualizer-line-fill);\n    stroke: var(--cui-audio-visualizer-line-stroke);\n    transition: d var(--duration-base) var(--ease-standard);\n  }\n}\n\n@property --cui-audio-visualizer-radius {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-audio-visualizer-bar-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-audio-visualizer-line-fill {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-audio-visualizer-line-stroke {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/audio-visualizer.css\"": {}
  },
  "meta": {}
}
