{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-audio-clip-player",
  "type": "registry:ui",
  "title": "Audio Clip Player",
  "description": "Host-controlled play/pause, seekable waveform and time display.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/audio-clip-player.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/audio-clip-player\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\"\nimport type { CSSProperties, KeyboardEvent, PointerEvent } from \"react\"\nimport {\n  clamp,\n  waveformPath,\n  audioPreviewWaveformBars,\n  type AudioPreviewWaveform,\n  type AudioPreviewWaveformBar,\n} from \"maruaudio/ui/shared/audio-clip-waveform\"\nimport {\n  usePlaybackScrub,\n  usePlaybackScrubSnapshot,\n} from \"maruaudio/ui/shared/playback-clock\"\nimport { useElementSize } from \"maruaudio/ui/shared/use-element-size\"\nexport type AudioPreviewWaveformStyle = \"detailed\" | \"bars\"\n/** Presentation only: every surface retains the same source peaks and seek control. */\nexport const AUDIO_WAVEFORM_PRESENTATIONS = {\n  demo: { waveformStyle: \"bars\", height: 64 },\n  featured: { waveformStyle: \"bars\", height: 56 },\n  table: { waveformStyle: \"detailed\", height: 30 },\n  transport: { waveformStyle: \"bars\", height: 28 },\n} as const satisfies Record<\n  string,\n  { readonly waveformStyle: AudioPreviewWaveformStyle; readonly height: number }\n>\nexport type AudioWaveformPresentation =\n  keyof typeof AUDIO_WAVEFORM_PRESENTATIONS\nexport type { AudioPreviewWaveform }\nfunction WaveformGraphic({\n  bars,\n  durationFraction,\n  path,\n  waveformStyle,\n}: {\n  readonly bars: ReadonlyArray<AudioPreviewWaveformBar>\n  readonly durationFraction: number\n  readonly path: string\n  readonly waveformStyle: AudioPreviewWaveformStyle\n}) {\n  if (waveformStyle === \"detailed\") {\n    return (\n      <>\n        <line\n          x1=\"0\"\n          y1=\"50\"\n          x2=\"100\"\n          y2=\"50\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.5\"\n        />\n        <g transform={`scale(${durationFraction} 1)`}>\n          <path d={path} fill=\"currentColor\" />\n        </g>\n      </>\n    )\n  }\n\n  return (\n    <g transform={`scale(${durationFraction} 1)`}>\n      {bars.map((bar, index) => (\n        <line\n          key={index}\n          x1={bar.x}\n          y1={bar.y1}\n          x2={bar.x}\n          y2={bar.y2}\n          stroke=\"currentColor\"\n          strokeWidth=\"3\"\n          strokeLinecap=\"round\"\n          vectorEffect=\"non-scaling-stroke\"\n        />\n      ))}\n    </g>\n  )\n}\n\nfunction formatPreviewTime(seconds: number): string {\n  if (!Number.isFinite(seconds) || seconds < 0) return \"0:00\"\n  const wholeSeconds = Math.round(seconds)\n  const minutes = Math.floor(wholeSeconds / 60)\n  return `${minutes}:${String(wholeSeconds % 60).padStart(2, \"0\")}`\n}\n\nexport interface AudioClipSeekProps {\n  readonly positionSeconds: number\n  readonly durationSeconds?: number\n  readonly onSeek?: (seconds: number) => void\n  readonly label: string\n  readonly disabled?: boolean\n  readonly thumbVisibility?: \"always\" | \"on-intent\"\n  readonly className?: string\n  readonly style?: CSSProperties\n}\n\nconst SEEK_STYLES = `\n[data-audio-clip-seek]{--audio-clip-seek-played:var(--audio-clip-seek-value,var(--uikit-accent,#60a5fa));appearance:none;box-sizing:border-box;display:block;min-width:0;width:100%;height:28px;margin:0;padding:0;border:0;background:transparent;cursor:pointer;touch-action:none}\n[data-audio-clip-seek]::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:var(--audio-clip-seek-background)}\n[data-audio-clip-seek]::-moz-range-track{height:4px;border-radius:2px;background:var(--audio-clip-seek-background)}\n[data-audio-clip-seek]::-webkit-slider-thumb{appearance:none;width:16px;height:16px;margin-top:-6px;border:2px solid var(--audio-clip-seek-surface,var(--uikit-surface,#1e293b));border-radius:50%;background:var(--audio-clip-seek-value,var(--uikit-accent,#60a5fa))}\n[data-audio-clip-seek]::-moz-range-thumb{box-sizing:border-box;width:16px;height:16px;border:2px solid var(--audio-clip-seek-surface,var(--uikit-surface,#1e293b));border-radius:50%;background:var(--audio-clip-seek-value,var(--uikit-accent,#60a5fa))}\n[data-audio-clip-seek][data-thumb=\"on-intent\"]::-webkit-slider-thumb{opacity:0}\n[data-audio-clip-seek][data-thumb=\"on-intent\"]::-moz-range-thumb{opacity:0}\n[data-audio-clip-seek]:hover::-webkit-slider-thumb,[data-audio-clip-seek]:focus-visible::-webkit-slider-thumb{opacity:1}\n[data-audio-clip-seek]:hover::-moz-range-thumb,[data-audio-clip-seek]:focus-visible::-moz-range-thumb{opacity:1}\n[data-audio-clip-seek]:hover,[data-audio-clip-seek]:focus-visible{--audio-clip-seek-played:var(--audio-clip-seek-active,var(--uikit-accent,#60a5fa))}\n[data-audio-clip-seek]:focus-visible{outline:2px solid var(--uikit-focus,var(--uikit-accent,#60a5fa));outline-offset:2px}\n[data-audio-clip-seek]:disabled{cursor:default;opacity:.5}\n[data-audio-clip-seek]:disabled::-webkit-slider-thumb{opacity:0}\n[data-audio-clip-seek]:disabled::-moz-range-thumb{opacity:0}\n`\n\n/** Native timeline scrubber shared by the session playback bar and module faces. */\nexport function AudioClipSeek({\n  positionSeconds,\n  durationSeconds,\n  onSeek,\n  label,\n  disabled = false,\n  thumbVisibility = \"always\",\n  className,\n  style,\n}: AudioClipSeekProps) {\n  const known =\n    typeof durationSeconds === \"number\" &&\n    Number.isFinite(durationSeconds) &&\n    durationSeconds > 0\n  const duration = known ? durationSeconds : 1\n  const position =\n    known && Number.isFinite(positionSeconds)\n      ? clamp(positionSeconds, 0, duration)\n      : 0\n  const readout = `${formatPreviewTime(Math.floor(position))} of ${known ? formatPreviewTime(Math.floor(duration)) : \"unknown\"}`\n  return (\n    <>\n      <style href=\"uikit-audio-clip-seek\" precedence=\"default\">\n        {SEEK_STYLES}\n      </style>\n      <input\n        data-audio-clip-seek=\"\"\n        data-thumb={thumbVisibility}\n        type=\"range\"\n        min={0}\n        max={duration}\n        step={0.01}\n        value={position}\n        disabled={disabled || !known || !onSeek}\n        onChange={(event) => onSeek?.(Number(event.target.value))}\n        aria-label={`Seek ${label}`}\n        aria-valuetext={readout}\n        className={className}\n        style={\n          {\n            ...style,\n            \"--audio-clip-seek-background\": `linear-gradient(to right, var(--audio-clip-seek-played) ${(position / duration) * 100}%, var(--audio-clip-seek-track,var(--uikit-border,#475569)) ${(position / duration) * 100}%)`,\n          } as CSSProperties\n        }\n      />\n    </>\n  )\n}\n\nexport function AudioClipWaveform({\n  variant,\n  progress,\n  displayProgress,\n  progressTransition,\n  timelineDurationMs,\n  presentation,\n  waveformStyle: styleOverride,\n  onSeek,\n  disabled = false,\n  height: heightOverride,\n  showPlayhead = true,\n  onScrubStart,\n  onScrubEnd,\n  onScrubCancel,\n  waveformSource = \"provided\",\n}: {\n  readonly variant: {\n    readonly label: string\n    readonly waveform: AudioPreviewWaveform\n    readonly durationMs?: number\n  }\n  readonly waveformSource?: \"provided\" | \"placeholder\" | \"measured\"\n  readonly progress: number\n  /** Visual-only position. Spoken time and keyboard seeking retain `progress`. */\n  readonly displayProgress?: number\n  /** CSS paints between sparse host updates; a new key snaps a seek/source. */\n  readonly progressTransition?: {\n    readonly durationMs: number\n    readonly resetKey: number\n  }\n  readonly timelineDurationMs?: number\n  /** Named surface sizing/style; explicit overrides remain available to editors. */\n  readonly presentation?: AudioWaveformPresentation\n  readonly waveformStyle?: AudioPreviewWaveformStyle\n  readonly onSeek: (progress: number) => void\n  readonly disabled?: boolean\n  readonly height?: number\n  /** Hide when a containing timeline draws a shared playhead. */\n  readonly showPlayhead?: boolean\n  /** Coordinate pointer ownership with a containing timeline/playhead. */\n  readonly onScrubStart?: () => void\n  readonly onScrubEnd?: () => void\n  readonly onScrubCancel?: () => void\n}) {\n  const preset = presentation && AUDIO_WAVEFORM_PRESENTATIONS[presentation]\n  const waveformStyle =\n    styleOverride ?? (preset ? preset.waveformStyle : \"bars\")\n  const height = heightOverride ?? (preset ? preset.height : 48)\n  const [surface, size] = useElementSize<HTMLDivElement>()\n  const width = size.width || 288\n  const activePointer = useRef<number | null>(null)\n  const cancelScrub = useRef(onScrubCancel)\n  cancelScrub.current = onScrubCancel\n  useEffect(\n    () => () => {\n      if (activePointer.current !== null) cancelScrub.current?.()\n    },\n    []\n  )\n  useEffect(() => {\n    if (disabled && activePointer.current !== null) {\n      activePointer.current = null\n      cancelScrub.current?.()\n    }\n  }, [disabled])\n  const rawId = useId()\n  const clipId = `audio-preview-${rawId.replace(/:/g, \"\")}`\n  const [readyMotionKey, setReadyMotionKey] = useState<number | null>(null)\n  useEffect(() => {\n    if (!progressTransition) return\n    // Give a seek or source switch one painted frame at its real position\n    // before moving toward the next predicted position.\n    let nextFrame = 0\n    const frame = requestAnimationFrame(() => {\n      nextFrame = requestAnimationFrame(() =>\n        setReadyMotionKey(progressTransition.resetKey)\n      )\n    })\n    return () => {\n      cancelAnimationFrame(frame)\n      cancelAnimationFrame(nextFrame)\n    }\n  }, [progressTransition?.resetKey])\n  const resetMotion =\n    progressTransition !== undefined &&\n    readyMotionKey !== progressTransition.resetKey\n  const paintedProgress = clamp(\n    resetMotion ? progress : (displayProgress ?? progress),\n    0,\n    1\n  )\n  const motionDuration =\n    progressTransition && !resetMotion\n      ? Math.max(0, progressTransition.durationMs)\n      : 0\n  const motionStyle: CSSProperties | undefined = progressTransition\n    ? {\n        transition:\n          motionDuration > 0 ? `width ${motionDuration}ms linear` : \"none\",\n        width: `${paintedProgress * 100}%`,\n      }\n    : undefined\n  const path = useMemo(() => waveformPath(variant.waveform), [variant.waveform])\n  const durationFraction = clamp(\n    variant.durationMs && timelineDurationMs\n      ? variant.durationMs / timelineDurationMs\n      : 1,\n    0,\n    1\n  )\n  const bars = useMemo(\n    () =>\n      audioPreviewWaveformBars(\n        variant.waveform,\n        Math.max(1, Math.floor((width * durationFraction) / 6))\n      ),\n    [variant.waveform, width, durationFraction]\n  )\n  const seekFromPointer = (event: PointerEvent<HTMLDivElement>) => {\n    const bounds = event.currentTarget.getBoundingClientRect()\n    if (bounds.width <= 0) return\n    onSeek(clamp((event.clientX - bounds.left) / bounds.width, 0, 1))\n  }\n  const seekFromKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {\n    const step = event.shiftKey ? 0.1 : 0.01\n    const next =\n      event.key === \"Home\"\n        ? 0\n        : event.key === \"End\"\n          ? 1\n          : event.key === \"ArrowLeft\" || event.key === \"ArrowDown\"\n            ? progress - step\n            : event.key === \"ArrowRight\" || event.key === \"ArrowUp\"\n              ? progress + step\n              : null\n    if (next === null) return\n    event.preventDefault()\n    onSeek(clamp(next, 0, 1))\n  }\n  const timelineSeconds =\n    (timelineDurationMs ?? variant.durationMs ?? 0) / 1_000\n  return (\n    <div\n      ref={surface}\n      role=\"slider\"\n      tabIndex={disabled ? -1 : 0}\n      aria-disabled={disabled}\n      aria-label={`Seek ${variant.label}`}\n      aria-valuemin={0}\n      aria-valuemax={Math.round(timelineSeconds)}\n      aria-valuenow={Math.round(progress * timelineSeconds)}\n      aria-valuetext={`${formatPreviewTime(progress * timelineSeconds)} of ${formatPreviewTime(timelineSeconds)}`}\n      style={{\n        minWidth: 0,\n        flex: 1,\n        cursor: \"crosshair\",\n        borderRadius: 3,\n        touchAction: \"none\",\n        userSelect: \"none\",\n      }}\n      onPointerDown={\n        disabled\n          ? undefined\n          : (event) => {\n              if (event.button !== 0 || activePointer.current !== null) return\n              event.currentTarget.focus()\n              event.currentTarget.setPointerCapture(event.pointerId)\n              activePointer.current = event.pointerId\n              onScrubStart?.()\n              seekFromPointer(event)\n            }\n      }\n      onPointerMove={\n        disabled\n          ? undefined\n          : (event) => {\n              if (event.currentTarget.hasPointerCapture(event.pointerId))\n                seekFromPointer(event)\n            }\n      }\n      onPointerUp={(event) => {\n        if (activePointer.current !== event.pointerId) return\n        activePointer.current = null\n        if (!disabled) {\n          seekFromPointer(event)\n          onScrubEnd?.()\n        } else onScrubCancel?.()\n        if (event.currentTarget.hasPointerCapture(event.pointerId))\n          event.currentTarget.releasePointerCapture(event.pointerId)\n      }}\n      onPointerCancel={(event) => {\n        if (activePointer.current !== event.pointerId) return\n        activePointer.current = null\n        onScrubCancel?.()\n        if (event.currentTarget.hasPointerCapture(event.pointerId))\n          event.currentTarget.releasePointerCapture(event.pointerId)\n      }}\n      onLostPointerCapture={(event) => {\n        if (activePointer.current !== event.pointerId) return\n        activePointer.current = null\n        onScrubCancel?.()\n      }}\n      onKeyDown={disabled ? undefined : seekFromKeyboard}\n    >\n      <svg\n        role=\"img\"\n        aria-label={\n          waveformSource === \"placeholder\"\n            ? \"Illustrative waveform; audio peaks not yet measured\"\n            : \"Source waveform\"\n        }\n        data-waveform-source={waveformSource}\n        data-waveform-style={waveformStyle}\n        data-waveform-presentation={presentation}\n        style={{\n          display: \"block\",\n          height,\n          width: \"100%\",\n          color: \"var(--uikit-track, #64748b)\",\n        }}\n        viewBox=\"0 0 100 100\"\n        preserveAspectRatio=\"none\"\n      >\n        <defs>\n          <clipPath id={clipId} clipPathUnits=\"userSpaceOnUse\">\n            <rect\n              x=\"0\"\n              y=\"0\"\n              width={paintedProgress * 100}\n              height=\"100\"\n              style={motionStyle}\n            />\n          </clipPath>\n        </defs>\n        <WaveformGraphic\n          bars={bars}\n          durationFraction={durationFraction}\n          path={path}\n          waveformStyle={waveformStyle}\n        />\n        {paintedProgress > 0 || progressTransition ? (\n          <g\n            clipPath={`url(#${clipId})`}\n            style={{ color: \"var(--uikit-accent, #60a5fa)\" }}\n          >\n            <WaveformGraphic\n              bars={bars}\n              durationFraction={durationFraction}\n              path={path}\n              waveformStyle={waveformStyle}\n            />\n          </g>\n        ) : null}\n        {showPlayhead && (paintedProgress > 0 || progressTransition) ? (\n          <line\n            x1={progressTransition ? 0 : paintedProgress * 100}\n            x2={progressTransition ? 0 : paintedProgress * 100}\n            y1=\"0\"\n            y2=\"100\"\n            stroke=\"var(--uikit-accent, #60a5fa)\"\n            strokeWidth=\"1\"\n            vectorEffect=\"non-scaling-stroke\"\n            style={\n              progressTransition\n                ? {\n                    transformBox: \"view-box\",\n                    transform: `translateX(${paintedProgress * 100}%)`,\n                    transition:\n                      motionDuration > 0\n                        ? `transform ${motionDuration}ms linear`\n                        : \"none\",\n                  }\n                : undefined\n            }\n          />\n        ) : null}\n      </svg>\n    </div>\n  )\n}\n\nexport interface AudioClipPlayerProps {\n  readonly label: string\n  readonly scrubIdentity?: unknown\n  readonly waveformSource?: \"provided\" | \"placeholder\" | \"measured\"\n  readonly waveform: AudioPreviewWaveform\n  /** Optional shorter clip within a shared A/B timeline. */\n  readonly waveformDurationSeconds?: number\n  readonly durationSeconds: number\n  readonly currentTimeSeconds: number\n  readonly playing: boolean\n  /** Omit to hide the loop control; the host owns repetition. */\n  readonly loop?: boolean\n  readonly onLoopChange?: (loop: boolean) => void\n  readonly disabled?: boolean\n  readonly waveformStyle?: AudioPreviewWaveformStyle\n  readonly waveformHeight?: number\n  readonly compact?: boolean\n  readonly playButtonVariant?: \"outline\" | \"solid\"\n  /** Optional visible action labels beside the transport icon. */\n  readonly playLabel?: string\n  readonly pauseLabel?: string\n  /** Host may warm media after pointer or keyboard intent. */\n  readonly onPlayIntent?: () => void\n  readonly onPlayIntentEnd?: () => void\n  readonly onToggle: () => void\n  /** Normalized position in the displayed timeline, 0..1. */\n  readonly onSeek: (progress: number) => void\n  readonly className?: string\n  readonly style?: CSSProperties\n}\n\n/** Controlled display: the receiving host owns media, clocks and playback. */\nexport function AudioClipPlayer({\n  label,\n  scrubIdentity = label,\n  waveformSource,\n  waveform,\n  durationSeconds,\n  waveformDurationSeconds = durationSeconds,\n  currentTimeSeconds,\n  playing,\n  loop,\n  onLoopChange,\n  disabled = false,\n  waveformStyle = \"bars\",\n  waveformHeight = 48,\n  compact = false,\n  playButtonVariant = \"outline\",\n  playLabel,\n  pauseLabel,\n  onToggle,\n  onPlayIntent,\n  onPlayIntentEnd,\n  onSeek,\n  className,\n  style,\n}: AudioClipPlayerProps) {\n  const hostProgress =\n    durationSeconds > 0 ? clamp(currentTimeSeconds / durationSeconds, 0, 1) : 0\n  const scrub = usePlaybackScrub(onSeek, scrubIdentity)\n  const scrubSnapshot = usePlaybackScrubSnapshot(scrub)\n  useEffect(() => {\n    scrub.acceptPosition(hostProgress, {\n      durationSeconds,\n      running: playing,\n      loop: loop ?? false,\n    })\n  }, [scrub, hostProgress, durationSeconds, playing, loop, scrubSnapshot.phase])\n  const progress = scrubSnapshot.position ?? hostProgress\n  const scrubbing = scrubSnapshot.phase !== \"idle\"\n  return (\n    <div\n      className={className}\n      style={{\n        color: \"var(--uikit-text, #e2e8f0)\",\n        fontFamily: \"var(--uikit-font, sans-serif)\",\n        ...style,\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: compact ? 8 : 12,\n          minWidth: 0,\n        }}\n      >\n        <AudioClipTransport\n          label={label}\n          playing={playing}\n          disabled={disabled}\n          compact={compact}\n          playButtonVariant={playButtonVariant}\n          playLabel={playLabel}\n          pauseLabel={pauseLabel}\n          onToggle={onToggle}\n          onPlayIntent={onPlayIntent}\n          onPlayIntentEnd={onPlayIntentEnd}\n        />\n        <AudioClipWaveform\n          variant={{\n            label,\n            waveform,\n            durationMs: waveformDurationSeconds * 1000,\n          }}\n          timelineDurationMs={durationSeconds * 1000}\n          progress={progress}\n          waveformStyle={waveformStyle}\n          waveformSource={waveformSource}\n          onSeek={scrub.seek}\n          onScrubStart={scrub.begin}\n          onScrubEnd={scrub.end}\n          onScrubCancel={scrub.cancel}\n          showPlayhead={playing || scrubbing}\n          disabled={disabled || !(durationSeconds > 0)}\n          height={waveformHeight}\n        />\n        <AudioClipTime\n          currentTimeSeconds={progress * durationSeconds}\n          durationSeconds={durationSeconds}\n          active={playing || scrubbing}\n        />\n        {loop !== undefined ? (\n          <button\n            type=\"button\"\n            data-audio-clip-loop=\"\"\n            aria-label={`Loop ${label}`}\n            aria-pressed={loop}\n            disabled={disabled || !onLoopChange}\n            onClick={() => onLoopChange?.(!loop)}\n            style={{\n              display: \"inline-flex\",\n              alignItems: \"center\",\n              justifyContent: \"center\",\n              width: compact ? 28 : 32,\n              height: compact ? 28 : 32,\n              padding: 0,\n              flexShrink: 0,\n              border: 0,\n              background: \"transparent\",\n              color: loop\n                ? \"var(--uikit-accent, #60a5fa)\"\n                : \"var(--uikit-track, #64748b)\",\n              opacity: disabled || !onLoopChange ? 0.45 : 1,\n              cursor: disabled || !onLoopChange ? \"default\" : \"pointer\",\n            }}\n          >\n            <svg\n              width=\"16\"\n              height=\"16\"\n              viewBox=\"0 0 20 20\"\n              aria-hidden=\"true\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <path d=\"m13 2 3 3-3 3M16 5H6a3 3 0 0 0-3 3v1m4 9-3-3 3-3M4 15h10a3 3 0 0 0 3-3v-1\" />\n            </svg>\n          </button>\n        ) : null}\n      </div>\n    </div>\n  )\n}\n\n/** Same transport as AudioClipPlayer, for a waveform in a shared timeline lane. */\nexport function AudioClipTransport({\n  label,\n  playing,\n  disabled = false,\n  compact = false,\n  playButtonVariant = \"outline\",\n  playLabel,\n  pauseLabel,\n  onToggle,\n  onPlayIntent,\n  onPlayIntentEnd,\n}: Pick<\n  AudioClipPlayerProps,\n  | \"label\"\n  | \"playing\"\n  | \"disabled\"\n  | \"compact\"\n  | \"playButtonVariant\"\n  | \"playLabel\"\n  | \"pauseLabel\"\n  | \"onToggle\"\n  | \"onPlayIntent\"\n  | \"onPlayIntentEnd\"\n>) {\n  const actionLabel = playing ? pauseLabel : playLabel\n  return (\n    <button\n      type=\"button\"\n      aria-label={actionLabel ?? `${playing ? \"Pause\" : \"Play\"} ${label}`}\n      aria-pressed={playing}\n      disabled={disabled}\n      onClick={onToggle}\n      onPointerEnter={(event) => {\n        if (!disabled && !playing && event.pointerType !== \"touch\")\n          onPlayIntent?.()\n      }}\n      onPointerLeave={onPlayIntentEnd}\n      onFocus={() => {\n        if (!disabled && !playing) onPlayIntent?.()\n      }}\n      onBlur={onPlayIntentEnd}\n      style={{\n        width: actionLabel ? undefined : compact ? 28 : 36,\n        height: compact ? 28 : 36,\n        flexShrink: 0,\n        display: \"inline-flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        gap: 6,\n        padding: actionLabel ? \"0 8px\" : 0,\n        fontFamily: \"inherit\",\n        fontSize: 12,\n        whiteSpace: \"nowrap\",\n        border: \"1px solid var(--uikit-border, #475569)\",\n        borderRadius: actionLabel ? 6 : \"50%\",\n        background:\n          playButtonVariant === \"solid\"\n            ? \"var(--uikit-accent, #60a5fa)\"\n            : \"var(--uikit-surface, #1e293b)\",\n        color:\n          playButtonVariant === \"solid\"\n            ? \"var(--uikit-accent-contrast, #101512)\"\n            : \"inherit\",\n        cursor: disabled ? \"default\" : \"pointer\",\n        opacity: disabled ? 0.45 : 1,\n      }}\n    >\n      <svg\n        width=\"16\"\n        height=\"16\"\n        viewBox=\"0 0 16 16\"\n        aria-hidden=\"true\"\n        fill=\"currentColor\"\n      >\n        {playing ? (\n          <path d=\"M3 2h3v12H3zM10 2h3v12h-3z\" />\n        ) : (\n          <path d=\"M4 2l10 6-10 6z\" />\n        )}\n      </svg>\n      {actionLabel ? <span>{actionLabel}</span> : null}\n    </button>\n  )\n}\n\nexport function AudioClipTime({\n  currentTimeSeconds,\n  durationSeconds,\n  active,\n}: Pick<AudioClipPlayerProps, \"currentTimeSeconds\" | \"durationSeconds\"> & {\n  readonly active?: boolean\n}) {\n  return (\n    <span\n      style={{\n        fontSize: 11,\n        fontVariantNumeric: \"tabular-nums\",\n        whiteSpace: \"nowrap\",\n      }}\n    >\n      {active === undefined\n        ? `${formatPreviewTime(currentTimeSeconds)} / ${formatPreviewTime(durationSeconds)}`\n        : formatPreviewTime(active ? currentTimeSeconds : durationSeconds)}\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/audio-clip-player.tsx"
    }
  ]
}
