@behindthemusictree/app-kit-ui 33.0.0 → 33.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GHFKICEB.js → chunk-2M4Q2IJU.js} +13 -7
- package/dist/chunk-2M4Q2IJU.js.map +1 -0
- package/dist/genre-tree/index.cjs +11 -5
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +11 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-GHFKICEB.js.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/popup/PopupTitle.tsx","../src/popup/PopupButtons.tsx","../src/popup/BasePopup.tsx","../src/popup/Toast.tsx","../src/popup/TrackUploadPopup.tsx","../src/popup/AuthErrorPopup.tsx","../src/popup/InternalErrorPopup.tsx","../src/popup/NetworkErrorPopup.tsx","../src/popup/AuthPopup.tsx","../src/popup/SpotifyAuthErrorPopup.tsx","../src/auth/AuthCallbackHandler.tsx","../src/genre-tree/GenreTreeView.tsx","../src/genre-tree/playlist-tree/size-metric.ts","../src/genre-tree/playlist-tree/toTreeNodes.ts","../src/genre-tree/playlist-tree/TreePerRoot.tsx","../src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts","../src/genre-tree/playlist-tree/TreeWheel.tsx","../src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx","../src/genre-tree/GenreTreeWheelHandoff.tsx","../src/genre-tree/GenreSearch.tsx","../src/genre-tree/GenreInfoPanelBody.tsx","../src/genre-tree/GenreActionBand.tsx","../src/genre-tree/ShareActionButton.tsx","../src/genre-tree/SharePopup.tsx","../src/genre-tree/ShareDestinationButton.tsx","../src/genre-tree/SharePreviewCard.tsx","../src/genre-tree/GenreDetailExtras.tsx","../src/genre-tree/ClampedText.tsx","../src/genre-tree/GenrePlaylistTracks.tsx","../src/genre-tree/MusicBrainzRecordingLink.tsx","../src/genre-tree/ChangeParentPopup.tsx","../src/genre-tree/artist/useArtistPanel.tsx","../src/genre-tree/artist/ArtistHeader.tsx","../src/genre-tree/artist/ArtistLinks.tsx","../src/genre-tree/artist/formatYears.ts","../src/genre-tree/artist/ArtistPhoto.tsx","../src/genre-tree/artist/ArtistMembers.tsx","../src/genre-tree/artist/ArtistDiscography.tsx","../src/genre-tree/artist/ArtistSongs.tsx","../src/genre-tree/track-list-sidebar/TrackItem.tsx","../src/genre-tree/TrackPositionPlayPause.tsx","../src/genre-tree/song/useSongPanel.tsx","../src/genre-tree/song/SongPanelContent.tsx","../src/genre-tree/SizeMetricToggle.tsx","../src/genre-tree/GenreDetailPanel.tsx","../src/genre-tree/index.ts","../src/genre-tree/track-list-sidebar/TrackListSidebar.tsx","../src/genre-tree/PlayerPanel.tsx","../src/genre-tree/PlayerControls.tsx","../src/genre-tree/Rating.tsx"],"sourcesContent":["export * from \"./popup\";\nexport * from \"./auth\";\nexport * from \"./genre-tree\";\n","\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\ninterface PopupTitleProps {\n title: string;\n onClose: () => void;\n isDismissable?: boolean;\n icon?: LucideIcon;\n className?: string;\n titleClassName?: string;\n closeButtonClassName?: string;\n}\n\nexport function PopupTitle({\n title,\n onClose,\n isDismissable,\n icon: Icon,\n className = \"\",\n titleClassName = \"\",\n closeButtonClassName = \"\",\n}: PopupTitleProps) {\n return (\n <div className={`popup-title flex items-center justify-between bg-black p-4 rounded-t-lg ${className}`}>\n <div className=\"flex items-center gap-2\">\n {Icon && <Icon className=\"h-5 w-5 text-white\" />}\n <h3 id=\"popup-title\" className={`text-white font-semibold ${titleClassName}`}>\n {title}\n </h3>\n </div>\n {isDismissable && (\n <Button\n onClick={onClose}\n className={`h-8 w-8 text-white hover:text-white/80 ${closeButtonClassName}`}\n aria-label=\"Close popup\"\n >\n <X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\ninterface PopupButtonsProps {\n children: ReactNode;\n alignment?: \"left\" | \"center\" | \"right\";\n className?: string;\n}\n\nexport function PopupButtons({ children, alignment = \"center\", className = \"\" }: PopupButtonsProps) {\n const alignmentClasses = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n };\n\n return <div className={`flex gap-3 ${alignmentClasses[alignment]} ${className}`}>{children}</div>;\n}\n","\"use client\";\n\nimport { createPortal } from \"react-dom\";\nimport { ReactNode, useEffect, useRef } from \"react\";\nimport { PopupTitle } from \"./PopupTitle\";\nimport { PopupButtons } from \"./PopupButtons\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\nexport interface BasePopupProps {\n title: string;\n children: ReactNode;\n onClose?: () => void;\n type?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\" | \"spotify\" | \"auth\";\n icon?: LucideIcon;\n isDismissable?: boolean;\n className?: string;\n overlayClassName?: string;\n titleClassName?: string;\n titleTextClassName?: string;\n closeButtonClassName?: string;\n /** Sizes the popup to its content (up to the type's max width) instead of filling it. */\n fitContent?: boolean;\n contentClassName?: string;\n showOkButton?: boolean;\n showCancelButton?: boolean;\n okButtonText?: string;\n okButtonDisabled?: boolean;\n cancelButtonText?: string;\n onOk?: () => void;\n onCancel?: () => void;\n buttonAlignment?: \"left\" | \"center\" | \"right\";\n /**\n * Distance (in px) reserved at the top of the viewport, e.g. for a fixed app header/banner.\n * Each consuming app passes its own value. Defaults to 0.\n */\n topOffset?: number;\n /** Keeps keyboard focus within the popup and restores it to the opener when dismissed. */\n trapFocus?: boolean;\n /** Closes a dismissable popup on Escape. */\n closeOnEscape?: boolean;\n /** Closes a dismissable popup when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n}\n\nconst typeClasses: Record<string, string> = {\n default: \"w-full max-w-lg\",\n success: \"w-full max-w-lg\",\n error: \"w-full max-w-lg\",\n warning: \"w-full max-w-lg\",\n info: \"w-full max-w-lg\",\n spotify: \"w-full max-w-md\",\n auth: \"w-full max-w-md\",\n};\n\nconst contentBgClasses: Record<string, string> = {\n spotify: \"bg-green-500\",\n auth: \"bg-gray-800\",\n};\n\nexport function BasePopup(props: BasePopupProps) {\n const {\n title,\n children,\n onClose,\n type = \"default\",\n icon,\n isDismissable = true,\n className,\n overlayClassName = \"\",\n titleClassName = \"\",\n titleTextClassName = \"\",\n closeButtonClassName = \"\",\n fitContent = false,\n contentClassName = \"\",\n showOkButton = false,\n showCancelButton = false,\n okButtonText = \"OK\",\n cancelButtonText = \"Cancel\",\n onOk,\n okButtonDisabled = false,\n onCancel,\n buttonAlignment = \"center\",\n topOffset = 0,\n trapFocus = false,\n closeOnEscape = false,\n closeOnBackdrop = false,\n } = props;\n const dialogRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const dialog = dialogRef.current;\n const focusableSelector =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n const getFocusable = () =>\n Array.from(dialog?.querySelectorAll<HTMLElement>(focusableSelector) ?? []).filter(\n (element) => !element.hasAttribute(\"hidden\") && element.getAttribute(\"aria-hidden\") !== \"true\",\n );\n\n if (trapFocus) (getFocusable()[0] ?? dialog)?.focus();\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && closeOnEscape && isDismissable) {\n event.preventDefault();\n onClose?.();\n return;\n }\n if (!trapFocus || event.key !== \"Tab\") return;\n\n const focusable = getFocusable();\n if (focusable.length === 0) {\n event.preventDefault();\n dialog?.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (event.shiftKey && (document.activeElement === first || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n first.focus();\n }\n };\n\n if (trapFocus || closeOnEscape) document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n if (trapFocus || closeOnEscape) document.removeEventListener(\"keydown\", handleKeyDown);\n if (trapFocus && previousFocus?.isConnected) previousFocus.focus();\n };\n }, [closeOnEscape, isDismissable, onClose, trapFocus]);\n\n const baseClasses = \"bg-white rounded-lg\";\n\n const renderButtons = () => {\n if (!showOkButton && !showCancelButton) return null;\n\n return (\n <div className=\"mt-4 pt-4 border-t\">\n <PopupButtons alignment={buttonAlignment}>\n {showCancelButton && (\n <Button onClick={onCancel || onClose || (() => {})} variant=\"secondary\">\n {cancelButtonText}\n </Button>\n )}\n {showOkButton && (\n <Button onClick={onOk || onClose || (() => {})} disabled={okButtonDisabled}>\n {okButtonText}\n </Button>\n )}\n </PopupButtons>\n </div>\n );\n };\n\n const popupComponent = (\n <div\n className={`fixed z-50 flex items-center justify-center bg-transparent ${overlayClassName}`}\n style={{\n top: topOffset,\n left: 0,\n right: 0,\n bottom: 0,\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"popup-title\"\n onClick={closeOnBackdrop && isDismissable ? onClose : undefined}\n >\n <div\n ref={dialogRef}\n tabIndex={trapFocus ? -1 : undefined}\n className={`${baseClasses} ${fitContent ? typeClasses[type].replace(\"w-full\", \"w-fit\") : typeClasses[type]} ${className || \"\"}`}\n onClick={(e) => e.stopPropagation()}\n >\n <PopupTitle\n title={title}\n onClose={onClose ?? (() => {})}\n isDismissable={isDismissable}\n icon={icon}\n className={titleClassName}\n titleClassName={titleTextClassName}\n closeButtonClassName={closeButtonClassName}\n />\n <div className={`popup-content rounded-b-lg p-4 ${contentBgClasses[type] ?? \"\"} ${contentClassName}`}>\n {children}\n {renderButtons()}\n </div>\n </div>\n </div>\n );\n\n return createPortal(popupComponent, document.body);\n}\n\nexport default BasePopup;\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nexport type ToastProps = {\n message: string;\n onClose: () => void;\n durationMs?: number;\n};\n\nexport default function Toast({ message, onClose, durationMs = 2200 }: ToastProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n const timeout = window.setTimeout(onClose, durationMs);\n return () => window.clearTimeout(timeout);\n }, [durationMs, onClose]);\n\n if (!mounted) return null;\n\n return createPortal(\n <div className=\"pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4\">\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-sm text-[var(--color-text)] shadow-lg ring-1 ring-black/10\"\n >\n {message}\n </div>\n </div>,\n document.body,\n );\n}\n","\"use client\";\n\nimport { MdError, MdCheckCircle, MdUpload } from \"react-icons/md\";\nimport { useTrackUpload, type UploadStatus, type UseTrackUploadOptions } from \"@behindthemusictree/app-kit/popup\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\ntype TrackUploadPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> &\n UseTrackUploadOptions & {\n onClose?: () => void;\n };\n\nfunction getStatusIcon(status: UploadStatus) {\n switch (status) {\n case \"success\":\n return <MdCheckCircle className=\"text-[var(--color-green-500)]\" size={20} />;\n case \"error\":\n return <MdError className=\"text-[var(--color-red-500)]\" size={20} />;\n case \"uploading\":\n return <MdUpload className=\"text-[var(--color-blue-500)] animate-pulse\" size={20} />;\n default:\n return <div className=\"w-5 h-5 rounded-full border-2 border-[var(--color-neutral-300)]\" />;\n }\n}\n\nfunction getStatusText(status: UploadStatus) {\n switch (status) {\n case \"success\":\n return \"Uploaded\";\n case \"error\":\n return \"Failed\";\n case \"uploading\":\n return \"Uploading...\";\n default:\n return \"Pending\";\n }\n}\n\nexport default function TrackUploadPopup({\n files,\n genre,\n onProcessFile,\n onComplete,\n onClose,\n uploadTimeoutMs,\n ...rest\n}: TrackUploadPopupProps) {\n const { items, isUploading, allComplete, successfulCount, errorCount } = useTrackUpload({\n files,\n genre,\n onProcessFile,\n onComplete,\n uploadTimeoutMs,\n });\n\n const displayTotal = items.length > 0 ? items.length : files.length;\n const trackNoun = useTrackNoun();\n\n return (\n <BasePopup\n {...rest}\n onClose={onClose}\n title={`Upload ${capitalize(trackNoun.plural)} (${successfulCount}/${displayTotal})`}\n isDismissable\n showOkButton\n okButtonText=\"OK\"\n okButtonDisabled={isUploading || !allComplete}\n onOk={onClose}\n >\n <div className=\"space-y-4\">\n {items.map((item) => (\n <div key={item.id} className=\"border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center space-x-3\">\n {getStatusIcon(item.status)}\n <div>\n <div className=\"font-medium text-sm truncate max-w-xs\" title={item.file.name}>\n {item.file.name}\n </div>\n <div className=\"text-xs text-[var(--color-neutral-500)]\">{(item.file.size / 1024 / 1024).toFixed(1)} MB</div>\n </div>\n </div>\n <div className=\"text-sm text-[var(--color-neutral-600)]\">{getStatusText(item.status)}</div>\n </div>\n\n {item.status === \"uploading\" && (\n <div className=\"w-full bg-[var(--color-neutral-200)] rounded-full h-2\">\n <div\n className=\"bg-[var(--color-blue-500)] h-2 rounded-full transition-all duration-300\"\n style={{ width: `${item.progress}%` }}\n />\n </div>\n )}\n\n {item.status === \"error\" && item.error && (\n <div className=\"text-[var(--color-red-500)] text-sm mt-2\">\n {item.error.name === \"InvalidInputError\"\n ? \"Upload failed due to invalid file data. Please check your file and try again.\"\n : item.error.name === \"ZodError\"\n ? \"Upload failed due to a server error. Please try again later.\"\n : item.error.message}\n </div>\n )}\n </div>\n ))}\n\n {allComplete && (\n <div className=\"flex justify-between items-center\">\n <div className=\"text-sm text-[var(--color-neutral-600)]\">\n {successfulCount} successful, {errorCount} failed\n </div>\n </div>\n )}\n </div>\n </BasePopup>\n );\n}\n","\"use client\";\n\nimport { AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\n\ntype AuthErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n message: string;\n onClose: () => void;\n};\n\nexport default function AuthErrorPopup({ message, onClose, ...rest }: AuthErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Sign-in error\"\n isDismissable\n icon={AlertCircle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <p className=\"text-center text-lg font-medium text-[var(--color-text)]\">{message}</p>\n <Button onClick={onClose} variant=\"secondary\" className=\"w-full max-w-xs\">\n Close\n </Button>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { AlertTriangle, AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype InternalErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n /**\n * Support contact email shown in the \"if the problem persists\" message. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function InternalErrorPopup({ errorCode, contactEmail, ...rest }: InternalErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Internal Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertCircle className=\"h-16 w-16 text-[var(--color-red-500)]\" strokeWidth={1.5} />\n <div>\n <p className=\"text-center text-[var(--color-neutral-600)]\">\n Please try again. If the problem persists, contact us at{\" \"}\n <a href={`mailto:${contactEmail}`} className=\"text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]\">\n {contactEmail}\n </a>\n </p>\n </div>\n {errorCode && (\n <div className=\"text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">\n Error Code: {errorCode}\n </div>\n )}\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { AlertTriangle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype NetworkErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n};\n\nconst OFFLINE_MESSAGE = \"It seems that you are not connected to the internet. Please check your connection and try again.\";\nconst UNREACHABLE_MESSAGE = \"We couldn't reach the server. Please try again in a moment.\";\n\nexport default function NetworkErrorPopup({ errorCode, ...rest }: NetworkErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Network Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertTriangle className=\"h-16 w-16 text-[var(--color-neutral-600)]\" strokeWidth={1.5} />\n <p className=\"text-center text-[var(--color-neutral-700)]\">\n {errorCode === ErrorCode.NETWORK_OFFLINE ? OFFLINE_MESSAGE : UNREACHABLE_MESSAGE}\n </p>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\nimport { User } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { FaSpotify } from \"react-icons/fa\";\nimport { FcGoogle } from \"react-icons/fc\";\n\ntype AuthPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n handleSpotifyOAuth: (redirectAfterAuthPath?: string) => void;\n handleGoogleOAuth?: (redirectAfterAuthPath?: string) => void;\n redirectAfterAuthPath?: string;\n spotifyOnly?: boolean;\n message?: string;\n /**\n * Body copy shown when `spotifyOnly` is true. Wording (app name, feature description) is\n * app-specific, so each consuming app supplies its own instead of this package assuming one.\n */\n spotifyOnlyDescription: ReactNode;\n /** Body copy shown when `spotifyOnly` is false, for the same reason as `spotifyOnlyDescription`. */\n defaultDescription: ReactNode;\n};\n\nexport default function AuthPopup({\n handleSpotifyOAuth,\n handleGoogleOAuth,\n redirectAfterAuthPath,\n spotifyOnly,\n message,\n spotifyOnlyDescription,\n defaultDescription,\n ...rest\n}: AuthPopupProps) {\n const showGoogle = !spotifyOnly && handleGoogleOAuth;\n\n return (\n <BasePopup\n {...rest}\n title={spotifyOnly ? \"Connect with Spotify\" : \"Sign in\"}\n isDismissable={false}\n icon={User}\n type=\"auth\"\n children={\n <div className=\"flex flex-col items-center space-y-7\">\n <div className=\"px-2 text-center\">\n {message && <p className=\"mb-3 text-base font-medium leading-relaxed text-amber-200\">{message}</p>}\n <p className=\"text-lg font-medium leading-relaxed text-white\">\n {spotifyOnly ? spotifyOnlyDescription : defaultDescription}\n </p>\n </div>\n <div className=\"flex w-full flex-col items-center gap-3\">\n <Button\n onClick={() => handleSpotifyOAuth(redirectAfterAuthPath)}\n className=\"relative mb-2 w-fit transform bg-green-600 px-8 py-3 text-white shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-green-700 hover:shadow-xl\"\n >\n <span className=\"flex items-center justify-center\">\n <FaSpotify className=\"absolute left-6 text-3xl\" />\n <span className=\"w-full pl-14 pr-8 text-center text-lg font-medium\">Sign in with Spotify</span>\n </span>\n </Button>\n {showGoogle && (\n <Button\n variant=\"outline\"\n onClick={() => handleGoogleOAuth!(redirectAfterAuthPath)}\n className=\"relative w-fit transform border-white bg-white px-8 py-3 text-black shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-[var(--color-neutral-100)] hover:shadow-xl\"\n >\n <span className=\"flex items-center justify-center\">\n <FcGoogle className=\"absolute left-6 text-2xl\" />\n <span className=\"w-full pl-14 pr-8 text-center text-lg font-medium text-black\">\n Sign in with Google\n </span>\n </span>\n </Button>\n )}\n </div>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { FaSpotify } from \"react-icons/fa\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { User } from \"lucide-react\";\nimport { getSpotifyAllowlistMailtoHref } from \"@behindthemusictree/app-kit/transport\";\n\ntype SpotifyAuthErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n message: string;\n details?: string;\n onClose: () => void;\n /**\n * Support contact email used to build the allowlist-request mailto link. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function SpotifyAuthErrorPopup({\n message,\n details,\n onClose,\n contactEmail = null,\n ...rest\n}: SpotifyAuthErrorPopupProps) {\n const requestAccessHref = getSpotifyAllowlistMailtoHref(contactEmail);\n\n return (\n <BasePopup\n {...rest}\n title=\"Authentication Failed\"\n isDismissable\n icon={User}\n children={\n <div className=\"flex flex-col items-center space-y-8 py-4\">\n <FaSpotify className=\"text-[#1DB954] text-7xl\" />\n <div className=\"space-y-3 text-center\">\n <p className=\"text-xl font-medium text-[var(--color-text)]\">{message}</p>\n {details && (\n <p className=\"text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">{details}</p>\n )}\n <div className=\"space-y-2 text-center\">\n <p className=\"text-base text-[var(--color-neutral-700)]\">\n The Spotify app is in development mode, so only allowlisted accounts can connect.\n </p>\n {requestAccessHref && contactEmail ? (\n <p className=\"text-base text-[var(--color-neutral-700)]\">\n Request access by emailing your{\" \"}\n <a\n href={requestAccessHref}\n className=\"text-[#1DB954] font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-[#1DB954] focus:ring-offset-1 rounded\"\n >\n Spotify full name and Spotify email address\n </a>{\" \"}\n to{\" \"}\n <a\n href={requestAccessHref}\n className=\"text-[#1DB954] font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-[#1DB954] focus:ring-offset-1 rounded\"\n >\n {contactEmail}\n </a>\n .\n </p>\n ) : (\n <p className=\"text-base text-[var(--color-neutral-700)]\">To request access, contact the app owner.</p>\n )}\n </div>\n </div>\n <Button\n onClick={onClose}\n className=\"bg-[#1DB954] hover:bg-[#1DB954]/90 text-white w-full max-w-xs transform transition-all duration-200 hover:scale-[1.02] shadow-lg hover:shadow-xl\"\n >\n <FaSpotify className=\"mr-2 text-lg\" />\n Try Again\n </Button>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\nimport { useAuthCallback, type AuthCallbackState, type UseAuthCallbackOptions } from \"@behindthemusictree/app-kit/auth\";\n\nexport interface AuthCallbackHandlerProps extends UseAuthCallbackOptions {\n /** Optional custom \"connecting…\" / error UI; defaults to a minimal full-screen overlay. */\n renderStatus?: (state: Exclude<AuthCallbackState, { status: null }>) => ReactNode;\n}\n\n/** Handles OAuth callbacks at layout level. Rendered in the app shell so it runs on every load. */\nexport default function AuthCallbackHandler({ renderStatus, ...options }: AuthCallbackHandlerProps) {\n const state = useAuthCallback(options);\n\n if (state.status === null) return null;\n\n if (renderStatus) return <>{renderStatus(state)}</>;\n\n if (state.status === \"error\") {\n return (\n <div className=\"fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[var(--color-neutral-100)] p-4\">\n <div className=\"w-full max-w-md rounded-lg border border-[color-mix(in_srgb,var(--color-red-500)_20%,transparent)] bg-[color-mix(in_srgb,var(--color-red-500)_10%,transparent)] p-4\">\n <h2 className=\"mb-2 font-semibold text-[var(--color-red-500)]\">Authentication Error</h2>\n <p className=\"text-[color-mix(in_srgb,var(--color-red-500)_80%,transparent)]\">{state.message}</p>\n <button\n type=\"button\"\n onClick={state.goHome}\n className=\"mt-4 rounded bg-[var(--color-red-600)] px-4 py-2 text-white hover:bg-[var(--color-red-700)]\"\n >\n Go to home\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[var(--color-neutral-100)]\" data-page=\"auth-callback-handler\">\n <div className=\"text-center\">\n <h1 className=\"mb-4 text-2xl font-bold\">Connecting…</h1>\n <p className=\"text-[var(--color-neutral-600)]\">Completing sign-in.</p>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, type ComponentProps, type ReactNode } from \"react\";\nimport { useIsMutating } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\nimport { Plus } from \"lucide-react\";\nimport { Maximize2, Minimize2 } from \"lucide-react\";\nimport { IconTextButton, IconButton, Button, SegmentedControl, DetailPanel, useObscuredInset } from \"@behindthemusictree/ui\";\n\nimport {\n GenreNodeAncestors,\n GenreNodeFamily,\n GenreTreeWheelSkeleton,\n GenreTreeViewSkeleton,\n useGenreNodeRelations,\n} from \"@behindthemusictree/genre-tree-view\";\nimport type {\n GenreTreeAction,\n GenreTreeNode,\n GenreTreeViewMode,\n GenreTreeViewport,\n GenreTreeProps,\n GenreNodeRelations,\n GenreNodeStyleResolver,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport {\n CriteriaMinimum,\n CriteriaPlaylistSimple,\n useArtistSearch,\n useSongSearch,\n isSongSearchResultPlayable,\n type ArtistSearchResult,\n type RenderSongLink,\n type SongSearchResult,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { useGenreTreeViewState, useArtist, useUpdateGenre, genreUpdateMutationKey } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport type { GenreDetailSlots } from \"./GenreDetailExtras\";\nimport type { GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nimport type { HostOverlayPanel } from \"./overlayPanel\";\nimport { toTreeNodes } from \"./playlist-tree/toTreeNodes\";\nimport type { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport GenrePlaylistTreePerRoot from \"./playlist-tree/TreePerRoot\";\nimport GenrePlaylistTreeWheel from \"./playlist-tree/TreeWheel\";\nimport GenrePlaylistTreeWheelRadialPopCore, { type GenreFocusProps } from \"./playlist-tree/TreeWheelRadialPopCore\";\nimport { GenreTreeWheelHandoff } from \"./GenreTreeWheelHandoff\";\nimport GenreSearch, { type GenreSearchExtraGroup } from \"./GenreSearch\";\nimport { GenreInfoPanelBody } from \"./GenreInfoPanelBody\";\nimport ChangeParentPopup, { type ParentChange } from \"./ChangeParentPopup\";\nimport { useArtistPanel } from \"./artist/useArtistPanel\";\nimport { useSongPanel } from \"./song/useSongPanel\";\nimport { SizeMetricToggle } from \"./SizeMetricToggle\";\nimport { hasViewsCount, type GenreTreeSizeMetric } from \"./playlist-tree/size-metric\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\nimport type { GetGenreTreeShareUrl, GenreTreeShareTarget } from \"./share\";\n\nexport type {\n FocusChangeReason,\n GenreTreeViewMode,\n} from \"@behindthemusictree/genre-tree-view\";\nexport type { GenreFocusProps } from \"./playlist-tree/TreeWheelRadialPopCore\";\n\n/** Artists are searched by default; picking one opens its panel (see `artistUuid`). */\nexport type ArtistSearchProps =\n | { artistSearch: false; onArtistSelect?: never; artistSearchBaseUrl?: never }\n | {\n artistSearch?: true;\n /** Notified when a suggestion is picked, after the view opens its artist panel. */\n onArtistSelect?: (artist: ArtistSearchResult) => void;\n /** Backend that serves `artists/` (search and the artist panel). Defaults to `getBackendBaseUrl`. */\n artistSearchBaseUrl?: () => string;\n };\n\nconst NO_POP_ROOT_TITLE = \"This genre tree has no 'Mainstream Pop' root yet\";\n\nexport type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {\n scope: Scope;\n /** \"regions\" lays the info panel out in the brand's left/center/right columns (see brand `--layout-center-*`). */\n placement?: \"default\" | \"regions\";\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n getBackendBaseUrl: () => string;\n /** Called after the Parent popup moved a genre; `from` and `to` are genre (criteria) uuids, `from` null for a root genre. */\n onParentChanged?: (change: ParentChange) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n /** Controlled view mode. When provided, the internal Stacked/Wheel toggle is not rendered — the consumer owns that UI. */\n viewMode?: GenreTreeViewMode;\n /** Controlled selection (a genre playlist uuid, i.e. the tree node id). When provided, the\n * consumer owns it — e.g. to move the selection after deleting the selected genre. */\n selectedNodeId?: string | null;\n /** Called on every user-driven selection change (tree click, search pick, search clear). */\n onSelectedNodeIdChange?: (nodeId: string | null) => void;\n /** Mount-only seed for the pan/zoom (wheel, pop-core and stacked views), replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Restore silently after layout; use a new revision for each navigation. Supply focus, selection,\n * and restoration together. User interaction supersedes a pending request. In stacked view,\n * the selected genre must resolve to a root; outline ignores restoration. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. In stacked view, only the tree holding the selection reports. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** When true, hides the \"Add root\" button and suppresses per-node\n * create/rename affordances, for a read-only consumer. Defaults to false. */\n readOnly?: boolean;\n /** Root element classes; the component adds no outer margin. */\n className?: string;\n /** Shows the \"Tree display options\" group (e.g. Rotation). Defaults to true. */\n showDisplayOptions?: boolean;\n /** Shows the \"Add root\" button (still hidden when `readOnly`). Defaults to true; a host rendering its own passes false. */\n showAddRoot?: boolean;\n /** Controlled wheel rotation. When provided, the internal Rotation toggle is not rendered — the consumer owns that UI. */\n allowWheelRotation?: boolean;\n /** Parses the selected genre's overview; pass an extended schema to keep consumer-specific fields. */\n criteriaOverviewSchema?: z.ZodType<O, unknown>;\n /** Consumer-specific rows rendered in the info panel after Summary. */\n renderGenreDetailExtras?: (overview: O) => ReactNode;\n /** Host control displayed before the size metric in the floating controls. */\n floatingControlsLeading?: ReactNode;\n /** Controlled size metric of the wheel and pop-core nodes: song count or summed song views. Uncontrolled when omitted\n * (starts on songs). The toggle is hidden while an artist filters the tree, which only has song counts. */\n sizeMetric?: GenreTreeSizeMetric;\n onSizeMetricChange?: (metric: GenreTreeSizeMetric) => void;\n /** When set, artist names in the info panel's track list render through it (e.g. a router `Link`). In the artist\n * panel, names otherwise render as buttons that switch the panel to that artist. */\n renderArtistLink?: RenderArtistLink;\n /** Controlled artist panel (an artist uuid). It shares the info panel's slot: opening an artist clears the selected\n * genre; selecting a genre keeps the artist as a filter (the tree shows only genres the artist has songs in, and\n * the genre's songs and play button are limited to the artist) and shows the genre's panel instead. A host\n * `overlayPanel` takes precedence over the artist panel. */\n artistUuid?: string | null;\n /** Called when the user opens an artist (search pick, member link) or closes its panel. */\n onArtistUuidChange?: (uuid: string | null) => void;\n /** Controlled expanded state of the artist panel. Uncontrolled, it resets to compact on each artist. */\n artistExpanded?: boolean;\n onArtistExpandedChange?: (expanded: boolean) => void;\n /** Controlled song panel (a song uuid). Like the artist panel it shares the info panel's slot: opening a song clears\n * the selected genre and the artist; selecting a genre closes it. A host `overlayPanel` takes precedence. */\n songUuid?: string | null;\n /** Called when the user opens a song (search pick, title link) or closes its panel. */\n onSongUuidChange?: (uuid: string | null) => void;\n /** Controlled expanded state of the song panel. Uncontrolled, it resets to compact on each song. */\n songExpanded?: boolean;\n onSongExpandedChange?: (expanded: boolean) => void;\n /** Songs are searched by default; picking one opens its panel. */\n songSearch?: boolean;\n onSongSelect?: (song: SongSearchResult) => void;\n /** When set, song titles in the info panel's track list render through it (e.g. a router `Link` to the song).\n * By default they are buttons that open the song panel. */\n renderSongLink?: RenderSongLink;\n /** Resolves a selected genre, artist or song to a share URL using the host app's routes. */\n getShareUrl?: GetGenreTreeShareUrl;\n} & ArtistSearchProps &\n GenreDetailSlots<O> &\n GenrePlaylistTracksSlots &\n GenreFocusProps & {\n /** Appended to a selected genre's panel, e.g. destructive actions. */\n renderDangerZone?: (node: GenreTreeNode) => ReactNode;\n /** Controlled expanded state of the genre panel. Uncontrolled when omitted. */\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** A host panel rendered in the detail panel's slot; it takes precedence over the genre, song and artist panels. */\n overlayPanel?: HostOverlayPanel;\n /** Hides the info panel behind its edge tab; uncontrolled when omitted. Only applies with `placement=\"regions\"`. */\n collapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n };\n\nconst REGIONS_CLASS =\n \"[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]\";\n\nconst noop = () => {};\n\nexport function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({\n scope,\n handleGenreCreationAction,\n handleGenreRenameAction,\n onParentChanged,\n getBackendBaseUrl,\n additionalActions,\n viewMode: controlledViewMode,\n selectedNodeId: controlledSelectedNodeId,\n onSelectedNodeIdChange,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n readOnly = false,\n className,\n showDisplayOptions = true,\n showAddRoot = true,\n allowWheelRotation: controlledAllowWheelRotation,\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n floatingControlsLeading,\n sizeMetric: controlledSizeMetric,\n onSizeMetricChange,\n renderArtistLink,\n artistUuid: controlledArtistUuid,\n onArtistUuidChange,\n artistExpanded: controlledArtistExpanded,\n onArtistExpandedChange,\n getShareUrl,\n artistSearch = true,\n onArtistSelect,\n artistSearchBaseUrl,\n songUuid: controlledSongUuid,\n onSongUuidChange,\n songExpanded: controlledSongExpanded,\n onSongExpandedChange,\n songSearch = true,\n onSongSelect,\n renderSongLink,\n renderSummary,\n renderEssentialTracks,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n renderDangerZone,\n expanded,\n onExpandedChange,\n collapsed,\n onCollapsedChange,\n overlayPanel,\n placement = \"default\",\n ...focus\n}: GenreTreeViewProps<O>) {\n const getShareUrlRef = useRef(getShareUrl);\n getShareUrlRef.current = getShareUrl;\n const [internalArtistUuid, setInternalArtistUuid] = useState<string | null>(null);\n const isArtistControlled = controlledArtistUuid !== undefined;\n const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;\n const [internalSongUuid, setInternalSongUuid] = useState<string | null>(null);\n const isSongControlled = controlledSongUuid !== undefined;\n const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;\n const changeSong = (uuid: string | null) => {\n if (!isSongControlled) setInternalSongUuid(uuid);\n onSongUuidChange?.(uuid);\n };\n const [internalSizeMetric, setInternalSizeMetric] = useState<GenreTreeSizeMetric>(\"songs\");\n const sizeMetric = controlledSizeMetric ?? internalSizeMetric;\n const deferredSizeMetric = useDeferredValue(sizeMetric);\n const changeSizeMetric = (metric: GenreTreeSizeMetric) => {\n if (controlledSizeMetric === undefined) setInternalSizeMetric(metric);\n onSizeMetricChange?.(metric);\n };\n const changeArtist = (uuid: string | null) => {\n if (!isArtistControlled) setInternalArtistUuid(uuid);\n onArtistUuidChange?.(uuid);\n };\n const {\n isLoading,\n isFetching: isTreeFetching,\n genrePlaylistByNodeId,\n visibleGenrePlaylists,\n groupedGenrePlaylistsByRoot,\n selectedNodeId,\n selectedGenrePlaylist,\n selectNode,\n selectGenrePlaylist,\n clearSelection,\n viewMode,\n isViewModeControlled: isControlled,\n setViewMode,\n canShowPopCore,\n allowWheelRotation: internalAllowWheelRotation,\n toggleWheelRotation,\n onNodeHover: handleNodeHover,\n } = useGenreTreeViewState<O>({\n scope,\n getBackendBaseUrl,\n viewMode: controlledViewMode,\n selectedNodeId: controlledSelectedNodeId,\n onSelectedNodeIdChange,\n criteriaOverviewSchema,\n artistUuid,\n });\n\n // Renderer revision tracking is per instance: bind a navigation to one stacked root so a\n // later user selection cannot replay the same request in another renderer.\n const restoreRevision = viewportRestore?.revision;\n const [stackedRestoreOwner, setStackedRestoreOwner] = useState<{\n revision: string | number | undefined;\n rootUuid: string | undefined;\n }>({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });\n let restoreRootUuid = stackedRestoreOwner.rootUuid;\n if (stackedRestoreOwner.revision !== restoreRevision) {\n restoreRootUuid = selectedGenrePlaylist?.root.uuid;\n setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });\n } else if (restoreRootUuid === undefined && selectedGenrePlaylist) {\n restoreRootUuid = selectedGenrePlaylist.root.uuid;\n setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });\n }\n\n const isGenreUpdating = useIsMutating({ mutationKey: genreUpdateMutationKey }) > 0;\n const { mutate: updateGenre } = useUpdateGenre(scope, getBackendBaseUrl);\n const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;\n // The artist detail can load after the filtered counts, so use the UUID as the filter state.\n const isArtistFiltered = artistUuid !== null;\n const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);\n\n const artistResult = useArtist(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);\n const filterArtistName = artistResult.status === \"success\" ? artistResult.artist.name : null;\n const filterArtist = useMemo(\n () => (artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null),\n [artistUuid, filterArtistName],\n );\n const hasSelectedGenreNode =\n selectedGenrePlaylist !== undefined && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);\n const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });\n const openArtist = (uuid: string) => {\n if (selectedNodeId !== null) clearSelection();\n if (songUuid !== null) changeSong(null);\n changeArtist(uuid);\n };\n const openSong = (uuid: string) => {\n if (selectedNodeId !== null) clearSelection();\n changeSong(uuid);\n };\n // Kept stable: it feeds the memoized info panel body, which must not rebuild on every render.\n const openSongRef = useRef(openSong);\n openSongRef.current = openSong;\n const resolvedRenderSongLink = useCallback<RenderSongLink>(\n (song) =>\n renderSongLink ? (\n renderSongLink(song)\n ) : (\n <button type=\"button\" onClick={() => openSongRef.current(song.uuid)} className=\"hover:underline\">\n {song.title}\n </button>\n ),\n [renderSongLink],\n );\n const [expandedSongUuid, setExpandedSongUuid] = useState<string | null>(null);\n // Keyed by artist, so switching artists (including a host's back/forward) starts compact.\n const [expandedArtistUuid, setExpandedArtistUuid] = useState<string | null>(null);\n const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;\n const songBaseUrl = getBackendBaseUrl;\n const artistPanel = useArtistPanel({\n uuid: artistUuid,\n scope,\n getBackendBaseUrl: artistBaseUrl,\n getShareUrl: getShareUrlRef.current,\n renderArtistLink:\n renderArtistLink ??\n ((artist) =>\n artist.isSpecialPurpose ? (\n artist.name\n ) : (\n <button type=\"button\" onClick={() => openArtist(artist.uuid)} className=\"hover:underline\">\n {artist.name}\n </button>\n )),\n expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),\n onExpandedChange: (on) => {\n if (controlledArtistExpanded === undefined) setExpandedArtistUuid(on ? artistUuid : null);\n onArtistExpandedChange?.(on);\n },\n renderSongLink: resolvedRenderSongLink,\n onClose: () => changeArtist(null),\n });\n const resolvedRenderArtistLink: RenderArtistLink =\n renderArtistLink ??\n ((artist) =>\n artist.isSpecialPurpose ? (\n artist.name\n ) : (\n <button type=\"button\" onClick={() => openArtist(artist.uuid)} className=\"hover:underline\">\n {artist.name}\n </button>\n ));\n const getGenrePath = (genre: CriteriaMinimum): CriteriaPlaylistSimple[] => {\n const path: CriteriaPlaylistSimple[] = [];\n let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);\n while (current) {\n path.unshift(current);\n const parentUuid: string | undefined = current.parent?.uuid;\n current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : undefined;\n }\n return path;\n };\n const songPanel = useSongPanel({\n uuid: songUuid,\n scope,\n getBackendBaseUrl: songBaseUrl,\n getShareUrl: getShareUrlRef.current,\n renderArtistLink: resolvedRenderArtistLink,\n getGenrePath,\n onSelectGenre: (genrePlaylist) => {\n changeSong(null);\n selectGenrePlaylist(genrePlaylist);\n },\n expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),\n onExpandedChange: (on) => {\n if (controlledSongExpanded === undefined) setExpandedSongUuid(on ? songUuid : null);\n onSongExpandedChange?.(on);\n },\n onClose: () => changeSong(null),\n });\n // Like a genre, the built-in artist panel shows its name in the search field and closes from its clear button.\n // A selected genre shows its own panel instead; the artist stays active as a filter.\n // A song, like a genre, replaces the artist panel; the artist stays active as a filter.\n const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? undefined : songPanel;\n const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? undefined : artistPanel;\n const builtInPanel = builtInSongPanel ?? builtInArtistPanel;\n\n const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(false);\n const [uncontrolledExpanded, setUncontrolledExpanded] = useState(false);\n const isCollapsed = collapsed ?? uncontrolledCollapsed;\n const isExpanded = expanded ?? uncontrolledExpanded;\n const changeCollapsed = (next: boolean) => {\n setUncontrolledCollapsed(next);\n onCollapsedChange?.(next);\n };\n const changeExpanded = (next: boolean) => {\n setUncontrolledExpanded(next);\n onExpandedChange?.(next);\n };\n useEffect(() => {\n setUncontrolledCollapsed(false);\n }, [selectedNodeId, artistUuid, songUuid]);\n\n const [isWheelRevealed, setIsWheelRevealed] = useState(false);\n const usesHandoff = viewMode === \"wheel\" || viewMode === \"pop-core\";\n // The panel stands beside a tree that fades in, so it waits for the tree.\n const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);\n\n // The tree publishes how it draws each node; the panel header and family chips reuse it.\n const [resolveStyle, setResolveStyle] = useState<GenreNodeStyleResolver | null>(null);\n const handleNodeStyleResolverChange = useCallback(\n (resolver: GenreNodeStyleResolver) => setResolveStyle(() => resolver),\n [],\n );\n const panelRef = useRef<HTMLDivElement>(null);\n const obscuredInset = useObscuredInset(panelRef);\n const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };\n\n const nodeMetric: GenreTreeSizeMetric = isArtistFiltered || !canSizeByViews ? \"songs\" : deferredSizeMetric;\n const treeNodes = useMemo(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);\n const relations = useGenreNodeRelations(\n treeNodes,\n !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,\n resolveStyle,\n );\n\n // \"Previous\" returns to the genre selected before the current one.\n const [selectionHistory, setSelectionHistory] = useState<{ current: string | null; previous: string | null }>({\n current: selectedNodeId,\n previous: null,\n });\n if (selectionHistory.current !== selectedNodeId) {\n setSelectionHistory({\n current: selectedNodeId,\n previous: selectionHistory.current ?? selectionHistory.previous,\n });\n }\n const previousPlaylist =\n selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId\n ? genrePlaylistByNodeId.get(selectionHistory.previous)\n : undefined;\n const handlePrevious =\n previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist)\n ? () => handleGenreSearchSelect(previousPlaylist)\n : undefined;\n\n const { showPopup, hidePopup } = usePopup();\n const [searchText, setSearchText] = useState(\"\");\n const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : \"\");\n const artistGroup: GenreSearchExtraGroup | undefined = artistSearch\n ? {\n key: \"artists\",\n label: \"Artists\",\n items: artistSearchResult.artists.map((artist) => ({\n key: artist.uuid,\n content: (\n <>\n <span className=\"block truncate font-medium\">\n {artist.name}\n {artist.disambiguation && (\n <span className=\"font-normal text-[var(--color-neutral-500)]\"> · {artist.disambiguation}</span>\n )}\n </span>\n {artist.matchedAlias && (\n <span className=\"block truncate text-xs text-[var(--color-neutral-500)]\">\n also known as {artist.matchedAlias}\n </span>\n )}\n </>\n ),\n onSelect: () => {\n openArtist(artist.uuid);\n onArtistSelect?.(artist);\n },\n })),\n status: artistSearchResult.isError\n ? \"Search failed\"\n : artistSearchResult.settled && artistSearchResult.artists.length === 0\n ? \"No artists found\"\n : \"\",\n loading: artistSearchResult.pending,\n }\n : undefined;\n const songSearchResult = useSongSearch(songBaseUrl, songSearch ? searchText : \"\");\n const songGroup: GenreSearchExtraGroup | undefined = songSearch\n ? {\n key: \"songs\",\n label: \"Songs\",\n items: songSearchResult.songs.map((song) => ({\n key: song.uuid,\n content: (\n <span className={isSongSearchResultPlayable(song) ? undefined : \"opacity-60\"}>\n <span className=\"block truncate font-medium\">{song.title}</span>\n <span className=\"block truncate text-xs text-[var(--color-neutral-500)]\">\n {[song.artists.map((artist) => artist.name).join(\", \"), song.genre?.name ?? \"No genre\"]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </span>\n ),\n onSelect: () => {\n openSong(song.uuid);\n onSongSelect?.(song);\n },\n })),\n status: songSearchResult.isError\n ? \"Search failed\"\n : songSearchResult.settled && songSearchResult.songs.length === 0\n ? \"No songs found\"\n : \"\",\n loading: songSearchResult.pending,\n }\n : undefined;\n const extraGroups = [artistGroup, songGroup].filter((group): group is GenreSearchExtraGroup => group !== undefined);\n const handleSelectedNodeChange = (node: { id: string } | null) => {\n if (node !== null && songUuid !== null) changeSong(null);\n selectNode(node);\n };\n const handleGenreSearchClear = clearSelection;\n const handleGenreSearchSelect = (genrePlaylist: CriteriaPlaylistSimple) => {\n if (songUuid !== null) changeSong(null);\n selectGenrePlaylist(genrePlaylist);\n };\n\n // Mirrors genre-tree-view's isZoomableNode: a node zooms when it has children and at most one pop-side child.\n const selectedUuid = selectedGenrePlaylist?.uuid;\n const selectedChildren = selectedUuid\n ? visibleGenrePlaylists.filter((genrePlaylist) => genrePlaylist.parent?.uuid === selectedUuid)\n : [];\n const canZoomIn =\n viewMode === \"pop-core\" &&\n focus.onFocusGenreChange !== undefined &&\n selectedUuid !== undefined &&\n focus.focusGenreUuid !== selectedUuid &&\n selectedChildren.length > 0 &&\n selectedChildren.filter((child) => child.criteria?.side === \"pop\").length <= 1;\n const handleGenreSearchZoomIn = () => {\n if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, \"zoom-in\");\n };\n const zoomInRef = useRef<(() => void) | undefined>(undefined);\n zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : undefined;\n\n const selectGenreFromPanel = (nodeId: string) => {\n const genrePlaylist = genrePlaylistByNodeId.get(nodeId);\n if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);\n };\n // The overview fetch lives in GenreDetailExtras, so its loading states re-render only the panel, never the tree.\n // The count row is left out: the tracks section title carries it.\n const renderGenreBody = (relation: GenreNodeRelations): { primary: ReactNode } => {\n const { node } = relation;\n const family = (\n <GenreNodeFamily\n node={node}\n fill={relation.fill}\n textColor={relation.textColor}\n parentNode={relation.parentNode}\n childNodes={relation.childNodes}\n onSelectNode={selectGenreFromPanel}\n />\n );\n const ancestors = <GenreNodeAncestors ancestorNodes={relation.ancestorNodes} onSelectNode={selectGenreFromPanel} />;\n const genrePlaylist = genrePlaylistByNodeId.get(node.id);\n const criteria = genrePlaylist?.criteria;\n if (!genrePlaylist || !criteria) return { primary: <>{family}{ancestors}</> };\n const genreUuid = criteria.uuid;\n // The API only accepts a side on a root's direct child, and at most one pop child per root.\n const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);\n const canToggleSide = !readOnly && parentPlaylist !== undefined && parentPlaylist.parent === null;\n const side = criteria.side ?? \"core\";\n const shareTarget: GenreTreeShareTarget = {\n kind: \"genre\",\n genrePlaylistUuid: genrePlaylist.uuid,\n name: criteria.name,\n scope,\n artistUuid,\n };\n const shareUrl = getShareUrlRef.current?.(shareTarget) ?? undefined;\n const popSibling =\n canToggleSide && side === \"core\"\n ? visibleGenrePlaylists.find(\n (sibling) =>\n sibling.parent?.uuid === parentPlaylist.uuid &&\n sibling.uuid !== genrePlaylist.uuid &&\n sibling.criteria?.side === \"pop\",\n )\n : undefined;\n return {\n primary: (\n <>\n <GenreInfoPanelBody<O>\n family={<>{family}{ancestors}</>}\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n wikidataId={criteria.wikidataId}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n playback={playback}\n band={{\n node,\n shareTarget: shareUrl ? shareTarget : undefined,\n shareUrl,\n onZoomIn: zoomInRef.current,\n onAddChild: readOnly ? undefined : () => handleGenreCreationAction(criteria),\n onRename: readOnly ? undefined : () => handleGenreRenameAction(criteria),\n onChangeParent: readOnly\n ? undefined\n : () =>\n showPopup(\n <ChangeParentPopup\n genrePlaylist={{ ...genrePlaylist, criteria }}\n genrePlaylists={visibleGenrePlaylists}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n onClose={hidePopup}\n onParentChanged={onParentChanged}\n />,\n ),\n side,\n onToggleSide: canToggleSide\n ? () => updateGenre({ uuid: genreUuid, data: { side: side === \"pop\" ? \"core\" : \"pop\" } })\n : undefined,\n toggleSideDisabledReason: popSibling\n ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}`\n : undefined,\n additionalActions,\n }}\n extras={{\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n renderArtistLink,\n renderSongLink: resolvedRenderSongLink,\n artist: filterArtist,\n renderSummary,\n renderEssentialTracks,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n }}\n />\n {renderDangerZone?.(node)}\n </>\n ),\n };\n };\n\n const isRotationControlled = controlledAllowWheelRotation !== undefined;\n const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;\n\n type ActivePanel = {\n title: ReactNode;\n content: ReactNode;\n aside?: ReactNode;\n expanded: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n onClose: () => void;\n // A genre's or built-in panel's name lives in the search field, and the search field clears it.\n compact: boolean;\n headerAccent?: { background: string; color: string };\n };\n let activePanel: ActivePanel | null = null;\n if (overlayPanel) {\n activePanel = {\n title: overlayPanel.title,\n content: overlayPanel.content,\n aside: overlayPanel.extraContent,\n expanded: overlayPanel.expanded ?? false,\n onExpandedChange: overlayPanel.onExpandedChange,\n onClose: overlayPanel.onClose,\n compact: false,\n };\n } else if (relations) {\n const { primary } = renderGenreBody(relations);\n activePanel = {\n title: relations.node.name,\n content: primary,\n expanded: isExpanded,\n onExpandedChange: changeExpanded,\n onClose: clearSelection,\n compact: true,\n headerAccent: { background: relations.fill, color: relations.textColor },\n };\n } else if (builtInPanel) {\n activePanel = {\n title: builtInPanel.title,\n content: builtInPanel.content,\n aside: builtInPanel.extraContent,\n expanded: builtInPanel.expanded,\n onExpandedChange: builtInPanel.onExpandedChange,\n onClose: builtInPanel.onClose,\n compact: true,\n };\n }\n const isPanelShown = isTreeReady && activePanel !== null;\n\n const detailTab =\n placement === \"regions\" && activePanel?.onExpandedChange\n ? {\n edge: activePanel.expanded\n ? { label: \"Shrink panel\", onClick: () => activePanel.onExpandedChange?.(false) }\n : isCollapsed\n ? null\n : { label: \"Hide panel\", onClick: () => changeCollapsed(true) },\n center: activePanel.expanded\n ? null\n : isCollapsed\n ? { label: \"Show panel\", onClick: () => changeCollapsed(false) }\n : activePanel.onExpandedChange\n ? { label: \"Extend panel\", onClick: () => activePanel.onExpandedChange?.(true) }\n : null,\n }\n : undefined;\n\n const expandButton =\n placement === \"default\" && activePanel?.onExpandedChange ? (\n <IconButton\n aria-label={activePanel.expanded ? \"Shrink panel\" : \"Extend panel\"}\n style={activePanel.headerAccent && { color: \"inherit\" }}\n onClick={() => activePanel.onExpandedChange?.(!activePanel.expanded)}\n >\n {activePanel.expanded ? (\n <Minimize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <Maximize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n </IconButton>\n ) : null;\n\n const renderArtistFilter = () => {\n if (isLoading || !filterArtist) return null;\n return (\n <span className=\"inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm\">\n <button\n type=\"button\"\n className=\"py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline\"\n aria-label={`Show artist ${filterArtist.name}`}\n onClick={() => {\n if (selectedNodeId !== null) clearSelection();\n }}\n >\n {filterArtist.name}\n </button>\n <button\n type=\"button\"\n className=\"py-1 pl-1 pr-3 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:text-[var(--color-text)]\"\n aria-label={`Clear artist filter ${filterArtist.name}`}\n onClick={() => changeArtist(null)}\n >\n ×\n </button>\n </span>\n );\n };\n\n const actions = (\n <>\n {!isLoading && (\n <GenreSearch\n genrePlaylists={visibleGenrePlaylists}\n onSelect={handleGenreSearchSelect}\n selectedName={\n selectedGenrePlaylist?.name ??\n (typeof builtInPanel?.title === \"string\" ? builtInPanel.title : null)\n }\n onClear={\n builtInSongPanel\n ? () => changeSong(null)\n : builtInArtistPanel\n ? () => changeArtist(null)\n : handleGenreSearchClear\n }\n onPrevious={handlePrevious}\n onQueryChange={artistSearch || songSearch ? setSearchText : undefined}\n extraGroups={extraGroups}\n />\n )}\n {!isLoading && filterArtist && (\n <div className=\"hidden w-full justify-center max-md:flex\">{renderArtistFilter()}</div>\n )}\n <div className=\"contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0\">\n {!isLoading && !isControlled && (\n <SegmentedControl\n aria-label=\"Tree view mode\"\n value={viewMode}\n onChange={setViewMode}\n options={[\n { value: \"pop-core\", label: \"Pop/Core\", disabled: !canShowPopCore, title: canShowPopCore ? undefined : NO_POP_ROOT_TITLE },\n { value: \"wheel\", label: \"Wheel\" },\n { value: \"stacked\", label: \"Stacked\" },\n { value: \"outline\", label: \"Outline\", disabled: !canShowPopCore, title: canShowPopCore ? undefined : NO_POP_ROOT_TITLE },\n ]}\n />\n )}\n {!isLoading && showDisplayOptions && (\n <div\n className=\"flex items-center gap-1\"\n role=\"group\"\n aria-label=\"Tree display options\"\n >\n {!isRotationControlled && viewMode !== \"stacked\" && viewMode !== \"outline\" && (\n <Button\n variant={allowWheelRotation ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={toggleWheelRotation}\n >\n Rotation\n </Button>\n )}\n </div>\n )}\n {!isLoading && !readOnly && showAddRoot && (\n <IconTextButton\n icon={Plus}\n text=\"Add root\"\n onClick={() => handleGenreCreationAction(null)}\n />\n )}\n </div>\n </>\n );\n\n return (\n <div\n data-left-panel={isPanelShown && !isCollapsed ? undefined : \"hidden\"}\n className={[\"genre-tree-view relative flex flex-col h-full\", placement === \"regions\" && REGIONS_CLASS, className]\n .filter(Boolean)\n .join(\" \")}\n >\n <div\n className={`actions-container absolute z-30 flex items-center gap-2 ${\n placement === \"regions\"\n ? \"left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch\"\n : \"left-5 top-5\"\n }`}\n >\n {actions}\n </div>\n {!isLoading && filterArtist && (\n <div\n className={`absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${\n placement === \"regions\" ? \"top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]\" : \"top-5\"\n }`}\n >\n {renderArtistFilter()}\n </div>\n )}\n <div className=\"content-container flex-1 min-h-0 flex flex-row gap-4\">\n <div className=\"tree-view-container relative flex-1 min-w-0 flex flex-col h-full\">\n {!isLoading && viewMode === \"outline\" && floatingControlsLeading && (\n <div className=\"absolute bottom-3 right-3 z-10\">{floatingControlsLeading}</div>\n )}\n {isLoading ? (\n <GenreTreeViewSkeleton viewMode={viewMode} />\n ) : viewMode === \"wheel\" ? (\n // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,\n // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,\n // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.\n // The panel's own Zoom in button is hidden: the search bar carries it.\n <div className=\"tree-container search-overlay relative flex-1 w-full min-h-0 \">\n <GenreTreeWheelHandoff skeleton={<GenreTreeWheelSkeleton />} onRevealedChange={setIsWheelRevealed}>\n <GenrePlaylistTreeWheel\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n allowWheelRotation={allowWheelRotation}\n sizeMetric={isArtistFiltered || !canSizeByViews ? \"songs\" : deferredSizeMetric}\n busy={busy}\n floatingControlsLeading={\n floatingControlsLeading || canSizeByViews ? (\n <div className=\"flex items-center gap-2\">\n {floatingControlsLeading}\n {canSizeByViews && <SizeMetricToggle value={sizeMetric} onChange={changeSizeMetric} />}\n </div>\n ) : undefined\n }\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n />\n </GenreTreeWheelHandoff>\n </div>\n ) : viewMode === \"pop-core\" ? (\n // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.\n <div className=\"tree-container search-overlay relative flex-1 w-full min-h-0 \">\n <GenreTreeWheelHandoff skeleton={<GenreTreeWheelSkeleton />} onRevealedChange={setIsWheelRevealed}>\n <GenrePlaylistTreeWheelRadialPopCore\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n allowWheelRotation={allowWheelRotation}\n sizeMetric={isArtistFiltered || !canSizeByViews ? \"songs\" : deferredSizeMetric}\n busy={busy}\n floatingControlsLeading={\n floatingControlsLeading || canSizeByViews ? (\n <div className=\"flex items-center gap-2\">\n {floatingControlsLeading}\n {canSizeByViews && <SizeMetricToggle value={sizeMetric} onChange={changeSizeMetric} />}\n </div>\n ) : undefined\n }\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...focus}\n />\n </GenreTreeWheelHandoff>\n </div>\n ) : viewMode === \"outline\" ? (\n <div className=\"tree-container flex-1 min-h-0 w-full overflow-y-auto relative\">\n <GenrePlaylistTreeWheelRadialPopCore\n outline\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n />\n </div>\n ) : (\n <div className=\"tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]\">\n {Object.entries(groupedGenrePlaylistsByRoot).map(\n ([uuid, genrePlaylistTreePerRoot]) => {\n return (\n <div\n key={uuid}\n className=\"tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg\"\n >\n <div className=\"graph-container relative z-10 h-full\">\n <GenrePlaylistTreePerRoot\n scope={scope}\n rootUuid={uuid}\n genrePlaylistTreePerRoot={genrePlaylistTreePerRoot}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n // Each root has its own panel; null closes the one not holding the selection.\n selectedNodeId={\n uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null\n }\n readOnly={readOnly}\n wheelZoom=\"modifier\"\n defaultViewport={\n uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : undefined\n }\n viewportRestore={\n uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : undefined\n }\n onViewportChange={\n uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : undefined\n }\n />\n </div>\n </div>\n );\n },\n )}\n </div>\n )}\n {/* Always mounted: useObscuredInset observes the node once, so it must exist when the host mounts. */}\n <DetailPanel\n ref={panelRef}\n side=\"left\"\n title={activePanel?.title ?? \"\"}\n hideTitle={activePanel?.compact}\n hideClose={activePanel?.compact}\n headerAccent={activePanel?.headerAccent}\n headerActions={expandButton}\n onClose={activePanel?.onClose ?? noop}\n collapsed={isCollapsed || !isTreeReady || !activePanel}\n expanded={activePanel?.expanded}\n onExpandedChange={activePanel?.onExpandedChange}\n aside={activePanel?.aside}\n edgeTab={detailTab}\n headerClassName={activePanel?.compact ? \"pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]\" : undefined}\n >\n {activePanel?.aside && !activePanel.expanded ? (\n <>\n {activePanel.content}\n {activePanel.aside}\n </>\n ) : (\n activePanel?.content\n )}\n </DetailPanel>\n </div>\n </div>\n </div>\n );\n}\n","import type { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport type GenreTreeSizeMetric = \"songs\" | \"views\";\n\nexport const hasViewsCount = (genrePlaylists: CriteriaPlaylistSimple[]): boolean =>\n genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== undefined);\n\nexport const genrePlaylistSize = (genrePlaylist: CriteriaPlaylistSimple, metric: GenreTreeSizeMetric): number => {\n if (metric === \"songs\") return genrePlaylist.tracksCount;\n if (genrePlaylist.viewsCount === undefined) {\n throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);\n }\n return genrePlaylist.viewsCount;\n};\n","import type { GenreTreeNode } from \"@behindthemusictree/genre-tree-view\";\nimport type { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { genrePlaylistSize, type GenreTreeSizeMetric } from \"./size-metric\";\n\n/** The tree nodes of a set of genre playlists, as every tree renderer and the host's detail panel see them. */\nexport function toTreeNodes(genrePlaylists: CriteriaPlaylistSimple[], sizeMetric: GenreTreeSizeMetric): GenreTreeNode[] {\n return genrePlaylists.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),\n actionable: Boolean(genrePlaylist.criteria),\n detached: !genrePlaylist.criteria,\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n umbrella: genrePlaylist.isUmbrella,\n scene: genrePlaylist.scene ?? undefined,\n }));\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport {\n GenreTree,\n getGenreTreeColor,\n type GenreTreeViewport,\n type GenreTreeProps,\n type GenreTreeAction,\n type GenreTreeNode,\n type WheelZoomMode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { HostPanelProps } from \"./hostPanelProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\n\nexport type GenrePlaylistTreePerRootProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n rootUuid: string;\n genrePlaylistTreePerRoot: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n wheelZoom?: WheelZoomMode;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Runtime viewport restoration request. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & HostPanelProps;\n\nexport default function GenrePlaylistTreePerRoot<T extends TrackBase>({\n scope,\n className,\n rootUuid,\n genrePlaylistTreePerRoot,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n readOnly = false,\n wheelZoom,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n playback,\n ...hostPanel\n}: GenrePlaylistTreePerRootProps<T>) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists: genrePlaylistTreePerRoot });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const { showPopup } = usePopup();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylistTreePerRoot.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylist.tracksCount,\n actionable: Boolean(genrePlaylist.criteria),\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n umbrella: genrePlaylist.isUmbrella,\n })),\n [genrePlaylistTreePerRoot],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n },\n [\n genrePlaylistTreePerRoot,\n playingNodeId,\n isPlaying,\n setIsPlaying,\n play,\n showPopup,\n ],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylistTreePerRoot, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylistTreePerRoot, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n return (\n <GenreTree\n className={className}\n nodes={nodes}\n rootColor={getGenreTreeColor(rootUuid)}\n playingNodeId={playingNodeId}\n playState={isPlaying ? \"playing\" : \"paused\"}\n onPlayPause={handlePlayPause}\n onAddChild={readOnly ? undefined : handleAddChild}\n onRenameRequest={readOnly ? undefined : handleRenameRequest}\n onDeleteRequest={readOnly ? undefined : handleDeleteRequest}\n additionalActions={additionalActions}\n onNodeClick={onNodeClick}\n onNodeHover={onNodeHover}\n selectedNodeId={selectedNodeId}\n onSelectedNodeChange={onSelectedNodeChange}\n wheelZoom={wheelZoom}\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...hostPanel}\n />\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport {\n TrackListOriginType,\n useTrackList,\n type CriteriaPlaylistMinimum,\n type CriteriaPlaylistSimple,\n type TrackBase,\n type TrackListOriginFromArtist,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\n/** What a play button needs of a playlist; `criteria` is only read when playing one artist's songs in the genre. */\nexport type PlayableGenrePlaylistInput = CriteriaPlaylistMinimum & { criteria?: CriteriaPlaylistSimple[\"criteria\"] };\n\n/** What a tree node's play button plays, and which node is the one currently playing. */\nexport type GenrePlaylistPlayback = {\n playingNodeId: string | null;\n play: (genrePlaylist: PlayableGenrePlaylistInput) => Promise<void>;\n};\n\nexport type UseGenrePlaylistPlaybackOptions = {\n scope: Scope;\n genrePlaylists: CriteriaPlaylistSimple[];\n /** When set, nodes play only this artist's songs in the genre's subtree instead of the whole genre playlist. */\n artist?: { uuid: string; name: string } | null;\n};\n\nexport function useGenrePlaylistPlayback<T extends TrackBase = TrackBase>({\n scope,\n genrePlaylists,\n artist = null,\n}: UseGenrePlaylistPlaybackOptions): GenrePlaylistPlayback {\n const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList<T>();\n const origin = trackList?.origin;\n\n const playingNodeId = useMemo(() => {\n if (!origin) return null;\n if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;\n if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;\n const { genreUuid } = origin as TrackListOriginFromArtist;\n return genreUuid ? (genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null) : null;\n }, [origin, artist, genrePlaylists]);\n\n const play = useCallback(\n async (genrePlaylist: PlayableGenrePlaylistInput) => {\n if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);\n // Genre-less songs have no genre to filter on.\n if (!genrePlaylist.criteria) return;\n return playNewTrackListFromArtist(artist, scope, { ordering: \"title\", genreUuid: genrePlaylist.criteria.uuid });\n },\n [artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist],\n );\n\n return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);\n}\n","\"use client\";\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n GenreTreeWheel,\n type GenreTreeViewport,\n type GenreTreeProps,\n type GenreTreeAction,\n type GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { HostPanelProps } from \"./hostPanelProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport { genrePlaylistSize, type GenreTreeSizeMetric } from \"./size-metric\";\n\nexport type GenrePlaylistTreeWheelProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n genrePlaylists: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, clicking a chip still selects its root, but the wheel doesn't spin to the\n * anchor. Defaults to true. */\n allowWheelRotation?: boolean;\n /** What node size is proportional to; defaults to the song count. */\n sizeMetric?: GenreTreeSizeMetric;\n /** Rendered in the floating controls, beside the zoom buttons. */\n floatingControlsLeading?: ReactNode;\n /** Shows the tree's busy indicator while the data behind it is changing. */\n busy?: boolean;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Runtime viewport restoration request. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & HostPanelProps;\n\nexport default function GenrePlaylistTreeWheel<T extends TrackBase>({\n scope,\n className,\n genrePlaylists,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n readOnly = false,\n allowWheelRotation,\n sizeMetric = \"songs\",\n floatingControlsLeading,\n busy,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n playback,\n ...hostPanel\n}: GenrePlaylistTreeWheelProps<T>) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const { showPopup } = usePopup();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylists.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),\n actionable: Boolean(genrePlaylist.criteria),\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n })),\n [genrePlaylists, sizeMetric],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n },\n [\n genrePlaylists,\n playingNodeId,\n isPlaying,\n setIsPlaying,\n play,\n showPopup,\n ],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n return (\n <GenreTreeWheel\n className={className}\n nodes={nodes}\n playingNodeId={playingNodeId}\n playState={isPlaying ? \"playing\" : \"paused\"}\n onPlayPause={handlePlayPause}\n onAddChild={readOnly ? undefined : handleAddChild}\n onRenameRequest={readOnly ? undefined : handleRenameRequest}\n onDeleteRequest={readOnly ? undefined : handleDeleteRequest}\n additionalActions={additionalActions}\n onNodeClick={onNodeClick}\n onNodeHover={onNodeHover}\n selectedNodeId={selectedNodeId}\n onSelectedNodeChange={onSelectedNodeChange}\n allowWheelRotation={allowWheelRotation}\n floatingControlsLeading={floatingControlsLeading}\n busy={busy}\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...hostPanel}\n />\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n GenreTreeOutline,\n GenreTreeWheelRadialPopCore,\n type GenreTreeViewport,\n type GenreTreeProps,\n type FocusChangeReason,\n type GenreTreeAction,\n type GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { HostPanelProps } from \"./hostPanelProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport type { GenreTreeSizeMetric } from \"./size-metric\";\nimport { toTreeNodes } from \"./toTreeNodes\";\n\n/** Pop-core focus is controlled-only: pass both props or neither. Ignored in outline mode. */\nexport type GenreFocusProps =\n | { focusGenreUuid?: undefined; onFocusGenreChange?: undefined }\n | {\n /** Zooms the pop-core wheel into this genre's subtree. An unknown or non-zoomable uuid shows\n * the whole tree and fires `onFocusGenreChange(null, \"rejected\")`. */\n focusGenreUuid: string | null;\n /** Fires with the genre to focus and why: `\"zoom-in\"` / `\"back\"` (parent) / `\"previous\"` /\n * `\"root\"` from the user's zoom controls, `\"search\"` when a selection outside the zoom refocuses\n * to its top-level root (null for Mainstream Pop), and\n * `\"rejected\"` when an invalid `focusGenreUuid` is reset to null. */\n onFocusGenreChange: (uuid: string | null, reason: FocusChangeReason) => void;\n };\n\nexport type GenrePlaylistTreeWheelRadialPopCoreProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n genrePlaylists: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, clicking a chip still selects its root, but the ring doesn't spin to the\n * landing angle. Defaults to true. */\n allowWheelRotation?: boolean;\n /** What node size is proportional to; defaults to the song count. */\n sizeMetric?: GenreTreeSizeMetric;\n /** Rendered in the floating controls, beside the zoom buttons. */\n floatingControlsLeading?: ReactNode;\n /** Shows the tree's busy indicator while the data behind it is changing; ignored in outline mode. */\n busy?: boolean;\n /** When true, renders the same forest as GenreTreeOutline's nested-list text view instead of\n * the radial wheel. Defaults to false. */\n outline?: boolean;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Runtime viewport restoration request. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & GenreFocusProps &\n HostPanelProps;\n\nexport default function GenrePlaylistTreeWheelRadialPopCore<T extends TrackBase>(\n props: GenrePlaylistTreeWheelRadialPopCoreProps<T>,\n) {\n const {\n scope,\n className,\n genrePlaylists,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n readOnly = false,\n allowWheelRotation,\n sizeMetric = \"songs\",\n floatingControlsLeading,\n busy,\n outline = false,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n playback,\n } = props;\n const focus: GenreFocusProps = props;\n const hostPanel: HostPanelProps = {\n obscuredInset: props.obscuredInset,\n onNodeStyleResolverChange: props.onNodeStyleResolverChange,\n };\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n\n const nodes = useMemo(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n });\n },\n [genrePlaylists, playingNodeId, isPlaying, setIsPlaying, play],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n const treeProps = {\n className,\n nodes,\n playingNodeId,\n playState: isPlaying ? (\"playing\" as const) : (\"paused\" as const),\n onPlayPause: handlePlayPause,\n onAddChild: readOnly ? undefined : handleAddChild,\n onRenameRequest: readOnly ? undefined : handleRenameRequest,\n onDeleteRequest: readOnly ? undefined : handleDeleteRequest,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n ...hostPanel,\n };\n\n return outline ? (\n <GenreTreeOutline {...treeProps} />\n ) : (\n <GenreTreeWheelRadialPopCore\n {...treeProps}\n allowWheelRotation={allowWheelRotation}\n floatingControlsLeading={floatingControlsLeading}\n busy={busy}\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...(focus.onFocusGenreChange\n ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange }\n : { focusNodeId: undefined, onFocusChange: undefined })}\n />\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type GenreTreeWheelHandoffProps = {\n skeleton: React.ReactNode;\n children: React.ReactNode;\n /** Called with `true` once `children` is revealed, and with `false` when the handoff unmounts. */\n onRevealedChange?: (revealed: boolean) => void;\n};\n\n/**\n * Keeps `skeleton` fully opaque over `children` for the first two animation frames after\n * `children` mounts, then cross-fades to `children`.\n *\n * `children` is one of genre-tree-view's wheel components (`GenreTreeWheel` /\n * `GenreTreeWheelRadialPopCore`), which compute their own pan/zoom \"fit to frame\" in a passive\n * effect (`hasInitialFitRef`) that runs *after* their first paint. Without this handoff, that\n * first paint — unfit, `translate(0,0) scale(1)` — is briefly visible the instant the skeleton is\n * swapped out, which reads as a flash/jump right as loading finishes. Two nested\n * `requestAnimationFrame` calls reliably span that effect's own render-commit-paint cycle, so\n * `children` has had time to begin layout before it is revealed. Viewport restoration waits for\n * measurable geometry independently of this visual handoff.\n *\n * Remounting this component (e.g. via a `key` change, or the parent unmounting/remounting the\n * branch that renders it) restarts the handoff, which is what's wanted any time `children` itself\n * is about to mount fresh and re-run its own fit-to-frame effect.\n */\nexport function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }: GenreTreeWheelHandoffProps) {\n const [revealed, setRevealed] = useState(false);\n const onRevealedChangeRef = useRef(onRevealedChange);\n onRevealedChangeRef.current = onRevealedChange;\n\n useEffect(() => {\n let innerFrame = 0;\n const outerFrame = requestAnimationFrame(() => {\n innerFrame = requestAnimationFrame(() => {\n setRevealed(true);\n onRevealedChangeRef.current?.(true);\n });\n });\n return () => {\n onRevealedChangeRef.current?.(false);\n cancelAnimationFrame(outerFrame);\n cancelAnimationFrame(innerFrame);\n };\n }, []);\n\n return (\n <div className=\"relative w-full h-full\">\n <div\n className=\"absolute inset-0 transition-opacity duration-150 ease-out\"\n style={{ opacity: revealed ? 1 : 0, pointerEvents: revealed ? \"auto\" : \"none\" }}\n aria-hidden={!revealed}\n >\n {children}\n </div>\n {!revealed && (\n <div className=\"absolute inset-0\" aria-hidden=\"true\">\n {skeleton}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport { ArrowLeft, LoaderCircle, Search, X } from \"lucide-react\";\nimport { Combobox } from \"@behindthemusictree/ui\";\n\nimport { CriteriaPlaylistSimple, useGenreSearch } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport type GenreSearchExtraGroup = {\n key: string;\n /** Heading of this group; genre matches then get a \"Genres\" heading too. */\n label: string;\n items: { key: string; content: ReactNode; onSelect: () => void }[];\n /** Announced politely, e.g. \"No artists found\". */\n status?: string;\n /** Shows a spinner in place of the search icon while the host resolves this group. */\n loading?: boolean;\n};\n\nexport type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple> = {\n genrePlaylists: T[];\n onSelect: (genrePlaylist: T) => void;\n /** Name of the currently selected genre; fills the input whenever it changes. */\n selectedName?: string | null;\n /** When set, renders a \"Previous\" button left of the text and makes room for it. */\n onPrevious?: () => void;\n /** Called by the clear button, after it empties the input. */\n onClear?: () => void;\n placeholder?: string;\n /** Called with what the user types, and with \"\" on clear or pick; never when `selectedName` refills the input. */\n onQueryChange?: (query: string) => void;\n /** Further result groups (artists, songs) the host resolves from `onQueryChange`. */\n extraGroups?: GenreSearchExtraGroup[];\n};\n\nexport default function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({\n genrePlaylists,\n onSelect,\n selectedName = null,\n onClear,\n onPrevious,\n placeholder,\n onQueryChange,\n extraGroups = [],\n}: GenreSearchProps<T>) {\n const { query, setQuery, results, canClear, clear, select } = useGenreSearch({\n genrePlaylists,\n onSelect,\n selectedName,\n onClear,\n });\n const [open, setOpen] = useState(false);\n\n const active = query !== \"\" && query !== selectedName;\n const hasExtras = extraGroups.length > 0;\n const label = hasExtras ? \"Search\" : \"Search a genre\";\n const loading = active && extraGroups.some((group) => group.loading);\n const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(\". \") : \"\";\n\n const groups = [\n {\n key: \"genres\",\n label: hasExtras ? \"Genres\" : undefined,\n options: results.map((genrePlaylist) => ({\n key: genrePlaylist.uuid,\n content: (\n <>\n {genrePlaylist.name}\n {genrePlaylist.isUmbrella && (\n <span className=\"ml-2 text-xs italic text-[var(--color-text-muted)]\">umbrella</span>\n )}\n </>\n ),\n })),\n },\n ...extraGroups.map((group) => ({\n key: group.key,\n label: group.label,\n options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : [],\n })),\n ];\n\n const handlePick = (groupKey: string, optionKey: string) => {\n setOpen(false);\n if (groupKey === \"genres\") {\n const genrePlaylist = results.find((result) => result.uuid === optionKey);\n if (!genrePlaylist) return;\n select(genrePlaylist);\n onQueryChange?.(\"\");\n return;\n }\n const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);\n if (!item) return;\n setQuery(\"\");\n onQueryChange?.(\"\");\n item.onSelect();\n };\n\n return (\n <div className=\"genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full\">\n {onPrevious && (\n <button\n type=\"button\"\n aria-label=\"Previous\"\n title=\"Previous\"\n className=\"genre-search-previous absolute left-2 top-1/2 z-10 flex items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]\"\n onClick={onPrevious}\n >\n <ArrowLeft aria-hidden=\"true\" className=\"w-4 h-4\" />\n </button>\n )}\n <Combobox\n ariaLabel={label}\n listLabel={`${label} results`}\n value={query}\n placeholder={placeholder ?? `${label}…`}\n groups={groups}\n open={open}\n onOpenChange={setOpen}\n onValueChange={(value) => {\n setQuery(value);\n onQueryChange?.(value);\n }}\n onPick={handlePick}\n status={status}\n loading={loading ? \"Searching…\" : undefined}\n inputClassName={`h-[var(--layout-search-h,3rem)] ${onPrevious ? \"pl-11\" : \"pl-6\"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md`}\n className=\"genre-search-results z-40 rounded-xl shadow-md\"\n listClassName=\"max-h-[min(20rem,50dvh)]\"\n optionClassName=\"[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]\"\n />\n <div className=\"absolute right-3 top-1/2 flex items-center -translate-y-1/2\">\n <span data-loading={loading} className=\"relative p-2 text-[var(--color-text-muted)] pointer-events-none\">\n <Search\n aria-hidden=\"true\"\n className={`w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? \"opacity-0\" : \"\"}`}\n />\n <LoaderCircle\n aria-hidden=\"true\"\n className={`absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${\n loading ? \"opacity-100 delay-300 animate-spin motion-reduce:animate-none\" : \"opacity-0\"\n }`}\n />\n </span>\n {canClear && (\n <button\n type=\"button\"\n aria-label=\"Clear search\"\n className=\"flex items-center justify-center p-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]\"\n onClick={() => {\n clear();\n onQueryChange?.(\"\");\n }}\n >\n <X aria-hidden=\"true\" className=\"w-6 h-6\" />\n </button>\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport {\n type CriteriaOverview,\n type CriteriaPlaylistMinimum,\n type GenreUuid,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\nimport GenreActionBand from \"./GenreActionBand\";\nimport { GenreDetailExtras, type GenreDetailExtrasProps } from \"./GenreDetailExtras\";\nimport type { GenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\n\n/** What the action band reads of a genre's playlist: enough to play it and to know whether it can be. */\nexport type PlayableGenrePlaylist = CriteriaPlaylistMinimum & { tracksCount: number };\n\ntype GenreNodeActionBandProps = Omit<ComponentProps<typeof GenreActionBand>, \"onPlayPause\" | \"isPlaying\"> & {\n genrePlaylist: PlayableGenrePlaylist;\n playback: GenrePlaylistPlayback;\n};\n\nexport function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }: GenreNodeActionBandProps) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;\n const handlePlayPause = () => {\n if (isCurrentTrackList) {\n setIsPlaying(!isPlaying);\n return;\n }\n playback.play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n });\n };\n return (\n <GenreActionBand\n {...bandProps}\n onPlayPause={genrePlaylist.tracksCount > 0 ? handlePlayPause : undefined}\n isPlaying={isCurrentTrackList && isPlaying}\n />\n );\n}\n\nexport type GenreInfoPanelBodyProps<O extends CriteriaOverview> = {\n /** genre-tree-view's toolbar section, rendered first. */\n toolbar?: ReactNode;\n family: ReactNode;\n genreUuid: GenreUuid;\n genrePlaylist: PlayableGenrePlaylist;\n wikidataId?: string | null;\n scope: Scope;\n getBackendBaseUrl: () => string;\n playback: GenrePlaylistPlayback;\n /** Action band props other than play, which the body derives from `playback`. */\n band: Omit<GenreNodeActionBandProps, \"genrePlaylist\" | \"playback\">;\n extras?: Omit<\n GenreDetailExtrasProps<O>,\n \"family\" | \"genreUuid\" | \"genrePlaylist\" | \"wikidataId\" | \"scope\" | \"getBackendBaseUrl\"\n >;\n};\n\n/** The body of a genre's info panel: action band, then the genre's overview, family and tracks.\n * Shared by the tree's panel and the standalone GenreDetailPanel. */\nexport function GenreInfoPanelBody<O extends CriteriaOverview>({\n toolbar,\n family,\n genreUuid,\n genrePlaylist,\n wikidataId,\n scope,\n getBackendBaseUrl,\n playback,\n band,\n extras,\n}: GenreInfoPanelBodyProps<O>) {\n return (\n <>\n {toolbar}\n <GenreNodeActionBand {...band} genrePlaylist={genrePlaylist} playback={playback} />\n <GenreDetailExtras<O>\n {...extras}\n key={genreUuid}\n family={family}\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n wikidataId={wikidataId}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n />\n </>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ArrowLeftRight, MoveUpRight, Pause, Pencil, Play, Plus, ZoomIn } from \"lucide-react\";\nimport type {\n GenreTreeAction,\n GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport ShareActionButton from \"./ShareActionButton\";\n\nexport type GenreActionBandProps = {\n node: GenreTreeNode;\n /** When set, shows the Play/Pause action. */\n onPlayPause?: () => void;\n isPlaying?: boolean;\n /** When set, shows the \"Zoom in\" action. */\n onZoomIn?: () => void;\n /** When set, shows the \"Add\" (sub-genre) action. */\n onAddChild?: () => void;\n /** When set, shows the \"Rename\" action. */\n onRename?: () => void;\n /** When set, shows the \"Parent\" (change parent) action. */\n onChangeParent?: () => void;\n side?: \"core\" | \"pop\";\n /** When set, shows the \"Make pop\" / \"Make core\" action. */\n onToggleSide?: () => void;\n /** When set, disables the side toggle and explains why in its tooltip. */\n toggleSideDisabledReason?: string;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n shareTarget?: GenreTreeShareTarget;\n shareUrl?: string;\n};\n\ntype BandItem = {\n key: string;\n label: string;\n icon: ReactNode;\n onClick: (event: React.MouseEvent) => void;\n disabled?: boolean;\n title?: string;\n};\n\nexport default function GenreActionBand({\n node,\n onPlayPause,\n isPlaying = false,\n onZoomIn,\n onAddChild,\n onRename,\n onChangeParent,\n side,\n onToggleSide,\n toggleSideDisabledReason,\n additionalActions,\n shareTarget,\n shareUrl,\n}: GenreActionBandProps) {\n const items: BandItem[] = [];\n const adminItems: BandItem[] = [];\n if (onPlayPause) {\n items.push({\n key: \"play-pause\",\n label: isPlaying ? \"Pause\" : \"Play\",\n icon: isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"w-5 h-5\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"w-5 h-5\" />\n ),\n onClick: onPlayPause,\n });\n }\n if (onZoomIn) {\n items.push({\n key: \"zoom-in\",\n label: \"Focus\",\n icon: <ZoomIn aria-hidden=\"true\" className=\"w-5 h-5\" />,\n onClick: onZoomIn,\n });\n }\n if (onAddChild) {\n adminItems.push({ key: \"add-child\", label: \"Add\", icon: <Plus aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onAddChild });\n }\n if (onRename) {\n adminItems.push({ key: \"rename\", label: \"Rename\", icon: <Pencil aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onRename });\n }\n if (onChangeParent) {\n adminItems.push({ key: \"change-parent\", label: \"Parent\", icon: <MoveUpRight aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onChangeParent });\n }\n if (onToggleSide) {\n adminItems.push({\n key: \"toggle-side\",\n label: side === \"pop\" ? \"Make core\" : \"Make pop\",\n icon: <ArrowLeftRight aria-hidden=\"true\" className=\"w-5 h-5\" />,\n onClick: onToggleSide,\n disabled: !!toggleSideDisabledReason,\n title: toggleSideDisabledReason,\n });\n }\n for (const action of additionalActions?.(node) ?? []) {\n items.push({\n key: action.key,\n label: action.label(node),\n icon: action.icon(node),\n onClick: (event) => action.onClick(event, node),\n disabled: action.enabled ? !action.enabled(node) : false,\n });\n }\n if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;\n\n const renderItems = (list: BandItem[]) =>\n list.map((item) => (\n <button\n key={item.key}\n type=\"button\"\n disabled={item.disabled}\n title={item.title}\n onClick={item.onClick}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50\"\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]\">\n {item.icon}\n </span>\n {item.label}\n </button>\n ));\n\n return (\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n {(items.length > 0 || (shareTarget && shareUrl)) && (\n <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">\n {renderItems(items)}\n {shareTarget && shareUrl && <ShareActionButton target={shareTarget} url={shareUrl} />}\n </div>\n )}\n {adminItems.length > 0 && (\n <section\n aria-label=\"Admin actions\"\n className={`px-3.5 py-3 ${items.length > 0 ? \"border-t border-[var(--color-border)]\" : \"\"}`}\n >\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]\">Admin</h3>\n <div className=\"flex flex-wrap gap-1\">{renderItems(adminItems)}</div>\n </section>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { Share2 } from \"lucide-react\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport SharePopup from \"./SharePopup\";\n\ntype ShareActionButtonProps = {\n target: GenreTreeShareTarget;\n url: string;\n};\n\nexport default function ShareActionButton({ target, url }: ShareActionButtonProps) {\n const { showPopup, hidePopup } = usePopup();\n\n const openSharePopup = () => {\n showPopup(<SharePopup target={target} url={url} onClose={hidePopup} />);\n };\n\n return (\n <button\n type=\"button\"\n onClick={openSharePopup}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none\"\n aria-label={`Share ${target.name}`}\n title={`Share ${target.name}`}\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]\">\n <Share2 aria-hidden=\"true\" className=\"w-5 h-5\" />\n </span>\n Share\n </button>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Link2, Share2 } from \"lucide-react\";\nimport { FaWhatsapp } from \"react-icons/fa\";\nimport { SiGmail } from \"react-icons/si\";\nimport BasePopup from \"../popup/BasePopup\";\nimport Toast from \"../popup/Toast\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport ShareDestinationButton from \"./ShareDestinationButton\";\nimport SharePreviewCard from \"./SharePreviewCard\";\n\ntype SharePopupProps = {\n target: GenreTreeShareTarget;\n url: string;\n onClose: () => void;\n};\n\ntype CopyStatus = \"idle\" | \"failed\";\n\nfunction resolveAbsoluteUrl(url: string): string | undefined {\n try {\n const parsed = new URL(url, window.location.href);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\" ? parsed.href : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function copyText(text: string): Promise<boolean> {\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return true;\n }\n } catch {\n // Use the legacy copy path when clipboard access is denied or unavailable.\n }\n\n const input = document.createElement(\"textarea\");\n input.value = text;\n input.setAttribute(\"readonly\", \"\");\n input.style.position = \"fixed\";\n input.style.opacity = \"0\";\n document.body.append(input);\n input.select();\n\n try {\n return document.execCommand(\"copy\");\n } catch {\n return false;\n } finally {\n input.remove();\n }\n}\n\nexport default function SharePopup({ target, url, onClose }: SharePopupProps) {\n const [absoluteUrl, setAbsoluteUrl] = useState(url);\n const [copyStatus, setCopyStatus] = useState<CopyStatus>(\"idle\");\n const [canUseNativeShare, setCanUseNativeShare] = useState(false);\n const [shareError, setShareError] = useState<string | null>(null);\n const [toastId, setToastId] = useState(0);\n\n useEffect(() => {\n setAbsoluteUrl(resolveAbsoluteUrl(url) ?? \"\");\n setCopyStatus(\"idle\");\n setShareError(null);\n }, [url]);\n\n useEffect(() => {\n setCanUseNativeShare(typeof navigator !== \"undefined\" && typeof navigator.share === \"function\");\n }, []);\n\n const destinationUrls = useMemo(() => {\n if (!absoluteUrl) return null;\n const message = `${target.name} — ${absoluteUrl}`;\n return {\n whatsapp: `https://wa.me/?text=${encodeURIComponent(message)}`,\n gmail: `https://mail.google.com/mail/?view=cm&fs=1&su=${encodeURIComponent(`Share: ${target.name}`)}&body=${encodeURIComponent(message)}`,\n };\n }, [absoluteUrl, target.name]);\n\n const handleCopy = async () => {\n if (!absoluteUrl) {\n setCopyStatus(\"failed\");\n return;\n }\n setShareError(null);\n const copied = await copyText(absoluteUrl);\n setCopyStatus(copied ? \"idle\" : \"failed\");\n if (copied) setToastId((id) => id + 1);\n };\n\n const dismissToast = useCallback(() => setToastId(0), []);\n\n const handleNativeShare = async () => {\n if (!absoluteUrl || !navigator.share) return;\n setShareError(null);\n try {\n await navigator.share({ title: target.name, url: absoluteUrl });\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n setShareError(\"Could not open the share options.\");\n }\n };\n\n return (\n <>\n <BasePopup\n title=\"Share\"\n onClose={onClose}\n isDismissable\n trapFocus\n closeOnEscape\n closeOnBackdrop\n className=\"!w-full !max-w-[28rem] overflow-hidden !rounded-[2rem] !bg-[var(--color-surface)] shadow-2xl\"\n overlayClassName=\"!bg-black/50 p-4 backdrop-blur-[2px]\"\n titleClassName=\"!rounded-none !bg-transparent !px-6 !pb-2 !pt-6\"\n titleTextClassName=\"!text-xl !font-semibold !text-[var(--color-text)]\"\n closeButtonClassName=\"!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]\"\n contentClassName=\"!rounded-none !bg-transparent !px-6 !pb-6 !pt-2\"\n >\n <div className=\"flex flex-col gap-4\">\n <SharePreviewCard target={target} url={absoluteUrl || url} />\n <div className=\"grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4\">\n <ShareDestinationButton\n label=\"Copy Link\"\n icon={<Link2 aria-hidden=\"true\" className=\"h-6 w-6\" />}\n onClick={() => void handleCopy()}\n ariaLabel=\"Copy link\"\n disabled={!absoluteUrl}\n />\n <ShareDestinationButton\n label=\"WhatsApp\"\n icon={<FaWhatsapp aria-hidden=\"true\" className=\"h-9 w-9 text-[#25D366]\" />}\n href={destinationUrls?.whatsapp}\n ariaLabel={`Share ${target.name} via WhatsApp`}\n disabled={!absoluteUrl}\n />\n <ShareDestinationButton\n label=\"Gmail\"\n icon={<SiGmail aria-hidden=\"true\" className=\"h-8 w-8 text-[#EA4335]\" />}\n href={destinationUrls?.gmail}\n ariaLabel={`Email ${target.name} with Gmail`}\n disabled={!absoluteUrl}\n />\n {canUseNativeShare && (\n <ShareDestinationButton\n label=\"More\"\n icon={<Share2 aria-hidden=\"true\" className=\"h-6 w-6\" />}\n onClick={() => void handleNativeShare()}\n ariaLabel=\"More sharing options\"\n disabled={!absoluteUrl}\n />\n )}\n </div>\n {copyStatus === \"failed\" && (\n <p role=\"alert\" className=\"text-center text-sm text-[var(--color-red-500)]\">\n Could not copy the link.\n </p>\n )}\n {shareError && (\n <p role=\"alert\" className=\"text-center text-sm text-[var(--color-red-500)]\">\n {shareError}\n </p>\n )}\n </div>\n </BasePopup>\n {toastId > 0 && <Toast key={toastId} message=\"Link copied\" onClose={dismissToast} />}\n </>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nexport type ShareDestinationButtonProps = {\n label: string;\n icon: ReactNode;\n href?: string;\n onClick?: () => void;\n disabled?: boolean;\n ariaLabel: string;\n};\n\nconst className =\n \"group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50\";\n\nfunction Contents({ icon, label }: Pick<ShareDestinationButtonProps, \"icon\" | \"label\">) {\n return (\n <>\n <span className=\"flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]\">\n {icon}\n </span>\n <span className=\"max-w-full truncate\">{label}</span>\n </>\n );\n}\n\nexport default function ShareDestinationButton({\n label,\n icon,\n href,\n onClick,\n disabled = false,\n ariaLabel,\n}: ShareDestinationButtonProps) {\n if (href) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" aria-label={ariaLabel} className={className}>\n <Contents icon={icon} label={label} />\n </a>\n );\n }\n\n return (\n <button type=\"button\" onClick={onClick} disabled={disabled} aria-label={ariaLabel} className={className}>\n <Contents icon={icon} label={label} />\n </button>\n );\n}\n","\"use client\";\n\nimport { Music, Music2, UserRound } from \"lucide-react\";\nimport type { GenreTreeShareTarget } from \"./share\";\n\ntype SharePreviewCardProps = {\n target: GenreTreeShareTarget;\n url: string;\n};\n\nexport default function SharePreviewCard({ target, url }: SharePreviewCardProps) {\n const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];\n const typeLabel = { genre: \"Genre\", artist: \"Artist\", song: \"Song\" }[target.kind];\n\n return (\n <div className=\"rounded-3xl bg-[var(--color-surface-muted)] p-4\">\n <div className=\"flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg\">\n <div className=\"flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]\">\n <Icon aria-hidden=\"true\" className=\"h-10 w-10\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col justify-center gap-1 p-4\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]\">{typeLabel}</span>\n <span className=\"truncate text-base font-semibold text-[var(--color-text)]\" title={target.name}>\n {target.name}\n </span>\n <span className=\"truncate text-sm text-[var(--color-primary)]\" title={url}>\n {url}\n </span>\n </div>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { z } from \"zod\";\n\nimport { CriteriaOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { useFetchGenreOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaPlaylistMinimum, type GenreUuid } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { ClampedText } from \"./ClampedText\";\nimport { GenrePlaylistTracks, type GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nimport type { RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\nimport { SiWikidata, SiWikipedia } from \"react-icons/si\";\n\nexport type GenreDetailSlots<O extends CriteriaOverview> = {\n /** Replaces the body of the default Summary section; the title and frame stay. */\n renderSummary?: (overview: O) => ReactNode;\n /** Replaces the body of the default Essential section (e.g. \"Essential songs\"); the title and frame stay. */\n renderEssentialTracks?: (overview: O) => ReactNode;\n};\n\nexport type GenreDetailExtrasProps<O extends CriteriaOverview> = GenreDetailSlots<O> & GenrePlaylistTracksSlots & {\n genreUuid: GenreUuid;\n genrePlaylist: CriteriaPlaylistMinimum;\n wikidataId?: string | null;\n scope: Scope;\n getBackendBaseUrl: () => string;\n criteriaOverviewSchema?: z.ZodType<O, unknown>;\n renderGenreDetailExtras?: (overview: O) => ReactNode;\n renderArtistLink?: RenderArtistLink;\n renderSongLink?: RenderSongLink;\n /** When set, the tracks list shows only this artist's songs in the genre. */\n artist?: { uuid: string; name: string } | null;\n /** genre-tree-view's Family section, placed right after the Summary. */\n family: ReactNode;\n};\n\n// Owns the overview fetch so its pending → success transitions re-render only the info panel,\n// not the tree that renders it.\nexport function GenreDetailExtras<O extends CriteriaOverview>({\n genreUuid,\n genrePlaylist,\n wikidataId,\n scope,\n getBackendBaseUrl,\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n renderArtistLink,\n renderSongLink,\n renderSummary,\n renderEssentialTracks,\n family,\n ...trackSlots\n}: GenreDetailExtrasProps<O>) {\n const trackNoun = useTrackNoun();\n const { data: overview, isPending } = useFetchGenreOverview<O>(\n genreUuid,\n scope,\n getBackendBaseUrl,\n criteriaOverviewSchema,\n );\n\n if (isPending) {\n return (\n <>\n <PanelSection title=\"Summary\">\n <div aria-busy=\"true\" data-testid=\"genre-detail-extras-skeleton\">\n <Skeleton className=\"h-4 w-3/4\" />\n </div>\n </PanelSection>\n {family}\n </>\n );\n }\n if (!overview) return <>{family}</>;\n\n const { summary, summarySourceUrl, essentialSongs } = overview;\n const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;\n\n return (\n <>\n {(summarySourceUrl || wikidataUrl) && (\n <PanelSection title=\"Links\">\n <div className=\"flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]\">\n {summarySourceUrl && (\n <a\n href={summarySourceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]\"\n >\n <SiWikipedia aria-hidden=\"true\" className=\"h-3.5 w-3.5\" />\n Wikipedia\n </a>\n )}\n {wikidataUrl && (\n <a\n href={wikidataUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]\"\n >\n <SiWikidata aria-hidden=\"true\" className=\"h-3.5 w-3.5\" />\n Wikidata\n </a>\n )}\n </div>\n </PanelSection>\n )}\n <PanelSection title=\"Summary\">\n {renderSummary ? (\n renderSummary(overview)\n ) : (\n <ClampedText key={genreUuid} className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">\n {summary ?? \"—\"}\n </ClampedText>\n )}\n </PanelSection>\n {family}\n {renderGenreDetailExtras?.(overview)}\n {(renderEssentialTracks || essentialSongs) && (\n <PanelSection title={`Essential ${trackNoun.plural}`}>\n {renderEssentialTracks ? (\n renderEssentialTracks(overview)\n ) : essentialSongs && essentialSongs.length > 0 ? (\n <ul className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">\n {essentialSongs.map((track) => (\n <li key={track.uuid} className=\"py-1\">\n {track.title}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">—</p>\n )}\n </PanelSection>\n )}\n <GenrePlaylistTracks\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n {...trackSlots}\n />\n </>\n );\n}\n","\"use client\";\n\nimport { useLayoutEffect, useRef, useState } from \"react\";\n\ntype ClampedTextProps = {\n children: string;\n className?: string;\n};\n\nexport function ClampedText({ children, className = \"\" }: ClampedTextProps) {\n const ref = useRef<HTMLParagraphElement>(null);\n const [expanded, setExpanded] = useState(false);\n const [overflows, setOverflows] = useState(false);\n\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element || expanded) return;\n setOverflows(element.scrollHeight > element.clientHeight);\n }, [children, expanded]);\n\n return (\n <>\n <p ref={ref} className={`${className} ${expanded ? \"\" : \"line-clamp-3\"}`}>\n {children}\n </p>\n {(overflows || expanded) && (\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded((value) => !value)}\n className=\"mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]\"\n >\n {expanded ? \"Show less\" : \"Show more\"}\n </button>\n )}\n </>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState, type ReactNode } from \"react\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\nimport { FaPlay, FaPause } from \"react-icons/fa\";\nimport { PanelSection, RingLoader, Skeleton } from \"@behindthemusictree/ui\";\n\nimport { useSession } from \"@behindthemusictree/app-kit/auth\";\nimport { useFetchWrapper } from \"@behindthemusictree/app-kit/transport\";\nimport { PaginatedResponseSchema } from \"@behindthemusictree/app-kit/transport\";\nimport { parseWithLog } from \"@behindthemusictree/app-kit/transport\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { PlayStates } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { genrePlaylistQueryKeys } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { libraryEndpoints, libraryQueryKeys } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaPlaylistMinimum, type GenreUuid } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { isSong, songUnplayableReason, SongDetailedSchema } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { GenrePlaylistTracksPage, useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { MusicBrainzRecordingLink } from \"./MusicBrainzRecordingLink\";\nimport { ArtistNames, RenderArtistLink, type RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nconst CATALOGUE_PAGE_SIZE = 100;\n// withTotal=false: the backend skips COUNT(*) and pages by keyset cursor, so huge genre subtrees\n// stay O(page) at any depth; page numbers and totals are then null.\nconst SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({\n overallTotal: z.number().nullable(),\n page: z.number().nullable(),\n totalPages: z.number().nullable(),\n});\n\n// Container query on the list (arbitrary variant: Tailwind 3 core has no `@container`): once the\n// list is wide (expanded info panel), title, artist and album become columns instead of one line.\n// Class names stay literal so Tailwind's scanner sees them.\nconst TEXT_CELL = \"min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2\";\nconst TITLE_CELL = \"[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate\";\nconst COLUMN_CELL = \"[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate\";\nconst ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;\nconst SEPARATOR = \"[@container(min-width:40rem)]:hidden\";\n\nconst nextCursor = (next: string): string => {\n const cursor = new URL(next).searchParams.get(\"cursor\");\n if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);\n return cursor;\n};\n\n// Songs are not narrowed to SongDetailed here: \"me\" scope lists uploaded tracks too.\nexport type GenrePlaylistTracksSlots = {\n /** Rendered first in each row, e.g. a selection checkbox. `index` is the row's position in the loaded list. */\n renderSongLeading?: (song: TrackBase, index: number) => ReactNode;\n /** Rendered last in each row. The song's own genre is `song.genre`. */\n renderSongActions?: (song: TrackBase) => ReactNode;\n /** Rendered between the title and the list, e.g. a \"+ Link\" button or a selection bar. */\n renderTracksToolbar?: () => ReactNode;\n};\n\nexport type GenrePlaylistTracksProps = GenrePlaylistTracksSlots & {\n genreUuid: GenreUuid;\n genrePlaylist: CriteriaPlaylistMinimum;\n scope: Scope;\n getBackendBaseUrl: () => string;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, song titles render through it (e.g. open the song panel) instead of linking to MusicBrainz. */\n renderSongLink?: RenderSongLink;\n /** When set, lists and plays only this artist's songs in the genre. */\n artist?: { uuid: string; name: string } | null;\n};\n\n/**\n * \"reference\" lists the genre's whole songs catalogue (genre subtree), playable or not, while\n * playing goes through the playable-only playlist. Other scopes have no catalogue endpoint; their\n * playlist is the catalogue (e.g. hear's uploaded tracks are always playable).\n */\nexport function GenrePlaylistTracks({\n genreUuid,\n genrePlaylist,\n scope,\n getBackendBaseUrl,\n renderArtistLink,\n renderSongLink,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n artist = null,\n}: GenrePlaylistTracksProps) {\n const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList();\n const { session, sessionRestored } = useSession();\n const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer();\n const { showPopup } = usePopup();\n const trackNoun = useTrackNoun();\n const { uuid } = genrePlaylist;\n\n const { fetch } = useFetchWrapper(getBackendBaseUrl);\n\n const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({\n queryKey:\n scope === \"me\"\n ? genrePlaylistQueryKeys.me.tracks(uuid)\n : artist\n ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), \"artist\", artist.uuid]\n : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),\n queryFn: async ({ pageParam }): Promise<Omit<GenrePlaylistTracksPage<TrackBase>, \"total\" | \"nextPage\"> & { total: number | null; nextPage: number | string | null }> => {\n if (scope === \"me\") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));\n const response = await fetch(\n libraryEndpoints.reference.songs.list(),\n true,\n false,\n {},\n { genre: genreUuid, ...(artist && { artist: artist.uuid }), pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...(pageParam && { cursor: pageParam }) },\n );\n const parsed = parseWithLog(SongPageSchema, response, \"GenrePlaylistTracks\");\n return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };\n },\n initialPageParam: scope === \"me\" ? 1 : \"\",\n getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,\n enabled: scope !== \"me\" || (sessionRestored && !!session?.accessToken),\n });\n\n // State, not a ref object: PanelSection unmounts its body when collapsed, so the observer must\n // re-attach when the sentinel remounts.\n const [sentinel, setSentinel] = useState<HTMLLIElement | null>(null);\n useEffect(() => {\n if (!hasNextPage || isFetchingNextPage || !sentinel) return;\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n void fetchNextPage();\n }\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasNextPage, isFetchingNextPage, fetchNextPage, sentinel]);\n\n const pages = data?.pages ?? [];\n const tracks = pages.flatMap((page) => page.tracks);\n const total = pages[0]?.total ?? undefined;\n\n const play = (startSongUuid: string) => {\n const started = artist\n ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: \"title\", genreUuid })\n : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });\n started.catch((error) => {\n console.error(\"Failed to play genre playlist:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n };\n\n return (\n <PanelSection title={`${capitalize(trackNoun.plural)}${total !== undefined ? ` (${total})` : \"\"}`}>\n <div>\n {renderTracksToolbar?.()}\n {isPending ? (\n <Skeleton className=\"h-4 w-3/4\" aria-busy=\"true\" />\n ) : isError && tracks.length === 0 ? (\n <p role=\"alert\">\n Couldn't load {trackNoun.plural}.{\" \"}\n <button type=\"button\" className=\"underline\" onClick={() => void refetch()}>\n Retry\n </button>\n </p>\n ) : tracks.length === 0 ? (\n <p>—</p>\n ) : (\n <ul className=\"mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]\" aria-label={`${genrePlaylist.name} ${trackNoun.plural}`}>\n {tracks.map((track, index) => {\n const label = track.artists?.length\n ? `${track.title} — ${track.artists.map((artist) => artist.name).join(\", \")}`\n : track.title;\n const isCurrent = playerTrackObject?.track.id === track.uuid;\n const isPlaying = isCurrent && playState === PlayStates.PLAYING;\n const text = (\n <span className={TEXT_CELL}>\n <span className={TITLE_CELL}>\n {renderSongLink ? (\n renderSongLink(track)\n ) : isSong(track) ? (\n <MusicBrainzRecordingLink song={track} className=\"hover:underline focus-visible:underline\">\n {track.title}\n </MusicBrainzRecordingLink>\n ) : (\n track.title\n )}\n </span>\n {track.artists?.length ? <span className={SEPARATOR}>{\" — \"}</span> : null}\n <span className={COLUMN_CELL}>\n {track.artists?.length ? (\n <ArtistNames artists={track.artists} renderArtistLink={renderArtistLink} />\n ) : null}\n </span>\n {track.album && <span className={ALBUM_CELL}>{track.album.name}</span>}\n </span>\n );\n const leading = renderSongLeading?.(track, index);\n const actions = renderSongActions?.(track);\n const isUnplayable = isSong(track) && songUnplayableReason(track) !== null;\n if (isUnplayable) {\n return (\n <li key={track.uuid} className=\"flex min-h-8 items-center gap-1 px-1 opacity-50\">\n {leading}\n <button\n type=\"button\"\n disabled\n aria-disabled=\"true\"\n aria-label={label}\n className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed\"\n >\n <FaPlay size={10} />\n </button>\n {text}\n {actions}\n </li>\n );\n }\n return (\n <li\n key={track.uuid}\n className={`group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? \"font-medium text-[var(--color-primary)]\" : \"\"}`}\n >\n {leading}\n <button\n type=\"button\"\n aria-label={`${isPlaying ? \"Pause\" : \"Play\"} ${label}`}\n className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== PlayStates.STOPPED ? \"opacity-100\" : \"opacity-0 [@media(pointer:coarse)]:opacity-100\"}`}\n onClick={() => (isCurrent ? handlePlayPauseAction() : play(track.uuid))}\n >\n {isCurrent && playState === PlayStates.LOADING ? (\n <RingLoader size={10} />\n ) : isPlaying ? (\n <FaPause size={10} />\n ) : (\n <FaPlay size={10} />\n )}\n </button>\n {text}\n {actions}\n </li>\n );\n })}\n {hasNextPage && <li ref={setSentinel} aria-hidden=\"true\" className=\"h-4\" />}\n </ul>\n )}\n </div>\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { SongDetailed, musicBrainzRecordingUrl } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport function MusicBrainzRecordingLink({\n song,\n children,\n className = \"\",\n}: {\n song: SongDetailed;\n children: ReactNode;\n className?: string;\n}) {\n const url = musicBrainzRecordingUrl(song);\n if (!url) return <>{children}</>;\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`View ${song.title} on MusicBrainz`}\n className={className}\n onClick={(event) => event.stopPropagation()}\n >\n {children}\n </a>\n );\n}\n","\"use client\";\n\nimport { useEffect, useMemo } from \"react\";\n\nimport {\n CriteriaPlaylistSimple,\n ReparentCandidate,\n type GenreUuid,\n getReparentCandidates,\n useUpdateGenre,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport BasePopup from \"../popup/BasePopup\";\nimport GenreSearch from \"./GenreSearch\";\n\nexport type ParentChange = { genre: GenreUuid; from: GenreUuid | null; to: GenreUuid };\n\ntype ChangeParentPopupProps = {\n genrePlaylist: ReparentCandidate;\n genrePlaylists: CriteriaPlaylistSimple[];\n scope: Scope;\n getBackendBaseUrl: () => string;\n onClose: () => void;\n onParentChanged?: (change: ParentChange) => void;\n};\n\nexport default function ChangeParentPopup({\n genrePlaylist,\n genrePlaylists,\n scope,\n getBackendBaseUrl,\n onClose,\n onParentChanged,\n}: ChangeParentPopupProps) {\n const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);\n const candidates = useMemo(\n () => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),\n [genrePlaylists, genrePlaylist.uuid],\n );\n\n const handleSelect = (newParent: ReparentCandidate) => {\n const genre = genrePlaylist.criteria.uuid;\n const parentPlaylistUuid = genrePlaylist.parent?.uuid;\n const from = parentPlaylistUuid\n ? (genrePlaylists.find(({ uuid }) => uuid === parentPlaylistUuid)?.criteria?.uuid ?? null)\n : null;\n mutate(\n { uuid: genre, data: { parent: newParent.criteria.uuid } },\n {\n onSuccess: () => {\n onClose();\n onParentChanged?.({ genre, from, to: newParent.criteria.uuid });\n },\n },\n );\n };\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onClose]);\n\n return (\n <BasePopup title={`Change parent of ${genrePlaylist.name}`} onClose={onClose} showCancelButton fitContent>\n <p className=\"mb-3 text-sm text-[var(--color-text-muted)]\">\n Current parent: {genrePlaylist.parent?.name ?? \"none\"}\n </p>\n <div aria-busy={isPending} className={isPending ? \"pointer-events-none opacity-60\" : undefined}>\n <GenreSearch\n genrePlaylists={candidates}\n onSelect={handleSelect}\n placeholder=\"Search a new parent…\"\n />\n </div>\n {formErrors.length > 0 && (\n <p role=\"alert\" className=\"mt-3 text-sm text-[var(--color-red-500)]\">\n {formErrors.map(({ message }) => message).join(\" \")}\n </p>\n )}\n </BasePopup>\n );\n}\n","\"use client\";\n\nimport { Skeleton } from \"@behindthemusictree/ui\";\nimport { useArtist, type RenderArtistLink, type RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport ArtistHeader from \"./ArtistHeader\";\nimport ArtistPhoto from \"./ArtistPhoto\";\nimport ArtistMembers from \"./ArtistMembers\";\nimport ArtistDiscography from \"./ArtistDiscography\";\nimport ArtistSongs from \"./ArtistSongs\";\nimport ShareActionButton from \"../ShareActionButton\";\nimport type { GetGenreTreeShareUrl } from \"../share\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { OverlayPanel } from \"../overlayPanel\";\n\nexport interface ArtistPanelOptions {\n uuid: string | null;\n scope?: Scope;\n getBackendBaseUrl: () => string;\n getShareUrl?: GetGenreTreeShareUrl;\n /** Renders member and song artist names. */\n renderArtistLink: RenderArtistLink;\n renderSongLink?: RenderSongLink;\n expanded: boolean;\n onExpandedChange: (expanded: boolean) => void;\n onClose: () => void;\n}\n\n/** The artist detail overlay panel for `uuid`, or undefined when no artist is set. */\nexport function useArtistPanel({\n uuid,\n scope,\n getBackendBaseUrl,\n getShareUrl,\n renderArtistLink,\n renderSongLink,\n expanded,\n onExpandedChange,\n onClose,\n}: ArtistPanelOptions): OverlayPanel | undefined {\n const result = useArtist(uuid, getBackendBaseUrl);\n\n const panel = { expanded, onExpandedChange, onClose };\n switch (result.status) {\n case \"idle\":\n return undefined;\n case \"loading\":\n return {\n ...panel,\n title: \"Artist\",\n content: (\n <div aria-busy=\"true\" className=\"flex flex-col gap-2.5 px-6 py-2.5\">\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-16 mt-2.5\" />\n <Skeleton className=\"h-16\" />\n </div>\n ),\n };\n case \"not-found\":\n return {\n ...panel,\n title: \"Artist not found\",\n content: <p className=\"px-6 py-2.5 text-[var(--color-text-muted)]\">This artist does not exist.</p>,\n };\n case \"error\":\n return {\n ...panel,\n title: \"Artist\",\n content: <p className=\"px-6 py-2.5 text-red-600\">Could not load this artist.</p>,\n };\n case \"success\": {\n const { artist } = result;\n const shareTarget = scope\n ? { kind: \"artist\" as const, uuid: artist.uuid, name: artist.name, scope }\n : undefined;\n const shareUrl = shareTarget ? getShareUrl?.(shareTarget) : undefined;\n return {\n ...panel,\n title: artist.name,\n content: (\n <>\n {shareUrl && (\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">\n {shareTarget && <ShareActionButton target={shareTarget} url={shareUrl} />}\n </div>\n </div>\n )}\n {artist.image && <ArtistPhoto image={artist.image} name={artist.name} />}\n <ArtistHeader artist={artist} />\n <ArtistMembers title=\"Members\" memberships={artist.members} renderArtistLink={renderArtistLink} />\n <ArtistMembers title=\"Member of\" memberships={artist.memberOf} renderArtistLink={renderArtistLink} />\n {artist.tracksCount > 0 && (\n <ArtistSongs\n artist={{ uuid: artist.uuid, name: artist.name }}\n getBackendBaseUrl={getBackendBaseUrl}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n )}\n </>\n ),\n extraContent: artist.albums.length > 0 ? <ArtistDiscography albums={artist.albums} /> : undefined,\n };\n }\n }\n}\n","import type { ReactNode } from \"react\";\nimport { FieldList } from \"@behindthemusictree/ui\";\nimport type { ArtistDetailed } from \"@behindthemusictree/app-kit/genre-tree\";\nimport ArtistLinks, { artistLinks } from \"./ArtistLinks\";\nimport { formatYears } from \"./formatYears\";\n\nconst countryNames = new Intl.DisplayNames([\"en\"], { type: \"region\" });\n\nexport default function ArtistHeader({ artist }: { artist: ArtistDetailed }) {\n const fields: { label: string; value: ReactNode }[] = [];\n if (artist.type) fields.push({ label: \"Type\", value: artist.type.charAt(0).toUpperCase() + artist.type.slice(1) });\n const country = artist.country && countryNames.of(artist.country);\n if (country) fields.push({ label: \"Country\", value: country });\n const years = formatYears(artist.beginYear, artist.endYear, artist.ended);\n if (years) fields.push({ label: \"Active\", value: years });\n const links = artistLinks(artist);\n if (links.length > 0) fields.push({ label: \"Links\", value: <ArtistLinks links={links} /> });\n\n if (fields.length === 0) return null;\n return <FieldList items={fields} />;\n}\n","import type { ComponentType } from \"react\";\nimport { Globe, Share2 } from \"lucide-react\";\nimport {\n SiBandcamp,\n SiDiscogs,\n SiMusicbrainz,\n SiSoundcloud,\n SiSpotify,\n SiWikidata,\n SiWikipedia,\n SiYoutube,\n} from \"react-icons/si\";\nimport { Chip } from \"@behindthemusictree/ui\";\nimport type { ArtistDetailed, ArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nconst LINK_KINDS: Record<ArtistLink[\"kind\"], { label: string; Icon: ComponentType<{ className?: string }> }> = {\n official: { label: \"Official website\", Icon: Globe },\n wikipedia: { label: \"Wikipedia\", Icon: SiWikipedia },\n wikidata: { label: \"Wikidata\", Icon: SiWikidata },\n spotify: { label: \"Spotify\", Icon: SiSpotify },\n youtube: { label: \"YouTube\", Icon: SiYoutube },\n bandcamp: { label: \"Bandcamp\", Icon: SiBandcamp },\n soundcloud: { label: \"SoundCloud\", Icon: SiSoundcloud },\n discogs: { label: \"Discogs\", Icon: SiDiscogs },\n social: { label: \"Social\", Icon: Share2 },\n};\n\nfunction socialLabel(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, \"\");\n } catch {\n return LINK_KINDS.social.label;\n }\n}\n\ninterface ArtistLinkItem {\n key: string;\n url: string;\n Icon: ComponentType<{ className?: string }>;\n label: string;\n}\n\nexport function artistLinks(artist: ArtistDetailed): ArtistLinkItem[] {\n const links = artist.links.map(({ kind, url }) => ({\n key: `${kind}-${url}`,\n url,\n Icon: LINK_KINDS[kind].Icon,\n label: kind === \"social\" ? socialLabel(url) : LINK_KINDS[kind].label,\n }));\n if (artist.musicbrainzArtistId) {\n links.push({\n key: \"musicbrainz\",\n url: `https://musicbrainz.org/artist/${artist.musicbrainzArtistId}`,\n Icon: SiMusicbrainz,\n label: \"MusicBrainz\",\n });\n }\n return links;\n}\n\nexport default function ArtistLinks({ links }: { links: ArtistLinkItem[] }) {\n return (\n <ul className=\"flex flex-wrap gap-1.5\">\n {links.map(({ key, url, Icon, label }) => (\n <li key={key}>\n <Chip href={url} icon={<Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />}>\n {label}\n </Chip>\n </li>\n ))}\n </ul>\n );\n}\n","/** \"1994–present\", \"1994–2008\", \"?–2008\", or null when nothing is known. */\nexport function formatYears(beginYear: number | null, endYear: number | null, ended: boolean): string | null {\n if (beginYear === null && endYear === null) return ended ? \"ended\" : null;\n const end = endYear ?? (ended ? \"?\" : \"present\");\n return `${beginYear ?? \"?\"}–${end}`;\n}\n","import type { ReactNode } from \"react\";\nimport type { ArtistImage } from \"@behindthemusictree/app-kit/genre-tree\";\n\nfunction ExternalLink({ href, children }: { href: string; children: ReactNode }) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" className=\"underline-offset-2 hover:underline\">\n {children}\n </a>\n );\n}\n\nexport default function ArtistPhoto({ image, name }: { image: ArtistImage; name: string }) {\n return (\n <figure className=\"flex flex-col gap-1.5 px-6 py-2.5\">\n <img src={image.url} alt={name} className=\"h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded\" />\n <figcaption className=\"text-xs text-[var(--color-text-muted)]\">\n Photo: <ExternalLink href={image.pageUrl}>{image.author ?? \"Wikimedia Commons\"}</ExternalLink>,{\" \"}\n {image.licenseUrl ? <ExternalLink href={image.licenseUrl}>{image.license}</ExternalLink> : image.license}\n </figcaption>\n </figure>\n );\n}\n","import type { ArtistMembership, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelGroup, PanelSection } from \"@behindthemusictree/ui\";\nimport { formatYears } from \"./formatYears\";\n\ninterface MembershipListProps {\n title: string;\n memberships: ArtistMembership[];\n renderArtistLink: RenderArtistLink;\n}\n\nfunction MembershipList({ title, memberships, renderArtistLink }: MembershipListProps) {\n if (memberships.length === 0) return null;\n return (\n <PanelGroup title={title}>\n <ul>\n {memberships.map((membership, index) => {\n const details = [\n formatYears(membership.beginYear, membership.endYear, membership.ended),\n ...membership.roles,\n ];\n const detailsText = details.filter(Boolean).join(\" · \");\n return (\n // A member can have several periods in the same group.\n <li key={`${membership.artist.uuid}-${index}`} className=\"truncate py-0.5\" title={detailsText || undefined}>\n <span className=\"font-medium text-[var(--color-text)]\">{renderArtistLink(membership.artist)}</span>\n {detailsText && <span className=\"text-[var(--color-text-muted)]\"> — {detailsText}</span>}\n </li>\n );\n })}\n </ul>\n </PanelGroup>\n );\n}\n\nexport default function ArtistMembers({ title, memberships, renderArtistLink }: MembershipListProps) {\n if (memberships.length === 0) return null;\n const isCurrent = (membership: ArtistMembership) => !membership.ended && membership.endYear === null;\n\n return (\n <PanelSection title={title}>\n <MembershipList title=\"Current\" memberships={memberships.filter(isCurrent)} renderArtistLink={renderArtistLink} />\n <MembershipList\n title=\"Past\"\n memberships={memberships.filter((membership) => !isCurrent(membership))}\n renderArtistLink={renderArtistLink}\n />\n </PanelSection>\n );\n}\n","import type { ArtistAlbum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelGroup, PanelSection } from \"@behindthemusictree/ui\";\n\nconst GROUPS = [\"Albums\", \"EPs\", \"Singles\", \"Live\", \"Compilations\", \"Other\"] as const;\ntype Group = (typeof GROUPS)[number];\n\nconst PRIMARY_GROUPS: Record<string, Group> = { album: \"Albums\", ep: \"EPs\", single: \"Singles\" };\n\n// Secondary types win over the primary one: a live album belongs with live releases, not studio albums.\nexport function discographyGroup({ primaryType, secondaryTypes }: ArtistAlbum): Group {\n const secondary = secondaryTypes.map((type) => type.toLowerCase());\n if (secondary.includes(\"live\")) return \"Live\";\n if (secondary.includes(\"compilation\")) return \"Compilations\";\n return PRIMARY_GROUPS[primaryType?.toLowerCase() ?? \"\"] ?? \"Other\";\n}\n\nfunction byYear(a: ArtistAlbum, b: ArtistAlbum): number {\n if (a.year !== b.year) {\n if (a.year === null) return 1;\n if (b.year === null) return -1;\n return a.year.localeCompare(b.year);\n }\n return a.name.localeCompare(b.name);\n}\n\nexport default function ArtistDiscography({ albums }: { albums: ArtistAlbum[] }) {\n if (albums.length === 0) return null;\n const sorted = [...albums].sort(byYear);\n\n return (\n <PanelSection title=\"Discography\">\n {GROUPS.map((group) => {\n const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);\n if (groupAlbums.length === 0) return null;\n return (\n <PanelGroup key={group} title={group}>\n <ul>\n {groupAlbums.map((album) => (\n <li key={album.uuid} className=\"grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5\">\n <span className=\"tabular-nums text-[var(--color-text-muted)]\">{album.year ?? \"—\"}</span>\n <span className=\"truncate text-[var(--color-text)]\" title={album.name}>\n {album.name}\n </span>\n </li>\n ))}\n </ul>\n </PanelGroup>\n );\n })}\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport { useState, type MouseEvent } from \"react\";\nimport { keepPreviousData } from \"@tanstack/react-query\";\nimport { Play } from \"lucide-react\";\nimport { Pagination, PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport {\n SongDetailedSchema,\n libraryEndpoints,\n songUnplayableReason,\n useTrackList,\n type RenderArtistLink,\n type RenderSongLink,\n type SongDetailed,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PaginatedResponseSchema, useFetchWrapper, useQueryWithParse } from \"@behindthemusictree/app-kit/transport\";\nimport TrackItem from \"../track-list-sidebar/TrackItem\";\n\nconst SongPageSchema = PaginatedResponseSchema(SongDetailedSchema);\n\nconst SORTS = [\n { ordering: \"title\", label: \"Title\" },\n { ordering: \"-albumYear\", label: \"Year\" },\n] as const;\ntype Ordering = (typeof SORTS)[number][\"ordering\"];\n\ninterface ArtistSongsProps {\n artist: { uuid: string; name: string };\n getBackendBaseUrl: () => string;\n renderArtistLink: RenderArtistLink;\n renderSongLink?: RenderSongLink;\n}\n\nexport default function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }: ArtistSongsProps) {\n const [page, setPage] = useState(1);\n const [ordering, setOrdering] = useState<Ordering>(\"title\");\n const { fetch } = useFetchWrapper(getBackendBaseUrl);\n const { playNewTrackListFromArtist } = useTrackList<SongDetailed>();\n const { playerTrackObject } = usePlayer();\n const { data, isPending, isError } = useQueryWithParse({\n queryKey: [\"referenceSongs\", \"artist\", artist.uuid, ordering, page],\n queryFn: () =>\n fetch(libraryEndpoints.reference.songs.list(), true, false, undefined, {\n artist: artist.uuid,\n ordering,\n page,\n }),\n schema: SongPageSchema,\n placeholderData: keepPreviousData,\n context: \"ArtistSongs\",\n });\n\n const play = (startSongUuid?: string) =>\n playNewTrackListFromArtist({ uuid: artist.uuid, name: artist.name }, \"reference\", {\n ordering,\n startSongUuid,\n });\n\n // TrackItem's own play control (`.track-position-play-pause`, a div) plays from whatever list is active; a click on a track that isn't\n // loaded yet must queue this artist's list instead.\n const handleRowClickCapture = (event: MouseEvent, song: SongDetailed) => {\n if (!(event.target instanceof Element) || !event.target.closest(\".track-position-play-pause\")) return;\n if (playerTrackObject?.track.id === song.uuid || songUnplayableReason(song) !== null) return;\n event.stopPropagation();\n void play(song.uuid);\n };\n\n return (\n <PanelSection title=\"Songs\">\n {isPending ? (\n <Skeleton className=\"h-14\" aria-busy=\"true\" />\n ) : isError ? (\n <p className=\"text-red-600\">Could not load songs.</p>\n ) : data.results.length === 0 ? (\n <p className=\"text-[var(--color-text-muted)]\">No songs yet.</p>\n ) : (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2 mb-2\">\n <button\n type=\"button\"\n onClick={() => void play()}\n className=\"flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700\"\n >\n <Play className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Play all\n </button>\n <div role=\"group\" aria-label=\"Sort songs by\" className=\"flex gap-1 text-xs\">\n {SORTS.map((sort) => (\n <button\n key={sort.ordering}\n type=\"button\"\n aria-pressed={ordering === sort.ordering}\n onClick={() => {\n setOrdering(sort.ordering);\n setPage(1);\n }}\n className=\"px-2.5 py-0.5 text-[var(--color-text)] rounded-full hover:bg-[var(--color-surface-muted)] aria-pressed:bg-gray-900 aria-pressed:text-white\"\n >\n {sort.label}\n </button>\n ))}\n </div>\n </div>\n <ul className=\"overflow-hidden mb-2 bg-zinc-950 rounded-lg\">\n {data.results.map((song, index) => (\n <li key={song.uuid} onClickCapture={(event) => handleRowClickCapture(event, song)}>\n <TrackItem\n track={song}\n position={(page - 1) * data.pageSize + index + 1}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n </li>\n ))}\n </ul>\n <Pagination currentPage={page} totalPages={data.totalPages} onPageChange={setPage} />\n </>\n )}\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport TrackPositionPlayPause from \"../TrackPositionPlayPause\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { isSong, songUnplayableReason } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { MusicBrainzRecordingLink } from \"../MusicBrainzRecordingLink\";\nimport type { RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { ArtistNames, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport interface TrackItemProps<T extends TrackBase> {\n track: T;\n position: number;\n renderDuration?: (track: T) => ReactNode;\n renderActions?: (track: T) => ReactNode;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, a song title renders through it (e.g. opens the song panel) instead of linking to MusicBrainz. */\n renderSongLink?: RenderSongLink;\n}\n\nexport default function TrackItem<T extends TrackBase>({\n track,\n position,\n renderDuration,\n renderActions,\n renderArtistLink,\n renderSongLink,\n}: TrackItemProps<T>) {\n const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer();\n const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList<T>();\n const scope = trackList?.origin?.scope ?? null;\n const isCurrentTrack = playerTrackObject?.track.id === track.uuid;\n const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;\n const isUnplayable =\n hasPlaybackError ||\n runtimeUnplayableTrackIds?.has(track.uuid) === true ||\n (isSong(track) && songUnplayableReason(track) !== null);\n\n const handlePlayPauseClick = (event: React.MouseEvent) => {\n event.stopPropagation();\n if (isCurrentTrack) {\n handlePlayPauseAction();\n } else if (scope != null) {\n toTrackAtPosition(position);\n loadTrackForPlayer(track.uuid);\n }\n };\n\n return (\n <div\n className={`track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? \"opacity-50\" : \"hover:bg-[var(--color-surface-muted)] group\"}`}\n >\n {isUnplayable ? (\n <div\n className=\"flex items-center justify-center text-lg w-16\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n aria-disabled=\"true\"\n >\n {position}\n </div>\n ) : (\n <TrackPositionPlayPause\n position={position}\n uuid={track.uuid}\n handlePlayPauseClick={handlePlayPauseClick}\n isSelected={selectedTrack?.uuid === track.uuid}\n />\n )}\n <div className=\"title-artist-container flex flex-col items-start justify-center min-w-0 flex-1\">\n <div className=\"flex max-w-full items-center gap-2\">\n <div className=\"title text-lg font-bold text-[var(--color-text)] max-w-full truncate\">\n {renderSongLink ? (\n renderSongLink(track)\n ) : isSong(track) ? (\n <MusicBrainzRecordingLink song={track} className=\"hover:underline focus-visible:underline\">\n {track.title}\n </MusicBrainzRecordingLink>\n ) : (\n track.title\n )}\n </div>\n </div>\n {track.artists && track.artists.length > 0 ? (\n <div className=\"artist text-base max-w-full truncate text-[var(--color-text-muted)]\">\n <ArtistNames artists={track.artists} renderArtistLink={renderArtistLink} />\n </div>\n ) : (\n \"\"\n )}\n </div>\n <div className=\"genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0\">\n {track.genre ? (\n <div className=\"genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold\">{track.genre.name}</div>\n ) : (\n \"\"\n )}\n </div>\n {renderDuration && (\n <div\n className=\"duration flex text-base w-16 items-center justify-center\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n >\n {renderDuration(track)}\n </div>\n )}\n {renderActions && (\n <div className=\"edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer\">\n {renderActions(track)}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { FaPlay, FaPause } from \"react-icons/fa\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { PlayStates } from \"@behindthemusictree/app-kit/player\";\n\nexport interface TrackPositionPlayPauseProps {\n position: number;\n uuid: string;\n handlePlayPauseClick: (event: React.MouseEvent) => void;\n isSelected?: boolean;\n}\n\nexport default function TrackPositionPlayPause({\n position,\n uuid,\n handlePlayPauseClick,\n isSelected = false,\n}: TrackPositionPlayPauseProps) {\n const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = usePlayer();\n const isThisTrack = playerTrackObject?.track.id === uuid;\n const isConfirmedPlayable = playbackConfirmedTrackId === uuid;\n const isAwaitingPlayback =\n (isSelected || isThisTrack) &&\n !isConfirmedPlayable &&\n !(isThisTrack && playerTrackObject?.loadError) &&\n (!isThisTrack || isPlayerLoading || playState === PlayStates.LOADING || playState === PlayStates.PLAYING);\n const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates.PLAYING;\n const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates.PAUSED;\n const isCurrentTrackActive = isPlaying || isPaused;\n\n return (\n <div\n className=\"track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n onClick={handlePlayPauseClick}\n >\n <div className=\"track-position-play-pause__status group-hover:hidden flex items-center justify-center\">\n {isAwaitingPlayback ? (\n <span className=\"track-position-play-pause__spinner\" role=\"status\" aria-label=\"Loading track\" />\n ) : isCurrentTrackActive ? (\n <span\n className=\"track-position-play-pause__bars\"\n data-playing={isPlaying}\n role=\"img\"\n aria-label={isPlaying ? \"Playing\" : \"Paused\"}\n >\n <span className=\"track-position-play-pause__bar\" />\n <span className=\"track-position-play-pause__bar\" />\n <span className=\"track-position-play-pause__bar\" />\n </span>\n ) : (\n position\n )}\n </div>\n <div\n className={`hidden group-hover:flex items-center justify-center ${\n isCurrentTrackActive ? \"\" : \"absolute inset-0\"\n }`}\n aria-hidden=\"true\"\n >\n {isAwaitingPlayback ? (\n <span className=\"track-position-play-pause__spinner\" />\n ) : isPlaying ? (\n <FaPause />\n ) : (\n <FaPlay />\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { Skeleton } from \"@behindthemusictree/ui\";\nimport {\n useSong,\n type CriteriaMinimum,\n type CriteriaPlaylistSimple,\n type RenderArtistLink,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { GetGenreTreeShareUrl } from \"../share\";\nimport SongPanelContent from \"./SongPanelContent\";\nimport type { OverlayPanel } from \"../overlayPanel\";\n\nexport interface SongPanelOptions {\n uuid: string | null;\n scope: Scope;\n getBackendBaseUrl: () => string;\n getShareUrl?: GetGenreTreeShareUrl;\n renderArtistLink: RenderArtistLink;\n /** Root-to-leaf playlists of a genre in the loaded tree; empty when the tree doesn't hold it. */\n getGenrePath: (genre: CriteriaMinimum) => CriteriaPlaylistSimple[];\n onSelectGenre: (genrePlaylist: CriteriaPlaylistSimple) => void;\n expanded: boolean;\n onExpandedChange: (expanded: boolean) => void;\n onClose: () => void;\n}\n\n/** The song detail overlay panel for `uuid`, or undefined when no song is set. */\nexport function useSongPanel({\n uuid,\n scope,\n getBackendBaseUrl,\n getShareUrl,\n renderArtistLink,\n getGenrePath,\n onSelectGenre,\n expanded,\n onExpandedChange,\n onClose,\n}: SongPanelOptions): OverlayPanel | undefined {\n const result = useSong(uuid, getBackendBaseUrl);\n\n const panel = { expanded, onExpandedChange, onClose };\n switch (result.status) {\n case \"idle\":\n return undefined;\n case \"loading\":\n return {\n ...panel,\n title: \"Song\",\n content: (\n <div aria-busy=\"true\" className=\"flex flex-col gap-2.5 px-6 py-2.5\">\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-24\" />\n </div>\n ),\n };\n case \"not-found\":\n return {\n ...panel,\n title: \"Song not found\",\n content: <p className=\"px-6 py-2.5 text-[var(--color-text-muted)]\">This song does not exist.</p>,\n };\n case \"error\":\n return {\n ...panel,\n title: \"Song\",\n content: <p className=\"px-6 py-2.5 text-red-600\">Could not load this song.</p>,\n };\n case \"success\": {\n const { song } = result;\n const shareUrl = getShareUrl?.({ kind: \"song\", uuid: song.uuid, name: song.title, scope });\n return {\n ...panel,\n title: song.title,\n content: (\n <SongPanelContent\n song={song}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n shareUrl={shareUrl ?? undefined}\n renderArtistLink={renderArtistLink}\n genrePath={song.genre ? getGenrePath(song.genre) : []}\n onSelectGenre={onSelectGenre}\n />\n ),\n };\n }\n }\n}\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport { Pause, Play } from \"lucide-react\";\nimport { FieldList, PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { PlayStates, usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport {\n ArtistNames,\n musicBrainzRecordingUrl,\n songUnplayableReason,\n useSongHistory,\n useTrackList,\n type CriteriaPlaylistSimple,\n type RenderArtistLink,\n type SongDetailed,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport ShareActionButton from \"../ShareActionButton\";\nimport type { GenreTreeShareTarget } from \"../share\";\n\nexport interface SongPanelContentProps {\n song: SongDetailed;\n scope: Scope;\n getBackendBaseUrl: () => string;\n shareUrl?: string;\n renderArtistLink: RenderArtistLink;\n /** Root-to-leaf playlists of the song's genre in the loaded tree; empty when the tree doesn't hold it. */\n genrePath: CriteriaPlaylistSimple[];\n onSelectGenre: (genrePlaylist: CriteriaPlaylistSimple) => void;\n}\n\nconst numberFormat = new Intl.NumberFormat(\"en\");\nconst dateFormat = new Intl.DateTimeFormat(\"en\", { dateStyle: \"medium\" });\n\nconst externalLink = \"text-[var(--color-primary)] hover:underline focus-visible:underline\";\n\nfunction SongHistory({ uuid, getBackendBaseUrl }: { uuid: string; getBackendBaseUrl: () => string }) {\n const [open, setOpen] = useState(false);\n const history = useSongHistory(uuid, getBackendBaseUrl, open);\n let body: ReactNode = null;\n if (history.status === \"loading\") body = <Skeleton className=\"h-10\" aria-busy=\"true\" />;\n else if (history.status === \"error\") body = <p className=\"text-red-600\">Could not load the history.</p>;\n else if (history.status === \"success\")\n body =\n history.entries.length === 0 ? (\n <p className=\"text-[var(--color-text-muted)]\">No changes yet.</p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-border)]\">\n {history.entries.map((entry) => (\n <li key={entry.uuid} className=\"py-1.5 text-sm\">\n <span className=\"font-medium\">{entry.action}</span>\n {entry.oldValue !== null || entry.newValue !== null ? (\n <span className=\"text-[var(--color-text-muted)]\">\n {\" \"}\n — {entry.oldValue ?? \"none\"} → {entry.newValue ?? \"none\"}\n </span>\n ) : null}\n <span className=\"block text-xs text-[var(--color-text-muted)]\">\n {entry.actorPseudo ?? \"Anonymous\"} · {dateFormat.format(new Date(entry.createdOn))}\n </span>\n </li>\n ))}\n </ul>\n );\n\n return (\n <details\n onToggle={(event) => setOpen(event.currentTarget.open)}\n className=\"px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]\"\n >\n <summary className=\"mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]\">\n History\n </summary>\n <div className=\"mt-1.5\">{body}</div>\n </details>\n );\n}\n\nexport default function SongPanelContent({\n song,\n scope,\n getBackendBaseUrl,\n shareUrl,\n renderArtistLink,\n genrePath,\n onSelectGenre,\n}: SongPanelContentProps) {\n const { playNewTrackListFromTrackUuid } = useTrackList<SongDetailed>();\n const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer();\n const unplayableReason = songUnplayableReason(song);\n const isCurrent = playerTrackObject?.track.id === song.uuid;\n const isPlaying = isCurrent && playState === PlayStates.PLAYING;\n const shareTarget: GenreTreeShareTarget = { kind: \"song\", uuid: song.uuid, name: song.title, scope };\n const musicBrainzUrl = musicBrainzRecordingUrl(song);\n\n const fields: { label: string; value: ReactNode }[] = [];\n if (song.genre) {\n const path = genrePath.length > 0 ? genrePath : null;\n fields.push({\n label: \"Genre\",\n value: path ? (\n <span className=\"flex flex-wrap items-center gap-x-1\">\n {path.map((genrePlaylist, index) => (\n <span key={genrePlaylist.uuid}>\n {index > 0 && <span aria-hidden=\"true\">› </span>}\n <button type=\"button\" onClick={() => onSelectGenre(genrePlaylist)} className=\"hover:underline\">\n {genrePlaylist.name}\n </button>\n </span>\n ))}\n </span>\n ) : (\n song.genre.name\n ),\n });\n } else {\n fields.push({ label: \"Genre\", value: <span className=\"text-[var(--color-text-muted)]\">No genre</span> });\n }\n if (song.firstReleaseYear) fields.push({ label: \"First released\", value: song.firstReleaseYear });\n const viewCount = song.youtubeVideo?.viewCount;\n if (viewCount !== null && viewCount !== undefined)\n fields.push({ label: \"YouTube views\", value: numberFormat.format(viewCount) });\n fields.push({ label: \"Plays\", value: numberFormat.format(song.playCount) });\n const links: ReactNode[] = [];\n if (musicBrainzUrl)\n links.push(\n <a key=\"mb\" href={musicBrainzUrl} target=\"_blank\" rel=\"noopener noreferrer\" className={externalLink}>\n MusicBrainz\n </a>,\n );\n if (song.youtubeVideo)\n links.push(\n <a\n key=\"yt\"\n href={`https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={externalLink}\n >\n YouTube\n </a>,\n );\n if (links.length > 0)\n fields.push({\n label: \"Links\",\n value: <span className=\"flex gap-3\">{links}</span>,\n });\n\n return (\n <>\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">\n {unplayableReason ? (\n <p className=\"flex items-center px-2.5 text-sm text-[var(--color-text-muted)]\">{unplayableReason}</p>\n ) : (\n <button\n type=\"button\"\n onClick={() => (isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope))}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none\"\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]\">\n {isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"w-5 h-5\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"w-5 h-5\" />\n )}\n </span>\n {isPlaying ? \"Pause\" : \"Play\"}\n </button>\n )}\n {shareUrl && <ShareActionButton target={shareTarget} url={shareUrl} />}\n </div>\n </div>\n {song.artists && song.artists.length > 0 && (\n <p className=\"px-6 pt-2.5 text-[var(--color-text-muted)]\">\n <ArtistNames artists={song.artists} renderArtistLink={renderArtistLink} />\n </p>\n )}\n <FieldList items={fields} />\n {song.essentialGenres && song.essentialGenres.length > 0 && (\n <PanelSection title=\"Essential in\">\n <ul>\n {song.essentialGenres.map((genre) => (\n <li key={genre.uuid} className=\"py-0.5\">\n {genre.name}\n </li>\n ))}\n </ul>\n </PanelSection>\n )}\n <SongHistory uuid={song.uuid} getBackendBaseUrl={getBackendBaseUrl} />\n </>\n );\n}\n","\"use client\";\n\nimport { MdMusicNote, MdVisibility } from \"react-icons/md\";\nimport { SegmentedControl } from \"@behindthemusictree/ui\";\n\nimport type { GenreTreeSizeMetric } from \"./playlist-tree/size-metric\";\n\nexport function SizeMetricToggle({\n value,\n onChange,\n}: {\n value: GenreTreeSizeMetric;\n onChange: (value: GenreTreeSizeMetric) => void;\n}) {\n return (\n <SegmentedControl\n variant=\"floating\"\n aria-label=\"Size nodes by\"\n value={value}\n onChange={onChange}\n options={[\n { value: \"songs\", label: <MdMusicNote size={18} aria-hidden />, \"aria-label\": \"Size by songs\", title: \"Node size by song count\" },\n { value: \"views\", label: <MdVisibility size={18} aria-hidden />, \"aria-label\": \"Size by views\", title: \"Node size by song views\" },\n ]}\n />\n );\n}\n","\"use client\";\n\nimport { useMemo, type ComponentProps } from \"react\";\nimport {\n ACCENT_TEXT_COLOR,\n GenreNodeAncestors,\n GenreNodeFamily,\n getGenreTreeColor,\n TEXT_COLOR,\n tintSurface,\n type GenreTreeNode,\n type InfoPanelChild,\n} from \"@behindthemusictree/genre-tree-view\";\nimport { DetailPanel, IconButton, Skeleton } from \"@behindthemusictree/ui\";\nimport { Maximize2, Minimize2 } from \"lucide-react\";\n\nimport {\n useFetchGenreDetail,\n type CriteriaMinimum,\n type CriteriaOverview,\n type GenreUuid,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\nimport { GenreInfoPanelBody, type GenreInfoPanelBodyProps } from \"./GenreInfoPanelBody\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport { useGenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\n\nexport type GenreDetailPanelProps<O extends CriteriaOverview> = {\n genreUuid: GenreUuid;\n scope: Scope;\n getBackendBaseUrl: () => string;\n onClose: () => void;\n /** Fired with the uuid of a family or ancestor chip the user picked. */\n onSelectGenre: (genreUuid: GenreUuid) => void;\n /** When set, the panel offers \"Focus\". */\n onZoomIn?: () => void;\n /** Absolute or relative URL that shares this genre; the share action shows only when set. */\n shareUrl?: string;\n shareTarget?: GenreTreeShareTarget;\n collapsed?: boolean;\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Tab on the panel's inner edge, for hosts that hide and show the panel. */\n edgeTab?: ComponentProps<typeof DetailPanel>[\"edgeTab\"];\n extras?: GenreInfoPanelBodyProps<O>[\"extras\"];\n};\n\n// Relatives from the detail endpoint carry no song count, so the family dots share one size.\nconst RELATIVE_ITEM_COUNT = 1;\n\nfunction toNode(genre: CriteriaMinimum, parentId: string | null): GenreTreeNode {\n return {\n id: genre.uuid,\n parentId,\n name: genre.name,\n itemCount: RELATIVE_ITEM_COUNT,\n side: genre.side ?? undefined,\n wikidataId: genre.wikidataId,\n };\n}\n\n/** A genre's detail panel driven by one uuid, with no tree behind it. The tree's own panel shares its body. */\nexport function GenreDetailPanel<O extends CriteriaOverview>({\n genreUuid,\n scope,\n getBackendBaseUrl,\n onClose,\n onSelectGenre,\n onZoomIn,\n shareUrl,\n shareTarget,\n collapsed,\n expanded,\n onExpandedChange,\n edgeTab,\n extras,\n}: GenreDetailPanelProps<O>) {\n const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);\n const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });\n\n const view = useMemo(() => {\n if (!detail) return null;\n const rootColor = getGenreTreeColor(detail.root.wikidataId);\n const relative = (genre: CriteriaMinimum, parentId: string | null): InfoPanelChild => ({\n node: toNode(genre, parentId),\n fill: tintSurface(rootColor),\n textColor: TEXT_COLOR,\n });\n const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);\n return {\n node: {\n id: detail.uuid,\n parentId: detail.parent?.uuid ?? null,\n name: detail.name,\n itemCount: detail.tracksCount,\n wikidataId: detail.wikidataId,\n } satisfies GenreTreeNode,\n fill: rootColor,\n parentNode: detail.parent ? relative(detail.parent, null) : null,\n childNodes: detail.children.map((child) => relative(child, detail.uuid)),\n ancestorNodes: ancestors.map(({ ascendant }, index) =>\n relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid),\n ),\n };\n }, [detail]);\n\n if (isError) {\n return (\n <div role=\"alert\" className=\"p-4 text-sm text-[var(--color-text)]\">\n This genre could not be loaded.\n </div>\n );\n }\n if (isPending || !detail || !view) {\n return (\n <div className=\"flex flex-col gap-3 p-4\" aria-busy=\"true\" data-testid=\"genre-detail-panel-skeleton\">\n <Skeleton className=\"h-6 w-2/3\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-3/4\" />\n </div>\n );\n }\n\n const selectGenre = (nodeId: string) => onSelectGenre(nodeId as GenreUuid);\n const family = (\n <GenreNodeFamily\n node={view.node}\n fill={view.fill}\n textColor={ACCENT_TEXT_COLOR}\n parentNode={view.parentNode}\n childNodes={view.childNodes}\n onSelectNode={selectGenre}\n />\n );\n const ancestors = <GenreNodeAncestors ancestorNodes={view.ancestorNodes} onSelectNode={selectGenre} />;\n\n return (\n <DetailPanel\n side=\"left\"\n title={view.node.name}\n headerAccent={{ background: view.fill, color: ACCENT_TEXT_COLOR }}\n headerActions={\n onExpandedChange ? (\n <IconButton\n aria-label={expanded ? \"Shrink panel\" : \"Extend panel\"}\n style={{ color: \"inherit\" }}\n onClick={() => onExpandedChange(!expanded)}\n >\n {expanded ? (\n <Minimize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <Maximize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n </IconButton>\n ) : undefined\n }\n onClose={onClose}\n collapsed={collapsed}\n expanded={expanded}\n onExpandedChange={onExpandedChange}\n edgeTab={edgeTab}\n >\n <GenreInfoPanelBody<O>\n family={<>{family}{ancestors}</>}\n genreUuid={detail.uuid}\n genrePlaylist={{ ...detail.criteriaPlaylist, tracksCount: detail.tracksCount }}\n wikidataId={detail.wikidataId}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n playback={playback}\n band={{ node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : undefined }}\n extras={extras}\n />\n {!expanded && ancestors}\n </DetailPanel>\n );\n}\n","export * from \"./GenreTreeView\";\nexport type { GenreTreeShareTarget, GetGenreTreeShareUrl } from \"./share\";\nexport type { ParentChange } from \"./ChangeParentPopup\";\nexport type { GenreDetailSlots } from \"./GenreDetailExtras\";\nexport { GenreDetailPanel } from \"./GenreDetailPanel\";\nexport type { GenreDetailPanelProps } from \"./GenreDetailPanel\";\nexport { GenreInfoPanelBody, GenreNodeActionBand } from \"./GenreInfoPanelBody\";\nexport type { GenreInfoPanelBodyProps, PlayableGenrePlaylist } from \"./GenreInfoPanelBody\";\nexport { useGenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\nexport type { GenrePlaylistPlayback, UseGenrePlaylistPlaybackOptions } from \"./playlist-tree/useGenrePlaylistPlayback\";\nexport type { GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nexport { default as GenrePlaylistTreePerRoot } from \"./playlist-tree/TreePerRoot\";\nexport type { GenrePlaylistTreePerRootProps } from \"./playlist-tree/TreePerRoot\";\nexport { default as GenreSearch } from \"./GenreSearch\";\nexport type { GenreSearchProps, GenreSearchExtraGroup } from \"./GenreSearch\";\nexport {\n GenreTreeSkeleton,\n GenreTreeWheelSkeleton,\n GenreTreeViewSkeleton,\n GenreTreeOutlineSkeleton,\n} from \"@behindthemusictree/genre-tree-view\";\nexport type { GenreTreeAction, GenreTreeViewport, GenreTreeViewSkeletonProps } from \"@behindthemusictree/genre-tree-view\";\nexport { default as TrackListSidebar } from \"./track-list-sidebar/TrackListSidebar\";\nexport type { TrackListSidebarProps } from \"./track-list-sidebar/TrackListSidebar\";\nexport { default as PlayerPanel } from \"./PlayerPanel\";\nexport type { PlayerPanelProps } from \"./PlayerPanel\";\nexport { default as TrackItem } from \"./track-list-sidebar/TrackItem\";\nexport type { TrackItemProps } from \"./track-list-sidebar/TrackItem\";\nexport { default as TrackPositionPlayPause } from \"./TrackPositionPlayPause\";\nexport type { TrackPositionPlayPauseProps } from \"./TrackPositionPlayPause\";\nexport { default as Rating } from \"./Rating\";\nexport type { RatingProps } from \"./Rating\";\n","\"use client\";\n\nimport { ReactNode, useEffect, useLayoutEffect, useRef } from \"react\";\nimport { ListMusic } from \"lucide-react\";\n\nimport TrackItem from \"./TrackItem\";\nimport { useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackListOriginType } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nconst useBrowserLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport interface TrackListSidebarProps<T extends TrackBase> {\n className?: string;\n renderDuration?: (track: T) => ReactNode;\n renderActions?: (track: T) => ReactNode;\n layout?: \"fixed\" | \"inline\";\n isCollapsed?: boolean;\n onExpand?: () => void;\n onMinimize?: () => void;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, song titles render through it (e.g. open the song panel). */\n renderSongLink?: RenderSongLink;\n}\n\nexport default function TrackListSidebar<T extends TrackBase>({\n className = \"\",\n renderDuration,\n renderActions,\n layout = \"fixed\",\n isCollapsed = false,\n onExpand,\n onMinimize,\n renderArtistLink,\n renderSongLink,\n}: TrackListSidebarProps<T>) {\n const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList<T>();\n const trackNoun = useTrackNoun();\n const trackListRef = useRef<HTMLUListElement>(null);\n const previousOriginRef = useRef(trackList?.origin ?? null);\n const handledAutoAdvanceRequestIdRef = useRef<number | null>(null);\n const shouldFollowPlaybackRef = useRef(true);\n const programmaticScrollTopRef = useRef<number | null>(null);\n const userScrollIntentRef = useRef(false);\n const userScrollIntentTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const loadMoreSentinelRef = useRef<HTMLLIElement>(null);\n const hasMore = !!trackList && trackList.nextPage !== null;\n\n useEffect(() => {\n const sentinel = loadMoreSentinelRef.current;\n if (!hasMore || !sentinel) return;\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch((error) => console.error(\"Failed to load more tracks:\", error));\n }\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasMore, loadMore]);\n\n useBrowserLayoutEffect(() => {\n const origin = trackList?.origin ?? null;\n if (previousOriginRef.current === origin) return;\n previousOriginRef.current = origin;\n handledAutoAdvanceRequestIdRef.current = null;\n shouldFollowPlaybackRef.current = true;\n programmaticScrollTopRef.current = null;\n if (trackListRef.current) trackListRef.current.scrollTop = 0;\n }, [trackList?.origin]);\n\n useEffect(() => {\n const list = trackListRef.current;\n if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;\n }, [isCollapsed]);\n\n useEffect(\n () => () => {\n if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);\n },\n [],\n );\n\n useBrowserLayoutEffect(() => {\n if (\n !autoAdvanceScrollRequest ||\n handledAutoAdvanceRequestIdRef.current === autoAdvanceScrollRequest.id\n ) {\n return;\n }\n if (isCollapsed) return;\n\n const list = trackListRef.current;\n if (!list) return;\n if (list.scrollTop <= 1) shouldFollowPlaybackRef.current = true;\n if (!shouldFollowPlaybackRef.current) {\n handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;\n return;\n }\n\n const row = Array.from(list.querySelectorAll<HTMLElement>(\"[data-track-uuid]\")).find(\n (element) => element.dataset.trackUuid === autoAdvanceScrollRequest.trackUuid,\n );\n if (!row) return;\n handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;\n\n const rowTop = row.getBoundingClientRect().top - list.getBoundingClientRect().top;\n const targetScrollTop = list.scrollTop + rowTop;\n programmaticScrollTopRef.current = targetScrollTop;\n list.scrollTop = targetScrollTop;\n programmaticScrollTopRef.current = list.scrollTop;\n }, [autoAdvanceScrollRequest, isCollapsed, trackList]);\n\n const markUserScrollIntent = () => {\n userScrollIntentRef.current = true;\n if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);\n userScrollIntentTimerRef.current = setTimeout(() => {\n userScrollIntentRef.current = false;\n userScrollIntentTimerRef.current = null;\n }, 250);\n };\n\n const handleTrackListScroll = (event: React.UIEvent<HTMLUListElement>) => {\n const list = event.currentTarget;\n if (userScrollIntentRef.current) {\n userScrollIntentRef.current = false;\n if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);\n userScrollIntentTimerRef.current = null;\n programmaticScrollTopRef.current = null;\n shouldFollowPlaybackRef.current = list.scrollTop <= 1;\n return;\n }\n\n const programmaticScrollTop = programmaticScrollTopRef.current;\n if (programmaticScrollTop !== null) {\n if (Math.abs(list.scrollTop - programmaticScrollTop) <= 1) programmaticScrollTopRef.current = null;\n return;\n }\n };\n const positionClasses =\n layout === \"inline\"\n ? \"relative w-full flex h-full flex-col\"\n : \"fixed right-0 w-[50rem] flex flex-col\";\n\n return trackList ? (\n <div\n data-theme=\"dark\"\n className={`track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className}`}\n style={layout === \"fixed\" ? { top: \"63.5px\", maxHeight: \"calc(100vh - 63.5px - 79px)\" } : undefined}\n >\n <div className=\"header flex h-16 items-center px-4 py-2 text-[var(--color-text)]\">\n <div className=\"min-w-0 flex flex-1 flex-col justify-center\">\n <div className=\"origin flex text-xl\">\n <div className=\"from h-auto flex flex-col justify-center items-center mr-2\">From</div>\n <div className=\"name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]\">\n <div className=\"name max-w-full truncate\">{trackList.origin.label}</div>\n </div>\n </div>\n <div className=\"info flex items-center text-sm text-[var(--color-text-muted)]\">\n {trackList.origin.type === TrackListOriginType.GENRE_PLAYLIST\n ? \"Genre • \"\n : trackList.origin.type === TrackListOriginType.ARTIST\n ? \"Artist • \"\n : `${trackNoun.singular} playlist • `}\n {`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`}\n </div>\n </div>\n {((isCollapsed && onExpand) || (!isCollapsed && onMinimize)) && (\n <button\n type=\"button\"\n aria-label={isCollapsed ? \"Show tracks\" : \"Hide tracks\"}\n title={isCollapsed ? \"Show tracks\" : \"Hide tracks\"}\n aria-expanded={!isCollapsed}\n className={`ml-2 flex h-8 w-8 shrink-0 items-center justify-center rounded-md hover:bg-[var(--color-surface-muted)] ${isCollapsed ? \"text-[var(--color-text-muted)]\" : \"text-white\"}`}\n onClick={isCollapsed ? onExpand : onMinimize}\n >\n <ListMusic aria-hidden=\"true\" className=\"h-5 w-5\" />\n </button>\n )}\n </div>\n {!isCollapsed && (\n <ul\n ref={trackListRef}\n onScroll={handleTrackListScroll}\n onWheel={markUserScrollIntent}\n onTouchMove={markUserScrollIntent}\n onPointerDown={markUserScrollIntent}\n className={\n layout === \"inline\"\n ? \"track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0\"\n : \"track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0\"\n }\n >\n {trackList.tracks.map((track, index) => (\n <li key={track.uuid} data-track-uuid={track.uuid}>\n <TrackItem\n track={track}\n position={index + 1}\n renderDuration={renderDuration}\n renderActions={renderActions}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n </li>\n ))}\n {hasMore && <li ref={loadMoreSentinelRef} aria-hidden=\"true\" className=\"h-px\" />}\n </ul>\n )}\n </div>\n ) : null;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { twMerge } from \"tailwind-merge\";\nimport {\n usePlayer,\n PlayerVideoSurface,\n} from \"@behindthemusictree/app-kit/player\";\nimport {\n useTrackListSidebarVisibility,\n type RenderArtistLink,\n type RenderSongLink,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelEdgeTab } from \"@behindthemusictree/ui\";\nimport PlayerControls from \"./PlayerControls\";\nimport TrackListSidebar from \"./track-list-sidebar/TrackListSidebar\";\n\nexport interface PlayerPanelProps {\n className?: string;\n /** Hidden via CSS, never unmounted, so the video keeps playing while the host hides the panel. */\n isVisible?: boolean;\n /** @deprecated The player no longer closes; its header action minimizes to video-only mode. */\n onClose?: () => void;\n /** Controlled extended state; uncontrolled when omitted. */\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Width once extended, e.g. to stop short of a left-hand detail panel. */\n extendedWidth?: string;\n renderArtistLink?: RenderArtistLink;\n /** When set, song titles in the tracklist render through it (e.g. open the song panel). */\n renderSongLink?: RenderSongLink;\n}\n\nconst PANEL_ID = \"player-panel\";\n\n// Spans the center and right columns.\nconst DEFAULT_EXTENDED_WIDTH =\n \"calc(100vw - var(--layout-center-left) - var(--layout-gutter))\";\n\nexport default function PlayerPanel({\n className,\n isVisible = true,\n expanded,\n onExpandedChange,\n extendedWidth = DEFAULT_EXTENDED_WIDTH,\n renderArtistLink,\n renderSongLink,\n}: PlayerPanelProps) {\n const { playerTrackObject } = usePlayer();\n const { toggleTrackListSidebar, isTrackListSidebarVisible } =\n useTrackListSidebarVisibility();\n const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState(\"\");\n const loadedTrackTitle = playerTrackObject?.track.title;\n useEffect(() => {\n if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);\n }, [loadedTrackTitle]);\n const [uncontrolledExtended, setUncontrolledExtended] = useState(false);\n const isExtendRequested = expanded ?? uncontrolledExtended;\n const setIsExtendRequested = (next: boolean) => {\n setUncontrolledExtended(next);\n onExpandedChange?.(next);\n };\n const isExtended = isExtendRequested && isTrackListSidebarVisible;\n const hideSongList = () => {\n setIsExtendRequested(false);\n if (isTrackListSidebarVisible) toggleTrackListSidebar();\n };\n\n const tabEdge =\n isTrackListSidebarVisible && isExtended\n ? {\n label: \"Shrink player panel\",\n onClick: () => setIsExtendRequested(false),\n expanded: true,\n }\n : null;\n const tabCenter =\n isTrackListSidebarVisible && !isExtended\n ? {\n label: \"Extend player panel\",\n onClick: () => setIsExtendRequested(true),\n expanded: false,\n }\n : null;\n\n return (\n <div\n id={PANEL_ID}\n className={twMerge(\n \"fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]\",\n !isExtended && \"w-[var(--layout-right-w)]\",\n isTrackListSidebarVisible &&\n \"h-[calc(100vh-var(--layout-band-top)-3.5rem)]\",\n !isVisible && \"hidden\",\n className,\n )}\n style={isExtended ? { width: extendedWidth } : undefined}\n >\n <PanelEdgeTab\n side=\"right\"\n edge={tabEdge}\n center={tabCenter}\n controls={PANEL_ID}\n className=\"absolute right-full top-1/2 -translate-y-1/2\"\n />\n <div\n className={twMerge(\n \"flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]\",\n isExtended ? \"flex-row\" : \"flex-col\",\n )}\n >\n <div className=\"flex w-[var(--layout-right-w)] shrink-0 flex-col\">\n <div className=\"flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2\">\n <span className=\"min-w-0 truncate text-sm font-semibold\">\n {loadedTrackTitle || lastLoadedTrackTitle}\n </span>\n </div>\n <div className=\"relative w-full shrink-0\">\n <PlayerVideoSurface className=\"aspect-video w-full bg-black\" />\n {playerTrackObject?.loadError && (\n <span className=\"absolute bottom-2 left-2 right-2 text-xs text-red-400\">\n {playerTrackObject.loadError}\n </span>\n )}\n </div>\n <PlayerControls />\n </div>\n <TrackListSidebar\n layout=\"inline\"\n isCollapsed={!isTrackListSidebarVisible}\n onExpand={toggleTrackListSidebar}\n onMinimize={hideSongList}\n className={twMerge(\n \"min-h-0 min-w-0 flex-1\",\n isExtended && \"border-l border-zinc-800\",\n )}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { Pause, Play, SkipBack, SkipForward, Volume2, VolumeX } from \"lucide-react\";\nimport { formatTime, useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PlayStates, useCurrentTime, usePlayer } from \"@behindthemusictree/app-kit/player\";\n\nexport default function PlayerControls() {\n const {\n duration,\n handlePlayPauseAction,\n isLoading,\n playState,\n playerTrackObject,\n seekTo,\n setVolume,\n volume,\n } = usePlayer();\n const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = useTrackList();\n const currentTime = useCurrentTime();\n const isBusy = isLoading || playState === PlayStates.LOADING;\n const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;\n const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;\n const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;\n const isPlaying = playState === PlayStates.PLAYING;\n\n return (\n <section\n aria-label=\"Player controls\"\n className=\"player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200\"\n >\n <div className=\"flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400\">\n <span aria-label=\"Elapsed time\">{formatTime(safeCurrentTime)}</span>\n <input\n aria-label=\"Seek\"\n className=\"h-1 min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40\"\n type=\"range\"\n min={0}\n max={safeDuration}\n step={0.1}\n value={safeCurrentTime}\n disabled={!isReady || safeDuration <= 0}\n onChange={(event) => seekTo(Number(event.currentTarget.value))}\n />\n <span aria-label=\"Duration\">{formatTime(safeDuration)}</span>\n </div>\n\n <div className=\"grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3\">\n <div className=\"col-start-2 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n aria-label=\"Previous track\"\n title=\"Previous track\"\n disabled={!isReady || !hasPreviousTrack}\n onClick={playPreviousTrack}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <SkipBack aria-hidden=\"true\" className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n aria-label={isPlaying ? \"Pause\" : \"Play\"}\n title={isPlaying ? \"Pause\" : \"Play\"}\n disabled={!isReady}\n onClick={handlePlayPauseAction}\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-zinc-950 hover:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-40\"\n >\n {isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"h-4 w-4\" fill=\"currentColor\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"h-4 w-4\" fill=\"currentColor\" />\n )}\n </button>\n <button\n type=\"button\"\n aria-label=\"Next track\"\n title=\"Next track\"\n disabled={!isReady || !hasNextTrack}\n onClick={playNextTrack}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <SkipForward aria-hidden=\"true\" className=\"h-4 w-4\" />\n </button>\n </div>\n\n <label className=\"col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]\">\n {volume === 0 ? (\n <VolumeX aria-hidden=\"true\" className=\"h-[0.7rem] w-[0.7rem] shrink-0\" />\n ) : (\n <Volume2 aria-hidden=\"true\" className=\"h-[0.7rem] w-[0.7rem] shrink-0\" />\n )}\n <span className=\"sr-only\">Volume</span>\n <input\n aria-label=\"Volume\"\n className=\"h-[3px] min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40\"\n type=\"range\"\n min={0}\n max={100}\n step={1}\n value={volume}\n disabled={!isReady}\n onChange={(event) => setVolume(Number(event.currentTarget.value))}\n />\n </label>\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { AiFillStar, AiOutlineStar } from \"react-icons/ai\";\n\nimport { FORM_RATING_NULL_VALUE } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport interface RatingProps {\n rating: number | null | undefined;\n handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;\n}\n\nexport default function Rating({ rating, handleChange }: RatingProps) {\n const instanceId = uuidv4();\n return (\n <div className=\"flex items-center\">\n {[...Array(6)].map((_, i) => (\n <div key={i} className={`relative ${i === 0 ? \"mr-4\" : \"mr-1\"}`}>\n <input\n id={`star-${instanceId}-${i}`}\n className=\"w-6 h-6 opacity-0\"\n type=\"radio\"\n name=\"rating\"\n value={i === 0 ? FORM_RATING_NULL_VALUE : i}\n checked={rating === (i === 0 ? null : i)}\n onChange={handleChange}\n />\n <label htmlFor={`star-${instanceId}-${i}`} className=\"cursor-pointer\">\n {i === 0 ? (\n <div className=\"flex\">\n <AiOutlineStar\n className={`w-7 h-7 absolute top-0 left-0 ${\n rating === FORM_RATING_NULL_VALUE ? \"text-[var(--color-accent-rating)]\" : \"text-[var(--color-text-muted)]\"\n }`}\n />\n <span className=\"flex-grow w-8\"></span>{\" \"}\n </div>\n ) : (\n <AiFillStar\n className={`w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? \"text-[var(--color-accent-rating)]\" : \"text-[var(--color-text-muted)]\"}`}\n />\n )}\n </label>\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["className","import_jsx_runtime","className","import_ui","import_jsx_runtime","className","import_react","import_react_dom","import_jsx_runtime","import_jsx_runtime","import_lucide_react","import_ui","import_jsx_runtime","import_lucide_react","import_jsx_runtime","import_lucide_react","import_jsx_runtime","import_lucide_react","import_ui","import_jsx_runtime","import_fa","import_ui","import_lucide_react","import_transport","import_jsx_runtime","import_jsx_runtime","import_react","import_react_query","import_lucide_react","import_ui","import_genre_tree_view","import_genre_tree","import_popup","import_react","import_popup","import_transport","import_react","import_jsx_runtime","className","import_react","import_genre_tree_view","import_player","import_popup","import_transport","import_jsx_runtime","className","import_react","import_genre_tree_view","import_player","import_jsx_runtime","className","import_react","import_jsx_runtime","import_react","import_lucide_react","import_ui","import_genre_tree","import_jsx_runtime","import_player","import_lucide_react","import_lucide_react","import_popup","import_react","import_lucide_react","import_fa","import_jsx_runtime","import_lucide_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_ui","import_genre_tree","import_react","import_jsx_runtime","className","import_react","import_fa","import_ui","import_auth","import_transport","import_player","import_popup","import_transport","import_genre_tree","import_genre_tree","import_jsx_runtime","className","import_genre_tree","import_track_noun","import_jsx_runtime","artist","import_track_noun","import_si","import_jsx_runtime","import_jsx_runtime","import_react","import_react","import_genre_tree","import_jsx_runtime","import_ui","import_genre_tree","import_ui","import_lucide_react","import_si","import_ui","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_ui","import_jsx_runtime","import_ui","import_jsx_runtime","import_react","import_react_query","import_lucide_react","import_ui","import_player","import_genre_tree","import_transport","import_player","import_genre_tree","import_fa","import_player","import_jsx_runtime","import_genre_tree","import_genre_tree","import_jsx_runtime","import_jsx_runtime","SongPageSchema","import_jsx_runtime","import_ui","import_genre_tree","import_react","import_lucide_react","import_ui","import_player","import_genre_tree","import_jsx_runtime","import_jsx_runtime","import_md","import_ui","import_jsx_runtime","import_jsx_runtime","className","import_react","import_genre_tree_view","import_ui","import_lucide_react","import_genre_tree","import_jsx_runtime","ancestors","import_genre_tree_view","import_react","import_lucide_react","import_genre_tree","import_track_noun","import_jsx_runtime","className","import_react","import_player","import_genre_tree","import_ui","import_lucide_react","import_genre_tree","import_player","import_jsx_runtime","import_jsx_runtime","className","import_genre_tree","import_jsx_runtime","uuidv4"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/popup/PopupTitle.tsx","../src/popup/PopupButtons.tsx","../src/popup/BasePopup.tsx","../src/popup/Toast.tsx","../src/popup/TrackUploadPopup.tsx","../src/popup/AuthErrorPopup.tsx","../src/popup/InternalErrorPopup.tsx","../src/popup/NetworkErrorPopup.tsx","../src/popup/AuthPopup.tsx","../src/popup/SpotifyAuthErrorPopup.tsx","../src/auth/AuthCallbackHandler.tsx","../src/genre-tree/GenreTreeView.tsx","../src/genre-tree/playlist-tree/size-metric.ts","../src/genre-tree/playlist-tree/toTreeNodes.ts","../src/genre-tree/playlist-tree/TreePerRoot.tsx","../src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts","../src/genre-tree/playlist-tree/TreeWheel.tsx","../src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx","../src/genre-tree/GenreTreeWheelHandoff.tsx","../src/genre-tree/GenreSearch.tsx","../src/genre-tree/GenreInfoPanelBody.tsx","../src/genre-tree/GenreActionBand.tsx","../src/genre-tree/ShareActionButton.tsx","../src/genre-tree/SharePopup.tsx","../src/genre-tree/ShareDestinationButton.tsx","../src/genre-tree/SharePreviewCard.tsx","../src/genre-tree/GenreDetailExtras.tsx","../src/genre-tree/ClampedText.tsx","../src/genre-tree/GenrePlaylistTracks.tsx","../src/genre-tree/MusicBrainzRecordingLink.tsx","../src/genre-tree/ChangeParentPopup.tsx","../src/genre-tree/artist/useArtistPanel.tsx","../src/genre-tree/artist/ArtistHeader.tsx","../src/genre-tree/artist/ArtistLinks.tsx","../src/genre-tree/artist/formatYears.ts","../src/genre-tree/artist/ArtistPhoto.tsx","../src/genre-tree/artist/ArtistMembers.tsx","../src/genre-tree/artist/ArtistDiscography.tsx","../src/genre-tree/artist/ArtistSongs.tsx","../src/genre-tree/track-list-sidebar/TrackItem.tsx","../src/genre-tree/TrackPositionPlayPause.tsx","../src/genre-tree/song/useSongPanel.tsx","../src/genre-tree/song/SongPanelContent.tsx","../src/genre-tree/SizeMetricToggle.tsx","../src/genre-tree/GenreDetailPanel.tsx","../src/genre-tree/index.ts","../src/genre-tree/track-list-sidebar/TrackListSidebar.tsx","../src/genre-tree/PlayerPanel.tsx","../src/genre-tree/PlayerControls.tsx","../src/genre-tree/Rating.tsx"],"sourcesContent":["export * from \"./popup\";\nexport * from \"./auth\";\nexport * from \"./genre-tree\";\n","\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\ninterface PopupTitleProps {\n title: string;\n onClose: () => void;\n isDismissable?: boolean;\n icon?: LucideIcon;\n className?: string;\n titleClassName?: string;\n closeButtonClassName?: string;\n}\n\nexport function PopupTitle({\n title,\n onClose,\n isDismissable,\n icon: Icon,\n className = \"\",\n titleClassName = \"\",\n closeButtonClassName = \"\",\n}: PopupTitleProps) {\n return (\n <div className={`popup-title flex items-center justify-between bg-black p-4 rounded-t-lg ${className}`}>\n <div className=\"flex items-center gap-2\">\n {Icon && <Icon className=\"h-5 w-5 text-white\" />}\n <h3 id=\"popup-title\" className={`text-white font-semibold ${titleClassName}`}>\n {title}\n </h3>\n </div>\n {isDismissable && (\n <Button\n onClick={onClose}\n className={`h-8 w-8 text-white hover:text-white/80 ${closeButtonClassName}`}\n aria-label=\"Close popup\"\n >\n <X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\ninterface PopupButtonsProps {\n children: ReactNode;\n alignment?: \"left\" | \"center\" | \"right\";\n className?: string;\n}\n\nexport function PopupButtons({ children, alignment = \"center\", className = \"\" }: PopupButtonsProps) {\n const alignmentClasses = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n };\n\n return <div className={`flex gap-3 ${alignmentClasses[alignment]} ${className}`}>{children}</div>;\n}\n","\"use client\";\n\nimport { createPortal } from \"react-dom\";\nimport { ReactNode, useEffect, useRef } from \"react\";\nimport { PopupTitle } from \"./PopupTitle\";\nimport { PopupButtons } from \"./PopupButtons\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\nexport interface BasePopupProps {\n title: string;\n children: ReactNode;\n onClose?: () => void;\n type?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\" | \"spotify\" | \"auth\";\n icon?: LucideIcon;\n isDismissable?: boolean;\n className?: string;\n overlayClassName?: string;\n titleClassName?: string;\n titleTextClassName?: string;\n closeButtonClassName?: string;\n /** Sizes the popup to its content (up to the type's max width) instead of filling it. */\n fitContent?: boolean;\n contentClassName?: string;\n showOkButton?: boolean;\n showCancelButton?: boolean;\n okButtonText?: string;\n okButtonDisabled?: boolean;\n cancelButtonText?: string;\n onOk?: () => void;\n onCancel?: () => void;\n buttonAlignment?: \"left\" | \"center\" | \"right\";\n /**\n * Distance (in px) reserved at the top of the viewport, e.g. for a fixed app header/banner.\n * Each consuming app passes its own value. Defaults to 0.\n */\n topOffset?: number;\n /** Keeps keyboard focus within the popup and restores it to the opener when dismissed. */\n trapFocus?: boolean;\n /** Closes a dismissable popup on Escape. */\n closeOnEscape?: boolean;\n /** Closes a dismissable popup when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n}\n\nconst typeClasses: Record<string, string> = {\n default: \"w-full max-w-lg\",\n success: \"w-full max-w-lg\",\n error: \"w-full max-w-lg\",\n warning: \"w-full max-w-lg\",\n info: \"w-full max-w-lg\",\n spotify: \"w-full max-w-md\",\n auth: \"w-full max-w-md\",\n};\n\nconst contentBgClasses: Record<string, string> = {\n spotify: \"bg-green-500\",\n auth: \"bg-gray-800\",\n};\n\nexport function BasePopup(props: BasePopupProps) {\n const {\n title,\n children,\n onClose,\n type = \"default\",\n icon,\n isDismissable = true,\n className,\n overlayClassName = \"\",\n titleClassName = \"\",\n titleTextClassName = \"\",\n closeButtonClassName = \"\",\n fitContent = false,\n contentClassName = \"\",\n showOkButton = false,\n showCancelButton = false,\n okButtonText = \"OK\",\n cancelButtonText = \"Cancel\",\n onOk,\n okButtonDisabled = false,\n onCancel,\n buttonAlignment = \"center\",\n topOffset = 0,\n trapFocus = false,\n closeOnEscape = false,\n closeOnBackdrop = false,\n } = props;\n const dialogRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const dialog = dialogRef.current;\n const focusableSelector =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n const getFocusable = () =>\n Array.from(dialog?.querySelectorAll<HTMLElement>(focusableSelector) ?? []).filter(\n (element) => !element.hasAttribute(\"hidden\") && element.getAttribute(\"aria-hidden\") !== \"true\",\n );\n\n if (trapFocus) (getFocusable()[0] ?? dialog)?.focus();\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && closeOnEscape && isDismissable) {\n event.preventDefault();\n onClose?.();\n return;\n }\n if (!trapFocus || event.key !== \"Tab\") return;\n\n const focusable = getFocusable();\n if (focusable.length === 0) {\n event.preventDefault();\n dialog?.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (event.shiftKey && (document.activeElement === first || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n first.focus();\n }\n };\n\n if (trapFocus || closeOnEscape) document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n if (trapFocus || closeOnEscape) document.removeEventListener(\"keydown\", handleKeyDown);\n if (trapFocus && previousFocus?.isConnected) previousFocus.focus();\n };\n }, [closeOnEscape, isDismissable, onClose, trapFocus]);\n\n const baseClasses = \"bg-white rounded-lg\";\n\n const renderButtons = () => {\n if (!showOkButton && !showCancelButton) return null;\n\n return (\n <div className=\"mt-4 pt-4 border-t\">\n <PopupButtons alignment={buttonAlignment}>\n {showCancelButton && (\n <Button onClick={onCancel || onClose || (() => {})} variant=\"secondary\">\n {cancelButtonText}\n </Button>\n )}\n {showOkButton && (\n <Button onClick={onOk || onClose || (() => {})} disabled={okButtonDisabled}>\n {okButtonText}\n </Button>\n )}\n </PopupButtons>\n </div>\n );\n };\n\n const popupComponent = (\n <div\n className={`fixed z-50 flex items-center justify-center bg-transparent ${overlayClassName}`}\n style={{\n top: topOffset,\n left: 0,\n right: 0,\n bottom: 0,\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"popup-title\"\n onClick={closeOnBackdrop && isDismissable ? onClose : undefined}\n >\n <div\n ref={dialogRef}\n tabIndex={trapFocus ? -1 : undefined}\n className={`${baseClasses} ${fitContent ? typeClasses[type].replace(\"w-full\", \"w-fit\") : typeClasses[type]} ${className || \"\"}`}\n onClick={(e) => e.stopPropagation()}\n >\n <PopupTitle\n title={title}\n onClose={onClose ?? (() => {})}\n isDismissable={isDismissable}\n icon={icon}\n className={titleClassName}\n titleClassName={titleTextClassName}\n closeButtonClassName={closeButtonClassName}\n />\n <div className={`popup-content rounded-b-lg p-4 ${contentBgClasses[type] ?? \"\"} ${contentClassName}`}>\n {children}\n {renderButtons()}\n </div>\n </div>\n </div>\n );\n\n return createPortal(popupComponent, document.body);\n}\n\nexport default BasePopup;\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nexport type ToastProps = {\n message: string;\n onClose: () => void;\n durationMs?: number;\n};\n\nexport default function Toast({ message, onClose, durationMs = 2200 }: ToastProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n const timeout = window.setTimeout(onClose, durationMs);\n return () => window.clearTimeout(timeout);\n }, [durationMs, onClose]);\n\n if (!mounted) return null;\n\n return createPortal(\n <div className=\"pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4\">\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-sm text-[var(--color-text)] shadow-lg ring-1 ring-black/10\"\n >\n {message}\n </div>\n </div>,\n document.body,\n );\n}\n","\"use client\";\n\nimport { MdError, MdCheckCircle, MdUpload } from \"react-icons/md\";\nimport { useTrackUpload, type UploadStatus, type UseTrackUploadOptions } from \"@behindthemusictree/app-kit/popup\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\ntype TrackUploadPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> &\n UseTrackUploadOptions & {\n onClose?: () => void;\n };\n\nfunction getStatusIcon(status: UploadStatus) {\n switch (status) {\n case \"success\":\n return <MdCheckCircle className=\"text-[var(--color-green-500)]\" size={20} />;\n case \"error\":\n return <MdError className=\"text-[var(--color-red-500)]\" size={20} />;\n case \"uploading\":\n return <MdUpload className=\"text-[var(--color-blue-500)] animate-pulse\" size={20} />;\n default:\n return <div className=\"w-5 h-5 rounded-full border-2 border-[var(--color-neutral-300)]\" />;\n }\n}\n\nfunction getStatusText(status: UploadStatus) {\n switch (status) {\n case \"success\":\n return \"Uploaded\";\n case \"error\":\n return \"Failed\";\n case \"uploading\":\n return \"Uploading...\";\n default:\n return \"Pending\";\n }\n}\n\nexport default function TrackUploadPopup({\n files,\n genre,\n onProcessFile,\n onComplete,\n onClose,\n uploadTimeoutMs,\n ...rest\n}: TrackUploadPopupProps) {\n const { items, isUploading, allComplete, successfulCount, errorCount } = useTrackUpload({\n files,\n genre,\n onProcessFile,\n onComplete,\n uploadTimeoutMs,\n });\n\n const displayTotal = items.length > 0 ? items.length : files.length;\n const trackNoun = useTrackNoun();\n\n return (\n <BasePopup\n {...rest}\n onClose={onClose}\n title={`Upload ${capitalize(trackNoun.plural)} (${successfulCount}/${displayTotal})`}\n isDismissable\n showOkButton\n okButtonText=\"OK\"\n okButtonDisabled={isUploading || !allComplete}\n onOk={onClose}\n >\n <div className=\"space-y-4\">\n {items.map((item) => (\n <div key={item.id} className=\"border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center space-x-3\">\n {getStatusIcon(item.status)}\n <div>\n <div className=\"font-medium text-sm truncate max-w-xs\" title={item.file.name}>\n {item.file.name}\n </div>\n <div className=\"text-xs text-[var(--color-neutral-500)]\">{(item.file.size / 1024 / 1024).toFixed(1)} MB</div>\n </div>\n </div>\n <div className=\"text-sm text-[var(--color-neutral-600)]\">{getStatusText(item.status)}</div>\n </div>\n\n {item.status === \"uploading\" && (\n <div className=\"w-full bg-[var(--color-neutral-200)] rounded-full h-2\">\n <div\n className=\"bg-[var(--color-blue-500)] h-2 rounded-full transition-all duration-300\"\n style={{ width: `${item.progress}%` }}\n />\n </div>\n )}\n\n {item.status === \"error\" && item.error && (\n <div className=\"text-[var(--color-red-500)] text-sm mt-2\">\n {item.error.name === \"InvalidInputError\"\n ? \"Upload failed due to invalid file data. Please check your file and try again.\"\n : item.error.name === \"ZodError\"\n ? \"Upload failed due to a server error. Please try again later.\"\n : item.error.message}\n </div>\n )}\n </div>\n ))}\n\n {allComplete && (\n <div className=\"flex justify-between items-center\">\n <div className=\"text-sm text-[var(--color-neutral-600)]\">\n {successfulCount} successful, {errorCount} failed\n </div>\n </div>\n )}\n </div>\n </BasePopup>\n );\n}\n","\"use client\";\n\nimport { AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\n\ntype AuthErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n message: string;\n onClose: () => void;\n};\n\nexport default function AuthErrorPopup({ message, onClose, ...rest }: AuthErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Sign-in error\"\n isDismissable\n icon={AlertCircle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <p className=\"text-center text-lg font-medium text-[var(--color-text)]\">{message}</p>\n <Button onClick={onClose} variant=\"secondary\" className=\"w-full max-w-xs\">\n Close\n </Button>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { AlertTriangle, AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype InternalErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n /**\n * Support contact email shown in the \"if the problem persists\" message. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function InternalErrorPopup({ errorCode, contactEmail, ...rest }: InternalErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Internal Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertCircle className=\"h-16 w-16 text-[var(--color-red-500)]\" strokeWidth={1.5} />\n <div>\n <p className=\"text-center text-[var(--color-neutral-600)]\">\n Please try again. If the problem persists, contact us at{\" \"}\n <a href={`mailto:${contactEmail}`} className=\"text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]\">\n {contactEmail}\n </a>\n </p>\n </div>\n {errorCode && (\n <div className=\"text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">\n Error Code: {errorCode}\n </div>\n )}\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { AlertTriangle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype NetworkErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n};\n\nconst OFFLINE_MESSAGE = \"It seems that you are not connected to the internet. Please check your connection and try again.\";\nconst UNREACHABLE_MESSAGE = \"We couldn't reach the server. Please try again in a moment.\";\n\nexport default function NetworkErrorPopup({ errorCode, ...rest }: NetworkErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Network Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertTriangle className=\"h-16 w-16 text-[var(--color-neutral-600)]\" strokeWidth={1.5} />\n <p className=\"text-center text-[var(--color-neutral-700)]\">\n {errorCode === ErrorCode.NETWORK_OFFLINE ? OFFLINE_MESSAGE : UNREACHABLE_MESSAGE}\n </p>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\nimport { User } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { FaSpotify } from \"react-icons/fa\";\nimport { FcGoogle } from \"react-icons/fc\";\n\ntype AuthPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n handleSpotifyOAuth: (redirectAfterAuthPath?: string) => void;\n handleGoogleOAuth?: (redirectAfterAuthPath?: string) => void;\n redirectAfterAuthPath?: string;\n spotifyOnly?: boolean;\n message?: string;\n /**\n * Body copy shown when `spotifyOnly` is true. Wording (app name, feature description) is\n * app-specific, so each consuming app supplies its own instead of this package assuming one.\n */\n spotifyOnlyDescription: ReactNode;\n /** Body copy shown when `spotifyOnly` is false, for the same reason as `spotifyOnlyDescription`. */\n defaultDescription: ReactNode;\n};\n\nexport default function AuthPopup({\n handleSpotifyOAuth,\n handleGoogleOAuth,\n redirectAfterAuthPath,\n spotifyOnly,\n message,\n spotifyOnlyDescription,\n defaultDescription,\n ...rest\n}: AuthPopupProps) {\n const showGoogle = !spotifyOnly && handleGoogleOAuth;\n\n return (\n <BasePopup\n {...rest}\n title={spotifyOnly ? \"Connect with Spotify\" : \"Sign in\"}\n isDismissable={false}\n icon={User}\n type=\"auth\"\n children={\n <div className=\"flex flex-col items-center space-y-7\">\n <div className=\"px-2 text-center\">\n {message && <p className=\"mb-3 text-base font-medium leading-relaxed text-amber-200\">{message}</p>}\n <p className=\"text-lg font-medium leading-relaxed text-white\">\n {spotifyOnly ? spotifyOnlyDescription : defaultDescription}\n </p>\n </div>\n <div className=\"flex w-full flex-col items-center gap-3\">\n <Button\n onClick={() => handleSpotifyOAuth(redirectAfterAuthPath)}\n className=\"relative mb-2 w-fit transform bg-green-600 px-8 py-3 text-white shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-green-700 hover:shadow-xl\"\n >\n <span className=\"flex items-center justify-center\">\n <FaSpotify className=\"absolute left-6 text-3xl\" />\n <span className=\"w-full pl-14 pr-8 text-center text-lg font-medium\">Sign in with Spotify</span>\n </span>\n </Button>\n {showGoogle && (\n <Button\n variant=\"outline\"\n onClick={() => handleGoogleOAuth!(redirectAfterAuthPath)}\n className=\"relative w-fit transform border-white bg-white px-8 py-3 text-black shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-[var(--color-neutral-100)] hover:shadow-xl\"\n >\n <span className=\"flex items-center justify-center\">\n <FcGoogle className=\"absolute left-6 text-2xl\" />\n <span className=\"w-full pl-14 pr-8 text-center text-lg font-medium text-black\">\n Sign in with Google\n </span>\n </span>\n </Button>\n )}\n </div>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { FaSpotify } from \"react-icons/fa\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { User } from \"lucide-react\";\nimport { getSpotifyAllowlistMailtoHref } from \"@behindthemusictree/app-kit/transport\";\n\ntype SpotifyAuthErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n message: string;\n details?: string;\n onClose: () => void;\n /**\n * Support contact email used to build the allowlist-request mailto link. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function SpotifyAuthErrorPopup({\n message,\n details,\n onClose,\n contactEmail = null,\n ...rest\n}: SpotifyAuthErrorPopupProps) {\n const requestAccessHref = getSpotifyAllowlistMailtoHref(contactEmail);\n\n return (\n <BasePopup\n {...rest}\n title=\"Authentication Failed\"\n isDismissable\n icon={User}\n children={\n <div className=\"flex flex-col items-center space-y-8 py-4\">\n <FaSpotify className=\"text-[#1DB954] text-7xl\" />\n <div className=\"space-y-3 text-center\">\n <p className=\"text-xl font-medium text-[var(--color-text)]\">{message}</p>\n {details && (\n <p className=\"text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">{details}</p>\n )}\n <div className=\"space-y-2 text-center\">\n <p className=\"text-base text-[var(--color-neutral-700)]\">\n The Spotify app is in development mode, so only allowlisted accounts can connect.\n </p>\n {requestAccessHref && contactEmail ? (\n <p className=\"text-base text-[var(--color-neutral-700)]\">\n Request access by emailing your{\" \"}\n <a\n href={requestAccessHref}\n className=\"text-[#1DB954] font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-[#1DB954] focus:ring-offset-1 rounded\"\n >\n Spotify full name and Spotify email address\n </a>{\" \"}\n to{\" \"}\n <a\n href={requestAccessHref}\n className=\"text-[#1DB954] font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-[#1DB954] focus:ring-offset-1 rounded\"\n >\n {contactEmail}\n </a>\n .\n </p>\n ) : (\n <p className=\"text-base text-[var(--color-neutral-700)]\">To request access, contact the app owner.</p>\n )}\n </div>\n </div>\n <Button\n onClick={onClose}\n className=\"bg-[#1DB954] hover:bg-[#1DB954]/90 text-white w-full max-w-xs transform transition-all duration-200 hover:scale-[1.02] shadow-lg hover:shadow-xl\"\n >\n <FaSpotify className=\"mr-2 text-lg\" />\n Try Again\n </Button>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\nimport { useAuthCallback, type AuthCallbackState, type UseAuthCallbackOptions } from \"@behindthemusictree/app-kit/auth\";\n\nexport interface AuthCallbackHandlerProps extends UseAuthCallbackOptions {\n /** Optional custom \"connecting…\" / error UI; defaults to a minimal full-screen overlay. */\n renderStatus?: (state: Exclude<AuthCallbackState, { status: null }>) => ReactNode;\n}\n\n/** Handles OAuth callbacks at layout level. Rendered in the app shell so it runs on every load. */\nexport default function AuthCallbackHandler({ renderStatus, ...options }: AuthCallbackHandlerProps) {\n const state = useAuthCallback(options);\n\n if (state.status === null) return null;\n\n if (renderStatus) return <>{renderStatus(state)}</>;\n\n if (state.status === \"error\") {\n return (\n <div className=\"fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[var(--color-neutral-100)] p-4\">\n <div className=\"w-full max-w-md rounded-lg border border-[color-mix(in_srgb,var(--color-red-500)_20%,transparent)] bg-[color-mix(in_srgb,var(--color-red-500)_10%,transparent)] p-4\">\n <h2 className=\"mb-2 font-semibold text-[var(--color-red-500)]\">Authentication Error</h2>\n <p className=\"text-[color-mix(in_srgb,var(--color-red-500)_80%,transparent)]\">{state.message}</p>\n <button\n type=\"button\"\n onClick={state.goHome}\n className=\"mt-4 rounded bg-[var(--color-red-600)] px-4 py-2 text-white hover:bg-[var(--color-red-700)]\"\n >\n Go to home\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[var(--color-neutral-100)]\" data-page=\"auth-callback-handler\">\n <div className=\"text-center\">\n <h1 className=\"mb-4 text-2xl font-bold\">Connecting…</h1>\n <p className=\"text-[var(--color-neutral-600)]\">Completing sign-in.</p>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, type ComponentProps, type ReactNode } from \"react\";\nimport { useIsMutating } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\nimport { Plus } from \"lucide-react\";\nimport { Maximize2, Minimize2 } from \"lucide-react\";\nimport { IconTextButton, IconButton, Button, SegmentedControl, DetailPanel, useObscuredInset } from \"@behindthemusictree/ui\";\n\nimport {\n GenreNodeAncestors,\n GenreNodeFamily,\n GenreTreeWheelSkeleton,\n GenreTreeViewSkeleton,\n useGenreNodeRelations,\n} from \"@behindthemusictree/genre-tree-view\";\nimport type {\n GenreTreeAction,\n GenreTreeNode,\n GenreTreeViewMode,\n GenreTreeViewport,\n GenreTreeProps,\n GenreNodeRelations,\n GenreNodeStyleResolver,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport {\n CriteriaMinimum,\n CriteriaPlaylistSimple,\n useArtistSearch,\n useSongSearch,\n isSongSearchResultPlayable,\n type ArtistSearchResult,\n type RenderSongLink,\n type SongSearchResult,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { useGenreTreeViewState, useArtist, useUpdateGenre, genreUpdateMutationKey } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport type { GenreDetailSlots } from \"./GenreDetailExtras\";\nimport type { GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nimport type { HostOverlayPanel } from \"./overlayPanel\";\nimport { toTreeNodes } from \"./playlist-tree/toTreeNodes\";\nimport type { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport GenrePlaylistTreePerRoot from \"./playlist-tree/TreePerRoot\";\nimport GenrePlaylistTreeWheel from \"./playlist-tree/TreeWheel\";\nimport GenrePlaylistTreeWheelRadialPopCore, { type GenreFocusProps } from \"./playlist-tree/TreeWheelRadialPopCore\";\nimport { GenreTreeWheelHandoff } from \"./GenreTreeWheelHandoff\";\nimport GenreSearch, { type GenreSearchExtraGroup } from \"./GenreSearch\";\nimport { GenreInfoPanelBody } from \"./GenreInfoPanelBody\";\nimport ChangeParentPopup, { type ParentChange } from \"./ChangeParentPopup\";\nimport { useArtistPanel } from \"./artist/useArtistPanel\";\nimport { useSongPanel } from \"./song/useSongPanel\";\nimport { SizeMetricToggle } from \"./SizeMetricToggle\";\nimport { hasViewsCount, type GenreTreeSizeMetric } from \"./playlist-tree/size-metric\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\nimport type { GetGenreTreeShareUrl, GenreTreeShareTarget } from \"./share\";\n\nexport type {\n FocusChangeReason,\n GenreTreeViewMode,\n} from \"@behindthemusictree/genre-tree-view\";\nexport type { GenreFocusProps } from \"./playlist-tree/TreeWheelRadialPopCore\";\n\n/** Artists are searched by default; picking one opens its panel (see `artistUuid`). */\nexport type ArtistSearchProps =\n | { artistSearch: false; onArtistSelect?: never; artistSearchBaseUrl?: never }\n | {\n artistSearch?: true;\n /** Notified when a suggestion is picked, after the view opens its artist panel. */\n onArtistSelect?: (artist: ArtistSearchResult) => void;\n /** Backend that serves `artists/` (search and the artist panel). Defaults to `getBackendBaseUrl`. */\n artistSearchBaseUrl?: () => string;\n };\n\nconst NO_POP_ROOT_TITLE = \"This genre tree has no 'Mainstream Pop' root yet\";\n\nexport type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {\n scope: Scope;\n /** \"regions\" lays the info panel out in the brand's left/center/right columns (see brand `--layout-center-*`). */\n placement?: \"default\" | \"regions\";\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n getBackendBaseUrl: () => string;\n /** Called after the Parent popup moved a genre; `from` and `to` are genre (criteria) uuids, `from` null for a root genre. */\n onParentChanged?: (change: ParentChange) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n /** Controlled view mode. When provided, the internal Stacked/Wheel toggle is not rendered — the consumer owns that UI. */\n viewMode?: GenreTreeViewMode;\n /** Controlled selection (a genre playlist uuid, i.e. the tree node id). When provided, the\n * consumer owns it — e.g. to move the selection after deleting the selected genre. */\n selectedNodeId?: string | null;\n /** Called on every user-driven selection change (tree click, search pick, search clear). */\n onSelectedNodeIdChange?: (nodeId: string | null) => void;\n /** Mount-only seed for the pan/zoom (wheel, pop-core and stacked views), replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Restore silently after layout; use a new revision for each navigation. Supply focus, selection,\n * and restoration together. User interaction supersedes a pending request. In stacked view,\n * the selected genre must resolve to a root; outline ignores restoration. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. In stacked view, only the tree holding the selection reports. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** When true, hides the \"Add root\" button and suppresses per-node\n * create/rename affordances, for a read-only consumer. Defaults to false. */\n readOnly?: boolean;\n /** Root element classes; the component adds no outer margin. */\n className?: string;\n /** Shows the \"Tree display options\" group (e.g. Rotation). Defaults to true. */\n showDisplayOptions?: boolean;\n /** Shows the \"Add root\" button (still hidden when `readOnly`). Defaults to true; a host rendering its own passes false. */\n showAddRoot?: boolean;\n /** Controlled wheel rotation. When provided, the internal Rotation toggle is not rendered — the consumer owns that UI. */\n allowWheelRotation?: boolean;\n /** Parses the selected genre's overview; pass an extended schema to keep consumer-specific fields. */\n criteriaOverviewSchema?: z.ZodType<O, unknown>;\n /** Consumer-specific rows rendered in the info panel after Summary. */\n renderGenreDetailExtras?: (overview: O) => ReactNode;\n /** Host control displayed before the size metric in the floating controls. */\n floatingControlsLeading?: ReactNode;\n /** Controlled size metric of the wheel and pop-core nodes: song count or summed song views. Uncontrolled when omitted\n * (starts on songs). The toggle is hidden while an artist filters the tree, which only has song counts. */\n sizeMetric?: GenreTreeSizeMetric;\n onSizeMetricChange?: (metric: GenreTreeSizeMetric) => void;\n /** When set, artist names in the info panel's track list render through it (e.g. a router `Link`). In the artist\n * panel, names otherwise render as buttons that switch the panel to that artist. */\n renderArtistLink?: RenderArtistLink;\n /** Controlled artist panel (an artist uuid). It shares the info panel's slot: opening an artist clears the selected\n * genre; selecting a genre keeps the artist as a filter (the tree shows only genres the artist has songs in, and\n * the genre's songs and play button are limited to the artist) and shows the genre's panel instead. A host\n * `overlayPanel` takes precedence over the artist panel. */\n artistUuid?: string | null;\n /** Called when the user opens an artist (search pick, member link) or closes its panel. */\n onArtistUuidChange?: (uuid: string | null) => void;\n /** Controlled expanded state of the artist panel. Uncontrolled, it resets to compact on each artist. */\n artistExpanded?: boolean;\n onArtistExpandedChange?: (expanded: boolean) => void;\n /** Controlled song panel (a song uuid). Like the artist panel it shares the info panel's slot: opening a song clears\n * the selected genre and the artist; selecting a genre closes it. A host `overlayPanel` takes precedence. */\n songUuid?: string | null;\n /** Called when the user opens a song (search pick, title link) or closes its panel. */\n onSongUuidChange?: (uuid: string | null) => void;\n /** Controlled expanded state of the song panel. Uncontrolled, it resets to compact on each song. */\n songExpanded?: boolean;\n onSongExpandedChange?: (expanded: boolean) => void;\n /** Songs are searched by default; picking one opens its panel. */\n songSearch?: boolean;\n onSongSelect?: (song: SongSearchResult) => void;\n /** When set, song titles in the info panel's track list render through it (e.g. a router `Link` to the song).\n * By default they are buttons that open the song panel. */\n renderSongLink?: RenderSongLink;\n /** Resolves a selected genre, artist or song to a share URL using the host app's routes. */\n getShareUrl?: GetGenreTreeShareUrl;\n} & ArtistSearchProps &\n GenreDetailSlots<O> &\n GenrePlaylistTracksSlots &\n GenreFocusProps & {\n /** Appended to a selected genre's panel, e.g. destructive actions. */\n renderDangerZone?: (node: GenreTreeNode) => ReactNode;\n /** Controlled expanded state of the genre panel. Uncontrolled when omitted. */\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** A host panel rendered in the detail panel's slot; it takes precedence over the genre, song and artist panels. */\n overlayPanel?: HostOverlayPanel;\n /** Hides the info panel behind its edge tab; uncontrolled when omitted. Only applies with `placement=\"regions\"`. */\n collapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n };\n\nconst REGIONS_CLASS =\n \"[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]\";\n\nconst noop = () => {};\n\nexport function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({\n scope,\n handleGenreCreationAction,\n handleGenreRenameAction,\n onParentChanged,\n getBackendBaseUrl,\n additionalActions,\n viewMode: controlledViewMode,\n selectedNodeId: controlledSelectedNodeId,\n onSelectedNodeIdChange,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n readOnly = false,\n className,\n showDisplayOptions = true,\n showAddRoot = true,\n allowWheelRotation: controlledAllowWheelRotation,\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n floatingControlsLeading,\n sizeMetric: controlledSizeMetric,\n onSizeMetricChange,\n renderArtistLink,\n artistUuid: controlledArtistUuid,\n onArtistUuidChange,\n artistExpanded: controlledArtistExpanded,\n onArtistExpandedChange,\n getShareUrl,\n artistSearch = true,\n onArtistSelect,\n artistSearchBaseUrl,\n songUuid: controlledSongUuid,\n onSongUuidChange,\n songExpanded: controlledSongExpanded,\n onSongExpandedChange,\n songSearch = true,\n onSongSelect,\n renderSongLink,\n renderSummary,\n renderEssentialTracks,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n renderDangerZone,\n expanded,\n onExpandedChange,\n collapsed,\n onCollapsedChange,\n overlayPanel,\n placement = \"default\",\n ...focus\n}: GenreTreeViewProps<O>) {\n const getShareUrlRef = useRef(getShareUrl);\n getShareUrlRef.current = getShareUrl;\n const [internalArtistUuid, setInternalArtistUuid] = useState<string | null>(null);\n const isArtistControlled = controlledArtistUuid !== undefined;\n const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;\n const [internalSongUuid, setInternalSongUuid] = useState<string | null>(null);\n const isSongControlled = controlledSongUuid !== undefined;\n const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;\n const changeSong = (uuid: string | null) => {\n if (!isSongControlled) setInternalSongUuid(uuid);\n onSongUuidChange?.(uuid);\n };\n const [internalSizeMetric, setInternalSizeMetric] = useState<GenreTreeSizeMetric>(\"songs\");\n const sizeMetric = controlledSizeMetric ?? internalSizeMetric;\n const deferredSizeMetric = useDeferredValue(sizeMetric);\n const changeSizeMetric = (metric: GenreTreeSizeMetric) => {\n if (controlledSizeMetric === undefined) setInternalSizeMetric(metric);\n onSizeMetricChange?.(metric);\n };\n const changeArtist = (uuid: string | null) => {\n if (!isArtistControlled) setInternalArtistUuid(uuid);\n onArtistUuidChange?.(uuid);\n };\n const {\n isLoading,\n isFetching: isTreeFetching,\n genrePlaylistByNodeId,\n visibleGenrePlaylists,\n groupedGenrePlaylistsByRoot,\n selectedNodeId,\n selectedGenrePlaylist,\n selectNode,\n selectGenrePlaylist,\n clearSelection,\n viewMode,\n isViewModeControlled: isControlled,\n setViewMode,\n canShowPopCore,\n allowWheelRotation: internalAllowWheelRotation,\n toggleWheelRotation,\n onNodeHover: handleNodeHover,\n } = useGenreTreeViewState<O>({\n scope,\n getBackendBaseUrl,\n viewMode: controlledViewMode,\n selectedNodeId: controlledSelectedNodeId,\n onSelectedNodeIdChange,\n criteriaOverviewSchema,\n artistUuid,\n });\n\n // Renderer revision tracking is per instance: bind a navigation to one stacked root so a\n // later user selection cannot replay the same request in another renderer.\n const restoreRevision = viewportRestore?.revision;\n const [stackedRestoreOwner, setStackedRestoreOwner] = useState<{\n revision: string | number | undefined;\n rootUuid: string | undefined;\n }>({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });\n let restoreRootUuid = stackedRestoreOwner.rootUuid;\n if (stackedRestoreOwner.revision !== restoreRevision) {\n restoreRootUuid = selectedGenrePlaylist?.root.uuid;\n setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });\n } else if (restoreRootUuid === undefined && selectedGenrePlaylist) {\n restoreRootUuid = selectedGenrePlaylist.root.uuid;\n setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });\n }\n\n const isGenreUpdating = useIsMutating({ mutationKey: genreUpdateMutationKey }) > 0;\n const { mutate: updateGenre } = useUpdateGenre(scope, getBackendBaseUrl);\n const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;\n // The artist detail can load after the filtered counts, so use the UUID as the filter state.\n const isArtistFiltered = artistUuid !== null;\n const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);\n\n const artistResult = useArtist(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);\n const filterArtistName = artistResult.status === \"success\" ? artistResult.artist.name : null;\n const filterArtist = useMemo(\n () => (artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null),\n [artistUuid, filterArtistName],\n );\n const hasSelectedGenreNode =\n selectedGenrePlaylist !== undefined && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);\n const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });\n const openArtist = (uuid: string) => {\n if (selectedNodeId !== null) clearSelection();\n if (songUuid !== null) changeSong(null);\n changeArtist(uuid);\n };\n const openSong = (uuid: string) => {\n if (selectedNodeId !== null) clearSelection();\n changeSong(uuid);\n };\n // Kept stable: it feeds the memoized info panel body, which must not rebuild on every render.\n const openSongRef = useRef(openSong);\n openSongRef.current = openSong;\n const resolvedRenderSongLink = useCallback<RenderSongLink>(\n (song) =>\n renderSongLink ? (\n renderSongLink(song)\n ) : (\n <button type=\"button\" onClick={() => openSongRef.current(song.uuid)} className=\"hover:underline\">\n {song.title}\n </button>\n ),\n [renderSongLink],\n );\n const [expandedSongUuid, setExpandedSongUuid] = useState<string | null>(null);\n // Keyed by artist, so switching artists (including a host's back/forward) starts compact.\n const [expandedArtistUuid, setExpandedArtistUuid] = useState<string | null>(null);\n const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;\n const songBaseUrl = getBackendBaseUrl;\n const artistPanel = useArtistPanel({\n uuid: artistUuid,\n scope,\n getBackendBaseUrl: artistBaseUrl,\n getShareUrl: getShareUrlRef.current,\n renderArtistLink:\n renderArtistLink ??\n ((artist) =>\n artist.isSpecialPurpose ? (\n artist.name\n ) : (\n <button type=\"button\" onClick={() => openArtist(artist.uuid)} className=\"hover:underline\">\n {artist.name}\n </button>\n )),\n expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),\n onExpandedChange: (on) => {\n if (controlledArtistExpanded === undefined) setExpandedArtistUuid(on ? artistUuid : null);\n onArtistExpandedChange?.(on);\n },\n renderSongLink: resolvedRenderSongLink,\n onClose: () => changeArtist(null),\n });\n const resolvedRenderArtistLink: RenderArtistLink =\n renderArtistLink ??\n ((artist) =>\n artist.isSpecialPurpose ? (\n artist.name\n ) : (\n <button type=\"button\" onClick={() => openArtist(artist.uuid)} className=\"hover:underline\">\n {artist.name}\n </button>\n ));\n const getGenrePath = (genre: CriteriaMinimum): CriteriaPlaylistSimple[] => {\n const path: CriteriaPlaylistSimple[] = [];\n let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);\n while (current) {\n path.unshift(current);\n const parentUuid: string | undefined = current.parent?.uuid;\n current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : undefined;\n }\n return path;\n };\n const songPanel = useSongPanel({\n uuid: songUuid,\n scope,\n getBackendBaseUrl: songBaseUrl,\n getShareUrl: getShareUrlRef.current,\n renderArtistLink: resolvedRenderArtistLink,\n getGenrePath,\n onSelectGenre: (genrePlaylist) => {\n changeSong(null);\n selectGenrePlaylist(genrePlaylist);\n },\n expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),\n onExpandedChange: (on) => {\n if (controlledSongExpanded === undefined) setExpandedSongUuid(on ? songUuid : null);\n onSongExpandedChange?.(on);\n },\n onClose: () => changeSong(null),\n });\n // Like a genre, the built-in artist panel shows its name in the search field and closes from its clear button.\n // A selected genre shows its own panel instead; the artist stays active as a filter.\n // A song, like a genre, replaces the artist panel; the artist stays active as a filter.\n const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? undefined : songPanel;\n const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? undefined : artistPanel;\n const builtInPanel = builtInSongPanel ?? builtInArtistPanel;\n\n const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(false);\n const [uncontrolledExpanded, setUncontrolledExpanded] = useState(false);\n const isCollapsed = collapsed ?? uncontrolledCollapsed;\n const isExpanded = expanded ?? uncontrolledExpanded;\n const changeCollapsed = (next: boolean) => {\n setUncontrolledCollapsed(next);\n onCollapsedChange?.(next);\n };\n const changeExpanded = (next: boolean) => {\n setUncontrolledExpanded(next);\n onExpandedChange?.(next);\n };\n useEffect(() => {\n setUncontrolledCollapsed(false);\n }, [selectedNodeId, artistUuid, songUuid]);\n\n const [isWheelRevealed, setIsWheelRevealed] = useState(false);\n const usesHandoff = viewMode === \"wheel\" || viewMode === \"pop-core\";\n // The panel stands beside a tree that fades in, so it waits for the tree.\n const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);\n\n // The tree publishes how it draws each node; the panel header and family chips reuse it.\n const [resolveStyle, setResolveStyle] = useState<GenreNodeStyleResolver | null>(null);\n const handleNodeStyleResolverChange = useCallback(\n (resolver: GenreNodeStyleResolver) => setResolveStyle(() => resolver),\n [],\n );\n const panelRef = useRef<HTMLDivElement>(null);\n const obscuredInset = useObscuredInset(panelRef);\n const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };\n\n const nodeMetric: GenreTreeSizeMetric = isArtistFiltered || !canSizeByViews ? \"songs\" : deferredSizeMetric;\n const treeNodes = useMemo(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);\n const relations = useGenreNodeRelations(\n treeNodes,\n !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,\n resolveStyle,\n );\n\n // \"Previous\" returns to the genre selected before the current one.\n const [selectionHistory, setSelectionHistory] = useState<{ current: string | null; previous: string | null }>({\n current: selectedNodeId,\n previous: null,\n });\n if (selectionHistory.current !== selectedNodeId) {\n setSelectionHistory({\n current: selectedNodeId,\n previous: selectionHistory.current ?? selectionHistory.previous,\n });\n }\n const previousPlaylist =\n selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId\n ? genrePlaylistByNodeId.get(selectionHistory.previous)\n : undefined;\n const handlePrevious =\n previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist)\n ? () => handleGenreSearchSelect(previousPlaylist)\n : undefined;\n\n const { showPopup, hidePopup } = usePopup();\n const [searchText, setSearchText] = useState(\"\");\n const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : \"\");\n const artistGroup: GenreSearchExtraGroup | undefined = artistSearch\n ? {\n key: \"artists\",\n label: \"Artists\",\n items: artistSearchResult.artists.map((artist) => ({\n key: artist.uuid,\n content: (\n <>\n <span className=\"block truncate font-medium\">\n {artist.name}\n {artist.disambiguation && (\n <span className=\"font-normal text-[var(--color-neutral-500)]\"> · {artist.disambiguation}</span>\n )}\n </span>\n {artist.matchedAlias && (\n <span className=\"block truncate text-xs text-[var(--color-neutral-500)]\">\n also known as {artist.matchedAlias}\n </span>\n )}\n </>\n ),\n onSelect: () => {\n openArtist(artist.uuid);\n onArtistSelect?.(artist);\n },\n })),\n status: artistSearchResult.isError\n ? \"Search failed\"\n : artistSearchResult.settled && artistSearchResult.artists.length === 0\n ? \"No artists found\"\n : \"\",\n loading: artistSearchResult.pending,\n }\n : undefined;\n const songSearchResult = useSongSearch(songBaseUrl, songSearch ? searchText : \"\");\n const songGroup: GenreSearchExtraGroup | undefined = songSearch\n ? {\n key: \"songs\",\n label: \"Songs\",\n items: songSearchResult.songs.map((song) => ({\n key: song.uuid,\n content: (\n <span className={isSongSearchResultPlayable(song) ? undefined : \"opacity-60\"}>\n <span className=\"block truncate font-medium\">{song.title}</span>\n <span className=\"block truncate text-xs text-[var(--color-neutral-500)]\">\n {[song.artists.map((artist) => artist.name).join(\", \"), song.genre?.name ?? \"No genre\"]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </span>\n ),\n onSelect: () => {\n openSong(song.uuid);\n onSongSelect?.(song);\n },\n })),\n status: songSearchResult.isError\n ? \"Search failed\"\n : songSearchResult.settled && songSearchResult.songs.length === 0\n ? \"No songs found\"\n : \"\",\n loading: songSearchResult.pending,\n }\n : undefined;\n const extraGroups = [artistGroup, songGroup].filter((group): group is GenreSearchExtraGroup => group !== undefined);\n const handleSelectedNodeChange = (node: { id: string } | null) => {\n if (node !== null && songUuid !== null) changeSong(null);\n selectNode(node);\n };\n const handleGenreSearchClear = clearSelection;\n const handleGenreSearchSelect = (genrePlaylist: CriteriaPlaylistSimple) => {\n if (songUuid !== null) changeSong(null);\n selectGenrePlaylist(genrePlaylist);\n };\n\n // Mirrors genre-tree-view's isZoomableNode: a node zooms when it has children and at most one pop-side child.\n const selectedUuid = selectedGenrePlaylist?.uuid;\n const selectedChildren = selectedUuid\n ? visibleGenrePlaylists.filter((genrePlaylist) => genrePlaylist.parent?.uuid === selectedUuid)\n : [];\n const canZoomIn =\n viewMode === \"pop-core\" &&\n focus.onFocusGenreChange !== undefined &&\n selectedUuid !== undefined &&\n focus.focusGenreUuid !== selectedUuid &&\n selectedChildren.length > 0 &&\n selectedChildren.filter((child) => child.criteria?.side === \"pop\").length <= 1;\n const handleGenreSearchZoomIn = () => {\n if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, \"zoom-in\");\n };\n const zoomInRef = useRef<(() => void) | undefined>(undefined);\n zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : undefined;\n\n const selectGenreFromPanel = (nodeId: string) => {\n const genrePlaylist = genrePlaylistByNodeId.get(nodeId);\n if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);\n };\n // The overview fetch lives in GenreDetailExtras, so its loading states re-render only the panel, never the tree.\n // The count row is left out: the tracks section title carries it.\n const renderGenreBody = (relation: GenreNodeRelations): { primary: ReactNode } => {\n const { node } = relation;\n const family = (\n <GenreNodeFamily\n node={node}\n fill={relation.fill}\n textColor={relation.textColor}\n parentNode={relation.parentNode}\n childNodes={relation.childNodes}\n onSelectNode={selectGenreFromPanel}\n />\n );\n const ancestors = <GenreNodeAncestors ancestorNodes={relation.ancestorNodes} onSelectNode={selectGenreFromPanel} />;\n const genrePlaylist = genrePlaylistByNodeId.get(node.id);\n const criteria = genrePlaylist?.criteria;\n if (!genrePlaylist || !criteria) return { primary: <>{family}{ancestors}</> };\n const genreUuid = criteria.uuid;\n // The API only accepts a side on a root's direct child, and at most one pop child per root.\n const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);\n const canToggleSide = !readOnly && parentPlaylist !== undefined && parentPlaylist.parent === null;\n const side = criteria.side ?? \"core\";\n const shareTarget: GenreTreeShareTarget = {\n kind: \"genre\",\n genrePlaylistUuid: genrePlaylist.uuid,\n name: criteria.name,\n scope,\n artistUuid,\n };\n const shareUrl = getShareUrlRef.current?.(shareTarget) ?? undefined;\n const popSibling =\n canToggleSide && side === \"core\"\n ? visibleGenrePlaylists.find(\n (sibling) =>\n sibling.parent?.uuid === parentPlaylist.uuid &&\n sibling.uuid !== genrePlaylist.uuid &&\n sibling.criteria?.side === \"pop\",\n )\n : undefined;\n return {\n primary: (\n <>\n <GenreInfoPanelBody<O>\n family={<>{family}{ancestors}</>}\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n wikidataId={criteria.wikidataId}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n playback={playback}\n band={{\n node,\n shareTarget: shareUrl ? shareTarget : undefined,\n shareUrl,\n onZoomIn: zoomInRef.current,\n onAddChild: readOnly ? undefined : () => handleGenreCreationAction(criteria),\n onRename: readOnly ? undefined : () => handleGenreRenameAction(criteria),\n onChangeParent: readOnly\n ? undefined\n : () =>\n showPopup(\n <ChangeParentPopup\n genrePlaylist={{ ...genrePlaylist, criteria }}\n genrePlaylists={visibleGenrePlaylists}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n onClose={hidePopup}\n onParentChanged={onParentChanged}\n />,\n ),\n side,\n onToggleSide: canToggleSide\n ? () => updateGenre({ uuid: genreUuid, data: { side: side === \"pop\" ? \"core\" : \"pop\" } })\n : undefined,\n toggleSideDisabledReason: popSibling\n ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}`\n : undefined,\n additionalActions,\n }}\n extras={{\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n renderArtistLink,\n renderSongLink: resolvedRenderSongLink,\n artist: filterArtist,\n renderSummary,\n renderEssentialTracks,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n }}\n />\n {renderDangerZone?.(node)}\n </>\n ),\n };\n };\n\n const isRotationControlled = controlledAllowWheelRotation !== undefined;\n const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;\n\n type ActivePanel = {\n title: ReactNode;\n content: ReactNode;\n aside?: ReactNode;\n expanded: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n onClose: () => void;\n // A genre's or built-in panel's name lives in the search field, and the search field clears it.\n compact: boolean;\n headerAccent?: { background: string; color: string };\n };\n let activePanel: ActivePanel | null = null;\n if (overlayPanel) {\n activePanel = {\n title: overlayPanel.title,\n content: overlayPanel.content,\n aside: overlayPanel.extraContent,\n expanded: overlayPanel.expanded ?? false,\n onExpandedChange: overlayPanel.onExpandedChange,\n onClose: overlayPanel.onClose,\n compact: false,\n };\n } else if (relations) {\n const { primary } = renderGenreBody(relations);\n activePanel = {\n title: relations.node.name,\n content: primary,\n expanded: isExpanded,\n onExpandedChange: changeExpanded,\n onClose: clearSelection,\n compact: true,\n headerAccent: { background: relations.fill, color: relations.textColor },\n };\n } else if (builtInPanel) {\n activePanel = {\n title: builtInPanel.title,\n content: builtInPanel.content,\n aside: builtInPanel.extraContent,\n expanded: builtInPanel.expanded,\n onExpandedChange: builtInPanel.onExpandedChange,\n onClose: builtInPanel.onClose,\n compact: true,\n };\n }\n const isPanelShown = isTreeReady && activePanel !== null;\n\n const detailTab =\n placement === \"regions\" && activePanel?.onExpandedChange\n ? {\n edge: activePanel.expanded\n ? { label: \"Shrink panel\", onClick: () => activePanel.onExpandedChange?.(false) }\n : isCollapsed\n ? null\n : { label: \"Hide panel\", onClick: () => changeCollapsed(true) },\n center: activePanel.expanded\n ? null\n : isCollapsed\n ? { label: \"Show panel\", onClick: () => changeCollapsed(false) }\n : activePanel.onExpandedChange\n ? { label: \"Extend panel\", onClick: () => activePanel.onExpandedChange?.(true) }\n : null,\n }\n : undefined;\n\n const expandButton =\n placement === \"default\" && activePanel?.onExpandedChange ? (\n <IconButton\n aria-label={activePanel.expanded ? \"Shrink panel\" : \"Extend panel\"}\n style={activePanel.headerAccent && { color: \"inherit\" }}\n onClick={() => activePanel.onExpandedChange?.(!activePanel.expanded)}\n >\n {activePanel.expanded ? (\n <Minimize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <Maximize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n </IconButton>\n ) : null;\n\n const renderArtistFilter = () => {\n if (isLoading || !filterArtist) return null;\n return (\n <span className=\"inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm\">\n <button\n type=\"button\"\n className=\"py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline\"\n aria-label={`Show artist ${filterArtist.name}`}\n onClick={() => {\n if (selectedNodeId !== null) clearSelection();\n }}\n >\n {filterArtist.name}\n </button>\n <button\n type=\"button\"\n className=\"py-1 pl-1 pr-3 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:text-[var(--color-text)]\"\n aria-label={`Clear artist filter ${filterArtist.name}`}\n onClick={() => changeArtist(null)}\n >\n ×\n </button>\n </span>\n );\n };\n\n const actions = (\n <>\n {!isLoading && (\n <GenreSearch\n genrePlaylists={visibleGenrePlaylists}\n onSelect={handleGenreSearchSelect}\n selectedName={\n selectedGenrePlaylist?.name ??\n (typeof builtInPanel?.title === \"string\" ? builtInPanel.title : null)\n }\n onClear={\n builtInSongPanel\n ? () => changeSong(null)\n : builtInArtistPanel\n ? () => changeArtist(null)\n : handleGenreSearchClear\n }\n onPrevious={handlePrevious}\n onQueryChange={artistSearch || songSearch ? setSearchText : undefined}\n extraGroups={extraGroups}\n />\n )}\n {!isLoading && filterArtist && (\n <div className=\"hidden w-full justify-center max-md:flex\">{renderArtistFilter()}</div>\n )}\n <div className=\"contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0\">\n {!isLoading && !isControlled && (\n <SegmentedControl\n aria-label=\"Tree view mode\"\n value={viewMode}\n onChange={setViewMode}\n options={[\n { value: \"pop-core\", label: \"Pop/Core\", disabled: !canShowPopCore, title: canShowPopCore ? undefined : NO_POP_ROOT_TITLE },\n { value: \"wheel\", label: \"Wheel\" },\n { value: \"stacked\", label: \"Stacked\" },\n { value: \"outline\", label: \"Outline\", disabled: !canShowPopCore, title: canShowPopCore ? undefined : NO_POP_ROOT_TITLE },\n ]}\n />\n )}\n {!isLoading && showDisplayOptions && (\n <div\n className=\"flex items-center gap-1\"\n role=\"group\"\n aria-label=\"Tree display options\"\n >\n {!isRotationControlled && viewMode !== \"stacked\" && viewMode !== \"outline\" && (\n <Button\n variant={allowWheelRotation ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={toggleWheelRotation}\n >\n Rotation\n </Button>\n )}\n </div>\n )}\n {!isLoading && !readOnly && showAddRoot && (\n <IconTextButton\n icon={Plus}\n text=\"Add root\"\n onClick={() => handleGenreCreationAction(null)}\n />\n )}\n </div>\n </>\n );\n\n return (\n <div\n data-left-panel={isPanelShown && !isCollapsed ? undefined : \"hidden\"}\n className={[\"genre-tree-view relative flex flex-col h-full\", placement === \"regions\" && REGIONS_CLASS, className]\n .filter(Boolean)\n .join(\" \")}\n >\n <div\n className={`actions-container absolute z-30 flex items-center gap-2 ${\n placement === \"regions\"\n ? \"left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch\"\n : \"left-5 top-5\"\n }`}\n >\n {actions}\n </div>\n {!isLoading && filterArtist && (\n <div\n className={`absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${\n placement === \"regions\" ? \"top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]\" : \"top-5\"\n }`}\n >\n {renderArtistFilter()}\n </div>\n )}\n <div className=\"content-container flex-1 min-h-0 flex flex-row gap-4\">\n <div className=\"tree-view-container relative flex-1 min-w-0 flex flex-col h-full\">\n {!isLoading && viewMode === \"outline\" && floatingControlsLeading && (\n <div className=\"absolute bottom-3 right-3 z-10\">{floatingControlsLeading}</div>\n )}\n {isLoading ? (\n <GenreTreeViewSkeleton viewMode={viewMode} />\n ) : viewMode === \"wheel\" ? (\n // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,\n // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,\n // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.\n // The panel's own Zoom in button is hidden: the search bar carries it.\n <div className=\"tree-container search-overlay relative flex-1 w-full min-h-0 \">\n <GenreTreeWheelHandoff skeleton={<GenreTreeWheelSkeleton />} onRevealedChange={setIsWheelRevealed}>\n <GenrePlaylistTreeWheel\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n allowWheelRotation={allowWheelRotation}\n sizeMetric={isArtistFiltered || !canSizeByViews ? \"songs\" : deferredSizeMetric}\n busy={busy}\n floatingControlsLeading={\n floatingControlsLeading || canSizeByViews ? (\n <div className=\"flex items-center gap-2\">\n {floatingControlsLeading}\n {canSizeByViews && <SizeMetricToggle value={sizeMetric} onChange={changeSizeMetric} />}\n </div>\n ) : undefined\n }\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n />\n </GenreTreeWheelHandoff>\n </div>\n ) : viewMode === \"pop-core\" ? (\n // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.\n <div className=\"tree-container search-overlay relative flex-1 w-full min-h-0 \">\n <GenreTreeWheelHandoff skeleton={<GenreTreeWheelSkeleton />} onRevealedChange={setIsWheelRevealed}>\n <GenrePlaylistTreeWheelRadialPopCore\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n allowWheelRotation={allowWheelRotation}\n sizeMetric={isArtistFiltered || !canSizeByViews ? \"songs\" : deferredSizeMetric}\n busy={busy}\n floatingControlsLeading={\n floatingControlsLeading || canSizeByViews ? (\n <div className=\"flex items-center gap-2\">\n {floatingControlsLeading}\n {canSizeByViews && <SizeMetricToggle value={sizeMetric} onChange={changeSizeMetric} />}\n </div>\n ) : undefined\n }\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...focus}\n />\n </GenreTreeWheelHandoff>\n </div>\n ) : viewMode === \"outline\" ? (\n <div className=\"tree-container flex-1 min-h-0 w-full overflow-y-auto relative\">\n <GenrePlaylistTreeWheelRadialPopCore\n outline\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n />\n </div>\n ) : (\n <div className=\"tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]\">\n {Object.entries(groupedGenrePlaylistsByRoot).map(\n ([uuid, genrePlaylistTreePerRoot]) => {\n return (\n <div\n key={uuid}\n className=\"tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg\"\n >\n <div className=\"graph-container relative z-10 h-full\">\n <GenrePlaylistTreePerRoot\n scope={scope}\n rootUuid={uuid}\n genrePlaylistTreePerRoot={genrePlaylistTreePerRoot}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n onNodeHover={handleNodeHover}\n {...hostPanel}\n playback={playback}\n // Each root has its own panel; null closes the one not holding the selection.\n selectedNodeId={\n uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null\n }\n readOnly={readOnly}\n wheelZoom=\"modifier\"\n defaultViewport={\n uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : undefined\n }\n viewportRestore={\n uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : undefined\n }\n onViewportChange={\n uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : undefined\n }\n />\n </div>\n </div>\n );\n },\n )}\n </div>\n )}\n {/* Always mounted: useObscuredInset observes the node once, so it must exist when the host mounts. */}\n <DetailPanel\n ref={panelRef}\n side=\"left\"\n title={activePanel?.title ?? \"\"}\n hideTitle={activePanel?.compact}\n hideClose={activePanel?.compact}\n headerAccent={activePanel?.headerAccent}\n headerActions={expandButton}\n onClose={activePanel?.onClose ?? noop}\n collapsed={isCollapsed || !isTreeReady || !activePanel}\n expanded={activePanel?.expanded}\n onExpandedChange={activePanel?.onExpandedChange}\n aside={activePanel?.aside}\n edgeTab={detailTab}\n headerClassName={activePanel?.compact ? \"pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]\" : undefined}\n >\n {activePanel?.aside && !activePanel.expanded ? (\n <>\n {activePanel.content}\n {activePanel.aside}\n </>\n ) : (\n activePanel?.content\n )}\n </DetailPanel>\n </div>\n </div>\n </div>\n );\n}\n","import type { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport type GenreTreeSizeMetric = \"songs\" | \"views\";\n\nexport const hasViewsCount = (genrePlaylists: CriteriaPlaylistSimple[]): boolean =>\n genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== undefined);\n\nexport const genrePlaylistSize = (genrePlaylist: CriteriaPlaylistSimple, metric: GenreTreeSizeMetric): number => {\n if (metric === \"songs\") return genrePlaylist.tracksCount;\n if (genrePlaylist.viewsCount === undefined) {\n throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);\n }\n return genrePlaylist.viewsCount;\n};\n","import type { GenreTreeNode } from \"@behindthemusictree/genre-tree-view\";\nimport type { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { genrePlaylistSize, type GenreTreeSizeMetric } from \"./size-metric\";\n\n/** The tree nodes of a set of genre playlists, as every tree renderer and the host's detail panel see them. */\nexport function toTreeNodes(genrePlaylists: CriteriaPlaylistSimple[], sizeMetric: GenreTreeSizeMetric): GenreTreeNode[] {\n return genrePlaylists.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),\n actionable: Boolean(genrePlaylist.criteria),\n detached: !genrePlaylist.criteria,\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n umbrella: genrePlaylist.isUmbrella,\n scene: genrePlaylist.scene ?? undefined,\n }));\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport {\n GenreTree,\n getGenreTreeColor,\n type GenreTreeViewport,\n type GenreTreeProps,\n type GenreTreeAction,\n type GenreTreeNode,\n type WheelZoomMode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { HostPanelProps } from \"./hostPanelProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\n\nexport type GenrePlaylistTreePerRootProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n rootUuid: string;\n genrePlaylistTreePerRoot: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n wheelZoom?: WheelZoomMode;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Runtime viewport restoration request. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & HostPanelProps;\n\nexport default function GenrePlaylistTreePerRoot<T extends TrackBase>({\n scope,\n className,\n rootUuid,\n genrePlaylistTreePerRoot,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n readOnly = false,\n wheelZoom,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n playback,\n ...hostPanel\n}: GenrePlaylistTreePerRootProps<T>) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists: genrePlaylistTreePerRoot });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const { showPopup } = usePopup();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylistTreePerRoot.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylist.tracksCount,\n actionable: Boolean(genrePlaylist.criteria),\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n umbrella: genrePlaylist.isUmbrella,\n })),\n [genrePlaylistTreePerRoot],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n },\n [\n genrePlaylistTreePerRoot,\n playingNodeId,\n isPlaying,\n setIsPlaying,\n play,\n showPopup,\n ],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylistTreePerRoot, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylistTreePerRoot, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n return (\n <GenreTree\n className={className}\n nodes={nodes}\n rootColor={getGenreTreeColor(rootUuid)}\n playingNodeId={playingNodeId}\n playState={isPlaying ? \"playing\" : \"paused\"}\n onPlayPause={handlePlayPause}\n onAddChild={readOnly ? undefined : handleAddChild}\n onRenameRequest={readOnly ? undefined : handleRenameRequest}\n onDeleteRequest={readOnly ? undefined : handleDeleteRequest}\n additionalActions={additionalActions}\n onNodeClick={onNodeClick}\n onNodeHover={onNodeHover}\n selectedNodeId={selectedNodeId}\n onSelectedNodeChange={onSelectedNodeChange}\n wheelZoom={wheelZoom}\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...hostPanel}\n />\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport {\n TrackListOriginType,\n useTrackList,\n type CriteriaPlaylistMinimum,\n type CriteriaPlaylistSimple,\n type TrackBase,\n type TrackListOriginFromArtist,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\n/** What a play button needs of a playlist; `criteria` is only read when playing one artist's songs in the genre. */\nexport type PlayableGenrePlaylistInput = CriteriaPlaylistMinimum & { criteria?: CriteriaPlaylistSimple[\"criteria\"] };\n\n/** What a tree node's play button plays, and which node is the one currently playing. */\nexport type GenrePlaylistPlayback = {\n playingNodeId: string | null;\n play: (genrePlaylist: PlayableGenrePlaylistInput) => Promise<void>;\n};\n\nexport type UseGenrePlaylistPlaybackOptions = {\n scope: Scope;\n genrePlaylists: CriteriaPlaylistSimple[];\n /** When set, nodes play only this artist's songs in the genre's subtree instead of the whole genre playlist. */\n artist?: { uuid: string; name: string } | null;\n};\n\nexport function useGenrePlaylistPlayback<T extends TrackBase = TrackBase>({\n scope,\n genrePlaylists,\n artist = null,\n}: UseGenrePlaylistPlaybackOptions): GenrePlaylistPlayback {\n const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList<T>();\n const origin = trackList?.origin;\n\n const playingNodeId = useMemo(() => {\n if (!origin) return null;\n if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;\n if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;\n const { genreUuid } = origin as TrackListOriginFromArtist;\n return genreUuid ? (genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null) : null;\n }, [origin, artist, genrePlaylists]);\n\n const play = useCallback(\n async (genrePlaylist: PlayableGenrePlaylistInput) => {\n if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);\n // Genre-less songs have no genre to filter on.\n if (!genrePlaylist.criteria) return;\n return playNewTrackListFromArtist(artist, scope, { ordering: \"title\", genreUuid: genrePlaylist.criteria.uuid });\n },\n [artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist],\n );\n\n return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);\n}\n","\"use client\";\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n GenreTreeWheel,\n type GenreTreeViewport,\n type GenreTreeProps,\n type GenreTreeAction,\n type GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { HostPanelProps } from \"./hostPanelProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport { genrePlaylistSize, type GenreTreeSizeMetric } from \"./size-metric\";\n\nexport type GenrePlaylistTreeWheelProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n genrePlaylists: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, clicking a chip still selects its root, but the wheel doesn't spin to the\n * anchor. Defaults to true. */\n allowWheelRotation?: boolean;\n /** What node size is proportional to; defaults to the song count. */\n sizeMetric?: GenreTreeSizeMetric;\n /** Rendered in the floating controls, beside the zoom buttons. */\n floatingControlsLeading?: ReactNode;\n /** Shows the tree's busy indicator while the data behind it is changing. */\n busy?: boolean;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Runtime viewport restoration request. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & HostPanelProps;\n\nexport default function GenrePlaylistTreeWheel<T extends TrackBase>({\n scope,\n className,\n genrePlaylists,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n readOnly = false,\n allowWheelRotation,\n sizeMetric = \"songs\",\n floatingControlsLeading,\n busy,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n playback,\n ...hostPanel\n}: GenrePlaylistTreeWheelProps<T>) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const { showPopup } = usePopup();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylists.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),\n actionable: Boolean(genrePlaylist.criteria),\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n })),\n [genrePlaylists, sizeMetric],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n },\n [\n genrePlaylists,\n playingNodeId,\n isPlaying,\n setIsPlaying,\n play,\n showPopup,\n ],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n return (\n <GenreTreeWheel\n className={className}\n nodes={nodes}\n playingNodeId={playingNodeId}\n playState={isPlaying ? \"playing\" : \"paused\"}\n onPlayPause={handlePlayPause}\n onAddChild={readOnly ? undefined : handleAddChild}\n onRenameRequest={readOnly ? undefined : handleRenameRequest}\n onDeleteRequest={readOnly ? undefined : handleDeleteRequest}\n additionalActions={additionalActions}\n onNodeClick={onNodeClick}\n onNodeHover={onNodeHover}\n selectedNodeId={selectedNodeId}\n onSelectedNodeChange={onSelectedNodeChange}\n allowWheelRotation={allowWheelRotation}\n floatingControlsLeading={floatingControlsLeading}\n busy={busy}\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...hostPanel}\n />\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n GenreTreeOutline,\n GenreTreeWheelRadialPopCore,\n type GenreTreeViewport,\n type GenreTreeProps,\n type FocusChangeReason,\n type GenreTreeAction,\n type GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { HostPanelProps } from \"./hostPanelProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport type { GenreTreeSizeMetric } from \"./size-metric\";\nimport { toTreeNodes } from \"./toTreeNodes\";\n\n/** Pop-core focus is controlled-only: pass both props or neither. Ignored in outline mode. */\nexport type GenreFocusProps =\n | { focusGenreUuid?: undefined; onFocusGenreChange?: undefined }\n | {\n /** Zooms the pop-core wheel into this genre's subtree. An unknown or non-zoomable uuid shows\n * the whole tree and fires `onFocusGenreChange(null, \"rejected\")`. */\n focusGenreUuid: string | null;\n /** Fires with the genre to focus and why: `\"zoom-in\"` / `\"back\"` (parent) / `\"previous\"` /\n * `\"root\"` from the user's zoom controls, `\"search\"` when a selection outside the zoom refocuses\n * to its top-level root (null for Mainstream Pop), and\n * `\"rejected\"` when an invalid `focusGenreUuid` is reset to null. */\n onFocusGenreChange: (uuid: string | null, reason: FocusChangeReason) => void;\n };\n\nexport type GenrePlaylistTreeWheelRadialPopCoreProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n genrePlaylists: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, clicking a chip still selects its root, but the ring doesn't spin to the\n * landing angle. Defaults to true. */\n allowWheelRotation?: boolean;\n /** What node size is proportional to; defaults to the song count. */\n sizeMetric?: GenreTreeSizeMetric;\n /** Rendered in the floating controls, beside the zoom buttons. */\n floatingControlsLeading?: ReactNode;\n /** Shows the tree's busy indicator while the data behind it is changing; ignored in outline mode. */\n busy?: boolean;\n /** When true, renders the same forest as GenreTreeOutline's nested-list text view instead of\n * the radial wheel. Defaults to false. */\n outline?: boolean;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Runtime viewport restoration request. */\n viewportRestore?: GenreTreeProps[\"viewportRestore\"];\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & GenreFocusProps &\n HostPanelProps;\n\nexport default function GenrePlaylistTreeWheelRadialPopCore<T extends TrackBase>(\n props: GenrePlaylistTreeWheelRadialPopCoreProps<T>,\n) {\n const {\n scope,\n className,\n genrePlaylists,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n readOnly = false,\n allowWheelRotation,\n sizeMetric = \"songs\",\n floatingControlsLeading,\n busy,\n outline = false,\n defaultViewport,\n viewportRestore,\n onViewportChange,\n playback,\n } = props;\n const focus: GenreFocusProps = props;\n const hostPanel: HostPanelProps = {\n obscuredInset: props.obscuredInset,\n onNodeStyleResolverChange: props.onNodeStyleResolverChange,\n };\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n\n const nodes = useMemo(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n });\n },\n [genrePlaylists, playingNodeId, isPlaying, setIsPlaying, play],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n const treeProps = {\n className,\n nodes,\n playingNodeId,\n playState: isPlaying ? (\"playing\" as const) : (\"paused\" as const),\n onPlayPause: handlePlayPause,\n onAddChild: readOnly ? undefined : handleAddChild,\n onRenameRequest: readOnly ? undefined : handleRenameRequest,\n onDeleteRequest: readOnly ? undefined : handleDeleteRequest,\n additionalActions,\n onNodeClick,\n onNodeHover,\n selectedNodeId,\n onSelectedNodeChange,\n ...hostPanel,\n };\n\n return outline ? (\n <GenreTreeOutline {...treeProps} />\n ) : (\n <GenreTreeWheelRadialPopCore\n {...treeProps}\n allowWheelRotation={allowWheelRotation}\n floatingControlsLeading={floatingControlsLeading}\n busy={busy}\n defaultViewport={defaultViewport}\n viewportRestore={viewportRestore}\n onViewportChange={onViewportChange}\n {...(focus.onFocusGenreChange\n ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange }\n : { focusNodeId: undefined, onFocusChange: undefined })}\n />\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type GenreTreeWheelHandoffProps = {\n skeleton: React.ReactNode;\n children: React.ReactNode;\n /** Called with `true` once `children` is revealed, and with `false` when the handoff unmounts. */\n onRevealedChange?: (revealed: boolean) => void;\n};\n\n/**\n * Keeps `skeleton` fully opaque over `children` for the first two animation frames after\n * `children` mounts, then cross-fades to `children`.\n *\n * `children` is one of genre-tree-view's wheel components (`GenreTreeWheel` /\n * `GenreTreeWheelRadialPopCore`), which compute their own pan/zoom \"fit to frame\" in a passive\n * effect (`hasInitialFitRef`) that runs *after* their first paint. Without this handoff, that\n * first paint — unfit, `translate(0,0) scale(1)` — is briefly visible the instant the skeleton is\n * swapped out, which reads as a flash/jump right as loading finishes. Two nested\n * `requestAnimationFrame` calls reliably span that effect's own render-commit-paint cycle, so\n * `children` has had time to begin layout before it is revealed. Viewport restoration waits for\n * measurable geometry independently of this visual handoff.\n *\n * Remounting this component (e.g. via a `key` change, or the parent unmounting/remounting the\n * branch that renders it) restarts the handoff, which is what's wanted any time `children` itself\n * is about to mount fresh and re-run its own fit-to-frame effect.\n */\nexport function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }: GenreTreeWheelHandoffProps) {\n const [revealed, setRevealed] = useState(false);\n const onRevealedChangeRef = useRef(onRevealedChange);\n onRevealedChangeRef.current = onRevealedChange;\n\n useEffect(() => {\n let innerFrame = 0;\n const outerFrame = requestAnimationFrame(() => {\n innerFrame = requestAnimationFrame(() => {\n setRevealed(true);\n onRevealedChangeRef.current?.(true);\n });\n });\n return () => {\n onRevealedChangeRef.current?.(false);\n cancelAnimationFrame(outerFrame);\n cancelAnimationFrame(innerFrame);\n };\n }, []);\n\n return (\n <div className=\"relative w-full h-full\">\n <div\n className=\"absolute inset-0 transition-opacity duration-150 ease-out\"\n style={{ opacity: revealed ? 1 : 0, pointerEvents: revealed ? \"auto\" : \"none\" }}\n aria-hidden={!revealed}\n >\n {children}\n </div>\n {!revealed && (\n <div className=\"absolute inset-0\" aria-hidden=\"true\">\n {skeleton}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport { ArrowLeft, LoaderCircle, Search, X } from \"lucide-react\";\nimport { Combobox } from \"@behindthemusictree/ui\";\n\nimport { CriteriaPlaylistSimple, useGenreSearch } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport type GenreSearchExtraGroup = {\n key: string;\n /** Heading of this group; genre matches then get a \"Genres\" heading too. */\n label: string;\n items: { key: string; content: ReactNode; onSelect: () => void }[];\n /** Announced politely, e.g. \"No artists found\". */\n status?: string;\n /** Shows a spinner in place of the search icon while the host resolves this group. */\n loading?: boolean;\n};\n\nexport type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple> = {\n genrePlaylists: T[];\n onSelect: (genrePlaylist: T) => void;\n /** Name of the currently selected genre; fills the input whenever it changes. */\n selectedName?: string | null;\n /** When set, renders a \"Previous\" button left of the text and makes room for it. */\n onPrevious?: () => void;\n /** Called by the clear button, after it empties the input. */\n onClear?: () => void;\n placeholder?: string;\n /** Called with what the user types, and with \"\" on clear or pick; never when `selectedName` refills the input. */\n onQueryChange?: (query: string) => void;\n /** Further result groups (artists, songs) the host resolves from `onQueryChange`. */\n extraGroups?: GenreSearchExtraGroup[];\n};\n\nexport default function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({\n genrePlaylists,\n onSelect,\n selectedName = null,\n onClear,\n onPrevious,\n placeholder,\n onQueryChange,\n extraGroups = [],\n}: GenreSearchProps<T>) {\n const { query, setQuery, results, canClear, clear, select } = useGenreSearch({\n genrePlaylists,\n onSelect,\n selectedName,\n onClear,\n });\n const [open, setOpen] = useState(false);\n\n const active = query !== \"\" && query !== selectedName;\n const hasExtras = extraGroups.length > 0;\n const label = hasExtras ? \"Search\" : \"Search a genre\";\n const loading = active && extraGroups.some((group) => group.loading);\n const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(\". \") : \"\";\n\n const groups = [\n {\n key: \"genres\",\n label: hasExtras ? \"Genres\" : undefined,\n options: results.map((genrePlaylist) => ({\n key: genrePlaylist.uuid,\n content: (\n <>\n {genrePlaylist.name}\n {genrePlaylist.isUmbrella && (\n <span className=\"ml-2 text-xs italic text-[var(--color-text-muted)]\">umbrella</span>\n )}\n </>\n ),\n })),\n },\n ...extraGroups.map((group) => ({\n key: group.key,\n label: group.label,\n options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : [],\n })),\n ];\n\n const handlePick = (groupKey: string, optionKey: string) => {\n setOpen(false);\n if (groupKey === \"genres\") {\n const genrePlaylist = results.find((result) => result.uuid === optionKey);\n if (!genrePlaylist) return;\n select(genrePlaylist);\n onQueryChange?.(\"\");\n return;\n }\n const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);\n if (!item) return;\n setQuery(\"\");\n onQueryChange?.(\"\");\n item.onSelect();\n };\n\n return (\n <div className=\"genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full\">\n {onPrevious && (\n <button\n type=\"button\"\n aria-label=\"Previous\"\n title=\"Previous\"\n className=\"genre-search-previous absolute left-2 top-1/2 z-10 flex items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]\"\n onClick={onPrevious}\n >\n <ArrowLeft aria-hidden=\"true\" className=\"w-4 h-4\" />\n </button>\n )}\n <Combobox\n ariaLabel={label}\n listLabel={`${label} results`}\n value={query}\n placeholder={placeholder ?? `${label}…`}\n groups={groups}\n open={open}\n onOpenChange={setOpen}\n onValueChange={(value) => {\n setQuery(value);\n onQueryChange?.(value);\n }}\n onPick={handlePick}\n status={status}\n loading={loading ? \"Searching…\" : undefined}\n inputClassName={`h-[var(--layout-search-h,3rem)] ${onPrevious ? \"pl-11\" : \"pl-6\"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md focus-visible:ring-0`}\n className=\"genre-search-results z-40 rounded-xl shadow-md\"\n listClassName=\"max-h-[min(20rem,50dvh)]\"\n optionClassName=\"[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]\"\n />\n <div className=\"absolute right-3 top-1/2 flex items-center -translate-y-1/2\">\n <span data-loading={loading} className=\"relative p-2 text-[var(--color-text-muted)] pointer-events-none\">\n <Search\n aria-hidden=\"true\"\n className={`w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? \"opacity-0\" : \"\"}`}\n />\n <LoaderCircle\n aria-hidden=\"true\"\n className={`absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${\n loading ? \"opacity-100 delay-300 animate-spin motion-reduce:animate-none\" : \"opacity-0\"\n }`}\n />\n </span>\n {canClear && (\n <button\n type=\"button\"\n aria-label=\"Clear search\"\n className=\"flex items-center justify-center p-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]\"\n onClick={() => {\n clear();\n onQueryChange?.(\"\");\n }}\n >\n <X aria-hidden=\"true\" className=\"w-6 h-6\" />\n </button>\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport {\n type CriteriaOverview,\n type CriteriaPlaylistMinimum,\n type GenreUuid,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\nimport GenreActionBand from \"./GenreActionBand\";\nimport { GenreDetailExtras, type GenreDetailExtrasProps } from \"./GenreDetailExtras\";\nimport type { GenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\n\n/** What the action band reads of a genre's playlist: enough to play it and to know whether it can be. */\nexport type PlayableGenrePlaylist = CriteriaPlaylistMinimum & { tracksCount: number };\n\ntype GenreNodeActionBandProps = Omit<ComponentProps<typeof GenreActionBand>, \"onPlayPause\" | \"isPlaying\"> & {\n genrePlaylist: PlayableGenrePlaylist;\n playback: GenrePlaylistPlayback;\n};\n\nexport function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }: GenreNodeActionBandProps) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;\n const handlePlayPause = () => {\n if (isCurrentTrackList) {\n setIsPlaying(!isPlaying);\n return;\n }\n playback.play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n });\n };\n return (\n <GenreActionBand\n {...bandProps}\n onPlayPause={genrePlaylist.tracksCount > 0 ? handlePlayPause : undefined}\n isPlaying={isCurrentTrackList && isPlaying}\n />\n );\n}\n\nexport type GenreInfoPanelBodyProps<O extends CriteriaOverview> = {\n /** genre-tree-view's toolbar section, rendered first. */\n toolbar?: ReactNode;\n family: ReactNode;\n genreUuid: GenreUuid;\n genrePlaylist: PlayableGenrePlaylist;\n wikidataId?: string | null;\n scope: Scope;\n getBackendBaseUrl: () => string;\n playback: GenrePlaylistPlayback;\n /** Action band props other than play, which the body derives from `playback`. */\n band: Omit<GenreNodeActionBandProps, \"genrePlaylist\" | \"playback\">;\n extras?: Omit<\n GenreDetailExtrasProps<O>,\n \"family\" | \"genreUuid\" | \"genrePlaylist\" | \"wikidataId\" | \"scope\" | \"getBackendBaseUrl\"\n >;\n};\n\n/** The body of a genre's info panel: action band, then the genre's overview, family and tracks.\n * Shared by the tree's panel and the standalone GenreDetailPanel. */\nexport function GenreInfoPanelBody<O extends CriteriaOverview>({\n toolbar,\n family,\n genreUuid,\n genrePlaylist,\n wikidataId,\n scope,\n getBackendBaseUrl,\n playback,\n band,\n extras,\n}: GenreInfoPanelBodyProps<O>) {\n return (\n <>\n {toolbar}\n <GenreNodeActionBand {...band} genrePlaylist={genrePlaylist} playback={playback} />\n <GenreDetailExtras<O>\n {...extras}\n key={genreUuid}\n family={family}\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n wikidataId={wikidataId}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n />\n </>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ArrowLeftRight, MoveUpRight, Pause, Pencil, Play, Plus, ZoomIn } from \"lucide-react\";\nimport type {\n GenreTreeAction,\n GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport ShareActionButton from \"./ShareActionButton\";\n\nexport type GenreActionBandProps = {\n node: GenreTreeNode;\n /** When set, shows the Play/Pause action. */\n onPlayPause?: () => void;\n isPlaying?: boolean;\n /** When set, shows the \"Zoom in\" action. */\n onZoomIn?: () => void;\n /** When set, shows the \"Add\" (sub-genre) action. */\n onAddChild?: () => void;\n /** When set, shows the \"Rename\" action. */\n onRename?: () => void;\n /** When set, shows the \"Parent\" (change parent) action. */\n onChangeParent?: () => void;\n side?: \"core\" | \"pop\";\n /** When set, shows the \"Make pop\" / \"Make core\" action. */\n onToggleSide?: () => void;\n /** When set, disables the side toggle and explains why in its tooltip. */\n toggleSideDisabledReason?: string;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n shareTarget?: GenreTreeShareTarget;\n shareUrl?: string;\n};\n\ntype BandItem = {\n key: string;\n label: string;\n icon: ReactNode;\n onClick: (event: React.MouseEvent) => void;\n disabled?: boolean;\n title?: string;\n};\n\nexport default function GenreActionBand({\n node,\n onPlayPause,\n isPlaying = false,\n onZoomIn,\n onAddChild,\n onRename,\n onChangeParent,\n side,\n onToggleSide,\n toggleSideDisabledReason,\n additionalActions,\n shareTarget,\n shareUrl,\n}: GenreActionBandProps) {\n const items: BandItem[] = [];\n const adminItems: BandItem[] = [];\n if (onPlayPause) {\n items.push({\n key: \"play-pause\",\n label: isPlaying ? \"Pause\" : \"Play\",\n icon: isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"w-5 h-5\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"w-5 h-5\" />\n ),\n onClick: onPlayPause,\n });\n }\n if (onZoomIn) {\n items.push({\n key: \"zoom-in\",\n label: \"Focus\",\n icon: <ZoomIn aria-hidden=\"true\" className=\"w-5 h-5\" />,\n onClick: onZoomIn,\n });\n }\n if (onAddChild) {\n adminItems.push({ key: \"add-child\", label: \"Add\", icon: <Plus aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onAddChild });\n }\n if (onRename) {\n adminItems.push({ key: \"rename\", label: \"Rename\", icon: <Pencil aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onRename });\n }\n if (onChangeParent) {\n adminItems.push({ key: \"change-parent\", label: \"Parent\", icon: <MoveUpRight aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onChangeParent });\n }\n if (onToggleSide) {\n adminItems.push({\n key: \"toggle-side\",\n label: side === \"pop\" ? \"Make core\" : \"Make pop\",\n icon: <ArrowLeftRight aria-hidden=\"true\" className=\"w-5 h-5\" />,\n onClick: onToggleSide,\n disabled: !!toggleSideDisabledReason,\n title: toggleSideDisabledReason,\n });\n }\n for (const action of additionalActions?.(node) ?? []) {\n items.push({\n key: action.key,\n label: action.label(node),\n icon: action.icon(node),\n onClick: (event) => action.onClick(event, node),\n disabled: action.enabled ? !action.enabled(node) : false,\n });\n }\n if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;\n\n const renderItems = (list: BandItem[]) =>\n list.map((item) => (\n <button\n key={item.key}\n type=\"button\"\n disabled={item.disabled}\n title={item.title}\n onClick={item.onClick}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50\"\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]\">\n {item.icon}\n </span>\n {item.label}\n </button>\n ));\n\n return (\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n {(items.length > 0 || (shareTarget && shareUrl)) && (\n <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">\n {renderItems(items)}\n {shareTarget && shareUrl && <ShareActionButton target={shareTarget} url={shareUrl} />}\n </div>\n )}\n {adminItems.length > 0 && (\n <section\n aria-label=\"Admin actions\"\n className={`px-3.5 py-3 ${items.length > 0 ? \"border-t border-[var(--color-border)]\" : \"\"}`}\n >\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]\">Admin</h3>\n <div className=\"flex flex-wrap gap-1\">{renderItems(adminItems)}</div>\n </section>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { Share2 } from \"lucide-react\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport SharePopup from \"./SharePopup\";\n\ntype ShareActionButtonProps = {\n target: GenreTreeShareTarget;\n url: string;\n};\n\nexport default function ShareActionButton({ target, url }: ShareActionButtonProps) {\n const { showPopup, hidePopup } = usePopup();\n\n const openSharePopup = () => {\n showPopup(<SharePopup target={target} url={url} onClose={hidePopup} />);\n };\n\n return (\n <button\n type=\"button\"\n onClick={openSharePopup}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none\"\n aria-label={`Share ${target.name}`}\n title={`Share ${target.name}`}\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]\">\n <Share2 aria-hidden=\"true\" className=\"w-5 h-5\" />\n </span>\n Share\n </button>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Link2, Share2 } from \"lucide-react\";\nimport { FaWhatsapp } from \"react-icons/fa\";\nimport { SiGmail } from \"react-icons/si\";\nimport BasePopup from \"../popup/BasePopup\";\nimport Toast from \"../popup/Toast\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport ShareDestinationButton from \"./ShareDestinationButton\";\nimport SharePreviewCard from \"./SharePreviewCard\";\n\ntype SharePopupProps = {\n target: GenreTreeShareTarget;\n url: string;\n onClose: () => void;\n};\n\ntype CopyStatus = \"idle\" | \"failed\";\n\nfunction resolveAbsoluteUrl(url: string): string | undefined {\n try {\n const parsed = new URL(url, window.location.href);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\" ? parsed.href : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function copyText(text: string): Promise<boolean> {\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return true;\n }\n } catch {\n // Use the legacy copy path when clipboard access is denied or unavailable.\n }\n\n const input = document.createElement(\"textarea\");\n input.value = text;\n input.setAttribute(\"readonly\", \"\");\n input.style.position = \"fixed\";\n input.style.opacity = \"0\";\n document.body.append(input);\n input.select();\n\n try {\n return document.execCommand(\"copy\");\n } catch {\n return false;\n } finally {\n input.remove();\n }\n}\n\nexport default function SharePopup({ target, url, onClose }: SharePopupProps) {\n const [absoluteUrl, setAbsoluteUrl] = useState(url);\n const [copyStatus, setCopyStatus] = useState<CopyStatus>(\"idle\");\n const [canUseNativeShare, setCanUseNativeShare] = useState(false);\n const [shareError, setShareError] = useState<string | null>(null);\n const [toastId, setToastId] = useState(0);\n\n useEffect(() => {\n setAbsoluteUrl(resolveAbsoluteUrl(url) ?? \"\");\n setCopyStatus(\"idle\");\n setShareError(null);\n }, [url]);\n\n useEffect(() => {\n setCanUseNativeShare(typeof navigator !== \"undefined\" && typeof navigator.share === \"function\");\n }, []);\n\n const destinationUrls = useMemo(() => {\n if (!absoluteUrl) return null;\n const message = `${target.name} — ${absoluteUrl}`;\n return {\n whatsapp: `https://wa.me/?text=${encodeURIComponent(message)}`,\n gmail: `https://mail.google.com/mail/?view=cm&fs=1&su=${encodeURIComponent(`Share: ${target.name}`)}&body=${encodeURIComponent(message)}`,\n };\n }, [absoluteUrl, target.name]);\n\n const handleCopy = async () => {\n if (!absoluteUrl) {\n setCopyStatus(\"failed\");\n return;\n }\n setShareError(null);\n const copied = await copyText(absoluteUrl);\n setCopyStatus(copied ? \"idle\" : \"failed\");\n if (copied) setToastId((id) => id + 1);\n };\n\n const dismissToast = useCallback(() => setToastId(0), []);\n\n const handleNativeShare = async () => {\n if (!absoluteUrl || !navigator.share) return;\n setShareError(null);\n try {\n await navigator.share({ title: target.name, url: absoluteUrl });\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n setShareError(\"Could not open the share options.\");\n }\n };\n\n return (\n <>\n <BasePopup\n title=\"Share\"\n onClose={onClose}\n isDismissable\n trapFocus\n closeOnEscape\n closeOnBackdrop\n className=\"!w-full !max-w-[28rem] overflow-hidden !rounded-[2rem] !bg-[var(--color-surface)] shadow-2xl\"\n overlayClassName=\"!bg-black/50 p-4 backdrop-blur-[2px]\"\n titleClassName=\"!rounded-none !bg-transparent !px-6 !pb-2 !pt-6\"\n titleTextClassName=\"!text-xl !font-semibold !text-[var(--color-text)]\"\n closeButtonClassName=\"!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]\"\n contentClassName=\"!rounded-none !bg-transparent !px-6 !pb-6 !pt-2\"\n >\n <div className=\"flex flex-col gap-4\">\n <SharePreviewCard target={target} url={absoluteUrl || url} />\n <div className=\"grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4\">\n <ShareDestinationButton\n label=\"Copy Link\"\n icon={<Link2 aria-hidden=\"true\" className=\"h-6 w-6\" />}\n onClick={() => void handleCopy()}\n ariaLabel=\"Copy link\"\n disabled={!absoluteUrl}\n />\n <ShareDestinationButton\n label=\"WhatsApp\"\n icon={<FaWhatsapp aria-hidden=\"true\" className=\"h-9 w-9 text-[#25D366]\" />}\n href={destinationUrls?.whatsapp}\n ariaLabel={`Share ${target.name} via WhatsApp`}\n disabled={!absoluteUrl}\n />\n <ShareDestinationButton\n label=\"Gmail\"\n icon={<SiGmail aria-hidden=\"true\" className=\"h-8 w-8 text-[#EA4335]\" />}\n href={destinationUrls?.gmail}\n ariaLabel={`Email ${target.name} with Gmail`}\n disabled={!absoluteUrl}\n />\n {canUseNativeShare && (\n <ShareDestinationButton\n label=\"More\"\n icon={<Share2 aria-hidden=\"true\" className=\"h-6 w-6\" />}\n onClick={() => void handleNativeShare()}\n ariaLabel=\"More sharing options\"\n disabled={!absoluteUrl}\n />\n )}\n </div>\n {copyStatus === \"failed\" && (\n <p role=\"alert\" className=\"text-center text-sm text-[var(--color-red-500)]\">\n Could not copy the link.\n </p>\n )}\n {shareError && (\n <p role=\"alert\" className=\"text-center text-sm text-[var(--color-red-500)]\">\n {shareError}\n </p>\n )}\n </div>\n </BasePopup>\n {toastId > 0 && <Toast key={toastId} message=\"Link copied\" onClose={dismissToast} />}\n </>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nexport type ShareDestinationButtonProps = {\n label: string;\n icon: ReactNode;\n href?: string;\n onClick?: () => void;\n disabled?: boolean;\n ariaLabel: string;\n};\n\nconst className =\n \"group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50\";\n\nfunction Contents({ icon, label }: Pick<ShareDestinationButtonProps, \"icon\" | \"label\">) {\n return (\n <>\n <span className=\"flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]\">\n {icon}\n </span>\n <span className=\"max-w-full truncate\">{label}</span>\n </>\n );\n}\n\nexport default function ShareDestinationButton({\n label,\n icon,\n href,\n onClick,\n disabled = false,\n ariaLabel,\n}: ShareDestinationButtonProps) {\n if (href) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" aria-label={ariaLabel} className={className}>\n <Contents icon={icon} label={label} />\n </a>\n );\n }\n\n return (\n <button type=\"button\" onClick={onClick} disabled={disabled} aria-label={ariaLabel} className={className}>\n <Contents icon={icon} label={label} />\n </button>\n );\n}\n","\"use client\";\n\nimport { Music, Music2, UserRound } from \"lucide-react\";\nimport type { GenreTreeShareTarget } from \"./share\";\n\ntype SharePreviewCardProps = {\n target: GenreTreeShareTarget;\n url: string;\n};\n\nexport default function SharePreviewCard({ target, url }: SharePreviewCardProps) {\n const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];\n const typeLabel = { genre: \"Genre\", artist: \"Artist\", song: \"Song\" }[target.kind];\n\n return (\n <div className=\"rounded-3xl bg-[var(--color-surface-muted)] p-4\">\n <div className=\"flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg\">\n <div className=\"flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]\">\n <Icon aria-hidden=\"true\" className=\"h-10 w-10\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col justify-center gap-1 p-4\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]\">{typeLabel}</span>\n <span className=\"truncate text-base font-semibold text-[var(--color-text)]\" title={target.name}>\n {target.name}\n </span>\n <span className=\"truncate text-sm text-[var(--color-primary)]\" title={url}>\n {url}\n </span>\n </div>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { z } from \"zod\";\n\nimport { CriteriaOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { useFetchGenreOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaPlaylistMinimum, type GenreUuid } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { ClampedText } from \"./ClampedText\";\nimport { GenrePlaylistTracks, type GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nimport type { RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\nimport { SiWikidata, SiWikipedia } from \"react-icons/si\";\n\nexport type GenreDetailSlots<O extends CriteriaOverview> = {\n /** Replaces the body of the default Summary section; the title and frame stay. */\n renderSummary?: (overview: O) => ReactNode;\n /** Replaces the body of the default Essential section (e.g. \"Essential songs\"); the title and frame stay. */\n renderEssentialTracks?: (overview: O) => ReactNode;\n};\n\nexport type GenreDetailExtrasProps<O extends CriteriaOverview> = GenreDetailSlots<O> & GenrePlaylistTracksSlots & {\n genreUuid: GenreUuid;\n genrePlaylist: CriteriaPlaylistMinimum;\n wikidataId?: string | null;\n scope: Scope;\n getBackendBaseUrl: () => string;\n criteriaOverviewSchema?: z.ZodType<O, unknown>;\n renderGenreDetailExtras?: (overview: O) => ReactNode;\n renderArtistLink?: RenderArtistLink;\n renderSongLink?: RenderSongLink;\n /** When set, the tracks list shows only this artist's songs in the genre. */\n artist?: { uuid: string; name: string } | null;\n /** genre-tree-view's Family section, placed right after the Summary. */\n family: ReactNode;\n};\n\n// Owns the overview fetch so its pending → success transitions re-render only the info panel,\n// not the tree that renders it.\nexport function GenreDetailExtras<O extends CriteriaOverview>({\n genreUuid,\n genrePlaylist,\n wikidataId,\n scope,\n getBackendBaseUrl,\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n renderArtistLink,\n renderSongLink,\n renderSummary,\n renderEssentialTracks,\n family,\n ...trackSlots\n}: GenreDetailExtrasProps<O>) {\n const trackNoun = useTrackNoun();\n const { data: overview, isPending } = useFetchGenreOverview<O>(\n genreUuid,\n scope,\n getBackendBaseUrl,\n criteriaOverviewSchema,\n );\n\n if (isPending) {\n return (\n <>\n <PanelSection title=\"Summary\">\n <div aria-busy=\"true\" data-testid=\"genre-detail-extras-skeleton\">\n <Skeleton className=\"h-4 w-3/4\" />\n </div>\n </PanelSection>\n {family}\n </>\n );\n }\n if (!overview) return <>{family}</>;\n\n const { summary, summarySourceUrl, essentialSongs } = overview;\n const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;\n\n return (\n <>\n {(summarySourceUrl || wikidataUrl) && (\n <PanelSection title=\"Links\">\n <div className=\"flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]\">\n {summarySourceUrl && (\n <a\n href={summarySourceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]\"\n >\n <SiWikipedia aria-hidden=\"true\" className=\"h-3.5 w-3.5\" />\n Wikipedia\n </a>\n )}\n {wikidataUrl && (\n <a\n href={wikidataUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]\"\n >\n <SiWikidata aria-hidden=\"true\" className=\"h-3.5 w-3.5\" />\n Wikidata\n </a>\n )}\n </div>\n </PanelSection>\n )}\n <PanelSection title=\"Summary\">\n {renderSummary ? (\n renderSummary(overview)\n ) : (\n <ClampedText key={genreUuid} className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">\n {summary ?? \"—\"}\n </ClampedText>\n )}\n </PanelSection>\n {family}\n {renderGenreDetailExtras?.(overview)}\n {(renderEssentialTracks || essentialSongs) && (\n <PanelSection title={`Essential ${trackNoun.plural}`}>\n {renderEssentialTracks ? (\n renderEssentialTracks(overview)\n ) : essentialSongs && essentialSongs.length > 0 ? (\n <ul className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">\n {essentialSongs.map((track) => (\n <li key={track.uuid} className=\"py-1\">\n {track.title}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">—</p>\n )}\n </PanelSection>\n )}\n <GenrePlaylistTracks\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n {...trackSlots}\n />\n </>\n );\n}\n","\"use client\";\n\nimport { useLayoutEffect, useRef, useState } from \"react\";\n\ntype ClampedTextProps = {\n children: string;\n className?: string;\n};\n\nexport function ClampedText({ children, className = \"\" }: ClampedTextProps) {\n const ref = useRef<HTMLParagraphElement>(null);\n const [expanded, setExpanded] = useState(false);\n const [overflows, setOverflows] = useState(false);\n\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element || expanded) return;\n setOverflows(element.scrollHeight > element.clientHeight);\n }, [children, expanded]);\n\n return (\n <>\n <p ref={ref} className={`${className} ${expanded ? \"\" : \"line-clamp-3\"}`}>\n {children}\n </p>\n {(overflows || expanded) && (\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded((value) => !value)}\n className=\"mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]\"\n >\n {expanded ? \"Show less\" : \"Show more\"}\n </button>\n )}\n </>\n );\n}\n","\"use client\";\n\nimport { useEffect, useState, type ReactNode } from \"react\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\nimport { FaPlay, FaPause } from \"react-icons/fa\";\nimport { PanelSection, RingLoader, Skeleton } from \"@behindthemusictree/ui\";\n\nimport { useSession } from \"@behindthemusictree/app-kit/auth\";\nimport { usePlaybackContext, useFetchWrapper } from \"@behindthemusictree/app-kit/transport\";\nimport { PaginatedResponseSchema } from \"@behindthemusictree/app-kit/transport\";\nimport { parseWithLog } from \"@behindthemusictree/app-kit/transport\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { PlayStates } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { genrePlaylistQueryKeys } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { libraryEndpoints, libraryQueryKeys } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaPlaylistMinimum, type GenreUuid } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { isSong, songUnplayableReason, SongDetailedSchema } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { GenrePlaylistTracksPage, useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { MusicBrainzRecordingLink } from \"./MusicBrainzRecordingLink\";\nimport { ArtistNames, RenderArtistLink, type RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nconst CATALOGUE_PAGE_SIZE = 100;\n// withTotal=false: the backend skips COUNT(*) and pages by keyset cursor, so huge genre subtrees\n// stay O(page) at any depth; page numbers and totals are then null.\nconst SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({\n overallTotal: z.number().nullable(),\n page: z.number().nullable(),\n totalPages: z.number().nullable(),\n});\n\n// Container query on the list (arbitrary variant: Tailwind 3 core has no `@container`): once the\n// list is wide (expanded info panel), title, artist and album become columns instead of one line.\n// Class names stay literal so Tailwind's scanner sees them.\nconst TEXT_CELL = \"min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2\";\nconst TITLE_CELL = \"[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate\";\nconst COLUMN_CELL = \"[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate\";\nconst ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;\nconst SEPARATOR = \"[@container(min-width:40rem)]:hidden\";\n\nconst nextCursor = (next: string): string => {\n const cursor = new URL(next).searchParams.get(\"cursor\");\n if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);\n return cursor;\n};\n\n// Songs are not narrowed to SongDetailed here: \"me\" scope lists uploaded tracks too.\nexport type GenrePlaylistTracksSlots = {\n /** Rendered first in each row, e.g. a selection checkbox. `index` is the row's position in the loaded list. */\n renderSongLeading?: (song: TrackBase, index: number) => ReactNode;\n /** Rendered last in each row. The song's own genre is `song.genre`. */\n renderSongActions?: (song: TrackBase) => ReactNode;\n /** Rendered between the title and the list, e.g. a \"+ Link\" button or a selection bar. */\n renderTracksToolbar?: () => ReactNode;\n};\n\nexport type GenrePlaylistTracksProps = GenrePlaylistTracksSlots & {\n genreUuid: GenreUuid;\n genrePlaylist: CriteriaPlaylistMinimum;\n scope: Scope;\n getBackendBaseUrl: () => string;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, song titles render through it (e.g. open the song panel) instead of linking to MusicBrainz. */\n renderSongLink?: RenderSongLink;\n /** When set, lists and plays only this artist's songs in the genre. */\n artist?: { uuid: string; name: string } | null;\n};\n\n/**\n * \"reference\" lists the genre's whole songs catalogue (genre subtree), playable or not, while\n * playing goes through the playable-only playlist. Other scopes have no catalogue endpoint; their\n * playlist is the catalogue (e.g. hear's uploaded tracks are always playable).\n */\nexport function GenrePlaylistTracks({\n genreUuid,\n genrePlaylist,\n scope,\n getBackendBaseUrl,\n renderArtistLink,\n renderSongLink,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n artist = null,\n}: GenrePlaylistTracksProps) {\n const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList();\n const { session, sessionRestored } = useSession();\n const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer();\n const { showPopup } = usePopup();\n const trackNoun = useTrackNoun();\n const { uuid } = genrePlaylist;\n\n const { fetch } = useFetchWrapper(getBackendBaseUrl);\n const playback = usePlaybackContext();\n\n const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({\n queryKey: [...(\n scope === \"me\"\n ? genrePlaylistQueryKeys.me.tracks(uuid)\n : artist\n ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), \"artist\", artist.uuid]\n : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid)\n ), ...(playback.key ? [playback.key] : [])],\n queryFn: async ({ pageParam }): Promise<Omit<GenrePlaylistTracksPage<TrackBase>, \"total\" | \"nextPage\"> & { total: number | null; nextPage: number | string | null }> => {\n if (scope === \"me\") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));\n const response = await fetch(\n libraryEndpoints.reference.songs.list(),\n true,\n false,\n {},\n { genre: genreUuid, ...(artist && { artist: artist.uuid }), pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...(pageParam && { cursor: pageParam }) },\n );\n const parsed = parseWithLog(SongPageSchema, response, \"GenrePlaylistTracks\");\n return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };\n },\n initialPageParam: scope === \"me\" ? 1 : \"\",\n getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,\n enabled: playback.ready && (scope !== \"me\" || (sessionRestored && !!session?.accessToken)),\n });\n\n // State, not a ref object: PanelSection unmounts its body when collapsed, so the observer must\n // re-attach when the sentinel remounts.\n const [sentinel, setSentinel] = useState<HTMLLIElement | null>(null);\n useEffect(() => {\n if (!hasNextPage || isFetchingNextPage || !sentinel) return;\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n void fetchNextPage();\n }\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasNextPage, isFetchingNextPage, fetchNextPage, sentinel]);\n\n const pages = data?.pages ?? [];\n const tracks = pages.flatMap((page) => page.tracks);\n const total = pages[0]?.total ?? undefined;\n\n const play = (startSongUuid: string) => {\n const started = artist\n ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: \"title\", genreUuid })\n : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });\n started.catch((error) => {\n console.error(\"Failed to play genre playlist:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n };\n\n return (\n <PanelSection title={`${capitalize(trackNoun.plural)}${total !== undefined ? ` (${total})` : \"\"}`}>\n <div>\n {renderTracksToolbar?.()}\n {isPending ? (\n <Skeleton className=\"h-4 w-3/4\" aria-busy=\"true\" />\n ) : isError && tracks.length === 0 ? (\n <p role=\"alert\">\n Couldn't load {trackNoun.plural}.{\" \"}\n <button type=\"button\" className=\"underline\" onClick={() => void refetch()}>\n Retry\n </button>\n </p>\n ) : tracks.length === 0 ? (\n <p>—</p>\n ) : (\n <ul className=\"mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]\" aria-label={`${genrePlaylist.name} ${trackNoun.plural}`}>\n {tracks.map((track, index) => {\n const label = track.artists?.length\n ? `${track.title} — ${track.artists.map((artist) => artist.name).join(\", \")}`\n : track.title;\n const isCurrent = playerTrackObject?.track.id === track.uuid;\n const isPlaying = isCurrent && playState === PlayStates.PLAYING;\n const text = (\n <span className={TEXT_CELL}>\n <span className={TITLE_CELL}>\n {renderSongLink ? (\n renderSongLink(track)\n ) : isSong(track) ? (\n <MusicBrainzRecordingLink song={track} className=\"hover:underline focus-visible:underline\">\n {track.title}\n </MusicBrainzRecordingLink>\n ) : (\n track.title\n )}\n </span>\n {track.artists?.length ? <span className={SEPARATOR}>{\" — \"}</span> : null}\n <span className={COLUMN_CELL}>\n {track.artists?.length ? (\n <ArtistNames artists={track.artists} renderArtistLink={renderArtistLink} />\n ) : null}\n </span>\n {track.album && <span className={ALBUM_CELL}>{track.album.name}</span>}\n </span>\n );\n const leading = renderSongLeading?.(track, index);\n const actions = renderSongActions?.(track);\n const isUnplayable = isSong(track) && songUnplayableReason(track) !== null;\n if (isUnplayable) {\n return (\n <li key={track.uuid} className=\"flex min-h-8 items-center gap-1 px-1 opacity-50\">\n {leading}\n <button\n type=\"button\"\n disabled\n aria-disabled=\"true\"\n aria-label={label}\n className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed\"\n >\n <FaPlay size={10} />\n </button>\n {text}\n {actions}\n </li>\n );\n }\n return (\n <li\n key={track.uuid}\n className={`group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? \"font-medium text-[var(--color-primary)]\" : \"\"}`}\n >\n {leading}\n <button\n type=\"button\"\n aria-label={`${isPlaying ? \"Pause\" : \"Play\"} ${label}`}\n className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== PlayStates.STOPPED ? \"opacity-100\" : \"opacity-0 [@media(pointer:coarse)]:opacity-100\"}`}\n onClick={() => (isCurrent ? handlePlayPauseAction() : play(track.uuid))}\n >\n {isCurrent && playState === PlayStates.LOADING ? (\n <RingLoader size={10} />\n ) : isPlaying ? (\n <FaPause size={10} />\n ) : (\n <FaPlay size={10} />\n )}\n </button>\n {text}\n {actions}\n </li>\n );\n })}\n {hasNextPage && <li ref={setSentinel} aria-hidden=\"true\" className=\"h-4\" />}\n </ul>\n )}\n </div>\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { SongDetailed, musicBrainzRecordingUrl } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport function MusicBrainzRecordingLink({\n song,\n children,\n className = \"\",\n}: {\n song: SongDetailed;\n children: ReactNode;\n className?: string;\n}) {\n const url = musicBrainzRecordingUrl(song);\n if (!url) return <>{children}</>;\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`View ${song.title} on MusicBrainz`}\n className={className}\n onClick={(event) => event.stopPropagation()}\n >\n {children}\n </a>\n );\n}\n","\"use client\";\n\nimport { useEffect, useMemo } from \"react\";\n\nimport {\n CriteriaPlaylistSimple,\n ReparentCandidate,\n type GenreUuid,\n getReparentCandidates,\n useUpdateGenre,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport BasePopup from \"../popup/BasePopup\";\nimport GenreSearch from \"./GenreSearch\";\n\nexport type ParentChange = { genre: GenreUuid; from: GenreUuid | null; to: GenreUuid };\n\ntype ChangeParentPopupProps = {\n genrePlaylist: ReparentCandidate;\n genrePlaylists: CriteriaPlaylistSimple[];\n scope: Scope;\n getBackendBaseUrl: () => string;\n onClose: () => void;\n onParentChanged?: (change: ParentChange) => void;\n};\n\nexport default function ChangeParentPopup({\n genrePlaylist,\n genrePlaylists,\n scope,\n getBackendBaseUrl,\n onClose,\n onParentChanged,\n}: ChangeParentPopupProps) {\n const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);\n const candidates = useMemo(\n () => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),\n [genrePlaylists, genrePlaylist.uuid],\n );\n\n const handleSelect = (newParent: ReparentCandidate) => {\n const genre = genrePlaylist.criteria.uuid;\n const parentPlaylistUuid = genrePlaylist.parent?.uuid;\n const from = parentPlaylistUuid\n ? (genrePlaylists.find(({ uuid }) => uuid === parentPlaylistUuid)?.criteria?.uuid ?? null)\n : null;\n mutate(\n { uuid: genre, data: { parent: newParent.criteria.uuid } },\n {\n onSuccess: () => {\n onClose();\n onParentChanged?.({ genre, from, to: newParent.criteria.uuid });\n },\n },\n );\n };\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onClose]);\n\n return (\n <BasePopup title={`Change parent of ${genrePlaylist.name}`} onClose={onClose} showCancelButton fitContent>\n <p className=\"mb-3 text-sm text-[var(--color-text-muted)]\">\n Current parent: {genrePlaylist.parent?.name ?? \"none\"}\n </p>\n <div aria-busy={isPending} className={isPending ? \"pointer-events-none opacity-60\" : undefined}>\n <GenreSearch\n genrePlaylists={candidates}\n onSelect={handleSelect}\n placeholder=\"Search a new parent…\"\n />\n </div>\n {formErrors.length > 0 && (\n <p role=\"alert\" className=\"mt-3 text-sm text-[var(--color-red-500)]\">\n {formErrors.map(({ message }) => message).join(\" \")}\n </p>\n )}\n </BasePopup>\n );\n}\n","\"use client\";\n\nimport { Skeleton } from \"@behindthemusictree/ui\";\nimport { useArtist, type RenderArtistLink, type RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport ArtistHeader from \"./ArtistHeader\";\nimport ArtistPhoto from \"./ArtistPhoto\";\nimport ArtistMembers from \"./ArtistMembers\";\nimport ArtistDiscography from \"./ArtistDiscography\";\nimport ArtistSongs from \"./ArtistSongs\";\nimport ShareActionButton from \"../ShareActionButton\";\nimport type { GetGenreTreeShareUrl } from \"../share\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { OverlayPanel } from \"../overlayPanel\";\n\nexport interface ArtistPanelOptions {\n uuid: string | null;\n scope?: Scope;\n getBackendBaseUrl: () => string;\n getShareUrl?: GetGenreTreeShareUrl;\n /** Renders member and song artist names. */\n renderArtistLink: RenderArtistLink;\n renderSongLink?: RenderSongLink;\n expanded: boolean;\n onExpandedChange: (expanded: boolean) => void;\n onClose: () => void;\n}\n\n/** The artist detail overlay panel for `uuid`, or undefined when no artist is set. */\nexport function useArtistPanel({\n uuid,\n scope,\n getBackendBaseUrl,\n getShareUrl,\n renderArtistLink,\n renderSongLink,\n expanded,\n onExpandedChange,\n onClose,\n}: ArtistPanelOptions): OverlayPanel | undefined {\n const result = useArtist(uuid, getBackendBaseUrl);\n\n const panel = { expanded, onExpandedChange, onClose };\n switch (result.status) {\n case \"idle\":\n return undefined;\n case \"loading\":\n return {\n ...panel,\n title: \"Artist\",\n content: (\n <div aria-busy=\"true\" className=\"flex flex-col gap-2.5 px-6 py-2.5\">\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-16 mt-2.5\" />\n <Skeleton className=\"h-16\" />\n </div>\n ),\n };\n case \"not-found\":\n return {\n ...panel,\n title: \"Artist not found\",\n content: <p className=\"px-6 py-2.5 text-[var(--color-text-muted)]\">This artist does not exist.</p>,\n };\n case \"error\":\n return {\n ...panel,\n title: \"Artist\",\n content: <p className=\"px-6 py-2.5 text-red-600\">Could not load this artist.</p>,\n };\n case \"success\": {\n const { artist } = result;\n const shareTarget = scope\n ? { kind: \"artist\" as const, uuid: artist.uuid, name: artist.name, scope }\n : undefined;\n const shareUrl = shareTarget ? getShareUrl?.(shareTarget) : undefined;\n return {\n ...panel,\n title: artist.name,\n content: (\n <>\n {shareUrl && (\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">\n {shareTarget && <ShareActionButton target={shareTarget} url={shareUrl} />}\n </div>\n </div>\n )}\n {artist.image && <ArtistPhoto image={artist.image} name={artist.name} />}\n <ArtistHeader artist={artist} />\n <ArtistMembers title=\"Members\" memberships={artist.members} renderArtistLink={renderArtistLink} />\n <ArtistMembers title=\"Member of\" memberships={artist.memberOf} renderArtistLink={renderArtistLink} />\n {artist.tracksCount > 0 && (\n <ArtistSongs\n artist={{ uuid: artist.uuid, name: artist.name }}\n getBackendBaseUrl={getBackendBaseUrl}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n )}\n </>\n ),\n extraContent: artist.albums.length > 0 ? <ArtistDiscography albums={artist.albums} /> : undefined,\n };\n }\n }\n}\n","import type { ReactNode } from \"react\";\nimport { FieldList } from \"@behindthemusictree/ui\";\nimport type { ArtistDetailed } from \"@behindthemusictree/app-kit/genre-tree\";\nimport ArtistLinks, { artistLinks } from \"./ArtistLinks\";\nimport { formatYears } from \"./formatYears\";\n\nconst countryNames = new Intl.DisplayNames([\"en\"], { type: \"region\" });\n\nexport default function ArtistHeader({ artist }: { artist: ArtistDetailed }) {\n const fields: { label: string; value: ReactNode }[] = [];\n if (artist.type) fields.push({ label: \"Type\", value: artist.type.charAt(0).toUpperCase() + artist.type.slice(1) });\n const country = artist.country && countryNames.of(artist.country);\n if (country) fields.push({ label: \"Country\", value: country });\n const years = formatYears(artist.beginYear, artist.endYear, artist.ended);\n if (years) fields.push({ label: \"Active\", value: years });\n const links = artistLinks(artist);\n if (links.length > 0) fields.push({ label: \"Links\", value: <ArtistLinks links={links} /> });\n\n if (fields.length === 0) return null;\n return <FieldList items={fields} />;\n}\n","import type { ComponentType } from \"react\";\nimport { Globe, Share2 } from \"lucide-react\";\nimport {\n SiBandcamp,\n SiDiscogs,\n SiMusicbrainz,\n SiSoundcloud,\n SiSpotify,\n SiWikidata,\n SiWikipedia,\n SiYoutube,\n} from \"react-icons/si\";\nimport { Chip } from \"@behindthemusictree/ui\";\nimport type { ArtistDetailed, ArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nconst LINK_KINDS: Record<ArtistLink[\"kind\"], { label: string; Icon: ComponentType<{ className?: string }> }> = {\n official: { label: \"Official website\", Icon: Globe },\n wikipedia: { label: \"Wikipedia\", Icon: SiWikipedia },\n wikidata: { label: \"Wikidata\", Icon: SiWikidata },\n spotify: { label: \"Spotify\", Icon: SiSpotify },\n youtube: { label: \"YouTube\", Icon: SiYoutube },\n bandcamp: { label: \"Bandcamp\", Icon: SiBandcamp },\n soundcloud: { label: \"SoundCloud\", Icon: SiSoundcloud },\n discogs: { label: \"Discogs\", Icon: SiDiscogs },\n social: { label: \"Social\", Icon: Share2 },\n};\n\nfunction socialLabel(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, \"\");\n } catch {\n return LINK_KINDS.social.label;\n }\n}\n\ninterface ArtistLinkItem {\n key: string;\n url: string;\n Icon: ComponentType<{ className?: string }>;\n label: string;\n}\n\nexport function artistLinks(artist: ArtistDetailed): ArtistLinkItem[] {\n const links = artist.links.map(({ kind, url }) => ({\n key: `${kind}-${url}`,\n url,\n Icon: LINK_KINDS[kind].Icon,\n label: kind === \"social\" ? socialLabel(url) : LINK_KINDS[kind].label,\n }));\n if (artist.musicbrainzArtistId) {\n links.push({\n key: \"musicbrainz\",\n url: `https://musicbrainz.org/artist/${artist.musicbrainzArtistId}`,\n Icon: SiMusicbrainz,\n label: \"MusicBrainz\",\n });\n }\n return links;\n}\n\nexport default function ArtistLinks({ links }: { links: ArtistLinkItem[] }) {\n return (\n <ul className=\"flex flex-wrap gap-1.5\">\n {links.map(({ key, url, Icon, label }) => (\n <li key={key}>\n <Chip href={url} icon={<Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />}>\n {label}\n </Chip>\n </li>\n ))}\n </ul>\n );\n}\n","/** \"1994–present\", \"1994–2008\", \"?–2008\", or null when nothing is known. */\nexport function formatYears(beginYear: number | null, endYear: number | null, ended: boolean): string | null {\n if (beginYear === null && endYear === null) return ended ? \"ended\" : null;\n const end = endYear ?? (ended ? \"?\" : \"present\");\n return `${beginYear ?? \"?\"}–${end}`;\n}\n","import type { ReactNode } from \"react\";\nimport type { ArtistImage } from \"@behindthemusictree/app-kit/genre-tree\";\n\nfunction ExternalLink({ href, children }: { href: string; children: ReactNode }) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" className=\"underline-offset-2 hover:underline\">\n {children}\n </a>\n );\n}\n\nexport default function ArtistPhoto({ image, name }: { image: ArtistImage; name: string }) {\n return (\n <figure className=\"flex flex-col gap-1.5 px-6 py-2.5\">\n <img src={image.url} alt={name} className=\"h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded\" />\n <figcaption className=\"text-xs text-[var(--color-text-muted)]\">\n Photo: <ExternalLink href={image.pageUrl}>{image.author ?? \"Wikimedia Commons\"}</ExternalLink>,{\" \"}\n {image.licenseUrl ? <ExternalLink href={image.licenseUrl}>{image.license}</ExternalLink> : image.license}\n </figcaption>\n </figure>\n );\n}\n","import type { ArtistMembership, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelGroup, PanelSection } from \"@behindthemusictree/ui\";\nimport { formatYears } from \"./formatYears\";\n\ninterface MembershipListProps {\n title: string;\n memberships: ArtistMembership[];\n renderArtistLink: RenderArtistLink;\n}\n\nfunction MembershipList({ title, memberships, renderArtistLink }: MembershipListProps) {\n if (memberships.length === 0) return null;\n return (\n <PanelGroup title={title}>\n <ul>\n {memberships.map((membership, index) => {\n const details = [\n formatYears(membership.beginYear, membership.endYear, membership.ended),\n ...membership.roles,\n ];\n const detailsText = details.filter(Boolean).join(\" · \");\n return (\n // A member can have several periods in the same group.\n <li key={`${membership.artist.uuid}-${index}`} className=\"truncate py-0.5\" title={detailsText || undefined}>\n <span className=\"font-medium text-[var(--color-text)]\">{renderArtistLink(membership.artist)}</span>\n {detailsText && <span className=\"text-[var(--color-text-muted)]\"> — {detailsText}</span>}\n </li>\n );\n })}\n </ul>\n </PanelGroup>\n );\n}\n\nexport default function ArtistMembers({ title, memberships, renderArtistLink }: MembershipListProps) {\n if (memberships.length === 0) return null;\n const isCurrent = (membership: ArtistMembership) => !membership.ended && membership.endYear === null;\n\n return (\n <PanelSection title={title}>\n <MembershipList title=\"Current\" memberships={memberships.filter(isCurrent)} renderArtistLink={renderArtistLink} />\n <MembershipList\n title=\"Past\"\n memberships={memberships.filter((membership) => !isCurrent(membership))}\n renderArtistLink={renderArtistLink}\n />\n </PanelSection>\n );\n}\n","import type { ArtistAlbum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelGroup, PanelSection } from \"@behindthemusictree/ui\";\n\nconst GROUPS = [\"Albums\", \"EPs\", \"Singles\", \"Live\", \"Compilations\", \"Other\"] as const;\ntype Group = (typeof GROUPS)[number];\n\nconst PRIMARY_GROUPS: Record<string, Group> = { album: \"Albums\", ep: \"EPs\", single: \"Singles\" };\n\n// Secondary types win over the primary one: a live album belongs with live releases, not studio albums.\nexport function discographyGroup({ primaryType, secondaryTypes }: ArtistAlbum): Group {\n const secondary = secondaryTypes.map((type) => type.toLowerCase());\n if (secondary.includes(\"live\")) return \"Live\";\n if (secondary.includes(\"compilation\")) return \"Compilations\";\n return PRIMARY_GROUPS[primaryType?.toLowerCase() ?? \"\"] ?? \"Other\";\n}\n\nfunction byYear(a: ArtistAlbum, b: ArtistAlbum): number {\n if (a.year !== b.year) {\n if (a.year === null) return 1;\n if (b.year === null) return -1;\n return a.year.localeCompare(b.year);\n }\n return a.name.localeCompare(b.name);\n}\n\nexport default function ArtistDiscography({ albums }: { albums: ArtistAlbum[] }) {\n if (albums.length === 0) return null;\n const sorted = [...albums].sort(byYear);\n\n return (\n <PanelSection title=\"Discography\">\n {GROUPS.map((group) => {\n const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);\n if (groupAlbums.length === 0) return null;\n return (\n <PanelGroup key={group} title={group}>\n <ul>\n {groupAlbums.map((album) => (\n <li key={album.uuid} className=\"grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5\">\n <span className=\"tabular-nums text-[var(--color-text-muted)]\">{album.year ?? \"—\"}</span>\n <span className=\"truncate text-[var(--color-text)]\" title={album.name}>\n {album.name}\n </span>\n </li>\n ))}\n </ul>\n </PanelGroup>\n );\n })}\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport { useState, type MouseEvent } from \"react\";\nimport { keepPreviousData } from \"@tanstack/react-query\";\nimport { Play } from \"lucide-react\";\nimport { Pagination, PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport {\n SongDetailedSchema,\n libraryEndpoints,\n songUnplayableReason,\n useTrackList,\n type RenderArtistLink,\n type RenderSongLink,\n type SongDetailed,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PaginatedResponseSchema, useFetchWrapper, useQueryWithParse } from \"@behindthemusictree/app-kit/transport\";\nimport TrackItem from \"../track-list-sidebar/TrackItem\";\n\nconst SongPageSchema = PaginatedResponseSchema(SongDetailedSchema);\n\nconst SORTS = [\n { ordering: \"title\", label: \"Title\" },\n { ordering: \"-albumYear\", label: \"Year\" },\n] as const;\ntype Ordering = (typeof SORTS)[number][\"ordering\"];\n\ninterface ArtistSongsProps {\n artist: { uuid: string; name: string };\n getBackendBaseUrl: () => string;\n renderArtistLink: RenderArtistLink;\n renderSongLink?: RenderSongLink;\n}\n\nexport default function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }: ArtistSongsProps) {\n const [page, setPage] = useState(1);\n const [ordering, setOrdering] = useState<Ordering>(\"title\");\n const { fetch } = useFetchWrapper(getBackendBaseUrl);\n const { playNewTrackListFromArtist } = useTrackList<SongDetailed>();\n const { playerTrackObject } = usePlayer();\n const { data, isPending, isError } = useQueryWithParse({\n queryKey: [\"referenceSongs\", \"artist\", artist.uuid, ordering, page],\n queryFn: () =>\n fetch(libraryEndpoints.reference.songs.list(), true, false, undefined, {\n artist: artist.uuid,\n ordering,\n page,\n }),\n schema: SongPageSchema,\n placeholderData: keepPreviousData,\n context: \"ArtistSongs\",\n });\n\n const play = (startSongUuid?: string) =>\n playNewTrackListFromArtist({ uuid: artist.uuid, name: artist.name }, \"reference\", {\n ordering,\n startSongUuid,\n });\n\n // TrackItem's own play control (`.track-position-play-pause`, a div) plays from whatever list is active; a click on a track that isn't\n // loaded yet must queue this artist's list instead.\n const handleRowClickCapture = (event: MouseEvent, song: SongDetailed) => {\n if (!(event.target instanceof Element) || !event.target.closest(\".track-position-play-pause\")) return;\n if (playerTrackObject?.track.id === song.uuid || songUnplayableReason(song) !== null) return;\n event.stopPropagation();\n void play(song.uuid);\n };\n\n return (\n <PanelSection title=\"Songs\">\n {isPending ? (\n <Skeleton className=\"h-14\" aria-busy=\"true\" />\n ) : isError ? (\n <p className=\"text-red-600\">Could not load songs.</p>\n ) : data.results.length === 0 ? (\n <p className=\"text-[var(--color-text-muted)]\">No songs yet.</p>\n ) : (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2 mb-2\">\n <button\n type=\"button\"\n onClick={() => void play()}\n className=\"flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700\"\n >\n <Play className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Play all\n </button>\n <div role=\"group\" aria-label=\"Sort songs by\" className=\"flex gap-1 text-xs\">\n {SORTS.map((sort) => (\n <button\n key={sort.ordering}\n type=\"button\"\n aria-pressed={ordering === sort.ordering}\n onClick={() => {\n setOrdering(sort.ordering);\n setPage(1);\n }}\n className=\"px-2.5 py-0.5 text-[var(--color-text)] rounded-full hover:bg-[var(--color-surface-muted)] aria-pressed:bg-gray-900 aria-pressed:text-white\"\n >\n {sort.label}\n </button>\n ))}\n </div>\n </div>\n <ul className=\"overflow-hidden mb-2 bg-zinc-950 rounded-lg\">\n {data.results.map((song, index) => (\n <li key={song.uuid} onClickCapture={(event) => handleRowClickCapture(event, song)}>\n <TrackItem\n track={song}\n position={(page - 1) * data.pageSize + index + 1}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n </li>\n ))}\n </ul>\n <Pagination currentPage={page} totalPages={data.totalPages} onPageChange={setPage} />\n </>\n )}\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport TrackPositionPlayPause from \"../TrackPositionPlayPause\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { isSong, songUnplayableReason } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { MusicBrainzRecordingLink } from \"../MusicBrainzRecordingLink\";\nimport type { RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { ArtistNames, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport interface TrackItemProps<T extends TrackBase> {\n track: T;\n position: number;\n renderDuration?: (track: T) => ReactNode;\n renderActions?: (track: T) => ReactNode;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, a song title renders through it (e.g. opens the song panel) instead of linking to MusicBrainz. */\n renderSongLink?: RenderSongLink;\n}\n\nexport default function TrackItem<T extends TrackBase>({\n track,\n position,\n renderDuration,\n renderActions,\n renderArtistLink,\n renderSongLink,\n}: TrackItemProps<T>) {\n const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer();\n const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList<T>();\n const scope = trackList?.origin?.scope ?? null;\n const isCurrentTrack = playerTrackObject?.track.id === track.uuid;\n const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;\n const isUnplayable =\n hasPlaybackError ||\n runtimeUnplayableTrackIds?.has(track.uuid) === true ||\n (isSong(track) && songUnplayableReason(track) !== null);\n\n const handlePlayPauseClick = (event: React.MouseEvent) => {\n event.stopPropagation();\n if (isCurrentTrack) {\n handlePlayPauseAction();\n } else if (scope != null) {\n toTrackAtPosition(position);\n loadTrackForPlayer(track.uuid);\n }\n };\n\n return (\n <div\n className={`track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? \"opacity-50\" : \"hover:bg-[var(--color-surface-muted)] group\"}`}\n >\n {isUnplayable ? (\n <div\n className=\"flex items-center justify-center text-lg w-16\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n aria-disabled=\"true\"\n >\n {position}\n </div>\n ) : (\n <TrackPositionPlayPause\n position={position}\n uuid={track.uuid}\n handlePlayPauseClick={handlePlayPauseClick}\n isSelected={selectedTrack?.uuid === track.uuid}\n />\n )}\n <div className=\"title-artist-container flex flex-col items-start justify-center min-w-0 flex-1\">\n <div className=\"flex max-w-full items-center gap-2\">\n <div className=\"title text-lg font-bold text-[var(--color-text)] max-w-full truncate\">\n {renderSongLink ? (\n renderSongLink(track)\n ) : isSong(track) ? (\n <MusicBrainzRecordingLink song={track} className=\"hover:underline focus-visible:underline\">\n {track.title}\n </MusicBrainzRecordingLink>\n ) : (\n track.title\n )}\n </div>\n </div>\n {track.artists && track.artists.length > 0 ? (\n <div className=\"artist text-base max-w-full truncate text-[var(--color-text-muted)]\">\n <ArtistNames artists={track.artists} renderArtistLink={renderArtistLink} />\n </div>\n ) : (\n \"\"\n )}\n </div>\n <div className=\"genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0\">\n {track.genre ? (\n <div className=\"genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold\">{track.genre.name}</div>\n ) : (\n \"\"\n )}\n </div>\n {renderDuration && (\n <div\n className=\"duration flex text-base w-16 items-center justify-center\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n >\n {renderDuration(track)}\n </div>\n )}\n {renderActions && (\n <div className=\"edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer\">\n {renderActions(track)}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { FaPlay, FaPause } from \"react-icons/fa\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { PlayStates } from \"@behindthemusictree/app-kit/player\";\n\nexport interface TrackPositionPlayPauseProps {\n position: number;\n uuid: string;\n handlePlayPauseClick: (event: React.MouseEvent) => void;\n isSelected?: boolean;\n}\n\nexport default function TrackPositionPlayPause({\n position,\n uuid,\n handlePlayPauseClick,\n isSelected = false,\n}: TrackPositionPlayPauseProps) {\n const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = usePlayer();\n const isThisTrack = playerTrackObject?.track.id === uuid;\n const isConfirmedPlayable = playbackConfirmedTrackId === uuid;\n const isAwaitingPlayback =\n (isSelected || isThisTrack) &&\n !isConfirmedPlayable &&\n !(isThisTrack && playerTrackObject?.loadError) &&\n (!isThisTrack || isPlayerLoading || playState === PlayStates.LOADING || playState === PlayStates.PLAYING);\n const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates.PLAYING;\n const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates.PAUSED;\n const isCurrentTrackActive = isPlaying || isPaused;\n\n return (\n <div\n className=\"track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n onClick={handlePlayPauseClick}\n >\n <div className=\"track-position-play-pause__status group-hover:hidden flex items-center justify-center\">\n {isAwaitingPlayback ? (\n <span className=\"track-position-play-pause__spinner\" role=\"status\" aria-label=\"Loading track\" />\n ) : isCurrentTrackActive ? (\n <span\n className=\"track-position-play-pause__bars\"\n data-playing={isPlaying}\n role=\"img\"\n aria-label={isPlaying ? \"Playing\" : \"Paused\"}\n >\n <span className=\"track-position-play-pause__bar\" />\n <span className=\"track-position-play-pause__bar\" />\n <span className=\"track-position-play-pause__bar\" />\n </span>\n ) : (\n position\n )}\n </div>\n <div\n className={`hidden group-hover:flex items-center justify-center ${\n isCurrentTrackActive ? \"\" : \"absolute inset-0\"\n }`}\n aria-hidden=\"true\"\n >\n {isAwaitingPlayback ? (\n <span className=\"track-position-play-pause__spinner\" />\n ) : isPlaying ? (\n <FaPause />\n ) : (\n <FaPlay />\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { Skeleton } from \"@behindthemusictree/ui\";\nimport {\n useSong,\n type CriteriaMinimum,\n type CriteriaPlaylistSimple,\n type RenderArtistLink,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { GetGenreTreeShareUrl } from \"../share\";\nimport SongPanelContent from \"./SongPanelContent\";\nimport type { OverlayPanel } from \"../overlayPanel\";\n\nexport interface SongPanelOptions {\n uuid: string | null;\n scope: Scope;\n getBackendBaseUrl: () => string;\n getShareUrl?: GetGenreTreeShareUrl;\n renderArtistLink: RenderArtistLink;\n /** Root-to-leaf playlists of a genre in the loaded tree; empty when the tree doesn't hold it. */\n getGenrePath: (genre: CriteriaMinimum) => CriteriaPlaylistSimple[];\n onSelectGenre: (genrePlaylist: CriteriaPlaylistSimple) => void;\n expanded: boolean;\n onExpandedChange: (expanded: boolean) => void;\n onClose: () => void;\n}\n\n/** The song detail overlay panel for `uuid`, or undefined when no song is set. */\nexport function useSongPanel({\n uuid,\n scope,\n getBackendBaseUrl,\n getShareUrl,\n renderArtistLink,\n getGenrePath,\n onSelectGenre,\n expanded,\n onExpandedChange,\n onClose,\n}: SongPanelOptions): OverlayPanel | undefined {\n const result = useSong(uuid, getBackendBaseUrl);\n\n const panel = { expanded, onExpandedChange, onClose };\n switch (result.status) {\n case \"idle\":\n return undefined;\n case \"loading\":\n return {\n ...panel,\n title: \"Song\",\n content: (\n <div aria-busy=\"true\" className=\"flex flex-col gap-2.5 px-6 py-2.5\">\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-24\" />\n </div>\n ),\n };\n case \"not-found\":\n return {\n ...panel,\n title: \"Song not found\",\n content: <p className=\"px-6 py-2.5 text-[var(--color-text-muted)]\">This song does not exist.</p>,\n };\n case \"error\":\n return {\n ...panel,\n title: \"Song\",\n content: <p className=\"px-6 py-2.5 text-red-600\">Could not load this song.</p>,\n };\n case \"success\": {\n const { song } = result;\n const shareUrl = getShareUrl?.({ kind: \"song\", uuid: song.uuid, name: song.title, scope });\n return {\n ...panel,\n title: song.title,\n content: (\n <SongPanelContent\n song={song}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n shareUrl={shareUrl ?? undefined}\n renderArtistLink={renderArtistLink}\n genrePath={song.genre ? getGenrePath(song.genre) : []}\n onSelectGenre={onSelectGenre}\n />\n ),\n };\n }\n }\n}\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport { Pause, Play } from \"lucide-react\";\nimport { FieldList, PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { PlayStates, usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport {\n ArtistNames,\n musicBrainzRecordingUrl,\n songUnplayableReason,\n useSongHistory,\n useTrackList,\n type CriteriaPlaylistSimple,\n type RenderArtistLink,\n type SongDetailed,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport ShareActionButton from \"../ShareActionButton\";\nimport type { GenreTreeShareTarget } from \"../share\";\n\nexport interface SongPanelContentProps {\n song: SongDetailed;\n scope: Scope;\n getBackendBaseUrl: () => string;\n shareUrl?: string;\n renderArtistLink: RenderArtistLink;\n /** Root-to-leaf playlists of the song's genre in the loaded tree; empty when the tree doesn't hold it. */\n genrePath: CriteriaPlaylistSimple[];\n onSelectGenre: (genrePlaylist: CriteriaPlaylistSimple) => void;\n}\n\nconst numberFormat = new Intl.NumberFormat(\"en\");\nconst dateFormat = new Intl.DateTimeFormat(\"en\", { dateStyle: \"medium\" });\n\nconst externalLink = \"text-[var(--color-primary)] hover:underline focus-visible:underline\";\n\nfunction SongHistory({ uuid, getBackendBaseUrl }: { uuid: string; getBackendBaseUrl: () => string }) {\n const [open, setOpen] = useState(false);\n const history = useSongHistory(uuid, getBackendBaseUrl, open);\n let body: ReactNode = null;\n if (history.status === \"loading\") body = <Skeleton className=\"h-10\" aria-busy=\"true\" />;\n else if (history.status === \"error\") body = <p className=\"text-red-600\">Could not load the history.</p>;\n else if (history.status === \"success\")\n body =\n history.entries.length === 0 ? (\n <p className=\"text-[var(--color-text-muted)]\">No changes yet.</p>\n ) : (\n <ul className=\"divide-y divide-[var(--color-border)]\">\n {history.entries.map((entry) => (\n <li key={entry.uuid} className=\"py-1.5 text-sm\">\n <span className=\"font-medium\">{entry.action}</span>\n {entry.oldValue !== null || entry.newValue !== null ? (\n <span className=\"text-[var(--color-text-muted)]\">\n {\" \"}\n — {entry.oldValue ?? \"none\"} → {entry.newValue ?? \"none\"}\n </span>\n ) : null}\n <span className=\"block text-xs text-[var(--color-text-muted)]\">\n {entry.actorPseudo ?? \"Anonymous\"} · {dateFormat.format(new Date(entry.createdOn))}\n </span>\n </li>\n ))}\n </ul>\n );\n\n return (\n <details\n onToggle={(event) => setOpen(event.currentTarget.open)}\n className=\"px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]\"\n >\n <summary className=\"mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]\">\n History\n </summary>\n <div className=\"mt-1.5\">{body}</div>\n </details>\n );\n}\n\nexport default function SongPanelContent({\n song,\n scope,\n getBackendBaseUrl,\n shareUrl,\n renderArtistLink,\n genrePath,\n onSelectGenre,\n}: SongPanelContentProps) {\n const { playNewTrackListFromTrackUuid } = useTrackList<SongDetailed>();\n const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer();\n const unplayableReason = songUnplayableReason(song);\n const isCurrent = playerTrackObject?.track.id === song.uuid;\n const isPlaying = isCurrent && playState === PlayStates.PLAYING;\n const shareTarget: GenreTreeShareTarget = { kind: \"song\", uuid: song.uuid, name: song.title, scope };\n const musicBrainzUrl = musicBrainzRecordingUrl(song);\n\n const fields: { label: string; value: ReactNode }[] = [];\n if (song.genre) {\n const path = genrePath.length > 0 ? genrePath : null;\n fields.push({\n label: \"Genre\",\n value: path ? (\n <span className=\"flex flex-wrap items-center gap-x-1\">\n {path.map((genrePlaylist, index) => (\n <span key={genrePlaylist.uuid}>\n {index > 0 && <span aria-hidden=\"true\">› </span>}\n <button type=\"button\" onClick={() => onSelectGenre(genrePlaylist)} className=\"hover:underline\">\n {genrePlaylist.name}\n </button>\n </span>\n ))}\n </span>\n ) : (\n song.genre.name\n ),\n });\n } else {\n fields.push({ label: \"Genre\", value: <span className=\"text-[var(--color-text-muted)]\">No genre</span> });\n }\n if (song.firstReleaseYear) fields.push({ label: \"First released\", value: song.firstReleaseYear });\n const viewCount = song.youtubeVideo?.viewCount;\n if (viewCount !== null && viewCount !== undefined)\n fields.push({ label: \"YouTube views\", value: numberFormat.format(viewCount) });\n fields.push({ label: \"Plays\", value: numberFormat.format(song.playCount) });\n const links: ReactNode[] = [];\n if (musicBrainzUrl)\n links.push(\n <a key=\"mb\" href={musicBrainzUrl} target=\"_blank\" rel=\"noopener noreferrer\" className={externalLink}>\n MusicBrainz\n </a>,\n );\n if (song.youtubeVideo)\n links.push(\n <a\n key=\"yt\"\n href={`https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={externalLink}\n >\n YouTube\n </a>,\n );\n if (links.length > 0)\n fields.push({\n label: \"Links\",\n value: <span className=\"flex gap-3\">{links}</span>,\n });\n\n return (\n <>\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">\n {unplayableReason ? (\n <p className=\"flex items-center px-2.5 text-sm text-[var(--color-text-muted)]\">{unplayableReason}</p>\n ) : (\n <button\n type=\"button\"\n onClick={() => (isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope))}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none\"\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]\">\n {isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"w-5 h-5\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"w-5 h-5\" />\n )}\n </span>\n {isPlaying ? \"Pause\" : \"Play\"}\n </button>\n )}\n {shareUrl && <ShareActionButton target={shareTarget} url={shareUrl} />}\n </div>\n </div>\n {song.artists && song.artists.length > 0 && (\n <p className=\"px-6 pt-2.5 text-[var(--color-text-muted)]\">\n <ArtistNames artists={song.artists} renderArtistLink={renderArtistLink} />\n </p>\n )}\n <FieldList items={fields} />\n {song.essentialGenres && song.essentialGenres.length > 0 && (\n <PanelSection title=\"Essential in\">\n <ul>\n {song.essentialGenres.map((genre) => (\n <li key={genre.uuid} className=\"py-0.5\">\n {genre.name}\n </li>\n ))}\n </ul>\n </PanelSection>\n )}\n <SongHistory uuid={song.uuid} getBackendBaseUrl={getBackendBaseUrl} />\n </>\n );\n}\n","\"use client\";\n\nimport { MdMusicNote, MdVisibility } from \"react-icons/md\";\nimport { SegmentedControl } from \"@behindthemusictree/ui\";\n\nimport type { GenreTreeSizeMetric } from \"./playlist-tree/size-metric\";\n\nexport function SizeMetricToggle({\n value,\n onChange,\n}: {\n value: GenreTreeSizeMetric;\n onChange: (value: GenreTreeSizeMetric) => void;\n}) {\n return (\n <SegmentedControl\n variant=\"floating\"\n aria-label=\"Size nodes by\"\n value={value}\n onChange={onChange}\n options={[\n { value: \"songs\", label: <MdMusicNote size={18} aria-hidden />, \"aria-label\": \"Size by songs\", title: \"Node size by song count\" },\n { value: \"views\", label: <MdVisibility size={18} aria-hidden />, \"aria-label\": \"Size by views\", title: \"Node size by song views\" },\n ]}\n />\n );\n}\n","\"use client\";\n\nimport { useMemo, type ComponentProps } from \"react\";\nimport {\n ACCENT_TEXT_COLOR,\n GenreNodeAncestors,\n GenreNodeFamily,\n getGenreTreeColor,\n TEXT_COLOR,\n tintSurface,\n type GenreTreeNode,\n type InfoPanelChild,\n} from \"@behindthemusictree/genre-tree-view\";\nimport { DetailPanel, IconButton, Skeleton } from \"@behindthemusictree/ui\";\nimport { Maximize2, Minimize2 } from \"lucide-react\";\n\nimport {\n useFetchGenreDetail,\n type CriteriaMinimum,\n type CriteriaOverview,\n type GenreUuid,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\nimport { GenreInfoPanelBody, type GenreInfoPanelBodyProps } from \"./GenreInfoPanelBody\";\nimport type { GenreTreeShareTarget } from \"./share\";\nimport { useGenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\n\nexport type GenreDetailPanelProps<O extends CriteriaOverview> = {\n genreUuid: GenreUuid;\n scope: Scope;\n getBackendBaseUrl: () => string;\n onClose: () => void;\n /** Fired with the uuid of a family or ancestor chip the user picked. */\n onSelectGenre: (genreUuid: GenreUuid) => void;\n /** When set, the panel offers \"Focus\". */\n onZoomIn?: () => void;\n /** Absolute or relative URL that shares this genre; the share action shows only when set. */\n shareUrl?: string;\n shareTarget?: GenreTreeShareTarget;\n collapsed?: boolean;\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Tab on the panel's inner edge, for hosts that hide and show the panel. */\n edgeTab?: ComponentProps<typeof DetailPanel>[\"edgeTab\"];\n extras?: GenreInfoPanelBodyProps<O>[\"extras\"];\n};\n\n// Relatives from the detail endpoint carry no song count, so the family dots share one size.\nconst RELATIVE_ITEM_COUNT = 1;\n\nfunction toNode(genre: CriteriaMinimum, parentId: string | null): GenreTreeNode {\n return {\n id: genre.uuid,\n parentId,\n name: genre.name,\n itemCount: RELATIVE_ITEM_COUNT,\n side: genre.side ?? undefined,\n wikidataId: genre.wikidataId,\n };\n}\n\n/** A genre's detail panel driven by one uuid, with no tree behind it. The tree's own panel shares its body. */\nexport function GenreDetailPanel<O extends CriteriaOverview>({\n genreUuid,\n scope,\n getBackendBaseUrl,\n onClose,\n onSelectGenre,\n onZoomIn,\n shareUrl,\n shareTarget,\n collapsed,\n expanded,\n onExpandedChange,\n edgeTab,\n extras,\n}: GenreDetailPanelProps<O>) {\n const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);\n const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });\n\n const view = useMemo(() => {\n if (!detail) return null;\n const rootColor = getGenreTreeColor(detail.root.wikidataId);\n const relative = (genre: CriteriaMinimum, parentId: string | null): InfoPanelChild => ({\n node: toNode(genre, parentId),\n fill: tintSurface(rootColor),\n textColor: TEXT_COLOR,\n });\n const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);\n return {\n node: {\n id: detail.uuid,\n parentId: detail.parent?.uuid ?? null,\n name: detail.name,\n itemCount: detail.tracksCount,\n wikidataId: detail.wikidataId,\n } satisfies GenreTreeNode,\n fill: rootColor,\n parentNode: detail.parent ? relative(detail.parent, null) : null,\n childNodes: detail.children.map((child) => relative(child, detail.uuid)),\n ancestorNodes: ancestors.map(({ ascendant }, index) =>\n relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid),\n ),\n };\n }, [detail]);\n\n if (isError) {\n return (\n <div role=\"alert\" className=\"p-4 text-sm text-[var(--color-text)]\">\n This genre could not be loaded.\n </div>\n );\n }\n if (isPending || !detail || !view) {\n return (\n <div className=\"flex flex-col gap-3 p-4\" aria-busy=\"true\" data-testid=\"genre-detail-panel-skeleton\">\n <Skeleton className=\"h-6 w-2/3\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-3/4\" />\n </div>\n );\n }\n\n const selectGenre = (nodeId: string) => onSelectGenre(nodeId as GenreUuid);\n const family = (\n <GenreNodeFamily\n node={view.node}\n fill={view.fill}\n textColor={ACCENT_TEXT_COLOR}\n parentNode={view.parentNode}\n childNodes={view.childNodes}\n onSelectNode={selectGenre}\n />\n );\n const ancestors = <GenreNodeAncestors ancestorNodes={view.ancestorNodes} onSelectNode={selectGenre} />;\n\n return (\n <DetailPanel\n side=\"left\"\n title={view.node.name}\n headerAccent={{ background: view.fill, color: ACCENT_TEXT_COLOR }}\n headerActions={\n onExpandedChange ? (\n <IconButton\n aria-label={expanded ? \"Shrink panel\" : \"Extend panel\"}\n style={{ color: \"inherit\" }}\n onClick={() => onExpandedChange(!expanded)}\n >\n {expanded ? (\n <Minimize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <Maximize2 aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n </IconButton>\n ) : undefined\n }\n onClose={onClose}\n collapsed={collapsed}\n expanded={expanded}\n onExpandedChange={onExpandedChange}\n edgeTab={edgeTab}\n >\n <GenreInfoPanelBody<O>\n family={<>{family}{ancestors}</>}\n genreUuid={detail.uuid}\n genrePlaylist={{ ...detail.criteriaPlaylist, tracksCount: detail.tracksCount }}\n wikidataId={detail.wikidataId}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n playback={playback}\n band={{ node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : undefined }}\n extras={extras}\n />\n {!expanded && ancestors}\n </DetailPanel>\n );\n}\n","export * from \"./GenreTreeView\";\nexport type { GenreTreeShareTarget, GetGenreTreeShareUrl } from \"./share\";\nexport type { ParentChange } from \"./ChangeParentPopup\";\nexport type { GenreDetailSlots } from \"./GenreDetailExtras\";\nexport { GenreDetailPanel } from \"./GenreDetailPanel\";\nexport type { GenreDetailPanelProps } from \"./GenreDetailPanel\";\nexport { GenreInfoPanelBody, GenreNodeActionBand } from \"./GenreInfoPanelBody\";\nexport type { GenreInfoPanelBodyProps, PlayableGenrePlaylist } from \"./GenreInfoPanelBody\";\nexport { useGenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\nexport type { GenrePlaylistPlayback, UseGenrePlaylistPlaybackOptions } from \"./playlist-tree/useGenrePlaylistPlayback\";\nexport type { GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nexport { default as GenrePlaylistTreePerRoot } from \"./playlist-tree/TreePerRoot\";\nexport type { GenrePlaylistTreePerRootProps } from \"./playlist-tree/TreePerRoot\";\nexport { default as GenreSearch } from \"./GenreSearch\";\nexport type { GenreSearchProps, GenreSearchExtraGroup } from \"./GenreSearch\";\nexport {\n GenreTreeSkeleton,\n GenreTreeWheelSkeleton,\n GenreTreeViewSkeleton,\n GenreTreeOutlineSkeleton,\n} from \"@behindthemusictree/genre-tree-view\";\nexport type { GenreTreeAction, GenreTreeViewport, GenreTreeViewSkeletonProps } from \"@behindthemusictree/genre-tree-view\";\nexport { default as TrackListSidebar } from \"./track-list-sidebar/TrackListSidebar\";\nexport type { TrackListSidebarProps } from \"./track-list-sidebar/TrackListSidebar\";\nexport { default as PlayerPanel } from \"./PlayerPanel\";\nexport type { PlayerPanelProps } from \"./PlayerPanel\";\nexport { default as TrackItem } from \"./track-list-sidebar/TrackItem\";\nexport type { TrackItemProps } from \"./track-list-sidebar/TrackItem\";\nexport { default as TrackPositionPlayPause } from \"./TrackPositionPlayPause\";\nexport type { TrackPositionPlayPauseProps } from \"./TrackPositionPlayPause\";\nexport { default as Rating } from \"./Rating\";\nexport type { RatingProps } from \"./Rating\";\n","\"use client\";\n\nimport { ReactNode, useEffect, useLayoutEffect, useRef } from \"react\";\nimport { ListMusic } from \"lucide-react\";\n\nimport TrackItem from \"./TrackItem\";\nimport { useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackListOriginType } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { RenderSongLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nconst useBrowserLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport interface TrackListSidebarProps<T extends TrackBase> {\n className?: string;\n renderDuration?: (track: T) => ReactNode;\n renderActions?: (track: T) => ReactNode;\n layout?: \"fixed\" | \"inline\";\n isCollapsed?: boolean;\n onExpand?: () => void;\n onMinimize?: () => void;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, song titles render through it (e.g. open the song panel). */\n renderSongLink?: RenderSongLink;\n}\n\nexport default function TrackListSidebar<T extends TrackBase>({\n className = \"\",\n renderDuration,\n renderActions,\n layout = \"fixed\",\n isCollapsed = false,\n onExpand,\n onMinimize,\n renderArtistLink,\n renderSongLink,\n}: TrackListSidebarProps<T>) {\n const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList<T>();\n const trackNoun = useTrackNoun();\n const trackListRef = useRef<HTMLUListElement>(null);\n const previousOriginRef = useRef(trackList?.origin ?? null);\n const handledAutoAdvanceRequestIdRef = useRef<number | null>(null);\n const shouldFollowPlaybackRef = useRef(true);\n const programmaticScrollTopRef = useRef<number | null>(null);\n const userScrollIntentRef = useRef(false);\n const userScrollIntentTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const loadMoreSentinelRef = useRef<HTMLLIElement>(null);\n const hasMore = !!trackList && trackList.nextPage !== null;\n\n useEffect(() => {\n const sentinel = loadMoreSentinelRef.current;\n if (!hasMore || !sentinel) return;\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch((error) => console.error(\"Failed to load more tracks:\", error));\n }\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasMore, loadMore]);\n\n useBrowserLayoutEffect(() => {\n const origin = trackList?.origin ?? null;\n if (previousOriginRef.current === origin) return;\n previousOriginRef.current = origin;\n handledAutoAdvanceRequestIdRef.current = null;\n shouldFollowPlaybackRef.current = true;\n programmaticScrollTopRef.current = null;\n if (trackListRef.current) trackListRef.current.scrollTop = 0;\n }, [trackList?.origin]);\n\n useEffect(() => {\n const list = trackListRef.current;\n if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;\n }, [isCollapsed]);\n\n useEffect(\n () => () => {\n if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);\n },\n [],\n );\n\n useBrowserLayoutEffect(() => {\n if (\n !autoAdvanceScrollRequest ||\n handledAutoAdvanceRequestIdRef.current === autoAdvanceScrollRequest.id\n ) {\n return;\n }\n if (isCollapsed) return;\n\n const list = trackListRef.current;\n if (!list) return;\n if (list.scrollTop <= 1) shouldFollowPlaybackRef.current = true;\n if (!shouldFollowPlaybackRef.current) {\n handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;\n return;\n }\n\n const row = Array.from(list.querySelectorAll<HTMLElement>(\"[data-track-uuid]\")).find(\n (element) => element.dataset.trackUuid === autoAdvanceScrollRequest.trackUuid,\n );\n if (!row) return;\n handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;\n\n const rowTop = row.getBoundingClientRect().top - list.getBoundingClientRect().top;\n const targetScrollTop = list.scrollTop + rowTop;\n programmaticScrollTopRef.current = targetScrollTop;\n list.scrollTop = targetScrollTop;\n programmaticScrollTopRef.current = list.scrollTop;\n }, [autoAdvanceScrollRequest, isCollapsed, trackList]);\n\n const markUserScrollIntent = () => {\n userScrollIntentRef.current = true;\n if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);\n userScrollIntentTimerRef.current = setTimeout(() => {\n userScrollIntentRef.current = false;\n userScrollIntentTimerRef.current = null;\n }, 250);\n };\n\n const handleTrackListScroll = (event: React.UIEvent<HTMLUListElement>) => {\n const list = event.currentTarget;\n if (userScrollIntentRef.current) {\n userScrollIntentRef.current = false;\n if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);\n userScrollIntentTimerRef.current = null;\n programmaticScrollTopRef.current = null;\n shouldFollowPlaybackRef.current = list.scrollTop <= 1;\n return;\n }\n\n const programmaticScrollTop = programmaticScrollTopRef.current;\n if (programmaticScrollTop !== null) {\n if (Math.abs(list.scrollTop - programmaticScrollTop) <= 1) programmaticScrollTopRef.current = null;\n return;\n }\n };\n const positionClasses =\n layout === \"inline\"\n ? \"relative w-full flex h-full flex-col\"\n : \"fixed right-0 w-[50rem] flex flex-col\";\n\n return trackList ? (\n <div\n data-theme=\"dark\"\n className={`track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className}`}\n style={layout === \"fixed\" ? { top: \"63.5px\", maxHeight: \"calc(100vh - 63.5px - 79px)\" } : undefined}\n >\n <div className=\"header flex h-16 items-center px-4 py-2 text-[var(--color-text)]\">\n <div className=\"min-w-0 flex flex-1 flex-col justify-center\">\n <div className=\"origin flex text-xl\">\n <div className=\"from h-auto flex flex-col justify-center items-center mr-2\">From</div>\n <div className=\"name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]\">\n <div className=\"name max-w-full truncate\">{trackList.origin.label}</div>\n </div>\n </div>\n <div className=\"info flex items-center text-sm text-[var(--color-text-muted)]\">\n {trackList.origin.type === TrackListOriginType.GENRE_PLAYLIST\n ? \"Genre • \"\n : trackList.origin.type === TrackListOriginType.ARTIST\n ? \"Artist • \"\n : `${trackNoun.singular} playlist • `}\n {`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`}\n </div>\n </div>\n {((isCollapsed && onExpand) || (!isCollapsed && onMinimize)) && (\n <button\n type=\"button\"\n aria-label={isCollapsed ? \"Show tracks\" : \"Hide tracks\"}\n title={isCollapsed ? \"Show tracks\" : \"Hide tracks\"}\n aria-expanded={!isCollapsed}\n className={`ml-2 flex h-8 w-8 shrink-0 items-center justify-center rounded-md hover:bg-[var(--color-surface-muted)] ${isCollapsed ? \"text-[var(--color-text-muted)]\" : \"text-white\"}`}\n onClick={isCollapsed ? onExpand : onMinimize}\n >\n <ListMusic aria-hidden=\"true\" className=\"h-5 w-5\" />\n </button>\n )}\n </div>\n {!isCollapsed && (\n <ul\n ref={trackListRef}\n onScroll={handleTrackListScroll}\n onWheel={markUserScrollIntent}\n onTouchMove={markUserScrollIntent}\n onPointerDown={markUserScrollIntent}\n className={\n layout === \"inline\"\n ? \"track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0\"\n : \"track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0\"\n }\n >\n {trackList.tracks.map((track, index) => (\n <li key={track.uuid} data-track-uuid={track.uuid}>\n <TrackItem\n track={track}\n position={index + 1}\n renderDuration={renderDuration}\n renderActions={renderActions}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n </li>\n ))}\n {hasMore && <li ref={loadMoreSentinelRef} aria-hidden=\"true\" className=\"h-px\" />}\n </ul>\n )}\n </div>\n ) : null;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { twMerge } from \"tailwind-merge\";\nimport {\n usePlayer,\n PlayerVideoSurface,\n} from \"@behindthemusictree/app-kit/player\";\nimport {\n useTrackListSidebarVisibility,\n type RenderArtistLink,\n type RenderSongLink,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelEdgeTab } from \"@behindthemusictree/ui\";\nimport PlayerControls from \"./PlayerControls\";\nimport TrackListSidebar from \"./track-list-sidebar/TrackListSidebar\";\n\nexport interface PlayerPanelProps {\n className?: string;\n /** Hidden via CSS, never unmounted, so the video keeps playing while the host hides the panel. */\n isVisible?: boolean;\n /** @deprecated The player no longer closes; its header action minimizes to video-only mode. */\n onClose?: () => void;\n /** Controlled extended state; uncontrolled when omitted. */\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Width once extended, e.g. to stop short of a left-hand detail panel. */\n extendedWidth?: string;\n renderArtistLink?: RenderArtistLink;\n /** When set, song titles in the tracklist render through it (e.g. open the song panel). */\n renderSongLink?: RenderSongLink;\n}\n\nconst PANEL_ID = \"player-panel\";\n\n// Spans the center and right columns.\nconst DEFAULT_EXTENDED_WIDTH =\n \"calc(100vw - var(--layout-center-left) - var(--layout-gutter))\";\n\nexport default function PlayerPanel({\n className,\n isVisible = true,\n expanded,\n onExpandedChange,\n extendedWidth = DEFAULT_EXTENDED_WIDTH,\n renderArtistLink,\n renderSongLink,\n}: PlayerPanelProps) {\n const { playerTrackObject, retryPlayback } = usePlayer();\n const { toggleTrackListSidebar, isTrackListSidebarVisible } =\n useTrackListSidebarVisibility();\n const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState(\"\");\n const loadedTrackTitle = playerTrackObject?.track.title;\n useEffect(() => {\n if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);\n }, [loadedTrackTitle]);\n const [uncontrolledExtended, setUncontrolledExtended] = useState(false);\n const isExtendRequested = expanded ?? uncontrolledExtended;\n const setIsExtendRequested = (next: boolean) => {\n setUncontrolledExtended(next);\n onExpandedChange?.(next);\n };\n const isExtended = isExtendRequested && isTrackListSidebarVisible;\n const hideSongList = () => {\n setIsExtendRequested(false);\n if (isTrackListSidebarVisible) toggleTrackListSidebar();\n };\n\n const tabEdge =\n isTrackListSidebarVisible && isExtended\n ? {\n label: \"Shrink player panel\",\n onClick: () => setIsExtendRequested(false),\n expanded: true,\n }\n : null;\n const tabCenter =\n isTrackListSidebarVisible && !isExtended\n ? {\n label: \"Extend player panel\",\n onClick: () => setIsExtendRequested(true),\n expanded: false,\n }\n : null;\n\n return (\n <div\n id={PANEL_ID}\n className={twMerge(\n \"fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]\",\n !isExtended && \"w-[var(--layout-right-w)]\",\n isTrackListSidebarVisible &&\n \"h-[calc(100vh-var(--layout-band-top)-3.5rem)]\",\n !isVisible && \"hidden\",\n className,\n )}\n style={isExtended ? { width: extendedWidth } : undefined}\n >\n <PanelEdgeTab\n side=\"right\"\n edge={tabEdge}\n center={tabCenter}\n controls={PANEL_ID}\n className=\"absolute right-full top-1/2 -translate-y-1/2\"\n />\n <div\n className={twMerge(\n \"flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]\",\n isExtended ? \"flex-row\" : \"flex-col\",\n )}\n >\n <div className=\"flex w-[var(--layout-right-w)] shrink-0 flex-col\">\n <div className=\"flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2\">\n <span className=\"min-w-0 truncate text-sm font-semibold\">\n {loadedTrackTitle || lastLoadedTrackTitle}\n </span>\n </div>\n <div className=\"relative w-full shrink-0\">\n <PlayerVideoSurface className=\"aspect-video w-full bg-black\" />\n {playerTrackObject?.loadError && (\n <span className=\"absolute bottom-2 left-2 right-2 text-xs text-red-400\">\n {playerTrackObject.loadError}\n <button type=\"button\" className=\"ml-2 underline\" onClick={() => { void retryPlayback(); }}>Retry</button>\n </span>\n )}\n </div>\n <PlayerControls />\n </div>\n <TrackListSidebar\n layout=\"inline\"\n isCollapsed={!isTrackListSidebarVisible}\n onExpand={toggleTrackListSidebar}\n onMinimize={hideSongList}\n className={twMerge(\n \"min-h-0 min-w-0 flex-1\",\n isExtended && \"border-l border-zinc-800\",\n )}\n renderArtistLink={renderArtistLink}\n renderSongLink={renderSongLink}\n />\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { Pause, Play, SkipBack, SkipForward, Volume2, VolumeX } from \"lucide-react\";\nimport { formatTime, useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PlayStates, useCurrentTime, usePlayer } from \"@behindthemusictree/app-kit/player\";\n\nexport default function PlayerControls() {\n const {\n duration,\n handlePlayPauseAction,\n isLoading,\n playState,\n playerTrackObject,\n seekTo,\n setVolume,\n volume,\n } = usePlayer();\n const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = useTrackList();\n const currentTime = useCurrentTime();\n const isBusy = isLoading || playState === PlayStates.LOADING;\n const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;\n const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;\n const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;\n const isPlaying = playState === PlayStates.PLAYING;\n\n return (\n <section\n aria-label=\"Player controls\"\n className=\"player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200\"\n >\n <div className=\"flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400\">\n <span aria-label=\"Elapsed time\">{formatTime(safeCurrentTime)}</span>\n <input\n aria-label=\"Seek\"\n className=\"h-1 min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40\"\n type=\"range\"\n min={0}\n max={safeDuration}\n step={0.1}\n value={safeCurrentTime}\n disabled={!isReady || safeDuration <= 0}\n onChange={(event) => seekTo(Number(event.currentTarget.value))}\n />\n <span aria-label=\"Duration\">{formatTime(safeDuration)}</span>\n </div>\n\n <div className=\"grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3\">\n <div className=\"col-start-2 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n aria-label=\"Previous track\"\n title=\"Previous track\"\n disabled={!isReady || !hasPreviousTrack}\n onClick={playPreviousTrack}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <SkipBack aria-hidden=\"true\" className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n aria-label={isPlaying ? \"Pause\" : \"Play\"}\n title={isPlaying ? \"Pause\" : \"Play\"}\n disabled={!isReady}\n onClick={handlePlayPauseAction}\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-zinc-950 hover:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-40\"\n >\n {isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"h-4 w-4\" fill=\"currentColor\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"h-4 w-4\" fill=\"currentColor\" />\n )}\n </button>\n <button\n type=\"button\"\n aria-label=\"Next track\"\n title=\"Next track\"\n disabled={!isReady || !hasNextTrack}\n onClick={playNextTrack}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <SkipForward aria-hidden=\"true\" className=\"h-4 w-4\" />\n </button>\n </div>\n\n <label className=\"col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]\">\n {volume === 0 ? (\n <VolumeX aria-hidden=\"true\" className=\"h-[0.7rem] w-[0.7rem] shrink-0\" />\n ) : (\n <Volume2 aria-hidden=\"true\" className=\"h-[0.7rem] w-[0.7rem] shrink-0\" />\n )}\n <span className=\"sr-only\">Volume</span>\n <input\n aria-label=\"Volume\"\n className=\"h-[3px] min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40\"\n type=\"range\"\n min={0}\n max={100}\n step={1}\n value={volume}\n disabled={!isReady}\n onChange={(event) => setVolume(Number(event.currentTarget.value))}\n />\n </label>\n </div>\n </section>\n );\n}\n","\"use client\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { AiFillStar, AiOutlineStar } from \"react-icons/ai\";\n\nimport { FORM_RATING_NULL_VALUE } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport interface RatingProps {\n rating: number | null | undefined;\n handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;\n}\n\nexport default function Rating({ rating, handleChange }: RatingProps) {\n const instanceId = uuidv4();\n return (\n <div className=\"flex items-center\">\n {[...Array(6)].map((_, i) => (\n <div key={i} className={`relative ${i === 0 ? \"mr-4\" : \"mr-1\"}`}>\n <input\n id={`star-${instanceId}-${i}`}\n className=\"w-6 h-6 opacity-0\"\n type=\"radio\"\n name=\"rating\"\n value={i === 0 ? FORM_RATING_NULL_VALUE : i}\n checked={rating === (i === 0 ? null : i)}\n onChange={handleChange}\n />\n <label htmlFor={`star-${instanceId}-${i}`} className=\"cursor-pointer\">\n {i === 0 ? (\n <div className=\"flex\">\n <AiOutlineStar\n className={`w-7 h-7 absolute top-0 left-0 ${\n rating === FORM_RATING_NULL_VALUE ? \"text-[var(--color-accent-rating)]\" : \"text-[var(--color-text-muted)]\"\n }`}\n />\n <span className=\"flex-grow w-8\"></span>{\" \"}\n </div>\n ) : (\n <AiFillStar\n className={`w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? \"text-[var(--color-accent-rating)]\" : \"text-[var(--color-text-muted)]\"}`}\n />\n )}\n </label>\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["className","import_jsx_runtime","className","import_ui","import_jsx_runtime","className","import_react","import_react_dom","import_jsx_runtime","import_jsx_runtime","import_lucide_react","import_ui","import_jsx_runtime","import_lucide_react","import_jsx_runtime","import_lucide_react","import_jsx_runtime","import_lucide_react","import_ui","import_jsx_runtime","import_fa","import_ui","import_lucide_react","import_transport","import_jsx_runtime","import_jsx_runtime","import_react","import_react_query","import_lucide_react","import_ui","import_genre_tree_view","import_genre_tree","import_popup","import_react","import_popup","import_transport","import_react","import_jsx_runtime","className","import_react","import_genre_tree_view","import_player","import_popup","import_transport","import_jsx_runtime","className","import_react","import_genre_tree_view","import_player","import_jsx_runtime","className","import_react","import_jsx_runtime","import_react","import_lucide_react","import_ui","import_genre_tree","import_jsx_runtime","import_player","import_lucide_react","import_lucide_react","import_popup","import_react","import_lucide_react","import_fa","import_jsx_runtime","import_lucide_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_ui","import_genre_tree","import_react","import_jsx_runtime","className","import_react","import_fa","import_ui","import_auth","import_transport","import_player","import_popup","import_transport","import_genre_tree","import_genre_tree","import_jsx_runtime","className","import_genre_tree","import_track_noun","import_jsx_runtime","artist","import_track_noun","import_si","import_jsx_runtime","import_jsx_runtime","import_react","import_react","import_genre_tree","import_jsx_runtime","import_ui","import_genre_tree","import_ui","import_lucide_react","import_si","import_ui","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_ui","import_jsx_runtime","import_ui","import_jsx_runtime","import_react","import_react_query","import_lucide_react","import_ui","import_player","import_genre_tree","import_transport","import_player","import_genre_tree","import_fa","import_player","import_jsx_runtime","import_genre_tree","import_genre_tree","import_jsx_runtime","import_jsx_runtime","SongPageSchema","import_jsx_runtime","import_ui","import_genre_tree","import_react","import_lucide_react","import_ui","import_player","import_genre_tree","import_jsx_runtime","import_jsx_runtime","import_md","import_ui","import_jsx_runtime","import_jsx_runtime","className","import_react","import_genre_tree_view","import_ui","import_lucide_react","import_genre_tree","import_jsx_runtime","ancestors","import_genre_tree_view","import_react","import_lucide_react","import_genre_tree","import_track_noun","import_jsx_runtime","className","import_react","import_player","import_genre_tree","import_ui","import_lucide_react","import_genre_tree","import_player","import_jsx_runtime","import_jsx_runtime","className","import_genre_tree","import_jsx_runtime","uuidv4"]}
|