{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "apple-music-card",
  "title": "Apple Music Card",
  "description": "A track card loaded from an Apple Music link, with mini and full views.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/clover-ui/apple-music-card.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type AppleMusicCardView = \"mini\" | \"full\";\n\nexport interface AppleMusicCardProps extends Omit<React.ComponentProps<\"a\">, \"children\" | \"href\"> {\n  url: string;\n  view?: AppleMusicCardView;\n}\n\ninterface TrackData {\n  title: string;\n  artist: string;\n  image: string;\n  link: string;\n}\n\nfunction extractAppleMusicId(rawUrl: string): { id: string; kind: \"song\" | \"album\" } | null {\n  try {\n    const url = new URL(rawUrl);\n    if (url.hostname !== \"music.apple.com\" || ![\"https:\", \"http:\"].includes(url.protocol))\n      return null;\n\n    const trackId = url.searchParams.get(\"i\");\n    if (trackId) return /^\\d+$/.test(trackId) ? { id: trackId, kind: \"song\" } : null;\n\n    const segments = url.pathname.split(\"/\").filter(Boolean);\n    const last = segments[segments.length - 1];\n    if (last && /^\\d+$/.test(last)) return { id: last, kind: \"album\" };\n\n    return null;\n  } catch {\n    return null;\n  }\n}\n\nconst artwork = (url: string, size: number) => url.replace(/\\d+x\\d+bb/, `${size}x${size}bb`);\n\nasync function fetchTrackData(url: string, signal: AbortSignal): Promise<TrackData> {\n  const parsed = extractAppleMusicId(url);\n  if (!parsed) throw new Error(\"Not a valid music.apple.com link\");\n\n  const res = await fetch(`https://itunes.apple.com/lookup?id=${parsed.id}&entity=song`, {\n    signal,\n  });\n  if (!res.ok) throw new Error(\"iTunes lookup failed\");\n\n  const data = await res.json();\n  if (!data.resultCount) throw new Error(\"No results for that link\");\n\n  const result =\n    data.results.find(\n      (r: { wrapperType: string }) =>\n        r.wrapperType === (parsed.kind === \"song\" ? \"track\" : \"collection\"),\n    ) ?? data.results[0];\n\n  return {\n    title: result.trackName ?? result.collectionName ?? \"Unknown title\",\n    artist: result.artistName,\n    image: result.artworkUrl100 as string,\n    link: result.trackViewUrl ?? result.collectionViewUrl ?? url,\n  };\n}\n\nconst AppleMusicGlyph = ({ className }: { className?: string }) => (\n  <svg\n    viewBox=\"0 0 20 24\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    className={cn(\"overflow-visible\", className)}\n    aria-hidden\n  >\n    <path\n      d=\"M16.6638 23.0371C15.3716 24.3036 13.9606 24.1036 12.6024 23.5037C11.1651 22.8904 9.84643 22.8638 8.32999 23.5037C6.43114 24.3303 5.42897 24.0903 4.29493 23.0371C-2.14006 16.3313 -1.19063 6.11923 6.11466 5.74594C7.89483 5.83926 9.13436 6.73248 10.1761 6.81247C11.7321 6.49251 13.2222 5.57263 14.8837 5.69261C16.8748 5.85259 18.3781 6.65249 19.3671 8.09231C15.2529 10.5853 16.2287 16.0646 20 17.5978C19.2484 19.5975 18.2726 21.5839 16.6506 23.0504L16.6638 23.0371ZM10.0442 5.66595C9.84643 2.69299 12.2332 0.23997 14.976 0C15.3584 3.43957 11.8903 5.99924 10.0442 5.66595Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nconst frameClass = \"relative isolate block w-[200px] shrink-0 overflow-hidden rounded-[20px]\";\n\nconst MusicTag = () => (\n  <p className=\"px-1 pt-2 pb-0.5 text-[12px] leading-none font-semibold whitespace-nowrap text-white/55\">\n    <AppleMusicGlyph className=\"mr-[0.08em] inline-block h-[0.8em] w-[0.67em] align-baseline\" />\n    Music\n  </p>\n);\n\nconst AppleMusicCardSkeleton = ({\n  view,\n  className,\n}: {\n  view: AppleMusicCardView;\n  className?: string;\n}) => (\n  <div\n    className={cn(frameClass, \"bg-muted/50 ring-border ring-1 ring-inset\", className)}\n    aria-busy=\"true\"\n    aria-label=\"Loading Apple Music track\"\n  >\n    <div className=\"p-2\">\n      <div className=\"bg-muted aspect-square w-full animate-pulse rounded-[13px]\" />\n      {view === \"full\" && (\n        <div className=\"px-1 pt-2.5\">\n          <div className=\"bg-muted my-[3px] h-4 w-3/4 animate-pulse rounded\" />\n          <div className=\"bg-muted my-[3px] h-3.5 w-1/2 animate-pulse rounded\" />\n        </div>\n      )}\n      <div className=\"bg-muted mx-1 mt-2 mb-0.5 h-3 w-12 animate-pulse rounded\" />\n    </div>\n  </div>\n);\n\nconst AppleMusicCardError = ({ className }: { className?: string }) => (\n  <div className={cn(frameClass, \"bg-neutral-900\", className)}>\n    <div className=\"p-2\">\n      <div className=\"flex aspect-square w-full items-center justify-center rounded-[13px] bg-neutral-800\">\n        <p className=\"px-3 text-center text-xs text-white/60\">Failed to load Apple Music data</p>\n      </div>\n      <MusicTag />\n    </div>\n  </div>\n);\n\nfunction useTrack(url: string) {\n  const [state, setState] = React.useState<{ url: string; data: TrackData | null } | null>(null);\n\n  React.useEffect(() => {\n    let cancelled = false;\n    const controller = new AbortController();\n    fetchTrackData(url, controller.signal).then(\n      (data) => !cancelled && setState({ url, data }),\n      () => !cancelled && setState({ url, data: null }),\n    );\n    return () => {\n      cancelled = true;\n      controller.abort();\n    };\n  }, [url]);\n\n  if (!state || state.url !== url) return undefined;\n  return state.data;\n}\n\nfunction AppleMusicCard({ url, view = \"full\", className, ...props }: AppleMusicCardProps) {\n  const data = useTrack(url);\n  const expanded = view === \"full\";\n\n  if (data === undefined) return <AppleMusicCardSkeleton view={view} className={className} />;\n  if (!data) return <AppleMusicCardError className={className} />;\n\n  return (\n    <a\n      href={data.link}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      draggable={false}\n      aria-label={`Open ${data.title} by ${data.artist} in Apple Music`}\n      className={cn(\n        frameClass,\n        \"group focus-visible:ring-ring bg-neutral-900 focus-visible:ring-2 focus-visible:outline-none\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"absolute inset-0 -z-10 overflow-hidden\">\n        <img\n          src={artwork(data.image, 100)}\n          alt=\"\"\n          aria-hidden\n          draggable={false}\n          decoding=\"async\"\n          className=\"h-full w-full scale-125 transform-gpu object-cover blur-3xl brightness-125 saturate-[1.7] transition-transform duration-700 group-hover:scale-[1.32] motion-reduce:transition-none\"\n        />\n        <div className=\"absolute inset-0 bg-gradient-to-b from-black/5 via-black/20 to-black/75\" />\n      </div>\n\n      <div className=\"p-2\">\n        <div className=\"aspect-square overflow-hidden rounded-[13px] shadow-lg\">\n          <img\n            src={artwork(data.image, 400)}\n            alt={`${data.title} artwork`}\n            draggable={false}\n            decoding=\"async\"\n            className=\"h-full w-full bg-neutral-800 object-cover select-none\"\n          />\n        </div>\n\n        <div\n          aria-hidden={!expanded}\n          className={cn(\n            \"grid grid-cols-[minmax(0,1fr)] transition-[grid-template-rows,opacity] duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\",\n            expanded ? \"grid-rows-[1fr] opacity-100\" : \"grid-rows-[0fr] opacity-0\",\n          )}\n        >\n          <div className=\"min-h-0 min-w-0 overflow-hidden\">\n            <div\n              className={cn(\n                \"px-1 pt-2.5 transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\",\n                !expanded && \"-translate-y-3\",\n              )}\n            >\n              <p className=\"truncate text-[15px] font-semibold tracking-[-0.02em] text-white\">\n                {data.title}\n              </p>\n              <p className=\"truncate text-[13px] text-white/60\">{data.artist}</p>\n            </div>\n          </div>\n        </div>\n\n        <MusicTag />\n      </div>\n    </a>\n  );\n}\n\nAppleMusicCard.displayName = \"AppleMusicCard\";\n\nexport { AppleMusicCard };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}