PageSourceSearch

https://www.hamienet.com/assets/SectionContent-TfBXVZBi.js

js hamienet.com collected 2026-09-24 18:59:58 UTC 37,282 bytes, 585 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js";
2import { m as getOptimizedImageUrl, a as generateTrackUrl } from "./utils-BYCVoSTN.js";
3import { u as usePlayer, a as useAdmin } from "./main-CznOH3wZ.js";
4import { g as getBilingualTitle } from "./bilingual-B8ftIVs5.js";
5import { a as api } from "./index-D0Bmh-UX.js";
6import { u as useTranslation } from "./vendor-i18n-R-bMoYp_.js";
7import { cI as Flag, aW as X, aY as TriangleAlert, bZ as Pause, bU as Play, a_ as CircleCheck, cj as Layers, ce as Plus, b2 as Upload } from "./vendor-dVNaLJx_.js";
8const ReportBoundMidiModal = ({
9  isOpen,
10  onClose,
11  songId,
12  songTitle,
13  track,
14  currentUser,
15  onReportSubmitted
16}) => {
17  var _a;
18  const { t } = useTranslation();
19  const [reason, setReason] = reactExports.useState("wrong_song");
20  const [details, setDetails] = reactExports.useState("");
21  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
22  const [isSuccess, setIsSuccess] = reactExports.useState(false);
23  const [error, setError] = reactExports.useState(null);
24  const { activeTrack, isPlaying, setIsPlaying, playTrack } = usePlayer();
25  const isPlayingThisTrack = (activeTrack == null ? void 0 : activeTrack.id) === (track == null ? void 0 : track.id) && isPlaying;
26  if (!isOpen || !track) return null;
27  const handlePlayPreview = () => {
28    if (isPlayingThisTrack) {
29      setIsPlaying(false);
30    } else {
31      playTrack(track, [track]);
32    }
33  };
34  const handleSubmit = async (e) => {
35    e.preventDefault();
36    if (!currentUser) {
37      setError(t("report.loginRequired", "You must be logged in to file a report."));
38      return;
39    }
40    if (!reason) {
41      setError(t("report.selectReason", "Please select a reason for reporting."));
42      return;
43    }
44    setIsSubmitting(true);
45    setError(null);
46    try {
47      await api.post("/reports", {
48        targetId: songId,
49        targetType: "song_midi_binding",
50        midiId: track.id,
51        reason,
52        details: details.trim() || void 0,
53        reporterId: currentUser.id
54      });
55      setIsSuccess(true);
56      onReportSubmitted == null ? void 0 : onReportSubmitted();
57    } catch (err) {
58      setError(err.message || "Failed to submit report. Please try again.");
59    } finally {
60      setIsSubmitting(false);
61    }
62  };
63  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[1000] flex items-center justify-center p-4 bg-black/60 backdrop-blur-md animate-in fade-in duration-200", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-3xl shadow-2xl w-full max-w-lg overflow-hidden border border-zinc-200/50 dark:border-zinc-800/50 animate-in zoom-in-95 duration-200", children: [
64    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-5 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/50", children: [
65      /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2.5", children: [
66        /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 20, className: "text-red-500 animate-pulse" }),
67        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("report.boundMidiTitle", "Report Irrelevant MIDI Binding") })
68      ] }),
69      /* @__PURE__ */ jsxRuntimeExports.jsx(
70        "button",
71        {
72          onClick: onClose,
73          className: "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 p-1.5 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full transition-colors focus:outline-none",
74          children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 })
75        }
76      )
77    ] }),
78    !isSuccess ? /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-6 space-y-5", children: [
79      error && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 bg-red-100 dark:bg-red-900/20 text-red-600 dark:text-red-400 border border-red-200/50 dark:border-red-800/30 rounded-2xl text-sm flex gap-2", children: [
80        /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 18, className: "shrink-0 mt-0.5" }),
81        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: error })
82      ] }),
83      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 p-4 rounded-2xl border border-zinc-200/70 dark:border-zinc-800/70 space-y-3", children: [
84        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between", children: [
85          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
86            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-black uppercase tracking-wider text-zinc-400 dark:text-zinc-500", children: "Collection Song" }),
87            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-bold text-zinc-900 dark:text-white truncate max-w-[280px]", children: songTitle })
88          ] }),
89          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] px-2 py-0.5 rounded font-black uppercase tracking-wider bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border border-indigo-500/20", children: [
90            "Bound Track #",
91            track.id
92          ] })
93        ] }),
94        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 pt-2 border-t border-zinc-200/50 dark:border-zinc-800/50", children: [
95          /* @__PURE__ */ jsxRuntimeExports.jsx(
96            "button",
97            {
98              type: "button",
99              onClick: handlePlayPreview,
100              className: `w-9 h-9 rounded-full flex items-center justify-center shrink-0 transition-colors shadow-sm ${isPlayingThisTrack ? "bg-indigo-600 text-white animate-pulse" : "bg-indigo-100 dark:bg-indigo-900/40 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-200"}`,
101              children: isPlayingThisTrack ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { size: 16, className: "fill-current" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 16, className: "fill-current ml-0.5" })
102            }
103          ),
104          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
105            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-zinc-800 dark:text-zinc-200 truncate", children: track.title }),
106            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[11px] text-zinc-500 dark:text-zinc-400 truncate", children: [
107              track.artist || ((_a = track.uploader) == null ? void 0 : _a.username) || "Hamie Library",
108              " ",
109              track.duration && track.duration !== "0:00" ? `· ${track.duration}` : track.duration_str && track.duration_str !== "0:00" ? `· ${track.duration_str}` : ""
110            ] })
111          ] })
112        ] })
113      ] }),
114      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
115        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-black text-zinc-400 dark:text-zinc-500 uppercase tracking-widest", children: t("report.selectReasonLabel", "Why is this MIDI incorrect?") }),
116        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
117          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3 rounded-xl border cursor-pointer transition-all ${reason === "wrong_song" ? "bg-indigo-50/50 dark:bg-indigo-950/20 border-indigo-500 text-indigo-950 dark:text-indigo-200" : "bg-zinc-50/50 dark:bg-zinc-800/30 border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700 text-zinc-700 dark:text-zinc-300"}`, children: [
118            /* @__PURE__ */ jsxRuntimeExports.jsx(
119              "input",
120              {
121                type: "radio",
122                name: "boundMidiReason",
123                value: "wrong_song",
124                checked: reason === "wrong_song",
125                onChange: () => setReason("wrong_song"),
126                className: "mt-0.5 accent-indigo-600"
127              }
128            ),
129            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs", children: [
130              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold", children: t("report.reasonWrongSong", "Wrong or Unrelated Song") }),
131              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 mt-0.5", children: t("report.reasonWrongSongDesc", "This MIDI file is an entirely different piece or melody from the collection song.") })
132            ] })
133          ] }),
134          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3 rounded-xl border cursor-pointer transition-all ${reason === "poor_quality" ? "bg-indigo-50/50 dark:bg-indigo-950/20 border-indigo-500 text-indigo-950 dark:text-indigo-200" : "bg-zinc-50/50 dark:bg-zinc-800/30 border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700 text-zinc-700 dark:text-zinc-300"}`, children: [
135            /* @__PURE__ */ jsxRuntimeExports.jsx(
136              "input",
137              {
138                type: "radio",
139                name: "boundMidiReason",
140                value: "poor_quality",
141                checked: reason === "poor_quality",
142                onChange: () => setReason("poor_quality"),
143                className: "mt-0.5 accent-indigo-600"
144              }
145            ),
146            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs", children: [
147              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold", children: t("report.reasonPoorQuality", "Corrupted / Inaccurate Transcription") }),
148              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 mt-0.5", children: t("report.reasonPoorQualityDesc", "The MIDI is severely out of key, corrupted, or contains unusable chaotic notes.") })
149            ] })
150          ] }),
151          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3 rounded-xl border cursor-pointer transition-all ${reason === "incomplete" ? "bg-indigo-50/50 dark:bg-indigo-950/20 border-indigo-500 text-indigo-950 dark:text-indigo-200" : "bg-zinc-50/50 dark:bg-zinc-800/30 border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700 text-zinc-700 dark:text-zinc-300"}`, children: [
152            /* @__PURE__ */ jsxRuntimeExports.jsx(
153              "input",
154              {
155                type: "radio",
156                name: "boundMidiReason",
157                value: "incomplete",
158                checked: reason === "incomplete",
159                onChange: () => setReason("incomplete"),
160                className: "mt-0.5 accent-indigo-600"
161              }
162            ),
163            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs", children: [
164              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold", children: t("report.reasonIncomplete", "Incomplete / Missing Arrangement") }),
165              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 mt-0.5", children: t("report.reasonIncompleteDesc", "Only a few seconds long, missing main melody, or cut off prematurely.") })
166            ] })
167          ] }),
168          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3 rounded-xl border cursor-pointer transition-all ${reason === "other" ? "bg-indigo-50/50 dark:bg-indigo-950/20 border-indigo-500 text-indigo-950 dark:text-indigo-200" : "bg-zinc-50/50 dark:bg-zinc-800/30 border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700 text-zinc-700 dark:text-zinc-300"}`, children: [
169            /* @__PURE__ */ jsxRuntimeExports.jsx(
170              "input",
171              {
172                type: "radio",
173                name: "boundMidiReason",
174                value: "other",
175                checked: reason === "other",
176                onChange: () => setReason("other"),
177                className: "mt-0.5 accent-indigo-600"
178              }
179            ),
180            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold", children: t("report.reasonOther", "Other Discrepancy") }) })
181          ] })
182        ] })
183      ] }),
184      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
185        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-black text-zinc-400 dark:text-zinc-500 uppercase tracking-widest mb-1.5", children: t("report.additionalDetails", "Additional Details (Optional)") }),
186        /* @__PURE__ */ jsxRuntimeExports.jsx(
187          "textarea",
188          {
189            value: details,
190            onChange: (e) => setDetails(e.target.value),
191            placeholder: t("report.boundDetailsPlaceholder", "Explain why this MIDI is not the correct match..."),
192            rows: 2,
193            className: "w-full text-xs p-3 rounded-xl border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 resize-none"
194          }
195        )
196      ] }),
197      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 pt-2", children: [
198        /* @__PURE__ */ jsxRuntimeExports.jsx(
199          "button",
200          {
201            type: "button",
202            onClick: onClose,
203            className: "flex-1 py-2.5 rounded-xl border border-zinc-200 dark:border-zinc-800 text-xs font-bold text-zinc-600 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition",
204            children: t("common.cancel", "Cancel")
205          }
206        ),
207        /* @__PURE__ */ jsxRuntimeExports.jsxs(
208          "button",
209          {
210            type: "submit",
211            disabled: isSubmitting || !reason,
212            className: "flex-1 py-2.5 rounded-xl bg-red-600 hover:bg-red-700 text-white text-xs font-bold shadow-sm transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2",
213            children: [
214              /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 14 }),
215              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: isSubmitting ? t("report.submitting", "Submitting...") : t("report.submitButton", "Submit Report") })
216            ]
217          }
218        )
219      ] })
220    ] }) : (
221      /* Success State */
222      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-8 text-center space-y-4", children: [
223        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 rounded-full flex items-center justify-center mx-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 24 }) }),
224        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
225          /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-base font-bold text-zinc-900 dark:text-white", children: t("report.thankYou", "Report Submitted") }),
226          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-1 max-w-sm mx-auto", children: t("report.boundSuccessDesc", "Thank you for helping keep HamieNET collections accurate! Your report has been recorded and submitted to the Hamie Tribunal moderation system.") })
227        ] }),
228        /* @__PURE__ */ jsxRuntimeExports.jsx(
229          "button",
230          {
231            onClick: onClose,
232            className: "px-6 py-2 bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 rounded-xl text-xs font-bold transition hover:opacity-90",
233            children: t("common.close", "Close")
234          }
235        )
236      ] })
237    )
238  ] }) });
239};
240const SongItemRow = ({
241  song,
242  allContextTracks,
243  activeTrack,
244  isPlaying,
245  setIsPlaying,
246  playTrack,
247  setPlaylistLabel,
248  playlistTitle,
249  showUploadButton,
250  onUploadClick,
251  onReportClick,
252  i18n
253}) => {
254  const { t } = useTranslation();
255  const hasTracks = song.tracks && song.tracks.length > 0;
256  const { mainTitle: rawSongTitle, subTitle: songSubTitle, hoverTooltip: songTooltip, nativeTitle: songNativeTitle, nativeBadge: songNativeBadge } = getBilingualTitle(song.title, song.details, i18n.language);
257  const songMainTitle = rawSongTitle.replace(/^\d+\.\s*/, "");
258  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50/60 dark:bg-zinc-900/40 border border-zinc-100 dark:border-zinc-800/80 rounded-xl p-2.5 mb-2 hover:border-zinc-200 dark:hover:border-zinc-700/60 transition-all group/song", children: [
259    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2 mb-1 px-1", children: [
260      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", title: songTooltip || songMainTitle, children: [
261        songNativeBadge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black uppercase tracking-wider bg-zinc-200/80 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 px-1 py-0.5 rounded shrink-0 select-none", title: `Native Script: ${songNativeBadge}`, children: songNativeBadge }),
262        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-900 dark:text-zinc-100 truncate", children: songMainTitle }),
263        songNativeTitle && songNativeTitle !== songMainTitle && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] text-zinc-500 dark:text-zinc-400 truncate hidden sm:inline font-sans", children: [
264          "· ",
265          songNativeTitle
266        ] }),
267        songSubTitle && songSubTitle !== songNativeTitle && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] text-zinc-400 dark:text-zinc-500 truncate hidden sm:inline", children: [
268          "· ",
269          songSubTitle
270        ] }),
271        !hasTracks && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-black uppercase tracking-wider bg-amber-500/15 text-amber-600 dark:text-amber-400 px-1.5 py-0.5 rounded border border-amber-500/30 shrink-0", children: "Unbound" }),
272        hasTracks && song.tracks.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 px-1.5 py-0.5 rounded border border-indigo-500/20 shrink-0", children: [
273          song.tracks.length,
274          " versions"
275        ] })
276      ] }),
277      showUploadButton && /* @__PURE__ */ jsxRuntimeExports.jsxs(
278        "button",
279        {
280          type: "button",
281          onClick: (e) => {
282            e.stopPropagation();
283            onUploadClick(song);
284          },
285          title: t("collection.uploadMidiTooltip", "Upload & bind a MIDI to this song"),
286          className: "inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-bold rounded-lg bg-zinc-100 hover:bg-indigo-50 dark:bg-zinc-800 dark:hover:bg-indigo-950/40 text-zinc-600 hover:text-indigo-600 dark:text-zinc-300 dark:hover:text-indigo-400 border border-zinc-200/60 dark:border-zinc-700/60 hover:border-indigo-200 dark:hover:border-indigo-800 transition shrink-0",
287          children: [
288            /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }),
289            /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 11 }),
290            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Upload MIDI" })
291          ]
292        }
293      )
294    ] }),
295    hasTracks ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 mt-1.5", children: song.tracks.map((track, idx) => {
296      const isPlayingThisTrack = (activeTrack == null ? void 0 : activeTrack._playlistItemId) && track._playlistItemId ? activeTrack._playlistItemId === track._playlistItemId && isPlaying : (activeTrack == null ? void 0 : activeTrack.id) === track.id && isPlaying;
297      return /* @__PURE__ */ jsxRuntimeExports.jsxs(
298        "div",
299        {
300          className: `flex items-center gap-2.5 px-2.5 py-1.5 rounded-lg text-xs transition-colors group/track ${isPlayingThisTrack ? "bg-indigo-50 dark:bg-indigo-900/30 border border-indigo-200 dark:border-indigo-800/60" : "bg-white/80 dark:bg-zinc-800/40 hover:bg-zinc-100 dark:hover:bg-zinc-800 border border-transparent"}`,
301          children: [
302            /* @__PURE__ */ jsxRuntimeExports.jsx(
303              "button",
304              {
305                type: "button",
306                onClick: () => {
307                  if (isPlayingThisTrack) {
308                    setIsPlaying(false);
309                  } else {
310                    playTrack(track, allContextTracks);
311                    setPlaylistLabel == null ? void 0 : setPlaylistLabel(playlistTitle);
312                  }
313                },
314                className: `w-6 h-6 rounded-full flex items-center justify-center shrink-0 transition shadow-xs ${isPlayingThisTrack ? "bg-indigo-600 text-white" : "bg-zinc-200 dark:bg-zinc-700 text-zinc-700 dark:text-zinc-300 hover:bg-indigo-600 hover:text-white"}`,
315                children: isPlayingThisTrack ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { size: 10, className: "fill-current" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 10, className: "fill-current ml-0.5" })
316              }
317            ),
318            /* @__PURE__ */ jsxRuntimeExports.jsxs(
319              "a",
320              {
321                href: generateTrackUrl(track),
322                className: "flex-1 min-w-0 flex items-center gap-2 cursor-pointer",
323                children: [
324                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-medium truncate ${isPlayingThisTrack ? "text-indigo-700 dark:text-indigo-300 font-bold" : "text-zinc-800 dark:text-zinc-200 hover:text-indigo-600 dark:hover:text-indigo-400"}`, children: track.title || songMainTitle }),
325                  track.artist && track.artist !== songMainTitle && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-400 truncate hidden md:inline", children: [
326                    "(",
327                    track.artist,
328                    ")"
329                  ] })
330                ]
331              }
332            ),
333            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-mono text-zinc-400 dark:text-zinc-500 shrink-0", children: track.duration && track.duration !== "0:00" ? track.duration : track.duration_str && track.duration_str !== "0:00" ? track.duration_str : typeof track.durationSec === "number" && track.durationSec > 0 ? `${Math.floor(track.durationSec / 60)}:${Math.round(track.durationSec % 60).toString().padStart(2, "0")}` : "--:--" }),
334            /* @__PURE__ */ jsxRuntimeExports.jsx(
335              "button",
336              {
337                type: "button",
338                onClick: (e) => {
339                  e.stopPropagation();
340                  onReportClick(song, track);
341                },
342                title: t("collection.reportMidiTooltip", "Report irrelevant or mismatched MIDI"),
343                className: "p-1 text-zinc-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded transition shrink-0 opacity-0 group-hover/track:opacity-100 focus:opacity-100",
344                children: /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 12 })
345              }
346            )
347          ]
348        },
349        track.id || idx
350      );
351    }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] text-zinc-400 dark:text-zinc-500 italic px-2 py-1 flex items-center justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("collection.noMidiBound", "No MIDI files bound yet.") }) })
352  ] });
353};
354const SectionContent = ({
355  section,
356  activeSlug,
357  onClose,
358  currentUser,
359  onUploadRequested
360}) => {
361  const [songs, setSongs] = reactExports.useState([]);
362  const [isLoading, setIsLoading] = reactExports.useState(true);
363  const [imageError, setImageError] = reactExports.useState(false);
364  const [showAllSongs, setShowAllSongs] = reactExports.useState(false);
365  const [breadcrumbs, setBreadcrumbs] = reactExports.useState([]);
366  const [reportingItem, setReportingItem] = reactExports.useState(null);
367  const { i18n, t } = useTranslation();
368  const { staffModeEnabled } = useAdmin();
369  const { mainTitle: rawSecTitle, subTitle: secSubTitle } = getBilingualTitle(section.title, section.subtitle, i18n.language);
370  const secMainTitle = rawSecTitle.replace(/^\d+\.\s*/, "");
371  const {
372    activeTrack,
373    isPlaying,
374    setIsPlaying,
375    playTrack,
376    activePlaylist,
377    setPlaylistLabel
378  } = usePlayer();
379  const loadSectionSongs = reactExports.useCallback(() => {
380    let mounted = true;
381    fetch(`/api/collections/${activeSlug}/sections/${section.id}/songs`).then((res) => res.json()).then((data) => {
382      if (mounted && Array.isArray(data)) {
383        let gIdx = 0;
384        setSongs(data.map((s) => ({ ...s, tracks: (s.tracks || []).map((t2) => ({ ...t2, _playlistItemId: `col-${section.id}-${s.id}-${t2.id}-${gIdx++}` })) })));
385      }
386    }).catch((err) => {
387      console.error("Failed to load section songs", err);
388    });
389    return () => {
390      mounted = false;
391    };
392  }, [activeSlug, section.id]);
393  reactExports.useEffect(() => {
394    let mounted = true;
395    setIsLoading(true);
396    const songsPromise = fetch(`/api/collections/${activeSlug}/sections/${section.id}/songs`).then((res) => res.json()).then((data) => {
397      if (mounted && Array.isArray(data)) {
398        let gIdx = 0;
399        setSongs(data.map((s) => ({ ...s, tracks: (s.tracks || []).map((t2) => ({ ...t2, _playlistItemId: `col-${section.id}-${s.id}-${t2.id}-${gIdx++}` })) })));
400      }
401    }).catch((err) => {
402      console.error("Failed to load section songs", err);
403    });
404    const breadcrumbsPromise = fetch(`/api/collections/${activeSlug}/sections/${section.id}/breadcrumbs`).then((res) => res.json()).then((data) => {
405      if (mounted && Array.isArray(data)) {
406        setBreadcrumbs(data);
407      }
408    }).catch((err) => console.error("Failed to load breadcrumbs", err));
409    Promise.allSettled([songsPromise, breadcrumbsPromise]).finally(() => {
410      if (mounted) setIsLoading(false);
411    });
412    return () => {
413      mounted = false;
414    };
415  }, [activeSlug, section.id]);
416  reactExports.useEffect(() => {
417    const handleMidiUploaded = () => {
418      loadSectionSongs();
419    };
420    window.addEventListener("hamienet:midi_uploaded", handleMidiUploaded);
421    return () => window.removeEventListener("hamienet:midi_uploaded", handleMidiUploaded);
422  }, [loadSectionSongs]);
423  const allSectionTracks = React.useMemo(() => {
424    return songs.flatMap((s) => s.tracks);
425  }, [songs]);
426  const handlePlayAll = () => {
427    if (allSectionTracks.length > 0) {
428      playTrack(allSectionTracks[0], allSectionTracks);
429      setPlaylistLabel == null ? void 0 : setPlaylistLabel(secMainTitle);
430    }
431  };
432  const handleUploadClick = (song) => {
433    const initialData = {
434      title: song.title,
435      artist: song.details || section.composer || "",
436      canonicalWorkId: song.id
437    };
438    if (onUploadRequested) {
439      onUploadRequested(initialData);
440    } else {
441      window.dispatchEvent(new CustomEvent("hamienet:request_upload", { detail: initialData }));
442    }
443  };
444  const handleReportClick = (song, track) => {
445    setReportingItem({
446      songId: song.id,
447      songTitle: song.title,
448      track
449    });
450  };
451  const groupedSongs = React.useMemo(() => {
452    const groups = {};
453    songs.forEach((song) => {
454      const group = song.groupName || "Other Works";
455      if (!groups[group]) groups[group] = [];
456      groups[group].push(song);
457    });
458    return groups;
459  }, [songs]);
460  const isSectionPlaying = activePlaylist && activePlaylist.length > 0 && activePlaylist.some((t2) => allSectionTracks.some((st) => st.id === t2.id));
461  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col bg-white dark:bg-[#0f0f0f] w-full min-h-[50vh]", children: [
462    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full h-48 md:h-64 lg:h-80 bg-zinc-100 dark:bg-zinc-900 shrink-0 border-b border-zinc-200 dark:border-zinc-800", children: [
463      section.bannerImage && !imageError ? /* @__PURE__ */ jsxRuntimeExports.jsx(
464        "img",
465        {
466          src: getOptimizedImageUrl(section.bannerImage, "banner"),
467          className: "w-full h-full object-cover",
468          alt: secMainTitle,
469          title: secSubTitle ? `${secMainTitle} (${secSubTitle})` : secMainTitle,
470          onError: () => setImageError(true)
471        }
472      ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full h-full bg-gradient-to-br from-indigo-500/20 to-purple-500/20 flex flex-col items-center justify-center text-indigo-700/50 dark:text-indigo-300/50", children: [
473        /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 48, className: "mb-2" }),
474        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold opacity-80", children: "No Banner Image" })
475      ] }),
476      onClose && /* @__PURE__ */ jsxRuntimeExports.jsx(
477        "button",
478        {
479          onClick: onClose,
480          className: "absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 text-white rounded-full backdrop-blur-sm transition z-10",
481          children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 })
482        }
483      )
484    ] }),
485    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 md:p-8 space-y-6", children: [
486      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
487        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-3", children: [
488          section.composer && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex items-center bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300 font-bold text-[10px] uppercase tracking-wider px-2 py-1 rounded", children: section.composer }),
489          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 font-bold text-[10px] uppercase tracking-wider px-2 py-1 rounded", children: [
490            section.trackCount || allSectionTracks.length,
491            " Tracks"
492          ] })
493        ] }),
494        /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-2xl md:text-3xl font-black text-zinc-900 dark:text-white tracking-tight flex flex-wrap items-center gap-2", children: breadcrumbs.length > 0 ? breadcrumbs.map((crumb, index) => {
495          const { mainTitle: crumbMainTitle, hoverTooltip: crumbTooltip } = getBilingualTitle(crumb.title, crumb.subtitle, i18n.language);
496          const isLast = index === breadcrumbs.length - 1;
497          const href = crumb.isCollection ? `/collections/${crumb.slug}` : `/collections/${activeSlug}#section-${crumb.id}`;
498          return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
499            index > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 dark:text-zinc-600 font-normal", children: "/" }),
500            /* @__PURE__ */ jsxRuntimeExports.jsx(
501              "a",
502              {
503                href,
504                title: crumbTooltip || crumbMainTitle,
505                onClick: (e) => {
506                  if (onClose) {
507                    setTimeout(() => onClose(), 10);
508                  }
509                },
510                className: `hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors ${isLast ? "text-zinc-900 dark:text-white" : "text-zinc-500 dark:text-zinc-400 font-bold"}`,
511                children: crumbMainTitle
512              }
513            )
514          ] }, crumb.id);
515        }) : secMainTitle }),
516        secSubTitle && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400 mt-1", children: secSubTitle }),
517        section.description && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-600 dark:text-zinc-300 mt-4 leading-relaxed max-w-4xl", children: section.description })
518      ] }),
519      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3 pt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
520        "button",
521        {
522          onClick: handlePlayAll,
523          disabled: allSectionTracks.length === 0,
524          className: `flex items-center gap-2 px-5 py-2.5 rounded-full font-bold text-sm shadow-sm transition ${isSectionPlaying ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800" : "bg-indigo-600 hover:bg-indigo-700 text-white disabled:opacity-50 disabled:cursor-not-allowed"}`,
525          children: [
526            /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 16, className: isSectionPlaying ? "text-indigo-500 fill-current" : "fill-current" }),
527            isSectionPlaying ? "Now Playing ✓" : "Play All"
528          ]
529        }
530      ) }),
531      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-8", children: isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-16 opacity-70 animate-in fade-in duration-300", children: [
532        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-10 h-10 border-4 border-indigo-200 dark:border-indigo-900 border-t-indigo-600 dark:border-t-indigo-400 rounded-full animate-spin mb-4 shadow-sm" }),
533        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-semibold text-zinc-500 dark:text-zinc-400 tracking-wide uppercase", children: "Loading Tracks..." }),
534        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-8 w-full max-w-md space-y-3 px-4", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-14 bg-zinc-100/50 dark:bg-zinc-800/30 rounded-xl w-full animate-pulse" }, i)) })
535      ] }) : songs.length === 0 || Object.keys(groupedSongs).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-10 flex flex-col items-center justify-center text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm italic", children: "No tracks available for this section yet." }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-8", children: songs.length > 0 && Object.keys(groupedSongs).length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-8", children: [
536        Object.entries(groupedSongs).map(([group, groupSongs]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
537          /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-bold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-3 px-2 border-b border-zinc-100 dark:border-zinc-800 pb-1", children: group }),
538          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1", children: groupSongs.filter((s) => showAllSongs || s.tracks && s.tracks.length > 0).map((song) => /* @__PURE__ */ jsxRuntimeExports.jsx(
539            SongItemRow,
540            {
541              song,
542              allContextTracks: allSectionTracks,
543              activeTrack,
544              isPlaying,
545              setIsPlaying,
546              playTrack,
547              setPlaylistLabel,
548              playlistTitle: secMainTitle,
549              showUploadButton: showAllSongs,
550              onUploadClick: handleUploadClick,
551              onReportClick: handleReportClick,
552              i18n
553            },
554            song.id
555          )) })
556        ] }, group)),
557        songs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pt-4 border-t border-zinc-100 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
558          "button",
559          {
560            onClick: () => setShowAllSongs(!showAllSongs),
561            className: "w-full py-3 text-sm font-bold text-zinc-500 hover:text-indigo-600 dark:hover:text-indigo-400 hover:bg-zinc-50 dark:hover:bg-zinc-800/50 rounded-lg transition-colors border-2 border-dashed border-zinc-200 dark:border-zinc-800",
562            children: showAllSongs ? songs.filter((s) => !(s.tracks && s.tracks.length > 0)).length > 0 ? "Hide unbound tracks & upload options" : "Hide upload options" : songs.filter((s) => !(s.tracks && s.tracks.length > 0)).length > 0 ? `Show all tracks & upload options (${songs.filter((s) => !(s.tracks && s.tracks.length > 0)).length} unbound tracks hidden)` : "Show upload options"
563          }
564        ) })
565      ] }) }) })
566    ] }),
567    reportingItem && /* @__PURE__ */ jsxRuntimeExports.jsx(
568      ReportBoundMidiModal,
569      {
570        isOpen: Boolean(reportingItem),
571        onClose: () => setReportingItem(null),
572        songId: reportingItem.songId,
573        songTitle: reportingItem.songTitle,
574        track: reportingItem.track,
575        currentUser: currentUser || null,
576        onReportSubmitted: () => {
577          loadSectionSongs();
578        }
579      }
580    )
581  ] });
582};
583export {
584  SectionContent as S
585};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.