@behindthemusictree/app-kit-ui 40.0.0 → 41.0.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.
@@ -15,6 +15,9 @@ import {
15
15
  GenreTreeViewSkeleton,
16
16
  useGenreNodeRelations
17
17
  } from "@behindthemusictree/genre-tree-view";
18
+ import {
19
+ toGenreUuid
20
+ } from "@behindthemusictree/app-kit/genre-tree";
18
21
  import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
19
22
  import { usePopup as usePopup4 } from "@behindthemusictree/app-kit/popup";
20
23
 
@@ -46,6 +49,21 @@ function toTreeNodes(genrePlaylists, sizeMetric, focusUuids) {
46
49
  }));
47
50
  }
48
51
 
52
+ // src/genre-tree/playlist-tree/toGenreNodeStyles.ts
53
+ function toGenreNodeStyles(relations, genrePlaylists) {
54
+ const styles = /* @__PURE__ */ new Map();
55
+ if (!relations) return styles;
56
+ const genreUuidByPlaylistUuid = new Map(
57
+ genrePlaylists.flatMap((genrePlaylist) => genrePlaylist.criteria ? [[genrePlaylist.uuid, genrePlaylist.criteria.uuid]] : [])
58
+ );
59
+ const drawn = [relations, relations.parentNode, ...relations.childNodes, ...relations.ancestorNodes];
60
+ for (const entry of drawn) {
61
+ const genreUuid = entry && genreUuidByPlaylistUuid.get(entry.node.id);
62
+ if (entry && genreUuid) styles.set(genreUuid, { fill: entry.fill, textColor: entry.textColor });
63
+ }
64
+ return styles;
65
+ }
66
+
49
67
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
50
68
  import { genrePlaylistTrackCount as genrePlaylistTrackCount2 } from "@behindthemusictree/app-kit/genre-tree";
51
69
  import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
@@ -1145,6 +1163,29 @@ function ClampedText({ children, className: className2 = "" }) {
1145
1163
  ] });
1146
1164
  }
1147
1165
 
1166
+ // src/genre-tree/CommonsPhoto.tsx
1167
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
1168
+ function ExternalLink({ href, children }) {
1169
+ return /* @__PURE__ */ jsx20("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1170
+ }
1171
+ function CommonsPhoto({
1172
+ image,
1173
+ alt,
1174
+ creditLabel = "Photo"
1175
+ }) {
1176
+ return /* @__PURE__ */ jsxs12("figure", { className: "flex flex-col gap-1.5", children: [
1177
+ /* @__PURE__ */ jsx20("img", { src: image.url, alt, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1178
+ /* @__PURE__ */ jsxs12("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
1179
+ creditLabel,
1180
+ ": ",
1181
+ /* @__PURE__ */ jsx20(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1182
+ ",",
1183
+ " ",
1184
+ image.licenseUrl ? /* @__PURE__ */ jsx20(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1185
+ ] })
1186
+ ] });
1187
+ }
1188
+
1148
1189
  // src/genre-tree/GenrePlaylistTracks.tsx
1149
1190
  import { useEffect as useEffect4, useState as useState7 } from "react";
1150
1191
  import { useInfiniteQuery } from "@tanstack/react-query";
@@ -1167,15 +1208,15 @@ import { TracksFailureAlreadyReportedError as TracksFailureAlreadyReportedError2
1167
1208
 
1168
1209
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1169
1210
  import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
1170
- import { Fragment as Fragment8, jsx as jsx20 } from "react/jsx-runtime";
1211
+ import { Fragment as Fragment8, jsx as jsx21 } from "react/jsx-runtime";
1171
1212
  function MusicBrainzRecordingLink({
1172
1213
  song,
1173
1214
  children,
1174
1215
  className: className2 = ""
1175
1216
  }) {
1176
1217
  const url = musicBrainzRecordingUrl(song);
1177
- if (!url) return /* @__PURE__ */ jsx20(Fragment8, { children });
1178
- return /* @__PURE__ */ jsx20(
1218
+ if (!url) return /* @__PURE__ */ jsx21(Fragment8, { children });
1219
+ return /* @__PURE__ */ jsx21(
1179
1220
  "a",
1180
1221
  {
1181
1222
  href: url,
@@ -1192,7 +1233,7 @@ function MusicBrainzRecordingLink({
1192
1233
  // src/genre-tree/GenrePlaylistTracks.tsx
1193
1234
  import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
1194
1235
  import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
1195
- import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
1236
+ import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
1196
1237
  var CATALOGUE_PAGE_SIZE = 100;
1197
1238
  var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
1198
1239
  overallTotal: z.number().nullable(),
@@ -1279,35 +1320,35 @@ function GenrePlaylistTracks({
1279
1320
  started.catch((error) => {
1280
1321
  console.error("Failed to play genre playlist:", error);
1281
1322
  if (error instanceof TracksFailureAlreadyReportedError2) return;
1282
- showPopup(/* @__PURE__ */ jsx21(InternalErrorPopup, { errorCode: error instanceof AppError2 ? error.code : ErrorCode2.CLIENT_UNKNOWN }));
1323
+ showPopup(/* @__PURE__ */ jsx22(InternalErrorPopup, { errorCode: error instanceof AppError2 ? error.code : ErrorCode2.CLIENT_UNKNOWN }));
1283
1324
  });
1284
1325
  };
1285
- return /* @__PURE__ */ jsx21(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs12("div", { children: [
1326
+ return /* @__PURE__ */ jsx22(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs13("div", { children: [
1286
1327
  renderTracksToolbar?.(),
1287
- scope === "me" && !genrePlaylist ? /* @__PURE__ */ jsx21("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ jsx21(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
1328
+ scope === "me" && !genrePlaylist ? /* @__PURE__ */ jsx22("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ jsx22(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs13("p", { role: "alert", children: [
1288
1329
  "Couldn't load ",
1289
1330
  trackNoun.plural,
1290
1331
  ".",
1291
1332
  " ",
1292
- /* @__PURE__ */ jsx21("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1293
- ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx21("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1333
+ /* @__PURE__ */ jsx22("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1334
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx22("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs13("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1294
1335
  tracks.map((track, index) => {
1295
1336
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1296
1337
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1297
1338
  const isPlaying = isCurrent && playState === PlayStates.PLAYING;
1298
- const text = /* @__PURE__ */ jsxs12("span", { className: TEXT_CELL, children: [
1299
- /* @__PURE__ */ jsx21("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx21(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1300
- track.artists?.length ? /* @__PURE__ */ jsx21("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1301
- /* @__PURE__ */ jsx21("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx21(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1302
- track.album && /* @__PURE__ */ jsx21("span", { className: ALBUM_CELL, children: track.album.name })
1339
+ const text = /* @__PURE__ */ jsxs13("span", { className: TEXT_CELL, children: [
1340
+ /* @__PURE__ */ jsx22("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx22(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1341
+ track.artists?.length ? /* @__PURE__ */ jsx22("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1342
+ /* @__PURE__ */ jsx22("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx22(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1343
+ track.album && /* @__PURE__ */ jsx22("span", { className: ALBUM_CELL, children: track.album.name })
1303
1344
  ] });
1304
1345
  const leading = renderSongLeading?.(track, index);
1305
1346
  const actions = renderSongActions?.(track);
1306
1347
  const isUnplayable = isSong(track) && songUnplayableReason(track) !== null;
1307
1348
  if (isUnplayable) {
1308
- return /* @__PURE__ */ jsxs12("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1349
+ return /* @__PURE__ */ jsxs13("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1309
1350
  leading,
1310
- /* @__PURE__ */ jsx21(
1351
+ /* @__PURE__ */ jsx22(
1311
1352
  "button",
1312
1353
  {
1313
1354
  type: "button",
@@ -1315,27 +1356,27 @@ function GenrePlaylistTracks({
1315
1356
  "aria-disabled": "true",
1316
1357
  "aria-label": label,
1317
1358
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1318
- children: /* @__PURE__ */ jsx21(FaPlay, { size: 10 })
1359
+ children: /* @__PURE__ */ jsx22(FaPlay, { size: 10 })
1319
1360
  }
1320
1361
  ),
1321
1362
  text,
1322
1363
  actions
1323
1364
  ] }, track.uuid);
1324
1365
  }
1325
- return /* @__PURE__ */ jsxs12(
1366
+ return /* @__PURE__ */ jsxs13(
1326
1367
  "li",
1327
1368
  {
1328
1369
  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)]" : ""}`,
1329
1370
  children: [
1330
1371
  leading,
1331
- /* @__PURE__ */ jsx21(
1372
+ /* @__PURE__ */ jsx22(
1332
1373
  "button",
1333
1374
  {
1334
1375
  type: "button",
1335
1376
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1336
1377
  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"}`,
1337
1378
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1338
- children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx21(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx21(FaPause, { size: 10 }) : /* @__PURE__ */ jsx21(FaPlay, { size: 10 })
1379
+ children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx22(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx22(FaPause, { size: 10 }) : /* @__PURE__ */ jsx22(FaPlay, { size: 10 })
1339
1380
  }
1340
1381
  ),
1341
1382
  text,
@@ -1345,7 +1386,7 @@ function GenrePlaylistTracks({
1345
1386
  track.uuid
1346
1387
  );
1347
1388
  }),
1348
- hasNextPage && /* @__PURE__ */ jsx21("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1389
+ hasNextPage && /* @__PURE__ */ jsx22("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1349
1390
  ] })
1350
1391
  ] }) });
1351
1392
  }
@@ -1353,7 +1394,7 @@ function GenrePlaylistTracks({
1353
1394
  // src/genre-tree/GenreDetailExtras.tsx
1354
1395
  import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
1355
1396
  import { SiWikidata, SiWikipedia } from "react-icons/si";
1356
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
1397
+ import { Fragment as Fragment9, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1357
1398
  function GenreDetailExtras({
1358
1399
  genreUuid,
1359
1400
  genrePlaylist,
@@ -1379,17 +1420,18 @@ function GenreDetailExtras({
1379
1420
  criteriaOverviewSchema
1380
1421
  );
1381
1422
  if (isPending) {
1382
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
1383
- /* @__PURE__ */ jsx22(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx22("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx22(Skeleton2, { className: "h-4 w-3/4" }) }) }),
1423
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1424
+ /* @__PURE__ */ jsx23(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx23("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx23(Skeleton2, { className: "h-4 w-3/4" }) }) }),
1384
1425
  family
1385
1426
  ] });
1386
1427
  }
1387
- if (!overview) return /* @__PURE__ */ jsx22(Fragment9, { children: family });
1388
- const { summary, summarySourceUrl, essentialSongs } = overview;
1428
+ if (!overview) return /* @__PURE__ */ jsx23(Fragment9, { children: family });
1429
+ const { summary, summarySourceUrl, essentialSongs, image } = overview;
1389
1430
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1390
- return /* @__PURE__ */ jsxs13(Fragment9, { children: [
1391
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx22(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1392
- summarySourceUrl && /* @__PURE__ */ jsxs13(
1431
+ return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1432
+ image && /* @__PURE__ */ jsx23(CommonsPhoto, { image, alt: overview.name, creditLabel: "Image" }),
1433
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx23(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1434
+ summarySourceUrl && /* @__PURE__ */ jsxs14(
1393
1435
  "a",
1394
1436
  {
1395
1437
  href: summarySourceUrl,
@@ -1397,12 +1439,12 @@ function GenreDetailExtras({
1397
1439
  rel: "noopener noreferrer",
1398
1440
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1399
1441
  children: [
1400
- /* @__PURE__ */ jsx22(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1442
+ /* @__PURE__ */ jsx23(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1401
1443
  "Wikipedia"
1402
1444
  ]
1403
1445
  }
1404
1446
  ),
1405
- wikidataUrl && /* @__PURE__ */ jsxs13(
1447
+ wikidataUrl && /* @__PURE__ */ jsxs14(
1406
1448
  "a",
1407
1449
  {
1408
1450
  href: wikidataUrl,
@@ -1410,17 +1452,17 @@ function GenreDetailExtras({
1410
1452
  rel: "noopener noreferrer",
1411
1453
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1412
1454
  children: [
1413
- /* @__PURE__ */ jsx22(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1455
+ /* @__PURE__ */ jsx23(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1414
1456
  "Wikidata"
1415
1457
  ]
1416
1458
  }
1417
1459
  )
1418
1460
  ] }) }),
1419
- /* @__PURE__ */ jsx22(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx22(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1461
+ /* @__PURE__ */ jsx23(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx23(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1420
1462
  family,
1421
1463
  renderGenreDetailExtras?.(overview),
1422
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx22(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx22("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx22("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx22("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
1423
- /* @__PURE__ */ jsx22(
1464
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx23(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx23("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx23("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx23("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
1465
+ /* @__PURE__ */ jsx23(
1424
1466
  GenrePlaylistTracks,
1425
1467
  {
1426
1468
  genreUuid,
@@ -1438,10 +1480,10 @@ function GenreDetailExtras({
1438
1480
  }
1439
1481
 
1440
1482
  // src/genre-tree/GenreInfoPanelBody.tsx
1441
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1483
+ import { Fragment as Fragment10, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1442
1484
  import { createElement } from "react";
1443
1485
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1444
- return /* @__PURE__ */ jsx23(GenreActionBand, { ...bandProps, playActions: /* @__PURE__ */ jsx23(GenrePlayActions, { genrePlaylist, playback }) });
1486
+ return /* @__PURE__ */ jsx24(GenreActionBand, { ...bandProps, playActions: /* @__PURE__ */ jsx24(GenrePlayActions, { genrePlaylist, playback }) });
1445
1487
  }
1446
1488
  function GenreInfoPanelBody({
1447
1489
  toolbar,
@@ -1458,9 +1500,9 @@ function GenreInfoPanelBody({
1458
1500
  }) {
1459
1501
  const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist ?? catalogue) : void 0;
1460
1502
  const selection = summary ? defaultGenreMusicSelection2(summary) : void 0;
1461
- return /* @__PURE__ */ jsxs14(Fragment10, { children: [
1503
+ return /* @__PURE__ */ jsxs15(Fragment10, { children: [
1462
1504
  toolbar,
1463
- genrePlaylist ? /* @__PURE__ */ jsx23(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ jsx23(GenreActionBand, { ...band }),
1505
+ genrePlaylist ? /* @__PURE__ */ jsx24(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ jsx24(GenreActionBand, { ...band }),
1464
1506
  /* @__PURE__ */ createElement(
1465
1507
  GenreDetailExtras,
1466
1508
  {
@@ -1480,7 +1522,7 @@ function GenreInfoPanelBody({
1480
1522
  }
1481
1523
 
1482
1524
  // src/genre-tree/GenreDetailPanel.tsx
1483
- import { Fragment as Fragment11, jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1525
+ import { Fragment as Fragment11, jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1484
1526
  var RELATIVE_ITEM_COUNT = 1;
1485
1527
  function toNode(genre, parentId) {
1486
1528
  return {
@@ -1503,27 +1545,21 @@ function useGenreDetailContent({
1503
1545
  trailing,
1504
1546
  genrePlaylist: hostPlaylist,
1505
1547
  expanded,
1506
- treeRelations
1548
+ treeStyles
1507
1549
  }) {
1508
- const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
1550
+ const result = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
1551
+ const detail = result.status === "success" ? result.genre : void 0;
1509
1552
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
1510
1553
  const playback = hostPlayback ?? defaultPlayback;
1511
1554
  const view = useMemo6(() => {
1512
1555
  if (!detail) return null;
1513
1556
  const rootColor = getGenreTreeColor2(detail.root.wikidataId);
1514
- const treeStyles = /* @__PURE__ */ new Map();
1515
- if (treeRelations) {
1516
- const drawn = [treeRelations, treeRelations.parentNode, ...treeRelations.childNodes, ...treeRelations.ancestorNodes];
1517
- for (const entry of drawn) {
1518
- if (entry) treeStyles.set(entry.node.id, { fill: entry.fill, textColor: entry.textColor });
1519
- }
1520
- }
1521
1557
  const relative = (genre, parentId) => ({
1522
1558
  node: toNode(genre, parentId),
1523
- ...treeStyles.get(genre.uuid) ?? { fill: tintSurface(rootColor), textColor: TEXT_COLOR }
1559
+ ...treeStyles?.get(genre.uuid) ?? { fill: tintSurface(rootColor), textColor: TEXT_COLOR }
1524
1560
  });
1525
1561
  const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
1526
- const own = treeStyles.get(detail.uuid) ?? { fill: rootColor, textColor: getTextColorOnFill(rootColor) };
1562
+ const own = treeStyles?.get(detail.uuid) ?? { fill: rootColor, textColor: getTextColorOnFill(rootColor) };
1527
1563
  return {
1528
1564
  node: {
1529
1565
  id: detail.uuid,
@@ -1540,23 +1576,28 @@ function useGenreDetailContent({
1540
1576
  ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
1541
1577
  )
1542
1578
  };
1543
- }, [detail, scope, treeRelations]);
1544
- if (isError) return {
1579
+ }, [detail, scope, treeStyles]);
1580
+ if (result.status === "not-found") return {
1581
+ name: null,
1582
+ title: "Genre not found",
1583
+ content: /* @__PURE__ */ jsx25("p", { className: "text-[var(--color-text-muted)]", children: "This genre does not exist." })
1584
+ };
1585
+ if (result.status === "error") return {
1545
1586
  name: null,
1546
1587
  title: "Genre unavailable",
1547
- content: /* @__PURE__ */ jsx24("p", { role: "alert", className: "text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
1588
+ content: /* @__PURE__ */ jsx25("p", { role: "alert", className: "text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
1548
1589
  };
1549
- if (isPending || !detail || !view) return {
1590
+ if (!detail || !view) return {
1550
1591
  name: null,
1551
1592
  title: "Loading genre\u2026",
1552
- content: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
1553
- /* @__PURE__ */ jsx24(Skeleton3, { className: "h-6 w-2/3" }),
1554
- /* @__PURE__ */ jsx24(Skeleton3, { className: "h-4 w-full" }),
1555
- /* @__PURE__ */ jsx24(Skeleton3, { className: "h-4 w-3/4" })
1593
+ content: /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-3", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
1594
+ /* @__PURE__ */ jsx25(Skeleton3, { className: "h-6 w-2/3" }),
1595
+ /* @__PURE__ */ jsx25(Skeleton3, { className: "h-4 w-full" }),
1596
+ /* @__PURE__ */ jsx25(Skeleton3, { className: "h-4 w-3/4" })
1556
1597
  ] })
1557
1598
  };
1558
1599
  const selectGenre = (nodeId) => onSelectGenre(GenreUuidSchema.parse(nodeId));
1559
- const family = /* @__PURE__ */ jsx24(
1600
+ const family = /* @__PURE__ */ jsx25(
1560
1601
  GenreNodeFamily,
1561
1602
  {
1562
1603
  node: view.node,
@@ -1567,17 +1608,17 @@ function useGenreDetailContent({
1567
1608
  onSelectNode: selectGenre
1568
1609
  }
1569
1610
  );
1570
- const ancestors = /* @__PURE__ */ jsx24(GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
1611
+ const ancestors = /* @__PURE__ */ jsx25(GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
1571
1612
  return {
1572
1613
  name: detail.name,
1573
1614
  title: detail.name,
1574
1615
  headerAccent: { background: view.fill, color: view.textColor },
1575
1616
  aside: expanded ? ancestors : void 0,
1576
- content: /* @__PURE__ */ jsxs15(Fragment11, { children: [
1577
- /* @__PURE__ */ jsx24(
1617
+ content: /* @__PURE__ */ jsxs16(Fragment11, { children: [
1618
+ /* @__PURE__ */ jsx25(
1578
1619
  GenreInfoPanelBody,
1579
1620
  {
1580
- family: /* @__PURE__ */ jsxs15(Fragment11, { children: [
1621
+ family: /* @__PURE__ */ jsxs16(Fragment11, { children: [
1581
1622
  family,
1582
1623
  !expanded && ancestors
1583
1624
  ] }),
@@ -1621,7 +1662,7 @@ function GenreDetailPanel({
1621
1662
  expanded,
1622
1663
  band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
1623
1664
  });
1624
- return /* @__PURE__ */ jsx24(
1665
+ return /* @__PURE__ */ jsx25(
1625
1666
  DetailPanel,
1626
1667
  {
1627
1668
  side: "left",
@@ -1634,7 +1675,7 @@ function GenreDetailPanel({
1634
1675
  edgeTab,
1635
1676
  aside: panel.aside,
1636
1677
  titleInSearch,
1637
- headerActions: onExpandedChange ? /* @__PURE__ */ jsx24(IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ jsx24(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx24(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
1678
+ headerActions: onExpandedChange ? /* @__PURE__ */ jsx25(IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ jsx25(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx25(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
1638
1679
  children: panel.content
1639
1680
  }
1640
1681
  );
@@ -1646,7 +1687,7 @@ import {
1646
1687
  getReparentCandidates,
1647
1688
  useUpdateGenre
1648
1689
  } from "@behindthemusictree/app-kit/genre-tree";
1649
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1690
+ import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1650
1691
  function ChangeParentPopup({
1651
1692
  genrePlaylist,
1652
1693
  genrePlaylists,
@@ -1681,12 +1722,12 @@ function ChangeParentPopup({
1681
1722
  window.addEventListener("keydown", handleKeyDown);
1682
1723
  return () => window.removeEventListener("keydown", handleKeyDown);
1683
1724
  }, [onClose]);
1684
- return /* @__PURE__ */ jsxs16(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1685
- /* @__PURE__ */ jsxs16("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
1725
+ return /* @__PURE__ */ jsxs17(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1726
+ /* @__PURE__ */ jsxs17("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
1686
1727
  "Current parent: ",
1687
1728
  genrePlaylist.parent?.name ?? "none"
1688
1729
  ] }),
1689
- /* @__PURE__ */ jsx25("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx25(
1730
+ /* @__PURE__ */ jsx26("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx26(
1690
1731
  GenreSearch,
1691
1732
  {
1692
1733
  genrePlaylists: candidates,
@@ -1694,7 +1735,7 @@ function ChangeParentPopup({
1694
1735
  placeholder: "Search a new parent\u2026"
1695
1736
  }
1696
1737
  ) }),
1697
- formErrors.length > 0 && /* @__PURE__ */ jsx25("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1738
+ formErrors.length > 0 && /* @__PURE__ */ jsx26("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1698
1739
  ] });
1699
1740
  }
1700
1741
 
@@ -1707,7 +1748,7 @@ import { Pause as Pause2, Play as Play2 } from "lucide-react";
1707
1748
  import { ActionBand as ActionBand2, ActionButton as ActionButton4, PanelBleed as PanelBleed2 } from "@behindthemusictree/ui";
1708
1749
  import { TrackListOriginType as TrackListOriginType2, useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre-tree";
1709
1750
  import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
1710
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
1751
+ import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
1711
1752
  function ArtistActionBand({ artist, share }) {
1712
1753
  const { trackList, playNewTrackListFromArtist } = useTrackList3();
1713
1754
  const { isPlaying, setIsPlaying } = usePlayer6();
@@ -1723,11 +1764,11 @@ function ArtistActionBand({ artist, share }) {
1723
1764
  console.error("Failed to load artist tracks:", error);
1724
1765
  });
1725
1766
  };
1726
- return /* @__PURE__ */ jsx26(PanelBleed2, { children: /* @__PURE__ */ jsxs17(ActionBand2, { children: [
1727
- /* @__PURE__ */ jsx26(
1767
+ return /* @__PURE__ */ jsx27(PanelBleed2, { children: /* @__PURE__ */ jsxs18(ActionBand2, { children: [
1768
+ /* @__PURE__ */ jsx27(
1728
1769
  ActionButton4,
1729
1770
  {
1730
- icon: playing ? /* @__PURE__ */ jsx26(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx26(Play2, { "aria-hidden": "true", className: "w-5 h-5" }),
1771
+ icon: playing ? /* @__PURE__ */ jsx27(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx27(Play2, { "aria-hidden": "true", className: "w-5 h-5" }),
1731
1772
  label: playing ? "Pause" : "Play",
1732
1773
  caption: String(artist.playableTracksCount),
1733
1774
  disabled: artist.playableTracksCount === 0,
@@ -1755,7 +1796,7 @@ import {
1755
1796
  SiYoutube
1756
1797
  } from "react-icons/si";
1757
1798
  import { Chip } from "@behindthemusictree/ui";
1758
- import { jsx as jsx27 } from "react/jsx-runtime";
1799
+ import { jsx as jsx28 } from "react/jsx-runtime";
1759
1800
  var LINK_KINDS = {
1760
1801
  official: { label: "Official website", Icon: Globe },
1761
1802
  wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
@@ -1792,7 +1833,7 @@ function artistLinks(artist) {
1792
1833
  return links;
1793
1834
  }
1794
1835
  function ArtistLinks({ links }) {
1795
- return /* @__PURE__ */ jsx27("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx27("li", { children: /* @__PURE__ */ jsx27(Chip, { href: url, icon: /* @__PURE__ */ jsx27(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1836
+ return /* @__PURE__ */ jsx28("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx28("li", { children: /* @__PURE__ */ jsx28(Chip, { href: url, icon: /* @__PURE__ */ jsx28(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1796
1837
  }
1797
1838
 
1798
1839
  // src/genre-tree/artist/formatYears.ts
@@ -1803,7 +1844,7 @@ function formatYears(beginYear, endYear, ended) {
1803
1844
  }
1804
1845
 
1805
1846
  // src/genre-tree/artist/ArtistHeader.tsx
1806
- import { jsx as jsx28 } from "react/jsx-runtime";
1847
+ import { jsx as jsx29 } from "react/jsx-runtime";
1807
1848
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1808
1849
  function ArtistHeader({ artist }) {
1809
1850
  const fields = [];
@@ -1813,35 +1854,23 @@ function ArtistHeader({ artist }) {
1813
1854
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1814
1855
  if (years) fields.push({ label: "Active", value: years });
1815
1856
  const links = artistLinks(artist);
1816
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx28(ArtistLinks, { links }) });
1857
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx29(ArtistLinks, { links }) });
1817
1858
  if (fields.length === 0) return null;
1818
- return /* @__PURE__ */ jsx28(FieldList, { items: fields });
1859
+ return /* @__PURE__ */ jsx29(FieldList, { items: fields });
1819
1860
  }
1820
1861
 
1821
1862
  // src/genre-tree/artist/ArtistPhoto.tsx
1822
- import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
1823
- function ExternalLink({ href, children }) {
1824
- return /* @__PURE__ */ jsx29("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1825
- }
1863
+ import { jsx as jsx30 } from "react/jsx-runtime";
1826
1864
  function ArtistPhoto({ image, name }) {
1827
- return /* @__PURE__ */ jsxs18("figure", { className: "flex flex-col gap-1.5", children: [
1828
- /* @__PURE__ */ jsx29("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1829
- /* @__PURE__ */ jsxs18("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
1830
- "Photo: ",
1831
- /* @__PURE__ */ jsx29(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1832
- ",",
1833
- " ",
1834
- image.licenseUrl ? /* @__PURE__ */ jsx29(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1835
- ] })
1836
- ] });
1865
+ return /* @__PURE__ */ jsx30(CommonsPhoto, { image, alt: name });
1837
1866
  }
1838
1867
 
1839
1868
  // src/genre-tree/artist/ArtistMembers.tsx
1840
1869
  import { PanelGroup, PanelSection as PanelSection3 } from "@behindthemusictree/ui";
1841
- import { jsx as jsx30, jsxs as jsxs19 } from "react/jsx-runtime";
1870
+ import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
1842
1871
  function MembershipList({ title, memberships, renderArtistLink }) {
1843
1872
  if (memberships.length === 0) return null;
1844
- return /* @__PURE__ */ jsx30(PanelGroup, { title, children: /* @__PURE__ */ jsx30("ul", { children: memberships.map((membership, index) => {
1873
+ return /* @__PURE__ */ jsx31(PanelGroup, { title, children: /* @__PURE__ */ jsx31("ul", { children: memberships.map((membership, index) => {
1845
1874
  const details = [
1846
1875
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1847
1876
  ...membership.roles
@@ -1850,7 +1879,7 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1850
1879
  return (
1851
1880
  // A member can have several periods in the same group.
1852
1881
  /* @__PURE__ */ jsxs19("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1853
- /* @__PURE__ */ jsx30("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1882
+ /* @__PURE__ */ jsx31("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1854
1883
  detailsText && /* @__PURE__ */ jsxs19("span", { className: "text-[var(--color-text-muted)]", children: [
1855
1884
  " \u2014 ",
1856
1885
  detailsText
@@ -1863,8 +1892,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1863
1892
  if (memberships.length === 0) return null;
1864
1893
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1865
1894
  return /* @__PURE__ */ jsxs19(PanelSection3, { title, children: [
1866
- /* @__PURE__ */ jsx30(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1867
- /* @__PURE__ */ jsx30(
1895
+ /* @__PURE__ */ jsx31(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1896
+ /* @__PURE__ */ jsx31(
1868
1897
  MembershipList,
1869
1898
  {
1870
1899
  title: "Past",
@@ -1877,7 +1906,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1877
1906
 
1878
1907
  // src/genre-tree/artist/ArtistDiscography.tsx
1879
1908
  import { PanelGroup as PanelGroup2, PanelSection as PanelSection4 } from "@behindthemusictree/ui";
1880
- import { jsx as jsx31, jsxs as jsxs20 } from "react/jsx-runtime";
1909
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
1881
1910
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1882
1911
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1883
1912
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1897,12 +1926,12 @@ function byYear(a, b) {
1897
1926
  function ArtistDiscography({ albums }) {
1898
1927
  if (albums.length === 0) return null;
1899
1928
  const sorted = [...albums].sort(byYear);
1900
- return /* @__PURE__ */ jsx31(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1929
+ return /* @__PURE__ */ jsx32(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1901
1930
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1902
1931
  if (groupAlbums.length === 0) return null;
1903
- return /* @__PURE__ */ jsx31(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx31("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs20("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1904
- /* @__PURE__ */ jsx31("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1905
- /* @__PURE__ */ jsx31("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1932
+ return /* @__PURE__ */ jsx32(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx32("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs20("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1933
+ /* @__PURE__ */ jsx32("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1934
+ /* @__PURE__ */ jsx32("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1906
1935
  ] }, album.uuid)) }) }, group);
1907
1936
  }) });
1908
1937
  }
@@ -1910,7 +1939,6 @@ function ArtistDiscography({ albums }) {
1910
1939
  // src/genre-tree/artist/ArtistSongs.tsx
1911
1940
  import { useState as useState8 } from "react";
1912
1941
  import { keepPreviousData } from "@tanstack/react-query";
1913
- import { Play as Play3 } from "lucide-react";
1914
1942
  import { Pagination, PanelSection as PanelSection5, Skeleton as Skeleton4 } from "@behindthemusictree/ui";
1915
1943
  import { usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
1916
1944
  import {
@@ -1929,7 +1957,7 @@ import { useTrackList as useTrackList4 } from "@behindthemusictree/app-kit/genre
1929
1957
  import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
1930
1958
  import { usePlayer as usePlayer7 } from "@behindthemusictree/app-kit/player";
1931
1959
  import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
1932
- import { jsx as jsx32, jsxs as jsxs21 } from "react/jsx-runtime";
1960
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
1933
1961
  function TrackPositionPlayPause({
1934
1962
  position,
1935
1963
  uuid,
@@ -1950,7 +1978,7 @@ function TrackPositionPlayPause({
1950
1978
  style: { minWidth: "4rem", maxWidth: "4rem" },
1951
1979
  onClick: handlePlayPauseClick,
1952
1980
  children: [
1953
- /* @__PURE__ */ jsx32("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx32("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs21(
1981
+ /* @__PURE__ */ jsx33("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx33("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs21(
1954
1982
  "span",
1955
1983
  {
1956
1984
  className: "track-position-play-pause__bars",
@@ -1958,18 +1986,18 @@ function TrackPositionPlayPause({
1958
1986
  role: "img",
1959
1987
  "aria-label": isPlaying ? "Playing" : "Paused",
1960
1988
  children: [
1961
- /* @__PURE__ */ jsx32("span", { className: "track-position-play-pause__bar" }),
1962
- /* @__PURE__ */ jsx32("span", { className: "track-position-play-pause__bar" }),
1963
- /* @__PURE__ */ jsx32("span", { className: "track-position-play-pause__bar" })
1989
+ /* @__PURE__ */ jsx33("span", { className: "track-position-play-pause__bar" }),
1990
+ /* @__PURE__ */ jsx33("span", { className: "track-position-play-pause__bar" }),
1991
+ /* @__PURE__ */ jsx33("span", { className: "track-position-play-pause__bar" })
1964
1992
  ]
1965
1993
  }
1966
1994
  ) : position }),
1967
- /* @__PURE__ */ jsx32(
1995
+ /* @__PURE__ */ jsx33(
1968
1996
  "div",
1969
1997
  {
1970
1998
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1971
1999
  "aria-hidden": "true",
1972
- children: isAwaitingPlayback ? /* @__PURE__ */ jsx32("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx32(FaPause2, {}) : /* @__PURE__ */ jsx32(FaPlay2, {})
2000
+ children: isAwaitingPlayback ? /* @__PURE__ */ jsx33("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx33(FaPause2, {}) : /* @__PURE__ */ jsx33(FaPlay2, {})
1973
2001
  }
1974
2002
  )
1975
2003
  ]
@@ -1980,7 +2008,7 @@ function TrackPositionPlayPause({
1980
2008
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1981
2009
  import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
1982
2010
  import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
1983
- import { jsx as jsx33, jsxs as jsxs22 } from "react/jsx-runtime";
2011
+ import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1984
2012
  function TrackItem({
1985
2013
  track,
1986
2014
  position,
@@ -2009,7 +2037,7 @@ function TrackItem({
2009
2037
  {
2010
2038
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
2011
2039
  children: [
2012
- isUnplayable ? /* @__PURE__ */ jsx33(
2040
+ isUnplayable ? /* @__PURE__ */ jsx34(
2013
2041
  "div",
2014
2042
  {
2015
2043
  className: "flex items-center justify-center text-lg w-16",
@@ -2017,7 +2045,7 @@ function TrackItem({
2017
2045
  "aria-disabled": "true",
2018
2046
  children: position
2019
2047
  }
2020
- ) : /* @__PURE__ */ jsx33(
2048
+ ) : /* @__PURE__ */ jsx34(
2021
2049
  TrackPositionPlayPause,
2022
2050
  {
2023
2051
  position,
@@ -2027,11 +2055,11 @@ function TrackItem({
2027
2055
  }
2028
2056
  ),
2029
2057
  /* @__PURE__ */ jsxs22("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2030
- /* @__PURE__ */ jsx33("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx33("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx33(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2031
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx33("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx33(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
2058
+ /* @__PURE__ */ jsx34("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx34("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx34(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2059
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx34("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx34(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
2032
2060
  ] }),
2033
- /* @__PURE__ */ jsx33("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx33("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2034
- renderDuration && /* @__PURE__ */ jsx33(
2061
+ /* @__PURE__ */ jsx34("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx34("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2062
+ renderDuration && /* @__PURE__ */ jsx34(
2035
2063
  "div",
2036
2064
  {
2037
2065
  className: "duration flex text-base w-16 items-center justify-center",
@@ -2039,14 +2067,14 @@ function TrackItem({
2039
2067
  children: renderDuration(track)
2040
2068
  }
2041
2069
  ),
2042
- renderActions && /* @__PURE__ */ jsx33("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2070
+ renderActions && /* @__PURE__ */ jsx34("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2043
2071
  ]
2044
2072
  }
2045
2073
  );
2046
2074
  }
2047
2075
 
2048
2076
  // src/genre-tree/artist/ArtistSongs.tsx
2049
- import { Fragment as Fragment12, jsx as jsx34, jsxs as jsxs23 } from "react/jsx-runtime";
2077
+ import { Fragment as Fragment12, jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
2050
2078
  var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
2051
2079
  var SORTS = [
2052
2080
  { ordering: "title", label: "Title" },
@@ -2079,36 +2107,22 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2079
2107
  event.stopPropagation();
2080
2108
  void play(song.uuid);
2081
2109
  };
2082
- return /* @__PURE__ */ jsx34(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx34(Skeleton4, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx34("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx34("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs23(Fragment12, { children: [
2083
- /* @__PURE__ */ jsxs23("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2084
- /* @__PURE__ */ jsxs23(
2085
- "button",
2086
- {
2087
- type: "button",
2088
- onClick: () => void play(),
2089
- className: "flex w-fit items-center gap-1.5 px-3 py-1 text-caption font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
2090
- children: [
2091
- /* @__PURE__ */ jsx34(Play3, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2092
- "Play all"
2093
- ]
2094
- }
2095
- ),
2096
- /* @__PURE__ */ jsx34("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ jsx34(
2097
- "button",
2098
- {
2099
- type: "button",
2100
- "aria-pressed": ordering === sort.ordering,
2101
- onClick: () => {
2102
- setOrdering(sort.ordering);
2103
- setPage(1);
2104
- },
2105
- 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",
2106
- children: sort.label
2110
+ return /* @__PURE__ */ jsx35(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx35(Skeleton4, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx35("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx35("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs23(Fragment12, { children: [
2111
+ /* @__PURE__ */ jsx35("div", { className: "flex flex-wrap items-center justify-end gap-2 mb-2", children: /* @__PURE__ */ jsx35("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ jsx35(
2112
+ "button",
2113
+ {
2114
+ type: "button",
2115
+ "aria-pressed": ordering === sort.ordering,
2116
+ onClick: () => {
2117
+ setOrdering(sort.ordering);
2118
+ setPage(1);
2107
2119
  },
2108
- sort.ordering
2109
- )) })
2110
- ] }),
2111
- /* @__PURE__ */ jsx34("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx34("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx34(
2120
+ 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",
2121
+ children: sort.label
2122
+ },
2123
+ sort.ordering
2124
+ )) }) }),
2125
+ /* @__PURE__ */ jsx35("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx35("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx35(
2112
2126
  TrackItem,
2113
2127
  {
2114
2128
  track: song,
@@ -2117,12 +2131,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2117
2131
  renderSongLink
2118
2132
  }
2119
2133
  ) }, song.uuid)) }),
2120
- /* @__PURE__ */ jsx34(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2134
+ /* @__PURE__ */ jsx35(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2121
2135
  ] }) });
2122
2136
  }
2123
2137
 
2124
2138
  // src/genre-tree/artist/useArtistPanel.tsx
2125
- import { Fragment as Fragment13, jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
2139
+ import { Fragment as Fragment13, jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
2126
2140
  function useArtistPanel({
2127
2141
  uuid,
2128
2142
  scope,
@@ -2144,25 +2158,25 @@ function useArtistPanel({
2144
2158
  ...panel,
2145
2159
  title: "Artist",
2146
2160
  content: /* @__PURE__ */ jsxs24("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2147
- /* @__PURE__ */ jsx35(Skeleton5, { className: "h-3 w-16" }),
2148
- /* @__PURE__ */ jsx35(Skeleton5, { className: "h-4 w-32" }),
2149
- /* @__PURE__ */ jsx35(Skeleton5, { className: "h-3 w-16" }),
2150
- /* @__PURE__ */ jsx35(Skeleton5, { className: "h-4 w-24" }),
2151
- /* @__PURE__ */ jsx35(Skeleton5, { className: "h-16 mt-2.5" }),
2152
- /* @__PURE__ */ jsx35(Skeleton5, { className: "h-16" })
2161
+ /* @__PURE__ */ jsx36(Skeleton5, { className: "h-3 w-16" }),
2162
+ /* @__PURE__ */ jsx36(Skeleton5, { className: "h-4 w-32" }),
2163
+ /* @__PURE__ */ jsx36(Skeleton5, { className: "h-3 w-16" }),
2164
+ /* @__PURE__ */ jsx36(Skeleton5, { className: "h-4 w-24" }),
2165
+ /* @__PURE__ */ jsx36(Skeleton5, { className: "h-16 mt-2.5" }),
2166
+ /* @__PURE__ */ jsx36(Skeleton5, { className: "h-16" })
2153
2167
  ] })
2154
2168
  };
2155
2169
  case "not-found":
2156
2170
  return {
2157
2171
  ...panel,
2158
2172
  title: "Artist not found",
2159
- content: /* @__PURE__ */ jsx35("p", { className: "text-[var(--color-text-muted)]", children: "This artist does not exist." })
2173
+ content: /* @__PURE__ */ jsx36("p", { className: "text-[var(--color-text-muted)]", children: "This artist does not exist." })
2160
2174
  };
2161
2175
  case "error":
2162
2176
  return {
2163
2177
  ...panel,
2164
2178
  title: "Artist",
2165
- content: /* @__PURE__ */ jsx35("p", { className: "text-red-600", children: "Could not load this artist." })
2179
+ content: /* @__PURE__ */ jsx36("p", { className: "text-red-600", children: "Could not load this artist." })
2166
2180
  };
2167
2181
  case "success": {
2168
2182
  const { artist } = result;
@@ -2172,18 +2186,18 @@ function useArtistPanel({
2172
2186
  ...panel,
2173
2187
  title: artist.name,
2174
2188
  content: /* @__PURE__ */ jsxs24(Fragment13, { children: [
2175
- /* @__PURE__ */ jsx35(
2189
+ /* @__PURE__ */ jsx36(
2176
2190
  ArtistActionBand,
2177
2191
  {
2178
2192
  artist,
2179
- share: shareTarget && shareUrl ? /* @__PURE__ */ jsx35(ShareActionButton, { target: shareTarget, url: shareUrl }) : void 0
2193
+ share: shareTarget && shareUrl ? /* @__PURE__ */ jsx36(ShareActionButton, { target: shareTarget, url: shareUrl }) : void 0
2180
2194
  }
2181
2195
  ),
2182
- artist.image && /* @__PURE__ */ jsx35(ArtistPhoto, { image: artist.image, name: artist.name }),
2183
- /* @__PURE__ */ jsx35(ArtistHeader, { artist }),
2184
- /* @__PURE__ */ jsx35(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2185
- /* @__PURE__ */ jsx35(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2186
- artist.tracksCount > 0 && /* @__PURE__ */ jsx35(
2196
+ artist.image && /* @__PURE__ */ jsx36(ArtistPhoto, { image: artist.image, name: artist.name }),
2197
+ /* @__PURE__ */ jsx36(ArtistHeader, { artist }),
2198
+ /* @__PURE__ */ jsx36(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2199
+ /* @__PURE__ */ jsx36(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2200
+ artist.tracksCount > 0 && /* @__PURE__ */ jsx36(
2187
2201
  ArtistSongs,
2188
2202
  {
2189
2203
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2193,7 +2207,7 @@ function useArtistPanel({
2193
2207
  }
2194
2208
  )
2195
2209
  ] }),
2196
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx35(ArtistDiscography, { albums: artist.albums }) : void 0
2210
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx36(ArtistDiscography, { albums: artist.albums }) : void 0
2197
2211
  };
2198
2212
  }
2199
2213
  }
@@ -2207,7 +2221,7 @@ import {
2207
2221
 
2208
2222
  // src/genre-tree/song/SongPanelContent.tsx
2209
2223
  import { useState as useState9 } from "react";
2210
- import { Pause as Pause3, Play as Play4 } from "lucide-react";
2224
+ import { Pause as Pause3, Play as Play3 } from "lucide-react";
2211
2225
  import { ActionBand as ActionBand3, ActionButton as ActionButton5, FieldList as FieldList2, PANEL_LABEL_CLASS, PanelBleed as PanelBleed3, PanelSection as PanelSection6, Skeleton as Skeleton6 } from "@behindthemusictree/ui";
2212
2226
  import { PlayStates as PlayStates3, usePlayer as usePlayer10 } from "@behindthemusictree/app-kit/player";
2213
2227
  import {
@@ -2217,7 +2231,7 @@ import {
2217
2231
  useSongHistory,
2218
2232
  useTrackList as useTrackList6
2219
2233
  } from "@behindthemusictree/app-kit/genre-tree";
2220
- import { Fragment as Fragment14, jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
2234
+ import { Fragment as Fragment14, jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
2221
2235
  var numberFormat = new Intl.NumberFormat("en");
2222
2236
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2223
2237
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
@@ -2225,11 +2239,11 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2225
2239
  const [open, setOpen] = useState9(false);
2226
2240
  const history = useSongHistory(uuid, getBackendBaseUrl, open);
2227
2241
  let body = null;
2228
- if (history.status === "loading") body = /* @__PURE__ */ jsx36(Skeleton6, { className: "h-10", "aria-busy": "true" });
2229
- else if (history.status === "error") body = /* @__PURE__ */ jsx36("p", { className: "text-red-600", children: "Could not load the history." });
2242
+ if (history.status === "loading") body = /* @__PURE__ */ jsx37(Skeleton6, { className: "h-10", "aria-busy": "true" });
2243
+ else if (history.status === "error") body = /* @__PURE__ */ jsx37("p", { className: "text-red-600", children: "Could not load the history." });
2230
2244
  else if (history.status === "success")
2231
- body = history.entries.length === 0 ? /* @__PURE__ */ jsx36("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx36("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs25("li", { className: "py-1.5 text-body", children: [
2232
- /* @__PURE__ */ jsx36("span", { className: "font-medium", children: entry.action }),
2245
+ body = history.entries.length === 0 ? /* @__PURE__ */ jsx37("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx37("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs25("li", { className: "py-1.5 text-body", children: [
2246
+ /* @__PURE__ */ jsx37("span", { className: "font-medium", children: entry.action }),
2233
2247
  entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs25("span", { className: "text-[var(--color-text-muted)]", children: [
2234
2248
  " ",
2235
2249
  "\u2014 ",
@@ -2243,9 +2257,9 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2243
2257
  dateFormat.format(new Date(entry.createdOn))
2244
2258
  ] })
2245
2259
  ] }, entry.uuid)) });
2246
- return /* @__PURE__ */ jsx36(PanelBleed3, { padded: true, className: "border-t border-[var(--color-border)]", children: /* @__PURE__ */ jsxs25("details", { onToggle: (event) => setOpen(event.currentTarget.open), className: "pb-2.5", children: [
2247
- /* @__PURE__ */ jsx36("summary", { className: `mt-2.5 cursor-pointer ${PANEL_LABEL_CLASS}`, children: "History" }),
2248
- /* @__PURE__ */ jsx36("div", { className: "mt-1.5", children: body })
2260
+ return /* @__PURE__ */ jsx37(PanelBleed3, { padded: true, className: "border-t border-[var(--color-border)]", children: /* @__PURE__ */ jsxs25("details", { onToggle: (event) => setOpen(event.currentTarget.open), className: "pb-2.5", children: [
2261
+ /* @__PURE__ */ jsx37("summary", { className: `mt-2.5 cursor-pointer ${PANEL_LABEL_CLASS}`, children: "History" }),
2262
+ /* @__PURE__ */ jsx37("div", { className: "mt-1.5", children: body })
2249
2263
  ] }) });
2250
2264
  }
2251
2265
  function SongPanelContent(props) {
@@ -2264,13 +2278,13 @@ function SongPanelContent(props) {
2264
2278
  const genre = song.genre;
2265
2279
  fields.push({
2266
2280
  label: "Genre",
2267
- value: onSelectGenre ? /* @__PURE__ */ jsx36("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ jsx36("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs25("span", { children: [
2268
- index > 0 && /* @__PURE__ */ jsx36("span", { "aria-hidden": "true", children: "\u203A " }),
2269
- /* @__PURE__ */ jsx36("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2281
+ value: onSelectGenre ? /* @__PURE__ */ jsx37("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ jsx37("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs25("span", { children: [
2282
+ index > 0 && /* @__PURE__ */ jsx37("span", { "aria-hidden": "true", children: "\u203A " }),
2283
+ /* @__PURE__ */ jsx37("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2270
2284
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2271
2285
  });
2272
2286
  } else {
2273
- fields.push({ label: "Genre", value: /* @__PURE__ */ jsx36("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2287
+ fields.push({ label: "Genre", value: /* @__PURE__ */ jsx37("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2274
2288
  }
2275
2289
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2276
2290
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2280,11 +2294,11 @@ function SongPanelContent(props) {
2280
2294
  const links = [];
2281
2295
  if (musicBrainzUrl)
2282
2296
  links.push(
2283
- /* @__PURE__ */ jsx36("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2297
+ /* @__PURE__ */ jsx37("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2284
2298
  );
2285
2299
  if (song.youtubeVideo)
2286
2300
  links.push(
2287
- /* @__PURE__ */ jsx36(
2301
+ /* @__PURE__ */ jsx37(
2288
2302
  "a",
2289
2303
  {
2290
2304
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2299,31 +2313,31 @@ function SongPanelContent(props) {
2299
2313
  if (links.length > 0)
2300
2314
  fields.push({
2301
2315
  label: "Links",
2302
- value: /* @__PURE__ */ jsx36("span", { className: "flex gap-3", children: links })
2316
+ value: /* @__PURE__ */ jsx37("span", { className: "flex gap-3", children: links })
2303
2317
  });
2304
2318
  return /* @__PURE__ */ jsxs25(Fragment14, { children: [
2305
- /* @__PURE__ */ jsx36(PanelBleed3, { children: /* @__PURE__ */ jsxs25(ActionBand3, { children: [
2306
- /* @__PURE__ */ jsx36(
2319
+ /* @__PURE__ */ jsx37(PanelBleed3, { children: /* @__PURE__ */ jsxs25(ActionBand3, { children: [
2320
+ /* @__PURE__ */ jsx37(
2307
2321
  ActionButton5,
2308
2322
  {
2309
- icon: isPlaying ? /* @__PURE__ */ jsx36(Pause3, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx36(Play4, { "aria-hidden": "true", className: "w-5 h-5" }),
2323
+ icon: isPlaying ? /* @__PURE__ */ jsx37(Pause3, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx37(Play3, { "aria-hidden": "true", className: "w-5 h-5" }),
2310
2324
  label: isPlaying ? "Pause" : "Play",
2311
2325
  disabled: !!unplayableReason,
2312
2326
  title: unplayableReason ?? void 0,
2313
2327
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope)
2314
2328
  }
2315
2329
  ),
2316
- shareUrl && /* @__PURE__ */ jsx36(ShareActionButton, { target: shareTarget, url: shareUrl })
2330
+ shareUrl && /* @__PURE__ */ jsx37(ShareActionButton, { target: shareTarget, url: shareUrl })
2317
2331
  ] }) }),
2318
- song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx36("p", { className: "text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx36(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
2319
- /* @__PURE__ */ jsx36(FieldList2, { items: fields }),
2320
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx36(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx36("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx36("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2321
- /* @__PURE__ */ jsx36(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2332
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx37("p", { className: "text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx37(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
2333
+ /* @__PURE__ */ jsx37(FieldList2, { items: fields }),
2334
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx37(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx37("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx37("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2335
+ /* @__PURE__ */ jsx37(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2322
2336
  ] });
2323
2337
  }
2324
2338
 
2325
2339
  // src/genre-tree/song/useSongPanel.tsx
2326
- import { jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
2340
+ import { jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
2327
2341
  function useSongPanel({
2328
2342
  uuid,
2329
2343
  scope,
@@ -2345,23 +2359,23 @@ function useSongPanel({
2345
2359
  ...panel,
2346
2360
  title: "Song",
2347
2361
  content: /* @__PURE__ */ jsxs26("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2348
- /* @__PURE__ */ jsx37(Skeleton7, { className: "h-3 w-16" }),
2349
- /* @__PURE__ */ jsx37(Skeleton7, { className: "h-4 w-32" }),
2350
- /* @__PURE__ */ jsx37(Skeleton7, { className: "h-3 w-16" }),
2351
- /* @__PURE__ */ jsx37(Skeleton7, { className: "h-4 w-24" })
2362
+ /* @__PURE__ */ jsx38(Skeleton7, { className: "h-3 w-16" }),
2363
+ /* @__PURE__ */ jsx38(Skeleton7, { className: "h-4 w-32" }),
2364
+ /* @__PURE__ */ jsx38(Skeleton7, { className: "h-3 w-16" }),
2365
+ /* @__PURE__ */ jsx38(Skeleton7, { className: "h-4 w-24" })
2352
2366
  ] })
2353
2367
  };
2354
2368
  case "not-found":
2355
2369
  return {
2356
2370
  ...panel,
2357
2371
  title: "Song not found",
2358
- content: /* @__PURE__ */ jsx37("p", { className: "text-[var(--color-text-muted)]", children: "This song does not exist." })
2372
+ content: /* @__PURE__ */ jsx38("p", { className: "text-[var(--color-text-muted)]", children: "This song does not exist." })
2359
2373
  };
2360
2374
  case "error":
2361
2375
  return {
2362
2376
  ...panel,
2363
2377
  title: "Song",
2364
- content: /* @__PURE__ */ jsx37("p", { className: "text-red-600", children: "Could not load this song." })
2378
+ content: /* @__PURE__ */ jsx38("p", { className: "text-red-600", children: "Could not load this song." })
2365
2379
  };
2366
2380
  case "success": {
2367
2381
  const { song } = result;
@@ -2369,7 +2383,7 @@ function useSongPanel({
2369
2383
  return {
2370
2384
  ...panel,
2371
2385
  title: song.title,
2372
- content: /* @__PURE__ */ jsx37(
2386
+ content: /* @__PURE__ */ jsx38(
2373
2387
  SongPanelContent,
2374
2388
  {
2375
2389
  song,
@@ -2422,12 +2436,12 @@ function useEntityPanel({
2422
2436
  // src/genre-tree/SizeMetricToggle.tsx
2423
2437
  import { MdMusicNote, MdVisibility } from "react-icons/md";
2424
2438
  import { SegmentedControl } from "@behindthemusictree/ui";
2425
- import { jsx as jsx38 } from "react/jsx-runtime";
2439
+ import { jsx as jsx39 } from "react/jsx-runtime";
2426
2440
  function SizeMetricToggle({
2427
2441
  value,
2428
2442
  onChange
2429
2443
  }) {
2430
- return /* @__PURE__ */ jsx38(
2444
+ return /* @__PURE__ */ jsx39(
2431
2445
  SegmentedControl,
2432
2446
  {
2433
2447
  variant: "floating",
@@ -2435,15 +2449,15 @@ function SizeMetricToggle({
2435
2449
  value,
2436
2450
  onChange,
2437
2451
  options: [
2438
- { value: "songs", label: /* @__PURE__ */ jsx38(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2439
- { value: "views", label: /* @__PURE__ */ jsx38(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2452
+ { value: "songs", label: /* @__PURE__ */ jsx39(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2453
+ { value: "views", label: /* @__PURE__ */ jsx39(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2440
2454
  ]
2441
2455
  }
2442
2456
  );
2443
2457
  }
2444
2458
 
2445
2459
  // src/genre-tree/GenreTreeView.tsx
2446
- import { Fragment as Fragment15, jsx as jsx39, jsxs as jsxs27 } from "react/jsx-runtime";
2460
+ import { Fragment as Fragment15, jsx as jsx40, jsxs as jsxs27 } from "react/jsx-runtime";
2447
2461
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2448
2462
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2449
2463
  var noop = () => {
@@ -2595,14 +2609,14 @@ function GenreTreeView({
2595
2609
  const openSongRef = useRef3(openSong);
2596
2610
  openSongRef.current = openSong;
2597
2611
  const resolvedRenderSongLink = useCallback6(
2598
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx39("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2612
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx40("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2599
2613
  [renderSongLink]
2600
2614
  );
2601
2615
  const [expandedSongUuid, setExpandedSongUuid] = useState10(null);
2602
2616
  const [expandedArtistUuid, setExpandedArtistUuid] = useState10(null);
2603
2617
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2604
2618
  const songBaseUrl = getBackendBaseUrl;
2605
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx39("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2619
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx40("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2606
2620
  const getGenrePath = (genre) => {
2607
2621
  const path = [];
2608
2622
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2678,9 +2692,11 @@ function GenreTreeView({
2678
2692
  !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2679
2693
  resolveStyle
2680
2694
  );
2681
- const [selectionHistory, setSelectionHistory] = useState10({ current: selectedGenreUuid, previous: null });
2682
- if (selectionHistory.current !== selectedGenreUuid) {
2683
- setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
2695
+ const treeStyles = useMemo8(() => toGenreNodeStyles(relations, visibleGenrePlaylists), [relations, visibleGenrePlaylists]);
2696
+ const validSelectedGenreUuid = toGenreUuid(selectedGenreUuid);
2697
+ const [selectionHistory, setSelectionHistory] = useState10({ current: validSelectedGenreUuid, previous: null });
2698
+ if (selectionHistory.current !== validSelectedGenreUuid) {
2699
+ setSelectionHistory({ current: validSelectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
2684
2700
  }
2685
2701
  const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
2686
2702
  const { showPopup, hidePopup } = usePopup4();
@@ -2712,7 +2728,7 @@ function GenreTreeView({
2712
2728
  const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2713
2729
  const side = criteria?.side ?? "core";
2714
2730
  const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
2715
- const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
2731
+ const shareTarget = validSelectedGenreUuid ? { kind: "genre", genreUuid: validSelectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
2716
2732
  const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
2717
2733
  const genreContent = useGenreDetailContent({
2718
2734
  genreUuid: selectedGenreUuid,
@@ -2723,7 +2739,7 @@ function GenreTreeView({
2723
2739
  selectGenre(uuid);
2724
2740
  },
2725
2741
  expanded: isExpanded,
2726
- treeRelations: relations,
2742
+ treeStyles,
2727
2743
  playback,
2728
2744
  genrePlaylist: selectedGenrePlaylist,
2729
2745
  band: {
@@ -2734,7 +2750,7 @@ function GenreTreeView({
2734
2750
  onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
2735
2751
  onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
2736
2752
  onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
2737
- /* @__PURE__ */ jsx39(
2753
+ /* @__PURE__ */ jsx40(
2738
2754
  ChangeParentPopup,
2739
2755
  {
2740
2756
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -2778,7 +2794,7 @@ function GenreTreeView({
2778
2794
  onClose: otherPanel.onClose,
2779
2795
  searchName: otherPanel.title
2780
2796
  };
2781
- } else if (selectedGenreUuid) {
2797
+ } else if (selectedGenreUuid !== null) {
2782
2798
  activePanel = {
2783
2799
  title: genreContent.title,
2784
2800
  content: genreContent.content,
@@ -2795,19 +2811,19 @@ function GenreTreeView({
2795
2811
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2796
2812
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2797
2813
  } : void 0;
2798
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx39(
2814
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx40(
2799
2815
  IconButton2,
2800
2816
  {
2801
2817
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2802
2818
  style: activePanel.headerAccent && { color: "inherit" },
2803
2819
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2804
- children: activePanel.expanded ? /* @__PURE__ */ jsx39(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx39(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2820
+ children: activePanel.expanded ? /* @__PURE__ */ jsx40(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx40(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2805
2821
  }
2806
2822
  ) : null;
2807
2823
  const renderArtistFilter = () => {
2808
2824
  if (isLoading || !filterArtist) return null;
2809
2825
  return /* @__PURE__ */ jsxs27("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-body", children: [
2810
- /* @__PURE__ */ jsx39(
2826
+ /* @__PURE__ */ jsx40(
2811
2827
  "button",
2812
2828
  {
2813
2829
  type: "button",
@@ -2819,7 +2835,7 @@ function GenreTreeView({
2819
2835
  children: filterArtist.name
2820
2836
  }
2821
2837
  ),
2822
- /* @__PURE__ */ jsx39(
2838
+ /* @__PURE__ */ jsx40(
2823
2839
  "button",
2824
2840
  {
2825
2841
  type: "button",
@@ -2832,7 +2848,7 @@ function GenreTreeView({
2832
2848
  ] });
2833
2849
  };
2834
2850
  const actions = /* @__PURE__ */ jsxs27(Fragment15, { children: [
2835
- !isLoading && !isError && /* @__PURE__ */ jsx39(
2851
+ !isLoading && !isError && /* @__PURE__ */ jsx40(
2836
2852
  GenreTreeSearch,
2837
2853
  {
2838
2854
  artistBaseUrl,
@@ -2865,9 +2881,9 @@ function GenreTreeView({
2865
2881
  extraGroups: extraSearchGroups
2866
2882
  }
2867
2883
  ),
2868
- !isLoading && !isError && filterArtist && /* @__PURE__ */ jsx39("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2884
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ jsx40("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2869
2885
  /* @__PURE__ */ jsxs27("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", children: [
2870
- !isLoading && !isError && !isControlled && /* @__PURE__ */ jsx39(
2886
+ !isLoading && !isError && !isControlled && /* @__PURE__ */ jsx40(
2871
2887
  SegmentedControl2,
2872
2888
  {
2873
2889
  "aria-label": "Tree view mode",
@@ -2881,13 +2897,13 @@ function GenreTreeView({
2881
2897
  ]
2882
2898
  }
2883
2899
  ),
2884
- !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ jsx39(
2900
+ !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ jsx40(
2885
2901
  "div",
2886
2902
  {
2887
2903
  className: "flex items-center gap-1",
2888
2904
  role: "group",
2889
2905
  "aria-label": "Tree display options",
2890
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx39(
2906
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx40(
2891
2907
  Button,
2892
2908
  {
2893
2909
  variant: allowWheelRotation ? "default" : "outline",
@@ -2898,7 +2914,7 @@ function GenreTreeView({
2898
2914
  )
2899
2915
  }
2900
2916
  ),
2901
- !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ jsx39(
2917
+ !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ jsx40(
2902
2918
  IconTextButton,
2903
2919
  {
2904
2920
  icon: Plus2,
@@ -2914,32 +2930,32 @@ function GenreTreeView({
2914
2930
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2915
2931
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2916
2932
  children: [
2917
- /* @__PURE__ */ jsx39(
2933
+ /* @__PURE__ */ jsx40(
2918
2934
  "div",
2919
2935
  {
2920
2936
  className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] md:left-[var(--layout-panel-inset)] md:top-[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" : "left-5 top-5"}`,
2921
2937
  children: actions
2922
2938
  }
2923
2939
  ),
2924
- !isLoading && !isError && filterArtist && /* @__PURE__ */ jsx39(
2940
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ jsx40(
2925
2941
  "div",
2926
2942
  {
2927
2943
  className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] md:top-[var(--layout-panel-inset)]" : "top-5"}`,
2928
2944
  children: renderArtistFilter()
2929
2945
  }
2930
2946
  ),
2931
- /* @__PURE__ */ jsx39("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs27("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2932
- !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx39("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2933
- isLoading ? /* @__PURE__ */ jsx39(GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ jsxs27("p", { role: "alert", className: "m-auto text-center", children: [
2947
+ /* @__PURE__ */ jsx40("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs27("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2948
+ !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx40("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2949
+ isLoading ? /* @__PURE__ */ jsx40(GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ jsxs27("p", { role: "alert", className: "m-auto text-center", children: [
2934
2950
  "Couldn't load the genre tree.",
2935
2951
  " ",
2936
- /* @__PURE__ */ jsx39("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
2952
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
2937
2953
  ] }) : viewMode === "wheel" ? (
2938
2954
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2939
2955
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2940
2956
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2941
2957
  // The panel's own Zoom in button is hidden: the search bar carries it.
2942
- /* @__PURE__ */ jsx39("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx39(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx39(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx39(
2958
+ /* @__PURE__ */ jsx40("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx40(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx40(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx40(
2943
2959
  GenrePlaylistTreeWheel,
2944
2960
  {
2945
2961
  scope,
@@ -2959,7 +2975,7 @@ function GenreTreeView({
2959
2975
  busy,
2960
2976
  floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2", children: [
2961
2977
  floatingControlsLeading,
2962
- canSizeByViews && /* @__PURE__ */ jsx39(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2978
+ canSizeByViews && /* @__PURE__ */ jsx40(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2963
2979
  ] }) : void 0,
2964
2980
  defaultViewport,
2965
2981
  viewportRestore,
@@ -2968,7 +2984,7 @@ function GenreTreeView({
2968
2984
  ) }) })
2969
2985
  ) : viewMode === "pop-core" ? (
2970
2986
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2971
- /* @__PURE__ */ jsx39("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx39(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx39(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx39(
2987
+ /* @__PURE__ */ jsx40("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx40(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx40(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx40(
2972
2988
  GenrePlaylistTreeWheelRadialPopCore,
2973
2989
  {
2974
2990
  scope,
@@ -2988,7 +3004,7 @@ function GenreTreeView({
2988
3004
  busy,
2989
3005
  floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2", children: [
2990
3006
  floatingControlsLeading,
2991
- canSizeByViews && /* @__PURE__ */ jsx39(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3007
+ canSizeByViews && /* @__PURE__ */ jsx40(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2992
3008
  ] }) : void 0,
2993
3009
  defaultViewport,
2994
3010
  viewportRestore,
@@ -2996,7 +3012,7 @@ function GenreTreeView({
2996
3012
  ...focus
2997
3013
  }
2998
3014
  ) }) })
2999
- ) : viewMode === "outline" ? /* @__PURE__ */ jsx39("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx39(
3015
+ ) : viewMode === "outline" ? /* @__PURE__ */ jsx40("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx40(
3000
3016
  GenrePlaylistTreeWheelRadialPopCore,
3001
3017
  {
3002
3018
  outline: true,
@@ -3013,13 +3029,13 @@ function GenreTreeView({
3013
3029
  selectedNodeId,
3014
3030
  readOnly
3015
3031
  }
3016
- ) }) : /* @__PURE__ */ jsx39("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3032
+ ) }) : /* @__PURE__ */ jsx40("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3017
3033
  ([uuid, genrePlaylistTreePerRoot]) => {
3018
- return /* @__PURE__ */ jsx39(
3034
+ return /* @__PURE__ */ jsx40(
3019
3035
  "div",
3020
3036
  {
3021
3037
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
3022
- children: /* @__PURE__ */ jsx39("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx39(
3038
+ children: /* @__PURE__ */ jsx40("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx40(
3023
3039
  GenrePlaylistTreePerRoot,
3024
3040
  {
3025
3041
  scope,
@@ -3046,7 +3062,7 @@ function GenreTreeView({
3046
3062
  );
3047
3063
  }
3048
3064
  ) }),
3049
- /* @__PURE__ */ jsx39(
3065
+ /* @__PURE__ */ jsx40(
3050
3066
  DetailPanel2,
3051
3067
  {
3052
3068
  ref: panelRef,
@@ -3087,7 +3103,7 @@ import { ListMusic } from "lucide-react";
3087
3103
  import { useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
3088
3104
  import { TrackListOriginType as TrackListOriginType3 } from "@behindthemusictree/app-kit/genre-tree";
3089
3105
  import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
3090
- import { jsx as jsx40, jsxs as jsxs28 } from "react/jsx-runtime";
3106
+ import { jsx as jsx41, jsxs as jsxs28 } from "react/jsx-runtime";
3091
3107
  var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect7 : useLayoutEffect2;
3092
3108
  function TrackListSidebar({
3093
3109
  className: className2 = "",
@@ -3199,15 +3215,15 @@ function TrackListSidebar({
3199
3215
  /* @__PURE__ */ jsxs28("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3200
3216
  /* @__PURE__ */ jsxs28("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3201
3217
  /* @__PURE__ */ jsxs28("div", { className: "origin flex text-xl", children: [
3202
- /* @__PURE__ */ jsx40("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3203
- /* @__PURE__ */ jsx40("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ jsx40("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3218
+ /* @__PURE__ */ jsx41("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3219
+ /* @__PURE__ */ jsx41("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ jsx41("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3204
3220
  ] }),
3205
3221
  /* @__PURE__ */ jsxs28("div", { className: "info flex items-center text-body text-[var(--color-text-muted)]", children: [
3206
3222
  trackList.origin.type === TrackListOriginType3.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType3.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3207
3223
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3208
3224
  ] })
3209
3225
  ] }),
3210
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx40(
3226
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx41(
3211
3227
  "button",
3212
3228
  {
3213
3229
  type: "button",
@@ -3216,7 +3232,7 @@ function TrackListSidebar({
3216
3232
  "aria-expanded": !isCollapsed,
3217
3233
  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"}`,
3218
3234
  onClick: isCollapsed ? onExpand : onMinimize,
3219
- children: /* @__PURE__ */ jsx40(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3235
+ children: /* @__PURE__ */ jsx41(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3220
3236
  }
3221
3237
  )
3222
3238
  ] }),
@@ -3230,7 +3246,7 @@ function TrackListSidebar({
3230
3246
  onPointerDown: markUserScrollIntent,
3231
3247
  className: layout === "inline" ? "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0" : "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0",
3232
3248
  children: [
3233
- trackList.tracks.map((track, index) => /* @__PURE__ */ jsx40("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx40(
3249
+ trackList.tracks.map((track, index) => /* @__PURE__ */ jsx41("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx41(
3234
3250
  TrackItem,
3235
3251
  {
3236
3252
  track,
@@ -3241,7 +3257,7 @@ function TrackListSidebar({
3241
3257
  renderSongLink
3242
3258
  }
3243
3259
  ) }, track.uuid)),
3244
- hasMore && /* @__PURE__ */ jsx40("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3260
+ hasMore && /* @__PURE__ */ jsx41("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3245
3261
  ]
3246
3262
  }
3247
3263
  )
@@ -3263,10 +3279,10 @@ import {
3263
3279
  import { PanelEdgeTab } from "@behindthemusictree/ui";
3264
3280
 
3265
3281
  // src/genre-tree/PlayerControls.tsx
3266
- import { Pause as Pause4, Play as Play5, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
3282
+ import { Pause as Pause4, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
3267
3283
  import { formatTime, useTrackList as useTrackList8 } from "@behindthemusictree/app-kit/genre-tree";
3268
3284
  import { PlayStates as PlayStates4, useCurrentTime, usePlayer as usePlayer11 } from "@behindthemusictree/app-kit/player";
3269
- import { jsx as jsx41, jsxs as jsxs29 } from "react/jsx-runtime";
3285
+ import { jsx as jsx42, jsxs as jsxs29 } from "react/jsx-runtime";
3270
3286
  function PlayerControls() {
3271
3287
  const {
3272
3288
  duration,
@@ -3292,8 +3308,8 @@ function PlayerControls() {
3292
3308
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3293
3309
  children: [
3294
3310
  /* @__PURE__ */ jsxs29("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3295
- /* @__PURE__ */ jsx41("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3296
- /* @__PURE__ */ jsx41(
3311
+ /* @__PURE__ */ jsx42("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
3312
+ /* @__PURE__ */ jsx42(
3297
3313
  "input",
3298
3314
  {
3299
3315
  "aria-label": "Seek",
@@ -3307,11 +3323,11 @@ function PlayerControls() {
3307
3323
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3308
3324
  }
3309
3325
  ),
3310
- /* @__PURE__ */ jsx41("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3326
+ /* @__PURE__ */ jsx42("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
3311
3327
  ] }),
3312
3328
  /* @__PURE__ */ jsxs29("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3313
3329
  /* @__PURE__ */ jsxs29("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3314
- /* @__PURE__ */ jsx41(
3330
+ /* @__PURE__ */ jsx42(
3315
3331
  "button",
3316
3332
  {
3317
3333
  type: "button",
@@ -3320,10 +3336,10 @@ function PlayerControls() {
3320
3336
  disabled: !isReady || !hasPreviousTrack,
3321
3337
  onClick: playPreviousTrack,
3322
3338
  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",
3323
- children: /* @__PURE__ */ jsx41(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3339
+ children: /* @__PURE__ */ jsx42(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3324
3340
  }
3325
3341
  ),
3326
- /* @__PURE__ */ jsx41(
3342
+ /* @__PURE__ */ jsx42(
3327
3343
  "button",
3328
3344
  {
3329
3345
  type: "button",
@@ -3332,10 +3348,10 @@ function PlayerControls() {
3332
3348
  disabled: !isReady,
3333
3349
  onClick: handlePlayPauseAction,
3334
3350
  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",
3335
- children: isPlaying ? /* @__PURE__ */ jsx41(Pause4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx41(Play5, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3351
+ children: isPlaying ? /* @__PURE__ */ jsx42(Pause4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx42(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3336
3352
  }
3337
3353
  ),
3338
- /* @__PURE__ */ jsx41(
3354
+ /* @__PURE__ */ jsx42(
3339
3355
  "button",
3340
3356
  {
3341
3357
  type: "button",
@@ -3344,14 +3360,14 @@ function PlayerControls() {
3344
3360
  disabled: !isReady || !hasNextTrack,
3345
3361
  onClick: playNextTrack,
3346
3362
  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",
3347
- children: /* @__PURE__ */ jsx41(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3363
+ children: /* @__PURE__ */ jsx42(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3348
3364
  }
3349
3365
  )
3350
3366
  ] }),
3351
3367
  /* @__PURE__ */ jsxs29("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]", children: [
3352
- volume === 0 ? /* @__PURE__ */ jsx41(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx41(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3353
- /* @__PURE__ */ jsx41("span", { className: "sr-only", children: "Volume" }),
3354
- /* @__PURE__ */ jsx41(
3368
+ volume === 0 ? /* @__PURE__ */ jsx42(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx42(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3369
+ /* @__PURE__ */ jsx42("span", { className: "sr-only", children: "Volume" }),
3370
+ /* @__PURE__ */ jsx42(
3355
3371
  "input",
3356
3372
  {
3357
3373
  "aria-label": "Volume",
@@ -3373,7 +3389,7 @@ function PlayerControls() {
3373
3389
  }
3374
3390
 
3375
3391
  // src/genre-tree/PlayerPanel.tsx
3376
- import { jsx as jsx42, jsxs as jsxs30 } from "react/jsx-runtime";
3392
+ import { jsx as jsx43, jsxs as jsxs30 } from "react/jsx-runtime";
3377
3393
  var PANEL_ID = "player-panel";
3378
3394
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3379
3395
  function PlayerPanel({
@@ -3426,7 +3442,7 @@ function PlayerPanel({
3426
3442
  ),
3427
3443
  style: isExtended ? { width: extendedWidth } : void 0,
3428
3444
  children: [
3429
- /* @__PURE__ */ jsx42(
3445
+ /* @__PURE__ */ jsx43(
3430
3446
  PanelEdgeTab,
3431
3447
  {
3432
3448
  side: "right",
@@ -3445,19 +3461,19 @@ function PlayerPanel({
3445
3461
  ),
3446
3462
  children: [
3447
3463
  /* @__PURE__ */ jsxs30("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3448
- /* @__PURE__ */ jsx42("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx42("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3464
+ /* @__PURE__ */ jsx43("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx43("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3449
3465
  /* @__PURE__ */ jsxs30("div", { className: "relative w-full shrink-0", children: [
3450
- /* @__PURE__ */ jsx42(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3466
+ /* @__PURE__ */ jsx43(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3451
3467
  playerTrackObject?.loadError && /* @__PURE__ */ jsxs30("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3452
3468
  playerTrackObject.loadError,
3453
- /* @__PURE__ */ jsx42("button", { type: "button", className: "ml-2 underline", onClick: () => {
3469
+ /* @__PURE__ */ jsx43("button", { type: "button", className: "ml-2 underline", onClick: () => {
3454
3470
  void retryPlayback();
3455
3471
  }, children: "Retry" })
3456
3472
  ] })
3457
3473
  ] }),
3458
- /* @__PURE__ */ jsx42(PlayerControls, {})
3474
+ /* @__PURE__ */ jsx43(PlayerControls, {})
3459
3475
  ] }),
3460
- /* @__PURE__ */ jsx42(
3476
+ /* @__PURE__ */ jsx43(
3461
3477
  TrackListSidebar,
3462
3478
  {
3463
3479
  layout: "inline",
@@ -3484,11 +3500,11 @@ function PlayerPanel({
3484
3500
  import { v4 as uuidv4 } from "uuid";
3485
3501
  import { AiFillStar, AiOutlineStar } from "react-icons/ai";
3486
3502
  import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
3487
- import { jsx as jsx43, jsxs as jsxs31 } from "react/jsx-runtime";
3503
+ import { jsx as jsx44, jsxs as jsxs31 } from "react/jsx-runtime";
3488
3504
  function Rating({ rating, handleChange }) {
3489
3505
  const instanceId = uuidv4();
3490
- return /* @__PURE__ */ jsx43("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs31("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3491
- /* @__PURE__ */ jsx43(
3506
+ return /* @__PURE__ */ jsx44("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs31("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3507
+ /* @__PURE__ */ jsx44(
3492
3508
  "input",
3493
3509
  {
3494
3510
  id: `star-${instanceId}-${i}`,
@@ -3500,16 +3516,16 @@ function Rating({ rating, handleChange }) {
3500
3516
  onChange: handleChange
3501
3517
  }
3502
3518
  ),
3503
- /* @__PURE__ */ jsx43("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs31("div", { className: "flex", children: [
3504
- /* @__PURE__ */ jsx43(
3519
+ /* @__PURE__ */ jsx44("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs31("div", { className: "flex", children: [
3520
+ /* @__PURE__ */ jsx44(
3505
3521
  AiOutlineStar,
3506
3522
  {
3507
3523
  className: `w-7 h-7 absolute top-0 left-0 ${rating === FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
3508
3524
  }
3509
3525
  ),
3510
- /* @__PURE__ */ jsx43("span", { className: "flex-grow w-8" }),
3526
+ /* @__PURE__ */ jsx44("span", { className: "flex-grow w-8" }),
3511
3527
  " "
3512
- ] }) : /* @__PURE__ */ jsx43(
3528
+ ] }) : /* @__PURE__ */ jsx44(
3513
3529
  AiFillStar,
3514
3530
  {
3515
3531
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -3521,7 +3537,7 @@ function Rating({ rating, handleChange }) {
3521
3537
  // src/genre-tree/EntityDetailPanel.tsx
3522
3538
  import { useState as useState12 } from "react";
3523
3539
  import { DetailPanel as DetailPanel3 } from "@behindthemusictree/ui";
3524
- import { Fragment as Fragment16, jsx as jsx44, jsxs as jsxs32 } from "react/jsx-runtime";
3540
+ import { Fragment as Fragment16, jsx as jsx45, jsxs as jsxs32 } from "react/jsx-runtime";
3525
3541
  function EntityDetailPanel({
3526
3542
  scope,
3527
3543
  getBackendBaseUrl,
@@ -3544,8 +3560,8 @@ function EntityDetailPanel({
3544
3560
  getBackendBaseUrl,
3545
3561
  getArtistBackendBaseUrl,
3546
3562
  getShareUrl,
3547
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx44("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
3548
- renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ jsx44("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
3563
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx45("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
3564
+ renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ jsx45("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
3549
3565
  song: {
3550
3566
  uuid: songUuid,
3551
3567
  expanded: songUuid !== null && expandedSongUuid === songUuid,
@@ -3561,7 +3577,7 @@ function EntityDetailPanel({
3561
3577
  ...navigation
3562
3578
  });
3563
3579
  if (!panel) return null;
3564
- return /* @__PURE__ */ jsx44(
3580
+ return /* @__PURE__ */ jsx45(
3565
3581
  DetailPanel3,
3566
3582
  {
3567
3583
  side: "left",
@@ -3607,4 +3623,4 @@ export {
3607
3623
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3608
3624
  GenreTreeOutlineSkeleton
3609
3625
  };
3610
- //# sourceMappingURL=chunk-I2TEOHVG.js.map
3626
+ //# sourceMappingURL=chunk-GR4PCPCU.js.map