{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "volume-control",
  "title": "Volume Control",
  "description": "A composable volume control with mute restore, perceptual or linear curves, horizontal and vertical sliders, and accessible level text.",
  "dependencies": [
    "solid-js@^1.9.15",
    "@kobalte/core@https://github.com/jhomra21/kobalte/releases/download/kobalte-solid1-audiocn-e0e3bf095f05c7e61230a251b79181c6d834d408/kobalte-core-0.13.14-audiocn.0.e0e3bf09.tgz"
  ],
  "registryDependencies": [
    "@audiocn-solid/core",
    "@audiocn-solid/use-audio-config"
  ],
  "files": [
    {
      "path": "components/ui/volume-control.tsx",
      "content": "import * as SliderPrimitive from \"@kobalte/core/slider\";\nimport { createContext, createMemo, createSignal, useContext } from \"solid-js\";\n\nimport { useAudioConfig } from \"@/hooks/use-audio-config\";\nimport type { AudioSize } from \"@/hooks/use-audio-config\";\nimport { clamp } from \"@/lib/audio/decibels\";\nimport type { Orientation } from \"@/lib/audio/types\";\nimport { roundValue } from \"@/lib/number\";\nimport { provideContext } from \"@/lib/solid/context\";\nimport { createCompatEffect } from \"@/lib/solid/effect\";\nimport type {\n  ButtonDOMProps,\n  DivDOMProps,\n  SpanDOMProps,\n} from \"@/lib/solid/jsx-types\";\nimport { useKobalteSliderCompat } from \"@/lib/solid/kobalte-slider\";\nimport { omitProps } from \"@/lib/solid/props\";\nimport { cn } from \"@/lib/utils\";\n\nconst PERCENT = 100;\n\nconst LOW_LEVEL = 0.34;\n\nconst MEDIUM_LEVEL = 0.67;\n\nconst PERCEPTUAL_EXPONENT = 2;\n\nexport type VolumeLevel = \"muted\" | \"low\" | \"medium\" | \"high\";\n\nconst curves = {\n  linear: {\n    toPosition: (volume: number) => volume,\n    toVolume: (position: number) => position,\n  },\n  perceptual: {\n    toPosition: (volume: number) => volume ** (1 / PERCEPTUAL_EXPONENT),\n    toVolume: (position: number) => position ** PERCEPTUAL_EXPONENT,\n  },\n} as const;\n\ninterface VolumeControlContextValue {\n  commit: (volume: number) => void;\n  disabled: () => boolean;\n  handleKeyDown: (event: KeyboardEvent) => void;\n  level: () => VolumeLevel;\n  muted: () => boolean;\n  orientation: () => Orientation;\n  position: () => number;\n  setPosition: (position: number) => number;\n  shownPosition: () => number;\n  step: () => number;\n  toggleMuted: () => void;\n  volume: () => number;\n}\n\nconst VolumeControlContext = createContext<VolumeControlContextValue | null>(\n  null\n);\n\nconst useVolumeControl = (part: string): VolumeControlContextValue => {\n  const context = useContext(VolumeControlContext);\n\n  if (!context) {\n    throw new Error(`${part} must be used inside VolumeControl.`);\n  }\n\n  return context;\n};\n\nconst levelFor = (volume: number, muted: boolean): VolumeLevel => {\n  if (muted || volume <= 0) {\n    return \"muted\";\n  }\n\n  if (volume < LOW_LEVEL) {\n    return \"low\";\n  }\n\n  if (volume < MEDIUM_LEVEL) {\n    return \"medium\";\n  }\n\n  return \"high\";\n};\n\ninterface VolumeSliderCompatProps {\n  changeFromPointer: (position: number) => number;\n  commitPointer: (volume: number) => void;\n  currentPosition: () => number;\n  orientation: () => Orientation;\n}\n\nconst VolumeSliderCompat = (props: VolumeSliderCompatProps) => {\n  const context = SliderPrimitive.useSliderContext();\n\n  let dragPosition = props.currentPosition();\n\n  let pointerVolume = 0;\n\n  useKobalteSliderCompat(context, {\n    onSlideEnd: (originalSlideEnd) => {\n      originalSlideEnd?.();\n      props.commitPointer(pointerVolume);\n    },\n    onSlideMove: (originalSlideMove, { deltaX, deltaY }) => {\n      if (context.state.isDisabled()) {\n        return;\n      }\n\n      const track = context.trackRef();\n\n      if (!track) {\n        originalSlideMove?.({ deltaX, deltaY });\n\n        return;\n      }\n\n      const rect = track.getBoundingClientRect();\n      const horizontal = props.orientation() === \"horizontal\";\n      const size = horizontal ? rect.width : rect.height;\n\n      if (size <= 0) {\n        return;\n      }\n\n      const delta = horizontal ? deltaX : deltaY;\n\n      const direction = horizontal\n        ? context.isSlidingFromLeft()\n          ? 1\n          : -1\n        : context.isSlidingFromBottom()\n          ? -1\n          : 1;\n\n      dragPosition = clamp(dragPosition + direction * (delta / size), 0, 1);\n      pointerVolume = props.changeFromPointer(dragPosition);\n    },\n    onSlideStart: (originalSlideStart, index, position) => {\n      originalSlideStart?.(index, position);\n\n      if (context.state.isDisabled()) {\n        return;\n      }\n\n      dragPosition = clamp(position, 0, 1);\n      pointerVolume = props.changeFromPointer(dragPosition);\n    },\n    suppressStep: true,\n  });\n\n  return null;\n};\n\nexport interface VolumeControlProps extends Omit<\n  DivDOMProps,\n  \"children\" | \"class\" | \"className\" | \"onChange\"\n> {\n  children?: DivDOMProps[\"children\"];\n  class?: string;\n  className?: string;\n  curve?: \"linear\" | \"perceptual\";\n  defaultMuted?: boolean;\n  defaultValue?: number;\n  disabled?: boolean;\n  muted?: boolean;\n  onMutedChange?: (muted: boolean) => void;\n  onValueChange?: (value: number) => void;\n  onValueCommitted?: (value: number) => void;\n  orientation?: Orientation;\n  size?: AudioSize;\n  step?: number;\n  value?: number;\n}\n\nconst VOLUME_CONTROL_OWN = [\n  \"children\",\n  \"class\",\n  \"className\",\n  \"curve\",\n  \"defaultMuted\",\n  \"defaultValue\",\n  \"disabled\",\n  \"muted\",\n  \"onMutedChange\",\n  \"onValueChange\",\n  \"onValueCommitted\",\n  \"orientation\",\n  \"size\",\n  \"step\",\n  \"value\",\n] as const;\n\nexport const VolumeControl = (props: VolumeControlProps) => {\n  const rest = omitProps(props, VOLUME_CONTROL_OWN);\n\n  const config = useAudioConfig();\n\n  const orientation = () => props.orientation ?? \"horizontal\";\n\n  const size = () => props.size ?? config.size ?? \"default\";\n\n  const disabled = () => props.disabled ?? config.disabled ?? false;\n\n  const step = () => props.step ?? 0.05;\n\n  const mapping = () => curves[props.curve ?? \"perceptual\"];\n\n  const [uncontrolledVolume, setUncontrolledVolume] = createSignal(\n    clamp(props.defaultValue ?? 1, 0, 1)\n  );\n\n  const [uncontrolledMuted, setUncontrolledMuted] = createSignal(\n    props.defaultMuted ?? false\n  );\n\n  const volume = () => props.value ?? uncontrolledVolume();\n\n  const muted = () => props.muted ?? uncontrolledMuted();\n\n  let lastAudible = volume() > 0 ? volume() : 1;\n\n  createCompatEffect(volume, (next) => {\n    if (next > 0) {\n      lastAudible = next;\n    }\n  });\n\n  const setVolume = (next: number): number => {\n    const clamped = roundValue(clamp(next, 0, 1));\n\n    if (clamped > 0) {\n      lastAudible = clamped;\n    }\n\n    if (props.value === undefined) {\n      setUncontrolledVolume(clamped);\n    }\n\n    props.onValueChange?.(clamped);\n\n    return clamped;\n  };\n\n  const setMuted = (next: boolean) => {\n    if (props.muted === undefined) {\n      setUncontrolledMuted(next);\n    }\n\n    props.onMutedChange?.(next);\n  };\n\n  const position = () => mapping().toPosition(volume());\n\n  const shownPosition = () => (muted() ? 0 : position());\n\n  const quantizePosition = (next: number): number => {\n    const increment = step();\n\n    return roundValue(clamp(Math.round(next / increment) * increment, 0, 1));\n  };\n\n  const setPosition = (next: number): number => {\n    const quantized = quantizePosition(next);\n\n    const changed = setVolume(mapping().toVolume(quantized));\n\n    if (muted() && quantized > 0) {\n      setMuted(false);\n    }\n\n    return changed;\n  };\n\n  const commit = (next: number) => {\n    props.onValueCommitted?.(next);\n  };\n\n  const toggleMuted = () => {\n    if (muted() && volume() <= 0) {\n      setVolume(lastAudible);\n    }\n\n    setMuted(!muted());\n  };\n\n  const pageStep = () => Math.max(step(), 0.1);\n\n  const handleKeyDown = (event: KeyboardEvent) => {\n    const current = shownPosition();\n\n    let next: number | undefined;\n\n    switch (event.key) {\n      case \"ArrowRight\":\n      case \"ArrowUp\": {\n        next = current + (event.shiftKey ? pageStep() : step());\n\n        break;\n      }\n\n      case \"ArrowLeft\":\n      case \"ArrowDown\": {\n        next = current - (event.shiftKey ? pageStep() : step());\n\n        break;\n      }\n\n      case \"PageUp\": {\n        next = current + pageStep();\n\n        break;\n      }\n\n      case \"PageDown\": {\n        next = current - pageStep();\n\n        break;\n      }\n\n      case \"Home\": {\n        next = 0;\n\n        break;\n      }\n\n      case \"End\": {\n        next = 1;\n\n        break;\n      }\n\n      default: {\n        return;\n      }\n    }\n\n    event.preventDefault();\n\n    const changed = setPosition(next);\n\n    commit(changed);\n  };\n\n  const level = () => levelFor(volume(), muted());\n\n  const context: VolumeControlContextValue = {\n    commit,\n    disabled,\n    handleKeyDown,\n    level,\n    muted,\n    orientation,\n    position,\n    setPosition,\n    shownPosition,\n    step,\n    toggleMuted,\n    volume,\n  };\n\n  return provideContext(VolumeControlContext, context, () => (\n    <div\n      aria-label=\"Volume\"\n      class={cn(\n        \"group/volume-control flex items-center gap-2 data-disabled:opacity-50\",\n        orientation() === \"vertical\" ? \"flex-col-reverse\" : undefined,\n        size() === \"sm\"\n          ? \"[--volume-thumb-size:0.75rem] [--volume-track-size:0.1875rem]\"\n          : undefined,\n        size() === \"default\"\n          ? \"[--volume-thumb-size:0.875rem] [--volume-track-size:0.25rem]\"\n          : undefined,\n        size() === \"lg\"\n          ? \"[--volume-thumb-size:1rem] [--volume-track-size:0.375rem]\"\n          : undefined,\n        props.class,\n        props.className\n      )}\n      data-disabled={disabled() ? \"\" : undefined}\n      data-level={level()}\n      data-muted={muted() ? \"\" : undefined}\n      data-orientation={orientation()}\n      data-slot=\"volume-control\"\n      role=\"group\"\n      {...rest}\n    >\n      {props.children ?? (\n        <>\n          <VolumeControlMute />\n          <VolumeControlSlider />\n        </>\n      )}\n    </div>\n  ));\n};\n\nexport interface VolumeControlMuteProps extends Omit<\n  ButtonDOMProps,\n  \"children\" | \"class\" | \"className\" | \"disabled\" | \"onClick\" | \"type\"\n> {\n  children?: ButtonDOMProps[\"children\"];\n  class?: string;\n  className?: string;\n  onClick?: (event: MouseEvent) => void;\n  type?: \"button\" | \"submit\" | \"reset\";\n}\n\nconst MUTE_OWN = [\"children\", \"class\", \"className\", \"onClick\", \"type\"] as const;\n\nexport const VolumeControlMute = (props: VolumeControlMuteProps) => {\n  const context = useVolumeControl(\"VolumeControlMute\");\n\n  const rest = omitProps(props, MUTE_OWN);\n\n  const label = () => (context.muted() ? \"Unmute\" : \"Mute\");\n\n  return (\n    <button\n      aria-label={label()}\n      aria-pressed={context.muted() ? \"true\" : \"false\"}\n      class={cn(\n        \"text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring/30 inline-flex size-7 shrink-0 items-center justify-center rounded-lg transition-colors outline-none focus-visible:ring-3 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4\",\n        props.children ? undefined : \"w-auto px-2\",\n        props.class,\n        props.className\n      )}\n      data-level={context.level()}\n      data-muted={context.muted() ? \"\" : undefined}\n      data-slot=\"volume-control-mute\"\n      disabled={context.disabled()}\n      onClick={(event) => {\n        props.onClick?.(event);\n\n        if (event.defaultPrevented) {\n          return;\n        }\n\n        context.toggleMuted();\n      }}\n      type={props.type ?? \"button\"}\n      {...rest}\n    >\n      {props.children ?? <span class=\"text-xs\">{label()}</span>}\n    </button>\n  );\n};\n\ntype SliderRootProps = Parameters<typeof SliderPrimitive.Root>[0];\n\nexport type VolumeControlSliderProps = Omit<\n  SliderRootProps,\n  | \"children\"\n  | \"class\"\n  | \"className\"\n  | \"defaultValue\"\n  | \"disabled\"\n  | \"getValueLabel\"\n  | \"maxValue\"\n  | \"minValue\"\n  | \"onChange\"\n  | \"onChangeEnd\"\n  | \"orientation\"\n  | \"step\"\n  | \"value\"\n> & {\n  class?: string;\n  className?: string;\n};\n\nconst SLIDER_OWN = [\"class\", \"className\"] as const;\n\nexport const VolumeControlSlider = (props: VolumeControlSliderProps) => {\n  const context = useVolumeControl(\"VolumeControlSlider\");\n\n  const rest = omitProps(props, SLIDER_OWN);\n\n  const horizontal = () => context.orientation() === \"horizontal\";\n\n  const sliderValue = createMemo(() => [context.shownPosition()]);\n\n  return (\n    <SliderPrimitive.Root\n      class={cn(\n        \"relative flex touch-none items-center select-none\",\n        horizontal() ? \"w-full min-w-20\" : \"h-24 flex-col\",\n        props.class,\n        props.className\n      )}\n      data-slot=\"volume-control-slider\"\n      disabled={context.disabled()}\n      getValueLabel={() =>\n        context.muted() ? \"Muted\" : `${Math.round(context.volume() * PERCENT)}%`\n      }\n      maxValue={1}\n      minValue={0}\n      onChange={() => undefined}\n      orientation={context.orientation()}\n      step={context.step()}\n      value={sliderValue()}\n      {...rest}\n    >\n      <VolumeSliderCompat\n        changeFromPointer={context.setPosition}\n        commitPointer={context.commit}\n        currentPosition={context.shownPosition}\n        orientation={context.orientation}\n      />\n\n      <SliderPrimitive.Track\n        class={cn(\n          \"relative flex items-center\",\n          horizontal()\n            ? \"h-(--volume-thumb-size) w-full px-[calc(var(--volume-thumb-size)/2)] before:absolute before:inset-x-0 before:-inset-y-1.5 pointer-coarse:before:-inset-y-3\"\n            : \"h-full w-(--volume-thumb-size) flex-col py-[calc(var(--volume-thumb-size)/2)] before:absolute before:-inset-x-1.5 before:inset-y-0 pointer-coarse:before:-inset-x-3\"\n        )}\n      >\n        <div\n          class={cn(\n            \"bg-input/90 relative grow rounded-full\",\n            horizontal()\n              ? \"h-(--volume-track-size) w-full\"\n              : \"h-full w-(--volume-track-size)\"\n          )}\n          data-slot=\"volume-control-track\"\n        >\n          <div\n            aria-hidden=\"true\"\n            class={cn(\n              \"bg-primary absolute rounded-full\",\n              horizontal() ? \"inset-y-0 left-0\" : \"inset-x-0 bottom-0\"\n            )}\n            data-slot=\"volume-control-range\"\n            style={\n              horizontal()\n                ? {\n                    width: `${context.shownPosition() * PERCENT}%`,\n                  }\n                : {\n                    height: `${context.shownPosition() * PERCENT}%`,\n                  }\n            }\n          />\n        </div>\n\n        <SliderPrimitive.Thumb\n          aria-label=\"Volume\"\n          aria-valuetext={\n            context.muted()\n              ? \"Muted\"\n              : `${Math.round(context.volume() * PERCENT)}%`\n          }\n          class=\"bg-background ring-foreground/15 hover:ring-ring/30 focus-visible:ring-ring/40 data-dragging:ring-ring/30 block size-(--volume-thumb-size) shrink-0 rounded-full shadow-sm ring-1 outline-hidden transition-[box-shadow] hover:ring-4 focus-visible:ring-4 data-dragging:ring-4\"\n          data-slot=\"volume-control-thumb\"\n          onKeyDown={context.handleKeyDown}\n        >\n          <SliderPrimitive.Input />\n        </SliderPrimitive.Thumb>\n      </SliderPrimitive.Track>\n    </SliderPrimitive.Root>\n  );\n};\n\nexport interface VolumeControlValueProps extends Omit<\n  SpanDOMProps,\n  \"children\" | \"class\" | \"className\"\n> {\n  class?: string;\n  className?: string;\n}\n\nconst VALUE_OWN = [\"class\", \"className\"] as const;\n\nexport const VolumeControlValue = (props: VolumeControlValueProps) => {\n  const context = useVolumeControl(\"VolumeControlValue\");\n\n  const rest = omitProps(props, VALUE_OWN);\n\n  return (\n    <span\n      class={cn(\n        \"text-muted-foreground w-9 shrink-0 text-end font-mono text-xs tabular-nums\",\n        props.class,\n        props.className\n      )}\n      data-slot=\"volume-control-value\"\n      {...rest}\n    >\n      {context.muted() ? \"0%\" : `${Math.round(context.position() * PERCENT)}%`}\n    </span>\n  );\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "lib/solid/kobalte-slider.ts",
      "content": "import type { SliderContextValue } from \"@kobalte/core/slider\";\nimport * as Solid from \"solid-js\";\n\nimport { createCompatEffect } from \"@/lib/solid/effect\";\n\ninterface SliderCallbacks {\n  onChangeEnd?: () => void;\n  onSlideEnd?: (original: SliderContextValue[\"onSlideEnd\"]) => void;\n  onSlideMove?: (\n    original: SliderContextValue[\"onSlideMove\"],\n    delta: { deltaX: number; deltaY: number }\n  ) => void;\n  onSlideStart?: (\n    original: SliderContextValue[\"onSlideStart\"],\n    index: number,\n    value: number\n  ) => void;\n  suppressStep?: boolean;\n}\n\n/**\n * Kobalte 0.13.14 and 2.0.0-alpha.2 call these context methods dynamically.\n * SliderContextValue exposes them, but method replacement is not a documented\n * extension point, so keep the Solid 2 thumb-editability workaround and all\n * required pointer remapping in one adapter. Alpha.2's move handler calls\n * onChange twice: setThumbPercent emits the new value, then onChange(values())\n * replays the previous controlled value before Solid 2's queued update settles.\n * Its end handler likewise reads the old dragging array after writing it and\n * misses onChangeEnd. Adapt those two synchronous-read assumptions here, while\n * leaving vendor pointer capture, focus, keyboard and input handling intact.\n */\nexport const useKobalteSliderCompat = (\n  context: SliderContextValue,\n  callbacks: SliderCallbacks = {}\n): void => {\n  const originals = {\n    onSlideEnd: context.onSlideEnd,\n    onSlideMove: context.onSlideMove,\n    onSlideStart: context.onSlideStart,\n    onStepKeyDown: context.onStepKeyDown,\n  };\n\n  const queuedUpdates = \"onSettled\" in Solid;\n  let activeIndex: number | undefined;\n  let currentPosition: number | null = null;\n\n  const move: SliderContextValue[\"onSlideMove\"] = (delta) => {\n    if (!queuedUpdates) {\n      // 0.13.14 uses inversion alone for horizontal deltas, although thumb\n      // placement and track presses also use locale. Correct RTL before delegation.\n      const rtl = context.isSlidingFromLeft() === context.inverted();\n      originals.onSlideMove?.({\n        deltaX: rtl ? -delta.deltaX : delta.deltaX,\n        deltaY: delta.deltaY,\n      });\n\n      return;\n    }\n\n    if (activeIndex === undefined || context.state.isDisabled()) return;\n    const rect = context.trackRef()!.getBoundingClientRect();\n    const vertical = context.state.orientation() === \"vertical\";\n    const size = vertical ? rect.height : rect.width;\n\n    if (size <= 0) return;\n    currentPosition ??= context.state.getThumbPercent(activeIndex) * size;\n\n    const direction = vertical\n      ? context.isSlidingFromBottom()\n        ? -1\n        : 1\n      : context.isSlidingFromLeft()\n        ? 1\n        : -1;\n\n    currentPosition += (vertical ? delta.deltaY : delta.deltaX) * direction;\n    context.state.setThumbPercent(\n      activeIndex,\n      Math.min(1, Math.max(0, currentPosition / size))\n    );\n  };\n\n  context.onSlideStart = (index, value) => {\n    activeIndex = index;\n    currentPosition = null;\n\n    if (callbacks.onSlideStart)\n      callbacks.onSlideStart(originals.onSlideStart, index, value);\n    else originals.onSlideStart?.(index, value);\n  };\n\n  context.onSlideMove = (delta) => {\n    if (callbacks.onSlideMove) callbacks.onSlideMove(move, delta);\n    else move(delta);\n  };\n\n  context.onSlideEnd = () => {\n    if (callbacks.onSlideEnd) callbacks.onSlideEnd(originals.onSlideEnd);\n    else {\n      originals.onSlideEnd?.();\n\n      if (\n        queuedUpdates &&\n        activeIndex !== undefined &&\n        !context.state.isDisabled()\n      )\n        callbacks.onChangeEnd?.();\n    }\n\n    activeIndex = undefined;\n  };\n\n  if (callbacks.suppressStep) {\n    context.onStepKeyDown = () => undefined;\n  }\n\n  Solid.onCleanup(() => {\n    context.onSlideEnd = originals.onSlideEnd;\n    context.onSlideMove = originals.onSlideMove;\n    context.onSlideStart = originals.onSlideStart;\n    context.onStepKeyDown = originals.onStepKeyDown;\n  });\n\n  createCompatEffect(\n    () => [context.thumbs().length, context.state.isDisabled()] as const,\n    ([thumbCount, isDisabled]) => {\n      for (let index = 0; index < thumbCount; index += 1) {\n        context.state.setThumbEditable(index, !isDisabled);\n      }\n    }\n  );\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/audiocn-license.txt",
      "content": "Audio component source adapted from https://github.com/audiocn/ui.\nMIT License — Copyright (c) 2026 OrcDev\n\nRegular SVG icon geometry adapted from @phosphor-icons/react 2.1.10.\nMIT License — Copyright (c) 2020 Phosphor Icons\n\nThe following permission and warranty terms apply to both notices.\nRetain this file with copies or substantial portions of the source.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
      "type": "registry:file",
      "target": "@lib/audiocn-license.txt"
    }
  ],
  "type": "registry:ui"
}
