PageSourceSearch

https://www.hamienet.com/assets/WatchPage-B8_07PT2.js

js hamienet.com collected 2026-09-24 18:59:09 UTC 644,819 bytes, 12,689 lines download raw bytes

1const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/SpectrumBarsVisualizer-D1eLSVtr.js","assets/vendor-react-Bay9Y7Lb.js","assets/SheetMusicViewer-CoFH8B_z.js","assets/vendor-react-three-MDLA5jog.js","assets/vendor-dVNaLJx_.js","assets/vendor-i18n-R-bMoYp_.js","assets/vendor-DqKRdbjz.css","assets/SharedKeyboard-B3r3Jaho.js","assets/useMidiKeyboard-DlyEIN8T.js","assets/wasm-synth-adapter-BrqgBX1y.js","assets/vendor-spessasynth-DH8n6o_V.js","assets/ParametricEQ-wkxmF0lG.js","assets/synth-BE1TSTD4.js","assets/vendor-audio-DH3w0BzW.js","assets/OrchestraVisualizer-Cwwo0xaQ.js","assets/StagePuppetEngine-BtPqoUG7.js","assets/IkMath-D6fi1ppd.js","assets/vendor-gsap-50Lx19ak.js","assets/vendor-framer-C8eEgkdL.js","assets/InstrumentMappings-UYUlFRMy.js","assets/CinematicVisualizer-BM6Yo-FE.js","assets/Musician3D-D56sI7cs.js","assets/SouthparkOrchestraVisualizer-BGIvyqzT.js","assets/SouthparkCharacter-BNwA9Wd0.js","assets/SouthparkVariants-DgWdjJ-T.js","assets/SouthparkStageBanner-BCxT9awg.js","assets/utils-BYCVoSTN.js","assets/index-D0Bmh-UX.js","assets/index-WEfYDNmt.css","assets/Taiko-BWRRYy3Y.js","assets/GlobeFlag-V1uaxVEK.js"])))=>i.map(i=>d[i]);
2import { r as reactExports, j as jsxRuntimeExports, R as React, a as reactDomExports } from "./vendor-react-Bay9Y7Lb.js";
3import { a as api, u as useSettings } from "./index-D0Bmh-UX.js";
4import { u as useTranslation } from "./vendor-i18n-R-bMoYp_.js";
5import { cG as Tag, aW as X, bf as Search, ct as TrendingUp, cH as Disc, bg as Music, b1 as CircleAlert, cI as Flag, aY as TriangleAlert, cq as Scale, cJ as ShieldAlert, a_ as CircleCheck, b3 as FileHeadphone, b2 as Upload, cw as List, cK as Navigation, cL as RefreshCw, cM as Cable, cN as Gamepad2, cF as Keyboard, cO as GraduationCap, bS as Mic, cP as MicOff, cC as Drum, c7 as Timer, cQ as Minus, ce as Plus, cR as SquareSquare, cS as Route, cT as Type, cf as Activity, bs as Zap, bi as Sparkles, cs as BookOpen, cz as SlidersHorizontal, cU as RectangleHorizontal, cV as Minimize, cW as Maximize, b6 as LoaderCircle, cp as Settings2, cX as MicVocal, bU as Play, cY as ChevronLeft, bE as ChevronRight, cj as Layers, cZ as Minimize2, bQ as ExternalLink, cB as ChevronDown$1, bJ as PanelsTopLeft, c_ as Maximize2, c3 as RotateCcw, b7 as Check, cA as ChevronUp, bW as VolumeX, bT as Volume2, cD as Headphones, bz as EyeOff, bA as Eye, c$ as Download, a$ as Shield, d0 as UserCheck, d1 as UserMinus, d2 as Ghost, d3 as ChartColumn, d4 as ShieldCheck, ba as Info, aZ as Mail, c1 as Copy, cl as OctagonAlert, ch as Database, bK as ThumbsUp, d5 as ThumbsDown, c0 as Share2, cd as Ellipsis, d6 as SquarePen, bY as FolderPlus, d7 as Image, d8 as Trash, bL as Clock, d9 as Music2, bm as ArrowUp, da as ArrowDown, bH as Hash, b_ as EllipsisVertical, db as ListVideo } from "./vendor-dVNaLJx_.js";
6import { A as AnimatePresence, m as motion } from "./vendor-framer-C8eEgkdL.js";
7import { A as AdSlot } from "./AdSlot-CPW4Sb2o.js";
8import { T as TagAutocomplete } from "./Waveform-MxtdejK7.js";
9import { b as getInstrumentName, e as isSlowMobile, G as GM_PERCUSSION_MAP, j as getTrackColor, i as isPro, I as INSTRUMENT_CATEGORIES, k as GM_INSTRUMENTS, t as timeAgo, d as isContentBlocked, f as getBestAudioMode } from "./utils-BYCVoSTN.js";
10import { s as synth, p as parseMidiInstruments } from "./synth-BE1TSTD4.js";
11import { V as VideoCard, a as useAdmin, u as usePlayer } from "./main-CznOH3wZ.js";
12import { _ as __vitePreload } from "./vendor-react-three-MDLA5jog.js";
13import { u as useSoundfontKeyboard, g as getKeyGeometry, a as useKeyboardInput, b as getMidiFromKeyboardClick, C as CONFIG, c as getPcKeyLabel, r as renderSharedKeyboard } from "./SharedKeyboard-B3r3Jaho.js";
14import { G as GlobeFlag } from "./GlobeFlag-V1uaxVEK.js";
15import { U as UserAvatar } from "./UserAvatar-BQN4jlY-.js";
16import "./wasm-synth-adapter-BrqgBX1y.js";
17import "./vendor-spessasynth-DH8n6o_V.js";
18import "./vendor-audio-DH3w0BzW.js";
19import "./labTools-BATZiaPB.js";
20import "./useMidiKeyboard-DlyEIN8T.js";
21const TagModal = ({ isOpen, onClose, onSubmit }) => {
22  const { t, i18n } = useTranslation();
23  const [tagName, setTagName] = reactExports.useState("");
24  const [suggestions, setSuggestions] = reactExports.useState([]);
25  const [showSuggestions, setShowSuggestions] = reactExports.useState(false);
26  const [activeIndex, setActiveIndex] = reactExports.useState(-1);
27  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
28  const [isLoadingSuggestions, setIsLoadingSuggestions] = reactExports.useState(false);
29  const [error, setError] = reactExports.useState(null);
30  const containerRef = reactExports.useRef(null);
31  reactExports.useEffect(() => {
32    const trimmed = tagName.trim();
33    if (trimmed.length < 2) {
34      setSuggestions([]);
35      setShowSuggestions(false);
36      return;
37    }
38    setIsLoadingSuggestions(true);
39    const timer = setTimeout(async () => {
40      try {
41        const results = await api.getSuggestedTags(trimmed);
42        setSuggestions(results);
43        setShowSuggestions(results.length > 0);
44        setActiveIndex(-1);
45      } catch (err) {
46        console.error("Error fetching suggestions:", err);
47      } finally {
48        setIsLoadingSuggestions(false);
49      }
50    }, 200);
51    return () => clearTimeout(timer);
52  }, [tagName]);
53  reactExports.useEffect(() => {
54    const handleClickOutside = (e) => {
55      if (containerRef.current && !containerRef.current.contains(e.target)) {
56        setShowSuggestions(false);
57      }
58    };
59    document.addEventListener("mousedown", handleClickOutside);
60    return () => document.removeEventListener("mousedown", handleClickOutside);
61  }, []);
62  if (!isOpen) return null;
63  const handleSelectSuggestion = async (name) => {
64    setTagName("");
65    setShowSuggestions(false);
66    setIsSubmitting(true);
67    setError(null);
68    try {
69      await onSubmit(name);
70      onClose();
71    } catch (err) {
72      setError(err.message || "Failed to add tag");
73    } finally {
74      setIsSubmitting(false);
75    }
76  };
77  const handleSubmit = async (e) => {
78    e.preventDefault();
79    if (!tagName.trim()) return;
80    if (showSuggestions && activeIndex >= 0 && suggestions[activeIndex]) {
81      handleSelectSuggestion(suggestions[activeIndex].name);
82      return;
83    }
84    setIsSubmitting(true);
85    setError(null);
86    try {
87      await onSubmit(tagName.trim());
88      setTagName("");
89      onClose();
90    } catch (err) {
91      setError(err.message || "Failed to add tag");
92    } finally {
93      setIsSubmitting(false);
94    }
95  };
96  const handleKeyDown = (e) => {
97    if (e.key === "ArrowDown") {
98      e.preventDefault();
99      if (showSuggestions && suggestions.length > 0) {
100        setActiveIndex((prev) => Math.min(prev + 1, suggestions.length - 1));
101      }
102    } else if (e.key === "ArrowUp") {
103      e.preventDefault();
104      if (showSuggestions && suggestions.length > 0) {
105        setActiveIndex((prev) => Math.max(prev - 1, -1));
106      }
107    } else if (e.key === "Escape") {
108      if (showSuggestions) {
109        e.preventDefault();
110        setShowSuggestions(false);
111      } else {
112        onClose();
113      }
114    }
115  };
116  const getLocalizedName = (s) => {
117    const lang = i18n.language;
118    if (s.translations && s.translations[lang]) return s.translations[lang];
119    return s.name;
120  };
121  const getTypeIcon = (type) => {
122    switch (type) {
123      case "COMPOSER":
124        return /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { className: "w-3.5 h-3.5" });
125      case "SHOW":
126        return /* @__PURE__ */ jsxRuntimeExports.jsx(Disc, { className: "w-3.5 h-3.5" });
127      case "INSTRUMENT":
128        return /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "w-3.5 h-3.5" });
129      default:
130        return /* @__PURE__ */ jsxRuntimeExports.jsx(Tag, { className: "w-3.5 h-3.5" });
131    }
132  };
133  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl w-full max-w-md overflow-hidden border border-zinc-200/50 dark:border-zinc-800/50 animate-in zoom-in-95 duration-200", children: [
134    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/50", children: [
135      /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
136        /* @__PURE__ */ jsxRuntimeExports.jsx(Tag, { size: 18, className: "text-indigo-500 animate-pulse" }),
137        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Add Tag" })
138      ] }),
139      /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) })
140    ] }),
141    /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-5", children: [
142      error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-4 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-xl text-sm", children: error }),
143      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 relative", ref: containerRef, children: [
144        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2 dark:text-zinc-400", children: "Tag Name" }),
145        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
146          /* @__PURE__ */ jsxRuntimeExports.jsx(
147            "input",
148            {
149              type: "text",
150              value: tagName,
151              onChange: (e) => {
152                setTagName(e.target.value);
153                if (e.target.value.trim() === "") {
154                  setShowSuggestions(false);
155                }
156              },
157              onKeyDown: handleKeyDown,
158              onFocus: () => {
159                if (tagName.trim().length >= 2 && suggestions.length > 0) {
160                  setShowSuggestions(true);
161                }
162              },
163              placeholder: "e.g. piano, classical, fast",
164              className: "w-full p-3 pl-10 border border-zinc-200 dark:border-zinc-800 bg-zinc-50/50 dark:bg-zinc-800/50 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-zinc-900 dark:text-white transition-all duration-200 font-medium placeholder:text-zinc-400 text-sm",
165              autoFocus: true,
166              disabled: isSubmitting
167            }
168          ),
169          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-3.5 top-3.5 text-zinc-400 dark:text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 16 }) }),
170          isLoadingSuggestions && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-3.5 top-3.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" }) })
171        ] }),
172        /* @__PURE__ */ jsxRuntimeExports.jsx(AnimatePresence, { children: showSuggestions && suggestions.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
173          motion.div,
174          {
175            initial: { opacity: 0, y: -8, scale: 0.98 },
176            animate: { opacity: 1, y: 0, scale: 1 },
177            exit: { opacity: 0, y: -8, scale: 0.98 },
178            transition: { duration: 0.15 },
179            className: "absolute left-0 right-0 z-50 mt-1.5 bg-white/95 dark:bg-zinc-900/95 border border-zinc-200 dark:border-zinc-800 rounded-xl shadow-2xl overflow-hidden backdrop-blur-md",
180            children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-h-60 overflow-y-auto py-1.5 custom-scrollbar", children: [
181              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1 text-[10px] font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider", children: "Similar Tags in Database" }),
182              suggestions.map((s, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
183                "button",
184                {
185                  type: "button",
186                  onClick: () => handleSelectSuggestion(s.name),
187                  onMouseEnter: () => setActiveIndex(idx),
188                  className: `w-full flex items-center justify-between px-3 py-2 text-left transition-all duration-150 ${idx === activeIndex ? "bg-indigo-500 text-white font-medium shadow-md shadow-indigo-500/10" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50 text-zinc-700 dark:text-zinc-300"}`,
189                  children: [
190                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5", children: [
191                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `p-1.5 rounded-lg transition-colors ${idx === activeIndex ? "bg-white/20" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-500 dark:text-zinc-400"}`, children: getTypeIcon(s.type) }),
192                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
193                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold", children: getLocalizedName(s) }),
194                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `text-[9px] uppercase tracking-wider font-bold ${idx === activeIndex ? "text-white/70" : "text-zinc-400 dark:text-zinc-500"}`, children: s.type })
195                      ] })
196                    ] }),
197                    s.score > 50 && /* @__PURE__ */ jsxRuntimeExports.jsx(TrendingUp, { className: `w-3.5 h-3.5 ${idx === activeIndex ? "text-white/75" : "text-zinc-400 dark:text-zinc-500"}` })
198                  ]
199                },
200                s.id
201              ))
202            ] })
203          }
204        ) })
205      ] }),
206      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2.5 mt-8 border-t border-zinc-100 dark:border-zinc-800/50 pt-4", children: [
207        /* @__PURE__ */ jsxRuntimeExports.jsx(
208          "button",
209          {
210            type: "button",
211            onClick: onClose,
212            className: "px-4 py-2.5 text-sm font-semibold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-xl transition-all duration-200",
213            disabled: isSubmitting,
214            children: "Cancel"
215          }
216        ),
217        /* @__PURE__ */ jsxRuntimeExports.jsx(
218          "button",
219          {
220            type: "submit",
221            disabled: isSubmitting || !tagName.trim(),
222            className: "px-5 py-2.5 bg-indigo-600 text-white text-sm font-bold rounded-xl hover:bg-indigo-700 disabled:opacity-50 transition-all duration-200 shadow-lg shadow-indigo-500/15 hover:shadow-indigo-500/25 active:scale-98",
223            children: isSubmitting ? "Adding..." : "Add Tag"
224          }
225        )
226      ] })
227    ] })
228  ] }) });
229};
230function EditTrackModal({ isOpen, onClose, track, currentUser, onSuccess }) {
231  const { t } = useTranslation();
232  const [metadata, setMetadata] = reactExports.useState({
233    title: track.title,
234    artist: track.artist,
235    description: track.description || "",
236    tags: track.tags.map((t2) => typeof t2 === "string" ? t2 : t2.name).join(", "),
237    genre: ""
238  });
239  const [availableGenres, setAvailableGenres] = reactExports.useState([]);
240  const [saving, setSaving] = reactExports.useState(false);
241  const [validationErrors, setValidationErrors] = reactExports.useState({});
242  const [isValidating, setIsValidating] = reactExports.useState(false);
243  const validationTimeoutRef = React.useRef(null);
244  const lastAutofilledFor = React.useRef({ artist: track.artist, title: track.title });
245  reactExports.useEffect(() => {
246    if (isOpen) {
247      api.getTags({ type: "GENRE" }).then((tags) => {
248        const sorted = [...tags].sort((a, b) => a.name.localeCompare(b.name));
249        setAvailableGenres(sorted);
250      });
251      setMetadata({
252        title: track.title,
253        artist: track.artist,
254        description: track.description || "",
255        tags: track.tags.map((t2) => typeof t2 === "string" ? t2 : t2.name).join(", "),
256        genre: ""
257      });
258    }
259  }, [isOpen, track]);
260  reactExports.useEffect(() => {
261    if (isOpen) {
262      if (validationTimeoutRef.current) clearTimeout(validationTimeoutRef.current);
263      setIsValidating(true);
264      validationTimeoutRef.current = setTimeout(async () => {
265        try {
266          const res = await api.validateContent({
267            title: metadata.title,
268            artist: metadata.artist,
269            description: metadata.description
270          });
271          const errors = {};
272          Object.entries(res.results).forEach(([key, val]) => {
273            if (!val.valid) errors[key] = val.error || "Invalid content";
274          });
275          setValidationErrors(errors);
276        } catch (e) {
277          console.error("Validation failed", e);
278        } finally {
279          setIsValidating(false);
280        }
281      }, 500);
282    }
283    return () => {
284      if (validationTimeoutRef.current) clearTimeout(validationTimeoutRef.current);
285    };
286  }, [metadata.title, metadata.artist, metadata.description, isOpen]);
287  reactExports.useEffect(() => {
288    if (!isOpen) return;
289    const timeout = setTimeout(async () => {
290      const currentArtist = (metadata.artist || "").trim();
291      const currentTitle = (metadata.title || "").trim();
292      if (!currentArtist && !currentTitle) return;
293      if (lastAutofilledFor.current.artist === currentArtist && lastAutofilledFor.current.title === currentTitle) {
294        return;
295      }
296      try {
297        const autoTags = await api.autofillTags(currentArtist, currentTitle);
298        if (autoTags && autoTags.length > 0) {
299          setMetadata((prev) => {
300            const currentTagsList = prev.tags ? prev.tags.split(",").map((t2) => t2.trim()).filter(Boolean) : [];
301            let addedAny = false;
302            for (const t2 of autoTags) {
303              if (!currentTagsList.some((existing) => existing.toLowerCase() === t2.toLowerCase())) {
304                currentTagsList.push(t2);
305                addedAny = true;
306              }
307            }
308            if (addedAny) {
309              return { ...prev, tags: currentTagsList.join(", ") };
310            }
311            return prev;
312          });
313        }
314      } catch (err) {
315        console.error("Autofill failed", err);
316      } finally {
317        lastAutofilledFor.current = { artist: currentArtist, title: currentTitle };
318      }
319    }, 1e3);
320    return () => clearTimeout(timeout);
321  }, [metadata.artist, metadata.title, isOpen]);
322  if (!isOpen) return null;
323  const handleSave = async () => {
324    setSaving(true);
325    let finalTags = metadata.tags;
326    if (metadata.genre) {
327      finalTags = metadata.genre + (finalTags ? "," + finalTags : "");
328    }
329    try {
330      await api.editTrackWiki(track.id, {
331        title: metadata.title,
332        artist: metadata.artist,
333        description: metadata.description,
334        tags: finalTags,
335        userId: currentUser.id
336      });
337      onSuccess();
338      onClose();
339    } catch (error) {
340      console.error(error);
341      alert(t("upload.failed", "Edit failed. Please try again."));
342    } finally {
343      setSaving(false);
344    }
345  };
346  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[1000] flex items-center justify-center p-4", children: [
347    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/80 backdrop-blur-sm", onClick: onClose }),
348    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-2xl bg-white dark:bg-zinc-900 rounded-xl shadow-2xl flex flex-col max-h-[90vh] overflow-hidden animate-in zoom-in-95", children: [
349      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800", children: [
350        /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Edit MIDI Information" }),
351        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-md transition-colors text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 outline-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) })
352      ] }),
353      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-8 flex-1 overflow-y-auto", children: [
354        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-800 rounded-lg p-4 flex gap-3 text-emerald-800 dark:text-emerald-200 text-sm", children: [
355          /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "shrink-0 mt-0.5", size: 18 }),
356          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
357            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold", children: "You are editing a public Wiki track." }),
358            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 opacity-90", children: "Your edits will be visible to everyone and will be logged to your account. Helpful edits increase your Trust Score!" })
359          ] })
360        ] }),
361        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
362          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
363            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
364              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium mb-1 text-zinc-900 dark:text-white", children: t("upload.fields.title", "Title") }),
365              /* @__PURE__ */ jsxRuntimeExports.jsx(
366                "input",
367                {
368                  type: "text",
369                  maxLength: 200,
370                  value: metadata.title,
371                  onChange: (e) => setMetadata({ ...metadata, title: e.target.value }),
372                  className: `w-full bg-zinc-50 dark:bg-zinc-800 border ${validationErrors.title ? "border-red-500" : "border-zinc-300 dark:border-zinc-700"} rounded p-2 text-zinc-900 dark:text-white`
373                }
374              ),
375              validationErrors.title && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-500 mt-0.5", children: validationErrors.title })
376            ] }),
377            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
378              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium mb-1 text-zinc-900 dark:text-white", children: t("upload.fields.artist", "Composer/Artist") }),
379              /* @__PURE__ */ jsxRuntimeExports.jsx(
380                TagAutocomplete,
381                {
382                  value: metadata.artist,
383                  onChange: (val) => setMetadata({ ...metadata, artist: val }),
384                  onSelectSuggestion: (s) => {
385                    if (s.source === "MusicBrainz" || s.type === "GENRE") {
386                      const match = availableGenres.find((g) => g.name.toLowerCase() === s.name.toLowerCase());
387                      if (match) {
388                        if (!metadata.genre) {
389                          setMetadata((prev) => ({ ...prev, genre: match.name }));
390                        }
391                      } else {
392                        setMetadata((prev) => {
393                          const currentTags = prev.tags ? prev.tags.split(",").map((t2) => t2.trim()).filter(Boolean) : [];
394                          if (!currentTags.some((t2) => t2.toLowerCase() === s.name.toLowerCase())) {
395                            currentTags.push(s.name);
396                            return { ...prev, tags: currentTags.join(", ") };
397                          }
398                          return prev;
399                        });
400                      }
401                    }
402                  },
403                  placeholder: "Andrew Lloyd Webber, Joe Hisaishi...",
404                  maxLength: 100,
405                  className: validationErrors.artist ? "border-red-500" : ""
406                }
407              ),
408              validationErrors.artist && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-500 mt-0.5", children: validationErrors.artist })
409            ] })
410          ] }),
411          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
412            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium mb-1 text-zinc-900 dark:text-white", children: t("upload.fields.genre", "Add Genre") }),
413            /* @__PURE__ */ jsxRuntimeExports.jsxs(
414              "select",
415              {
416                value: metadata.genre,
417                onChange: (e) => setMetadata({ ...metadata, genre: e.target.value }),
418                className: "w-full bg-zinc-50 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded p-2 text-zinc-900 dark:text-white",
419                children: [
420                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: t("upload.fields.selectGenre", "-- Select Genre --") }),
421                  availableGenres.map((g) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: g.name, children: g.name }, g.id))
422                ]
423              }
424            )
425          ] }),
426          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
427            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium mb-1 text-zinc-900 dark:text-white", children: t("upload.fields.description", "Description") }),
428            /* @__PURE__ */ jsxRuntimeExports.jsx(
429              "textarea",
430              {
431                value: metadata.description,
432                onChange: (e) => setMetadata({ ...metadata, description: e.target.value }),
433                className: `w-full bg-zinc-50 dark:bg-zinc-800 border ${validationErrors.description ? "border-red-500" : "border-zinc-300 dark:border-zinc-700"} rounded p-2 h-24 text-zinc-900 dark:text-white`
434              }
435            ),
436            validationErrors.description && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-500 mt-0.5", children: validationErrors.description })
437          ] }),
438          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
439            /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium mb-1 text-zinc-900 dark:text-white", children: t("upload.fields.tags", "Tags") }),
440            /* @__PURE__ */ jsxRuntimeExports.jsx(
441              TagAutocomplete,
442              {
443                value: metadata.tags,
444                onChange: (val) => setMetadata({ ...metadata, tags: val }),
445                placeholder: "piano, virtuoso, 8-bit"
446              }
447            )
448          ] })
449        ] })
450      ] }),
451      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 flex justify-end gap-3", children: [
452        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-6 py-2 bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 rounded-lg font-bold text-zinc-900 dark:text-white", children: t("common.cancel", "Cancel") }),
453        /* @__PURE__ */ jsxRuntimeExports.jsx(
454          "button",
455          {
456            disabled: saving || isValidating || Object.keys(validationErrors).length > 0,
457            onClick: handleSave,
458            className: "px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-bold disabled:opacity-50 flex items-center gap-2",
459            children: saving ? "Saving..." : isValidating ? "Validating..." : "Save Edit"
460          }
461        )
462      ] })
463    ] })
464  ] });
465}
466const ReportModal = ({ isOpen, onClose, trackId, trackTitle, isUnowned, currentUser }) => {
467  const { t } = useTranslation();
468  const [reason, setReason] = reactExports.useState("");
469  const [details, setDetails] = reactExports.useState("");
470  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
471  const [isSuccess, setIsSuccess] = reactExports.useState(false);
472  const [error, setError] = reactExports.useState(null);
473  if (!isOpen) return null;
474  const handleSubmit = async (e) => {
475    e.preventDefault();
476    if (!currentUser) {
477      setError("You must be logged in to file a report.");
478      return;
479    }
480    if (!reason) {
481      setError("Please select a reason for reporting.");
482      return;
483    }
484    setIsSubmitting(true);
485    setError(null);
486    try {
487      const targetType = reason === "wiki_abuse" ? "wiki_edit" : "track";
488      await api.reportContent(String(trackId), targetType, reason, details, currentUser.id);
489      setIsSuccess(true);
490    } catch (err) {
491      setError(err.message || "Failed to submit report. Please try again.");
492    } finally {
493      setIsSubmitting(false);
494    }
495  };
496  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[1000] flex items-center justify-center p-4 bg-black/60 backdrop-blur-md", 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: [
497    /* @__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: [
498      /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2.5", children: [
499        /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 20, className: "text-red-500 animate-pulse" }),
500        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Report Content" })
501      ] }),
502      /* @__PURE__ */ jsxRuntimeExports.jsx(
503        "button",
504        {
505          onClick: onClose,
506          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",
507          children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 })
508        }
509      )
510    ] }),
511    !isSuccess ? /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-6", children: [
512      error && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-5 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: [
513        /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 18, className: "shrink-0 mt-0.5" }),
514        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: error })
515      ] }),
516      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
517        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-black text-zinc-400 dark:text-zinc-500 uppercase tracking-widest mb-3", children: "Select Reason for Report" }),
518        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
519          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3.5 rounded-2xl border cursor-pointer transition-all ${reason === "copyright" ? "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: [
520            /* @__PURE__ */ jsxRuntimeExports.jsx(
521              "input",
522              {
523                type: "radio",
524                name: "reason",
525                value: "copyright",
526                checked: reason === "copyright",
527                onChange: () => setReason("copyright"),
528                className: "mt-1 accent-indigo-600"
529              }
530            ),
531            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
532              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold text-sm", children: "Copyright Infringement" }),
533              /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: [
534                "This MIDI contains intellectual property you own or represent without permission. Please use our official ",
535                /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: `/dmca?trackId=${trackId}`, className: "text-indigo-600 dark:text-indigo-400 font-bold underline", onClick: (e) => e.stopPropagation(), children: "DMCA Takedown Portal" }),
536                " to submit an official claim."
537              ] })
538            ] })
539          ] }),
540          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3.5 rounded-2xl border cursor-pointer transition-all ${reason === "broken" ? "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: [
541            /* @__PURE__ */ jsxRuntimeExports.jsx(
542              "input",
543              {
544                type: "radio",
545                name: "reason",
546                value: "broken",
547                checked: reason === "broken",
548                onChange: () => setReason("broken"),
549                className: "mt-1 accent-indigo-600"
550              }
551            ),
552            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
553              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold text-sm", children: "Broken or Corrupted File" }),
554              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: "The track fails to load, has completely glitched playback, or was uploaded incorrectly." })
555            ] })
556          ] }),
557          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3.5 rounded-2xl border cursor-pointer transition-all ${reason === "duplicate" ? "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: [
558            /* @__PURE__ */ jsxRuntimeExports.jsx(
559              "input",
560              {
561                type: "radio",
562                name: "reason",
563                value: "duplicate",
564                checked: reason === "duplicate",
565                onChange: () => setReason("duplicate"),
566                className: "mt-1 accent-indigo-600"
567              }
568            ),
569            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
570              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-bold text-sm", children: "Duplicate Track" }),
571              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: "This arrangement is already available on the platform." })
572            ] })
573          ] }),
574          isUnowned && /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-start gap-3 p-3.5 rounded-2xl border cursor-pointer transition-all ${reason === "wiki_abuse" ? "bg-amber-50/50 dark:bg-amber-950/20 border-amber-500 text-amber-950 dark:text-amber-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: [
575            /* @__PURE__ */ jsxRuntimeExports.jsx(
576              "input",
577              {
578                type: "radio",
579                name: "reason",
580                value: "wiki_abuse",
581                checked: reason === "wiki_abuse",
582                onChange: () => setReason("wiki_abuse"),
583                className: "mt-1 accent-amber-600"
584              }
585            ),
586            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
587              /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "font-bold text-sm flex items-center gap-1.5 text-amber-600 dark:text-amber-400", children: [
588                /* @__PURE__ */ jsxRuntimeExports.jsx(Scale, { size: 14 }),
589                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Destructive Wiki Edits (Tribunal)" })
590              ] }),
591              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: "The recent wiki edits are destructive, malicious, or spammy." })
592            ] })
593          ] })
594        ] })
595      ] }),
596      /* @__PURE__ */ jsxRuntimeExports.jsx(AnimatePresence, { children: reason === "wiki_abuse" && /* @__PURE__ */ jsxRuntimeExports.jsx(
597        motion.div,
598        {
599          initial: { opacity: 0, height: 0 },
600          animate: { opacity: 1, height: "auto" },
601          exit: { opacity: 0, height: 0 },
602          className: "mb-4 overflow-hidden",
603          children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-900/50 rounded-2xl text-xs text-amber-800 dark:text-amber-300 flex gap-2.5", children: [
604            /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldAlert, { size: 18, className: "shrink-0 mt-0.5" }),
605            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
606              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-black", children: "Hamie Tribunal Escalation:" }),
607              " Reporting destructive wiki edits automatically starts a trial at the Hamie Tribunal. The editor will remain free during review. If found guilty by community votes, all of their recent edits will be automatically reverted."
608            ] })
609          ] })
610        }
611      ) }),
612      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-5", children: [
613        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-black text-zinc-400 dark:text-zinc-500 uppercase tracking-widest mb-2", children: "Details / Supporting Evidence" }),
614        /* @__PURE__ */ jsxRuntimeExports.jsx(
615          "textarea",
616          {
617            value: details,
618            onChange: (e) => setDetails(e.target.value),
619            placeholder: reason === "wiki_abuse" ? "Specify exactly what was vandalized or incorrect in the recent wiki edit..." : "Provide any additional context or proof here...",
620            className: "w-full p-3.5 border border-zinc-200 dark:border-zinc-800 bg-zinc-50/50 dark:bg-zinc-800/50 rounded-2xl focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-zinc-900 dark:text-white transition-all text-sm h-24 placeholder:text-zinc-400 resize-none font-medium",
621            required: reason === "wiki_abuse"
622          }
623        )
624      ] }),
625      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2.5 border-t border-zinc-100 dark:border-zinc-800/50 pt-4", children: [
626        /* @__PURE__ */ jsxRuntimeExports.jsx(
627          "button",
628          {
629            type: "button",
630            onClick: onClose,
631            className: "px-4 py-2.5 text-sm font-bold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-xl transition-all duration-200",
632            disabled: isSubmitting,
633            children: "Cancel"
634          }
635        ),
636        /* @__PURE__ */ jsxRuntimeExports.jsx(
637          "button",
638          {
639            type: "submit",
640            disabled: isSubmitting || !reason || reason === "wiki_abuse" && !details.trim(),
641            className: `px-5 py-2.5 text-sm font-bold rounded-xl transition-all duration-200 shadow-lg disabled:opacity-50 active:scale-98 ${reason === "wiki_abuse" ? "bg-amber-600 text-white hover:bg-amber-700 shadow-amber-500/15" : "bg-red-600 text-white hover:bg-red-700 shadow-red-500/15"}`,
642            children: isSubmitting ? "Submitting..." : reason === "wiki_abuse" ? "Escalate to Tribunal" : "Submit Report"
643          }
644        )
645      ] })
646    ] }) : (
647      // Success View
648      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-8 text-center flex flex-col items-center", children: [
649        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 bg-emerald-50 dark:bg-emerald-950/30 text-emerald-500 rounded-full flex items-center justify-center mb-4 border border-emerald-100 dark:border-emerald-900/40", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 36, className: "animate-bounce" }) }),
650        /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-lg font-bold text-zinc-900 dark:text-white mb-2", children: reason === "wiki_abuse" ? "Case Escalated Successfully!" : "Report Filed Successfully!" }),
651        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400 max-w-sm mb-6 leading-relaxed", children: reason === "wiki_abuse" ? "Your report has successfully created an active case in the Hamie Tribunal. The editor will remain free while the community reviews the edits and votes on the case." : "Thank you for helping keep HamieNet clean. Moderation systems and administrators have been notified of this report." }),
652        /* @__PURE__ */ jsxRuntimeExports.jsx(
653          "button",
654          {
655            onClick: onClose,
656            className: "px-6 py-2.5 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 font-bold text-sm rounded-xl hover:bg-zinc-800 dark:hover:bg-zinc-200 transition-all shadow-md active:scale-98",
657            children: "Done"
658          }
659        )
660      ] })
661    )
662  ] }) });
663};
664function ReuploadModal({ isOpen, onClose, trackId, trackTitle, onSuccess }) {
665  const { t } = useTranslation();
666  const [file, setFile] = reactExports.useState(null);
667  const [uploading, setUploading] = reactExports.useState(false);
668  const [error, setError] = reactExports.useState(null);
669  const [success, setSuccess] = reactExports.useState(false);
670  const fileInputRef = reactExports.useRef(null);
671  if (!isOpen) return null;
672  const handleFileSelect = (e) => {
673    var _a;
674    if (e.target.files && e.target.files[0]) {
675      const f = e.target.files[0];
676      const ext = (_a = f.name.split(".").pop()) == null ? void 0 : _a.toLowerCase();
677      if (!["mid", "midi", "kar"].includes(ext || "")) {
678        setError(t("upload.invalidType", "Please select a valid MIDI file (.mid, .midi, .kar)"));
679        setFile(null);
680        return;
681      }
682      setFile(f);
683      setError(null);
684    }
685  };
686  const handleUpload = async () => {
687    if (!file) return;
688    setUploading(true);
689    setError(null);
690    try {
691      await api.reuploadMidi(trackId, file);
692      setSuccess(true);
693      if (onSuccess) {
694        onSuccess();
695      }
696    } catch (e) {
697      setError(e.message || t("upload.failed", "Upload failed. Please try again."));
698    } finally {
699      setUploading(false);
700    }
701  };
702  const handleClose = () => {
703    setFile(null);
704    setError(null);
705    setSuccess(false);
706    onClose();
707  };
708  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[1000] flex items-center justify-center p-4", children: [
709    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/80 backdrop-blur-sm", onClick: handleClose }),
710    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-md bg-white dark:bg-zinc-900 rounded-xl shadow-2xl flex flex-col overflow-hidden animate-in zoom-in-95", children: [
711      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-4 border-b border-zinc-200 dark:border-zinc-800", children: [
712        /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-lg font-bold text-zinc-900 dark:text-white", children: t("upload.reuploadTitle", "Re-upload MIDI File") }),
713        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleClose, className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "text-zinc-500", size: 18 }) })
714      ] }),
715      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 space-y-4", children: [
716        /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm text-zinc-600 dark:text-zinc-400", children: [
717          t("upload.reuploadWarning", 'The MIDI file for "'),
718          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-900 dark:text-white", children: trackTitle }),
719          t("upload.reuploadWarning2", '" is missing on the server. Please re-upload the original file.')
720        ] }),
721        error && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 flex gap-2 text-red-800 dark:text-red-300 text-xs", children: [
722          /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "shrink-0", size: 16 }),
723          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: error })
724        ] }),
725        success ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center py-6 space-y-3", children: [
726          /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 48, className: "mx-auto text-emerald-500 animate-bounce" }),
727          /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white", children: t("upload.complete", "Re-upload Complete!") }),
728          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500", children: t("upload.reprocessing", "Your MIDI is now queued for reprocessing. It will become public once rendering finishes.") })
729        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: file ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 p-3 bg-zinc-50 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700", children: [
730          /* @__PURE__ */ jsxRuntimeExports.jsx(FileHeadphone, { className: "text-indigo-500 shrink-0", size: 20 }),
731          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs flex-1 truncate text-zinc-800 dark:text-zinc-200", children: file.name }),
732          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setFile(null), className: "text-xs text-red-500 hover:underline", children: t("common.change", "Change") })
733        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
734          "div",
735          {
736            className: "h-36 border-2 border-dashed border-zinc-300 dark:border-zinc-700 hover:border-indigo-500 dark:hover:border-indigo-400 rounded-xl flex flex-col items-center justify-center cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800/30 transition-all",
737            onClick: () => {
738              var _a;
739              return (_a = fileInputRef.current) == null ? void 0 : _a.click();
740            },
741            children: [
742              /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 24, className: "text-zinc-400 mb-2" }),
743              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs font-semibold text-zinc-700 dark:text-zinc-300", children: t("upload.selectFile", "Select MIDI file") }),
744              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 mt-1", children: ".mid, .midi, .kar up to 5MB" }),
745              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", ref: fileInputRef, className: "hidden", accept: ".mid,.midi,.kar", onChange: handleFileSelect })
746            ]
747          }
748        ) })
749      ] }),
750      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 flex justify-end gap-3", children: success ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleClose, className: "px-4 py-1.5 bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 rounded-lg font-semibold text-xs text-zinc-900 dark:text-white", children: t("common.close", "Close") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
751        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleClose, className: "px-4 py-1.5 bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 rounded-lg text-xs font-semibold", children: t("common.cancel", "Cancel") }),
752        /* @__PURE__ */ jsxRuntimeExports.jsx(
753          "button",
754          {
755            disabled: !file || uploading,
756            onClick: handleUpload,
757            className: "px-4 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-xs font-bold disabled:opacity-50 transition-opacity",
758            children: uploading ? t("upload.uploading", "Uploading...") : t("common.upload", "Upload")
759          }
760        )
761      ] }) })
762    ] })
763  ] });
764}
765const TrackEventsModal = ({ isOpen, onClose, instrument, currentTime = 0, tempo = 120, tickDiv = 480 }) => {
766  const { t } = useTranslation();
767  const [events, setEvents] = reactExports.useState([]);
768  const [loading, setLoading] = reactExports.useState(false);
769  const [error, setError] = reactExports.useState(null);
770  const [autoScroll, setAutoScroll] = reactExports.useState(true);
771  const tableContainerRef = reactExports.useRef(null);
772  const activeRowRef = reactExports.useRef(null);
773  reactExports.useEffect(() => {
774    if (isOpen && instrument) {
775      if (instrument.eventsJson) {
776        setLoading(true);
777        setError(null);
778        fetch(instrument.eventsJson).then((res) => {
779          if (!res.ok) throw new Error("Failed to load events");
780          return res.json();
781        }).then((data) => {
782          let currentTick = 0;
783          let currentBpm = tempo;
784          const processedEvents = data.map((evt) => {
785            currentTick += evt.delta;
786            if (evt.event === "Tempo") {
787              const match = evt.data.match(/([\d.]+)\s*BPM/);
788              if (match && match[1]) {
789                currentBpm = parseFloat(match[1]);
790              }
791            }
792            const timeInSeconds = currentTick / tickDiv * (60 / currentBpm);
793            return { ...evt, absTime: timeInSeconds, absTick: currentTick };
794          });
795          setEvents(processedEvents);
796          setLoading(false);
797        }).catch((e) => {
798          console.error(e);
799          setError("Could not load event data.");
800          setLoading(false);
801        });
802      } else {
803        setEvents([
804          { id: 1, delta: 0, event: "Info", data: "No detailed event log available for this track.", absTime: 0 }
805        ]);
806      }
807    } else {
808      setEvents([]);
809    }
810  }, [isOpen, instrument, tempo, tickDiv]);
811  const activeEventIndex = reactExports.useMemo(() => {
812    if (!events.length || currentTime === 0) return -1;
813    for (let i = events.length - 1; i >= 0; i--) {
814      if (events[i].absTime !== void 0 && events[i].absTime <= currentTime) {
815        return i;
816      }
817    }
818    return 0;
819  }, [events, currentTime]);
820  const nextEventInfo = reactExports.useMemo(() => {
821    if (activeEventIndex === -1 || activeEventIndex >= events.length - 1) return null;
822    const currentEvent = events[activeEventIndex];
823    const nextEvent = events[activeEventIndex + 1];
824    if (nextEvent.absTime === void 0 || currentEvent.absTime === void 0 || nextEvent.absTick === void 0 || currentEvent.absTick === void 0) return null;
825    const timeRemaining = nextEvent.absTime - currentTime;
826    const timeFraction = (currentTime - currentEvent.absTime) / (nextEvent.absTime - currentEvent.absTime);
827    const tickDiff = nextEvent.absTick - currentEvent.absTick;
828    const currentTick = currentEvent.absTick + tickDiff * timeFraction;
829    const ticksRemaining = Math.max(0, Math.round(nextEvent.absTick - currentTick));
830    return {
831      timeInSeconds: timeRemaining,
832      ticks: ticksRemaining
833    };
834  }, [events, currentTime, activeEventIndex]);
835  reactExports.useEffect(() => {
836    if (autoScroll && activeRowRef.current && tableContainerRef.current) {
837      const container = tableContainerRef.current;
838      const row = activeRowRef.current;
839      const containerHeight = container.clientHeight;
840      const rowTop = row.offsetTop;
841      const rowHeight = row.clientHeight;
842      const scrollTop = rowTop - containerHeight / 2 + rowHeight / 2;
843      container.scrollTo({
844        top: scrollTop,
845        behavior: "smooth"
846      });
847    }
848  }, [activeEventIndex, autoScroll]);
849  if (!isOpen || !instrument) return null;
850  const instrumentName = instrument.name || getInstrumentName(instrument.program);
851  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-x-0 top-0 bottom-[84px] z-[1000] flex items-end justify-center", children: [
852    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/80 backdrop-blur-sm", onClick: onClose }),
853    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-4xl bg-white dark:bg-zinc-900 rounded-t-xl flex flex-col shadow-2xl animate-in zoom-in-95 border border-zinc-200 dark:border-zinc-800 border-b-0 max-h-[calc(100vh-8rem)] overflow-hidden", children: [
854      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 shrink-0", children: [
855        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
856          /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-lg font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
857            /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "text-indigo-500" }),
858            " ",
859            t("events.title", "Track Events")
860          ] }),
861          /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm text-zinc-500 font-mono", children: [
862            t("events.track", { channel: instrument.trackId + 1, name: instrumentName, count: instrument.notesCount, defaultValue: `Track ${instrument.trackId + 1}: ${instrumentName} (${instrument.notesCount} notes)` }),
863            instrument.midiChannel !== void 0 && instrument.midiChannel !== -1 && ` • MIDI Channel: ${instrument.midiChannel + 1}`
864          ] })
865        ] }),
866        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
867          /* @__PURE__ */ jsxRuntimeExports.jsxs(
868            "button",
869            {
870              onClick: () => setAutoScroll(!autoScroll),
871              className: `flex items-center gap-2 px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${autoScroll ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-400" : "bg-zinc-200 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400"}`,
872              children: [
873                /* @__PURE__ */ jsxRuntimeExports.jsx(Navigation, { size: 14, className: autoScroll ? "animate-pulse" : "" }),
874                autoScroll ? "Auto-scroll On" : "Auto-scroll Off"
875              ]
876            }
877          ),
878          nextEventInfo !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-right", children: [
879            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-400", children: "Next Event In" }),
880            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-lg font-bold font-mono text-indigo-500", children: [
881              nextEventInfo.timeInSeconds.toFixed(1),
882              "s ",
883              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-zinc-400", children: [
884                "(",
885                nextEventInfo.ticks,
886                " ticks)"
887              ] })
888            ] })
889          ] }),
890          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "p-2 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded-full transition-colors text-zinc-500 hover:text-black dark:hover:text-white", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) })
891        ] })
892      ] }),
893      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: tableContainerRef, className: "flex-1 overflow-auto bg-white dark:bg-zinc-900 relative", children: [
894        loading && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-black/50 z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
895          /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin text-indigo-500", size: 32 }),
896          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-zinc-500", children: t("events.loading", "Loading Events...") })
897        ] }) }),
898        error ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-10 text-center text-red-500", children: t("events.error", "Could not load event data.") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left text-sm border-collapse", children: [
899          /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "bg-zinc-100 dark:bg-zinc-800 sticky top-0 z-10 shadow-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
900            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-6 py-3 font-bold text-zinc-600 dark:text-zinc-300 border-b border-zinc-200 dark:border-zinc-700 w-24", children: t("events.colId", "Item ID") }),
901            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-6 py-3 font-bold text-zinc-600 dark:text-zinc-300 border-b border-zinc-200 dark:border-zinc-700 w-32", children: t("events.colDelta", "Delta Time") }),
902            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-6 py-3 font-bold text-zinc-600 dark:text-zinc-300 border-b border-zinc-200 dark:border-zinc-700 w-48", children: t("events.colEvent", "Event") }),
903            /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-6 py-3 font-bold text-zinc-600 dark:text-zinc-300 border-b border-zinc-200 dark:border-zinc-700", children: t("events.colData", "Data (Note, Velocity)") })
904          ] }) }),
905          /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-zinc-100 dark:divide-zinc-800", children: events.map((evt, idx) => {
906            const isActive = idx === activeEventIndex;
907            return /* @__PURE__ */ jsxRuntimeExports.jsxs(
908              "tr",
909              {
910                ref: isActive ? activeRowRef : null,
911                className: `transition-colors font-mono text-xs ${isActive ? "bg-indigo-50 dark:bg-indigo-900/20 shadow-[inset_4px_0_0_0_rgba(99,102,241,1)]" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50"}`,
912                children: [
913                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: `px-6 py-2 ${isActive ? "text-indigo-600 dark:text-indigo-400 font-bold" : "text-zinc-500"}`, children: evt.id }),
914                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: `px-6 py-2 ${isActive ? "text-indigo-700 dark:text-indigo-300 font-bold" : "text-zinc-700 dark:text-zinc-300"}`, children: evt.delta }),
915                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-6 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-0.5 rounded text-[10px] font-bold uppercase ${evt.event === "Note on" ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400" : evt.event === "Note off" ? "bg-zinc-200 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400" : "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400"}`, children: evt.event }) }),
916                  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: `px-6 py-2 font-medium ${isActive ? "text-indigo-900 dark:text-indigo-100" : "text-zinc-800 dark:text-zinc-200"}`, children: evt.data === "No detailed event log available for this track." ? t("events.noLog", "No detailed event log available for this track.") : evt.data })
917                ]
918              },
919              evt.id || idx
920            );
921          }) })
922        ] })
923      ] }),
924      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 shrink-0 flex justify-between items-center text-xs text-zinc-500", children: [
925        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("events.showing", { count: events.length, defaultValue: `Showing ${events.length} events` }) }),
926        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-4 py-2 bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 rounded-lg font-bold text-zinc-700 dark:text-zinc-300 transition-colors", children: t("events.close", "Close Viewer") })
927      ] })
928    ] })
929  ] });
930};
931const PianoRollGame = ({
932  data,
933  melodyData,
934  currentTime,
935  isPlaying,
936  colorMap,
937  selectedTrackIndex,
938  processedInstruments,
939  uniqueNames,
940  onPlayPause,
941  onSeek,
942  toggleGameMode,
943  onGameModeMute,
944  trackId,
945  userId,
946  userName,
947  onAuthRequested,
948  duration,
949  tempo,
950  timeSignature,
951  isLoadingAudio,
952  isAudioLoaded,
953  onSelectTrack,
954  track,
955  soundfont,
956  melodySource,
957  playbackRate
958}) => {
959  const containerRef = reactExports.useRef(null);
960  const canvasRef = reactExports.useRef(null);
961  const requestRef = reactExports.useRef(null);
962  const lastTimeRef = reactExports.useRef(currentTime);
963  const lastTickRef = reactExports.useRef(0);
964  const particlesRef = reactExports.useRef([]);
965  const prevActiveNotesRef = reactExports.useRef(/* @__PURE__ */ new Set());
966  const starFieldRef = reactExports.useRef([]);
967  const targetStartTimeRef = reactExports.useRef(null);
968  reactExports.useEffect(() => {
969    lastTimeRef.current = currentTime;
970    lastTickRef.current = performance.now();
971  }, [currentTime]);
972  const [gameCountdown, setGameCountdown] = reactExports.useState(null);
973  const countdownStartTimeRef = reactExports.useRef(null);
974  const countdownDurationRef = reactExports.useRef(0);
975  const hasCountedDownRef = reactExports.useRef(false);
976  const countdownIntervalRef = reactExports.useRef(null);
977  const [isGamePaused, setIsGamePaused] = reactExports.useState(false);
978  const [gameScore, setGameScore] = reactExports.useState(0);
979  const gameScoreRef = reactExports.useRef(0);
980  const [gameCombo, setGameCombo] = reactExports.useState(0);
981  const gameComboRef = reactExports.useRef(0);
982  const [maxCombo, setMaxCombo] = reactExports.useState(0);
983  const maxComboRef = reactExports.useRef(0);
984  const [totalNotes, setTotalNotes] = reactExports.useState(0);
985  const [hitNotesCount, setHitNotesCount] = reactExports.useState(0);
986  const hitNotesCountRef = reactExports.useRef(0);
987  const [showEndScreen, setShowEndScreen] = reactExports.useState(false);
988  const [leaderboard, setLeaderboard] = reactExports.useState([]);
989  const [, setIsSubmittingScore] = reactExports.useState(false);
990  const [melodyTrackNames, setMelodyTrackNames] = reactExports.useState(/* @__PURE__ */ new Set());
991  const [melodyTrackIndices, setMelodyTrackIndices] = reactExports.useState(/* @__PURE__ */ new Set());
992  const melodyTrackIndicesRef = reactExports.useRef(/* @__PURE__ */ new Set());
993  const [currentMelodyTrackIdx, setCurrentMelodyTrackIdx] = reactExports.useState(-1);
994  const currentMelodyTrackRef = reactExports.useRef(-1);
995  reactExports.useEffect(() => {
996    melodyTrackIndicesRef.current = melodyTrackIndices;
997  }, [melodyTrackIndices]);
998  const getTrackIdxForName = reactExports.useCallback((name, noteTrackIdx) => {
999    if (noteTrackIdx !== void 0 && processedInstruments) {
1000      const musicalInstruments = processedInstruments.filter((inst) => inst.midiChannel !== -1);
1001      const numericIdx = Number(noteTrackIdx);
1002      if (numericIdx >= 0 && numericIdx < musicalInstruments.length) {
1003        const targetInst = musicalInstruments[numericIdx];
1004        return processedInstruments.findIndex((inst) => inst.trackId === targetInst.trackId);
1005      }
1006    }
1007    if (!processedInstruments) return -1;
1008    const idx = processedInstruments.findIndex(
1009      (inst) => inst.originalName === name || inst.displayName === name || inst.name === name
1010    );
1011    return idx;
1012  }, [processedInstruments]);
1013  reactExports.useEffect(() => {
1014    if (melodyData && melodyData.length > 0 && processedInstruments) {
1015      console.log("[DEBUG] --- PianoRollGame Melody Mapping Sample ---");
1016      melodyData.slice(0, 5).forEach((note, i) => {
1017        var _a;
1018        const finalIdx = getTrackIdxForName(note.i, note.trackIdx);
1019        const trackName = ((_a = processedInstruments[finalIdx]) == null ? void 0 : _a.displayName) || "Unknown";
1020        console.log(`[DEBUG] Note ${i}: JSON Name[${note.i}] JSON Id[${note.trackIdx}] -> Mapped Index[${finalIdx}] (${trackName})`);
1021      });
1022      console.log("----------------------------------------------------");
1023    }
1024  }, [melodyData, processedInstruments, getTrackIdxForName]);
1025  const hitNotesRef = reactExports.useRef(/* @__PURE__ */ new Set());
1026  const missedNotesRef = reactExports.useRef(/* @__PURE__ */ new Set());
1027  const activeHoldsRef = reactExports.useRef(/* @__PURE__ */ new Map());
1028  const dataRef = reactExports.useRef(data);
1029  const isPlayingRef = reactExports.useRef(isPlaying);
1030  const processedGameDataRef = reactExports.useRef([]);
1031  const [octaveTip, setOctaveTip] = reactExports.useState(null);
1032  const [gameSpeed, setGameSpeed] = reactExports.useState(1);
1033  const gameSpeedRef = reactExports.useRef(1);
1034  const prevMelodyDataRef = reactExports.useRef(melodyData);
1035  const prevMelodySourceRef = reactExports.useRef(melodySource);
1036  reactExports.useEffect(() => {
1037    dataRef.current = data;
1038  }, [data]);
1039  reactExports.useEffect(() => {
1040    if (isPlaying) {
1041      const elapsed = (performance.now() - lastTickRef.current) / 1e3;
1042      lastTimeRef.current += elapsed * gameSpeedRef.current;
1043      lastTickRef.current = performance.now();
1044    }
1045    gameSpeedRef.current = gameSpeed;
1046    window.dispatchEvent(new CustomEvent("setPlaybackRate", { detail: gameSpeed }));
1047  }, [gameSpeed, isPlaying]);
1048  reactExports.useEffect(() => {
1049    if (isPlaying) {
1050      lastTickRef.current = performance.now();
1051    } else {
1052      const elapsed = (performance.now() - lastTickRef.current) / 1e3;
1053      lastTimeRef.current += elapsed * gameSpeedRef.current;
1054    }
1055  }, [isPlaying]);
1056  reactExports.useEffect(() => {
1057    if (trackId) {
1058      api.getHighscores(trackId, "piano-roll", userId).then(setLeaderboard).catch(console.error);
1059    }
1060  }, [trackId, userId]);
1061  const [bgTheme, setBgTheme] = reactExports.useState({ color1: "#000000", color2: "#000000" });
1062  const generateBgTheme = reactExports.useCallback(() => {
1063    const color = "#000000";
1064    setBgTheme({ color1: color, color2: color });
1065  }, [trackId]);
1066  reactExports.useEffect(() => {
1067    generateBgTheme();
1068  }, [generateBgTheme]);
1069  const resetGame = reactExports.useCallback(() => {
1070    generateBgTheme();
1071    setGameScore(0);
1072    gameScoreRef.current = 0;
1073    setGameCombo(0);
1074    gameComboRef.current = 0;
1075    setMaxCombo(0);
1076    maxComboRef.current = 0;
1077    setHitNotesCount(0);
1078    hitNotesCountRef.current = 0;
1079    setShowEndScreen(false);
1080    setIsGamePaused(false);
1081    hitNotesRef.current.clear();
1082    missedNotesRef.current.clear();
1083    activeHoldsRef.current.clear();
1084    setOctaveTip(null);
1085    setGameSpeed(1);
1086    if (onSeek) onSeek(0);
1087    setGameCountdown(null);
1088  }, [onSeek, generateBgTheme]);
1089  reactExports.useEffect(() => {
1090    if (melodyData && melodyData.length > 0) {
1091      const mNames = /* @__PURE__ */ new Set();
1092      const mIndices = /* @__PURE__ */ new Set();
1093      console.log("[PianoRollGame] --- Melody Muting Sync ---");
1094      melodyData.forEach((note) => {
1095        mNames.add(note.i);
1096        const idx = getTrackIdxForName(note.i, note.trackIdx);
1097        if (idx !== -1) {
1098          mIndices.add(idx);
1099        }
1100      });
1101      console.log("[PianoRollGame] mNames (from melodyData):", Array.from(mNames));
1102      if (processedInstruments) {
1103        if (mIndices.size === 0) {
1104          console.error("[PianoRollGame] FAILED to match any melody tracks for muting!");
1105          console.log("[PianoRollGame] Instrument List:", processedInstruments.map((i) => i.originalName));
1106        } else {
1107          console.log("[PianoRollGame] Final Mute Indices:", Array.from(mIndices));
1108        }
1109      }
1110      setMelodyTrackNames(mNames);
1111      setMelodyTrackIndices(mIndices);
1112      if (onGameModeMute) {
1113        onGameModeMute(Array.from(mIndices));
1114      }
1115    } else {
1116      const trackStats = {};
1117      const grouped = {};
1118      const skylineCounts = {};
1119      data.forEach((note) => {
1120        if (!trackStats[note.i]) {
1121          trackStats[note.i] = { notes: [], polyphonicCount: 0, minPitch: 128, maxPitch: -1, totalDuration: 0, name: note.i };
1122        }
1123        trackStats[note.i].notes.push(note);
1124        trackStats[note.i].minPitch = Math.min(trackStats[note.i].minPitch, note.n);
1125        trackStats[note.i].maxPitch = Math.max(trackStats[note.i].maxPitch, note.n);
1126        trackStats[note.i].totalDuration += note.d;
1127      });
1128      Object.values(trackStats).forEach((stats) => {
1129        stats.notes.sort((a, b) => a.t - b.t);
1130        let overlaps = 0;
1131        for (let i = 0; i < stats.notes.length - 1; i++) {
1132          if (stats.notes[i].t + stats.notes[i].d > stats.notes[i + 1].t + 0.05) {
1133            overlaps++;
1134          }
1135        }
1136        stats.polyphonicCount = overlaps;
1137      });
1138      data.forEach((note) => {
1139        const nameLower = ((note == null ? void 0 : note.i) || "").toLowerCase();
1140        if (nameLower.includes("drum") || nameLower.includes("perc") || nameLower.includes("kit") || nameLower.includes("cymbal")) return;
1141        if (note.d > 4) return;
1142        const timeKey = Math.round(note.t * 20);
1143        if (!grouped[timeKey]) grouped[timeKey] = [];
1144        grouped[timeKey].push(note);
1145      });
1146      Object.values(grouped).forEach((group) => {
1147        let maxPitch = -1;
1148        let bestNote = null;
1149        group.forEach((n) => {
1150          if (n.n > maxPitch) {
1151            maxPitch = n.n;
1152            bestNote = n;
1153          }
1154        });
1155        if (bestNote) {
1156          skylineCounts[bestNote.i] = (skylineCounts[bestNote.i] || 0) + 1;
1157        }
1158      });
1159      const trackScores = {};
1160      const maxSkyline = Math.max(1, ...Object.values(skylineCounts));
1161      Object.values(trackStats).forEach((stats) => {
1162        const nameLower = ((stats == null ? void 0 : stats.name) || "").toLowerCase();
1163        let score = (skylineCounts[stats.name] || 0) / maxSkyline * 100;
1164        if (nameLower.includes("melody") || nameLower.includes("lead") || nameLower.includes("vocal") || nameLower.includes("solo") || nameLower.includes("right")) {
1165          score += 50;
1166        }
1167        if (nameLower.includes("bass") || nameLower.includes("pad") || nameLower.includes("chord") || nameLower.includes("left")) {
1168          score -= 30;
1169        }
1170        if (nameLower.includes("drum") || nameLower.includes("perc") || nameLower.includes("kit") || nameLower.includes("cymbal")) {
1171          score -= 100;
1172        }
1173        const polyRatio = stats.notes.length > 0 ? stats.polyphonicCount / stats.notes.length : 0;
1174        if (polyRatio > 0.5) {
1175          score -= 40;
1176        } else if (polyRatio > 0.2) {
1177          score -= 20;
1178        } else {
1179          score += 10;
1180        }
1181        const range = stats.maxPitch - stats.minPitch;
1182        if (range < 7) {
1183          score -= 30;
1184        } else if (range > 48) {
1185          score -= 20;
1186        }
1187        trackScores[stats.name] = score;
1188      });
1189      const melodyTracks = /* @__PURE__ */ new Set();
1190      let bestTrack = null;
1191      let bestScore = -Infinity;
1192      for (const [t, score] of Object.entries(trackScores)) {
1193        if (score > bestScore) {
1194          bestScore = score;
1195          bestTrack = t;
1196        }
1197        if (score > 40) {
1198          melodyTracks.add(t);
1199        }
1200      }
1201      if (melodyTracks.size === 0 && bestTrack) {
1202        melodyTracks.add(bestTrack);
1203      }
1204      setMelodyTrackNames(melodyTracks);
1205      const mIndices = /* @__PURE__ */ new Set();
1206      if (processedInstruments) {
1207        processedInstruments.forEach((inst, idx) => {
1208          if (melodyTracks.has(inst.originalName)) {
1209            mIndices.add(idx);
1210          }
1211        });
1212      }
1213      setMelodyTrackIndices(mIndices);
1214      if (onGameModeMute) {
1215        onGameModeMute(Array.from(mIndices));
1216      }
1217    }
1218    setGameScore(0);
1219    gameScoreRef.current = 0;
1220    setGameCombo(0);
1221    gameComboRef.current = 0;
1222    setMaxCombo(0);
1223    maxComboRef.current = 0;
1224    setHitNotesCount(0);
1225    hitNotesCountRef.current = 0;
1226    setShowEndScreen(false);
1227    setIsGamePaused(false);
1228    hitNotesRef.current.clear();
1229    missedNotesRef.current.clear();
1230    activeHoldsRef.current.clear();
1231    setOctaveTip(null);
1232    setGameSpeed(1);
1233    if (isPlaying && onPlayPause) onPlayPause(false);
1234    const isFreshLoad = prevMelodyDataRef.current !== melodyData || prevMelodySourceRef.current !== melodySource;
1235    if (onSeek && isFreshLoad) {
1236      onSeek(0);
1237      lastTimeRef.current = 0;
1238    }
1239    prevMelodyDataRef.current = melodyData;
1240    prevMelodySourceRef.current = melodySource;
1241    setGameCountdown(null);
1242    const currentMissedNotes = missedNotesRef.current;
1243    return () => {
1244      if (userId && gameScoreRef.current > 0 && trackId) {
1245        const accuracy = hitNotesCountRef.current / (hitNotesCountRef.current + currentMissedNotes.size || 1);
1246        api.postHighscore(trackId, {
1247          userId,
1248          userName: "Me",
1249          score: gameScoreRef.current,
1250          accuracy,
1251          maxCombo: maxComboRef.current
1252        }).catch(console.error);
1253      }
1254      if (onGameModeMute) {
1255        onGameModeMute(null);
1256      }
1257    };
1258  }, [melodyData, processedInstruments, uniqueNames]);
1259  reactExports.useEffect(() => {
1260    if (melodyData && melodyData.length > 0) {
1261      const rawMelodyNotes2 = [...melodyData].sort((a, b) => a.t - b.t);
1262      const finalData = rawMelodyNotes2.map((n, i) => {
1263        const resolvedIdx = getTrackIdxForName(n.i, n.trackIdx);
1264        const mappedI = resolvedIdx !== -1 && uniqueNames && uniqueNames[resolvedIdx] ? uniqueNames[resolvedIdx] : n.i;
1265        return {
1266          ...n,
1267          i: mappedI,
1268          trackIdx: resolvedIdx,
1269          id: `${n.t}-${n.n}-${i}`
1270        };
1271      });
1272      processedGameDataRef.current = finalData;
1273      return;
1274    }
1275    if (melodyTrackNames.size === 0) return;
1276    const grouped = {};
1277    data.forEach((note) => {
1278      if (!melodyTrackNames.has(note.i)) return;
1279      const timeKey = Math.round(note.t * 20);
1280      if (!grouped[timeKey]) grouped[timeKey] = [];
1281      grouped[timeKey].push(note);
1282    });
1283    const rawMelodyNotes = [];
1284    Object.values(grouped).forEach((group) => {
1285      let maxPitch = -1;
1286      let bestNote = null;
1287      group.forEach((n) => {
1288        if (n.n > maxPitch) {
1289          maxPitch = n.n;
1290          bestNote = n;
1291        }
1292      });
1293      if (bestNote) {
1294        rawMelodyNotes.push(bestNote);
1295      }
1296    });
1297    rawMelodyNotes.sort((a, b) => a.t - b.t);
1298    const filteredNotes = [];
1299    if (rawMelodyNotes.length > 0) {
1300      filteredNotes.push(rawMelodyNotes[0]);
1301      for (let i = 1; i < rawMelodyNotes.length; i++) {
1302        const current = rawMelodyNotes[i];
1303        const prev = filteredNotes[filteredNotes.length - 1];
1304        const next = i < rawMelodyNotes.length - 1 ? rawMelodyNotes[i + 1] : null;
1305        let isOutlier = false;
1306        if (prev && next) {
1307          const prevPitch = prev.n;
1308          const nextPitch = next.n;
1309          const currPitch = current.n;
1310          if (prevPitch - currPitch > 15 && nextPitch - currPitch > 15 && Math.abs(prevPitch - nextPitch) < 12) {
1311            isOutlier = true;
1312          }
1313        }
1314        if (!isOutlier) {
1315          filteredNotes.push(current);
1316        }
1317      }
1318    }
1319    const finalFilteredNotes = filteredNotes.map((n, i) => {
1320      const resolvedIdx = getTrackIdxForName(n.i, n.trackIdx);
1321      const mappedI = resolvedIdx !== -1 && uniqueNames && uniqueNames[resolvedIdx] ? uniqueNames[resolvedIdx] : n.i;
1322      return {
1323        ...n,
1324        i: mappedI,
1325        trackIdx: resolvedIdx,
1326        id: `${n.t}-${n.n}-${i}`
1327      };
1328    });
1329    processedGameDataRef.current = finalFilteredNotes;
1330    setTotalNotes(finalFilteredNotes.length);
1331  }, [data, melodyTrackNames, melodyData, getTrackIdxForName, uniqueNames]);
1332  const renderPodiumLeaderboard = (leaderboardData) => {
1333    if (leaderboardData.length === 0) {
1334      return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-8 text-white/20 text-xs italic", children: "No scores yet" });
1335    }
1336    const top3 = leaderboardData.slice(0, 3);
1337    const rest = leaderboardData.slice(3);
1338    const podiumOrder = [];
1339    if (top3[1]) podiumOrder.push({ ...top3[1], rank: 2 });
1340    if (top3[0]) podiumOrder.push({ ...top3[0], rank: 1 });
1341    if (top3[2]) podiumOrder.push({ ...top3[2], rank: 3 });
1342    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col w-full h-full", children: [
1343      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center items-end gap-2 sm:gap-4 mb-4 mt-6 shrink-0 px-2", children: podiumOrder.map((player) => {
1344        const isFirst = player.rank === 1;
1345        const isSecond = player.rank === 2;
1346        const heightClass = isFirst ? "h-28 sm:h-36" : isSecond ? "h-20 sm:h-28" : "h-16 sm:h-24";
1347        const avatarSize = isFirst ? "w-14 h-14 sm:w-16 sm:h-16" : isSecond ? "w-10 h-10 sm:w-12 sm:h-12" : "w-8 h-8 sm:w-10 sm:h-10";
1348        const medal = isFirst ? "🥇" : isSecond ? "🥈" : "🥉";
1349        const glowClass = isFirst ? "shadow-[0_0_30px_rgba(250,204,21,0.2)] border-yellow-400/50 bg-gradient-to-t from-yellow-500/20 to-transparent" : isSecond ? "shadow-[0_0_20px_rgba(161,161,170,0.15)] border-zinc-300/50 bg-gradient-to-t from-zinc-400/20 to-transparent" : "shadow-[0_0_20px_rgba(251,146,60,0.15)] border-orange-400/50 bg-gradient-to-t from-orange-500/20 to-transparent";
1350        const nameSize = isFirst ? "text-base sm:text-lg font-black" : "text-sm sm:text-base font-bold";
1351        const scoreSize = isFirst ? "text-lg sm:text-xl" : "text-base sm:text-lg";
1352        return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col items-center justify-end flex-1 max-w-[110px] ${isFirst ? "z-10 -mt-6" : "z-0"}`, children: [
1353          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative mb-2", children: [
1354            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `${avatarSize} rounded-full bg-zinc-800 flex items-center justify-center border-2 ${isFirst ? "border-yellow-400" : isSecond ? "border-zinc-300" : "border-orange-400"} overflow-hidden shadow-lg`, children: player.avatarUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: player.avatarUrl, alt: player.userName || "Player", className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white/50 text-lg font-bold", children: (player.userName || "P").charAt(0).toUpperCase() }) }),
1355            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute ${isFirst ? "-top-3 -right-3 text-3xl" : "-top-2 -right-2 text-2xl"} drop-shadow-md filter`, children: medal })
1356          ] }),
1357          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `w-full ${heightClass} ${glowClass} rounded-t-lg border-t-2 border-x border-x-white/5 flex flex-col items-center pt-2 px-1 ${player.isCurrentUser ? "bg-indigo-500/20 border-indigo-400/50" : ""}`, children: [
1358            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 justify-center w-full px-1", children: [
1359              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-white truncate ${nameSize}`, title: player.userName, children: player.userName }),
1360              player.countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: player.countryCode, size: 14 })
1361            ] }),
1362            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono font-bold text-emerald-400 mt-0.5 ${scoreSize}`, children: player.score.toLocaleString() })
1363          ] })
1364        ] }, player.id);
1365      }) }),
1366      rest.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto custom-scrollbar min-h-0 bg-black/40 rounded-lg p-1.5 border border-white/5", children: rest.map((entry, idx) => {
1367        const rank = idx + 4;
1368        return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 hover:bg-white/5 rounded border-b border-white/5 last:border-0 ${entry.isCurrentUser ? "bg-indigo-500/20 border-indigo-500/30" : ""}`, children: [
1369          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
1370            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold w-4 text-center ${entry.isCurrentUser ? "text-indigo-300" : "text-white/30"}`, children: entry.rank || rank }),
1371            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 max-w-[140px]", children: [
1372              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-sm font-medium truncate ${entry.isCurrentUser ? "text-indigo-200" : "text-white/80"}`, children: entry.userName }),
1373              entry.countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: entry.countryCode, size: 12 })
1374            ] })
1375          ] }),
1376          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-sm font-mono font-bold ${entry.isCurrentUser ? "text-emerald-400" : "text-emerald-500/80"}`, children: entry.score.toLocaleString() })
1377        ] }, entry.id);
1378      }) })
1379    ] });
1380  };
1381  const [hasSubmittedGuestScore, setHasSubmittedGuestScore] = reactExports.useState(false);
1382  const handleGameEnd = reactExports.useCallback(async () => {
1383    if (!trackId) return;
1384    try {
1385      const scores = await api.getHighscores(trackId, "piano-roll", userId);
1386      setLeaderboard(scores);
1387    } catch (e) {
1388      console.error(e);
1389    }
1390    if (userId && gameScoreRef.current > 0) {
1391      setIsSubmittingScore(true);
1392      try {
1393        await api.postHighscore(trackId, {
1394          userId,
1395          userName: userName || "Me",
1396          score: gameScoreRef.current,
1397          accuracy: hitNotesCountRef.current / totalNotes,
1398          maxCombo: maxComboRef.current
1399        });
1400        const scores = await api.getHighscores(trackId, "piano-roll", userId);
1401        setLeaderboard(scores);
1402      } catch (e) {
1403        console.error(e);
1404      }
1405      setIsSubmittingScore(false);
1406    }
1407  }, [trackId, userId, userName, totalNotes]);
1408  const submitGuestScore = async () => {
1409    if (!trackId || gameScoreRef.current <= 0 || hasSubmittedGuestScore) return;
1410    setIsSubmittingScore(true);
1411    try {
1412      await api.postHighscore(trackId, {
1413        userName: "Community Contributor",
1414        score: gameScoreRef.current,
1415        accuracy: hitNotesCountRef.current / totalNotes,
1416        maxCombo: maxComboRef.current
1417      });
1418      const scores = await api.getHighscores(trackId, "piano-roll", userId);
1419      setLeaderboard(scores);
1420      setHasSubmittedGuestScore(true);
1421    } catch (e) {
1422      console.error(e);
1423    }
1424    setIsSubmittingScore(false);
1425  };
1426  reactExports.useEffect(() => {
1427    if (isPlayingRef.current && !isPlaying && currentTime > duration - 1.5) {
1428      setShowEndScreen(true);
1429      handleGameEnd();
1430    }
1431    isPlayingRef.current = isPlaying;
1432  }, [isPlaying, currentTime, duration, handleGameEnd]);
1433  reactExports.useEffect(() => {
1434    if (userId && showEndScreen && gameScoreRef.current > 0 && !hasSubmittedGuestScore) {
1435      const isAlreadyInLeaderboard = leaderboard.some((l) => l.isCurrentUser);
1436      if (!isAlreadyInLeaderboard) {
1437        handleGameEnd();
1438      }
1439    }
1440  }, [userId, showEndScreen, hasSubmittedGuestScore, leaderboard, handleGameEnd]);
1441  reactExports.useEffect(() => {
1442    if (!onGameModeMute) return;
1443    const checkForMuteUpdates = () => {
1444      let time = lastTimeRef.current;
1445      if (isPlayingRef.current) {
1446        time += (performance.now() - lastTickRef.current) / 1e3 * gameSpeedRef.current;
1447      }
1448      const gameData = processedGameDataRef.current;
1449      if (!gameData || gameData.length === 0) return;
1450      const activeTracks = /* @__PURE__ */ new Set();
1451      const nextTracks = /* @__PURE__ */ new Set();
1452      let nextTime = Infinity;
1453      let bestIdx = -1;
1454      for (const note of gameData) {
1455        const idx = note.trackIdx;
1456        if (idx === void 0 || idx === -1) continue;
1457        if (note.t <= time && note.t + note.d > time) {
1458          activeTracks.add(idx);
1459          if (bestIdx === -1) bestIdx = idx;
1460        }
1461        if (note.t > time) {
1462          if (nextTime === Infinity) nextTime = note.t;
1463          if (note.t === nextTime) {
1464            nextTracks.add(idx);
1465          }
1466        }
1467        if (nextTime !== Infinity && note.t > nextTime) break;
1468      }
1469      const finalMuteTracks = activeTracks.size > 0 ? activeTracks : nextTracks;
1470      if (bestIdx === -1 && nextTracks.size > 0) {
1471        bestIdx = Array.from(nextTracks)[0];
1472      }
1473      const bestTrackIdx = bestIdx;
1474      const currentMuted = Array.from(melodyTrackIndicesRef.current);
1475      const newMuted = Array.from(finalMuteTracks);
1476      currentMuted.sort();
1477      newMuted.sort();
1478      const isDifferent = currentMuted.length !== newMuted.length || !newMuted.every((v, i) => v === currentMuted[i]);
1479      if (isDifferent || bestTrackIdx !== currentMelodyTrackRef.current) {
1480        setMelodyTrackIndices(finalMuteTracks);
1481        currentMelodyTrackRef.current = bestTrackIdx;
1482        setCurrentMelodyTrackIdx(bestTrackIdx);
1483        if (onGameModeMute) {
1484          console.log(`[PianoRollGame] Muting Melody Tracks:`, newMuted);
1485          onGameModeMute(newMuted);
1486        }
1487        if (onSelectTrack && bestTrackIdx !== -1) {
1488          onSelectTrack(bestTrackIdx);
1489        }
1490      }
1491    };
1492    const interval = setInterval(checkForMuteUpdates, 100);
1493    return () => clearInterval(interval);
1494  }, [onGameModeMute, onSelectTrack, processedInstruments]);
1495  reactExports.useEffect(() => {
1496  }, [gameSpeed]);
1497  const { audioContextRef, playNote: basePlayNote, stopNote: baseStopNote, stopAllNotes, previewActiveNotesRef } = useSoundfontKeyboard(selectedTrackIndex, processedInstruments, currentMelodyTrackIdx, soundfont);
1498  const handleGameStart = reactExports.useCallback((force = false, startFromBeat) => {
1499    var _a;
1500    synth.getAudioContext().resume().catch(() => {
1501    });
1502    if (((_a = audioContextRef.current) == null ? void 0 : _a.state) === "suspended") {
1503      audioContextRef.current.resume().catch(() => {
1504      });
1505    }
1506    if (!isAudioLoaded) {
1507      setWaitingForAudio(true);
1508      if (!isPlaying && onPlayPause) {
1509        onPlayPause(true);
1510      }
1511      return;
1512    }
1513    if (isLoadingAudio) {
1514      setWaitingForAudio(true);
1515      return;
1516    }
1517    if (onPlayPause && (!isPlaying || force)) {
1518      if (!isGamePaused) generateBgTheme();
1519      const startTime = targetStartTimeRef.current !== null ? targetStartTimeRef.current : currentTime;
1520      targetStartTimeRef.current = null;
1521      lastTimeRef.current = startTime;
1522      lastTickRef.current = performance.now();
1523      const currentSpeed = gameSpeedRef.current;
1524      const currentTempo = (tempo || 120) * currentSpeed;
1525      const beatsPerMeasure = timeSignature && timeSignature.length > 0 ? timeSignature[0] : 4;
1526      const totalBeats = startFromBeat !== void 0 ? startFromBeat : beatsPerMeasure * 2;
1527      const beatDurationMs = 60 / currentTempo * 1e3;
1528      const totalDurationMs = totalBeats * beatDurationMs;
1529      setGameCountdown(totalBeats);
1530      countdownStartTimeRef.current = performance.now();
1531      countdownDurationRef.current = totalDurationMs;
1532      hasCountedDownRef.current = true;
1533      setIsGamePaused(false);
1534      let count = totalBeats;
1535      const playTick = (isFirstBeatOfMeasure) => {
1536        if (!audioContextRef.current) return;
1537        const ctx = audioContextRef.current;
1538        if (ctx.state === "suspended") {
1539          ctx.resume().catch(() => {
1540          });
1541        }
1542        const osc = ctx.createOscillator();
1543        const gain = ctx.createGain();
1544        osc.type = "sine";
1545        osc.frequency.setValueAtTime(isFirstBeatOfMeasure ? 880 : 440, ctx.currentTime);
1546        gain.gain.setValueAtTime(0, ctx.currentTime);
1547        gain.gain.linearRampToValueAtTime(0.5, ctx.currentTime + 0.01);
1548        gain.gain.exponentialRampToValueAtTime(1e-3, ctx.currentTime + 0.1);
1549        osc.connect(gain);
1550        gain.connect(ctx.destination);
1551        osc.start();
1552        osc.stop(ctx.currentTime + 0.1);
1553      };
1554      if (startFromBeat === void 0) playTick(true);
1555      if (countdownIntervalRef.current) clearInterval(countdownIntervalRef.current);
1556      if (isPlaying) {
1557        if (onPlayPause) onPlayPause(false);
1558      }
1559      countdownIntervalRef.current = setInterval(() => {
1560        count--;
1561        if (count > 0) {
1562          setGameCountdown(count);
1563          playTick(count % beatsPerMeasure === 0);
1564        } else if (count === 0) {
1565          setGameCountdown("GO!");
1566          playTick(true);
1567        } else {
1568          if (countdownIntervalRef.current) clearInterval(countdownIntervalRef.current);
1569          setGameCountdown(null);
1570          countdownStartTimeRef.current = null;
1571          onPlayPause(true);
1572        }
1573      }, beatDurationMs);
1574    }
1575  }, [isAudioLoaded, isLoadingAudio, isPlaying, onPlayPause, tempo, timeSignature, audioContextRef, currentTime, generateBgTheme, isGamePaused]);
1576  const togglePause = reactExports.useCallback(() => {
1577    if (!isPlaying && gameCountdown === null && !showEndScreen && !isGamePaused) {
1578      return;
1579    }
1580    if (gameCountdown !== null) {
1581      if (isGamePaused) {
1582        if (typeof gameCountdown === "number") {
1583          handleGameStart(true, gameCountdown);
1584        } else if (gameCountdown === "GO!") {
1585          handleGameStart(true, 0);
1586        }
1587      } else {
1588        setIsGamePaused(true);
1589        if (countdownIntervalRef.current) clearInterval(countdownIntervalRef.current);
1590      }
1591    } else {
1592      if (isPlaying) {
1593        if (onPlayPause) onPlayPause(false);
1594        setIsGamePaused(true);
1595      } else {
1596        if (onPlayPause) onPlayPause(true);
1597        setIsGamePaused(false);
1598      }
1599    }
1600  }, [gameCountdown, isGamePaused, isPlaying, onPlayPause, showEndScreen, handleGameStart]);
1601  const [waitingForAudio, setWaitingForAudio] = reactExports.useState(false);
1602  reactExports.useEffect(() => {
1603    if (isPlayingRef.current && !isPlaying && currentTime > 0.1 && gameCountdown === null && !showEndScreen) {
1604      setIsGamePaused(true);
1605    } else if (isPlaying && isGamePaused) {
1606      setIsGamePaused(false);
1607    }
1608    isPlayingRef.current = isPlaying;
1609  }, [isPlaying, currentTime, gameCountdown, showEndScreen, isGamePaused]);
1610  reactExports.useEffect(() => {
1611    if (waitingForAudio && isAudioLoaded && !isLoadingAudio) {
1612      setWaitingForAudio(false);
1613      handleGameStart(true);
1614    }
1615  }, [waitingForAudio, isAudioLoaded, isLoadingAudio, handleGameStart]);
1616  reactExports.useEffect(() => {
1617    if (isPlaying && currentTime < 0.1 && !hasCountedDownRef.current) {
1618      handleGameStart(true);
1619    } else if (!isPlaying && currentTime < 0.1 && gameCountdown === null) {
1620      hasCountedDownRef.current = false;
1621    }
1622  }, [isPlaying, currentTime, gameCountdown, handleGameStart]);
1623  const playNote = reactExports.useCallback((midi2) => {
1624    basePlayNote(midi2);
1625    const currentT = lastTimeRef.current;
1626    const threshold = 0.2;
1627    const upcomingNotes = processedGameDataRef.current.filter(
1628      (n) => n.n === midi2 && Math.abs(n.t - currentT) <= threshold
1629    );
1630    if (upcomingNotes.length > 0) {
1631      upcomingNotes.sort((a, b) => Math.abs(a.t - currentT) - Math.abs(b.t - currentT));
1632      const targetNote = upcomingNotes[0];
1633      const noteId = `${targetNote.i}-${targetNote.n}-${targetNote.t}`;
1634      if (!hitNotesRef.current.has(noteId)) {
1635        hitNotesRef.current.add(noteId);
1636        const diff = Math.abs(targetNote.t - currentT);
1637        const points = diff < 0.05 ? 100 : diff < 0.1 ? 50 : 10;
1638        const isPerfect = diff < 0.05;
1639        gameComboRef.current += 1;
1640        if (gameComboRef.current > maxComboRef.current) {
1641          maxComboRef.current = gameComboRef.current;
1642          setMaxCombo(maxComboRef.current);
1643        }
1644        setGameCombo(gameComboRef.current);
1645        const comboMultiplier = Math.min(4, 1 + Math.floor(gameComboRef.current / 10));
1646        const speedMultiplier = gameSpeedRef.current;
1647        gameScoreRef.current += Math.round(points * comboMultiplier * speedMultiplier);
1648        setGameScore(gameScoreRef.current);
1649        hitNotesCountRef.current += 1;
1650        setHitNotesCount(hitNotesCountRef.current);
1651        if (targetNote.d > 0.1) {
1652          activeHoldsRef.current.set(midi2, targetNote);
1653        }
1654        if (isPerfect) {
1655          if (canvasRef.current) {
1656            const rect = canvasRef.current.getBoundingClientRect();
1657            const w = rect.width;
1658            const h = rect.height;
1659            const keyHeight = h * 0.15;
1660            const keyTop = h - keyHeight;
1661            const whiteKeyCount = 52;
1662            const keyWidth = w / whiteKeyCount;
1663            const blackKeyWidth = keyWidth * 0.65;
1664            const { x, w: kw } = getKeyGeometry(midi2, keyWidth, blackKeyWidth);
1665            const color = colorMap[targetNote.i] || "#ffffff";
1666            for (let i = 0; i < 15; i++) {
1667              particlesRef.current.push({
1668                x: x + kw / 2,
1669                y: keyTop,
1670                vx: (Math.random() - 0.5) * 10,
1671                vy: -(Math.random() * 10 + 5),
1672                size: Math.random() * 4 + 2,
1673                color,
1674                life: 1,
1675                decay: 0.03 + Math.random() * 0.05
1676              });
1677            }
1678          }
1679        }
1680      }
1681    } else {
1682      if (gameComboRef.current > 0) {
1683        gameComboRef.current = 0;
1684        setGameCombo(0);
1685      }
1686    }
1687  }, [colorMap, basePlayNote]);
1688  const stopNote = reactExports.useCallback((midi2) => {
1689    baseStopNote(midi2);
1690    const currentT = lastTimeRef.current;
1691    if (activeHoldsRef.current.has(midi2)) {
1692      const targetNote = activeHoldsRef.current.get(midi2);
1693      activeHoldsRef.current.delete(midi2);
1694      const noteEndTime = targetNote.t + targetNote.d;
1695      const diff = Math.abs(noteEndTime - currentT);
1696      if (diff < 0.2) {
1697        const points = diff < 0.05 ? 50 : 25;
1698        const comboMultiplier = Math.min(4, 1 + Math.floor(gameComboRef.current / 10));
1699        const speedMultiplier = gameSpeedRef.current;
1700        gameScoreRef.current += Math.round(points * comboMultiplier * speedMultiplier);
1701        setGameScore(gameScoreRef.current);
1702        if (diff < 0.05 && canvasRef.current) {
1703          const rect = canvasRef.current.getBoundingClientRect();
1704          const w = rect.width;
1705          const h = rect.height;
1706          const keyHeight = h * 0.15;
1707          const keyTop = h - keyHeight;
1708          const whiteKeyCount = 52;
1709          const keyWidth = w / whiteKeyCount;
1710          const blackKeyWidth = keyWidth * 0.65;
1711          const { x, w: kw } = getKeyGeometry(midi2, keyWidth, blackKeyWidth);
1712          const color = colorMap[targetNote.i] || "#ffffff";
1713          for (let i = 0; i < 8; i++) {
1714            particlesRef.current.push({
1715              x: x + kw / 2,
1716              y: keyTop,
1717              vx: (Math.random() - 0.5) * 6,
1718              vy: -(Math.random() * 6 + 3),
1719              size: Math.random() * 3 + 1,
1720              color,
1721              life: 1,
1722              decay: 0.04 + Math.random() * 0.05
1723            });
1724          }
1725        }
1726      } else if (currentT < noteEndTime - 0.2 || currentT > noteEndTime + 0.2) {
1727        if (gameComboRef.current > 0) {
1728          gameComboRef.current = 0;
1729          setGameCombo(0);
1730        }
1731      }
1732    }
1733  }, [colorMap, baseStopNote]);
1734  const baseOctaveRef = reactExports.useRef(4);
1735  const [, setBaseOctave] = reactExports.useState(4);
1736  const activeKeysRef = reactExports.useRef(/* @__PURE__ */ new Map());
1737  const [keyboardLabelMode, setKeyboardLabelMode] = reactExports.useState("pc");
1738  const keyboardLabelModeRef = reactExports.useRef("pc");
1739  reactExports.useEffect(() => {
1740    keyboardLabelModeRef.current = keyboardLabelMode;
1741  }, [keyboardLabelMode]);
1742  const midi = useKeyboardInput({
1743    playNote,
1744    stopNote,
1745    baseOctaveRef,
1746    activeKeysRef,
1747    onOctaveChange: setBaseOctave
1748  });
1749  const handlePointerDown = (e) => {
1750    if (!canvasRef.current) return;
1751    if (!isPlaying && gameCountdown === null && !showEndScreen && !isGamePaused) {
1752      return;
1753    }
1754    const rect = canvasRef.current.getBoundingClientRect();
1755    const y = e.clientY - rect.top;
1756    const h = rect.height;
1757    const keyHeight = h * 0.15;
1758    const keyTop = h - keyHeight;
1759    if (y >= keyTop) {
1760      e.stopPropagation();
1761      const x = e.clientX - rect.left;
1762      const w = rect.width;
1763      const midi2 = getMidiFromKeyboardClick(x, y - keyTop, w, keyHeight);
1764      if (midi2 !== null) {
1765        playNote(midi2);
1766      }
1767    }
1768  };
1769  const handlePointerUp = () => {
1770    stopAllNotes();
1771  };
1772  const handlePointerLeave = () => {
1773    stopAllNotes();
1774  };
1775  const [uiScale, setUiScale] = reactExports.useState(1);
1776  reactExports.useEffect(() => {
1777    if (!containerRef.current || !canvasRef.current) return;
1778    const updateDim = () => {
1779      if (containerRef.current && canvasRef.current) {
1780        const { width, height } = containerRef.current.getBoundingClientRect();
1781        const dpr = window.devicePixelRatio || 1;
1782        canvasRef.current.width = width * dpr;
1783        canvasRef.current.height = height * dpr;
1784        const ctx = canvasRef.current.getContext("2d");
1785        if (ctx) ctx.scale(dpr, dpr);
1786        canvasRef.current.style.width = `${width}px`;
1787        canvasRef.current.style.height = `${height}px`;
1788        const baseW = 800;
1789        const baseH = 600;
1790        const scaleW = width / baseW;
1791        const scaleH = height / baseH;
1792        setUiScale(Math.min(1, Math.max(0.3, Math.min(scaleW, scaleH))));
1793      }
1794    };
1795    updateDim();
1796    const ro = new ResizeObserver(updateDim);
1797    ro.observe(containerRef.current);
1798    return () => ro.disconnect();
1799  }, []);
1800  const animateRef = reactExports.useRef(() => {
1801  });
1802  const animate = () => {
1803    if (!canvasRef.current) return;
1804    const currentSpeed = gameSpeedRef.current;
1805    let visualTime = lastTimeRef.current;
1806    if (isPlaying) {
1807      visualTime += (performance.now() - lastTickRef.current) / 1e3 * currentSpeed;
1808    } else if (countdownStartTimeRef.current !== null && countdownDurationRef.current > 0) {
1809      const elapsed = performance.now() - countdownStartTimeRef.current;
1810      const remaining = Math.max(0, countdownDurationRef.current - elapsed);
1811      const targetTime = lastTimeRef.current;
1812      visualTime = Math.max(0, targetTime - remaining / 1e3 * currentSpeed);
1813    } else {
1814      visualTime = lastTimeRef.current;
1815    }
1816    const ctx = canvasRef.current.getContext("2d", { alpha: false });
1817    if (ctx) {
1818      const { width, height } = canvasRef.current;
1819      renderScene(ctx, width, height, visualTime);
1820    }
1821  };
1822  reactExports.useEffect(() => {
1823    animateRef.current = animate;
1824  });
1825  reactExports.useEffect(() => {
1826    if (starFieldRef.current.length === 0) {
1827      for (let i = 0; i < 80; i++) {
1828        starFieldRef.current.push({
1829          x: Math.random(),
1830          y: Math.random(),
1831          size: Math.random() * 1.5 + 0.5,
1832          speed: Math.random() * 0.05 + 0.01
1833        });
1834      }
1835    }
1836    let lastFrameTime = 0;
1837    const isSlow = isSlowMobile();
1838    const fpsInterval = isSlow ? 1e3 / 30 : 0;
1839    const loop = (timestamp) => {
1840      if (fpsInterval > 0) {
1841        const elapsed = timestamp - lastFrameTime;
1842        if (elapsed >= fpsInterval) {
1843          lastFrameTime = timestamp - elapsed % fpsInterval;
1844          animateRef.current();
1845        }
1846      } else {
1847        animateRef.current();
1848      }
1849      requestRef.current = requestAnimationFrame(loop);
1850    };
1851    requestRef.current = requestAnimationFrame(loop);
1852    return () => {
1853      if (requestRef.current) cancelAnimationFrame(requestRef.current);
1854    };
1855  }, []);
1856  const renderScene = (ctx, rawWidth, rawHeight, t) => {
1857    const dpr = window.devicePixelRatio || 1;
1858    const width = rawWidth / dpr;
1859    const fullHeight = rawHeight / dpr;
1860    const height = fullHeight;
1861    const effectiveTime = t + CONFIG.visualOffset;
1862    const keyHeight = height * 0.15;
1863    const keyTop = height - keyHeight;
1864    const whiteKeyCount = 52;
1865    const keyWidth = width / whiteKeyCount;
1866    const blackKeyWidth = keyWidth * 0.65;
1867    const gradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, Math.max(width, height));
1868    gradient.addColorStop(0, bgTheme.color2);
1869    gradient.addColorStop(1, bgTheme.color1);
1870    ctx.fillStyle = gradient;
1871    ctx.fillRect(0, 0, width, fullHeight);
1872    ctx.fillStyle = "rgba(255, 255, 255, 0.4)";
1873    starFieldRef.current.forEach((star) => {
1874      star.y -= star.speed * (isPlaying ? 1 : 0) * gameSpeedRef.current;
1875      if (star.y < 0) star.y = 1;
1876      ctx.beginPath();
1877      ctx.arc(star.x * width, star.y * height, star.size, 0, Math.PI * 2);
1878      ctx.fill();
1879    });
1880    ctx.strokeStyle = "rgba(255, 255, 255, 0.03)";
1881    ctx.lineWidth = 1;
1882    for (let x = 0; x < width; x += 50) {
1883      ctx.beginPath();
1884      ctx.moveTo(x, 0);
1885      ctx.lineTo(x, fullHeight);
1886      ctx.stroke();
1887    }
1888    for (let y = 0; y < fullHeight; y += 50) {
1889      ctx.beginPath();
1890      ctx.moveTo(0, y);
1891      ctx.lineTo(width, y);
1892      ctx.stroke();
1893    }
1894    const activeNotes = /* @__PURE__ */ new Set();
1895    previewActiveNotesRef.current.forEach((midi2) => activeNotes.add(midi2));
1896    if (isPlaying) {
1897      activeNotes.forEach((midi2) => {
1898        if (!prevActiveNotesRef.current.has(midi2)) {
1899          const { x, w } = getKeyGeometry(midi2, keyWidth, blackKeyWidth);
1900          const color = "#ffffff";
1901          for (let i = 0; i < 8; i++) {
1902            particlesRef.current.push({
1903              x: x + w / 2 + (Math.random() - 0.5) * w,
1904              y: keyTop,
1905              vx: (Math.random() - 0.5) * 4,
1906              vy: -(Math.random() * 5 + 2),
1907              size: Math.random() * 3 + 1,
1908              color,
1909              life: 1,
1910              decay: 0.02 + Math.random() * 0.03
1911            });
1912          }
1913        }
1914      });
1915    }
1916    prevActiveNotesRef.current = activeNotes;
1917    for (let i = particlesRef.current.length - 1; i >= 0; i--) {
1918      const p = particlesRef.current[i];
1919      p.x += p.vx;
1920      p.y += p.vy;
1921      p.life -= p.decay;
1922      p.vy += 0.1;
1923      if (p.life <= 0) {
1924        particlesRef.current.splice(i, 1);
1925        continue;
1926      }
1927      ctx.globalCompositeOperation = "lighter";
1928      ctx.fillStyle = p.color;
1929      ctx.globalAlpha = p.life;
1930      ctx.beginPath();
1931      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
1932      ctx.fill();
1933      ctx.globalAlpha = 1;
1934    }
1935    const gameData = processedGameDataRef.current || [];
1936    const finalVisibleNotes = [];
1937    const activeGameNotes = /* @__PURE__ */ new Set();
1938    let left = 0;
1939    let right = gameData.length - 1;
1940    let startIdx = 0;
1941    const searchTime = Math.max(0, effectiveTime - 10);
1942    while (left <= right) {
1943      const mid = Math.floor((left + right) / 2);
1944      if (gameData[mid].t < searchTime) {
1945        left = mid + 1;
1946        startIdx = left;
1947      } else {
1948        right = mid - 1;
1949      }
1950    }
1951    for (let i = startIdx; i < gameData.length; i++) {
1952      const note = gameData[i];
1953      if (note.t > effectiveTime + CONFIG.lookAheadTime) break;
1954      const timeStart = note.t;
1955      const timeEnd = note.t + note.d;
1956      if (effectiveTime >= timeStart && effectiveTime <= timeEnd) activeGameNotes.add(note.n);
1957      if (timeEnd > effectiveTime) finalVisibleNotes.push(note);
1958    }
1959    const missThreshold = 0.2;
1960    const checkStart = effectiveTime - 1;
1961    const checkEnd = effectiveTime;
1962    let leftMiss = 0;
1963    let rightMiss = gameData.length - 1;
1964    let startMissIdx = 0;
1965    while (leftMiss <= rightMiss) {
1966      const mid = Math.floor((leftMiss + rightMiss) / 2);
1967      if (gameData[mid].t < checkStart) {
1968        leftMiss = mid + 1;
1969        startMissIdx = leftMiss;
1970      } else {
1971        rightMiss = mid - 1;
1972      }
1973    }
1974    for (let i = startMissIdx; i < gameData.length; i++) {
1975      const note = gameData[i];
1976      if (note.t > checkEnd) break;
1977      const noteId = `${note.i}-${note.n}-${note.t}`;
1978      if (!hitNotesRef.current.has(noteId) && !missedNotesRef.current.has(noteId)) {
1979        if (effectiveTime > note.t + missThreshold) {
1980          missedNotesRef.current.add(noteId);
1981          if (gameComboRef.current > 0) {
1982            gameComboRef.current = 0;
1983            setGameCombo(0);
1984          }
1985        }
1986      }
1987    }
1988    for (const [midi2, note] of activeHoldsRef.current.entries()) {
1989      if (effectiveTime > note.t + note.d + missThreshold) {
1990        activeHoldsRef.current.delete(midi2);
1991        if (gameComboRef.current > 0) {
1992          gameComboRef.current = 0;
1993          setGameCombo(0);
1994        }
1995      }
1996    }
1997    let minPitch = 128;
1998    let maxPitch = 0;
1999    let hasUpcoming = false;
2000    for (const note of finalVisibleNotes) {
2001      if (note.t > effectiveTime && note.t < effectiveTime + 1.5) {
2002        if (note.n < minPitch) minPitch = note.n;
2003        if (note.n > maxPitch) maxPitch = note.n;
2004        hasUpcoming = true;
2005      }
2006    }
2007    if (hasUpcoming) {
2008      const baseOct = baseOctaveRef.current;
2009      const rangeStart = baseOct * 12;
2010      const rangeEnd = baseOct * 12 + 31;
2011      if (minPitch < rangeStart) {
2012        setOctaveTip("Press ← or Z for Lower Octave");
2013      } else if (maxPitch > rangeEnd) {
2014        setOctaveTip("Press → or X for Higher Octave");
2015      } else {
2016        setOctaveTip(null);
2017      }
2018    } else {
2019      setOctaveTip(null);
2020    }
2021    ctx.save();
2022    ctx.globalCompositeOperation = "source-over";
2023    const horizonY = height * 0.1;
2024    const bottomY = keyTop;
2025    const centerX = width / 2;
2026    ctx.beginPath();
2027    ctx.moveTo(centerX - width * 0.1, horizonY);
2028    ctx.lineTo(width, bottomY);
2029    ctx.lineTo(0, bottomY);
2030    ctx.lineTo(centerX + width * 0.1, horizonY);
2031    ctx.closePath();
2032    ctx.strokeStyle = "rgba(255, 255, 255, 0.1)";
2033    ctx.lineWidth = 1;
2034    const lanes = 12;
2035    for (let i = 0; i <= lanes; i++) {
2036      const t2 = i / lanes;
2037      const topX = centerX - width * 0.1 + width * 0.2 * t2;
2038      const bottomX = width * t2;
2039      ctx.beginPath();
2040      ctx.moveTo(topX, horizonY);
2041      ctx.lineTo(bottomX, bottomY);
2042      ctx.stroke();
2043    }
2044    const beatSpacing = 1;
2045    const timeOffset = effectiveTime % beatSpacing;
2046    for (let i = 0; i < 10; i++) {
2047      const lineTime = i * beatSpacing - timeOffset;
2048      if (lineTime < 0) continue;
2049      const progress = lineTime / CONFIG.lookAheadTime;
2050      if (progress > 1) continue;
2051      const y = bottomY - (bottomY - horizonY) * progress;
2052      const widthAtY = width * (1 - 0.8 * progress);
2053      const xLeft = (width - widthAtY) / 2;
2054      ctx.beginPath();
2055      ctx.moveTo(xLeft, y);
2056      ctx.lineTo(xLeft + widthAtY, y);
2057      ctx.stroke();
2058    }
2059    const getPerspective = (p) => {
2060      const clampedP = Math.max(0, Math.min(1, p));
2061      const y = bottomY - (bottomY - horizonY) * clampedP;
2062      const scale = 1 - 0.7 * clampedP;
2063      return { y, scale };
2064    };
2065    const gameDataToRender = processedGameDataRef.current || [];
2066    let leftRender = 0;
2067    let rightRender = gameDataToRender.length - 1;
2068    let startRenderIdx = 0;
2069    const searchRenderTime = effectiveTime - 0.2;
2070    while (leftRender <= rightRender) {
2071      const mid = Math.floor((leftRender + rightRender) / 2);
2072      if (gameDataToRender[mid].t < searchRenderTime) {
2073        leftRender = mid + 1;
2074        startRenderIdx = leftRender;
2075      } else {
2076        rightRender = mid - 1;
2077      }
2078    }
2079    for (let idx = startRenderIdx; idx < gameDataToRender.length; idx++) {
2080      const note = gameDataToRender[idx];
2081      const distFromPlayhead = note.t - effectiveTime;
2082      const progressStart = distFromPlayhead / CONFIG.lookAheadTime;
2083      if (progressStart > 1) break;
2084      const progressEnd = (distFromPlayhead + note.d) / CONFIG.lookAheadTime;
2085      const start = getPerspective(progressStart);
2086      const end = getPerspective(progressEnd);
2087      const { x: keyX, w: keyW } = getKeyGeometry(note.n, keyWidth, blackKeyWidth);
2088      const keyCenterX = keyX + keyW / 2;
2089      const distFromCenter = keyCenterX - width / 2;
2090      const x1 = width / 2 + distFromCenter * start.scale;
2091      const w1 = keyW * start.scale;
2092      const x2 = width / 2 + distFromCenter * end.scale;
2093      const w2 = keyW * end.scale;
2094      const isHeld = activeHoldsRef.current.has(note.n) && activeHoldsRef.current.get(note.n).t === note.t;
2095      const defaultColor = colorMap[note.i] || "#ffffff";
2096      let baseColor = defaultColor;
2097      let sideColor = defaultColor;
2098      if (isHeld) {
2099        baseColor = "#ffffff";
2100        sideColor = "#cccccc";
2101        if (Math.random() < 0.3) {
2102          particlesRef.current.push({
2103            x: x1 + (Math.random() - 0.5) * w1,
2104            y: start.y,
2105            vx: (Math.random() - 0.5) * 2,
2106            vy: -(Math.random() * 4 + 2),
2107            size: Math.random() * 2 + 1,
2108            color: baseColor,
2109            life: 1,
2110            decay: 0.05 + Math.random() * 0.05
2111          });
2112        }
2113      }
2114      const thickness = 15 * start.scale;
2115      ctx.fillStyle = sideColor;
2116      ctx.beginPath();
2117      ctx.moveTo(x1 - w1 / 2, start.y);
2118      ctx.lineTo(x1 + w1 / 2, start.y);
2119      ctx.lineTo(x1 + w1 / 2, start.y + thickness);
2120      ctx.lineTo(x1 - w1 / 2, start.y + thickness);
2121      ctx.closePath();
2122      ctx.fill();
2123      ctx.fillStyle = baseColor;
2124      ctx.shadowColor = baseColor;
2125      ctx.shadowBlur = isHeld ? 20 : 10;
2126      ctx.beginPath();
2127      ctx.moveTo(x1 - w1 / 2, start.y);
2128      ctx.lineTo(x1 + w1 / 2, start.y);
2129      ctx.lineTo(x2 + w2 / 2, end.y);
2130      ctx.lineTo(x2 - w2 / 2, end.y);
2131      ctx.closePath();
2132      ctx.fill();
2133      ctx.shadowBlur = 0;
2134      ctx.globalAlpha = 1;
2135      ctx.strokeStyle = "rgba(255,255,255,0.3)";
2136      ctx.lineWidth = 1;
2137      ctx.beginPath();
2138      ctx.moveTo(x1 - w1 / 2, start.y);
2139      ctx.lineTo(x2 - w2 / 2, end.y);
2140      ctx.stroke();
2141      const baseOct = baseOctaveRef.current;
2142      const label = getPcKeyLabel(note.n, baseOct);
2143      if (label && start.scale > 0.2) {
2144        const fontSize = Math.max(10, 24 * start.scale);
2145        ctx.font = `900 ${fontSize}px "Arial Black", sans-serif`;
2146        ctx.textAlign = "center";
2147        ctx.textBaseline = "middle";
2148        const labelY = start.y - (start.y - end.y) * 0.2;
2149        ctx.lineWidth = 6 * start.scale;
2150        ctx.lineJoin = "round";
2151        ctx.strokeStyle = "#000000";
2152        ctx.strokeText(label, x1, labelY);
2153        ctx.fillStyle = "#ffffff";
2154        ctx.fillText(label, x1, labelY);
2155      }
2156    }
2157    ctx.restore();
2158    ctx.globalCompositeOperation = "source-over";
2159    renderSharedKeyboard(ctx, width, height, keyHeight, activeNotes, keyboardLabelModeRef.current, baseOctaveRef.current);
2160  };
2161  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "w-full h-full bg-[#050505] relative @container", children: [
2162    /* @__PURE__ */ jsxRuntimeExports.jsx(
2163      "canvas",
2164      {
2165        ref: canvasRef,
2166        className: "w-full h-full block touch-none cursor-pointer",
2167        onClick: (e) => {
2168          if (!isPlaying && gameCountdown === null && !showEndScreen && !isGamePaused) {
2169            e.stopPropagation();
2170          }
2171        },
2172        onPointerDown: (e) => {
2173          var _a;
2174          const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
2175          if (rect) {
2176            const y = e.clientY - rect.top;
2177            const h = rect.height;
2178            const keyHeight = h * 0.15;
2179            const keyTop = h - keyHeight;
2180            if (y >= keyTop) {
2181              handlePointerDown(e);
2182            } else {
2183              togglePause();
2184            }
2185          }
2186        },
2187        onPointerUp: handlePointerUp,
2188        onPointerLeave: handlePointerLeave,
2189        onPointerCancel: handlePointerLeave
2190      }
2191    ),
2192    /* @__PURE__ */ jsxRuntimeExports.jsxs(
2193      "div",
2194      {
2195        className: "absolute top-4 right-4 flex flex-col gap-2 pointer-events-none select-none z-40 items-end origin-top-right",
2196        style: { transform: `scale(${uiScale})` },
2197        children: [
2198          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/60 backdrop-blur-md text-white px-4 py-2 rounded border border-white/10 text-right shrink-0", children: [
2199            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-white/60 font-mono", children: "SCORE" }),
2200            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold font-mono", children: gameScore.toLocaleString() })
2201          ] }),
2202          gameCombo > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/60 backdrop-blur-md text-white px-4 py-2 rounded border border-white/10 animate-bounce text-right shrink-0", children: [
2203            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-emerald-400 font-bold font-mono", children: "COMBO" }),
2204            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-3xl font-black font-mono italic text-emerald-400", children: [
2205              gameCombo,
2206              "x"
2207            ] })
2208          ] }),
2209          gameCombo >
2209= 10 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/60 backdrop-blur-md text-white px-4 py-2 rounded border border-white/10 text-right shrink-0", children: [
2210            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-yellow-400 font-mono", children: "MULTIPLIER" }),
2211            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xl font-bold text-yellow-400", children: [
2212              Math.min(4, 1 + Math.floor(gameCombo / 10)),
2213              "x"
2214            ] })
2215          ] }),
2216          /* @__PURE__ */ jsxRuntimeExports.jsxs(
2217            "div",
2218            {
2219              className: "bg-black/60 backdrop-blur-md text-white p-2 rounded border border-white/10 pointer-events-auto flex flex-col items-center gap-2 mt-auto",
2220              onClick: (e) => e.stopPropagation(),
2221              children: [
2222                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-white/60 font-mono uppercase", style: { writingMode: "vertical-rl", textOrientation: "mixed" }, children: "Speed" }),
2223                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-32 flex items-center justify-center py-2 relative w-6", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2224                  "input",
2225                  {
2226                    type: "range",
2227                    min: "0.5",
2228                    max: "1.2",
2229                    step: "0.1",
2230                    value: gameSpeed,
2231                    onChange: (e) => setGameSpeed(parseFloat(e.target.value)),
2232                    className: "absolute w-32 h-2 bg-white/20 rounded-full outline-none cursor-pointer",
2233                    style: {
2234                      transform: "rotate(-90deg)",
2235                      transformOrigin: "center",
2236                      top: "50%",
2237                      left: "50%",
2238                      marginTop: "-4px",
2239                      marginLeft: "-64px"
2240                    }
2241                  }
2242                ) }),
2243                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-bold font-mono", children: [
2244                  gameSpeed.toFixed(1),
2245                  "x"
2246                ] }),
2247                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[9px] text-emerald-400 font-mono", children: [
2248                  Math.round(gameSpeed * 100),
2249                  "%"
2250                ] })
2251              ]
2252            }
2253          )
2254        ]
2255      }
2256    ),
2257    isGamePaused && gameCountdown !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center z-50 bg-black/40 pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2258      "div",
2259      {
2260        className: "text-4xl font-black text-white tracking-widest drop-shadow-[0_0_20px_rgba(0,0,0,0.8)] animate-pulse",
2261        style: { transform: `scale(${uiScale})` },
2262        children: "PAUSED"
2263      }
2264    ) }),
2265    (!isPlaying && gameCountdown === null && !showEndScreen && !isGamePaused || isGamePaused && gameCountdown === null) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center z-30 bg-black/60 p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2266      "div",
2267      {
2268        className: "flex flex-row items-stretch justify-center gap-8 md:gap-16 w-full max-w-6xl h-[70vh]",
2269        style: { transform: `scale(${uiScale})` },
2270        children: [
2271          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pointer-events-auto bg-black/60 border border-white/10 rounded-xl p-6 w-1/2 overflow-hidden flex flex-col min-h-0 shadow-2xl", children: [
2272            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-bold text-white/40 uppercase tracking-widest mb-4 text-center shrink-0", children: "Top Players" }),
2273            leaderboard.length > 0 ? renderPodiumLeaderboard(leaderboard) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-white/20 italic text-sm", children: "No scores yet" })
2274          ] }),
2275          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-8 w-1/2 bg-white/5 border border-white/10 rounded-xl p-8 shadow-2xl", children: [
2276            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
2277              /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-4xl font-black text-white mb-2 tracking-tighter", children: isGamePaused ? "GAME PAUSED" : "PIANO ROLL CHALLENGE" }),
2278              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-white/40 text-sm font-medium tracking-widest uppercase", children: isGamePaused ? "Ready to continue?" : "Test your skills" })
2279            ] }),
2280            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
2281              /* @__PURE__ */ jsxRuntimeExports.jsxs(
2282                "button",
2283                {
2284                  onClick: () => {
2285                    if (isGamePaused) {
2286                      handleGameStart();
2287                    } else {
2288                      resetGame();
2289                      targetStartTimeRef.current = 0;
2290                      handleGameStart(true);
2291                    }
2292                  },
2293                  disabled: waitingForAudio,
2294                  className: `pointer-events-auto shrink-0 group relative px-16 py-8 ${waitingForAudio ? "bg-zinc-600" : "bg-emerald-500 hover:bg-emerald-400"} text-white font-black text-4xl tracking-wider rounded-2xl shadow-[0_0_50px_rgba(16,185,129,0.3)] hover:shadow-[0_0_80px_rgba(16,185,129,0.5)] transform hover:scale-105 transition-all border-2 ${waitingForAudio ? "border-zinc-500" : "border-emerald-300"}`,
2295                  children: [
2296                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "drop-shadow-lg", children: waitingForAudio ? "LOADING..." : isGamePaused ? "RESUME" : "START" }),
2297                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 rounded-2xl ring-4 ring-white/20 group-hover:ring-white/40 transition-all" })
2298                  ]
2299                }
2300              ),
2301              isGamePaused && /* @__PURE__ */ jsxRuntimeExports.jsx(
2302                "button",
2303                {
2304                  onClick: () => {
2305                    resetGame();
2306                    targetStartTimeRef.current = 0;
2307                    handleGameStart(true);
2308                  },
2309                  className: "pointer-events-auto text-white/60 hover:text-white font-bold text-sm tracking-widest uppercase transition-colors",
2310                  children: "Restart"
2311                }
2312              ),
2313              /* @__PURE__ */ jsxRuntimeExports.jsx(
2314                "button",
2315                {
2316                  onClick: () => toggleGameMode(),
2317                  className: "pointer-events-auto text-white/40 hover:text-white/80 font-medium text-xs tracking-widest uppercase transition-colors mt-2",
2318                  children: "Back to playback mode"
2319                }
2320              )
2321            ] }),
2322            !isGamePaused && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
2323              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-white/30 text-[10px] font-bold tracking-[0.3em] uppercase", children: "Controls" }),
2324              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4", children: [
2325                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [
2326                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white/10 px-2 py-1 rounded text-[10px] text-white font-mono mb-1", children: "SPACE" }),
2327                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[8px] text-white/40 uppercase", children: "Start/Pause" })
2328                ] }),
2329                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [
2330                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white/10 px-2 py-1 rounded text-[10px] text-white font-mono mb-1", children: "Z/X or ARROWS" }),
2331                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[8px] text-white/40 uppercase", children: "Octave" })
2332                ] })
2333              ] })
2334            ] })
2335          ] })
2336        ]
2337      }
2338    ) }),
2339    showEndScreen && /* @__PURE__ */ jsxRuntimeExports.jsx(
2340      "div",
2341      {
2342        className: "absolute inset-0 bg-black/80 backdrop-blur-xl z-50 flex items-center justify-center p-4",
2343        onClick: () => {
2344          setShowEndScreen(false);
2345          toggleGameMode();
2346        },
2347        children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2348          "div",
2349          {
2350            className: "bg-[#111] border border-white/10 rounded-2xl p-8 max-w-5xl w-full shadow-2xl transform transition-all flex flex-col",
2351            style: {
2352              maxHeight: "90vh",
2353              transform: `scale(${uiScale})`
2354            },
2355            onClick: (e) => e.stopPropagation(),
2356            children: [
2357              /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-3xl font-black text-white mb-8 text-center shrink-0 tracking-tighter", children: "SONG COMPLETE" }),
2358              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-row gap-8 flex-1 min-h-0", children: [
2359                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-1/2 flex flex-col min-h-0 bg-black/40 rounded-xl border border-white/5 p-6", children: [
2360                  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xs font-bold text-white/40 uppercase tracking-widest mb-4 text-center shrink-0", children: "Leaderboard" }),
2361                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: renderPodiumLeaderboard(leaderboard) })
2362                ] }),
2363                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-1/2 flex flex-col justify-between", children: [
2364                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2365                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center mb-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `text-9xl leading-none font-black ${hitNotesCount / totalNotes > 0.95 ? "text-emerald-400 drop-shadow-[0_0_30px_rgba(52,211,153,0.6)]" : hitNotesCount / totalNotes > 0.9 ? "text-blue-400" : hitNotesCount / totalNotes > 0.8 ? "text-yellow-400" : hitNotesCount / totalNotes > 0.7 ? "text-orange-400" : "text-red-400"}`, children: hitNotesCount / totalNotes > 0.95 ? "S" : hitNotesCount / totalNotes > 0.9 ? "A" : hitNotesCount / totalNotes > 0.8 ? "B" : hitNotesCount / totalNotes > 0.7 ? "C" : "D" }) }),
2366                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 mb-8", children: [
2367                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white/5 p-4 rounded-xl border border-white/5", children: [
2368                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-white/50 uppercase font-bold tracking-wider mb-1", children: "Final Score" }),
2369                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-black text-white font-mono", children: gameScore.toLocaleString() })
2370                      ] }),
2371                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white/5 p-4 rounded-xl border border-white/5", children: [
2372                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-white/50 uppercase font-bold tracking-wider mb-1", children: "Max Combo" }),
2373                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-black text-white font-mono", children: maxCombo })
2374                      ] }),
2375                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white/5 p-4 rounded-xl border border-white/5", children: [
2376                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-white/50 uppercase font-bold tracking-wider mb-1", children: "Accuracy" }),
2377                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-2xl font-black text-white font-mono", children: [
2378                          totalNotes > 0 ? Math.round(hitNotesCount / totalNotes * 100) : 0,
2379                          "%"
2380                        ] })
2381                      ] }),
2382                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white/5 p-4 rounded-xl border border-white/5", children: [
2383                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-white/50 uppercase font-bold tracking-wider mb-1", children: "Notes Hit" }),
2384                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-2xl font-black text-white font-mono", children: [
2385                          hitNotesCount,
2386                          " ",
2387                          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-white/40 text-sm font-bold", children: [
2388                            "/ ",
2389                            totalNotes
2390                          ] })
2391                        ] })
2392                      ] })
2393                    ] }),
2394                    !userId && !hasSubmittedGuestScore && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-indigo-500/10 border border-indigo-500/20 rounded-xl p-4 mb-8 text-center", children: [
2395                      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-indigo-300 font-medium mb-3", children: "Log in to save your score and compete!" }),
2396                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-center gap-4", children: [
2397                        /* @__PURE__ */ jsxRuntimeExports.jsx(
2398                          "button",
2399                          {
2400                            onClick: () => {
2401                              if (onAuthRequested) onAuthRequested();
2402                              else window.location.href = "/login";
2403                            },
2404                            className: "px-4 py-2 bg-indigo-500 hover:bg-indigo-400 text-white text-xs font-bold rounded-lg transition-colors",
2405                            children: "Login / Sign Up"
2406                          }
2407                        ),
2408                        /* @__PURE__ */ jsxRuntimeExports.jsx(
2409                          "button",
2410                          {
2411                            onClick: submitGuestScore,
2412                            className: "px-4 py-2 bg-white/10 hover:bg-white/20 text-white text-xs font-bold rounded-lg transition-colors",
2413                            children: "Save as Contributor"
2414                          }
2415                        )
2416                      ] })
2417                    ] }),
2418                    !userId && hasSubmittedGuestScore && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-emerald-500/10 border border-emerald-500/20 rounded-xl p-4 mb-8 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-emerald-300 font-medium", children: "Score saved as Community Contributor!" }) })
2419                  ] }),
2420                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4", children: [
2421                    /* @__PURE__ */ jsxRuntimeExports.jsx(
2422                      "button",
2423                      {
2424                        onClick: () => {
2425                          resetGame();
2426                        },
2427                        className: "flex-1 py-4 bg-emerald-500 text-white font-black rounded-xl hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/20 hover:shadow-emerald-500/40 transform hover:-translate-y-1 text-base tracking-wider",
2428                        children: "PLAY AGAIN"
2429                      }
2430                    ),
2431                    /* @__PURE__ */ jsxRuntimeExports.jsx(
2432                      "button",
2433                      {
2434                        onClick: () => {
2435                          setShowEndScreen(false);
2436                          toggleGameMode();
2437                        },
2438                        className: "flex-1 py-4 bg-white/5 text-white font-black rounded-xl hover:bg-white/10 transition-all border border-white/10 text-base tracking-wider",
2439                        children: "CLOSE"
2440                      }
2441                    )
2442                  ] })
2443                ] })
2444              ] })
2445            ]
2446          }
2447        )
2448      }
2449    ),
2450    gameCountdown !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2451      "div",
2452      {
2453        className: "text-9xl font-bold text-white drop-shadow-[0_0_30px_rgba(255,255,255,0.8)] animate-pulse",
2454        style: { transform: `scale(${uiScale})` },
2455        children: gameCountdown
2456      }
2457    ) }),
2458    octaveTip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none z-40", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2459      "div",
2460      {
2461        className: "bg-red-500/80 backdrop-blur-md text-white px-6 py-3 rounded-full font-bold text-xl animate-bounce shadow-lg border border-white/20",
2462        style: { transform: `scale(${uiScale})` },
2463        children: octaveTip
2464      }
2465    ) }),
2466    /* @__PURE__ */ jsxRuntimeExports.jsxs(
2467      "div",
2468      {
2469        className: "absolute bottom-[16%] right-4 flex flex-col gap-2 z-50 origin-bottom-right",
2470        style: { transform: `scale(${uiScale})` },
2471        children: [
2472          /* @__PURE__ */ jsxRuntimeExports.jsx(
2473            "button",
2474            {
2475              onClick: async (e) => {
2476                e.stopPropagation();
2477                if (!midi.isMidiConnected) {
2478                  const success = await midi.connectMidi();
2479                  if (!success) {
2480                    alert("Could not connect to MIDI devices. Please ensure your browser supports Web MIDI and permission is granted.");
2481                  }
2482                }
2483              },
2484              className: `p-2 rounded-full backdrop-blur-md border transition-all shadow-lg pointer-events-auto ${midi.isMidiConnected ? "bg-emerald-600/80 border-emerald-400/50 text-white opacity-100" : "bg-black/60 border-white/10 text-white/50 hover:opacity-100 hover:text-white/80"}`,
2485              title: midi.isMidiConnected ? "MIDI Connected" : "Connect MIDI Device",
2486              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cable, { size: 16 })
2487            }
2488          ),
2489          /* @__PURE__ */ jsxRuntimeExports.jsx(
2490            "button",
2491            {
2492              onClick: (e) => {
2493                e.stopPropagation();
2494                toggleGameMode();
2495              },
2496              className: "p-2 rounded-full backdrop-blur-md border transition-colors pointer-events-auto bg-emerald-600/80 border-emerald-400/50 text-white",
2497              title: "Toggle Game Mode",
2498              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Gamepad2, { size: 16 })
2499            }
2500          ),
2501          /* @__PURE__ */ jsxRuntimeExports.jsx(
2502            "button",
2503            {
2504              onClick: (e) => {
2505                e.stopPropagation();
2506                setKeyboardLabelMode((prev) => prev === "pc" ? "notes" : prev === "notes" ? "off" : "pc");
2507              },
2508              className: `p-2 rounded-full backdrop-blur-md border transition-colors pointer-events-auto ${keyboardLabelMode !== "off" ? "bg-indigo-600/80 border-indigo-400/50 text-white" : "bg-black/60 border-white/10 text-white/50 hover:text-white/80"}`,
2509              title: "Toggle Keyboard Labels",
2510              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size: 16 })
2511            }
2512          )
2513        ]
2514      }
2515    )
2516  ] });
2517};
2518const PianoRollVisualizer = ({ data, currentTime, isPlaying, hiddenTracks, colorMap, selectedTrackIndex, processedInstruments, uniqueNames, toggleGameMode, onPlayPause, onSeek, soundfont, theme = "dark", playbackRate = 1, trackId }) => {
2519  const containerRef = reactExports.useRef(null);
2520  const canvasRef = reactExports.useRef(null);
2521  const requestRef = reactExports.useRef(null);
2522  const lastTimeRef = reactExports.useRef(currentTime);
2523  const lastTickRef = reactExports.useRef(0);
2524  const particlesRef = reactExports.useRef([]);
2525  const prevActiveNotesRef = reactExports.useRef(/* @__PURE__ */ new Set());
2526  const starFieldRef = reactExports.useRef([]);
2527  const isMobile = reactExports.useRef(typeof window !== "undefined" && (/Mobi|Android|iPhone|iPad/i.test(navigator.userAgent) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints > 0 || "ontouchstart" in window)).current;
2528  const [lowPerf, setLowPerf] = reactExports.useState(() => {
2529    if (typeof window === "undefined") return false;
2530    const saved = localStorage.getItem("low_performance");
2531    if (saved !== null) return saved === "true";
2532    return isMobile;
2533  });
2534  reactExports.useEffect(() => {
2535    const handleLowPerfChange = () => {
2536      const saved = localStorage.getItem("low_performance");
2537      if (saved !== null) {
2538        setLowPerf(saved === "true");
2539      } else {
2540        setLowPerf(isMobile);
2541      }
2542    };
2543    window.addEventListener("storage", handleLowPerfChange);
2544    window.addEventListener("low_performance_change", handleLowPerfChange);
2545    return () => {
2546      window.removeEventListener("storage", handleLowPerfChange);
2547      window.removeEventListener("low_performance_change", handleLowPerfChange);
2548    };
2549  }, [isMobile]);
2550  reactExports.useEffect(() => {
2551    lastTimeRef.current = currentTime;
2552    lastTickRef.current = performance.now();
2553  }, [currentTime]);
2554  const musicalInstruments = React.useMemo(() => {
2555    return (processedInstruments == null ? void 0 : processedInstruments.filter((inst) => inst.midiChannel !== -1)) || [];
2556  }, [processedInstruments]);
2557  const isPercussionNote = React.useCallback((note) => {
2558    if (!processedInstruments) return false;
2559    let inst = musicalInstruments[note.trackIdx];
2560    if (!inst || inst.midiChannel !== 9) {
2561      inst = processedInstruments.find((i) => i.trackId === note.trackIdx);
2562    }
2563    if (!inst || inst.midiChannel !== 9) {
2564      inst = processedInstruments.find((i) => i.originalName === note.i || i.displayName === note.i || i.name === note.i);
2565    }
2566    if (inst) {
2567      return inst.midiChannel === 9;
2568    }
2569    const nameLower = (note.i || "").toLowerCase();
2570    return nameLower.includes("drum") || nameLower.includes("perc") || nameLower.includes("kit") || nameLower.includes("cymbal");
2571  }, [processedInstruments, musicalInstruments]);
2572  const getNoteKey = React.useCallback((note) => {
2573    if (!note) return "";
2574    if (note.i && colorMap && colorMap[note.i]) {
2575      return note.i;
2576    }
2577    if (processedInstruments && note.trackIdx !== void 0 && musicalInstruments[note.trackIdx]) {
2578      const inst = musicalInstruments[note.trackIdx];
2579      const foundIdx = processedInstruments.findIndex((i) => i.trackId === inst.trackId);
2580      if (foundIdx !== -1 && uniqueNames && uniqueNames[foundIdx]) {
2581        return uniqueNames[foundIdx];
2582      }
2583    }
2584    return note.i || "";
2585  }, [colorMap, processedInstruments, musicalInstruments, uniqueNames]);
2586  const { playNote, stopNote, stopAllNotes, previewActiveNotesRef } = useSoundfontKeyboard(selectedTrackIndex, processedInstruments, void 0, soundfont);
2587  const [practiceMode, setPracticeMode] = reactExports.useState(false);
2588  const lastSatisfiedTimeRef = reactExports.useRef(-1);
2589  const waitingNotesRef = reactExports.useRef(null);
2590  reactExports.useEffect(() => {
2591    lastSatisfiedTimeRef.current = -1;
2592    waitingNotesRef.current = null;
2593  }, [practiceMode, trackId]);
2594  const [micActive, setMicActive] = reactExports.useState(false);
2595  const micActiveNotesRef = reactExports.useRef(/* @__PURE__ */ new Map());
2596  reactExports.useEffect(() => {
2597    let active = true;
2598    if (micActive) {
2599      const startMic = async () => {
2600        try {
2601          await synth.pitchTracker.start();
2602          if (!active) {
2603            synth.pitchTracker.stop();
2604            return;
2605          }
2606          const rmsThreshold = 0.015;
2607          let currentRms = 0;
2608          synth.pitchTracker.onVolumeUpdate = (volume) => {
2609            if (!active) return;
2610            currentRms = volume;
2611          };
2612          synth.pitchTracker.onPitchDetected = (freq) => {
2613            if (!active) return;
2614            if (currentRms < rmsThreshold) return;
2615            const midi2 = Math.round(12 * Math.log2(freq / 440) + 69);
2616            if (midi2 >= CONFIG.keyRangeStart && midi2 <= CONFIG.keyRangeEnd) {
2617              micActiveNotesRef.current.set(midi2, Date.now());
2618              previewActiveNotesRef.current.add(midi2);
2619            }
2620          };
2621        } catch (err) {
2622          console.error("Failed to start pitch tracker:", err);
2623          if (active) setMicActive(false);
2624        }
2625      };
2626      startMic();
2627    } else {
2628      synth.pitchTracker.stop();
2629      synth.pitchTracker.onVolumeUpdate = void 0;
2630      synth.pitchTracker.onPitchDetected = void 0;
2631      micActiveNotesRef.current.forEach((_, note) => {
2632        previewActiveNotesRef.current.delete(note);
2633      });
2634      micActiveNotesRef.current.clear();
2635    }
2636    return () => {
2637      active = false;
2638      synth.pitchTracker.stop();
2639      synth.pitchTracker.onVolumeUpdate = void 0;
2640      synth.pitchTracker.onPitchDetected = void 0;
2641    };
2642  }, [micActive, previewActiveNotesRef]);
2643  reactExports.useEffect(() => {
2644    if (Math.abs(currentTime - lastTimeRef.current) > 0.5) {
2645      lastSatisfiedTimeRef.current = -1;
2646      waitingNotesRef.current = null;
2647    }
2648  }, [currentTime]);
2649  const baseOctaveRef = reactExports.useRef(4);
2650  const [, setBaseOctave] = reactExports.useState(4);
2651  const activeKeysRef = reactExports.useRef(/* @__PURE__ */ new Map());
2652  const [keyboardLabelMode, setKeyboardLabelMode] = reactExports.useState("pc");
2653  const keyboardLabelModeRef = reactExports.useRef("pc");
2654  reactExports.useEffect(() => {
2655    keyboardLabelModeRef.current = keyboardLabelMode;
2656  }, [keyboardLabelMode]);
2657  const midi = useKeyboardInput({
2658    playNote,
2659    stopNote,
2660    baseOctaveRef,
2661    activeKeysRef,
2662    onOctaveChange: setBaseOctave
2663  });
2664  const handlePointerDown = (e) => {
2665    if (!canvasRef.current) return;
2666    const rect = canvasRef.current.getBoundingClientRect();
2667    const y = e.clientY - rect.top;
2668    const h = rect.height;
2669    const keyHeight = h * 0.15;
2670    const keyTop = h - keyHeight;
2671    if (y >= keyTop) {
2672      e.stopPropagation();
2673      const x = e.clientX - rect.left;
2674      const w = rect.width;
2675      const midi2 = getMidiFromKeyboardClick(x, y - keyTop, w, keyHeight);
2676      if (midi2 !== null) {
2677        playNote(midi2);
2678      }
2679    }
2680  };
2681  const handlePointerUp = () => {
2682    stopAllNotes();
2683  };
2684  const handlePointerLeave = () => {
2685    stopAllNotes();
2686  };
2687  reactExports.useEffect(() => {
2688    if (!containerRef.current || !canvasRef.current) return;
2689    const updateDim = () => {
2690      if (containerRef.current && canvasRef.current) {
2691        const { width, height } = containerRef.current.getBoundingClientRect();
2692        let dpr = window.devicePixelRatio || 1;
2693        if (lowPerf) {
2694          dpr = Math.min(dpr, 1);
2695        } else if (isMobile) {
2696          dpr = Math.min(dpr, 1.5);
2697        }
2698        canvasRef.current.width = width * dpr;
2699        canvasRef.current.height = height * dpr;
2700        const ctx = canvasRef.current.getContext("2d");
2701        if (ctx) ctx.scale(dpr, dpr);
2702        canvasRef.current.style.width = `${width}px`;
2703        canvasRef.current.style.height = `${height}px`;
2704      }
2705    };
2706    updateDim();
2707    const ro = new ResizeObserver(updateDim);
2708    ro.observe(containerRef.current);
2709    return () => ro.disconnect();
2710  }, [lowPerf, isMobile]);
2711  const animateRef = reactExports.useRef(() => {
2712  });
2713  const drawNotes = (ctx, notes, t, kw, bkw, yBase, pps, cmap, active = false, labelMode = "off") => {
2714    notes.forEach((note) => {
2715      const { x, w } = getKeyGeometry(note.n, kw, bkw);
2716      const distFromPlayhead = (note.t - t) * pps;
2717      const noteHeight = note.d * pps;
2718      const yBottom = yBase - distFromPlayhead;
2719      const yTop = yBottom - noteHeight;
2720      if (yBottom < -50) return;
2721      const color = cmap[getNoteKey(note)] || "#888";
2722      if (active) {
2723        if (!isMobile) {
2724          ctx.shadowColor = color;
2725        }
2726        ctx.fillStyle = color;
2727        ctx.fillRect(x, yTop, w, noteHeight);
2728      } else {
2729        if (lowPerf) {
2730          ctx.fillStyle = color;
2731          ctx.fillRect(x, yTop, w - 1, noteHeight);
2732        } else if (isMobile) {
2733          ctx.fillStyle = color;
2734          ctx.beginPath();
2735          ctx.roundRect(x, yTop, w - 1, noteHeight, 4);
2736          ctx.fill();
2737        } else {
2738          const grad = ctx.createLinearGradient(x, yTop, x, yBottom);
2739          grad.addColorStop(0, "#222");
2740          grad.addColorStop(1, color);
2741          ctx.fillStyle = grad;
2742          ctx.beginPath();
2743          ctx.roundRect(x, yTop, w - 1, noteHeight, 4);
2744          ctx.fill();
2745          ctx.strokeStyle = "rgba(255,255,255,0.2)";
2746          ctx.lineWidth = 1;
2747          ctx.stroke();
2748        }
2749        if (labelMode !== "off") {
2750          const baseOct = baseOctaveRef.current;
2751          const notesArr = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
2752          const noteName = notesArr[note.n % 12];
2753          const octave = Math.floor(note.n / 12) - 1;
2754          const noteLabel = `${noteName}${octave}`;
2755          const pcLabel = getPcKeyLabel(note.n, baseOct);
2756          let mainLabel = "";
2757          if (labelMode === "notes") {
2758            mainLabel = noteLabel;
2759          } else if (labelMode === "pc") {
2760            if (pcLabel) {
2761              mainLabel = pcLabel;
2762            } else {
2763              mainLabel = noteLabel;
2764            }
2765          }
2766          if (mainLabel) {
2767            const centerX = x + w / 2;
2768            const labelY = yBottom - Math.min(noteHeight / 2, 20);
2769            ctx.textAlign = "center";
2770            ctx.textBaseline = "middle";
2771            ctx.fillStyle = "#fff";
2772            ctx.strokeStyle = "#000000";
2773            ctx.lineWidth = 3;
2774            ctx.lineJoin = "round";
2775            ctx.miterLimit = 2;
2776            let fontSize = w * 0.8;
2777            fontSize = Math.max(12, Math.min(fontSize, 40));
2778            ctx.font = `bold ${fontSize}px monospace`;
2779            ctx.strokeText(mainLabel, centerX, labelY);
2780            ctx.fillText(mainLabel, centerX, labelY);
2781          }
2782        }
2783      }
2784    });
2785  };
2786  const renderScene = (ctx, rawWidth, rawHeight, t) => {
2787    if (micActive) {
2788      const now = Date.now();
2789      const decayTime = 1200;
2790      micActiveNotesRef.current.forEach((timestamp, note) => {
2791        if (now - timestamp > decayTime) {
2792          micActiveNotesRef.current.delete(note);
2793          previewActiveNotesRef.current.delete(note);
2794        }
2795      });
2796    }
2797    let dpr = window.devicePixelRatio || 1;
2798    if (lowPerf) {
2799      dpr = Math.min(dpr, 1);
2800    } else if (isMobile) {
2801      dpr = Math.min(dpr, 1.5);
2802    }
2803    const width = rawWidth / dpr;
2804    const fullHeight = rawHeight / dpr;
2805    const height = fullHeight;
2806    const effectiveTime = t + CONFIG.visualOffset;
2807    const keyHeight = height * 0.15;
2808    const keyTop = height - keyHeight;
2809    const noteAreaHeight = keyTop;
2810    const pixelsPerSecond = noteAreaHeight / CONFIG.lookAheadTime;
2811    const whiteKeyCount = 52;
2812    const keyWidth = width / whiteKeyCount;
2813    const blackKeyWidth = keyWidth * 0.65;
2814    ctx.fillStyle = "#000000";
2815    ctx.fillRect(0, 0, width, fullHeight);
2816    if (!lowPerf) {
2817      ctx.fillStyle = "rgba(255, 255, 255, 0.4)";
2818      starFieldRef.current.forEach((star) => {
2819        star.y -= star.speed * (isPlaying ? 1 : 0) * (playbackRate || 1);
2820        if (star.y < 0) star.y = 1;
2821        if (isMobile) {
2822          ctx.fillRect(star.x * width - star.size, star.y * noteAreaHeight - star.size, star.size * 2, star.size * 2);
2823        } else {
2824          ctx.beginPath();
2825          ctx.arc(star.x * width, star.y * noteAreaHeight, star.size, 0, Math.PI * 2);
2826          ctx.fill();
2827        }
2828      });
2829    }
2830    const noteData = data && Array.isArray(data) ? data : [];
2831    const visibleNotes = [];
2832    const activeNotes = /* @__PURE__ */ new Set();
2833    let left = 0;
2834    let right = noteData.length - 1;
2835    let startIdx = 0;
2836    const searchTime = Math.max(0, effectiveTime - 10);
2837    while (left <= right) {
2838      const mid = Math.floor((left + right) / 2);
2839      if (noteData[mid].t < searchTime) {
2840        left = mid + 1;
2841        startIdx = left;
2842      } else {
2843        right = mid - 1;
2844      }
2845    }
2846    for (let i = startIdx; i < noteData.length; i++) {
2847      const note = noteData[i];
2848      if (note.t > effectiveTime + CONFIG.lookAheadTime) break;
2849      if (hiddenTracks.has(getNoteKey(note))) continue;
2850      if (isPercussionNote(note)) continue;
2851      const timeStart = note.t;
2852      const timeEnd = note.t + note.d;
2853      if (effectiveTime >= timeStart && effectiveTime <= timeEnd) activeNotes.add(note.n);
2854      if (timeEnd > effectiveTime) visibleNotes.push(note);
2855    }
2856    if (practiceMode && noteData.length > 0) {
2857      if (isPlaying) {
2858        const upcomingNotes = [];
2859        for (let i = startIdx; i < noteData.length; i++) {
2860          const note = noteData[i];
2861          if (hiddenTracks.has(getNoteKey(note))) continue;
2862          if (isPercussionNote(note)) continue;
2863          if (note.t <= effectiveTime + 0.05 && note.t > lastSatisfiedTimeRef.current + 0.02) {
2864            upcomingNotes.push(note);
2865          }
2866          if (note.t > effectiveTime + 0.2) break;
2867        }
2868        if (upcomingNotes.length > 0) {
2869          const chordTime = upcomingNotes[0].t;
2870          const chordNotes = upcomingNotes.filter((n) => Math.abs(n.t - chordTime) < 0.05);
2871          const targetNoteNumbers = new Set(chordNotes.map((n) => n.n));
2872          if (targetNoteNumbers.size > 0) {
2873            let allPressed = true;
2874            for (const midiNum of targetNoteNumbers) {
2875              if (!previewActiveNotesRef.current.has(midiNum)) {
2876                allPressed = false;
2877                break;
2878              }
2879            }
2880            if (allPressed) {
2881              lastSatisfiedTimeRef.current = chordTime;
2882              waitingNotesRef.current = null;
2883            } else {
2884              waitingNotesRef.current = targetNoteNumbers;
2885              if (onPlayPause) onPlayPause(false);
2886              if (onSeek) onSeek(chordTime);
2887            }
2888          }
2889        }
2890      } else if (waitingNotesRef.current) {
2891        let allPressed = true;
2892        for (const midiNum of waitingNotesRef.current) {
2893          if (!previewActiveNotesRef.current.has(midiNum)) {
2894            allPressed = false;
2895            break;
2896          }
2897        }
2898        if (allPressed) {
2899          const satisfiedTime = Array.from(waitingNotesRef.current)[0];
2900          const matchedNote = noteData.find((n) => n.n === satisfiedTime && !hiddenTracks.has(getNoteKey(n)) && !isPercussionNote(n) && Math.abs(n.t - effectiveTime) < 0.25);
2901          const satisfiedTimeSec = matchedNote ? matchedNote.t : effectiveTime;
2902          lastSatisfiedTimeRef.current = satisfiedTimeSec;
2903          waitingNotesRef.current = null;
2904          if (onPlayPause) onPlayPause(true);
2905        }
2906      }
2907    } else {
2908      waitingNotesRef.current = null;
2909    }
2910    previewActiveNotesRef.current.forEach((midi2) =>
2910 activeNotes.add(midi2));
2911    if (isPlaying) {
2912      const spawnCount = lowPerf ? 0 : isMobile ? 1 : 8;
2913      activeNotes.forEach((midi2) => {
2914        if (!prevActiveNotesRef.current.has(midi2)) {
2915          const { x, w } = getKeyGeometry(midi2, keyWidth, blackKeyWidth);
2916          const activeNoteObj = visibleNotes.find((n) => n.n === midi2 && effectiveTime >= n.t && effectiveTime <= n.t + n.d);
2917          const color = activeNoteObj ? colorMap[getNoteKey(activeNoteObj)] || "#ffffff" : "#ffffff";
2918          for (let i = 0; i < spawnCount; i++) {
2919            particlesRef.current.push({
2920              x: x + w / 2 + (Math.random() - 0.5) * w,
2921              y: keyTop,
2922              vx: (Math.random() - 0.5) * 4,
2923              vy: -(Math.random() * 5 + 2),
2924              size: Math.random() * 3 + 1,
2925              color,
2926              life: 1,
2927              decay: 0.02 + Math.random() * 0.03
2928            });
2929          }
2930        }
2931      });
2932    }
2933    prevActiveNotesRef.current = activeNotes;
2934    for (let i = particlesRef.current.length - 1; i >= 0; i--) {
2935      const p = particlesRef.current[i];
2936      p.x += p.vx;
2937      p.y += p.vy;
2938      p.life -= p.decay;
2939      p.vy += 0.1;
2940      if (p.life <= 0) {
2941        particlesRef.current.splice(i, 1);
2942        continue;
2943      }
2944      ctx.globalCompositeOperation = "lighter";
2945      ctx.fillStyle = p.color;
2946      ctx.globalAlpha = p.life;
2947      if (isMobile) {
2948        ctx.fillRect(p.x - p.size, p.y - p.size, p.size * 2, p.size * 2);
2949      } else {
2950        ctx.beginPath();
2951        ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
2952        ctx.fill();
2953      }
2954      ctx.globalAlpha = 1;
2955    }
2956    ctx.globalCompositeOperation = "source-over";
2957    if (!isMobile && !lowPerf) {
2958      ctx.save();
2959      ctx.translate(0, height + keyHeight);
2960      ctx.scale(1, -1);
2961      ctx.globalAlpha = 0.15;
2962      drawNotes(ctx, visibleNotes, effectiveTime, keyWidth, blackKeyWidth, keyTop, pixelsPerSecond, colorMap, false, "off");
2963      ctx.restore();
2964    }
2965    drawNotes(ctx, visibleNotes, effectiveTime, keyWidth, blackKeyWidth, keyTop, pixelsPerSecond, colorMap, false, keyboardLabelModeRef.current);
2966    ctx.globalCompositeOperation = "lighter";
2967    if (!isMobile && !lowPerf) {
2968      ctx.shadowBlur = 20;
2969    }
2970    const hitNotes = visibleNotes.filter((n) => activeNotes.has(n.n) && effectiveTime >= n.t && effectiveTime <= n.t + n.d);
2971    drawNotes(ctx, hitNotes, effectiveTime, keyWidth, blackKeyWidth, keyTop, pixelsPerSecond, colorMap, true, "off");
2972    if (!isMobile && !lowPerf) {
2973      ctx.shadowBlur = 0;
2974    }
2975    ctx.globalCompositeOperation = "source-over";
2976    const micNotesSet = micActive ? new Set(micActiveNotesRef.current.keys()) : void 0;
2977    renderSharedKeyboard(ctx, width, height, keyHeight, activeNotes, keyboardLabelModeRef.current, baseOctaveRef.current, waitingNotesRef.current || void 0, micNotesSet);
2978  };
2979  const animate = () => {
2980    if (!canvasRef.current) return;
2981    let visualTime = lastTimeRef.current;
2982    if (isPlaying) {
2983      visualTime += (performance.now() - lastTickRef.current) / 1e3;
2984    }
2985    const ctx = canvasRef.current.getContext("2d", { alpha: false });
2986    if (ctx) {
2987      const { width, height } = canvasRef.current;
2988      renderScene(ctx, width, height, visualTime);
2989    }
2990  };
2991  reactExports.useEffect(() => {
2992    animateRef.current = animate;
2993  });
2994  reactExports.useEffect(() => {
2995    if (starFieldRef.current.length === 0) {
2996      for (let i = 0; i < 80; i++) {
2997        starFieldRef.current.push({
2998          x: Math.random(),
2999          y: Math.random(),
3000          size: Math.random() * 1.5 + 0.5,
3001          speed: Math.random() * 0.05 + 0.01
3002        });
3003      }
3004    }
3005    let lastFrameTime = 0;
3006    const isSlow = isSlowMobile();
3007    const fpsInterval = isSlow ? 1e3 / 30 : 0;
3008    const loop = (timestamp) => {
3009      if (fpsInterval > 0) {
3010        const elapsed = timestamp - lastFrameTime;
3011        if (elapsed >= fpsInterval) {
3012          lastFrameTime = timestamp - elapsed % fpsInterval;
3013          animateRef.current();
3014        }
3015      } else {
3016        animateRef.current();
3017      }
3018      requestRef.current = requestAnimationFrame(loop);
3019    };
3020    requestRef.current = requestAnimationFrame(loop);
3021    return () => {
3022      if (requestRef.current) cancelAnimationFrame(requestRef.current);
3023    };
3024  }, []);
3025  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "w-full h-full relative", style: { backgroundColor: "#000000" }, children: [
3026    /* @__PURE__ */ jsxRuntimeExports.jsx(
3027      "canvas",
3028      {
3029        ref: canvasRef,
3030        className: "w-full h-full block touch-none cursor-pointer",
3031        onPointerDown: (e) => {
3032          var _a;
3033          const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
3034          if (rect) {
3035            const y = e.clientY - rect.top;
3036            const h = rect.height;
3037            const keyHeight = h * 0.15;
3038            const keyTop = h - keyHeight;
3039            if (y >= keyTop) {
3040              handlePointerDown(e);
3041            } else {
3042              if (onPlayPause) onPlayPause();
3043            }
3044          }
3045        },
3046        onPointerUp: handlePointerUp,
3047        onPointerLeave: handlePointerLeave,
3048        onPointerCancel: handlePointerLeave
3049      }
3050    ),
3051    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-[16%] right-4 flex flex-col gap-2 z-50", children: [
3052      /* @__PURE__ */ jsxRuntimeExports.jsx(
3053        "button",
3054        {
3055          onClick: async (e) => {
3056            e.stopPropagation();
3057            if (!midi.isMidiConnected) {
3058              const success = await midi.connectMidi();
3059              if (!success) {
3060                alert("Could not connect to MIDI devices. Please ensure your browser supports Web MIDI and permission is granted.");
3061              }
3062            }
3063          },
3064          className: `p-2 rounded-full backdrop-blur-md border transition-all shadow-lg pointer-events-auto ${midi.isMidiConnected ? "bg-emerald-600/80 border-emerald-400/50 text-white opacity-100" : "bg-black/60 border-white/10 text-white/50 hover:opacity-100 hover:text-white/80"}`,
3065          title: midi.isMidiConnected ? "MIDI Connected" : "Connect MIDI Device",
3066          children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cable, { size: 16 })
3067        }
3068      ),
3069      /* @__PURE__ */ jsxRuntimeExports.jsx(
3070        "button",
3071        {
3072          onClick: (e) => {
3073            e.stopPropagation();
3074            toggleGameMode();
3075          },
3076          className: "p-2 rounded-full backdrop-blur-md border transition-colors pointer-events-auto bg-black/60 border-white/10 text-white/50 hover:text-white/80",
3077          title: "Toggle Game Mode",
3078          children: /* @__PURE__ */ jsxRuntimeExports.jsx(Gamepad2, { size: 16 })
3079        }
3080      ),
3081      /* @__PURE__ */ jsxRuntimeExports.jsx(
3082        "button",
3083        {
3084          onClick: (e) => {
3085            e.stopPropagation();
3086            setPracticeMode((prev) => !prev);
3087          },
3088          className: `p-2 rounded-full backdrop-blur-md border transition-colors pointer-events-auto ${practiceMode ? "bg-amber-600/80 border-amber-400/50 text-white shadow-lg" : "bg-black/60 border-white/10 text-white/50 hover:text-white/80"}`,
3089          title: practiceMode ? "Disable Practice Mode" : "Enable Practice Mode",
3090          children: /* @__PURE__ */ jsxRuntimeExports.jsx(GraduationCap, { size: 16 })
3091        }
3092      ),
3093      /* @__PURE__ */ jsxRuntimeExports.jsx(
3094        "button",
3095        {
3096          onClick: (e) => {
3097            e.stopPropagation();
3098            setMicActive((prev) => !prev);
3099          },
3100          className: `p-2 rounded-full backdrop-blur-md border transition-colors pointer-events-auto ${micActive ? "bg-emerald-600/80 border-emerald-400/50 text-white shadow-lg" : "bg-black/60 border-white/10 text-white/50 hover:text-white/80"}`,
3101          title: micActive ? "Disable Microphone Input" : "Enable Microphone Input",
3102          children: micActive ? /* @__PURE__ */ jsxRuntimeExports.jsx(Mic, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(MicOff, { size: 16 })
3103        }
3104      ),
3105      /* @__PURE__ */ jsxRuntimeExports.jsx(
3106        "button",
3107        {
3108          onClick: (e) => {
3109            e.stopPropagation();
3110            setKeyboardLabelMode((prev) => prev === "pc" ? "notes" : prev === "notes" ? "off" : "pc");
3111          },
3112          className: `p-2 rounded-full backdrop-blur-md border transition-colors pointer-events-auto ${keyboardLabelMode !== "off" ? "bg-indigo-600/80 border-indigo-400/50 text-white" : "bg-black/60 border-white/10 text-white/50 hover:text-white/80"}`,
3113          title: "Toggle Keyboard Labels",
3114          children: /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size: 16 })
3115        }
3116      )
3117    ] })
3118  ] });
3119};
3120const PianoRoll = (props) => {
3121  const [isGameMode, setIsGameMode] = reactExports.useState(props.isGameModeProp || false);
3122  reactExports.useEffect(() => {
3123    if (props.isGameModeProp !== void 0 && props.isGameModeProp !== isGameMode) {
3124      setIsGameMode(props.isGameModeProp);
3125    }
3126  }, [props.isGameModeProp]);
3127  const toggleGameMode = () => {
3128    const newMode = !isGameMode;
3129    if (props.setIsGameModeProp) {
3130      props.setIsGameModeProp(newMode);
3131    } else {
3132      setIsGameMode(newMode);
3133    }
3134  };
3135  if (isGameMode) {
3136    return /* @__PURE__ */ jsxRuntimeExports.jsx(
3137      PianoRollGame,
3138      {
3139        ...props,
3140        toggleGameMode,
3141        soundfont: props.soundfont
3142      }
3143    );
3144  }
3145  return /* @__PURE__ */ jsxRuntimeExports.jsx(PianoRollVisualizer, { ...props, toggleGameMode, soundfont: props.soundfont });
3146};
3147const PercussionRoll = ({
3148  data,
3149  currentTime,
3150  isPlaying,
3151  hiddenTracks,
3152  colorMap,
3153  processedInstruments,
3154  uniqueNames,
3155  onPlayPause,
3156  playbackRate = 1,
3157  tempo = 120,
3158  timeSignature = [4, 4],
3159  soundfont = "MusyngKite"
3160}) => {
3161  const containerRef = reactExports.useRef(null);
3162  const canvasRef = reactExports.useRef(null);
3163  const requestRef = reactExports.useRef(null);
3164  const lastTimeRef = reactExports.useRef(currentTime);
3165  const lastTickRef = reactExports.useRef(0);
3166  const particlesRef = reactExports.useRef([]);
3167  const prevActivePitchesRef = reactExports.useRef(/* @__PURE__ */ new Set());
3168  const starFieldRef = reactExports.useRef([]);
3169  const activeClicksRef = reactExports.useRef(/* @__PURE__ */ new Map());
3170  const lastHitTimesRef = reactExports.useRef(/* @__PURE__ */ new Map());
3171  const lastVisualTimeRef = reactExports.useRef(currentTime);
3172  const isMobile = reactExports.useRef(typeof window !== "undefined" && (/Mobi|Android|iPhone|iPad/i.test(navigator.userAgent) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints > 0 || "ontouchstart" in window)).current;
3173  reactExports.useEffect(() => {
3174    lastTimeRef.current = currentTime;
3175    lastVisualTimeRef.current = currentTime;
3176    lastTickRef.current = performance.now();
3177  }, [currentTime]);
3178  const firstPercTrackIdx = processedInstruments == null ? void 0 : processedInstruments.findIndex((inst) => inst.midiChannel === 9);
3179  const { playNote, stopNote } = useSoundfontKeyboard(
3180    firstPercTrackIdx !== -1 ? firstPercTrackIdx : void 0,
3181    processedInstruments,
3182    void 0,
3183    soundfont
3184  );
3185  const musicalInstruments = React.useMemo(() => {
3186    return (processedInstruments == null ? void 0 : processedInstruments.filter((inst) => inst.midiChannel !== -1)) || [];
3187  }, [processedInstruments]);
3188  const percussionNotes = React.useMemo(() => {
3189    return data.filter((note) => {
3190      if (hiddenTracks.has(note.i)) return false;
3191      let inst = musicalInstruments[note.trackIdx];
3192      if (!inst || inst.midiChannel !== 9) {
3193        inst = processedInstruments == null ? void 0 : processedInstruments.find((i) => i.trackId === note.trackIdx);
3194      }
3195      if (!inst || inst.midiChannel !== 9) {
3196        inst = processedInstruments == null ? void 0 : processedInstruments.find((i) => i.originalName === note.i || i.displayName === note.i || i.name === note.i);
3197      }
3198      if (inst) {
3199        return inst.midiChannel === 9;
3200      }
3201      const nameLower = (note.i || "").toLowerCase();
3202      return nameLower.includes("drum") || nameLower.includes("perc") || nameLower.includes("kit") || nameLower.includes("cymbal");
3203    });
3204  }, [data, hiddenTracks, processedInstruments, musicalInstruments]);
3205  const uniquePitches = React.useMemo(() => {
3206    return Array.from(new Set(percussionNotes.map((n) => n.n))).sort((a, b) => b - a);
3207  }, [percussionNotes]);
3208  reactExports.useEffect(() => {
3209    if (!containerRef.current || !canvasRef.current) return;
3210    const updateDim = () => {
3211      if (containerRef.current && canvasRef.current) {
3212        const { width, height } = containerRef.current.getBoundingClientRect();
3213        const dpr = window.devicePixelRatio || 1;
3214        canvasRef.current.width = width * dpr;
3215        canvasRef.current.height = height * dpr;
3216        const ctx = canvasRef.current.getContext("2d");
3217        if (ctx) ctx.scale(dpr, dpr);
3218        canvasRef.current.style.width = `${width}px`;
3219        canvasRef.current.style.height = `${height}px`;
3220      }
3221    };
3222    updateDim();
3223    const ro = new ResizeObserver(updateDim);
3224    ro.observe(containerRef.current);
3225    return () => ro.disconnect();
3226  }, [uniquePitches.length]);
3227  const renderScene = (ctx, rawWidth, rawHeight, t) => {
3228    const prevT = lastVisualTimeRef.current;
3229    lastVisualTimeRef.current = t;
3230    const dpr = window.devicePixelRatio || 1;
3231    const width = rawWidth / dpr;
3232    const height = rawHeight / dpr;
3233    const labelWidth = 180;
3234    const playheadX = 220;
3235    const topMargin = 25;
3236    const bottomMargin = 25;
3237    const pixelsPerSecond = 140;
3238    const rowCount = Math.max(1, uniquePitches.length);
3239    const rowHeight = (height - topMargin - bottomMargin) / rowCount;
3240    ctx.fillStyle = "#06060c";
3241    ctx.fillRect(0, 0, width, height);
3242    ctx.fillStyle = "rgba(255, 255, 255, 0.35)";
3243    starFieldRef.current.forEach((star) => {
3244      star.x -= star.speed * (isPlaying ? 1 : 0) * (playbackRate || 1);
3245      if (star.x < 0) star.x = 1;
3246      if (isMobile) {
3247        ctx.fillRect(star.x * (width - labelWidth) + labelWidth - star.size, star.y * height - star.size, star.size * 2, star.size * 2);
3248      } else {
3249        ctx.beginPath();
3250        ctx.arc(star.x * (width - labelWidth) + labelWidth, star.y * height, star.size, 0, Math.PI * 2);
3251        ctx.fill();
3252      }
3253    });
3254    ctx.strokeStyle = "rgba(255, 255, 255, 0.04)";
3255    ctx.lineWidth = 1;
3256    for (let r = 0; r <= rowCount; r++) {
3257      const y = topMargin + r * rowHeight;
3258      ctx.beginPath();
3259      ctx.moveTo(labelWidth, y);
3260      ctx.lineTo(width, y);
3261      ctx.stroke();
3262    }
3263    const secondsPerBeat = 60 / (tempo || 120);
3264    const beatsPerMeasure = timeSignature[0] || 4;
3265    const windowStartTime = t - (playheadX - labelWidth) / pixelsPerSecond;
3266    const windowEndTime = t + (width - playheadX) / pixelsPerSecond;
3267    const startBeat = Math.floor(windowStartTime / secondsPerBeat);
3268    const endBeat = Math.ceil(windowEndTime / secondsPerBeat);
3269    for (let b = startBeat; b <= endBeat; b++) {
3270      const beatTime = b * secondsPerBeat;
3271      const bx = playheadX + (beatTime - t) * pixelsPerSecond;
3272      if (bx >= labelWidth && bx <= width) {
3273        const isMeasure = b % beatsPerMeasure === 0;
3274        ctx.strokeStyle = isMeasure ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.03)";
3275        ctx.lineWidth = isMeasure ? 1.5 : 1;
3276        ctx.beginPath();
3277        ctx.moveTo(bx, topMargin);
3278        ctx.lineTo(bx, height - bottomMargin);
3279        ctx.stroke();
3280        if (isMeasure && rowCount > 0) {
3281          ctx.fillStyle = "rgba(255, 255, 255, 0.25)";
3282          ctx.font = "9px monospace";
3283          ctx.textAlign = "center";
3284          ctx.fillText(`${Math.floor(b / beatsPerMeasure) + 1}`, bx, topMargin - 8);
3285        }
3286      }
3287    }
3288    const activePitches = /* @__PURE__ */ new Set();
3289    const nowMs = performance.now();
3290    percussionNotes.forEach((note) => {
3291      const isCrossing = t >= note.t && t <= note.t + note.d;
3292      const crossedOnset = isPlaying && t > prevT && t - prevT < 0.5 && note.t >= prevT && note.t <= t;
3293      if (isCrossing || crossedOnset) {
3294        activePitches.add(note.n);
3295        lastHitTimesRef.current.set(note.n, nowMs);
3296      }
3297    });
3298    lastHitTimesRef.current.forEach((lastTime, pitch) => {
3299      if (nowMs - lastTime < 150) {
3300        activePitches.add(pitch);
3301      } else {
3302        lastHitTimesRef.current.delete(pitch);
3303      }
3304    });
3305    activeClicksRef.current.forEach((_, pitch) => activePitches.add(pitch));
3306    const getNoteColor = (noteName, noteTrackIdx, fallback) => {
3307      if (!processedInstruments || !uniqueNames) return fallback;
3308      let instIdx = processedInstruments.findIndex((i) => i.originalName === noteName || i.displayName === noteName || i.name === noteName);
3309      if (instIdx === -1) {
3310        const inst = musicalInstruments[noteTrackIdx] || processedInstruments.find((i) => i.trackId === noteTrackIdx);
3311        if (inst) instIdx = processedInstruments.indexOf(inst);
3312      }
3313      if (instIdx !== -1 && uniqueNames[instIdx]) {
3314        return colorMap[uniqueNames[instIdx]] || fallback;
3315      }
3316      return colorMap[noteName] || fallback;
3317    };
3318    percussionNotes.forEach((note) => {
3319      const r = uniquePitches.indexOf(note.n);
3320      if (r === -1) return;
3321      const nx = playheadX + (note.t - t) * pixelsPerSecond;
3322      const nw = Math.max(12, note.d * pixelsPerSecond);
3323      const ny = topMargin + r * rowHeight + 4;
3324      const nh = rowHeight - 8;
3325      if (nx + nw < labelWidth || nx > width) return;
3326      const color = getNoteColor(note.i, note.trackIdx, "#a855f7");
3327      const isActive = t >= note.t && t <= note.t + note.d;
3328      ctx.save();
3329      if (isActive && !isMobile) {
3330        ctx.shadowColor = color;
3331        ctx.shadowBlur = 12;
3332      }
3333      if (isMobile) {
3334        ctx.fillStyle = color;
3335        ctx.beginPath();
3336        ctx.roundRect(nx, ny, nw, nh, 4);
3337        ctx.fill();
3338      } else {
3339        const grad = ctx.createLinearGradient(nx, ny, nx + nw, ny);
3340        grad.addColorStop(0, color);
3341        grad.addColorStop(1, "#1e1b4b");
3342        ctx.fillStyle = grad;
3343        ctx.beginPath();
3344        ctx.roundRect(nx, ny, nw, nh, 4);
3345        ctx.fill();
3346        ctx.strokeStyle = isActive ? "#ffffff" : "rgba(255,255,255,0.2)";
3347        ctx.lineWidth = isActive ? 1.5 : 0.8;
3348        ctx.stroke();
3349      }
3350      ctx.restore();
3351    });
3352    if (isPlaying) {
3353      const spawnCount = isMobile ? 2 : 8;
3354      activePitches.forEach((pitch) => {
3355        if (!prevActivePitchesRef.current.has(pitch)) {
3356          const r = uniquePitches.indexOf(pitch);
3357          if (r !== -1) {
3358            const py = topMargin + r * rowHeight + rowHeight / 2;
3359            const noteObj = percussionNotes.find((n) => n.n === pitch && t >= n.t && t <= n.t + n.d);
3360            const color = noteObj ? getNoteColor(noteObj.i, noteObj.trackIdx, "#ffffff") : "#ffffff";
3361            for (let i = 0; i < spawnCount; i++) {
3362              particlesRef.current.push({
3363                x: playheadX,
3364                y: py,
3365                vx: -(Math.random() * 4 + 1),
3366                // burst leftwards
3367                vy: (Math.random() - 0.5) * 3,
3368                size: Math.random() * 2.5 + 1.2,
3369                color,
3370                life: 1,
3371                decay: 0.03 + Math.random() * 0.03
3372              });
3373            }
3374          }
3375        }
3376      });
3377    }
3378    prevActivePitchesRef.current = activePitches;
3379    for (let i = particlesRef.current.length - 1; i >= 0; i--) {
3380      const p = particlesRef.current[i];
3381      p.x += p.vx;
3382      p.y += p.vy;
3383      p.life -= p.decay;
3384      if (p.life <= 0) {
3385        particlesRef.current.splice(i, 1);
3386        continue;
3387      }
3388      ctx.save();
3389      ctx.globalCompositeOperation = "lighter";
3390      ctx.fillStyle = p.color;
3391      ctx.globalAlpha = p.life;
3392      if (isMobile) {
3393        ctx.fillRect(p.x - p.size, p.y - p.size, p.size * 2, p.size * 2);
3394      } else {
3395        ctx.beginPath();
3396        ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
3397        ctx.fill();
3398      }
3399      ctx.restore();
3400    }
3401    ctx.save();
3402    ctx.strokeStyle = "#818cf8";
3403    ctx.lineWidth = 2;
3404    if (!isMobile) {
3405      ctx.shadowColor = "#4f46e5";
3406      ctx.shadowBlur = 8;
3407    }
3408    ctx.beginPath();
3409    ctx.moveTo(playheadX, topMargin);
3410    ctx.lineTo(playheadX, height - bottomMargin);
3411    ctx.stroke();
3412    ctx.fillStyle = "#818cf8";
3413    ctx.beginPath();
3414    ctx.moveTo(playheadX - 6, topMargin);
3415    ctx.lineTo(playheadX + 6, topMargin);
3416    ctx.lineTo(playheadX, topMargin + 8);
3417    ctx.closePath();
3418    ctx.fill();
3419    ctx.restore();
3420    ctx.fillStyle = "#08080f";
3421    ctx.fillRect(0, 0, labelWidth, height);
3422    ctx.strokeStyle = "rgba(255, 255, 255, 0.12)";
3423    ctx.lineWidth = 2;
3424    ctx.beginPath();
3425    ctx.moveTo(labelWidth, 0);
3426    ctx.lineTo(labelWidth, height);
3427    ctx.stroke();
3428    uniquePitches.forEach((pitch, r) => {
3429      const y = topMargin + r * rowHeight;
3430      const isActive = activePitches.has(pitch);
3431      const labelY = y + rowHeight / 2;
3432      ctx.font = "11px system-ui, sans-serif";
3433      ctx.fillStyle = isActive ? "#c7d2fe" : "#71717a";
3434      ctx.textAlign = "left";
3435      ctx.textBaseline = "middle";
3436      const instName = GM_PERCUSSION_MAP[pitch] || `Percussion (${pitch})`;
3437      const maxTextWidth = labelWidth - 35;
3438      let displayName = instName;
3439      if (ctx.measureText(instName).width > maxTextWidth) {
3440        while (ctx.measureText(displayName + "...").width > maxTextWidth && displayName.length > 0) {
3441          displayName = displayName.slice(0, -1);
3442        }
3443        displayName += "...";
3444      }
3445      ctx.fillText(displayName, 15, labelY);
3446      if (isActive) {
3447        ctx.fillStyle = "#818cf8";
3448        ctx.shadowColor = "#818cf8";
3449        ctx.shadowBlur = 6;
3450        ctx.beginPath();
3451        ctx.arc(labelWidth - 15, labelY, 4, 0, Math.PI * 2);
3452        ctx.fill();
3453        ctx.shadowBlur = 0;
3454      } else {
3455        ctx.strokeStyle = "#27272a";
3456        ctx.lineWidth = 1;
3457        ctx.beginPath();
3458        ctx.arc(labelWidth - 15, labelY, 4, 0, Math.PI * 2);
3459        ctx.stroke();
3460      }
3461    });
3462    ctx.fillStyle = "#06060c";
3463    ctx.fillRect(labelWidth, 0, width - labelWidth, topMargin);
3464    ctx.fillRect(labelWidth, height - bottomMargin, width - labelWidth, bottomMargin);
3465    ctx.fillStyle = "#3f3f46";
3466    ctx.font = "bold 9px system-ui, sans-serif";
3467    ctx.textAlign = "left";
3468    ctx.fillText("PERCUSSION INSTRUMENT", 15, topMargin / 2 + 2);
3469  };
3470  const animate = () => {
3471    if (!canvasRef.current) return;
3472    let visualTime = lastTimeRef.current;
3473    if (isPlaying) {
3474      visualTime += (performance.now() - lastTickRef.current) / 1e3;
3475    }
3476    const ctx = canvasRef.current.getContext("2d", { alpha: false });
3477    if (ctx) {
3478      const { width, height } = canvasRef.current;
3479      renderScene(ctx, width, height, visualTime);
3480    }
3481  };
3482  const animateRef = reactExports.useRef(() => {
3483  });
3484  reactExports.useEffect(() => {
3485    animateRef.current = animate;
3486  });
3487  reactExports.useEffect(() => {
3488    if (starFieldRef.current.length === 0) {
3489      for (let i = 0; i < 50; i++) {
3490        starFieldRef.current.push({
3491          x: Math.random(),
3492          y: Math.random(),
3493          size: Math.random() * 1.2 + 0.4,
3494          speed: Math.random() * 3e-3 + 1e-3
3495        });
3496      }
3497    }
3498    let lastFrameTime = 0;
3499    const isSlow = isSlowMobile();
3500    const fpsInterval = isSlow ? 1e3 / 30 : 0;
3501    const loop = (timestamp) => {
3502      if (fpsInterval > 0) {
3503        const elapsed = timestamp - lastFrameTime;
3504        if (elapsed >= fpsInterval) {
3505          lastFrameTime = timestamp - elapsed % fpsInterval;
3506          animateRef.current();
3507        }
3508      } else {
3509        animateRef.current();
3510      }
3511      requestRef.current = requestAnimationFrame(loop);
3512    };
3513    requestRef.current = requestAnimationFrame(loop);
3514    return () => {
3515      if (requestRef.current) cancelAnimationFrame(requestRef.current);
3516    };
3517  }, []);
3518  const handlePointerDown = (e) => {
3519    if (!canvasRef.current) return;
3520    const rect = canvasRef.current.getBoundingClientRect();
3521    const clickX = e.clientX - rect.left;
3522    const clickY = e.clientY - rect.top;
3523    const labelWidth = 180;
3524    const topMargin = 25;
3525    const bottomMargin = 25;
3526    if (clickX <= labelWidth) {
3527      e.stopPropagation();
3528      const rowCount = Math.max(1, uniquePitches.length);
3529      const rowHeight = (rect.height - topMargin - bottomMargin) / rowCount;
3530      const clickedRow = Math.floor((clickY - topMargin) / rowHeight);
3531      if (clickedRow >= 0 && clickedRow < uniquePitches.length) {
3532        const pitch = uniquePitches[clickedRow];
3533        if (activeClicksRef.current.has(pitch)) {
3534          clearTimeout(activeClicksRef.current.get(pitch));
3535        }
3536        playNote(pitch, 9);
3537        const timeoutId = setTimeout(() => {
3538          stopNote(pitch, 9);
3539          activeClicksRef.current.delete(pitch);
3540        }, 250);
3541        activeClicksRef.current.set(pitch, timeoutId);
3542      }
3543    } else {
3544      if (onPlayPause) onPlayPause();
3545    }
3546  };
3547  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: containerRef, className: "w-full h-full relative overflow-hidden", style: { backgroundColor: "#06060c" }
3547, children: uniquePitches.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-zinc-500 gap-2", children: [
3548    /* @__PURE__ */ jsxRuntimeExports.jsx(Drum, { size: 32, className: "opacity-40 animate-pulse" }),
3549    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: "No percussion notes found in this track." })
3550  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
3551    "canvas",
3552    {
3553      ref: canvasRef,
3554      className: "w-full h-full block touch-none cursor-pointer",
3555      onPointerDown: handlePointerDown
3556    }
3557  ) });
3558};
3559const COMMON_WORDS_LIST = [
3560  // Pronouns & Contractions
3561  "i",
3562  "me",
3563  "my",
3564  "myself",
3565  "we",
3566  "our",
3567  "ours",
3568  "ourselves",
3569  "you",
3570  "your",
3571  "yours",
3572  "yourself",
3573  "yourselves",
3574  "he",
3575  "him",
3576  "his",
3577  "himself",
3578  "she",
3579  "her",
3580  "hers",
3581  "herself",
3582  "it",
3583  "its",
3584  "itself",
3585  "they",
3586  "them",
3587  "their",
3588  "theirs",
3589  "themselves",
3590  "what",
3591  "which",
3592  "who",
3593  "whom",
3594  "this",
3595  "that",
3596  "these",
3597  "those",
3598  "am",
3599  "is",
3600  "are",
3601  "was",
3602  "were",
3603  "be",
3604  "been",
3605  "being",
3606  "have",
3607  "has",
3608  "had",
3609  "having",
3610  "do",
3611  "does",
3612  "did",
3613  "doing",
3614  "a",
3615  "an",
3616  "the",
3617  "and",
3618  "but",
3619  "if",
3620  "or",
3621  "because",
3622  "as",
3623  "until",
3624  "while",
3625  "of",
3626  "at",
3627  "by",
3628  "for",
3629  "with",
3630  "about",
3631  "against",
3632  "between",
3633  "into",
3634  "through",
3635  "during",
3636  "before",
3637  "after",
3638  "above",
3639  "below",
3640  "to",
3641  "from",
3642  "up",
3643  "down",
3644  "in",
3645  "out",
3646  "on",
3647  "off",
3648  "over",
3649  "under",
3650  "again",
3651  "further",
3652  "then",
3653  "once",
3654  "here",
3655  "there",
3656  "when",
3657  "where",
3658  "why",
3659  "how",
3660  "all",
3661  "any",
3662  "both",
3663  "each",
3664  "few",
3665  "more",
3666  "most",
3667  "other",
3668  "some",
3669  "such",
3670  "no",
3671  "nor",
3672  "not",
3673  "only",
3674  "own",
3675  "same",
3676  "so",
3677  "than",
3678  "too",
3679  "very",
3680  "s",
3681  "t",
3682  "can",
3683  "will",
3684  "just",
3685  "don",
3686  "should",
3687  "now",
3688  "im",
3689  "youre",
3690  "hes",
3691  "shes",
3692  "its",
3693  "were",
3694  "theyre",
3695  "ive",
3696  "youve",
3697  "weve",
3698  "theyve",
3699  "id",
3700  "youd",
3701  "hed",
3702  "shed",
3703  "wed",
3704  "theyd",
3705  "ill",
3706  "youll",
3707  "hell",
3708  "shell",
3709  "well",
3710  "theyll",
3711  "cant",
3712  "dont",
3713  "didnt",
3714  "wasnt",
3715  "werent",
3716  "havent",
3717  "hasnt",
3718  "hadnt",
3719  "isnt",
3720  "arent",
3721  "wouldnt",
3722  "shouldnt",
3723  "couldnt",
3724  "doesnt",
3725  // Common Verbs, Nouns, Adjectives, Adverbs in lyrics
3726  "love",
3727  "loved",
3728  "loves",
3729  "loving",
3730  "hate",
3731  "hated",
3732  "hating",
3733  "like",
3734  "liked",
3735  "likes",
3736  "liking",
3737  "want",
3738  "wants",
3739  "wanted",
3740  "wanting",
3741  "need",
3742  "needs",
3743  "needed",
3744  "needing",
3745  "feel",
3746  "feels",
3747  "felt",
3748  "feeling",
3749  "say",
3750  "says",
3751  "said",
3752  "saying",
3753  "tell",
3754  "tells",
3755  "told",
3756  "telling",
3757  "think",
3758  "thinks",
3759  "thought",
3760  "thinking",
3761  "know",
3762  "knows",
3763  "knew",
3764  "known",
3765  "knowing",
3766  "see",
3767  "sees",
3768  "saw",
3769  "seen",
3770  "seeing",
3771  "hear",
3772  "hears",
3773  "heard",
3774  "hearing",
3775  "listen",
3776  "listened",
3777  "listening",
3778  "look",
3779  "looks",
3780  "looked",
3781  "looking",
3782  "make",
3783  "makes",
3784  "made",
3785  "making",
3786  "take",
3787  "takes",
3788  "took",
3789  "taken",
3790  "taking",
3791  "give",
3792  "gives",
3793  "gave",
3794  "given",
3795  "giving",
3796  "go",
3797  "goes",
3798  "went",
3799  "gone",
3800  "going",
3801  "come",
3802  "comes",
3803  "came",
3804  "coming",
3805  "find",
3806  "finds",
3807  "found",
3808  "finding",
3809  "keep",
3810  "keeps",
3811  "kept",
3812  "keeping",
3813  "hold",
3814  "holds",
3815  "held",
3816  "holding",
3817  "turn",
3818  "turns",
3819  "turned",
3820  "turning",
3821  "walk",
3822  "walks",
3823  "walked",
3824  "walking",
3825  "run",
3826  "runs",
3827  "ran",
3828  "running",
3829  "sing",
3830  "sings",
3831  "sang",
3832  "sung",
3833  "singing",
3834  "dance",
3835  "dances",
3836  "danced",
3837  "dancing",
3838  "play",
3839  "plays",
3840  "played",
3841  "playing",
3842  "work",
3843  "works",
3844  "worked",
3845  "working",
3846  "live",
3847  "lives",
3848  "lived",
3849  "living",
3850  "die",
3851  "dies",
3852  "died",
3853  "dying",
3854  "dead",
3855  "death",
3856  "hope",
3857  "hopes",
3858  "hoped",
3859  "hoping",
3860  "wish",
3861  "wishes",
3862  "wished",
3863  "wishing",
3864  "dream",
3865  "dreams",
3866  "dreamed",
3867  "dreaming",
3868  "call",
3869  "calls",
3870  "called",
3871  "calling",
3872  "speak",
3873  "speaks",
3874  "spoke",
3875  "spoken",
3876  "speaking",
3877  "talk",
3878  "talks",
3879  "talked",
3880  "talking",
3881  "cry",
3882  "cries",
3883  "cried",
3884  "crying",
3885  "laugh",
3886  "laughs",
3887  "laughed",
3888  "laughing",
3889  "smile",
3890  "smiles",
3891  "smiled",
3892  "smiling",
3893  "wait",
3894  "waits",
3895  "waited",
3896  "waiting",
3897  "stay",
3898  "stays",
3899  "stayed",
3900  "staying",
3901  "leave",
3902  "leaves",
3903  "left",
3904  "leaving",
3905  "stop",
3906  "stops",
3907  "stopped",
3908  "stopping",
3909  "start",
3910  "starts",
3911  "started",
3912  "starting",
3913  "begin",
3914  "begins",
3915  "begun",
3916  "began",
3917  "beginning",
3918  "try",
3919  "tries",
3920  "tried",
3921  "trying",
3922  "help",
3923  "helps",
3924  "helped",
3925  "helping",
3926  "show",
3927  "shows",
3928  "showed",
3929  "shown",
3930  "showing",
3931  "bring",
3932  "brings",
3933  "brought",
3934  "bringing",
3935  "carry",
3936  "carries",
3937  "carried",
3938  "carrying",
3939  "fall",
3940  "falls",
3941  "fell",
3942  "fallen",
3943  "falling",
3944  "rise",
3945  "rises",
3946  "rose",
3947  "risen",
3948  "rising",
3949  "stand",
3950  "stands",
3951  "stood",
3952  "standing",
3953  "sit",
3954  "sits",
3955  "sat",
3956  "sitting",
3957  "lose",
3958  "loses",
3959  "lost",
3960  "losing",
3961  "win",
3962  "wins",
3963  "won",
3964  "winning",
3965  "break",
3966  "breaks",
3967  "broke",
3968  "broken",
3969  "breaking",
3970  "build",
3971  "builds",
3972  "built",
3973  "building",
3974  "burn",
3975  "burns",
3976  "burnt",
3977  "burned",
3978  "burning",
3979  "cut",
3980  "cuts",
3981  "cutting",
3982  "draw",
3983  "draws",
3984  "drew",
3985  "drawn",
3986  "drawing",
3987  "drive",
3988  "drives",
3989  "drove",
3990  "driven",
3991  "driving",
3992  "eat",
3993  "eats",
3994  "ate",
3995  "eaten",
3996  "eating",
3997  "drink",
3998  "drinks",
3999  "drank",
4000  "drunk",
4001  "drinking",
4002  "fly",
4003  "flies",
4004  "flew",
4005  "flown",
4006  "flying",
4007  "grow",
4008  "grows",
4009  "grew",
4010  "grown",
4011  "growing",
4012  "growl",
4013  "growls",
4014  "growled",
4015  "growling",
4016  "hide",
4017  "hides",
4018  "hid",
4019  "hidden",
4020  "hiding",
4021  "hit",
4022  "hits",
4023  "hitting",
4024  "hurt",
4025  "hurts",
4026  "hurting",
4027  "jump",
4028  "jumps",
4029  "jumped",
4030  "jumping",
4031  "lead",
4032  "leads",
4033  "led",
4034  "leading",
4035  "ride",
4036  "rides",
4037  "rode",
4038  "ridden",
4039  "riding",
4040  "shine",
4041  "shines",
4042  "shone",
4043  "shining",
4044  "shut",
4045  "shuts",
4046  "shutting",
4047  "sleep",
4048  "sleeps",
4049  "slept",
4050  "sleeping",
4051  "spend",
4052  "spends",
4053  "spent",
4054  "spending",
4055  "steal",
4056  "steals",
4057  "stole",
4058  "stolen",
4059  "stealing",
4060  "throw",
4061  "throws",
4062  "threw",
4063  "thrown",
4064  "throwing",
4065  "write",
4066  "writes",
4067  "wrote",
4068  "written",
4069  "writing",
4070  "speak",
4071  "speaks",
4072  "spoke",
4073  "spoken",
4074  "speaking",
4075  "apologize",
4076  "sorry",
4077  "excuse",
4078  "forgive",
4079  "forgiven",
4080  "forgiving",
4081  "please",
4082  "thanks",
4083  "thank",
4084  "welcome",
4085  // Nouns & Adjectives (Common in songs)
4086  "heart",
4087  "hearts",
4088  "mind",
4089  "minds",
4090  "soul",
4091  "souls",
4092  "body",
4093  "bodies",
4094  "life",
4095  "lives",
4096  "world",
4097  "worlds",
4098  "earth",
4099  "sky",
4100  "skies",
4101  "sun",
4102  "moon",
4103  "star",
4104  "stars",
4105  "cloud",
4106  "clouds",
4107  "rain",
4108  "wind",
4109  "snow",
4110  "storm",
4111  "fire",
4112  "fires",
4113  "water",
4114  "sea",
4115  "ocean",
4116  "river",
4117  "lake",
4118  "stone",
4119  "stones",
4120  "rock",
4121  "rocks",
4122  "sand",
4123  "dust",
4124  "light",
4125  "lights",
4126  "dark",
4127  "darkness",
4128  "shadow",
4129  "shadows",
4130  "night",
4131  "nights",
4132  "day",
4133  "days",
4134  "morning",
4135  "evening",
4136  "afternoon",
4137  "noon",
4138  "midnight",
4139  "time",
4140  "times",
4141  "year",
4142  "years",
4143  "month",
4144  "months",
4145  "week",
4146  "weeks",
4147  "hour",
4148  "hours",
4149  "minute",
4150  "minutes",
4151  "second",
4152  "seconds",
4153  "moment",
4154  "moments",
4155  "forever",
4156  "always",
4157  "never",
4158  "sometimes",
4159  "today",
4160  "yesterday",
4161  "tomorrow",
4162  "tonight",
4163  "home",
4164  "house",
4165  "room",
4166  "door",
4167  "window",
4168  "street",
4169  "road",
4170  "city",
4171  "town",
4172  "country",
4173  "place",
4174  "places",
4175  "space",
4176  "sky",
4177  "skies",
4178  "heaven",
4179  "hell",
4180  "angel",
4181  "angels",
4182  "devil",
4183  "devils",
4184  "god",
4185  "gods",
4186  "man",
4187  "men",
4188  "woman",
4189  "women",
4190  "boy",
4191  "boys",
4192  "girl",
4193  "girls",
4194  "child",
4195  "children",
4196  "baby",
4197  "babies",
4198  "friend",
4199  "friends",
4200  "enemy",
4201  "enemies",
4202  "family",
4203  "mother",
4204  "father",
4205  "brother",
4206  "sister",
4207  "son",
4208  "daughter",
4209  "king",
4210  "queen",
4211  "head",
4212  "face",
4213  "eye",
4214  "eyes",
4215  "ear",
4216  "ears",
4217  "nose",
4218  "mouth",
4219  "lip",
4220  "lips",
4221  "teeth",
4222  "tooth",
4223  "hair",
4224  "hand",
4225  "hands",
4226  "arm",
4227  "arms",
4228  "leg",
4229  "legs",
4230  "foot",
4231  "feet",
4232  "finger",
4233  "fingers",
4234  "toe",
4235  "toes",
4236  "back",
4237  "chest",
4238  "shoulder",
4239  "shoulders",
4240  "fire",
4241  "red",
4242  "blue",
4243  "green",
4244  "yellow",
4245  "orange",
4246  "purple",
4247  "pink",
4248  "brown",
4249  "black",
4250  "white",
4251  "gray",
4252  "grey",
4253  "silver",
4254  "gold",
4255  "golden",
4256  "bright",
4257  "clear",
4258  "cloudy",
4259  "sunny",
4260  "windy",
4261  "rainy",
4262  "stormy",
4263  "cold",
4264  "hot",
4265  "warm",
4266  "cool",
4267  "dry",
4268  "wet",
4269  "dirty",
4270  "clean",
4271  "soft",
4272  "hard",
4273  "loud",
4274  "quiet",
4275  "silent",
4276  "silence",
4277  "sound",
4278  "sounds",
4279  "voice",
4280  "voices",
4281  "noise",
4282  "noises",
4283  "sweet",
4284  "sour",
4285  "bitter",
4286  "salty",
4287  "happy",
4288  "sad",
4289  "angry",
4290  "scared",
4291  "afraid",
4292  "fear",
4293  "brave",
4294  "weak",
4295  "strong",
4296  "power",
4297  "heavy",
4298  "light",
4299  "fast",
4300  "slow",
4301  "quick",
4302  "rapid",
4303  "sudden",
4304  "suddenly",
4305  "slowly",
4306  "quickly",
4307  "high",
4308  "low",
4309  "deep",
4310  "shallow",
4311  "tall",
4312  "short",
4313  "long",
4314  "wide",
4315  "narrow",
4316  "thick",
4317  "thin",
4318  "fat",
4319  "slim",
4320  "skinny",
4321  "young",
4322  "old",
4323  "new",
4324  "ancient",
4325  "modern",
4326  "future",
4327  "past",
4328  "present",
4329  "true",
4330  "truth",
4331  "false",
4332  "lie",
4333  "lies",
4334  "beautiful",
4335  "pretty",
4336  "ugly",
4337  "cute",
4338  "nice",
4339  "kind",
4340  "mean",
4341  "cruel",
4342  "gentle",
4343  "rough",
4344  "smooth",
4345  "rope",
4346  "ropes",
4347  "ground",
4348  "pain",
4349  "hurt",
4350  "ache",
4351  "sickness",
4352  "healthy",
4353  "sick",
4354  "ill",
4355  "well",
4356  "fine",
4357  "good",
4358  "bad",
4359  "better",
4360  "best",
4361  "worse",
4362  "worst",
4363  "more",
4364  "most",
4365  "less",
4366  "least",
4367  "little",
4368  "few",
4369  "many",
4370  "much",
4371  "lot",
4372  "lots",
4373  "plenty",
4374  "some",
4375  "several",
4376  "all",
4377  "every",
4378  "each",
4379  "any",
4380  "no",
4381  "none",
4382  "nothing",
4383  "something",
4384  "anything",
4385  "everything",
4386  "someone",
4387  "somebody",
4388  "everyone",
4389  "everybody",
4390  "anyone",
4391  "anybody",
4392  "nobody",
4393  "noone",
4394  "nowhere",
4395  "somewhere",
4396  "everywhere",
4397  "anywhere",
4398  "here",
4399  "there",
4400  "where",
4401  "why",
4402  "how",
4403  "what",
4404  "which",
4405  "who",
4406  "whom",
4407  "whose",
4408  "when",
4409  "whenever",
4410  "wherever",
4411  "however",
4412  "another",
4413  "nother"
4414];
4415const COMMON_WORDS = new Set(COMMON_WORDS_LIST.map((w) => w.toLowerCase()));
4416const memo = {};
4417function segmentWord(word) {
4418  const w = word.toLowerCase().trim();
4419  if (!w) return null;
4420  if (COMMON_WORDS.has(w)) {
4421    return [word];
4422  }
4423  if (w.length <= 3) {
4424    return null;
4425  }
4426  if (memo[w] !== void 0) {
4427    return memo[w];
4428  }
4429  let bestSplit = null;
4430  if (Object.keys(memo).length > 5e3) {
4431    for (const key in memo) {
4432      delete memo[key];
4433    }
4434  }
4435  for (let i = 2; i <= w.length - 2; i++) {
4436    const leftLower = w.substring(0, i);
4437    const rightLower = w.substring(i);
4438    if (COMMON_WORDS.has(leftLower)) {
4439      const leftOrig = word.substring(0, i);
4440      const rightOrig = word.substring(i);
4441      let rightSplit = null;
4442      if (COMMON_WORDS.has(rightLower)) {
4443        rightSplit = [rightOrig];
4444      } else {
4445        rightSplit = segmentWord(rightOrig);
4446      }
4447      if (rightSplit) {
4448        const currentSplit = [leftOrig, ...rightSplit];
4449        if (!bestSplit || currentSplit.length < bestSplit.length) {
4450          bestSplit = currentSplit;
4451        }
4452      }
4453    }
4454  }
4455  memo[w] = bestSplit;
4456  return bestSplit;
4457}
4458function splitGluedWords(txt) {
4459  if (!txt) return txt;
4460  let s = txt;
4461  s = s.replace(/I'mHear/g, "I'm Hear");
4462  s = s.replace(/imhear/gi, "I'm hear");
4463  s = s.replace(/firered/gi, "fire red");
4464  s = s.replace(/inblue/gi, "in blue");
4465  s = s.replace(/yousay/gi, "you say");
4466  s = s.replace(/yo u/gi, "you");
4467  s = s.replace(/apologilt's/gi, "apologize It's");
4468  s = s.replace(/apologiit's/gi, "apologize It's");
4469  s = s.replace(/apologiIt's/g, "apologize It's");
4470  s = s.replace(/I'm([A-Z])/g, "I'm $1");
4471  s = s.replace(/it's([A-Z])/gi, "it's $1");
4472  s = s.replace(/don't([A-Z])/gi, "don't $1");
4473  s = s.replace(/didn't([A-Z])/gi, "didn't $1");
4474  const wordRegex = /[A-Za-z]+('[A-Za-z]+)?/g;
4475  return s.replace(wordRegex, (match) => {
4476    if (match.includes("'")) {
4477      match.split("'");
4478      return match;
4479    }
4480    const camelSplit = match.replace(/([a-z])([A-Z])/g, "$1 $2");
4481    if (camelSplit !== match) {
4482      return camelSplit.split(" ").map((w) => {
4483        const segmented2 = segmentWord(w);
4484        return segmented2 ? segmented2.join(" ") : w;
4485      }).join(" ");
4486    }
4487    const segmented = segmentWord(match);
4488    if (segmented && segmented.length > 1) {
4489      return segmented.join(" ");
4490    }
4491    return match;
4492  });
4493}
4494function shouldJoinSyllables(prev, curr) {
4495  const p = prev.toLowerCase().trim().replace(/['"’“”]/g, "");
4496  const c = curr.toLowerCase().trim().replace(/['"’“”]/g, "");
4497  if (!p || !c) return false;
4498  if (COMMON_WORDS.has(p + c)) {
4499    return true;
4500  }
4501  const commonCompleteWords = /^(i|im|id|ive|you|your|he|she|it|its|we|they|them|me|us|him|her|and|or|but|in|on|at|to|for|of|with|by|from|up|down|off|out|over|under|the|a|an|this|that|these|those|some|any|no|not|yes|ok|okay|too|very|ten|feet|fire|red|blue|say|says|said|hear|hears|heard|hearin|tell|tells|told|think|thinks|thought|sorry|apologize|another|nother)$/;
4502  if (commonCompleteWords.test(p)) return false;
4503  const prefixes = /^(hol|gro|hea|sor|di|apo|lo|gi|fi|an|be|de|re|con|pro|ex|im|un|pre|se|al|ar|as|at|ob|op|tra|trans|in|un|dis|mis|over|under|sub|super|inter|multi|wan|per|son|rev|o|lu|tion|ab|ac|ad|af|ag|al|an|ap|as|at|co|col|com|cor|dif|dis|ef|em|en|ep|es|ex|il|im|in|ir|ne|non|ob|oc|of|op|pe|poly|post|sub|suf|sug|sup|sur|sus|sym|syn|tele|tri|ultra|uni|vi|vo|shout|ech|whis|pers|con|ver|sa)$/;
4504  const suffixes = /^(ding|und|in'|ry|nd't|ze|re|gel|ven|y|ly|ing|ed|es|er|est|s|tion|sion|ment|ness|less|ful|able|ible|al|ic|ous|ive|ist|ism|ize|ise|ate|fy|en|some|ward|wise|na|per|son|rev|o|lu|tion|la|gy|gies|t|d|m|ve|ll|re|s|o|cho|pers|sa|ces|ross|g)$/;
4505  if (prefixes.test(p) || suffixes.test(c)) return true;
4506  return false;
4507}
4508const cleanGluedWords$3 = (txt) => {
4509  return splitGluedWords(txt);
4510};
4511const LyricsHighway = ({
4512  track,
4513  currentTime,
4514  melodyData,
4515  pianoRollData,
4516  isPlaying,
4517  isMicEnabled,
4518  setIsMicEnabled,
4519  score,
4520  maxScore,
4521  hitFlash,
4522  currentVolume,
4523  devices,
4524  selectedDevice,
4525  setSelectedDevice
4526}) => {
4527  var _a;
4528  const { t } = useTranslation();
4529  const rawLyrics = ((_a = track.midiMetadata) == null ? void 0 : _a.lyrics) || [];
4530  const highwayData = reactExports.useMemo(() => {
4531    if (rawLyrics.length === 0) return [];
4532    let pitchSourceNotes = [];
4533    let bestTrackNotes = [];
4534    let maxAlignments = 0;
4535    if (pianoRollData && pianoRollData.length > 0) {
4536      const byTrack = /* @__PURE__ */ new Map();
4537      pianoRollData.forEach((note) => {
4538        const tid = note.i ?? "default";
4539        if (!byTrack.has(tid)) byTrack.set(tid, []);
4540        byTrack.get(tid).push(note);
4541      });
4542      byTrack.forEach((notes) => {
4543        let alignments = 0;
4544        rawLyrics.forEach((lyric) => {
4545          const match = notes.find((n) => Math.abs(n.t - lyric.t) < 0.3);
4546          if (match) alignments++;
4547        });
4548        if (alignments > maxAlignments) {
4549          maxAlignments = alignments;
4550          bestTrackNotes = notes.map((n) => ({ start: n.t, duration: n.d, midi: n.n }));
4551        }
4552      });
4553    }
4554    if (maxAlignments > rawLyrics.length * 0.15) {
4555      pitchSourceNotes = bestTrackNotes;
4556    } else if (melodyData && melodyData.length > 0) {
4557      pitchSourceNotes = melodyData;
4558    }
4559    const blocks = [];
4560    let minMidi = 127;
4561    let maxMidi = 0;
4562    rawLyrics.forEach((syl, i) => {
4563      const nextSyl = rawLyrics[i + 1];
4564      let duration = nextSyl ? Math.min(nextSyl.t - syl.t, 1) : 1;
4565      let midi = 60;
4566      const match = pitchSourceNotes.find((n) => n.start >= syl.t - 0.2 && n.start <= syl.t + 0.2);
4567      if (match) {
4568        midi = match.midi;
4569        duration = match.duration;
4570      }
4571      let txt = syl.txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ").trim();
4572      txt = cleanGluedWords$3(txt);
4573      if (txt) {
4574        if (midi < minMidi) minMidi = midi;
4575        if (midi > maxMidi) maxMidi = midi;
4576        blocks.push({
4577          id: i,
4578          txt,
4579          start: syl.t,
4580          duration,
4581          midi
4582        });
4583      }
4584    });
4585    if (maxMidi - minMidi < 12) {
4586      const mid = (maxMidi + minMidi) / 2;
4587      minMidi = mid - 6;
4588      maxMidi = mid + 6;
4589    }
4590    return blocks.map((b) => ({
4591      ...b,
4592      normalizedPitch: Math.max(0, Math.min(1, (b.midi - minMidi) / (maxMidi - minMidi || 1)))
4593    }));
4594  }, [rawLyrics, melodyData, pianoRollData]);
4595  const SING_LINE_PCT = 20;
4596  const TIME_WINDOW_SEC = 10;
4597  const PIXELS_PER_SEC = 80 / TIME_WINDOW_SEC;
4598  if (highwayData.length === 0) {
4599    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full bg-[#111] text-zinc-500 font-serif italic text-lg", children: t("karaoke.noLyrics") });
4600  }
4601  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full bg-[#111] overflow-hidden relative w-full", children: [
4602    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-4 left-4 right-4 z-50 flex items-center justify-between pointer-events-none", children: isMicEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-end pointer-events-auto ml-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-3xl font-black font-mono text-white drop-shadow-[0_2px_10px_rgba(0,0,0,0.8)]", children: score.toLocaleString() }) }) }),
4603    hitFlash !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute top-20 left-1/2 -translate-x-1/2 z-50 text-4xl font-black italic uppercase tracking-wider animate-in fade-in zoom-in-50 duration-150 drop-shadow-[0_0_20px_rgba(0,0,0,0.8)] ${hitFlash === "perfect" ? "text-amber-400" : hitFlash === "good" ? "text-emerald-400" : hitFlash === "great" ? "text-blue-400" : "text-rose-500"}`, children: hitFlash }),
4604    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 right-0 h-2 bg-zinc-900 z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full bg-gradient-to-r from-emerald-400 to-indigo-500 transition-all", style: { width: `${maxScore > 0 ? score / (maxScore * 2) * 100 : 0}%` } }) }),
4605    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full h-[60%] border-y border-zinc-800 bg-[#0a0a0a] overflow-hidden", children: [
4606      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex flex-col justify-between opacity-10 pointer-events-none", children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-px bg-white" }, i)) }),
4607      /* @__PURE__ */ jsxRuntimeExports.jsx(
4608        "div",
4609        {
4610          className: "absolute top-0 bottom-0 w-1 bg-gradient-to-b from-transparent via-amber-400 to-transparent z-40 drop-shadow-[0_0_8px_rgba(251,191,36,0.8)]",
4611          style: { left: `${SING_LINE_PCT}%` }
4612        }
4613      ),
4614      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0", children: highwayData.map((block) => {
4615        const timeDiff = block.start - currentTime;
4616        const leftPct = SING_LINE_PCT + timeDiff * PIXELS_PER_SEC;
4617        const widthPct = block.duration * PIXELS_PER_SEC;
4618        if (leftPct + widthPct < -10 || leftPct > 110) return null;
4619        const isSung = timeDiff + block.duration < 0;
4620        const isCurrent = timeDiff <= 0 && timeDiff + block.duration >= 0;
4621        const bottomPct = 10 + block.normalizedPitch * 70;
4622        return /* @__PURE__ */ jsxRuntimeExports.jsxs(
4623          "div",
4624          {
4625            className: `absolute h-8 sm:h-10 rounded-full flex items-center justify-center font-bold px-3 transition-colors duration-200 overflow-hidden shadow-lg ${isSung ? "bg-zinc-700/50 text-zinc-500 border border-zinc-600" : isCurrent ? "bg-amber-500 text-black border-2 border-amber-300 drop-shadow-[0_0_15px_rgba(251,191,36,0.6)] scale-110 z-30" : "bg-indigo-600 text-white border border-indigo-400 drop-shadow-md z-20"}`,
4626            style: {
4627              left: `${leftPct}%`,
4628              width: `${Math.max(widthPct, 2)}%`,
4629              // Give a min width so very short notes don't disappear
4630              minWidth: "fit-content",
4631              bottom: `${bottomPct}%`,
4632              whiteSpace: "nowrap"
4633            },
4634            children: [
4635              isCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx(
4636                "div",
4637                {
4638                  className: "absolute left-0 top-0 bottom-0 bg-white/30",
4639                  style: { width: `${Math.max(0, Math.min(100, -timeDiff / block.duration * 100))}%` }
4640                }
4641              ),
4642              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "relative z-10 truncate px-2", children: block.txt })
4643            ]
4644          },
4645          block.id
4646        );
4647      }) })
4648    ] }),
4649    isMicEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-6 top-1/2 -translate-y-1/2 w-4 h-64 bg-zinc-900 rounded-full border border-zinc-800 overflow-hidden flex flex-col justify-end z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4650      "div",
4651      {
4652        className: "w-full bg-gradient-to-t from-emerald-400 via-amber-400 to-rose-500 transition-all duration-75 ease-out",
4653        style: { height: `${Math.min(100, currentVolume * 200)}%` }
4654      }
4655    ) }),
4656    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-6 left-6 z-50 flex items-center gap-2 bg-black/80 backdrop-blur p-2 rounded-2xl border border-zinc-800 shadow-2xl", children: [
4657      /* @__PURE__ */ jsxRuntimeExports.jsx(
4658        "button",
4659        {
4660          onClick: () => setIsMicEnabled(!isMicEnabled),
4661          className: `w-12 h-12 flex items-center justify-center rounded-xl transition-all ${isMicEnabled ? "bg-emerald-500/20 text-emerald-400" : "bg-zinc-800 text-zinc-400 hover:text-white"}`,
4662          children: isMicEnabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(Mic, { size: 24 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(MicOff, { size: 24 })
4663        }
4664      ),
4665      isMicEnabled && devices.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col ml-2", children: [
4666        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase font-bold text-zinc-500 tracking-wider mb-1", children: "Input Device" }),
4667        /* @__PURE__ */ jsxRuntimeExports.jsx(
4668          "select",
4669          {
4670            value: selectedDevice,
4671            onChange: (e) => setSelectedDevice(e.target.value),
4672            className: "bg-zinc-900 border border-zinc-700 text-xs text-white rounded-lg px-2 py-1 max-w-[200px] outline-none",
4673            children: devices.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: d.deviceId, children: d.label || `Mic ${d.deviceId.slice(0, 4)}` }, d.deviceId))
4674          }
4675        )
4676      ] })
4677    ] })
4678  ] });
4679};
4680const cleanGluedWords$2 = (txt) => {
4681  return splitGluedWords(txt);
4682};
4683const shouldAddSpace$2 = (prev, curr) => {
4684  if (!prev || !curr) return false;
4685  if (prev.endsWith(" ") || prev.endsWith(" ") || curr.startsWith(" ") || curr.startsWith(" ")) return false;
4686  if (shouldJoinSyllables(prev, curr)) return false;
4687  return true;
4688};
4689const isCapitalizedStart$2 = (txt) => {
4690  const clean = txt.trim();
4691  if (!/^[A-Z]/.test(clean)) return false;
4692  if (/^[I]($|'|m$)/.test(clean)) return true;
4693  if (clean.length === 1) return false;
4694  return true;
4695};
4696const groupLyricsIntoLines$2 = (lyrics) => {
4697  const preprocessed = [];
4698  for (let i = 0; i < lyrics.length; i++) {
4699    const syl = lyrics[i];
4700    const cleaned = cleanGluedWords$2(syl.txt);
4701    const parts = cleaned.split(" ");
4702    if (parts.length > 1) {
4703      parts.forEach((part, idx) => {
4704        preprocessed.push({
4705          t: syl.t + idx * 0.01,
4706          txt: part
4707        });
4708      });
4709    } else {
4710      preprocessed.push({
4711        t: syl.t,
4712        txt: cleaned
4713      });
4714    }
4715  }
4716  const lines = [];
4717  let currentLine = [];
4718  for (let i = 0; i < preprocessed.length; i++) {
4719    const syl = preprocessed[i];
4720    let txt = syl.txt;
4721    const isNewLine = txt.startsWith("/") || txt.startsWith("\\") || txt.includes("\n") || txt.includes("\r");
4722    if (isNewLine && currentLine.length > 0) {
4723      lines.push({
4724        startTime: currentLine[0].t,
4725        endTime: currentLine[currentLine.length - 1].t,
4726        syllables: currentLine
4727      });
4728      currentLine = [];
4729    }
4730    txt = txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ");
4731    if (currentLine.length > 0) {
4732      const prevSyl = currentLine[currentLine.length - 1];
4733      if (shouldAddSpace$2(prevSyl.txt, txt)) {
4734        txt = " " + txt;
4735      }
4736    }
4737    currentLine.push({ t: syl.t, txt });
4738    if (i < preprocessed.length - 1) {
4739      const nextSyl = preprocessed[i + 1];
4740      const cleanNextTxt = nextSyl.txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ");
4741      const hasGap = nextSyl.t - syl.t > 2.5;
4742      const isCapitalized = isCapitalizedStart$2(cleanNextTxt);
4743      const isJustI = /^[I]$/.test(cleanNextTxt.trim());
4744      const shouldBreakOnCapital = isCapitalized && !isJustI && currentLine.length >= 1;
4745      const isWordBoundary = cleanNextTxt.startsWith(" ") || shouldAddSpace$2(txt, cleanNextTxt);
4746      const isTooLong = currentLine.length >= 28 && isWordBoundary;
4747      if ((hasGap || shouldBreakOnCapital || isTooLong) && currentLine.length > 0) {
4748        lines.push({
4749          startTime: currentLine[0].t,
4750          endTime: currentLine[currentLine.length - 1].t,
4751          syllables: currentLine
4752        });
4753        currentLine = [];
4754      }
4755    }
4756  }
4757  if (currentLine.length > 0) {
4758    lines.push({
4759      startTime: currentLine[0].t,
4760      endTime: currentLine[currentLine.length - 1].t,
4761      syllables: currentLine
4762    });
4763  }
4764  return lines;
4765};
4766const SingThroughLyrics = ({ track, currentTime, melodyData, pianoRollData, isPlaying }) => {
4767  var _a;
4768  const { t } = useTranslation();
4769  const rawLyrics = ((_a = track.midiMetadata) == null ? void 0 : _a.lyrics) || [];
4770  const lines = reactExports.useMemo(() => groupLyricsIntoLines$2(rawLyrics), [rawLyrics]);
4771  const syllableNotes = reactExports.useMemo(() => {
4772    let bestTrackNotes = [];
4773    let maxAlignments = 0;
4774    if (pianoRollData && pianoRollData.length > 0) {
4775      pianoRollData.forEach((pTrack) => {
4776        if (!pTrack.notes) return;
4777        let alignments = 0;
4778        rawLyrics.forEach((lyric) => {
4779          const match = pTrack.notes.find((n) => Math.abs(n.t - lyric.t) < 0.2);
4780          if (match) alignments++;
4781        });
4782        if (alignments > maxAlignments) {
4783          maxAlignments = alignments;
4784          bestTrackNotes = pTrack.notes.map((n) => ({ start: n.t, duration: n.d, midi: n.midi, velocity: n.v }));
4785        }
4786      });
4787    }
4788    let pitchSourceNotes = bestTrackNotes;
4789    if (maxAlignments < rawLyrics.length * 0.2 && melodyData && melodyData.length > 0) {
4790      pitchSourceNotes = melodyData;
4791    }
4792    const map = /* @__PURE__ */ new Map();
4793    rawLyrics.forEach((syl) => {
4794      const match = pitchSourceNotes.find((n) => n.start >= syl.t - 0.2 && n.start <= syl.t + 0.2);
4795      if (match) {
4796        map.set(syl.t, match);
4797      }
4798    });
4799    return map;
4800  }, [rawLyrics, melodyData, pianoRollData]);
4801  if (lines.length === 0) {
4802    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full bg-[#111] text-zinc-500 font-serif italic text-lg", children: t("karaoke.noLyrics") });
4803  }
4804  let activeLineIdx = -1;
4805  for (let i = 0; i < lines.length; i++) {
4806    if (currentTime >= lines[i].startTime && currentTime <= lines[i].endTime) {
4807      activeLineIdx = i;
4808      break;
4809    }
4810  }
4811  if (activeLineIdx === -1) {
4812    for (let i = 0; i < lines.length; i++) {
4813      if (currentTime >= lines[i].startTime - 1.5 && currentTime <= lines[i].endTime + 1) {
4814        activeLineIdx = i;
4815        break;
4816      }
4817    }
4818  }
4819  if (activeLineIdx === -1) {
4820    for (let i = 0; i < lines.length; i++) {
4821      if (lines[i].startTime > currentTime) {
4822        activeLineIdx = i;
4823        break;
4824      }
4825    }
4826  }
4827  if (activeLineIdx === -1) activeLineIdx = lines.length - 1;
4828  const start = Math.max(0, activeLineIdx - 1);
4829  const end = Math.min(lines.length, activeLineIdx + 3);
4830  const visibleLines = lines.slice(start, end);
4831  let currentVelocity = 0;
4832  for (let i = 0; i < lines.length; i++) {
4833    if (currentTime >= lines[i].startTime - 1 && currentTime <= lines[i].endTime + 1) {
4834      for (let idx = 0; idx < lines[i].syllables.length; idx++) {
4835        const syl = lines[i].syllables[idx];
4836        const note = syllableNotes.get(syl.t);
4837        let duration = note ? note.duration : 0.5;
4838        if (currentTime >= syl.t && currentTime <= syl.t + duration) {
4839          currentVelocity = (note == null ? void 0 : note.velocity) ?? 0.5;
4840          break;
4841        }
4842      }
4843    }
4844  }
4845  const normalizedVelocity = currentVelocity > 1 ? currentVelocity / 127 : currentVelocity;
4846  const pulseOpacity = Math.min(1, normalizedVelocity * 0.8);
4847  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full bg-[#111] overflow-hidden relative w-full", children: [
4848    /* @__PURE__ */ jsxRuntimeExports.jsx(
4849      "div",
4850      {
4851        className: "absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-indigo-500/30 via-transparent to-transparent transition-opacity duration-100 ease-out",
4852        style: { opacity: isPlaying ? pulseOpacity : 0, transform: `scale(${1 + pulseOpacity * 0.1})` }
4853      }
4854    ),
4855    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4 sm:space-y-12 text-center w-full max-w-5xl px-3 sm:px-8 z-10", children: visibleLines.map((line, i) => {
4856      const globalIndex = start + i;
4857      const isActiveLine = globalIndex === activeLineIdx;
4858      return /* @__PURE__ */ jsxRuntimeExports.jsx(
4859        "div",
4860        {
4861          className: `transition-all duration-500 leading-tight flex flex-wrap justify-center ${isActiveLine ? "scale-105 opacity-100" : "blur-[2px] scale-95 opacity-30"}`,
4862          children: line.syllables.map((syl, idx) => {
4863            const note = syllableNotes.get(syl.t);
4864            const nextLine = lines[globalIndex + 1];
4865            const nextLineStart = nextLine ? nextLine.startTime : Infinity;
4866            const defaultNextTime = idx < line.syllables.length - 1 ? line.syllables[idx + 1].t : Math.min(line.endTime + 0.5, nextLineStart);
4867            const duration = note ? note.duration : defaultNextTime - syl.t;
4868            const noteEndTime = syl.t + duration;
4869            const isCurrentWord = currentTime >= syl.t && currentTime < noteEndTime;
4870            const isSung = currentTime >= noteEndTime;
4871            let fillPercentage = 0;
4872            if (isSung) {
4873              fillPercentage = 100;
4874            } else if (isCurrentWord) {
4875              fillPercentage = Math.max(0, Math.min(100, (currentTime - syl.t) / duration * 100));
4876            }
4877            const isCJK = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\uFAFF\uFF66-\uFF9F]/.test(syl.txt);
4878            const hasSpacePrefix = syl.txt.startsWith(" ");
4879            const isContinuation = !hasSpacePrefix && !isCJK && idx > 0;
4880            const marginClass = isCJK ? "mx-[0.5px]" : isContinuation ? "ml-[0px] mr-[4px]" : "ml-[12px] mr-[4px]";
4881            return /* @__PURE__ */ jsxRuntimeExports.jsxs(
4882              "span",
4883              {
4884                className: `transition-transform duration-75 inline-block whitespace-nowrap ${marginClass} ${isActiveLine && isCurrentWord ? "scale-105 sm:scale-110 -translate-y-1 sm:-translate-y-2" : ""}`,
4885                style: {
4886                  transformOrigin: "bottom center",
4887                  position: "relative",
4888                  paddingBottom: "8px"
4889                },
4890                children: [
4891                  hasSpacePrefix && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-2 sm:w-3 inline-block" }),
4892                  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "relative inline-block text-2xl sm:text-5xl md:text-7xl font-black tracking-tight", children: [
4893                    /* @__PURE__ */ jsxRuntimeExports.jsx(
4894                      "span",
4895                      {
4896                        className: "opacity-50",
4897                        style: {
4898                          WebkitTextStroke: "2px rgba(255, 255, 255, 0.4)",
4899                          color: "transparent"
4900                        },
4901                        children: syl.txt.trim()
4902                      }
4903                    ),
4904                    (fillPercentage > 0 || isSung) && /* @__PURE__ */ jsxRuntimeExports.jsx(
4905                      "span",
4906                      {
4907                        className: `absolute top-0 left-0 bottom-0 overflow-hidden whitespace-nowrap text-white ${!isSung ? "drop-shadow-[0_0_15px_rgba(129,140,248,0.8)]" : "drop-shadow-none text-indigo-200"}`,
4908                        style: {
4909                          width: `${fillPercentage}%`,
4910                          transition: "width 0.05s linear"
4911                        },
4912                        children: syl.txt.trim()
4913                      }
4914                    )
4915                  ] })
4916                ]
4917              },
4918              idx
4919            );
4920          })
4921        },
4922        globalIndex
4923      );
4924    }) })
4925  ] });
4926};
4927const KineticLine = ({
4928  line,
4929  lineIdx,
4930  lines,
4931  currentTime,
4932  d,
4933  textScale
4934}) => {
4935  const rowRef = reactExports.useRef(null);
4936  const [scaleX, setScaleX] = reactExports.useState(1);
4937  reactExports.useEffect(() => {
4938    if (!rowRef.current) return;
4939    const observer = new ResizeObserver(() => {
4940      if (!rowRef.current || !rowRef.current.parentElement) return;
4941      const containerWidth = rowRef.current.parentElement.clientWidth * 0.95;
4942      const rowWidth = rowRef.current.scrollWidth;
4943      if (rowWidth > containerWidth) {
4944        setScaleX(Math.max(0.55, containerWidth / rowWidth));
4945      } else {
4946        setScaleX(1);
4947      }
4948    });
4949    observer.observe(rowRef.current);
4950    return () => observer.disconnect();
4951  }, [textScale]);
4952  let translateY = 0, scale = 1, opacity = 0, zIndex = 10;
4953  if (d === -1) {
4954    translateY = -85;
4955    scale = 0.75;
4956    opacity = 0.35;
4957    zIndex = 20;
4958  } else if (d === 0) {
4959    translateY = 0;
4960    scale = 1.25;
4961    opacity = 1;
4962    zIndex = 30;
4963  } else if (d === 1) {
4964    translateY = 85;
4965    scale = 0.85;
4966    opacity = 0.45;
4967    zIndex = 20;
4968  } else if (d === -2) {
4969    translateY = -150;
4970    scale = 0.6;
4971    opacity = 0;
4972    zIndex = 10;
4973  } else if (d === 2) {
4974    translateY = 150;
4975    scale = 0.7;
4976    opacity = 0;
4977    zIndex = 10;
4978  }
4979  const baseFontSize = (d === 0 ? 1.5 : 1.1) * textScale;
4980  return /* @__PURE__ */ jsxRuntimeExports.jsx(
4981    "div",
4982    {
4983      className: "absolute left-[5%] right-[5%] w-[90%] flex items-center justify-center text-center px-2 sm:px-6 transition-all duration-700 ease-[cubic-bezier(0.34,1.56,0.64,1)] pointer-events-none",
4984      style: {
4985        transform: `translateY(${translateY}px) scale(${scale})`,
4986        opacity,
4987        zIndex,
4988        transformOrigin: "center center",
4989        willChange: "transform, opacity",
4990        fontSize: `${baseFontSize}rem`
4991      },
4992      children: /* @__PURE__ */ jsxRuntimeExports.jsx(
4993        "div",
4994        {
4995          ref: rowRef,
4996          className: "flex flex-wrap justify-center items-center",
4997          style: { transformOrigin: "center center", transform: `scaleX(${scaleX})` },
4998          children: line.syllables.map((syl, idx) => {
4999            const nextLine = lines[lineIdx + 1];
5000            const nextLineStart = nextLine ? nextLine.startTime : Infinity;
5001            const nextSylTime = idx < line.syllables.length - 1 ? line.syllables[idx + 1].t : Math.min(line.endTime + 0.5, nextLineStart);
5002            const isCurrentSyllable = currentTime >= syl.t && currentTime < nextSylTime;
5003            const isSung = currentTime >= nextSylTime;
5004            const duration = nextSylTime - syl.t;
5005            let fillPct = 0;
5006            if (isSung) fillPct = 100;
5007            else if (isCurrentSyllable) fillPct = Math.max(0, Math.min(100, (currentTime - syl.t) / duration * 100));
5008            const isCJK = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\uFAFF\uFF66-\uFF9F]/.test(syl.txt);
5009            const hasSpacePrefix = syl.txt.startsWith(" ");
5010            const isContinuation = !hasSpacePrefix && !isCJK && idx > 0;
5011            const marginClass = isCJK ? "mx-[0.5px]" : isContinuation ? "ml-[0px] mr-[2px]" : "ml-[6px] mr-[2px]";
5012            const glowStyle = d === 0 && isCurrentSyllable ? { textShadow: "0 0 15px rgba(52,211,153,0.8), 0 0 5px rgba(52,211,153,0.5), 0 0 30px rgba(255,255,255,0.25)" } : {};
5013            return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5014              "span",
5015              {
5016                className: `inline-block whitespace-nowrap ${marginClass} transition-all duration-300 ease-out`,
5017                style: {
5018                  transformOrigin: "center bottom",
5019                  transform: isCurrentSyllable && d === 0 ? "scale(1.12) translateY(-2px)" : "scale(1)",
5020                  willChange: "transform"
5021                },
5022                children: [
5023                  hasSpacePrefix && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 sm:w-2 inline-block" }),
5024                  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "relative inline-block", style: glowStyle, children: [
5025                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `${d === 0 ? isCurrentSyllable ? "text-emerald-400 font-extrabold" : isSung ? "text-indigo-400 font-bold" : "text-zinc-400 font-medium" : "text-zinc-500 font-normal"} transition-colors duration-200 ease-in-out`, children: syl.txt.trim() }),
5026                    d === 0 && fillPct > 0 && !isSung && /* @__PURE__ */ jsxRuntimeExports.jsx(
5027                      "span",
5028                      {
5029                        className: "absolute top-0 left-0 bottom-0 text-white overflow-hidden whitespace-nowrap font-extrabold",
5030                        style: { width: `${fillPct}%`, transition: "width 0.05s linear" },
5031                        children: syl.txt.trim()
5032                      }
5033                    )
5034                  ] })
5035                ]
5036              },
5037              idx
5038            );
5039          })
5040        }
5041      )
5042    }
5043  );
5044};
5045const LyricsKinetic = ({ lines, currentTime, activeLineIdx, textScale = 1 }) => {
5046  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full h-full flex flex-col items-center justify-center overflow-hidden bg-[#111]/90", children: [
5047    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 pointer-events-none overflow-hidden", children: [
5048      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute -top-40 left-1/4 w-[500px] h-[500px] rounded-full bg-indigo-500/10 blur-[120px] animate-pulse", style: { animationDuration: "8s" } }),
5049      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute -bottom-40 right-1/4 w-[500px] h-[500px] rounded-full bg-emerald-500/10 blur-[120px] animate-pulse", style: { animationDuration: "10s" } })
5050    ] }),
5051    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-full max-w-5xl h-[320px] flex items-center justify-center", children: lines.map((line, i) => {
5052      const d = i - activeLineIdx;
5053      if (d < -2 || d > 2) return null;
5054      return /* @__PURE__ */ jsxRuntimeExports.jsx(
5055        KineticLine,
5056        {
5057          line,
5058          lineIdx: i,
5059          lines,
5060          currentTime,
5061          d,
5062          textScale
5063        },
5064        i
5065      );
5066    }) })
5067  ] });
5068};
5069const BALL_ICONS = ["glow", "heart", "hamie", "star", "mic", "note", "fire", "diamond", "moon", "pixel"];
5070const BallSVG = ({ icon, squish, size = 30 }) => {
5071  const scaleY = 1 - squish * 0.4;
5072  const scaleX = 1 + squish * 0.3;
5073  const style = {
5074    transform: `scaleY(${scaleY}) scaleX(${scaleX})`,
5075    display: "inline-block",
5076    transition: squish > 0.01 ? "none" : "transform 0.15s cubic-bezier(0.34,1.56,0.64,1)"
5077  };
5078  if (icon === "glow") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5079    /* @__PURE__ */ jsxRuntimeExports.jsxs("defs", { children: [
5080      /* @__PURE__ */ jsxRuntimeExports.jsxs("radialGradient", { id: "bg1", cx: "40%", cy: "35%", r: "60%", children: [
5081        /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#fff", stopOpacity: "0.9" }),
5082        /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "50%", stopColor: "#a5f3fc" }),
5083        /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#0ea5e9" })
5084      ] }),
5085      /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "gf1", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
5086        /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "2.5", result: "b" }),
5087        /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5088          /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5089          /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5090        ] })
5091      ] })
5092    ] }),
5093    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "15", cy: "15", r: "12", fill: "url(#bg1)", filter: "url(#gf1)" }),
5094    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "10", cy: "10", rx: "3.5", ry: "2.5", fill: "white", opacity: "0.5" })
5095  ] }) });
5096  if (icon === "heart") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5097    /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "hf", children: [
5098      /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "2", result: "b" }),
5099      /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5100        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5101        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5102      ] })
5103    ] }) }),
5104    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15 26s-10-7-10-13a5.5 5.5 0 0 1 10-3.1A5.5 5.5 0 0 1 25 13c0 6-10 13-10 13z", fill: "#f43f5e", filter: "url(#hf)" }),
5105    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M10.5 12a2.5 2.5 0 0 1 2.5-2.5", stroke: "white", strokeWidth: "1.2", strokeLinecap: "round", fill: "none", opacity: "0.55" })
5106  ] }) });
5107  if (icon === "hamie") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5108    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "6", cy: "18", rx: "5.5", ry: "4.5", fill: "#f9a8d4", opacity: "0.8" }),
5109    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "24", cy: "18", rx: "5.5", ry: "4.5", fill: "#f9a8d4", opacity: "0.8" }),
5110    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "15", cy: "16", rx: "10", ry: "9", fill: "#fde68a" }),
5111    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "7.5", cy: "8", rx: "4.5", ry: "4", fill: "#fde68a" }),
5112    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "7.5", cy: "8", rx: "2.8", ry: "2.2", fill: "#fbcfe8" }),
5113    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "22.5", cy: "8", rx: "4.5", ry: "4", fill: "#fde68a" }),
5114    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "22.5", cy: "8", rx: "2.8", ry: "2.2", fill: "#fbcfe8" }),
5115    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "11", cy: "14", r: "2.2", fill: "#1c1917" }),
5116    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "19", cy: "14", r: "2.2", fill: "#1c1917" }),
5117    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "11.6", cy: "13.2", r: "0.7", fill: "white" }),
5118    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "19.6", cy: "13.2", r: "0.7", fill: "white" }),
5119    /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "15", cy: "18.5", rx: "1.8", ry: "1.2", fill: "#f472b6" }),
5120    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12.5 20.5 Q15 22.5 17.5 20.5", stroke: "#92400e", strokeWidth: "0.9", fill: "none", strokeLinecap: "round" })
5121  ] }) });
5122  if (icon === "star") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5123    /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "sf", children: [
5124      /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "2.5", result: "b" }),
5125      /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5126        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5127        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5128      ] })
5129    ] }) }),
5130    /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "15,3 18.5,11.5 28,11.5 20.5,17.5 23.5,27 15,21.5 6.5,27 9.5,17.5 2,11.5 11.5,11.5", fill: "#fbbf24", filter: "url(#sf)" }),
5131    /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "15,7 17,13 23,13 18,16.5 20,22 15,18.5 10,22 12,16.5 7,13 13,13", fill: "#fef08a", opacity: "0.55" })
5132  ] }) });
5133  if (icon === "mic") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5134    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "11", y: "3", width: "8", height: "14", rx: "4", fill: "#a78bfa" }),
5135    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 15a9 9 0 0 0 18 0", stroke: "#7c3aed", strokeWidth: "2.2", fill: "none", strokeLinecap: "round" }),
5136    /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "15", y1: "24", x2: "15", y2: "28", stroke: "#7c3aed", strokeWidth: "2", strokeLinecap: "round" }),
5137    /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "11", y1: "28", x2: "19", y2: "28", stroke: "#7c3aed", strokeWidth: "2", strokeLinecap: "round" }),
5138    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "13", y: "6", width: "2", height: "4", rx: "1", fill: "white", opacity: "0.5" })
5139  ] }) });
5140  if (icon === "note") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5141    /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "nf", children: [
5142      /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "1.5", result: "b" }),
5143      /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5144        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5145        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5146      ] })
5147    ] }) }),
5148    /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { fill: "#34d399", filter: "url(#nf)", children: [
5149      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "22", rx: "4.5", ry: "3" }),
5150      /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "13", y: "5", width: "2.5", height: "17", rx: "1.25" }),
5151      /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "13", y: "5", width: "10", height: "2.5", rx: "1.25" }),
5152      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "21.5", cy: "15", rx: "4.5", ry: "3" }),
5153      /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "25.5", y: "5", width: "2.5", height: "10", rx: "1.25" })
5154    ] })
5155  ] }) });
5156  if (icon === "fire") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5157    /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "ff", children: [
5158      /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "2", result: "b" }),
5159      /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5160        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5161        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5162      ] })
5163    ] }) }),
5164    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15 28C8.5 28 5 22.5 5 18c0-3.5 2.5-6 2.5-6s0 3.5 3.5 3.5C9.5 11 13 7 15 4c0 5 4.5 6 4.5 10.5C20.5 13 21 11 21 11c2.5 2.5 4 6 4 8.5 0 4.5-4 8.5-10 8.5z", fill: "#f97316", filter: "url(#ff)" }),
5165    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15 26c-3.5 0-6-3-6-5.5 0-2 1.5-3.5 1.5-3.5s0 2 2 2c-.5-2.5 1.5-4.5 2.5-6 0 2.5 2.5 3 2.5 5.5.5-.5.5-2 .5-2 1.5 1.5 2.5 3 2.5 4.5 0 2.5-2.5 5-5.5 5z", fill: "#fef08a" })
5166  ] }) });
5167  if (icon === "diamond") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5168    /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "df", children: [
5169      /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "2.5", result: "b" }),
5170      /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5171        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5172        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5173      ] })
5174    ] }) }),
5175    /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "15,2 26,11 15,28 4,11", fill: "#67e8f9", filter: "url(#df)" }),
5176    /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "15,2 26,11 15,16", fill: "#e0f2fe", opacity: "0.65" }),
5177    /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "4,11 15,16 15,28", fill: "#0891b2", opacity: "0.55" }),
5178    /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "15,2 21,11 15,16 9,11", fill: "white", opacity: "0.28" })
5179  ] }) });
5180  if (icon === "moon") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", children: [
5181    /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("filter", { id: "mf", children: [
5182      /* @__PURE__ */ jsxRuntimeExports.jsx("feGaussianBlur", { stdDeviation: "2", result: "b" }),
5183      /* @__PURE__ */ jsxRuntimeExports.jsxs("feMerge", { children: [
5184        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "b" }),
5185        /* @__PURE__ */ jsxRuntimeExports.jsx("feMergeNode", { in: "SourceGraphic" })
5186      ] })
5187    ] }) }),
5188    /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M22 15a9 9 0 1 1-9-9 7 7 0 0 0 9 9z", fill: "#fde68a", filter: "url(#mf)" }),
5189    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "20", cy: "9", r: "1.2", fill: "#fef9c3", opacity: "0.8" }),
5190    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "24", cy: "13", r: "0.8", fill: "#fef9c3", opacity: "0.6" }),
5191    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "22", cy: "18", r: "0.6", fill: "#fef9c3", opacity: "0.5" })
5192  ] }) });
5193  return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 30 30", shapeRendering: "crispEdges", children: [
5194    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "11", y: "4", width: "8", height: "2", fill: "#4ade80" }),
5195    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "9", y: "6", width: "12", height: "2", fill: "#4ade80" }),
5196    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "8", width: "16", height: "8", fill: "#4ade80" }),
5197    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "8", width: "4", height: "4", fill: "#111" }),
5198    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "19", y: "8", width: "4", height: "4", fill: "#111" }),
5199    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "9", y: "9", width: "2", height: "2", fill: "white" }),
5200    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "19", y: "9", width: "2", height: "2", fill: "white" }),
5201    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "16", width: "4", height: "4", fill: "#4ade80" }),
5202    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "19", y: "16", width: "4", height: "4", fill: "#4ade80" }),
5203    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "20", width: "4", height: "4", fill: "#4ade80" }),
5204    /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "19", y: "20", width: "4", height: "4", fill: "#4ade80" })
5205  ] }) });
5206};
5207const LineRow = ({
5208  line,
5209  lineIdx,
5210  isActiveLine,
5211  scaledFontSize,
5212  setWordRef,
5213  activeLineIdx,
5214  highlights
5215}) => {
5216  const rowRef = reactExports.useRef(null);
5217  const [scaleX, setScaleX] = reactExports.useState(1);
5218  reactExports.useEffect(() => {
5219    if (!rowRef.current) return;
5220    const observer = new ResizeObserver(() => {
5221      if (!rowRef.current || !rowRef.current.parentElement) return;
5222      const containerWidth = rowRef.current.parentElement.clientWidth * 0.9;
5223      const rowWidth = rowRef.current.scrollWidth;
5224      if (rowWidth > containerWidth) {
5225        setScaleX(Math.max(0.55, containerWidth / rowWidth));
5226      } else {
5227        setScaleX(1);
5228      }
5229    });
5230    observer.observe(rowRef.current);
5231    return () => observer.disconnect();
5232  }, [scaledFontSize]);
5233  return /* @__PURE__ */ jsxRuntimeExports.jsx(
5234    "div",
5235    {
5236      ref: rowRef,
5237      className: "flex flex-wrap justify-center items-baseline leading-relaxed transition-opacity duration-300 w-full",
5238      style: {
5239        fontSize: `${scaledFontSize}rem`,
5240        transform: `scaleX(${scaleX})`,
5241        transformOrigin: "center center",
5242        opacity: isActiveLine ? 1 : 0.35,
5243        fontWeight: isActiveLine ? 800 : 500,
5244        paddingBottom: typeof window !== "undefined" && window.innerWidth < 640 ? "12px" : "40px"
5245      },
5246      children: line.syllables.map((syl, idx) => {
5247        let fillPct = 0;
5248        if (lineIdx < activeLineIdx) {
5249          fillPct = 100;
5250        } else if (lineIdx > activeLineIdx) {
5251          fillPct = 0;
5252        } else {
5253          fillPct = highlights[`${lineIdx}-${idx}`] || 0;
5254        }
5255        const isCJK = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\uFAFF\uFF66-\uFF9F]/.test(syl.txt);
5256        const hasSpacePrefix = syl.txt.startsWith(" ");
5257        const isContinuation = !hasSpacePrefix && !isCJK && idx > 0;
5258        const marginClass = isCJK ? "mx-[0.5px]" : isContinuation ? "ml-[0px] mr-[2px]" : "ml-[6px] mr-[2px]";
5259        return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5260          "span",
5261          {
5262            ref: (el) => setWordRef(lineIdx, idx, el),
5263            className: `inline-block whitespace-nowrap ${marginClass}`,
5264            style: { position: "relative", paddingBottom: "4px" },
5265            children: [
5266              hasSpacePrefix && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 inline-block" }),
5267              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "relative inline-block", children: [
5268                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: lineIdx === activeLineIdx ? "text-zinc-500" : lineIdx < activeLineIdx ? "text-indigo-400 font-extrabold" : "text-zinc-600", children: syl.txt.trim() }),
5269                lineIdx === activeLineIdx && fillPct > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
5270                  "span",
5271                  {
5272                    className: "absolute top-0 left-0 bottom-0 text-indigo-400 font-extrabold overflow-hidden whitespace-nowrap transition-all duration-75",
5273                    style: { width: `${fillPct}%` },
5274                    children: syl.txt.trim()
5275                  }
5276                )
5277              ] })
5278            ]
5279          },
5280          idx
5281        );
5282      })
5283    }
5284  );
5285};
5286const LyricsBouncing = ({
5287  lines,
5288  currentTime,
5289  activeLineIdx,
5290  textScale = 1,
5291  ballIcon = "glow",
5292  onBallIconChange
5293}) => {
5294  const containerRef = reactExports.useRef(null);
5295  const wordRefs = reactExports.useRef(/* @__PURE__ */ new Map());
5296  const [ballPos, setBallPos] = reactExports.useState({
5297    x: 0,
5298    y: -60,
5299    opacity: 0,
5300    squish: 0
5301  });
5302  const [sylHighlights, setSylHighlights] = reactExports.useState({});
5303  const setWordRef = reactExports.useCallback((lineIdx, sylIdx, el) => {
5304    wordRefs.current.set(`${lineIdx}-${sylIdx}`, el);
5305  }, []);
5306  const flatSyllables = reactExports.useMemo(() => {
5307    const list = [];
5308    lines.forEach((line, lineIdx) => {
5309      line.syllables.forEach((syl, sylIdx) => {
5310        list.push({
5311          lineIdx,
5312          sylIdx,
5313          t: syl.t,
5314          txt: syl.txt
5315        });
5316      });
5317    });
5318    return list.sort((a, b) => a.t - b.t);
5319  }, [lines]);
5320  const flatSyllableIndices = reactExports.useMemo(() => {
5321    const map = /* @__PURE__ */ new Map();
5322    flatSyllables.forEach((fs, index) => {
5323      map.set(`${fs.lineIdx}-${fs.sylIdx}`, index);
5324    });
5325    return map;
5326  }, [flatSyllables]);
5327  const getBallInterpolation = reactExports.useCallback((time, cRect) => {
5328    if (flatSyllables.length === 0) {
5329      return { x: 0, y: 0, opacity: 0, squish: 0, activeLineIdx: -1, ballX: 0 };
5330    }
5331    if (time < flatSyllables[0].t) {
5332      const first = flatSyllables[0];
5333      const el = wordRefs.current.get(`${first.lineIdx}-${first.sylIdx}`);
5334      if (!el) return { x: 0, y: 0, opacity: 0, squish: 0, activeLineIdx: first.lineIdx, ballX: 0 };
5335      const rect = el.getBoundingClientRect();
5336      const x2 = rect.left + rect.width / 2 - cRect.left;
5337      const y2 = rect.top - cRect.top - 15;
5338      return { x: x2, y: y2, opacity: 1, squish: 0, activeLineIdx: first.lineIdx, ballX: x2 };
5339    }
5340    if (time >= flatSyllables[flatSyllables.length - 1].t) {
5341      const last = flatSyllables[flatSyllables.length - 1];
5342      const el = wordRefs.current.get(`${last.lineIdx}-${last.sylIdx}`);
5343      if (!el) return { x: 0, y: 0, opacity: 0, squish: 0, activeLineIdx: last.lineIdx, ballX: 0 };
5344      const rect = el.getBoundingClientRect();
5345      const x2 = rect.left + rect.width / 2 - cRect.left;
5346      const y2 = rect.top - cRect.top - 15;
5347      return { x: x2, y: y2, opacity: 1, squish: 0, activeLineIdx: last.lineIdx, ballX: x2 };
5348    }
5349    let idx = 0;
5350    for (let i = 0; i < flatSyllables.length - 1; i++) {
5351      if (time >= flatSyllables[i].t && time < flatSyllables[i + 1].t) {
5352        idx = i;
5353        break;
5354      }
5355    }
5356    const sylN = flatSyllables[idx];
5357    const sylNext = flatSyllables[idx + 1];
5358    const elN = wordRefs.current.get(`${sylN.lineIdx}-${sylN.sylIdx}`);
5359    const elNext = wordRefs.current.get(`${sylNext.lineIdx}-${sylNext.sylIdx}`);
5360    if (!elN) return { x: 0, y: 0, opacity: 0, squish: 0, activeLineIdx: sylN.lineIdx, ballX: 0 };
5361    const rectN = elN.getBoundingClientRect();
5362    const startX = rectN.left + rectN.width / 2 - cRect.left;
5363    const startY = rectN.top - cRect.top - 15;
5364    if (!elNext) {
5365      return { x: startX, y: startY, opacity: 1, squish: 0, activeLineIdx: sylN.lineIdx, ballX: startX };
5366    }
5367    const rectNext = elNext.getBoundingClientRect();
5368    const endX = rectNext.left + rectNext.width / 2 - cRect.left;
5369    const endY = rectNext.top - cRect.top - 15;
5370    const p = (time - sylN.t) / (sylNext.t - sylN.t);
5371    const x = startX + (endX - startX) * p;
5372    const easeY = 1 - Math.pow(Math.abs(2 * p - 1), 3);
5373    const dx = Math.abs(endX - startX);
5374    const dy = Math.abs(endY - startY);
5375    const isCrossLine = sylNext.lineIdx !== sylN.lineIdx;
5376    let H = Math.max(35, dx * 0.22 + dy * 0.1);
5377    if (isCrossLine) {
5378      H = Math.max(90, dx * 0.15);
5379    }
5380    H = Math.min(120, H);
5381    const y = startY + (endY - startY) * p - H * easeY;
5382    const timeSinceLanding = time - sylN.t;
5383    const squish = timeSinceLanding < 0.12 ? (1 - timeSinceLanding / 0.12) * 0.35 : 0;
5384    return { x, y, opacity: 1, squish, activeLineIdx: sylN.lineIdx, ballX: x };
5385  }, [flatSyllables]);
5386  reactExports.useEffect(() => {
5387    let raf;
5388    const update = () => {
5389      if (containerRef.current) {
5390        const cRect = containerRef.current.getBoundingClientRect();
5391        const interpolation = getBallInterpolation(currentTime, c
5391Rect);
5392        setBallPos({
5393          x: interpolation.x,
5394          y: interpolation.y,
5395          opacity: interpolation.opacity,
5396          squish: interpolation.squish
5397        });
5398        const highlights = {};
5399        if (flatSyllables.length > 0) {
5400          if (currentTime < flatSyllables[0].t) ;
5401          else if (currentTime >= flatSyllables[flatSyllables.length - 1].t) {
5402            lines.forEach((line, lIdx) => {
5403              line.syllables.forEach((_, sIdx) => {
5404                highlights[`${lIdx}-${sIdx}`] = 100;
5405              });
5406            });
5407          } else {
5408            let activeFlatIdx = 0;
5409            for (let i = 0; i < flatSyllables.length - 1; i++) {
5410              if (currentTime >= flatSyllables[i].t && currentTime < flatSyllables[i + 1].t) {
5411                activeFlatIdx = i;
5412                break;
5413              }
5414            }
5415            if (activeLineIdx >= 0 && activeLineIdx < lines.length) {
5416              const line = lines[activeLineIdx];
5417              line.syllables.forEach((_, sylIdx) => {
5418                const flatIdx = flatSyllableIndices.get(`${activeLineIdx}-${sylIdx}`) ?? -1;
5419                if (flatIdx < activeFlatIdx) {
5420                  highlights[`${activeLineIdx}-${sylIdx}`] = 100;
5421                } else if (flatIdx > activeFlatIdx) {
5422                  highlights[`${activeLineIdx}-${sylIdx}`] = 0;
5423                } else {
5424                  const el = wordRefs.current.get(`${activeLineIdx}-${sylIdx}`);
5425                  if (el) {
5426                    const rect = el.getBoundingClientRect();
5427                    const left = rect.left - cRect.left;
5428                    const right = rect.right - cRect.left;
5429                    let fillPct = 0;
5430                    if (interpolation.ballX >= right) {
5431                      fillPct = 100;
5432                    } else if (interpolation.ballX <= left) {
5433                      fillPct = 0;
5434                    } else {
5435                      fillPct = (interpolation.ballX - left) / (right - left) * 100;
5436                    }
5437                    highlights[`${activeLineIdx}-${sylIdx}`] = fillPct;
5438                  } else {
5439                    highlights[`${activeLineIdx}-${sylIdx}`] = 0;
5440                  }
5441                }
5442              });
5443            }
5444          }
5445        }
5446        setSylHighlights(highlights);
5447      }
5448      raf = requestAnimationFrame(update);
5449    };
5450    raf = requestAnimationFrame(update);
5451    return () => cancelAnimationFrame(raf);
5452  }, [currentTime, activeLineIdx, lines, getBallInterpolation, flatSyllables, flatSyllableIndices]);
5453  const displayLineIdxs = [];
5454  if (activeLineIdx >= 0) {
5455    if (activeLineIdx > 0) displayLineIdxs.push(activeLineIdx - 1);
5456    displayLineIdxs.push(activeLineIdx);
5457    if (activeLineIdx + 1 < lines.length) displayLineIdxs.push(activeLineIdx + 1);
5458  }
5459  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "relative w-full h-full flex flex-col items-center justify-center overflow-hidden bg-[#111] select-none", children: [
5460    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-center gap-2 sm:gap-6 w-full max-w-5xl px-3 sm:px-8", children: displayLineIdxs.map((lineIdx) => {
5461      const line = lines[lineIdx];
5462      const isActiveLine = lineIdx === activeLineIdx;
5463      const scaledFontSize = (isActiveLine ? 2.4 : 1.5) * textScale;
5464      return /* @__PURE__ */ jsxRuntimeExports.jsx(
5465        LineRow,
5466        {
5467          line,
5468          lineIdx,
5469          isActiveLine,
5470          scaledFontSize,
5471          setWordRef,
5472          activeLineIdx,
5473          highlights: sylHighlights
5474        },
5475        lineIdx
5476      );
5477    }) }),
5478    /* @__PURE__ */ jsxRuntimeExports.jsx(
5479      "div",
5480      {
5481        className: "absolute pointer-events-none z-50",
5482        style: {
5483          left: `${ballPos.x}px`,
5484          top: `${ballPos.y}px`,
5485          transform: "translate(-50%, -50%)",
5486          opacity: ballPos.opacity,
5487          transition: "opacity 0.2s ease"
5488        },
5489        children: /* @__PURE__ */ jsxRuntimeExports.jsx(BallSVG, { icon: ballIcon, squish: ballPos.squish })
5490      }
5491    ),
5492    onBallIconChange && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-2 sm:bottom-4 left-1/2 -translate-x-1/2 hidden sm:flex gap-1.5 bg-black/60 backdrop-blur border border-zinc-700/60 rounded-2xl px-3 py-2 shadow-xl z-40", children: BALL_ICONS.map((id) => /* @__PURE__ */ jsxRuntimeExports.jsx(
5493      "button",
5494      {
5495        onClick: () => onBallIconChange(id),
5496        className: `w-8 h-8 flex items-center justify-center rounded-lg transition-all ${ballIcon === id ? "bg-indigo-500/40 ring-2 ring-indigo-400 scale-110" : "hover:bg-zinc-700/60 hover:scale-105"}`,
5497        title: id,
5498        children: /* @__PURE__ */ jsxRuntimeExports.jsx(BallSVG, { icon: id, squish: 0, size: 22 })
5499      },
5500      id
5501    )) })
5502  ] });
5503};
5504const DISPLAY_MODES = [
5505  { value: "classic", label: "Classic", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SquareSquare, { size: 14 }) },
5506  { value: "highway", label: "Highway", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Route, { size: 14 }) },
5507  { value: "kinetic", label: "Kinetic", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Type, { size: 14 }) },
5508  { value: "sing-through", label: "Sing-Through", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 14 }) }
5509];
5510const DisplayModeDropdown = ({
5511  displayMode,
5512  setDisplayMode
5513}) => {
5514  const current = DISPLAY_MODES.find((m) => m.value === displayMode) ?? DISPLAY_MODES[0];
5515  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group/modemenu", children: [
5516    /* @__PURE__ */ jsxRuntimeExports.jsxs(
5517      "button",
5518      {
5519        className: "flex items-center gap-1.5 px-2.5 py-1.5 bg-zinc-800/80 hover:bg-zinc-700 backdrop-blur border border-zinc-700/50 rounded-lg text-zinc-300 transition-colors shadow-lg text-xs font-bold uppercase tracking-wider select-none",
5520        title: "Switch display mode",
5521        children: [
5522          current.icon,
5523          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: current.label }),
5524          /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 12, className: "opacity-60" })
5525        ]
5526      }
5527    ),
5528    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-full left-0 mt-1.5 min-w-[140px] bg-zinc-900/95 backdrop-blur-xl border border-zinc-700/60 rounded-xl shadow-2xl overflow-hidden z-[999] hidden group-hover/modemenu:flex flex-col py-1", children: DISPLAY_MODES.map((mode) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
5529      "button",
5530      {
5531        onClick: () => setDisplayMode(mode.value),
5532        className: `flex items-center gap-2 px-3 py-2 text-xs font-bold uppercase tracking-wider transition-colors ${mode.value === displayMode ? "bg-indigo-600/30 text-indigo-300" : "text-zinc-400 hover:bg-zinc-800 hover:text-white"}`,
5533        children: [
5534          mode.icon,
5535          mode.label,
5536          mode.value === displayMode && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto w-1.5 h-1.5 rounded-full bg-indigo-400" })
5537        ]
5538      },
5539      mode.value
5540    )) })
5541  ] });
5542};
5543const cleanGluedWords$1 = (txt) => {
5544  return splitGluedWords(txt);
5545};
5546const shouldAddSpace$1 = (prev, curr) => {
5547  if (!prev || !curr) return false;
5548  if (prev.endsWith(" ") || prev.endsWith(" ") || curr.startsWith(" ") || curr.startsWith(" ")) return false;
5549  if (shouldJoinSyllables(prev, curr)) return false;
5550  return true;
5551};
5552const isCapitalizedStart$1 = (txt) => {
5553  const clean = txt.trim();
5554  if (!/^[A-Z]/.test(clean)) return false;
5555  if (/^[I]($|'|m$)/.test(clean)) return true;
5556  if (clean.length === 1) return false;
5557  return true;
5558};
5559const groupLyricsIntoLines$1 = (lyrics) => {
5560  const preprocessed = [];
5561  for (let i = 0; i < lyrics.length; i++) {
5562    const syl = lyrics[i];
5563    const cleaned = cleanGluedWords$1(syl.txt);
5564    const parts = cleaned.split(" ");
5565    if (parts.length > 1) {
5566      parts.forEach((part, idx) => {
5567        preprocessed.push({
5568          t: syl.t + idx * 0.01,
5569          txt: part
5570        });
5571      });
5572    } else {
5573      preprocessed.push({
5574        t: syl.t,
5575        txt: cleaned
5576      });
5577    }
5578  }
5579  const lines = [];
5580  let currentLine = [];
5581  for (let i = 0; i < preprocessed.length; i++) {
5582    const syl = preprocessed[i];
5583    let txt = syl.txt;
5584    const isNewLine = txt.startsWith("/") || txt.startsWith("\\") || txt.includes("\n") || txt.includes("\r");
5585    if (isNewLine && currentLine.length > 0) {
5586      lines.push({
5587        startTime: currentLine[0].t,
5588        endTime: currentLine[currentLine.length - 1].t,
5589        syllables: currentLine
5590      });
5591      currentLine = [];
5592    }
5593    txt = txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ");
5594    if (currentLine.length > 0) {
5595      const prevSyl = currentLine[currentLine.length - 1];
5596      if (shouldAddSpace$1(prevSyl.txt, txt)) {
5597        txt = " " + txt;
5598      }
5599    }
5600    currentLine.push({ t: syl.t, txt });
5601    if (i < preprocessed.length - 1) {
5602      const nextSyl = preprocessed[i + 1];
5603      const cleanNextTxt = nextSyl.txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ");
5604      const hasGap = nextSyl.t - syl.t > 2.5;
5605      const isCapitalized = isCapitalizedStart$1(cleanNextTxt);
5606      const isJustI = /^[I]$/.test(cleanNextTxt.trim());
5607      const shouldBreakOnCapital = isCapitalized && !isJustI && currentLine.length >= 1;
5608      const isWordBoundary = cleanNextTxt.startsWith(" ") || shouldAddSpace$1(txt, cleanNextTxt);
5609      const isTooLong = currentLine.length >= 28 && isWordBoundary;
5610      if ((hasGap || shouldBreakOnCapital || isTooLong) && currentLine.length > 0) {
5611        lines.push({
5612          startTime: currentLine[0].t,
5613          endTime: currentLine[currentLine.length - 1].t,
5614          syllables: currentLine
5615        });
5616        currentLine = [];
5617      }
5618    }
5619  }
5620  if (currentLine.length > 0) {
5621    lines.push({
5622      startTime: currentLine[0].t,
5623      endTime: currentLine[currentLine.length - 1].t,
5624      syllables: currentLine
5625    });
5626  }
5627  return lines;
5628};
5629const Lyrics = ({ track, currentTime, melodyData, pianoRollData, isPlaying, playbackRate = 1, userId, trackId }) => {
5630  var _a;
5631  const { t } = useTranslation();
5632  const rawLyrics = ((_a = track.midiMetadata) == null ? void 0 : _a.lyrics) || [];
5633  const [score, setScore] = reactExports.useState(0);
5634  const [maxScore, setMaxScore] = reactExports.useState(0);
5635  const [hitFlash, setHitFlash] = reactExports.useState("none");
5636  const [isFinished, setIsFinished] = reactExports.useState(false);
5637  const [displayMode, setDisplayMode] = reactExports.useState("classic");
5638  const [textScale, setTextScaleState] = reactExports.useState(() => {
5639    const session = sessionStorage.getItem("karaokeTextScale");
5640    return session ? parseFloat(session) : 1;
5641  });
5642  const textScaleSaveTimer = reactExports.useRef(null);
5643  const [ballIcon, setBallIconState] = reactExports.useState(() => {
5644    const session = sessionStorage.getItem("karaokeBallIcon");
5645    return session || "glow";
5646  });
5647  const [isMicEnabled, setIsMicEnabled] = reactExports.useState(false);
5648  const [devices, setDevices] = reactExports.useState([]);
5649  const [selectedDevice, setSelectedDevice] = reactExports.useState("");
5650  const [currentVolume, setCurrentVolume] = reactExports.useState(0);
5651  const [hasUsedMic, setHasUsedMic] = reactExports.useState(false);
5652  const [showScoreScreen, setShowScoreScreen] = reactExports.useState(false);
5653  const [leaderboard, setLeaderboard] = reactExports.useState([]);
5654  const [isSubmittingScore, setIsSubmittingScore] = reactExports.useState(false);
5655  const [hasSubmittedScore, setHasSubmittedScore] = reactExports.useState(false);
5656  const lines = reactExports.useMemo(() => groupLyricsIntoLines$1(rawLyrics), [rawLyrics]);
5657  const timeRef = reactExports.useRef(currentTime);
5658  reactExports.useEffect(() => {
5659    timeRef.current = currentTime;
5660  }, [currentTime]);
5661  reactExports.useEffect(() => {
5662    if (!userId) return;
5663    api.request(`/users/${userId}`).then((user) => {
5664      var _a2;
5665      const saved = (_a2 = user == null ? void 0 : user.preferences) == null ? void 0 : _a2.karaokeTextScale;
5666      if (saved !== void 0) {
5667        const val = parseFloat(saved);
5668        if (!isNaN(val) && val >= 0.4 && val <= 1.5) {
5669          setTextScaleState(val);
5670          sessionStorage.setItem("karaokeTextScale", String(val));
5671        }
5672      }
5673    }).catch(() => {
5674    });
5675  }, [userId]);
5676  reactExports.useEffect(() => {
5677    const checkSize = () => {
5678      const isSmallScreen = window.innerWidth < 640 || window.innerHeight < 500;
5679      const session = sessionStorage.getItem("karaokeTextScale");
5680      if (!session && isSmallScreen) {
5681        setTextScaleState(0.65);
5682      }
5683    };
5684    checkSize();
5685  }, []);
5686  const setTextScale = reactExports.useCallback((newScale) => {
5687    const clamped = Math.max(0.4, Math.min(1.5, Math.round(newScale * 20) / 20));
5688    setTextScaleState(clamped);
5689    sessionStorage.setItem("karaokeTextScale", String(clamped));
5690    if (textScaleSaveTimer.current) clearTimeout(textScaleSaveTimer.current);
5691    textScaleSaveTimer.current = setTimeout(() => {
5692      if (!userId) return;
5693      api.request(`/users/${userId}`).then((user) => {
5694        return api.updateUser(userId, {
5695          preferences: { ...(user == null ? void 0 : user.preferences) || {}, karaokeTextScale: clamped }
5696        });
5697      }).catch(() => {
5698      });
5699    }, 500);
5700  }, [userId]);
5701  const handleBallIconChange = reactExports.useCallback((icon) => {
5702    setBallIconState(icon);
5703    sessionStorage.setItem("karaokeBallIcon", icon);
5704  }, []);
5705  const lastLogTimeRef = reactExports.useRef(0);
5706  reactExports.useEffect(() => {
5707    if (!isPlaying) {
5708      lastLogTimeRef.current = 0;
5709      return;
5710    }
5711    if (Math.abs(currentTime - lastLogTimeRef.current) >= 0.5) {
5712      lastLogTimeRef.current = currentTime;
5713      let activeLine = null;
5714      let activeSyl = null;
5715      let nextSylTimeVal = null;
5716      let fillPct = 0;
5717      let activeLineIdx2 = -1;
5718      for (let i = 0; i < lines.length; i++) {
5719        const line = lines[i];
5720        if (currentTime >= line.startTime && currentTime <= line.endTime) {
5721          activeLineIdx2 = i;
5722          break;
5723        }
5724      }
5725      if (activeLineIdx2 === -1) {
5726        for (let i = 0; i < lines.length; i++) {
5727          const line = lines[i];
5728          if (currentTime >= line.startTime - 1.5 && currentTime <= line.endTime + 1) {
5729            activeLineIdx2 = i;
5730            break;
5731          }
5732        }
5733      }
5734      if (activeLineIdx2 === -1) {
5735        for (let i = 0; i < lines.length; i++) {
5736          if (lines[i].startTime > currentTime) {
5737            activeLineIdx2 = i;
5738            break;
5739          }
5740        }
5741      }
5742      if (activeLineIdx2 === -1 && lines.length > 0) {
5743        activeLineIdx2 = lines.length - 1;
5744      }
5745      if (activeLineIdx2 !== -1) {
5746        activeLine = lines[activeLineIdx2];
5747      }
5748      if (activeLine) {
5749        for (let idx = 0; idx < activeLine.syllables.length; idx++) {
5750          const syl = activeLine.syllables[idx];
5751          const nextLine = lines[activeLineIdx2 + 1];
5752          const nextLineStart = nextLine ? nextLine.startTime : Infinity;
5753          const nextSylT = idx < activeLine.syllables.length - 1 ? activeLine.syllables[idx + 1].t : Math.min(activeLine.endTime + 0.5, nextLineStart);
5754          if (currentTime >= syl.t && currentTime < nextSylT) {
5755            activeSyl = syl;
5756            nextSylTimeVal = nextSylT;
5757            const duration = nextSylT - syl.t;
5758            fillPct = Math.max(0, Math.min(100, (currentTime - syl.t) / duration * 100));
5759            break;
5760          }
5761        }
5762      }
5763      console.log(`[KARAOKE_DEBUG]`, {
5764        currentTime,
5765        synthCurrentTime: synth.getCurrentTime(),
5766        audioContextTime: synth.getAudioContext().currentTime,
5767        isPlaying,
5768        activeSyllable: activeSyl ? {
5769          txt: activeSyl.txt,
5770          t: activeSyl.t,
5771          nextSylTime: nextSylTimeVal,
5772          fillPercentage: fillPct
5773        } : null
5774      });
5775    }
5776  }, [currentTime, isPlaying, lines]);
5777  reactExports.useEffect(() => {
5778    if (isMicEnabled) {
5779      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
5780        alert(t("karaoke.micNotSupported", "Microphone is not supported in this browser or requires a secure HTTPS connection."));
5781        setIsMicEnabled(false);
5782        return;
5783      }
5784      navigator.mediaDevices.getUserMedia({ audio: true }).then(() => {
5785        navigator.mediaDevices.enumerateDevices().then((devs) => {
5786          const audioInputs = devs.filter((d) => d.kind === "audioinput");
5787          setDevices(audioInputs);
5788          if (audioInputs.length > 0 && !selectedDevice) {
5789            setSelectedDevice(audioInputs[0].deviceId);
5790          }
5791        });
5792      }).catch((e) => {
5793        console.error("Mic permission denied", e);
5794        setIsMicEnabled(false);
5795      });
5796      setHasUsedMic(true);
5797    }
5798  }, [isMicEnabled]);
5799  reactExports.useEffect(() => {
5800    if (isPlaying && isMicEnabled && synth.pitchTracker) {
5801      synth.pitchTracker.start(selectedDevice);
5802      synth.pitchTracker.onVolumeUpdate = (vol) => {
5803        setCurrentVolume(vol);
5804      };
5805      synth.pitchTracker.onPitchDetected = (freq) => {
5806        if (!melodyData) return;
5807        const cTime = timeRef.current;
5808        const expected = melodyData.find((n) => cTime >= n.start && cTime <= n.start + n.duration);
5809        if (expected) {
5810          const targetFreq = 440 * Math.pow(2, (expected.midi - 69) / 12);
5811          const semitoneRatio = Math.pow(2, 1 / 12);
5812          const isPerfect = freq >= targetFreq / (semitoneRatio * 0.5) && freq <= targetFreq * (semitoneRatio * 0.5);
5813          const isGood = freq >= targetFreq / (semitoneRatio * 2) && freq <= targetFreq * (semitoneRatio * 2);
5814          setMaxScore((prev) => prev + 1);
5815          if (isPerfect) {
5816            setScore((prev) => prev + 2);
5817            setHitFlash("perfect");
5818          } else if (isGood) {
5819            setScore((prev) => prev + 1);
5820            setHitFlash("good");
5821          } else {
5822            setHitFlash("miss");
5823          }
5824          setTimeout(() => setHitFlash("none"), 300);
5825        }
5826      };
5827    } else if ((!isPlaying || !isMicEnabled) && synth.pitchTracker) {
5828      synth.pitchTracker.stop();
5829      setCurrentVolume(0);
5830    }
5831    return () => {
5832      if (synth.pitchTracker) synth.pitchTracker.stop();
5833    };
5834  }, [isPlaying, isMicEnabled, selectedDevice, melodyData]);
5835  reactExports.useEffect(() => {
5836    if (rawLyrics.length > 0 && currentTime > rawLyrics[rawLyrics.length - 1].t + 2) {
5837      if (!isFinished) {
5838        setIsFinished(true);
5839        if (hasUsedMic) {
5840          setShowScoreScreen(true);
5841          loadLeaderboard();
5842        }
5843      }
5844    } else {
5845      setIsFinished(false);
5846      setShowScoreScreen(false);
5847    }
5848  }, [currentTime, rawLyrics, hasUsedMic, isFinished]);
5849  const loadLeaderboard = async () => {
5850    if (trackId) {
5851      try {
5852        const scores = await api.getHighscores(trackId, "karaoke", userId);
5853        setLeaderboard(scores);
5854      } catch (e) {
5855        console.error("Failed to load karaoke leaderboard", e);
5856      }
5857    }
5858  };
5859  const submitScore = async () => {
5860    if (!trackId || score <= 0 || hasSubmittedScore) return;
5861    setIsSubmittingScore(true);
5862    try {
5863      await api.postHighscore(trackId, {
5864        mode: "karaoke",
5865        score: Math.floor(score),
5866        accuracy: maxScore > 0 ? score / (maxScore * 2) * 100 : 0
5867      });
5868      await loadLeaderboard();
5869      setHasSubmittedScore(true);
5870    } catch (e) {
5871      console.error("Failed to submit karaoke score", e);
5872    } finally {
5873      setIsSubmittingScore(false);
5874    }
5875  };
5876  let countdownData = null;
5877  if (isPlaying && lines.length > 0) {
5878    let nextLine = lines.find((l) => l.startTime > currentTime);
5879    if (nextLine) {
5880      const timeToNext = nextLine.startTime - currentTime;
5881      const prevLine = [...lines].reverse().find((l) => l.endTime <= currentTime);
5882      const gap = prevLine ? nextLine.startTime - prevLine.endTime : nextLine.startTime;
5883      const isLineActive = lines.some((l) => currentTime >= l.startTime && currentTime <= l.endTime);
5884      if (gap >= 4 && timeToNext <= 3 && timeToNext > 0 && !isLineActive) {
5885        countdownData = {
5886          timeToNext,
5887          percentage: (3 - timeToNext) / 3
5888        };
5889      }
5890    }
5891  }
5892  let timeToNextSyllable = 0;
5893  if (lines.length > 0) {
5894    let nextSyllable = null;
5895    for (const line of lines) {
5896      const found = line.syllables.find((s) => s.t > currentTime);
5897      if (found) {
5898        nextSyllable = found;
5899        break;
5900      }
5901    }
5902    if (nextSyllable) {
5903      timeToNextSyllable = nextSyllable.t - currentTime;
5904    }
5905  }
5906  if (lines.length === 0) {
5907    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full bg-[#111] text-zinc-500 font-serif italic text-lg relative", children: t("karaoke.noLyrics") });
5908  }
5909  if (displayMode === "highway") {
5910    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full h-full", children: [
5911      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-4 left-4 z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DisplayModeDropdown, { displayMode, setDisplayMode }) }),
5912      /* @__PURE__ */ jsxRuntimeExports.jsx(
5913        LyricsHighway,
5914        {
5915          track,
5916          currentTime,
5917          melodyData,
5918          pianoRollData,
5919          isPlaying,
5920          playbackRate,
5921          userId,
5922          trackId,
5923          isMicEnabled,
5924          setIsMicEnabled,
5925          score,
5926          maxScore,
5927          hitFlash,
5928          currentVolume,
5929          devices,
5930          selectedDevice,
5931          setSelectedDevice
5932        }
5933      )
5934    ] });
5935  }
5936  if (displayMode === "sing-through") {
5937    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full h-full", children: [
5938      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-4 left-4 z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(DisplayModeDropdown, { displayMode, setDisplayMode }) }),
5939      /* @__PURE__ */ jsxRuntimeExports.jsx(
5940        SingThroughLyrics,
5941        {
5942          track,
5943          currentTime,
5944          melodyData,
5945          pianoRollData,
5946          isPlaying,
5947          playbackRate,
5948          userId,
5949          trackId
5950        }
5951      ),
5952      isMicEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-6 top-1/2 -translate-y-1/2 w-4 h-64 bg-zinc-900 rounded-full border border-zinc-800 overflow-hidden flex flex-col justify-end pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5953        "div",
5954        {
5955          className: "w-full bg-gradient-to-t from-emerald-400 via-amber-400 to-rose-500 transition-all duration-75 ease-out",
5956          style: { height: `${Math.min(100, currentVolume * 200)}%` }
5957        }
5958      ) }),
5959      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-3 left-3 sm:bottom-6 sm:left-6 z-50 flex items-center gap-2 bg-black/80 backdrop-blur p-1.5 sm:p-2 rounded-2xl border border-zinc-800 shadow-2xl", children: [
5960        /* @__PURE__ */ jsxRuntimeExports.jsx(
5961          "button",
5962          {
5963            onClick: () => setIsMicEnabled(!isMicEnabled),
5964            className: `w-9 h-9 sm:w-12 sm:h-12 flex items-center justify-center rounded-lg sm:rounded-xl transition-all ${isMicEnabled ? "bg-emerald-500/20 text-emerald-400" : "bg-zinc-800 text-zinc-400 hover:text-white"}`,
5965            children: isMicEnabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(Mic, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(MicOff, { size: 18 })
5966          }
5967        ),
5968        isMicEnabled && devices.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col ml-2", children: [
5969          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase font-bold text-zinc-500 tracking-wider mb-1", children: "Input Device" }),
5970          /* @__PURE__ */ jsxRuntimeExports.jsx(
5971            "select",
5972            {
5973              value: selectedDevice,
5974              onChange: (e) => setSelectedDevice(e.target.value),
5975              className: "bg-zinc-900 border border-zinc-700 text-xs text-white rounded-lg px-2 py-1 max-w-[200px] outline-none",
5976              children: devices.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: d.deviceId, children: d.label || `Mic ${d.deviceId.slice(0, 4)}` }, d.deviceId))
5977            }
5978          )
5979        ] })
5980      ] }),
5981      isMicEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-4 right-4 z-50 flex flex-col items-end pointer-events-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-3xl font-black font-mono text-white drop-shadow-[0_2px_10px_rgba(0,0,0,0.8)]", children: score.toLocaleString() }) })
5982    ] });
5983  }
5984  let activeLineIdx = -1;
5985  for (let i = 0; i < lines.length; i++) {
5986    if (currentTime >= lines[i].startTime && currentTime <= lines[i].endTime) {
5987      activeLineIdx = i;
5988      break;
5989    }
5990  }
5991  if (activeLineIdx === -1) {
5992    for (let i = 0; i < lines.length; i++) {
5993      if (currentTime >= lines[i].startTime - 1.5 && currentTime <= lines[i].endTime + 1) {
5994        activeLineIdx = i;
5995        break;
5996      }
5997    }
5998  }
5999  if (activeLineIdx === -1) {
6000    for (let i = 0; i < lines.length; i++) {
6001      if (lines[i].startTime > currentTime) {
6002        activeLineIdx = i;
6003        break;
6004      }
6005    }
6006  }
6007  if (activeLineIdx === -1) activeLineIdx = lines.length - 1;
6008  const start = Math.max(0, activeLineIdx - 1);
6009  const end = Math.min(lines.length, activeLineIdx + 3);
6010  lines.slice(start, end);
6011  if (isFinished) {
6012    const accuracy = maxScore > 0 ? score / (maxScore * 2) * 100 : 0;
6013    let grade = "D";
6014    if (accuracy >= 95) grade = "S";
6015    else if (accuracy >= 85) grade = "A";
6016    else if (accuracy >= 70) grade = "B";
6017    else if (accuracy >= 50) grade = "C";
6018    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full bg-[#111] p-8 overflow-hidden relative overflow-y-auto", children: [
6019      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-800 p-12 rounded-3xl text-center shadow-2xl mb-8", children: [
6020        /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-3xl font-bold text-zinc-400 uppercase tracking-widest mb-4", children: t("karaoke.finalGrade", "Final Grade") }),
6021        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `text-9xl font-black mb-6 ${grade === "S" ? "text-amber-400 drop-shadow-[0_0_20px_rgba(251,191,36,0.5)]" : grade === "A" ? "text-emerald-400 drop-shadow-[0_0_20px_rgba(52,211,153,0.5)]" : grade === "B" ? "text-blue-400 drop-shadow-[0_0_20px_rgba(96,165,250,0.5)]" : grade === "C" ? "text-orange-400 drop-shadow-[0_0_20px_rgba(251,146,60,0.5)]" : "text-rose-500 drop-shadow-[0_0_20px_rgba(244,63,94,0.5)]"}`, children: grade }),
6022        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xl text-zinc-300 font-mono bg-black/50 py-2 px-6 rounded-full inline-block mb-4", children: [
6023          t("karaoke.accuracy", "Accuracy"),
6024          ": ",
6025          accuracy.toFixed(1),
6026          "%"
6027        ] }),
6028        hasUsedMic && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
6029          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-3xl text-white font-black mb-6", children: [
6030            score.toLocaleString(),
6031            " PTS"
6032          ] }),
6033          !hasSubmittedScore ? /* @__PURE__ */ jsxRuntimeExports.jsx(
6034            "button",
6035            {
6036              onClick: submitScore,
6037              disabled: isSubmittingScore || !userId,
6038              className: "bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-8 rounded-full shadow-lg disabled:opacity-50 transition-all",
6039              children: isSubmittingScore ? "Submitting..." : userId ? "Submit Score" : "Log in to submit"
6040            }
6041          ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-emerald-400 font-bold", children: "Score Submitted!" })
6042        ] })
6043      ] }),
6044      hasUsedMic && showScoreScreen && leaderboard.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-2xl bg-zinc-900 border border-zinc-800 rounded-3xl p-6 shadow-2xl", children: [
6045        /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-xl font-bold text-white mb-4 flex items-center gap-2", children: [
6046          /* @__PURE__ */ jsxRuntimeExports.jsx(Timer, { size: 20, className: "text-indigo-400" }),
6047          " Top Singers"
6048        ] }),
6049        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-2", children: leaderboard.map((entry, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center justify-between p-3 rounded-xl ${entry.isCurrentUser ? "bg-indigo-500/20 border border-indigo-500/50" : "bg-black/40"}`, children: [
6050          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
6051            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-zinc-500 font-black w-6 text-center", children: [
6052              "#",
6053              idx + 1
6054            ] }),
6055            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-white", children: entry.userName || "Guest" })
6056          ] }),
6057          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-emerald-400 font-mono font-bold", children: entry.score.toLocaleString() })
6058        ] }, idx)) })
6059      ] })
6060    ] });
6061  }
6062  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full bg-[#111] p-3 sm:p-8 overflow-hidden relative", children: [
6063    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-2 left-2 right-2 sm:top-4 sm:left-4 sm:right-4 z-50 flex items-center justify-between pointer-events-none", children: [
6064      timeToNextSyllable > 3 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 bg-black/60 backdrop-blur border border-zinc-800/80 px-3 py-1.5 rounded-full text-zinc-300 shadow-xl text-xs font-mono select-none animate-in fade-in slide-in-from-top-4", children: [
6065        /* @__PURE__ */ jsxRuntimeExports.jsx(Timer, { size: 14, className: "text-indigo-400 animate-pulse" }),
6066        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("karaoke.nextLyricIn", { seconds: Math.ceil(timeToNextSyllable) }) })
6067      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}),
6068      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pointer-events-auto ml-2 flex items-center gap-2", children: [
6069        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 bg-zinc-800/80 backdrop-blur border border-zinc-700/50 rounded-lg px-2 py-1.5 shadow-lg", children: [
6070          /* @__PURE__ */ jsxRuntimeExports.jsx(
6071            "button",
6072            {
6073              onClick: () => setTextScale(textScale - 0.05),
6074              className: "w-6 h-6 flex items-center justify-center text-zinc-400 hover:text-white transition-colors rounded",
6075              title: "Decrease text size",
6076              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Minus, { size: 12 })
6077            }
6078          ),
6079          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold text-zinc-400 w-9 text-center tracking-wider", children: [
6080            Math.round(textScale * 100),
6081            "%"
6082          ] }),
6083          /* @__PURE__ */ jsxRuntimeExports.jsx(
6084            "button",
6085            {
6086              onClick: () => setTextScale(textScale + 0.05),
6087              className: "w-6 h-6 flex items-center justify-center text-zinc-400 hover:text-white transition-colors rounded",
6088              title: "Increase text size",
6089              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 })
6090            }
6091          )
6092        ] }),
6093        displayMode === "classic" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
6094          "button",
6095          {
6096            onClick: () => setDisplayMode("highway"),
6097            className: "p-2 bg-zinc-800/80 hover:bg-zinc-700 backdrop-blur border border-zinc-700/50 rounded-lg text-zinc-400 transition-colors shadow-lg flex items-center gap-2",
6098            title: "Switch to Highway Mode",
6099            children: [
6100              /* @__PURE__ */ jsxRuntimeExports.jsx(SquareSquare, { size: 16 }),
6101              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold uppercase tracking-wider hidden sm:inline", children: "Classic" })
6102            ]
6103          }
6104        ) : displayMode === "highway" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
6105          "button",
6106          {
6107            onClick: () => setDisplayMode("kinetic"),
6108            className: "p-2 bg-zinc-800/80 hover:bg-zinc-700 backdrop-blur border border-zinc-700/50 rounded-lg text-zinc-400 transition-colors shadow-lg flex items-center gap-2",
6109            title: "Switch to Kinetic Mode",
6110            children: [
6111              /* @__PURE__ */ jsxRuntimeExports.jsx(Route, { size: 16 }),
6112              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold uppercase tracking-wider hidden sm:inline", children: "Highway" })
6113            ]
6114          }
6115        ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
6116          "button",
6117          {
6118            onClick: () => setDisplayMode("classic"),
6119            className: "p-2 bg-zinc-800/80 hover:bg-zinc-700 backdrop-blur border border-zinc-700/50 rounded-lg text-zinc-400 transition-colors shadow-lg flex items-center gap-2",
6120            title: "Switch to Classic Mode",
6121            children: [
6122              /* @__PURE__ */ jsxRuntimeExports.jsx(Type, { size: 16 }),
6123              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold uppercase tracking-wider hidden sm:inline", children: "Kinetic" })
6124            ]
6125          }
6126        )
6127      ] }),
6128      isMicEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-end pointer-events-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-3xl font-black font-mono text-white drop-shadow-[0_2px_10px_rgba(0,0,0,0.8)]", children: score.toLocaleString() }) })
6129    ] }),
6130    hitFlash !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute top-20 left-1/2 -translate-x-1/2 z-50 text-4xl font-black italic uppercase tracking-wider animate-in fade-in zoom-in-50 duration-150 drop-shadow-[0_0_20px_rgba(0,0,0,0.8)] ${hitFlash === "perfect" ? "text-amber-400" : hitFlash === "good" ? "text-emerald-400" : hitFlash === "great" ? "text-blue-400" : "text-rose-500"}`, children: hitFlash }),
6131    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 right-0 h-2 bg-zinc-900", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full bg-gradient-to-r from-emerald-400 to-indigo-500 transition-all", style: { width: `${maxScore > 0 ? score / (maxScore * 2) * 100 : 0}%` } }) }),
6132    countdownData && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-6 left-1/2 -translate-x-1/2 flex flex-col items-center z-50 animate-in fade-in zoom-in-95 duration-200", children: [
6133      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 text-[10px] font-bold tracking-widest mb-1 uppercase drop-shadow-md", children: t("karaoke.getReady", "Get Ready") }),
6134      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-12 h-12 flex items-center justify-center", children: [
6135        /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "absolute inset-0 w-full h-full -rotate-90", viewBox: "0 0 64 64", children: [
6136          /* @__PURE__ */ jsxRuntimeExports.jsx(
6137            "circle",
6138            {
6139              cx: "32",
6140              cy: "32",
6141              r: "26",
6142              fill: "transparent",
6143              stroke: "#27272a",
6144              strokeWidth: "4"
6145            }
6146          ),
6147          /* @__PURE__ */ jsxRuntimeExports.jsx(
6148            "circle",
6149            {
6150              cx: "32",
6151              cy: "32",
6152              r: "26",
6153              fill: "transparent",
6154              stroke: "#f50",
6155              strokeWidth: "4",
6156              strokeDasharray: 2 * Math.PI * 26,
6157              strokeDashoffset: 2 * Math.PI * 26 * (1 - countdownData.percentage),
6158              strokeLinecap: "round",
6159              className: "transition-all duration-75"
6160            }
6161          )
6162        ] }),
6163        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-lg font-bold text-white drop-shadow-md z-10", children: Math.ceil(countdownData.timeToNext) })
6164      ] })
6165    ] }),
6166    displayMode === "kinetic" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
6167      LyricsKinetic,
6168      {
6169        lines,
6170        currentTime,
6171        activeLineIdx,
6172        textScale
6173      }
6174    ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
6175      LyricsBouncing,
6176      {
6177        lines,
6178        currentTime,
6179        activeLineIdx,
6180        textScale,
6181        ballIcon,
6182        onBallIconChange: handleBallIconChange
6183      }
6184    ),
6185    isMicEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-6 top-1/2 -translate-y-1/2 w-4 h-64 bg-zinc-900 rounded-full border border-zinc-800 overflow-hidden flex flex-col justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6186      "div",
6187      {
6188        className: "w-full bg-gradient-to-t from-emerald-400 via-amber-400 to-rose-500 transition-all duration-75 ease-out",
6189        style: { height: `${Math.min(100, currentVolume * 200)}%` }
6190      }
6191    ) }),
6192    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-3 left-3 sm:bottom-6 sm:left-6 z-50 flex items-center gap-2 bg-black/80 backdrop-blur p-1.5 sm:p-2 rounded-2xl border border-zinc-800 shadow-2xl", children: [
6193      /* @__PURE__ */ jsxRuntimeExports.jsx(
6194        "button",
6195        {
6196          onClick: () => setIsMicEnabled(!isMicEnabled),
6197          className: `w-9 h-9 sm:w-12 sm:h-12 flex items-center justify-center rounded-lg sm:rounded-xl transition-all ${isMicEnabled ? "bg-emerald-500/20 text-emerald-400" : "bg-zinc-800 text-zinc-400 hover:text-white"}`,
6198          children: isMicEnabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(Mic, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(MicOff, { size: 18 })
6199        }
6200      ),
6201      isMicEnabled && devices.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col ml-2", children: [
6202        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase font-bold text-zinc-500 tracking-wider mb-1", children: "Input Device" }),
6203        /* @__PURE__ */ jsxRuntimeExports.jsx(
6204          "select",
6205          {
6206            value: selectedDevice,
6207            onChange: (e) => setSelectedDevice(e.target.value),
6208            className: "bg-zinc-900 border border-zinc-700 text-xs text-white rounded-lg px-2 py-1 max-w-[200px] outline-none",
6209            children: devices.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: d.deviceId, children: d.label || `Mic ${d.deviceId.slice(0, 4)}` }, d.deviceId))
6210          }
6211        )
6212      ] })
6213    ] })
6214  ] });
6215};
6216function usePerformanceStats(isPlaying = false) {
6217  const [fps, setFps] = reactExports.useState(60);
6218  const [memUsedMB, setMemUsedMB] = reactExports.useState(null);
6219  const [memLimitMB, setMemLimitMB] = reactExports.useState(null);
6220  const [isLagging, setIsLagging] = reactExports.useState(false);
6221  const [lowPerf, setLowPerf] = reactExports.useState(() => {
6222    if (typeof window === "undefined") return false;
6223    return localStorage.getItem("low_performance") === "true";
6224  });
6225  const frameCountRef = reactExports.useRef(0);
6226  const lastFpsUpdateRef = reactExports.useRef(performance.now());
6227  const consecutiveLowFpsCountRef = reactExports.useRef(0);
6228  const rafIdRef = reactExports.useRef(null);
6229  reactExports.useEffect(() => {
6230    const handleLowPerfChange = () => {
6231      setLowPerf(localStorage.getItem("low_performance") === "true");
6232    };
6233    window.addEventListener("storage", handleLowPerfChange);
6234    window.addEventListener("low_performance_change", handleLowPerfChange);
6235    return () => {
6236      window.removeEventListener("storage", handleLowPerfChange);
6237      window.removeEventListener("low_performance_change", handleLowPerfChange);
6238    };
6239  }, []);
6240  const toggleLowPerf = () => {
6241    const next = !lowPerf;
6242    setLowPerf(next);
6243    localStorage.setItem("low_performance", next ? "true" : "false");
6244    window.dispatchEvent(new Event("low_performance_change"));
6245  };
6246  reactExports.useEffect(() => {
6247    let active = true;
6248    const loop = (now) => {
6249      if (!active) return;
6250      frameCountRef.current++;
6251      const elapsed = now - lastFpsUpdateRef.current;
6252      if (elapsed >= 500) {
6253        const currentFps = Math.round(frameCountRef.current * 1e3 / elapsed);
6254        setFps(currentFps);
6255        frameCountRef.current = 0;
6256        lastFpsUpdateRef.current = now;
6257        if (typeof window !== "undefined" && (performance == null ? void 0 : performance.memory)) {
6258          const mem = performance.memory;
6259          setMemUsedMB(Math.round(mem.usedJSHeapSize / (1024 * 1024)));
6260          setMemLimitMB(Math.round(mem.jsHeapSizeLimit / (1024 * 1024)));
6261        }
6262        if (isPlaying && currentFps < 28) {
6263          consecutiveLowFpsCountRef.current++;
6264          if (consecutiveLowFpsCountRef.current >= 6) {
6265            setIsLagging(true);
6266          }
6267        } else if (currentFps >= 45) {
6268          consecutiveLowFpsCountRef.current = 0;
6269          setIsLagging(false);
6270        }
6271      }
6272      rafIdRef.current = requestAnimationFrame(loop);
6273    };
6274    rafIdRef.current = requestAnimationFrame(loop);
6275    return () => {
6276      active = false;
6277      if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
6278    };
6279  }, [isPlaying]);
6280  return {
6281    fps,
6282    memUsedMB,
6283    memLimitMB,
6284    isLagging,
6285    lowPerf,
6286    toggleLowPerf
6287  };
6288}
6289const PerformanceHud = ({ stats, onClose }) => {
6290  const { fps, memUsedMB, memLimitMB, lowPerf, toggleLowPerf } = stats;
6291  const fpsColor = fps >= 50 ? "bg-emerald-400" : fps >= 30 ? "bg-amber-400" : "bg-red-500 animate-pulse";
6292  const fpsTextColor = fps >= 50 ? "text-emerald-400" : fps >= 30 ? "text-amber-400" : "text-red-400 font-bold";
6293  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6294    "div",
6295    {
6296      className: "absolute top-0 left-0 z-40 flex items-center gap-2 bg-black/75 hover:bg-black/90 backdrop-blur-md px-2.5 py-1 rounded-br-lg border-b border-r border-white/15 shadow-md text-[11px] font-mono select-none transition-all",
6297      onClick: (e) => e.stopPropagation(),
6298      onPointerDown: (e) => e.stopPropagation(),
6299      children: [
6300        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
6301          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-2 h-2 rounded-full ${fpsColor}` }),
6302          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: fpsTextColor, children: [
6303            fps,
6304            " FPS"
6305          ] })
6306        ] }),
6307        memUsedMB !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6308          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "|" }),
6309          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-zinc-300", title: `JS Memory Heap: ${memUsedMB} MB / ${memLimitMB} MB`, children: [
6310            /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 12, className: "text-cyan-400" }),
6311            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
6312              memUsedMB,
6313              " MB"
6314            ] })
6315          ] })
6316        ] }),
6317        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "|" }),
6318        /* @__PURE__ */ jsxRuntimeExports.jsxs(
6319          "button",
6320          {
6321            onClick: toggleLowPerf,
6322            className: `px-1.5 py-0.5 rounded text-[10px] font-sans font-medium transition-colors flex items-center gap-1 ${lowPerf ? "bg-emerald-600/80 text-white hover:bg-emerald-500" : "bg-zinc-800 text-zinc-400 hover:text-white hover:bg-zinc-700"}`,
6323            title: lowPerf ? "Low Performance Mode is Active (reduces particles/DPR)" : "Click to enable Low Performance Mode for smoother playback",
6324            children: [
6325              /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { size: 10, className: lowPerf ? "fill-white" : "" }),
6326              lowPerf ? "Low-Perf ON" : "Low-Perf OFF"
6327            ]
6328          }
6329        ),
6330        /* @__PURE__ */ jsxRuntimeExports.jsx(
6331          "button",
6332          {
6333            onClick: onClose,
6334            className: "text-zinc-500 hover:text-zinc-200 transition-colors ml-0.5",
6335            title: "Hide Performance HUD",
6336            children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 13 })
6337          }
6338        )
6339      ]
6340    }
6341  );
6342};
6343const PerformanceLagBanner = ({ fps, onEnableLowPerf, onDismiss }) => {
6344  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6345    "div",
6346    {
6347      className: "absolute top-14 left-4 z-[70] flex items-center gap-3 bg-amber-950/95 border border-amber-500/60 backdrop-blur-md px-3.5 py-2 rounded-lg shadow-2xl text-xs text-amber-200 animate-in fade-in slide-in-from-top-2 max-w-[calc(100%-2rem)] sm:max-w-md md:max-w-lg",
6348      onClick: (e) => e.stopPropagation(),
6349      onPointerDown: (e) => e.stopPropagation(),
6350      children: [
6351        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
6352          /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 15, className: "text-amber-400 animate-pulse flex-shrink-0" }),
6353          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
6354            "Frame drop detected (",
6355            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-amber-300", children: [
6356              fps,
6357              " FPS"
6358            ] }),
6359            "). Enable Low-Performance Mode for smoother visuals?"
6360          ] })
6361        ] }),
6362        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
6363          /* @__PURE__ */ jsxRuntimeExports.jsx(
6364            "button",
6365            {
6366              onClick: onEnableLowPerf,
6367              className: "px-2 py-1 bg-amber-500 hover:bg-amber-400 text-black font-semibold rounded text-[11px] transition-colors",
6368              children: "Enable"
6369            }
6370          ),
6371          /* @__PURE__ */ jsxRuntimeExports.jsx(
6372            "button",
6373            {
6374              onClick: onDismiss,
6375              className: "p-1 hover:bg-white/10 rounded text-amber-300 hover:text-white transition-colors",
6376              title: "Dismiss",
6377              children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 13 })
6378            }
6379          )
6380        ] })
6381      ]
6382    }
6383  );
6384};
6385const SpectrumBarsVisualizer = reactExports.lazy(() => __vitePreload(() => import("./SpectrumBarsVisualizer-D1eLSVtr.js"), true ? __vite__mapDeps([0,1]) : void 0));
6386const SheetMusicViewer = reactExports.lazy(() => __vitePreload(() => import("./SheetMusicViewer-CoFH8B_z.js"), true ? __vite__mapDeps([2,3,1,4,5,6,7,8,9,10]) : void 0));
6387const ParametricEQ = reactExports.lazy(() => __vitePreload(() => import("./ParametricEQ-wkxmF0lG.js"), true ? __vite__mapDeps([11,3,1,4,5,6,12,9,10,13]) : void 0));
6388const OrchestraVisualizer = reactExports.lazy(() => __vitePreload(() => import("./OrchestraVisualizer-Cwwo0xaQ.js"), true ? __vite__mapDeps([14,1,15,12,3,4,5,6,9,10,13,16,17,18,19]) : void 0));
6389const CinematicVisualizer = reactExports.lazy(() => __vitePreload(() => import("./CinematicVisualizer-BM6Yo-FE.js"), true ? __vite__mapDeps([20,1,4,5,6,3,21]) : void 0));
6390const SouthparkOrchestraVisualizer = reactExports.lazy(() => __vitePreload(() => import("./SouthparkOrchestraVisualizer-BGIvyqzT.js"), true ? __vite__mapDeps([22,1,12,3,4,5,6,9,10,13,23,16,18,24,15,17,25,26,27,28]) : void 0));
6391const Taiko = reactExports.lazy(() => __vitePreload(() => import("./Taiko-BWRRYy3Y.js"), true ? __vite__mapDeps([29,1,23,16,18,4,5,6,24,27,28,30]) : void 0));
6392const VisualizerWindow = ({
6393  viewMode,
6394  setViewMode,
6395  track,
6396  currentTime,
6397  duration,
6398  isPlaying,
6399  onPlayPause,
6400  autoPlayCountdown,
6401  onCancelAutoPlay,
6402  onPlayNext,
6403  nextTrack,
6404  isTheaterMode,
6405  setIsTheaterMode,
6406  pianoRollData,
6407  melodyData,
6408  melodySource,
6409  setMelodySource,
6410  hiddenTracks,
6411  trackVisibility,
6412  colorMap,
6413  isLoadingAudio,
6414  isAudioLoaded,
6415  pitchSemitones,
6416  setPitchSemitones,
6417  currentTempo,
6418  setCurrentTempo,
6419  originalTempo,
6420  isBuffering = false,
6421  selectedTrackIndex,
6422  onSelectTrack,
6423  processedInstruments,
6424  uniqueNames,
6425  trackId,
6426  userId,
6427  userName,
6428  uploaderName,
6429  audioMode = "synth",
6430  onSeek,
6431  isGameMode,
6432  setIsGameMode,
6433  onGameModeMute,
6434  onAuthRequested,
6435  soloedIndex = null,
6436  onSolo = () => {
6437  },
6438  mutedIndices = /* @__PURE__ */ new Set(),
6439  onMute = () => {
6440  },
6441  onPanChange,
6442  onInstrumentChange,
6443  isAdmin = false,
6444  staffModeEnabled = false,
6445  soundfont,
6446  isAttached = false,
6447  theme = "light",
6448  playbackRate = 1
6449}) => {
6450  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D;
6451  const isStaff = Boolean(staffModeEnabled || typeof window !== "undefined" && window.location.search.includes("ai_admin=1"));
6452  const isMusicPlaying = isPlaying && !isBuffering;
6453  const containerRef = reactExports.useRef(null);
6454  const hideControlsTimer = reactExports.useRef(null);
6455  const [isFullScreen, setIsFullScreen] = reactExports.useState(false);
6456  const [showControls, setShowControls] = reactExports.useState(true);
6457  const [hasOpenedEQ, setHasOpenedEQ] = reactExports.useState(viewMode === "eq");
6458  const [showVocalControls, setShowVocalControls] = reactExports.useState(false);
6459  const [reverbAmount, setReverbAmount] = reactExports.useState(0);
6460  const [delayAmount, setDelayAmount] = reactExports.useState(0);
6461  const [vocalNormalize, setVocalNormalize] = reactExports.useState(true);
6462  const [autoVocal, setAutoVocal] = reactExports.useState(false);
6463  const [audioDevices, setAudioDevices] = reactExports.useState([]);
6464  const [selectedDeviceId, setSelectedDeviceId] = reactExports.useState("");
6465  const [scoreExists, setScoreExists] = reactExports.useState(null);
6466  const [lowPerf, setLowPerf] = reactExports.useState(() => {
6467    if (typeof window === "undefined") return false;
6468    const saved = localStorage.getItem("low_performance");
6469    if (saved !== null) return saved === "true";
6470    return /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent) || /Macintosh/i.test(navigator.userAgent) && navigator.maxTouchPoints > 0 || "ontouchstart" in window;
6471  });
6472  const [showPerfHud, setShowPerfHud] = reactExports.useState(() => {
6473    if (typeof window === "undefined") return false;
6474    return localStorage.getItem("show_perf_hud") === "true";
6475  });
6476  const [dismissedLag, setDismissedLag] = reactExports.useState(false);
6477  const perfStats = usePerformanceStats(isMusicPlaying);
6478  const togglePerfHud = () => {
6479    const next = !showPerfHud;
6480    setShowPerfHud(next);
6481    localStorage.setItem("show_perf_hud", next ? "true" : "false");
6482  };
6483  reactExports.useEffect(() => {
6484    const qPaths = track.quantizedScorePaths;
6485    const url = (qPaths == null ? void 0 : qPaths.krn) || (qPaths == null ? void 0 : qPaths.studio) || (qPaths == null ? void 0 : qPaths.musescore) || track.scorePath || (qPaths == null ? void 0 : qPaths.q8) || (qPaths == null ? void 0 : qPaths.q16) || (qPaths == null ? void 0 : qPaths.q4) || (qPaths == null ? void 0 : qPaths.q32) || (qPaths == null ? void 0 : qPaths.melody);
6486    if (!url) {
6487      setScoreExists(false);
6488      return;
6489    }
6490    let active = true;
6491    setScoreExists(null);
6492    fetch(url, { method: "HEAD" }).then((res) => {
6493      if (!active) return;
6494      if (res.ok) {
6495        setScoreExists(true);
6496      } else {
6497        fetch(url).then((resGet) => {
6498          if (active) setScoreExists(resGet.ok);
6499        }).catch(() => {
6500          if (active) setScoreExists(false);
6501        });
6502      }
6503    }).catch(() => {
6504      if (!active) return;
6505      fetch(url).then((resGet) => {
6506        if (active) setScoreExists(resGet.ok);
6507      }).catch(() => {
6508        if (active) setScoreExists(false);
6509      });
6510    });
6511    return () => {
6512      active = false;
6513    };
6514  }, [
6515    track.id,
6516    track.scorePath,
6517    (_a = track.quantizedScorePaths) == null ? void 0 : _a.krn,
6518    (_b = track.quantizedScorePaths) == null ? void 0 : _b.studio,
6519    (_c = track.quantizedScorePaths) == null ? void 0 : _c.musescore,
6520    (_d = track.quantizedScorePaths) == null ? void 0 : _d.q8,
6521    (_e = track.quantizedScorePaths) == null ? void 0 : _e.q16,
6522    (_f = track.quantizedScorePaths) == null ? void 0 : _f.q4,
6523    (_g = track.quantizedScorePaths) == null ? void 0 : _g.q32,
6524    (_h = track.quantizedScorePaths) == null ? void 0 : _h.melody
6525  ]);
6526  reactExports.useEffect(() => {
6527    if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
6528      navigator.mediaDevices.enumerateDevices().then((devices) => {
6529        const audioInputs = Array.isArray(devices) ? devices.filter((d) => d.kind === "audioinput") : [];
6530        setAudioDevices(audioInputs);
6531        if (audioInputs.length > 0 && !selectedDeviceId) {
6532          setSelectedDeviceId(audioInputs[0].deviceId);
6533        }
6534      });
6535    }
6536  }, []);
6537  reactExports.useEffect(() => {
6538    if (synth && synth.vocalDSP) {
6539      synth.vocalDSP.setEffects(reverbAmount, delayAmount, vocalNormalize);
6540      synth.vocalDSP.setAutoVocalMode(autoVocal);
6541      synth.setGuideTrackIndex(0);
6542    }
6543  }, [reverbAmount, delayAmount, vocalNormalize, autoVocal]);
6544  reactExports.useEffect(() => {
6545    if (showVocalControls || autoVocal) {
6546      synth.startVocalMic(selectedDeviceId);
6547    }
6548  }, [showVocalControls, autoVocal, selectedDeviceId]);
6549  reactExports.useEffect(() => {
6550    if (viewMode === "eq" && !hasOpenedEQ) {
6551      const timer = setTimeout(() => setHasOpenedEQ(true), 0);
6552      return () => clearTimeout(timer);
6553    }
6554  }, [viewMode, hasOpenedEQ]);
6555  reactExports.useEffect(() => {
6556    const handleFS = () => setIsFullScreen(!!document.fullscreenElement);
6557    document.addEventListener("fullscreenchange", handleFS);
6558    return () => document.removeEventListener("fullscreenchange", handleFS);
6559  }, []);
6560  reactExports.useEffect(() => {
6561    const handleMouseMove = (e) => {
6562      const isNearTop = e.clientY <= 80;
6563      if (isNearTop) {
6564        setShowControls(true);
6565        if (hideControlsTimer.current) {
6566          clearTimeout(hideControlsTimer.current);
6567          hideControlsTimer.current = null;
6568        }
6569      } else {
6570        if (!hideControlsTimer.current) {
6571          hideControlsTimer.current = setTimeout(() => {
6572            setShowControls(false);
6573            hideControlsTimer.current = null;
6574          }, 1e3);
6575        }
6576      }
6577    };
6578    if (!isFullScreen) {
6579      setShowControls(true);
6580      if (hideControlsTimer.current) {
6581        clearTimeout(hideControlsTimer.current);
6582        hideControlsTimer.current = null;
6583      }
6584      return;
6585    }
6586    setShowControls(false);
6587    window.addEventListener("mousemove", handleMouseMove);
6588    return () => {
6589      window.removeEventListener("mousemove", handleMouseMove);
6590      if (hideControlsTimer.current) {
6591        clearTimeout(hideControlsTimer.current);
6592        hideControlsTimer.current = null;
6593      }
6594    };
6595  }, [isFullScreen]);
6596  const toggleFullScreen = () => {
6597    var _a2;
6598    if (!document.fullscreenElement) {
6599      (_a2 = containerRef.current) == null ? void 0 : _a2.requestFullscreen();
6600    } else {
6601      document.exitFullscreen();
6602    }
6603  };
6604  const hasLyrics = ((_i = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _i.lyrics) && track.midiMetadata.lyrics.length > 0;
6605  const hasPercussion = (_j = track == null ? void 0 : track.instruments) == null ? void 0 : _j.some((inst) => inst.midiChannel === 9 && (inst.notesCount > 0 || inst.notesCount === void 0));
6606  const hasMelodicTracks = (_k = track == null ? void 0 : track.instruments) == null ? void 0 : _k.some((inst) => inst.midiChannel !== 9 && (inst.notesCount > 0 || inst.notesCount === void 0));
6607  const isPianoRollLoading = (!!(track == null ? void 0 : track.isLight) || !!track.pianoRollPath) && pianoRollData === null;
6608  const hasPianoRollData = isPianoRollLoading || pianoRollData && pianoRollData.length > 0;
6609  const isScoreLoading = !!(track == null ? void 0 : track.isLight);
6610  const hasScore = (isScoreLoading || !!(track.scorePath || ((_l = track.quantizedScorePaths) == null ? void 0 : _l.studio) || ((_m = track.quantizedScorePaths) == null ? void 0 : _m.engraved) || ((_n = track.quantizedScorePaths) == null ? void 0 : _n.q8) || ((_o = track.quantizedScorePaths) == null ? void 0 : _o.q16) || ((_p = track.quantizedScorePaths) == null ? void 0 : _p.krn) || ((_q = track.quantizedScorePaths) == null ? void 0 : _q.q4) || ((_r = track.quantizedScorePaths) == null ? void 0 : _r.q32))) && scoreExists !== false;
6611  reactExports.useEffect(() => {
6612    if (track == null ? void 0 : track.isLight) return;
6613    if (!isStaff && (viewMode === "orchestra" || viewMode === "cinematic")) {
6614      setViewMode(hasPianoRollData ? "premium_orchestra" : "waveform");
6615      return;
6616    }
6617    if ((!hasPianoRollData || !hasMelodicTracks) && viewMode === "piano") {
6618      setViewMode(hasPercussion ? "percussion" : "waveform");
6619    } else if (!hasPianoRollData && (viewMode === "percussion" || viewMode === "orchestra" || viewMode === "premium_orchestra" || viewMode === "cinematic")) {
6620      setViewMode("waveform");
6621    } else if (!hasScore && (viewMode === "score" || viewMode === "melody")) {
6622      setViewMode(hasPianoRollData && hasMelodicTracks ? "piano" : "waveform");
6623    }
6624  }, [hasPianoRollData, hasMelodicTracks, hasPercussion, hasScore, viewMode, setViewMode, track == null ? void 0 : track.isLight, isStaff]);
6625  const getBgColor = () => {
6626    if (viewMode === "score" || viewMode === "melody") return "bg-[#fdfaf5]";
6627    if (viewMode === "piano" || viewMode === "percussion" || viewMode === "orchestra" || viewMode === "cinematic" || viewMode === "premium_orchestra" || viewMode === "taiko") return "bg-[#050505]";
6628    if (theme === "light") {
6629      if (viewMode === "waveform") return "bg-zinc-50";
6630      if (viewMode === "karaoke") return "bg-zinc-100";
6631      return "bg-zinc-50";
6632    }
6633    if (viewMode === "waveform") return "bg-[#0a0a0a]";
6634    if (viewMode === "karaoke") return "bg-[#111]";
6635    return "bg-[#050505]";
6636  };
6637  const getTopBarClass = () => {
6638    if (theme === "light") return "bg-zinc-100 border-b border-zinc-200 text-zinc-800";
6639    if (theme === "fun") return "bg-[#fff1f2] border-b border-pink-200 text-pink-900";
6640    return "bg-[#050505] border-b border-white/10 text-white";
6641  };
6642  const getTabButtonClass = (isActive, isOrange = false, isDisabled = false) => {
6643    const base = `px-3 py-1.5 rounded-md text-xs font-bold flex items-center gap-2 transition-all ${isDisabled ? "opacity-40 cursor-not-allowed" : ""}`;
6644    if (isActive && !isDisabled) {
6645      if (theme === "fun") {
6646        return `${base} bg-gradient-to-r from-pink-500 to-indigo-500 text-white shadow-lg shadow-indigo-500/20`;
6647      }
6648      return `${base} ${isOrange ? "bg-orange-600" : "bg-indigo-600"} text-white shadow-lg ${isOrange ? "shadow-orange-500/20" : "shadow-indigo-500/20"}`;
6649    }
6650    if (theme === "light") return `${base} text-zinc-600 ${isDisabled ? "" : "hover:bg-zinc-200/60 hover:text-zinc-900"}`;
6651    if (theme === "fun") return `${base} text-pink-850 ${isDisabled ? "" : "hover:bg-pink-100/60 hover:text-pink-950"}`;
6652    return `${base} text-zinc-400 ${isDisabled ? "" : "hover:bg-white/5 hover:text-white"}`;
6653  };
6654  const getControlIconClass = (isDisabled = false) => {
6655    const baseStyle = "p-1.5 rounded-md transition-colors";
6656    if (isDisabled) return `${baseStyle} text-zinc-600 cursor-not-allowed`;
6657    if (theme === "light") return `${baseStyle} text-zinc-500 hover:text-zinc-900 hover:bg-zinc-200/60`;
6658    if (theme === "fun") return `${baseStyle} text-pink-700 hover:text-pink-950 hover:bg-pink-100/60`;
6659    return `${baseStyle} text-zinc-400 hover:text-white hover:bg-white/10`;
6660  };
6661  const getSeparatorClass = () => {
6662    if (theme === "light") return "flex items-center gap-1 pl-4 border-l border-zinc-200 ml-2";
6663    if (theme === "fun") return "flex items-center gap-1 pl-4 border-l border-pink-200 ml-2";
6664    return "flex items-center gap-1 pl-4 border-l border-white/10 ml-2";
6665  };
6666  const getBottomBarClass = () => {
6667    const base = "w-full border-t py-2 px-4 flex items-center justify-center gap-4 z-30 shrink-0 rounded-b-xl";
6668    if (theme === "light") return `${base} bg-zinc-100 border-zinc-200 text-zinc-800`;
6669    if (theme === "fun") return `${base} bg-[#fff1f2] border-t border-pink-200 text-pink-900`;
6670    return `${base} bg-[#050505] border-white/10 text-white`;
6671  };
6672  const getBottomButtonClass = () => {
6673    if (theme === "light") return "p-1.5 hover:bg-zinc-200/60 disabled:opacity-30 rounded-full transition-colors text-zinc-700";
6674    if (theme === "fun") return "p-1.5 hover:bg-pink-100/60 disabled:opacity-30 rounded-full transition-colors text-pink-800";
6675    return "p-1.5 hover:bg-white/20 disabled:opacity-30 rounded-full transition-colors text-zinc-200";
6676  };
6677  const getBottomTextClass = () => {
6678    if (theme === "light") return "text-sm font-bold text-zinc-700 min-w-[80px] text-center drop-shadow-sm";
6679    return "text-sm font-bold text-zinc-200 min-w-[80px] text-center drop-shadow-sm";
6680  };
6681  const [xmlData, setXmlData] = reactExports.useState(null);
6682  const [currentPage, setCurrentPage] = reactExports.useState(1);
6683  const [pageCount, setPageCount] = reactExports.useState(0);
6684  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6685    "div",
6686    {
6687      id: "visualizer-container",
6688      ref: containerRef,
6689      onContextMenu: (e) => e.preventDefault(),
6690      className: `relative flex flex-col overflow-hidden transition-all duration-300 ${getBgColor()} ${isFullScreen ? "w-screen h-screen pb-10 rounded-none border-0" : `shadow-2xl w-full ${viewMode === "taiko" ? "min-h-[480px] sm:min-h-0 sm:aspect-video" : "aspect-video"} min-w-0 md:min-w-[600px] border-l border-b border-white/10 ${isAttached ? "rounded-l-xl rounded-r-none" : "rounded-xl border-t border-r"}`}`,
6691      style: !isFullScreen ? viewMode === "taiko" ? void 0 : { aspectRatio: "16/9" } : void 0,
6692      children: [
6693        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center justify-between px-3 h-11 ${getTopBarClass()} z-30 transition-transform duration-300 ${isFullScreen ? "absolute top-0 left-0 right-0" : ""} ${isFullScreen && !showControls ? "-translate-y-full" : "translate-y-0"}`, children: [
6694          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 overflow-x-auto scrollbar-hide min-w-0 flex-1", children: [
6695            hasLyrics && /* @__PURE__ */ jsxRuntimeExports.jsxs(
6696              "button",
6697              {
6698                onClick: () => {
6699                  setViewMode("karaoke");
6700                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6701                },
6702                className: getTabButtonClass(viewMode === "karaoke"),
6703                title: "Karaoke / Lyrics",
6704                children: [
6705                  /* @__PURE__ */ jsxRuntimeExports.jsx(Mic, { size: 14 }),
6706                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Karaoke" })
6707                ]
6708              }
6709            ),
6710            isStaff && /* @__PURE__ */ jsxRuntimeExports.jsxs(
6711              "button",
6712              {
6713                onClick: () => {
6714                  if (!hasPianoRollData) return;
6715                  setViewMode("orchestra");
6716                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6717                },
6718                className: getTabButtonClass(viewMode === "orchestra", false, !hasPianoRollData),
6719                title: !hasPianoRollData ? "Orchestra View not available (processing in progress)" : "Orchestra View (Legacy)",
6720                disabled: !hasPianoRollData,
6721                children: [
6722                  /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 14 }),
6723                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Orch" })
6724                ]
6725              }
6726            ),
6727            isStaff && /* @__PURE__ */ jsxRuntimeExports.jsxs(
6728              "button",
6729              {
6730                onClick: () => {
6731                  if (!hasPianoRollData) return;
6732                  setViewMode("cinematic");
6733                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6734                },
6735                className: getTabButtonClass(viewMode === "cinematic", false, !hasPianoRollData),
6736                title: !hasPianoRollData ? "Cinematic View not available (processing in progress)" : "Cinematic (Beta)",
6737                disabled: !hasPianoRollData,
6738                children: [
6739                  /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 14 }),
6740                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Cinematic" })
6741                ]
6742              }
6743            ),
6744            /* @__PURE__ */ jsxRuntimeExports.jsxs(
6745              "button",
6746              {
6747                onClick: () => {
6748                  if (!hasPianoRollData) return;
6749                  setViewMode("premium_orchestra");
6750                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6751                },
6752                className: getTabButtonClass(viewMode === "premium_orchestra", false, !hasPianoRollData),
6753                title: !hasPianoRollData ? "Showtime not available (processing in progress)" : "Showtime (Animated Orchestra Stage)",
6754                disabled: !hasPianoRollData,
6755                children: [
6756                  /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14 }),
6757                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Showtime" })
6758                ]
6759              }
6760            ),
6761            /* @__PURE__ */ jsxRuntimeExports.jsxs(
6762              "button",
6763              {
6764                onClick: () => {
6765                  if (!hasPianoRollData || !hasMelodicTracks) return;
6766                  setViewMode("piano");
6767                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6768                },
6769                className: getTabButtonClass(viewMode === "piano" && !isGameMode, false, !hasPianoRollData || !hasMelodicTracks),
6770                title: !hasPianoRollData ? "Piano Roll not available (processing in progress)" : !hasMelodicTracks ? "Piano Roll not available for percussion-only tracks" : "Piano Roll",
6771                disabled: !hasPianoRollData || !hasMelodicTracks,
6772                children: [
6773                  /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size: 14 }),
6774                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Piano" })
6775                ]
6776              }
6777            ),
6778            /* @__PURE__ */ jsxRuntimeExports.jsx(
6779              "button",
6780              {
6781                onClick: () => {
6782                  if (!hasPianoRollData || !hasMelodicTracks) return;
6783                  setViewMode("piano");
6784                  if (setIsGameMode) setIsGameMode(true);
6785                },
6786                className: getTabButtonClass(viewMode === "piano" && isGameMode, true, !hasPianoRollData || !hasMelodicTracks),
6787                title: !hasPianoRollData ? "Gamepad Play-Along not available (processing in progress)" : !hasMelodicTracks ? "Gamepad Play-Along not available for percussion-only tracks" : "Play-Along Game Mode",
6788                disabled: !hasPianoRollData || !hasMelodicTracks,
6789                children: /* @__PURE__ */ jsxRuntimeExports.jsx(Gamepad2, { size: 14, className: viewMode === "piano" && isGameMode ? "animate-pulse" : "" })
6790              }
6791            ),
6792            hasPercussion && /* @__PURE__ */ jsxRuntimeExports.jsxs(
6793              "button",
6794              {
6795                onClick: () => {
6796                  if (!hasPianoRollData) return;
6797                  setViewMode("percussion");
6798                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6799                },
6800                className: getTabButtonClass(viewMode === "percussion", false, !hasPianoRollData),
6801                title: !hasPianoRollData ? "Percussion Roll not available (processing in progress)" : "Percussion Roll View",
6802                disabled: !hasPianoRollData,
6803                children: [
6804                  /* @__PURE__ */ jsxRuntimeExports.jsx(Drum, { size: 14 }),
6805                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Perc" })
6806                ]
6807              }
6808            ),
6809            /* @__PURE__ */ jsxRuntimeExports.jsxs(
6810              "button",
6811              {
6812                onClick: () => {
6813                  setViewMode("taiko");
6814                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6815                },
6816                className: getTabButtonClass(viewMode === "taiko", true, false),
6817                title: "Taiko no Hamie Rhythm Game",
6818                children: [
6819                  /* @__PURE__ */ jsxRuntimeExports.jsx(Drum, { size: 14, className: viewMode === "taiko" ? "animate-pulse text-amber-300" : "" }),
6820                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Taiko" })
6821                ]
6822              }
6823            ),
6824            /* @__PURE__ */ jsxRuntimeExports.jsxs(
6825              "button",
6826              {
6827                onClick: () => {
6828                  setViewMode("waveform");
6829                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6830                },
6831                className: getTabButtonClass(viewMode === "waveform"),
6832                title: "Waveform View",
6833                children: [
6834                  /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 14 }),
6835                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Wave" })
6836                ]
6837              }
6838            ),
6839            /* @__PURE__ */ jsxRuntimeExports.jsxs(
6840              "button",
6841              {
6842                onClick: () => {
6843                  if (!hasScore) return;
6844                  setViewMode(viewMode === "melody" ? "melody" : "score");
6845                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6846                },
6847                className: getTabButtonClass(viewMode === "score" || viewMode === "melody", false, !hasScore),
6848                title: !hasScore ? "Sheet Music Score not available (processing in progress)" : "Score",
6849                disabled: !hasScore,
6850                children: [
6851                  /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 14 }),
6852                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "Score" })
6853                ]
6854              }
6855            ),
6856            /* @__PURE__ */ jsxRuntimeExports.jsxs(
6857              "button",
6858              {
6859                onClick: () => {
6860                  setViewMode("eq");
6861                  if (isGameMode && setIsGameMode) setIsGameMode(false);
6862                },
6863                className: getTabButtonClass(viewMode === "eq"),
6864                title: "Equalizer Settings",
6865                children: [
6866                  /* @__PURE__ */ jsxRuntimeExports.jsx(SlidersHorizontal, { size: 14 }),
6867                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: "EQ" })
6868                ]
6869              }
6870            )
6871          ] }),
6872          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `${getSeparatorClass()} flex-shrink-0`, children: [
6873            /* @__PURE__ */ jsxRuntimeExports.jsx(
6874              "button",
6875              {
6876                onClick: togglePerfHud,
6877                className: `${getControlIconClass(false)} ${showPerfHud ? "text-cyan-400 bg-white/10" : ""}`,
6878                title: showPerfHud ? "Hide Device Performance Stats (FPS / RAM)" : "Show Device Performance Stats (FPS / RAM)",
6879                children: /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 18 })
6880              }
6881            ),
6882            /* @__PURE__ */ jsxRuntimeExports.jsx(
6883              "button",
6884              {
6885                onClick: () => !isFullScreen && setIsTheaterMode(!isTheaterMode),
6886                className: getControlIconClass(isFullScreen),
6887                title: isFullScreen ? "Theater Mode disabled in Full Screen" : isTheaterMode ? "Exit Theater Mode" : "Theater Mode",
6888                disabled: isFullScreen,
6889                children: /* @__PURE__ */ jsxRuntimeExports.jsx(RectangleHorizontal, { size: 18 })
6890              }
6891            ),
6892            /* @__PURE__ */ jsxRuntimeExports.jsx(
6893              "button",
6894              {
6895                onClick: toggleFullScreen,
6896                className: getControlIconClass(false),
6897                title: isFullScreen ? "Exit Full Screen" : "Full Screen",
6898                children: isFullScreen ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize, { size: 18 })
6899              }
6900            )
6901          ] })
6902        ] }),
6903        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `relative flex-1 overflow-hidden group transition-all duration-300 ${isFullScreen ? getBgColor() : ""}`, children: [
6904          /* @__PURE__ */ jsxRuntimeExports.jsxs(
6905            "div",
6906            {
6907              onClick: (e) => {
6908                const target = e.target;
6909                if (target.closest("button, select, input, textarea")) {
6910                  return;
6911                }
6912                if (window.isPuppetDragging) {
6913                  e.stopPropagation();
6914                  return;
6915                }
6916                if (viewMode === "eq" || viewMode === "score" || viewMode === "melody" || viewMode === "piano" || viewMode === "percussion" || isBuffering && isPlaying) return;
6917                if (!isPlaying && duration > 0 && currentTime >= duration - 0.1) {
6918                  if (onSeek) onSeek(0);
6919                  if (onPlayPause) onPlayPause(true);
6920                } else {
6921                  if (onPlayPause) onPlayPause();
6922                }
6923              },
6924              className: `absolute inset-x-0 bottom-0 ${isFullScreen ? showControls ? "top-11" : "top-0" : "top-0"} transition-all duration-300 ${viewMode === "eq" || viewMode === "score" || viewMode === "melody" || viewMode === "percussion" ? "" : "cursor-pointer"} ${getBgColor()}`,
6925              children: [
6926                viewMode === "orchestra" && isStaff && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-8 h-8 animate-spin" }) }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6927                  OrchestraVisualizer,
6928                  {
6929                    track,
6930                    currentTime,
6931                    isPlaying: isMusicPlaying,
6932                    pianoRollData,
6933                    processedInstruments,
6934                    uniqueNames,
6935                    soloedIndex,
6936                    onSolo,
6937                    mutedIndices,
6938                    onMute,
6939                    onPanChange,
6940                    hiddenTracks
6941                  }
6942                ) }),
6943                viewMode === "premium_orchestra" && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-8 h-8 animate-spin" }) }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6944                  SouthparkOrchestraVisualizer,
6945                  {
6946                    track,
6947                    currentTime,
6948                    isPlaying: isMusicPlaying,
6949                    pianoRollData,
6950                    processedInstruments,
6951                    uniqueNames,
6952                    soloedIndex,
6953                    onSolo,
6954                    mutedIndices,
6955                    onMute,
6956                    onPanChange,
6957                    hiddenTracks,
6958                    colorMap,
6959                    selectedTrackIndex,
6960                    onSelectTrack
6961                  }
6962                ) }),
6963                viewMode === "cinematic" && isStaff && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-8 h-8 animate-spin" }) }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6964                  CinematicVisualizer,
6965                  {
6966                    track,
6967                    currentTime,
6968                    isPlaying: isMusicPlaying,
6969                    pianoRollData,
6970                    processedInstruments,
6971                    uniqueNames,
6972                    soloedIndex,
6973                    onSolo,
6974                    mutedIndices,
6975                    onMute,
6976                    onPanChange,
6977                    hiddenTracks
6978                  }
6979                ) }),
6980                viewMode === "piano" && /* @__PURE__ */ jsxRuntimeExports.jsx(
6981                  PianoRoll,
6982                  {
6983                    data: pianoRollData,
6984                    melodyData,
6985                    currentTime,
6986                    isPlaying: isMusicPlaying,
6987                    isAudioLoaded,
6988                    hiddenTracks,
6989                    colorMap,
6990                    selectedTrackIndex,
6991                    processedInstruments,
6992                    uniqueNames,
6993                    onPlayPause,
6994                    onSeek,
6995                    isGameModeProp: isGameMode,
6996                    setIsGameModeProp: setIsGameMode,
6997                    onGameModeMute,
6998                    onAuthRequested,
6999                    trackId,
7000                    playbackRate,
7001                    userId,
7002                    userName,
7003                    duration,
7004                    tempo: (_s = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _s.tempo,
7005                    timeSignature: ((_t = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _t.timeSignature) ? track.midiMetadata.timeSignature.split("/").map(Number) : [4, 4],
7006                    isLoadingAudio,
7007                    onSelectTrack,
7008                    soundfont,
7009                    melodySource,
7010                    theme
7011                  }
7012                ),
7013                viewMode === "percussion" && /* @__PURE__ */ jsxRuntimeExports.jsx(
7014                  PercussionRoll,
7015                  {
7016                    data: pianoRollData,
7017                    currentTime,
7018                    isPlaying: isMusicPlaying,
7019                    hiddenTracks,
7020                    colorMap,
7021                    processedInstruments,
7022                    uniqueNames,
7023                    onPlayPause,
7024                    onSeek,
7025                    playbackRate,
7026                    duration,
7027                    tempo: (_u = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _u.tempo,
7028                    timeSignature: ((_v = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _v.timeSignature) ? track.midiMetadata.timeSignature.split("/").map(Number) : [4, 4],
7029                    soundfont,
7030                    theme
7031                  }
7032                ),
7033                viewMode === "taiko" && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin" }) }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7034                  Taiko,
7035                  {
7036                    data: pianoRollData || [],
7037                    melodyData,
7038                    processedInstruments,
7039                    midiMetadata: track == null ? void 0 : track.midiMetadata,
7040                    currentTime,
7041                    isPlaying: isMusicPlaying,
7042                    duration,
7043                    tempo: (_w = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _w.tempo,
7044                    timeSignature: ((_x = track == null ? void 0 : track.midiMetadata) == null ? void 0 : _x.timeSignature) ? track.midiMetadata.timeSignature.split("/").map(Number) : [4, 4],
7045                    trackId,
7046                    userId,
7047                    userName,
7048                    onPlayPause,
7049                    onSeek,
7050                    onGameModeMute,
7051                    onAuthRequested,
7052                    playbackRate,
7053                    theme,
7054                    isLoadingAudio,
7055                    isAudioLoaded
7056                  }
7057                ) }),
7058                viewMode === "waveform" && /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin" }) }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7059                  SpectrumBarsVisualizer,
7060                  {
7061                    isPlaying: isMusicPlaying,
7062                    audioNode: audioMode !== "webmidi" && audioMode !== "singleopus" ? synth.getMasterGain() : void 0,
7063                    audioContext: audioMode !== "webmidi" && audioMode !== "singleopus" ? synth.getAudioContext() : void 0
7064                  },
7065                  track == null ? void 0 : track.id
7066                ) }),
7067                (viewMode === "score" || viewMode === "melody") && /* @__P
7067URE__ */ jsxRuntimeExports.jsx("div", { className: "h-full w-full bg-[#fdfaf5] overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3 bg-[#fdfaf5]", children: [
7068                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex items-center justify-center w-12 h-12", children: [
7069                    /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 40, className: "text-indigo-600 animate-spin" }),
7070                    /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 18, className: "absolute text-indigo-600" })
7071                  ] }),
7072                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-600 tracking-wide", children: "Loading Sheet Music Engine..." })
7073                ] }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7074                  SheetMusicViewer,
7075                  {
7076                    xmlData,
7077                    scorePath: track.scorePath,
7078                    quantizedScorePaths: track.quantizedScorePaths,
7079                    trackVisibility: trackVisibility || uniqueNames.map((name) => !hiddenTracks.has(name)),
7080                    isPlaying: isMusicPlaying,
7081                    currentTime,
7082                    duration,
7083                    processedInstruments,
7084                    recommendedQuantization: (_y = track.midiMetadata) == null ? void 0 : _y.recommendedQuantization,
7085                    onSeek,
7086                    title: track.title,
7087                    selectedTrackIndex,
7088                    midiId: track.id,
7089                    isMelodyMode: viewMode === "melody",
7090                    onToggleMelody: () => setViewMode(viewMode === "melody" ? "score" : "melody"),
7091                    melodySource,
7092                    setMelodySource,
7093                    melodyData,
7094                    onGameModeMute,
7095                    onPlayPause,
7096                    uploaderName,
7097                    audioLatency: audioMode === "synth" ? (synth.getAudioContext().outputLatency || 0) + (synth.getAudioContext().baseLatency || 0) : 0,
7098                    sections: ((_z = track.midiMetadata) == null ? void 0 : _z.sections) || [],
7099                    measureMap: ((_A = track.midiMetadata) == null ? void 0 : _A.measureMap) || [],
7100                    soundfont,
7101                    harmonyChordsUrl: (_B = track.midiMetadata) == null ? void 0 : _B.harmonyChordsXmlPath,
7102                    harmonyRomanUrl: (_C = track.midiMetadata) == null ? void 0 : _C.harmonyRomanXmlPath,
7103                    theme
7104                  }
7105                ) }) }),
7106                viewMode === "karaoke" && /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin" }) }), children: [
7107                  hasLyrics && /* @__PURE__ */ jsxRuntimeExports.jsx(
7108                    Lyrics,
7109                    {
7110                      track,
7111                      currentTime,
7112                      melodyData: (_D = track.midiMetadata) == null ? void 0 : _D.melodyData,
7113                      pianoRollData,
7114                      isPlaying: isMusicPlaying,
7115                      playbackRate,
7116                      userId,
7117                      trackId
7118                    }
7119                  ),
7120                  "      ",
7121                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-4 right-4 flex items-center gap-2 z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7122                    "button",
7123                    {
7124                      onClick: (e) => {
7125                        e.stopPropagation();
7126                        setShowVocalControls(!showVocalControls);
7127                      },
7128                      onPointerDown: (e) => e.stopPropagation(),
7129                      className: "p-3 bg-zinc-800/80 hover:bg-zinc-700 backdrop-blur rounded-full text-zinc-300 transition-colors border border-zinc-700/50 shadow-xl",
7130                      title: "Vocal & Audio Controls",
7131                      children: /* @__PURE__ */ jsxRuntimeExports.jsx(Settings2, { size: 20 })
7132                    }
7133                  ) }),
7134                  showVocalControls && /* @__PURE__ */ jsxRuntimeExports.jsxs(
7135                    "div",
7136                    {
7137                      className: "absolute top-11 right-0 bottom-0 w-80 bg-zinc-900/95 backdrop-blur-xl border-l border-zinc-700/50 p-5 shadow-2xl z-50 overflow-y-auto animate-in slide-in-from-right",
7138                      onClick: (e) => e.stopPropagation(),
7139                      onPointerDown: (e) => e.stopPropagation(),
7140                      children: [
7141                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [
7142                          /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-white font-bold flex items-center gap-2", children: [
7143                            /* @__PURE__ */ jsxRuntimeExports.jsx(MicVocal, { size: 16, className: "text-indigo-400" }),
7144                            " Vocal DSP Engine"
7145                          ] }),
7146                          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setShowVocalControls(false), className: "text-zinc-400 hover:text-white transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 16 }) })
7147                        ] }),
7148                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
7149                          audioDevices.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
7150                            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-400 mb-1", children: "Microphone" }),
7151                            /* @__PURE__ */ jsxRuntimeExports.jsx(
7152                              "select",
7153                              {
7154                                value: selectedDeviceId,
7155                                onChange: (e) => setSelectedDeviceId(e.target.value),
7156                                className: "w-full bg-zinc-800 text-sm text-zinc-300 border border-zinc-700 rounded-lg p-2 focus:border-indigo-500 outline-none",
7157                                children: audioDevices.map((device) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: device.deviceId, children: device.label || `Microphone ${audioDevices.indexOf(device) + 1}` }, device.deviceId))
7158                              }
7159                            )
7160                          ] }),
7161                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
7162                            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-xs text-zinc-400 mb-1", children: [
7163                              /* @__P
7163URE__ */ jsxRuntimeExports.jsx("span", { children: "Reverb" }),
7164                              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
7165                                Math.round(reverbAmount * 100),
7166                                "%"
7167                              ] })
7168                            ] }),
7169                            /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "range", min: "0", max: "1", step: "0.05", value: reverbAmount, onChange: (e) => setReverbAmount(parseFloat(e.target.value)), className: "w-full accent-indigo-500 h-1 bg-zinc-700 rounded-lg appearance-none" })
7170                          ] }),
7171                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
7172                            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-xs text-zinc-400 mb-1", children: [
7173                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Echo / Delay" }),
7174                              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
7175                                Math.round(delayAmount * 100),
7176                                "%"
7177                              ] })
7178                            ] }),
7179                            /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "range", min: "0", max: "1", step: "0.05", value: delayAmount, onChange: (e) => setDelayAmount(parseFloat(e.target.value)), className: "w-full accent-indigo-500 h-1 bg-zinc-700 rounded-lg appearance-none" })
7180                          ] }),
7181                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-zinc-800", children: [
7182                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-300", children: "Volume Normalization" }),
7183                            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setVocalNormalize(!vocalNormalize), className: `w-10 h-5 rounded-full relative transition-colors ${vocalNormalize ? "bg-indigo-500" : "bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-3 h-3 bg-white rounded-full absolute top-1 transition-transform ${vocalNormalize ? "left-6" : "left-1"}` }) })
7184                          ] }),
7185                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-zinc-800", children: [
7186                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-300", title: "Mutes melody track when singing detected", children: "Auto-Vocal Mode" }),
7187                            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setAutoVocal(!autoVocal), className: `w-10 h-5 rounded-full relative transition-colors ${autoVocal ? "bg-indigo-500" : "bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-3 h-3 bg-white rounded-full absolute top-1 transition-transform ${autoVocal ? "left-6" : "left-1"}` }) })
7188                          ] }),
7189                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-zinc-800", children: [
7190                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-300", title: "Reduces graphic quality and active particles for smoother performance", children: "Low Performance Mode" }),
7191                            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => {
7192                              perfStats.toggleLowPerf();
7193                              setLowPerf(!perfStats.lowPerf);
7194                            }, className: `w-10 h-5 rounded-full relative transition-colors ${perfStats.lowPerf ? "bg-emerald-600" : "bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-3 h-3 bg-white rounded-full absolute top-1 transition-transform ${perfStats.lowPerf ? "left-6" : "left-1"}` }) })
7195                          ] }),
7196                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-zinc-800/60 text-xs text-zinc-400", children: [
7197                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Current Framerate" }),
7198                            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `font-mono font-bold ${perfStats.fps >= 50 ? "text-emerald-400" : perfStats.fps >= 30 ? "text-amber-400" : "text-red-400"}`, children: [
7199                              perfStats.fps,
7200                              " FPS"
7201                            ] })
7202                          ] }),
7203                          perfStats.memUsedMB !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs text-zinc-400", children: [
7204                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Browser Memory Heap" }),
7205                            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-zinc-300", children: [
7206                              perfStats.memUsedMB,
7207                              " MB / ",
7208                              perfStats.memLimitMB,
7209                              " MB"
7210                            ] })
7211                          ] }),
7212                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs text-zinc-400 pt-1", children: [
7213                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Performance HUD" }),
7214                            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: togglePerfHud, className: `px-2 py-0.5 rounded text-[11px] font-medium transition-colors ${showPerfHud ? "bg-cyan-600 text-white" : "bg-zinc-800 text-zinc-400 hover:text-white"}`, children: showPerfHud ? "Visible" : "Hidden" })
7215                          ] })
7216                        ] }),
7217                        /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-white font-bold mt-6 mb-4 flex items-center gap-2", children: [
7218                          /* @__PURE__ */ jsxRuntimeExports.jsx(Settings2, { size: 16, className: "text-emerald-400" }),
7219                          " Playback Controls"
7220                        ] }),
7221                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
7222                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
7223                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-300", children: "Key Shift" }),
7224                            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
7225                              /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setPitchSemitones && setPitchSemitones((pitchSemitones || 0) - 1), className: "w-6 h-6 flex items-center justify-center bg-zinc-800 rounded hover:bg-zinc-700 text-white", children: "-" }),
7226                              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm font-mono text-zinc-300 w-6 text-center", children: [
7227                                (pitchSemitones || 0) > 0 ? "+" : "",
7228                                pitchSemitones || 0
7229                              ] }),
7230                              /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setPitchSemitones && setPitchSemitones((pitchSemitones || 0) + 1), className: "w-6 h-6 flex items-center justify-center bg-zinc-800 rounded hover:bg-zinc-700 text-white", children: "+" })
7231                            ] })
7232                          ] }),
7233                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
7234                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-300", children: "Tempo" }),
7235                            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
7236                              /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCurrentTempo && setCurrentTempo((currentTempo || originalTempo || 120) * 0.95), className: "w-6 h-6 flex items-center justify-center bg-zinc-800 rounded hover:bg-zinc-700 text-white", children: "-" }),
7237                              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-mono text-zinc-300 w-10 text-center", children: [
7238                                Math.round((currentTempo || originalTempo || 120) / (originalTempo || 120) * 100),
7239                                "%"
7240                              ] }),
7241                              /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCurrentTempo && setCurrentTempo((currentTempo || originalTempo || 120) * 1.05), className: "w-6 h-6 flex items-center justify-center bg-zinc-800 rounded hover:bg-zinc-700 text-white", children: "+" })
7242                            ] })
7243                          ] })
7244                        ] })
7245                      ]
7246                    }
7247                  )
7248                ] })
7249              ]
7250            }
7251          ),
7252          hasOpenedEQ && /* @__PURE__ */ jsxRuntimeExports.jsx(
7253            "div",
7254            {
7255              style: { display: viewMode === "eq" ? "block" : "none" },
7256              className: `absolute inset-x-0 bottom-0 ${isFullScreen ? showControls ? "top-11" : "top-0" : "top-0"} transition-all duration-300 z-10 bg-[#050505]`,
7257              children: /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin" }) }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7258                ParametricEQ,
7259                {
7260                  inputNode: synth.getMasterGain(),
7261                  availableNodes: synth.getChannels(),
7262                  tracks: processedInstruments,
7263                  selectedTrackIndex,
7264                  onSelectTrack,
7265                  trackId,
7266                  userId
7267                }
7268              ) })
7269            }
7270          ),
7271          autoPlayCountdown !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 z-[60] bg-black/90 backdrop-blur-md flex flex-col items-center justify-center animate-in fade-in duration-500", children: [
7272            /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-zinc-400 text-sm font-bold uppercase tracking-widest mb-4", children: [
7273              "Up Next in ",
7274              autoPlayCountdown,
7275              "s"
7276            ] }),
7277            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-6 max-w-sm text-center", children: [
7278              nextTrack && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group relative cursor-pointer", onClick: onPlayNext, children: [
7279                /* @__PURE__ */ jsxRuntimeExports.jsx(
7280                  VideoCard,
7281                  {
7282                    track: nextTrack,
7283                    variant: "player-thumbnail",
7284                    sizeClass: "w-48 h-28 rounded-xl shadow-2xl ring-2 ring-zinc-800 group-hover:ring-indigo-500 transition-all",
7285                    onSelect: onPlayNext
7286                  }
7287                ),
7288                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4", children: [
7289                  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-2xl font-bold text-white group-hover:text-indigo-400 transition-colors", children: nextTrack.title }),
7290                  nextTrack.artist && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 text-sm", children: nextTrack.artist })
7291                ] })
7292              ] }),
7293              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mt-2", children: [
7294                /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onCancelAutoPlay, className: "px-6 py-2 rounded-full bg-zinc-800 text-white font-bold hover:bg-zinc-700 transition-colors border border-zinc-700", children: "Cancel" }),
7295                /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: onPlayNext, className: "px-6 py-2 rounded-full bg-white text-black font-bold hover:bg-zinc-200 transition-colors flex items-center gap-2", children: [
7296                  /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 18, fill: "currentColor" }),
7297                  " Play Now"
7298                ] })
7299              ] })
7300            ] })
7301          ] }),
7302          isLoadingAudio && isPlaying && viewMode !== "taiko" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center z-40 bg-black/40 backdrop-blur-sm animate-in fade-in", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-3", children: [
7303            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-16 bg-black/60 backdrop-blur-sm rounded-full flex items-center justify-center border-2 border-white/10 animate-pulse", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 32, className: "text-white animate-spin" }) }),
7304            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white font-bold text-sm bg-black/50 px-3 py-1 rounded-full backdrop-blur-sm shadow-lg", children: "Loading Audio..." })
7305          ] }) }),
7306          !isPlaying && !autoPlayCountdown && viewMode !== "eq" && viewMode !== "score" && viewMode !== "melody" && viewMode !== "percussion" && viewMode !== "taiko" && (!isGameMode || viewMode !== "piano") && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none z-40", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7307            "div",
7308            {
7309              className: "w-20 h-20 bg-black/40 backdrop-blur-sm rounded-full flex items-center justify-center border-2 border-white/20 animate-in zoom-in fade-in duration-300 pointer-events-auto cursor-pointer hover:bg-black/60 hover:scale-105 transition-all",
7310              onClick: (e) => {
7311                e.stopPropagation();
7312                if (!isPlaying && duration > 0 && currentTime >= duration - 0.1) {
7313                  if (onSeek) onSeek(0);
7314                  if (onPlayPause) onPlayPause(true);
7315                } else {
7316                  if (onPlayPause) onPlayPause();
7317                }
7318              },
7319              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 40, fill: "white", className: "text-white ml-2 opacity-80" })
7320            }
7321          ) }),
7322          showPerfHud && /* @__PURE__ */ jsxRuntimeExports.jsx(
7323            PerformanceHud,
7324            {
7325              stats: perfStats,
7326              onClose: togglePerfHud
7327            }
7328          ),
7329          perfStats.isLagging && !perfStats.lowPerf && !dismissedLag && !showPerfHud && /* @__PURE__ */ jsxRuntimeExports.jsx(
7330            PerformanceLagBanner,
7331            {
7332              fps: perfStats.fps,
7333              onEnableLowPerf: perfStats.toggleLowPerf,
7334              onDismiss: () => setDismissedLag(true)
7335            }
7336          )
7337        ] }),
7338        (viewMode === "score" || viewMode === "melody") && pageCount > 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: getBottomBarClass(), children: [
7339          /* @__PURE__ */ jsxRuntimeExports.jsx(
7340            "button",
7341            {
7342              onClick: () => {
7343                const newPage = Math.max(1, currentPage - 1);
7344                if (onSeek) {
7345                  setCurrentPage(newPage);
7346                } else {
7347                  setCurrentPage(newPage);
7348                }
7349              },
7350              disabled: currentPage === 1,
7351              className: getBottomButtonClass(),
7352              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { size: 20 })
7353            }
7354          ),
7355          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: getBottomTextClass(), children: [
7356            "Page ",
7357            currentPage,
7358            " / ",
7359            pageCount
7360          ] }),
7361          /* @__PURE__ */ jsxRuntimeExports.jsx(
7362            "button",
7363            {
7364              onClick: () => {
7365                const newPage = Math.min(pageCount, currentPage + 1);
7366                if (onSeek) {
7367                  setCurrentPage(newPage);
7368                } else {
7369                  setCurrentPage(newPage);
7370                }
7371              },
7372              disabled: currentPage === pageCount,
7373              className: getBottomButtonClass(),
7374              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 20 })
7375            }
7376          )
7377        ] })
7378      ]
7379    }
7380  );
7381};
7382const RotaryKnob = ({
7383  value,
7384  onChange,
7385  min = -1,
7386  max = 1,
7387  label,
7388  accentColor = "#4ee2e2",
7389  minLabel = "L",
7390  maxLabel = "R"
7391}) => {
7392  const ratio = (value - min) / (max - min);
7393  const angle = -135 + ratio * 270;
7394  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center select-none relative group w-14", children: [
7395    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[7.5px] font-extrabold text-zinc-400 tracking-wider mb-0.5 uppercase", children: label }),
7396    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-10 h-10 flex items-center justify-center cursor-pointer", children: [
7397      /* @__PURE__ */ jsxRuntimeExports.jsx(
7398        "div",
7399        {
7400          className: "absolute w-10 h-10 rounded-full border border-zinc-700 shadow-md",
7401          style: {
7402            background: "radial-gradient(circle, #a3a3a3 0%, #666666 100%)",
7403            boxShadow: "inset 0 1px 0 rgba(255,255,255,0.4), 0 2px 4px rgba(0,0,0,0.4)"
7404          }
7405        }
7406      ),
7407      /* @__PURE__ */ jsxRuntimeExports.jsx(
7408        "div",
7409        {
7410          className: "absolute w-7 h-7 rounded-full flex items-center justify-center border border-zinc-800",
7411          style: {
7412            transform: `rotate(${angle}deg)`,
7413            background: `radial-gradient(circle, ${accentColor} 0%, #112d2d 100%)`,
7414            boxShadow: "inset 0 1px 0 rgba(255,255,255,0.3), 0 1px 3px rgba(0,0,0,0.6)"
7415          },
7416          children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[1.5px] h-3 bg-white absolute top-0 rounded-full shadow-[0_0_2px_rgba(0,0,0,0.8)]" })
7417        }
7418      ),
7419      /* @__PURE__ */ jsxRuntimeExports.jsx(
7420        "input",
7421        {
7422          type: "range",
7423          min,
7424          max,
7425          step: "0.05",
7426          value,
7427          onClick: (e) => e.stopPropagation(),
7428          onDoubleClick: (e) => {
7429            e.stopPropagation();
7430            onChange(0);
7431          },
7432          onChange: (e) => onChange(parseFloat(e.target.value)),
7433          className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
7434        }
7435      )
7436    ] }),
7437    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between w-11 text-[7px] font-bold text-zinc-400 mt-0.5 leading-none", children: [
7438      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: minLabel }),
7439      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: maxLabel })
7440    ] })
7441  ] });
7442};
7443const LedButton = ({
7444  label,
7445  active,
7446  onClick,
7447  ledColor = "#fbbf24"
7448}) => {
7449  return /* @__PURE__ */ jsxRuntimeExports.jsx(
7450    "button",
7451    {
7452      onClick: (e) => {
7453        e.stopPropagation();
7454        onClick();
7455      },
7456      className: "w-full h-5 flex items-center px-1.5 py-0.5 rounded border border-zinc-950 text-[8px] font-extrabold uppercase tracking-wider text-zinc-300 transition-all select-none",
7457      style: {
7458        background: "linear-gradient(to bottom, #444444 0%, #292929 100%)",
7459        boxShadow: active ? "inset 0 2px 3px rgba(0,0,0,0.9), 0 1px 0 rgba(255,255,255,0.05)" : "inset 0 1px 0 rgba(255,255,255,0.1), 0 2px 3px rgba(0,0,0,0.5)"
7460      },
7461      children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 w-full", children: [
7462        /* @__PURE__ */ jsxRuntimeExports.jsx(
7463          "div",
7464          {
7465            className: "w-2 h-2 rounded-full border border-zinc-950 transition-all",
7466            style: {
7467              backgroundColor: active ? ledColor : "#1a1a1a",
7468              boxShadow: active ? `0 0 5px 1px ${ledColor}, inset 0 1px 1px rgba(255,255,255,0.4)` : "inset 0 1px 1px rgba(0,0,0,0.8)"
7469            }
7470          }
7471        ),
7472        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "leading-none", children: label })
7473      ] })
7474    }
7475  );
7476};
7477const getVuSegmentClass = (segmentNum, lit) => {
7478  const base = "w-1.5 h-1.5 rounded-full transition-all duration-75";
7479  if (lit) {
7480    if (segmentNum > 11) {
7481      return `${base} bg-rose-500 shadow-[0_0_5px_1px_rgba(244,63,94,0.8)]`;
7482    } else if (segmentNum > 9) {
7483      return `${base} bg-amber-400 shadow-[0_0_5px_1px_rgba(245,158,11,0.8)]`;
7484    } else {
7485      return `${base} bg-emerald-500 shadow-[0_0_5px_1px_rgba(16,185,129,0.8)]`;
7486    }
7487  } else {
7488    if (segmentNum > 11) {
7489      return `${base} bg-rose-950/45 border border-rose-900/10`;
7490    } else if (segmentNum > 9) {
7491      return `${base} bg-amber-950/45 border border-amber-900/10`;
7492    } else {
7493      return `${base} bg-emerald-950/45 border border-emerald-900/10`;
7494    }
7495  }
7496};
7497const SegmentedVuMeter = ({
7498  idx,
7499  vuMeterRefs
7500}) => {
7501  const segments = Array.from({ length: 14 });
7502  return /* @__PURE__ */ jsxRuntimeExports.jsx(
7503    "div",
7504    {
7505      className: "w-4 h-56 bg-zinc-950 border border-zinc-900 rounded-full relative flex flex-col justify-between items-center py-2 px-[3px]",
7506      style: {
7507        boxShadow: "inset 0 1.5px 3px rgba(0,0,0,0.85)"
7508      },
7509      children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7510        "div",
7511        {
7512          ref: (el) => {
7513            vuMeterRefs.current[idx] = el;
7514          },
7515          className: "absolute inset-[2px] flex flex-col justify-between items-center origin-bottom h-[calc(100%-4px)]",
7516          children: segments.map((_, sIdx) => {
7517            const segmentNum = 13 - sIdx;
7518            return /* @__PURE__ */ jsxRuntimeExports.jsx(
7519              "div",
7520              {
7521                "data-segment": segmentNum,
7522                className: getVuSegmentClass(segmentNum, false)
7523              },
7524              sIdx
7525            );
7526          })
7527        }
7528      )
7529    }
7530  );
7531};
7532const MasterSegmentedVuMeter = ({
7533  masterVuRef
7534}) => {
7535  const segments = Array.from({ length: 14 });
7536  return /* @__PURE__ */ jsxRuntimeExports.jsx(
7537    "div",
7538    {
7539      className: "w-4 h-56 bg-zinc-950 border border-zinc-900 rounded-full relative flex flex-col justify-between items-center py-2 px-[3px]",
7540      style: {
7541        boxShadow: "inset 0 1.5px 3px rgba(0,0,0,0.85)"
7542      },
7543      children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7544        "div",
7545        {
7546          ref: masterVuRef,
7547          className: "absolute inset-[2px] flex flex-col justify-between items-center origin-bottom h-[calc(100%-4px)]",
7548          children: segments.map((_, sIdx) => {
7549            const segmentNum = 13 - sIdx;
7550            return /* @__PURE__ */ jsxRuntimeExports.jsx(
7551              "div",
7552              {
7553                "data-segment": segmentNum,
7554                className: getVuSegmentClass(segmentNum, false)
7555              },
7556              sIdx
7557            );
7558          })
7559        }
7560      )
7561    }
7562  );
7563};
7564const copyStyles = (sourceDoc, targetDoc) => {
7565  Array.from(sourceDoc.querySelectorAll('link[rel="stylesheet"]')).forEach((link) => {
7566    const newLink = targetDoc.createElement("link");
7567    newLink.rel = "stylesheet";
7568    newLink.href = link.href;
7569    targetDoc.head.appendChild(newLink);
7570  });
7571  Array.from(sourceDoc.querySelectorAll("style")).forEach((style) => {
7572    const newStyle = targetDoc.createElement("style");
7573    newStyle.textContent = style.textContent;
7574    try {
7575      if (style.sheet) {
7576        const rules = Array.from(style.sheet.cssRules).map((r) => r.cssText).join("\n");
7577        newStyle.textContent = rules;
7578      }
7579    } catch (e) {
7580      console.warn("Could not copy stylesheet rules", e);
7581    }
7582    targetDoc.head.appendChild(newStyle);
7583  });
7584  Array.from(sourceDoc.styleSheets).forEach((sheet) => {
7585    try {
7586      if (sheet.cssRules && !Array.from(sourceDoc.querySelectorAll("style")).some((s) => s.sheet === sheet)) {
7587        const newStyle = targetDoc.createElement("style");
7588        const rules = Array.from(sheet.cssRules).map((r) => r.cssText).join("\n");
7589        newStyle.textContent = rules;
7590        targetDoc.head.appendChild(newStyle);
7591      }
7592    } catch (e) {
7593      if (sheet.href) {
7594        const newLink = targetDoc.createElement("link");
7595        newLink.rel = "stylesheet";
7596        newLink.href = sheet.href;
7597        targetDoc.head.appendChild(newLink);
7598      }
7599    }
7600  });
7601};
7602const InstrumentSelector = ({
7603  currentProgram,
7604  channel,
7605  onChange,
7606  variant = "console",
7607  theme = "dark",
7608  size = "md"
7609}) => {
7610  const [isOpen, setIsOpen] = reactExports.useState(false);
7611  const [expandedGroup, setExpandedGroup] = reactExports.useState(null);
7612  const [menuStyle, setMenuStyle] = reactExports.useState({});
7613  const buttonRef = reactExports.useRef(null);
7614  const menuRef = reactExports.useRef(null);
7615  const { t } = useTranslation();
7616  const scrollToSelected = reactExports.useCallback(() => {
7617    if (!menuRef.current) return;
7618    const selectedEl = menuRef.current.querySelector('[data-selected="true"]');
7619    if (selectedEl) {
7620      const menu = menuRef.current;
7621      const menuRect = menu.getBoundingClientRect();
7622      const itemRect = selectedEl.getBoundingClientRect();
7623      const offset = itemRect.top - menuRect.top - menuRect.height / 2 + itemRect.height / 2;
7624      menu.scrollTop += offset;
7625    }
7626  }, []);
7627  const updatePosition = reactExports.useCallback(() => {
7628    if (!buttonRef.current) return;
7629    const rect = buttonRef.current.getBoundingClientRect();
7630    const spaceBelow = window.innerHeight - rect.bottom;
7631    const spaceAbove = rect.top;
7632    const menuWidth = Math.min(window.innerWidth - 20, Math.max(rect.width, 270));
7633    let left = rect.left;
7634    if (left + menuWidth > window.innerWidth - 10) {
7635      left = Math.max(10, window.innerWidth - menuWidth - 10);
7636    }
7637    if (spaceBelow >= 200 || spaceBelow > spaceAbove) {
7638      setMenuStyle({
7639        position: "fixed",
7640        top: rect.bottom + 4,
7641        left,
7642        width: menuWidth,
7643        maxHeight: Math.min(320, spaceBelow - 16),
7644        zIndex: 99999
7645      });
7646    } else {
7647      setMenuStyle({
7648        position: "fixed",
7649        bottom: window.innerHeight - rect.top + 4,
7650        left,
7651        width: menuWidth,
7652        maxHeight: Math.min(320, spaceAbove - 16),
7653        zIndex: 99999
7654      });
7655    }
7656  }, []);
7657  reactExports.useEffect(() => {
7658    if (!isOpen) return;
7659    const raf = requestAnimationFrame(() => {
7660      scrollToSelected();
7661    });
7662    const timer = setTimeout(scrollToSelected, 50);
7663    const handleClickOutside = (event) => {
7664      const target = event.target;
7665      if (buttonRef.current && !buttonRef.current.contains(target) && (!menuRef.current || !menuRef.current.contains(target))) {
7666        setIsOpen(false);
7667      }
7668    };
7669    const handleScroll = (e) => {
7670      if (menuRef.current && menuRef.current.contains(e.target)) {
7671        return;
7672      }
7673      updatePosition();
7674    };
7675    document.addEventListener("mousedown", handleClickOutside);
7676    window.addEventListener("scroll", handleScroll, true);
7677    window.addEventListener("resize", updatePosition);
7678    return () => {
7679      cancelAnimationFrame(raf);
7680      clearTimeout(timer);
7681      document.removeEventListener("mousedown", handleClickOutside);
7682      window.removeEventListener("scroll", handleScroll, true);
7683      window.removeEventListener("resize", updatePosition);
7684    };
7685  }, [isOpen, expandedGroup, scrollToSelected, updatePosition]);
7686  const isPercussion = channel === 9;
7687  const bgClass = variant === "expand" ? "bg-[#3b0b0b] border-[#5f1313] hover:bg-[#4a0d0d] text-[#ff7b7b] shadow-[inset_0_1.5px_3px_rgba(0,0,0,0.8)]" : variant === "console" ? theme === "light" ? "bg-white border-zinc-200 hover:bg-zinc-50 text-zinc-700 shadow-sm" : theme === "retro" ? "bg-[#1a1a1a] border-[#3a3a3a] hover:bg-[#2a2a2a] text-[#ffd700]" : theme === "fun" ? "bg-white border-pink-200 hover:bg-pink-50 text-pink-800" : "bg-zinc-900/80 border-white/10 hover:bg-zinc-800 text-zinc-300" : theme === "light" ? "bg-transparent border-transparent hover:bg-zinc-100 text-zinc-800" : theme === "fun" ? "bg-transparent border-transparent hover:bg-pink-50 text-pink-900" : "bg-transparent border-transparent hover:bg-white/5 text-zinc-300";
7688  const dropdownBgClass = variant === "expand" ? "bg-zinc-900 border-zinc-700 shadow-2xl text-zinc-100" : theme === "light" ? "bg-white border-zinc-200 shadow-2xl text-zinc-800" : theme === "retro" ? "bg-[#1a1a1a] border-[#3a3a3a] shadow-2xl text-[#ffd700]" : theme === "fun" ? "bg-white border-pink-200 shadow-2xl text-pink-900" : "bg-zinc-900 border-zinc-700 shadow-2xl text-zinc-100";
7689  const itemHoverClass = variant === "expand" || theme === "dark" || theme === "retro" ? "hover:bg-indigo-600/30 hover:text-white" : theme === "light" ? "hover:bg-indigo-50 hover:text-indigo-900" : theme === "fun" ? "hover:bg-pink-100 hover:text-pink-950" : "hover:bg-indigo-600/30 hover:text-white";
7690  const selectedTextClass = variant === "expand" || theme === "dark" || theme === "retro" ? "text-indigo-400 font-bold" : "text-indigo-600 font-bold";
7691  const displayTitle = getInstrumentName(currentProgram, channel);
7692  const isDarkOrExpand = variant === "expand" || theme === "dark" || theme === "retro";
7693  const textUnselectedClass = isDarkOrExpand ? "text-zinc-200" : theme === "fun" ? "text-pink-900" : "text-zinc-800";
7694  const pitchNumberClass = isDarkOrExpand ? "text-zinc-400 font-mono" : "text-zinc-400 font-mono";
7695  const categoryHeaderClass = isDarkOrExpand ? "border-zinc-800 text-zinc-200 bg-zinc-950/80 hover:bg-zinc-800/80" : theme === "fun" ? "border-pink-200 text-pink-900 bg-pink-50 hover:bg-pink-100" : "border-zinc-200 text-zinc-800 bg-zinc-100/80 hover:bg-zinc-200/80";
7696  const paddingTextClass = size === "sm" ? "py-0.5 px-1.5 text-[11px]" : "py-1 px-2 text-xs";
7697  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full min-w-0", children: [
7698    /* @__PURE__ */ jsxRuntimeExports.jsxs(
7699      "button",
7700      {
7701        ref: buttonRef,
7702        onClick: (e) => {
7703          e.stopPropagation();
7704          if (isPercussion) return;
7705          if (!isOpen) {
7706            updatePosition();
7707            const cat = INSTRUMENT_CATEGORIES.find((c) => currentProgram >= c.start && currentProgram <= c.end);
7708            setExpandedGroup(cat ? cat.name : null);
7709          }
7710          setIsOpen(!isOpen);
7711        },
7712        disabled: isPercussion,
7713        className: `w-full flex items-center justify-between ${paddingTextClass} font-sans font-medium border rounded ${bgClass} transition-colors ${isPercussion ? "opacity-70 cursor-not-allowed" : "cursor-pointer"}`,
7714        title: isPercussion ? "Percussion channel cannot be changed" : displayTitle,
7715        children: [
7716          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate mr-0.5 min-w-0 flex-1 text-left tracking-tight", children: displayTitle }),
7717          !isPercussion && /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 10, className: "shrink-0 opacity-70 ml-1" })
7718        ]
7719      }
7720    ),
7721    isOpen && !isPercussion && reactDomExports.createPortal(
7722      /* @__PURE__ */ jsxRuntimeExports.jsx(AnimatePresence, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
7723        motion.div,
7724        {
7725          ref: menuRef,
7726          initial: { opacity: 0, scale: 0.95 },
7727          animate: { opacity: 1, scale: 1 },
7728          exit: { opacity: 0, scale: 0.95 },
7729          transition: { duration: 0.12 },
7730          className: `overflow-y-auto border rounded-md custom-scrollbar ${dropdownBgClass}`,
7731          style: menuStyle,
7732          onClick: (e) => e.stopPropagation(),
7733          children: INSTRUMENT_CATEGORIES.map((category) => {
7734            const isExpanded = expandedGroup === category.name;
7735            return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [
7736              /* @__PURE__ */ jsxRuntimeExports.jsxs(
7737                "button",
7738                {
7739                  className: `flex items-center justify-between w-full px-3 py-2 text-xs font-bold text-left border-b transition-colors ${categoryHeaderClass}`,
7740                  onClick: (e) => {
7741                    e.stopPropagation();
7742                    setExpandedGroup(isExpanded ? null : category.name);
7743                  },
7744                  children: [
7745                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: category.name }),
7746                    isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 12, className: "opacity-70" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 12, className: "opacity-70" })
7747                  ]
7748                }
7749              ),
7750              /* @__PURE__ */ jsxRuntimeExports.jsx(AnimatePresence, { children: isExpanded && /* @__PURE__ */ jsxRuntimeExports.jsx(
7751                motion.div,
7752                {
7753                  initial: { height: 0, opacity: 0 },
7754                  animate: { height: "auto", opacity: 1 },
7755                  exit: { height: 0, opacity: 0 },
7756                  transition: { duration: 0.15 },
7757                  className: `overflow-hidden ${isDarkOrExpand ? "bg-zinc-950/40" : theme === "fun" ? "bg-pink-50/20" : "bg-zinc-50/50"}`,
7758                  children: GM_INSTRUMENTS.slice(category.start, category.end + 1).map((instName, idx) => {
7759                    const progNum = category.start + idx;
7760                    const isSelected = progNum === currentProgram;
7761                    const selectedBgClass = isDarkOrExpand ? "bg-indigo-600/25 font-semibold" : "bg-indigo-50 font-semibold";
7762                    return /* @__PURE__ */ jsxRuntimeExports.jsxs(
7763                      "button",
7764                      {
7765                        "data-selected": isSelected ? "true" : void 0,
7766                        onClick: (e) => {
7767                          e.stopPropagation();
7768                          onChange(progNum);
7769                          setIsOpen(false);
7770                        },
7771                        className: `flex items-center w-full px-3.5 py-1.5 text-xs text-left transition-colors ${isSelected ? selectedBgClass : ""} ${itemHoverClass}`,
7772                        children: [
7773                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-7 font-mono text-[10px] shrink-0 ${isSelected ? selectedTextClass : pitchNumberClass}`, children: String(progNum).padStart(3, "0") }),
7774                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `truncate flex-1 ${isSelected ? selectedTextClass : textUnselectedClass}`, children: instName }),
7775                          isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 12, className: `${selectedTextClass} shrink-0 ml-1.5` })
7776                        ]
7777                      },
7778                      progNum
7779                    );
7780                  })
7781                }
7782              ) })
7783            ] }, category.name);
7784          })
7785        },
7786        "instrument-portal-menu"
7787      ) }),
7788      document.body
7789    )
7790  ] });
7791};
7792const MixerPanel = ({
7793  tracks,
7794  uniqueNames,
7795  mutedIndices,
7796  playAlongMutedTracks = /* @__PURE__ */ new Set(),
7797  isMelodyMode = false,
7798  toggleMute,
7799  trackVolumes,
7800  onVolumeChange,
7801  trackPans,
7802  onPanChange,
7803  onSolo,
7804  soloedIndex,
7805  hiddenTracks,
7806  toggleVisibility,
7807  audioMode,
7808  mixerMode,
7809  setMixerMode,
7810  colorMap,
7811  onViewEvents,
7812  isPlaying,
7813  currentTime,
7814  duration,
7815  trackStates = {},
7816  onResetMixer,
7817  selectedTrackIndex,
7818  onSelectTrack,
7819  isGameMode,
7820  midiTrack,
7821  soundfont,
7822  onSoundfontChange,
7823  libraries = [],
7824  currentUser,
7825  preRenderedSf,
7826  isAttached = false,
7827  theme = "dark",
7828  pianoRollData,
7829  onInstrumentChange
7830}) => {
7831  const { t } = useTranslation();
7832  const notesByTrack = React.useMemo(() => {
7833    const map = {};
7834    if (!pianoRollData) return map;
7835    for (const note of pianoRollData) {
7836      const trackIdx = note.trackIdx;
7837      if (trackIdx === void 0) continue;
7838      if (!map[trackIdx]) {
7839        map[trackIdx] = [];
7840      }
7841      map[trackIdx].push({
7842        t: note.t,
7843        e: note.t + note.d
7844      });
7845    }
7846    return map;
7847  }, [pianoRollData]);
7848  const isTrackActiveAtTime = (trackNotes, time) => {
7849    if (!trackNotes || trackNotes.length === 0) return false;
7850    let low = 0;
7851    let high = trackNotes.length;
7852    while (low < high) {
7853      const mid = low + high >> 1;
7854      if (trackNotes[mid].t > time) {
7855        high = mid;
7856      } else {
7857        low = mid + 1;
7858      }
7859    }
7860    for (let i = low - 1; i >= 0; i--) {
7861      const note = trackNotes[i];
7862      if (note.t < time - 30) {
7863        break;
7864      }
7865      if (time >= note.t && time <= note.e) {
7866        return true;
7867      }
7868    }
7869    return false;
7870  };
7871  const isLive = audioMode === "synth" || audioMode === "wasmsynth";
7872  const modeLibraries = reactExports.useMemo(() => {
7873    const isWasmFormat = (id, isSf2) => {
7874      return isSf2 || /\.h?sf[23]$/i.test(id) || /_hsf3bank$/i.test(id);
7875    };
7876    if (audioMode === "wasmsynth") {
7877      return libraries.filter((l) => isWasmFormat(l.id, l.isSf2));
7878    } else if (audioMode === "synth") {
7879      return libraries.filter((l) => !isWasmFormat(l.id, l.isSf2));
7880    }
7881    return libraries;
7882  }, [libraries, audioMode]);
7883  const [showInformational, setShowInformational] = reactExports.useState(false);
7884  const isInformationalTrack = reactExports.useCallback((instr, uName) => {
7885    const rawName = String((instr == null ? void 0 : instr.name) || (instr == null ? void 0 : instr.originalName) || (instr == null ? void 0 : instr.displayName) || "").toLowerCase().trim();
7886    const u = String(uName || "").toLowerCase().trim();
7887    const localized = t("mixer.informationalTrack", "Informational Track").toLowerCase().trim();
7888    return rawName === "informational track" || u === "informational track" || u === localized || (instr == null ? void 0 : instr.midiChannel) === -1 || (instr == null ? void 0 : instr.isInformational) === true;
7889  }, [t]);
7890  reactExports.useMemo(() => {
7891    return tracks.filter((instr, idx) => {
7892      const uName = uniqueNames[idx] || instr.displayName;
7893      return isInformationalTrack(instr, uName);
7894    }).length;
7895  }, [tracks, uniqueNames, isInformationalTrack]);
7896  const [downloadingIndices, setDownloadingIndices] = reactExports.useState(/* @__PURE__ */ new Set());
7897  const [isSfOpen, setIsSfOpen] = reactExports.useState(false);
7898  const sfRef = reactExports.useRef(null);
7899  const [isExpanded, setIsExpanded] = reactExports.useState(false);
7900  const [masterVolume, setMasterVolume] = reactExports.useState(1);
7901  const [masterPan, setMasterPan] = reactExports.useState(0);
7902  const [reverbLevel, setReverbLevel] = reactExports.useState(0.25);
7903  const [roomSize, setRoomSize] = reactExports.useState("Medium Hall");
7904  const [isRoomSizeOpen, setIsRoomSizeOpen] = reactExports.useState(false);
7905  const [isFullscreen, setIsFullscreen] = reactExports.useState(false);
7906  const [popoutWindow, setPopoutWindow] = reactExports.useState(null);
7907  const containerRef = reactExports.useRef(null);
7908  const openPopout = () => {
7909    if (popoutWindow) {
7910      popoutWindow.focus();
7911      return;
7912    }
7913    const width = 1200;
7914    const height = 800;
7915    const left = window.screenX + (window.outerWidth - width) / 2;
7916    const top = window.screenY + (window.outerHeight - height) / 2;
7917    const win = window.open(
7918      "",
7919      "studio-mixer-console",
7920      `width=${width},height=${height},left=${left},top=${top},menubar=no,status=no,toolbar=no,location=no`
7921    );
7922    if (!win) {
7923      alert("Popup blocker prevented opening the mixer window. Please allow popups for this site.");
7924      return;
7925    }
7926    win.document.title = t("watch.mixerConsole", "Studio Console Mixer");
7927    win.document.body.className = "bg-zinc-950 text-white overflow-hidden m-0 p-0 h-screen w-screen";
7928    win.document.body.style.background = "radial-gradient(ellipse at center, #424347 0%, #1e1f22 100%)";
7929    copyStyles(document, win.document);
7930    const handleUnload = () => {
7931      setPopoutWindow(null);
7932    };
7933    win.addEventListener("beforeunload", handleUnload);
7934    setPopoutWindow(win);
7935    setIsExpanded(true);
7936  };
7937  const closePopout = () => {
7938    if (popoutWindow) {
7939      popoutWindow.close();
7940      setPopoutWindow(null);
7941    }
7942  };
7943  reactExports.useEffect(() => {
7944    return () => {
7945      if (popoutWindow) {
7946        popoutWindow.close();
7947      }
7948    };
7949  }, [popoutWindow]);
7950  reactExports.useEffect(() => {
7951    const handleParentUnload = () => {
7952      if (popoutWindow) {
7953        popoutWindow.close();
7954      }
7955    };
7956    window.addEventListener("beforeunload", handleParentUnload);
7957    return () => {
7958      window.removeEventListener("beforeunload", handleParentUnload);
7959    };
7960  }, [popoutWindow]);
7961  reactExports.useEffect(() => {
7962    if (synth && typeof synth.setMasterReverbLevel === "function") {
7963      synth.setMasterReverbLevel(reverbLevel);
7964    }
7965  }, [reverbLevel]);
7966  reactExports.useEffect(() => {
7967    if (synth && typeof synth.setMasterReverbRoomSize === "function") {
7968      synth.setMasterReverbRoomSize(roomSize);
7969    }
7970  }, [roomSize]);
7971  reactExports.useEffect(() => {
7972    const handleFullscreenChange = () => {
7973      setIsFullscreen(!!document.fullscreenElement);
7974    };
7975    document.addEventListener("fullscreenchange", handleFullscreenChange);
7976    return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
7977  }, []);
7978  const analysersRef = reactExports.useRef([]);
7979  const masterAnalyserRef = reactExports.useRef(null);
7980  const vuMeterRefs = reactExports.useRef([]);
7981  const masterVuRef = reactExports.useRef(null);
7982  const toggleMuteAll = () => {
7983    const anyUnmuted = tracks.some((_, idx) => !mutedIndices.has(idx));
7984    tracks.forEach((_, idx) => {
7985      if (anyUnmuted && !mutedIndices.has(idx)) {
7986        toggleMute(idx);
7987      } else if (!anyUnmuted && mutedIndices.has(idx)) {
7988        toggleMute(idx);
7989      }
7990    });
7991  };
7992  reactExports.useEffect(() => {
7993    const handleClickOutside = (e) => {
7994      if (sfRef.current && !sfRef.current.contains(e.target)) {
7995        setIsSfOpen(false);
7996      }
7997    };
7998    document.addEventListener("mousedown", handleClickOutside);
7999    return () => document.removeEventListener("mousedown", handleClickOutside);
8000  }, []);
8001  reactExports.useEffect(() => {
8002    if (isExpanded && mixerMode === "console") {
8003      const originalOverflow = document.body.style.overflow;
8004      document.body.style.overflow = "hidden";
8005      return () => {
8006        document.body.style.overflow = originalOverflow;
8007      };
8008    }
8009  }, [isExpanded, mixerMode]);
8010  reactExports.useEffect(() => {
8011    if (!isExpanded) return;
8012    let animationFrameId;
8013    const audioCtx = synth.getAudioContext();
8014    const setupAnalysers = () => {
8015      const channels = synth.getChannels();
8016      if (analysersRef.current.length !== channels.length) {
8017        analysersRef.current.forEach((ana) => ana == null ? void 0 : ana.disconnect());
8018        analysersRef.current = new Array(channels.length).fill(null);
8019      }
8020      channels.forEach((ch, idx) => {
8021        if (ch && ch.gainNode && !analysersRef.current[idx]) {
8022          try {
8023            const analyser = audioCtx.createAnalyser();
8024            analyser.fftSize = 64;
8025            ch.gainNode.connect(analyser);
8026            analysersRef.current[idx] = analyser;
8027          } catch (e) {
8028            console.warn("Failed to connect analyser to channel", idx, e);
8029          }
8030        }
8031      });
8032      const masterGain = synth.getMasterGain();
8033      if (masterGain && !masterAnalyserRef.current) {
8034        try {
8035          const analyser = audioCtx.createAnalyser();
8036          analyser.fftSize = 64;
8037          masterGain.connect(analyser);
8038          masterAnalyserRef.current = analyser;
8039        } catch (e) {
8040          console.warn("Failed to connect analyser to master gain", e);
8041        }
8042      }
8043    };
8044    const updateMeters = () => {
8045      setupAnalysers();
8046      const channels = synth.getChannels();
8047      const dataArray = new Uint8Array(32);
8048      channels.forEach((ch, idx) => {
8049        const analyser = analysersRef.current[idx];
8050        const element = vuMeterRefs.current[idx];
8051        if (analyser && element) {
8052          analyser.getByteFrequencyData(dataArray);
8053          let sum = 0;
8054          for (let i = 0; i < dataArray.length; i++) {
8055            sum += dataArray[i];
8056          }
8057          const average = sum / dataArray.length;
8058          const percent = Math.min(100, average / 128 * 100);
8059          const segmentElements = element.querySelectorAll("[data-segment]");
8060          if (segmentElements.length > 0) {
8061            const litSegmentsCount = Math.round(percent / 100 * segmentElements.length);
8062            segmentElements.forEach((el) => {
8063              const segmentNum = parseInt(el.getAttribute("data-segment") || "0");
8064              el.className = getVuSegmentClass(segmentNum, segmentNum < litSegmentsCount);
8065            });
8066          } else {
8067            element.style.height = `${percent}%`;
8068          }
8069        } else if (element) {
8070          const segmentElements = element.querySelectorAll("[data-segment]");
8071          if (segmentElements.length > 0) {
8072            segmentElements.forEach((el) => {
8073              const segmentNum = parseInt(el.getAttribute("data-segment") || "0");
8074              el.className = getVuSegmentClass(segmentNum, false);
8075            });
8076          } else {
8077            element.style.height = "0%";
8078          }
8079        }
8080      });
8081      const masterAnalyser = masterAnalyserRef.current;
8082      const masterElement = masterVuRef.current;
8083      if (masterAnalyser && masterElement) {
8084        masterAnalyser.getByteFrequencyData(dataArray);
8085        let sum = 0;
8086        for (let i = 0; i < dataArray.length; i++) {
8087          sum += dataArray[i];
8088        }
8089        const average = sum / dataArray.length;
8090        const percent = Math.min(100, average / 128 * 100);
8091        const segmentElements = masterElement.querySelectorAll("[data-segment]");
8092        if (segmentElements.length > 0) {
8093          const litSegmentsCount = Math.round(percent / 100 * segmentElements.length);
8094          segmentElements.forEach((el) => {
8095            const segmentNum = parseInt(el.getAttribute("data-segment") || "0");
8096            el.className = getVuSegmentClass(segmentNum, segmentNum < litSegmentsCount);
8097          });
8098        } else {
8099          masterElement.style.height = `${percent}%`;
8100        }
8101      } else if (masterElement) {
8102        const segmentElements = masterElement.querySelectorAll("[data-segment]");
8103        if (segmentElements.length > 0) {
8104          segmentElements.forEach((el) => {
8105            const segmentNum = parseInt(el.getAttribute("data-segment") || "0");
8106            el.className = getVuSegmentClass(segmentNum, false);
8107          });
8108        } else {
8109          masterElement.style.height = "0%";
8110        }
8111      }
8112      animationFrameId = requestAnimationFrame(updateMeters);
8113    };
8114    animationFrameId = requestAnimationFrame(updateMeters);
8115    return () => {
8116      var _a;
8117      cancelAnimationFrame(animationFrameId);
8118      analysersRef.current.forEach((ana) => ana == null ? void 0 : ana.disconnect());
8119      analysersRef.current = [];
8120      (_a = masterAnalyserRef.current) == null ? void 0 : _a.disconnect();
8121      masterAnalyserRef.current = null;
8122    };
8123  }, [isExpanded, tracks.length]);
8124  const getPanelBg = () => {
8125    if (theme === "light") {
8126      return isAttached ? "px-3 pb-3 bg-zinc-50 rounded-r-xl border-b border-r border-zinc-200/80 shadow-2xl text-zinc-800" : "flex-col gap-2 text-zinc-800";
8127    }
8128    if (theme === "fun") {
8129      return isAttached ? "px-3 pb-3 bg-[#fff1f2] rounded-r-xl border-b border-r border-pink-200 shadow-2xl text-pink-900" : "flex-col gap-2 text-pink-900";
8130    }
8131    return isAttached ? "px-3 pb-3 bg-[#050505] rounded-r-xl border-b border-r border-white/10 shadow-2xl text-white" : "flex-col gap-2 text-white";
8132  };
8133  const getHeaderBorder = () => {
8134    if (theme === "light") return "flex items-center justify-between border-b border-zinc-200 h-11 mb-4";
8135    if (theme === "fun") return "flex items-center justify-between border-b border-pink-200 h-11 mb-4";
8136    return "flex items-center justify-between border-b border-white/10 h-11 mb-4";
8137  };
8138  const getHeaderTextClass = () => {
8139    if (theme === "light") return "font-bold text-zinc-800 text-sm flex items-center gap-2";
8140    if (theme === "fun") return "font-bold text-pink-900 text-sm flex items-center gap-2";
8141    return "font-bold text-white text-sm flex items-center gap-2";
8142  };
8143  const getSubheaderSeparator = () => {
8144    if (theme === "light") return "h-4 w-px bg-zinc-200 mx-2";
8145    if (theme === "fun") return "h-4 w-px bg-pink-200 mx-2";
8146    return "h-4 w-px bg-white/10 mx-2";
8147  };
8148  const getHeaderButtonClass = () => {
8149    if (theme === "light") return "text-[10px] bg-zinc-200 px-2 py-1 rounded-md text-zinc-600 hover:text-zinc-900 hover:bg-zinc-300 transition-all flex items-center gap-1.5 font-bold uppercase tracking-wider";
8150    if (theme === "fun") return "text-[10px] bg-pink-100 px-2 py-1 rounded-md text-pink-800 hover:text-pink-950 hover:bg-pink-200 transition-all flex items-center gap-1.5 font-bold uppercase tracking-wider border border-pink-200";
8151    return "text-[10px] bg-white/5 px-2 py-1 rounded-md text-zinc-400 hover:text-white hover:bg-white/10 transition-all flex items-center gap-1.5 font-bold uppercase tracking-wider";
8152  };
8153  const getResetButtonClass = () => {
8154    if (theme === "light") return "text-[10px] text-zinc-400 hover:text-red-500 hover:bg-zinc-100 transition-all flex items-center gap-1 px-2 py-1 rounded font-bold uppercase tracking-wider";
8155    if (theme === "fun") return "text-[10px] text-pink-400 hover:text-red-500 hover:bg-pink-100 transition-all flex items-center gap-1 px-2 py-1 rounded font-bold uppercase tracking-wider";
8156    return "text-[10px] text-zinc-500 hover:text-red-400 hover:bg-white/5 transition-all flex items-center gap-1 px-2 py-1 rounded font-bold uppercase tracking-wider";
8157  };
8158  const getTrackButtonClass = (isMutedBtn, isActive, isPlayAlongMute) => {
8159    if (isMutedBtn) {
8160      if (isPlayAlongMute) return "w-full h-7 flex items-center justify-center rounded transition-all bg-orange-500/20 text-orange-400 border border-orange-500/40";
8161      if (isActive) {
8162        if (theme === "light") return "w-full h-7 flex items-center justify-center rounded transition-all bg-red-100 text-red-600 border border-red-200";
8163        return "w-full h-7 flex items-center justify-center rounded transition-all bg-red-500/20 text-red-400 border border-red-500/40";
8164      }
8165      if (theme === "light") return "w-full h-7 flex items-center justify-center rounded transition-all bg-zinc-100 text-zinc-400 hover:bg-zinc-200 hover:text-zinc-800";
8166      return "w-full h-7 flex items-center justify-center rounded transition-all bg-white/5 text-zinc-400 hover:bg-white/10 hover:text-white";
8167    }
8168    if (isActive) {
8169      if (theme === "fun") return "w-full h-7 flex items-center justify-center rounded transition-all bg-pink-500 text-white shadow-lg shadow-pink-500/20";
8170      return "w-full h-7 flex items-center justify-center rounded transition-all bg-amber-500 text-white shadow-lg shadow-amber-500/20";
8171    }
8172    if (theme === "light") return "w-full h-7 flex items-center justify-center rounded transition-all bg-zinc-100 text-zinc-400 hover:bg-zinc-200 hover:text-zinc-800";
8173    if (theme === "fun") return "w-full h-7 flex items-center justify-center rounded transition-all bg-pink-50 text-pink-500 hover:bg-pink-100 hover:text-pink-800 border border-pink-200";
8174    return "w-full h-7 flex items-center justify-center rounded transition-all bg-white/5 text-zinc-400 hover:bg-white/10 hover:text-white";
8175  };
8176  const getVisibilityButtonClass = (isHidden) => {
8177    if (isHidden) {
8178      if (theme === "light") return "w-full h-7 flex items-center justify-center rounded transition-all bg-zinc-200 text-zinc-400";
8179      if (theme === "fun") return "w-full h-7 flex items-center justify-center rounded transition-all bg-pink-100 text-pink-400";
8180      return "w-full h-7 flex items-center justify-center rounded transition-all bg-zinc-800/50 text-zinc-600";
8181    }
8182    if (theme === "light") return "w-full h-7 flex items-center justify-center rounded transition-all bg-indigo-50 text-indigo-600 hover:bg-indigo-100";
8183    if (theme === "fun") return "w-full h-7 flex items-center justify-center rounded transition-all bg-pink-50 text-pink-650 hover:bg-pink-100 border border-pink-200";
8184    return "w-full h-7 flex items-center justify-center rounded transition-all bg-white/5 text-indigo-400 hover:bg-white/10 hover:text-indigo-300";
8185  };
8186  const getDownloadButtonClass = (isDownloading) => {
8187    if (theme === "light") return `w-full h-7 flex items-center justify-center rounded bg-zinc-100 text-zinc-400 hover:bg-zinc-200 hover:text-emerald-600 transition-all ${isDownloading ? "opacity-50 cursor-not-allowed" : ""}`;
8188    if (theme === "fun") return `w-full h-7 flex items-center justify-center rounded bg-pink-50 text-pink-500 hover:bg-pink-100 hover:text-emerald-600 transition-all ${isDownloading ? "opacity-50 cursor-not-allowed" : ""} border border-pink-200`;
8189    return `w-full h-7 flex items-center justify-center rounded bg-white/5 text-zinc-500 hover:bg-white/10 hover:text-emerald-400 transition-all ${isDownloading ? "opacity-50 cursor-not-allowed" : ""}`;
8190  };
8191  const getListModeRowClass = (isSelected, isPlayAlongMute, isMuted) => {
8192    const base = "backdrop-blur-sm border rounded-lg overflow-hidden transition-all relative group cursor-pointer";
8193    let opacityClass = isPlayAlongMute ? "opacity-90" : isMuted ? "opacity-40 grayscale" : "opacity-100";
8194    let borderClass = "";
8195    if (theme === "light") {
8196      borderClass = isSelected ? "border-indigo-500 bg-indigo-50/50 shadow-[0_0_15px_rgba(99,102,241,0.08)]" : isPlayAlongMute ? "border-orange-400 bg-orange-50/30 shadow-[0_0_10px_rgba(249,115,22,0.05)]" : "border-zinc-200 bg-white hover:border-zinc-300 hover:bg-zinc-50/50 text-zinc-700";
8197    } else if (theme === "fun") {
8198      borderClass = isSelected ? "border-pink-500 bg-pink-50/40 shadow-[0_0_15px_rgba(236,72,153,0.15)] text-pink-900" : isPlayAlongMute ? "border-orange-500/30 bg-
8198orange-500/10 shadow-[0_0_10px_rgba(249,115,22,0.1)] text-pink-900" : "border-pink-200 bg-white/60 hover:border-pink-300 hover:bg-white/80 text-pink-900";
8199    } else {
8200      borderClass = isSelected ? "border-indigo-500 bg-indigo-500/5 shadow-[0_0_15px_rgba(99,102,241,0.1)]" : isPlayAlongMute ? "border-orange-500/30 bg-orange-500/5 shadow-[0_0_10px_rgba(249,115,22,0.1)]" : "border-white/5 bg-white/5 hover:border-white/10 hover:bg-white/10";
8201    }
8202    return `${base} ${borderClass} ${opacityClass}`;
8203  };
8204  const getListModeMuteButtonClass = (isPlayAlongMute, isMuted) => {
8205    if (isPlayAlongMute) {
8206      if (theme === "light") return "p-1.5 rounded transition-all text-orange-600 bg-orange-50 border border-orange-200";
8207      return "p-1.5 rounded transition-all text-orange-400 bg-orange-500/10 border border-orange-500/20";
8208    }
8209    if (isGameMode || !isMixingSupported) return "p-1.5 rounded transition-all opacity-30 cursor-not-allowed";
8210    if (isMuted) {
8211      if (theme === "light") return "p-1.5 rounded transition-all text-red-500 bg-red-50 hover:bg-red-100";
8212      if (theme === "fun") return "p-1.5 rounded transition-all text-red-500 bg-red-50 hover:bg-red-100 border border-red-200";
8213      return "p-1.5 rounded transition-all text-red-400 bg-red-400/10 hover:bg-red-400/20";
8214    }
8215    if (theme === "light") return "p-1.5 rounded transition-all text-zinc-400 hover:text-zinc-800 hover:bg-zinc-100";
8216    if (theme === "fun") return "p-1.5 rounded transition-all text-pink-500 hover:text-pink-900 hover:bg-pink-100";
8217    return "p-1.5 rounded transition-all text-zinc-500 hover:text-zinc-200 hover:bg-white/5";
8218  };
8219  const getListModeSoloButtonClass = (isPlayAlongMute, isSoloed) => {
8220    if (isGameMode || !isMixingSupported || isPlayAlongMute) return "p-1.5 rounded transition-all opacity-30 cursor-not-allowed";
8221    if (isSoloed) {
8222      if (theme === "fun") return "p-1.5 rounded transition-all bg-pink-500 text-white shadow-lg";
8223      return "p-1.5 rounded transition-all bg-amber-500 text-white shadow-lg";
8224    }
8225    if (theme === "light") return "p-1.5 rounded transition-all text-zinc-400 hover:text-zinc-800 hover:bg-zinc-100";
8226    if (theme === "fun") return "p-1.5 rounded transition-all text-pink-500 hover:text-pink-900 hover:bg-pink-100";
8227    return "p-1.5 rounded transition-all text-zinc-500 hover:text-amber-400 hover:bg-white/5";
8228  };
8229  const getListModeVisibilityButtonClass = (isHidden) => {
8230    if (theme === "light") {
8231      return `p-1.5 rounded transition-all ${isHidden ? "text-zinc-300 hover:text-zinc-500" : "text-indigo-600 bg-indigo-50 hover:bg-indigo-100"}`;
8232    }
8233    if (theme === "fun") {
8234      return `p-1.5 rounded transition-all ${isHidden ? "text-pink-300 hover:text-pink-500" : "text-pink-600 bg-pink-50 hover:bg-pink-100 border border-pink-200"}`;
8235    }
8236    return `p-1.5 rounded transition-all ${isHidden ? "text-zinc-600 hover:text-zinc-400" : "text-indigo-400 bg-indigo-400/10 hover:bg-indigo-400/20"}`;
8237  };
8238  const getListModeEventsButtonClass = () => {
8239    if (theme === "light") return "p-1.5 text-zinc-400 hover:text-indigo-600 hover:bg-zinc-100 rounded transition-all";
8240    if (theme === "fun") return "p-1.5 text-pink-500 hover:text-pink-900 hover:bg-pink-100 rounded transition-all";
8241    return "p-1.5 text-zinc-500 hover:text-indigo-400 hover:bg-white/5 rounded transition-all";
8242  };
8243  const getListModeDownloadButtonClass = (isDownloading) => {
8244    if (theme === "light") return `p-1.5 text-zinc-400 hover:text-emerald-600 hover:bg-zinc-100 rounded transition-all ${isDownloading ? "opacity-50" : ""}`;
8245    if (theme === "fun") return `p-1.5 text-pink-500 hover:text-emerald-600 hover:bg-pink-100 rounded transition-all ${isDownloading ? "opacity-50" : ""}`;
8246    return `p-1.5 text-zinc-500 hover:text-emerald-400 hover:bg-white/5 rounded transition-all ${isDownloading ? "opacity-50" : ""}`;
8247  };
8248  const getInformationalTracksBtnClass = () => {
8249    if (theme === "light") return "w-full py-2.5 flex items-center justify-center gap-2 text-[10px] font-bold uppercase tracking-widest text-zinc-500 hover:text-zinc-800 transition-all border border-dashed border-zinc-200 rounded-lg hover:border-zinc-300 bg-zinc-100/50";
8250    return "w-full py-2.5 flex items-center justify-center gap-2 text-[10px] font-bold uppercase tracking-widest text-zinc-500 hover:text-zinc-300 transition-all border border-dashed border-white/10 rounded-lg hover:border-white/20 bg-white/5";
8251  };
8252  const getSoundfontPickerContainer = () => {
8253    if (theme === "light") return "flex flex-col gap-2 mt-4 p-3 rounded-xl bg-zinc-100/50 border border-zinc-200";
8254    return "flex flex-col gap-2 mt-4 p-3 rounded-xl bg-white/5 border border-white/5";
8255  };
8256  const getSoundfontButtonClass = () => {
8257    const isSfChangeable = audioMode === "synth" || audioMode === "wasmsynth" || audioMode === "opus";
8258    if (isGameMode || !isSfChangeable) return "opacity-40 cursor-not-allowed bg-black/20 border-white/5 text-zinc-500 w-full flex items-center justify-between border rounded-lg px-3 py-2 text-xs font-bold focus:ring-1 focus:ring-indigo-500 outline-none transition-all shadow-lg";
8259    if (theme === "light") return "bg-white border-zinc-200 text-zinc-700 hover:bg-zinc-50 hover:border-zinc-300 w-full flex items-center justify-between border rounded-lg px-3 py-2 text-xs font-bold focus:ring-1 focus:ring-indigo-500 outline-none transition-all shadow-md";
8260    return "bg-black/40 border-white/5 text-zinc-200 hover:bg-black/60 hover:border-white/10 w-full flex items-center justify-between border rounded-lg px-3 py-2 text-xs font-bold focus:ring-1 focus:ring-indigo-500 outline-none transition-all shadow-lg";
8261  };
8262  const getSoundfontDropdownBg = () => {
8263    if (theme === "light") return "absolute top-full left-0 right-0 mt-2 bg-white border border-zinc-200 rounded-xl shadow-2xl z-[60] overflow-hidden py-1";
8264    return "absolute top-full left-0 right-0 mt-2 bg-zinc-900 border border-white/10 rounded-xl shadow-2xl z-[60] overflow-hidden py-1 backdrop-blur-xl";
8265  };
8266  const getSoundfontDropdownItemClass = (isSelected, restricted) => {
8267    const base = "w-full flex flex-col items-start px-4 py-2.5 text-left transition-all relative";
8268    let state = "";
8269    if (theme === "light") {
8270      state = isSelected ? "bg-indigo-50" : "hover:bg-zinc-50";
8271    } else {
8272      state = isSelected ? "bg-indigo-500/10" : "hover:bg-white/5";
8273    }
8274    const opacity = restricted ? "opacity-40 cursor-not-allowed grayscale" : "";
8275    return `${base} ${state} ${opacity}`;
8276  };
8277  const getSoundfontDropdownItemTitleClass = (isSelected) => {
8278    if (isSelected) return "text-xs font-bold text-indigo-600";
8279    if (theme === "light") return "text-xs font-bold text-zinc-700";
8280    return "text-xs font-bold text-zinc-200";
8281  };
8282  const currentSf = libraries.find((l) => l.id === soundfont) || libraries[0] || { name: "GeneralUser GS (Pre-rendered)" };
8283  const isMixingSupported = audioMode === "synth" || audioMode === "wasmsynth" || audioMode === "opus";
8284  const renderBlockedMixer = () => {
8285    let title = "";
8286    let subtitle = "";
8287    if (audioMode === "opus") {
8288      title = t("mixer.blockedOpusTitle", "Mixer Unavailable in Opus Mode");
8289      subtitle = t("mixer.blockedOpusSub", "Switch to SF or JS Mode to mute, pan, and mix individual instruments.");
8290    } else if (audioMode === "singleopus") {
8291      title = t("mixer.blockedSingleOpusTitle", "Mixer Unavailable in Single Opus Mode");
8292      subtitle = t("mixer.blockedSingleOpusSub", "Muting and soloing individual tracks is not supported for single pre-mixed files.");
8293    } else if (audioMode === "webmidi") {
8294      title = t("mixer.blockedMidiTitle", "Mixer Unavailable in Web MIDI Mode");
8295      subtitle = t("mixer.blockedMidiSub", "Mixer controls are bypassed. Adjust levels directly on your external MIDI hardware.");
8296    } else if (audioMode === "chiptune") {
8297      title = t("mixer.blockedChiptuneTitle", "Mixer Unavailable in Chiptune Mode");
8298      subtitle = t("mixer.blockedChiptuneSub", "Synthesized waves are output directly. Switch to SF or JS Mode to mix individual tracks.");
8299    } else if (audioMode === "fmsynth") {
8300      title = t("mixer.blockedFmTitle", "Mixer Unavailable in FM Synth Mode");
8301      subtitle = t("mixer.blockedFmSub", "FM synthesis waves are output directly. Switch to SF or JS Mode to mix individual tracks.");
8302    } else {
8303      title = t("mixer.blockedTitle", "Mixer Unavailable");
8304      subtitle = t("mixer.blockedSub", "Mixer is not supported in the current playback mode.");
8305    }
8306    let containerClass = "";
8307    let iconClass = "";
8308    let titleClass = "";
8309    let subtitleClass = "";
8310    if (theme === "fun") {
8311      containerClass = "flex flex-row items-center justify-start py-2 px-3 border border-dashed border-pink-200 dark:border-pink-900/40 rounded-lg bg-pink-50/50 dark:bg-pink-950/10 text-left mx-2 mb-2 shrink-0 gap-3";
8312      iconClass = "text-pink-400 dark:text-pink-500 shrink-0";
8313      titleClass = "text-[11px] font-bold text-pink-700 dark:text-pink-300 uppercase tracking-widest";
8314      subtitleClass = "text-[9.5px] text-pink-600/90 dark:text-pink-400 leading-tight";
8315    } else if (theme === "retro") {
8316      containerClass = "flex flex-row items-center justify-start py-2 px-3 border border-dashed border-amber-500/30 rounded-none bg-black/40 text-left mx-2 mb-2 font-mono shrink-0 gap-3";
8317      iconClass = "text-amber-500 shrink-0";
8318      titleClass = "text-[11px] font-bold text-amber-500 uppercase tracking-widest";
8319      subtitleClass = "text-[9.5px] text-amber-600/90 leading-tight";
8320    } else if (theme === "light") {
8321      containerClass = "flex flex-row items-center justify-start py-2 px-3 border border-dashed border-zinc-300 rounded-lg bg-zinc-50 text-left mx-2 mb-2 shrink-0 gap-3";
8322      iconClass = "text-zinc-500 shrink-0";
8323      titleClass = "text-[11px] font-bold text-zinc-700 uppercase tracking-widest";
8324      subtitleClass = "text-[9.5px] text-zinc-600 leading-tight";
8325    } else {
8326      containerClass = "flex flex-row items-center justify-start py-2 px-3 border border-dashed border-white/20 rounded-lg bg-white/5 text-left mx-2 mb-2 shrink-0 gap-3";
8327      iconClass = "text-zinc-400 shrink-0";
8328      titleClass = "text-[11px] font-bold text-zinc-200 uppercase tracking-widest";
8329      subtitleClass = "text-[9.5px] text-zinc-400 leading-tight";
8330    }
8331    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: containerClass, children: [
8332      /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 20, className: iconClass }),
8333      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col min-w-0", children: [
8334        /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: titleClass, children: title }),
8335        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: subtitleClass, children: subtitle })
8336      ] })
8337    ] });
8338  };
8339  const handleStemDownload = async (idx, e) => {
8340    e.stopPropagation();
8341    const instr = tracks[idx];
8342    if (!instr.stemPath && !isLive) return;
8343    setDownloadingIndices((prev) => new Set(prev).add(idx));
8344    try {
8345      let blob = synth.getTrackBlob(idx);
8346      let url;
8347      if (blob) {
8348        url = window.URL.createObjectURL(blob);
8349      } else {
8350        const stemUrl = instr.stemPathHigh || instr.stemPath;
8351        if (!stemUrl) throw new Error("No stem URL available for download");
8352        const res = await fetch(stemUrl);
8353        blob = await res.blob();
8354        url = window.URL.createObjectURL(blob);
8355      }
8356      const uniqueName = uniqueNames[idx] || instr.name;
8357      const filename = midiTrack ? `${midiTrack.artist} - ${midiTrack.title} - ${uniqueName}.opus` : `${uniqueName}.opus`;
8358      const a = document.createElement("a");
8359      a.href = url;
8360      a.download = filename;
8361      document.body.appendChild(a);
8362      a.click();
8363      window.URL.revokeObjectURL(url);
8364      document.body.removeChild(a);
8365    } catch (err) {
8366      console.error("Stem download failed", err);
8367    } finally {
8368      setDownloadingIndices((prev) => {
8369        const next = new Set(prev);
8370        next.delete(idx);
8371        return next;
8372      });
8373    }
8374  };
8375  const renderConsole = () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col w-full h-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-2 pb-4 overflow-x-auto custom-scrollbar w-full h-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-2 min-w-max h-full", children: tracks.map((instr, idx) => {
8376    let uniqueName = uniqueNames[idx] || instr.displayName;
8377    if (!uniqueName) {
8378      uniqueName = instr.name === "Informational Track" ? t("mixer.informationalTrack", "Informational Track") : getInstrumentName(instr.program) || `${t("mixer.track", "Track")} ${idx + 1}`;
8379    }
8380    if (uniqueName === t("mixer.informationalTrack", "Informational Track") || instr.name === "Informational Track" || instr.midiChannel === -1 || instr.notesCount === 0) return null;
8381    const isMuted = mutedIndices.has(idx);
8382    const trackColor = colorMap[uniqueName] || getTrackColor(idx);
8383    const currentVol = trackVolumes[idx] !== void 0 ? trackVolumes[idx] : 1;
8384    const loadState = trackStates[idx];
8385    const isLoading = loadState && !loadState.ready && !loadState.error && instr.stemPath;
8386    const isError = loadState && loadState.error;
8387    const trackDisplay = `${t("mixer.trk", "TRK")} ${idx + 1}`;
8388    const isHidden = hiddenTracks.has(uniqueName);
8389    const isSelected = selectedTrackIndex === idx;
8390    const isPlayAlongMute = playAlongMutedTracks.has(idx);
8391    isPlaying && isTrackActiveAtTime(notesByTrack[idx], currentTime);
8392    return /* @__PURE__ */ jsxRuntimeExports.jsxs(
8393      "div",
8394      {
8395        onClick: () => onSelectTrack(idx),
8396        className: `w-[110px] shrink-0 flex flex-col h-full bg-gradient-to-b from-[#3a3b3f] to-[#242528] border-r border-zinc-950 relative select-none shadow-[inset_-1px_0_0_rgba(255,255,255,0.04)] ${isSelected ? "after:absolute after:inset-0 after:border-2 after:border-teal-500/30 after:pointer-events-none" : ""} ${isMuted ? "opacity-70" : ""}`,
8397        children: [
8398          isLoading && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 z-30 flex flex-col justify-end pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-teal-500/40 transition-all duration-300 w-full backdrop-blur-sm border-t border-teal-300 shadow-[0_0_10px_rgba(20,184,166,0.3)]", style: { height: `${loadState.percent}%` } }) }),
8399          isError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-red-950/60 z-30 flex items-center justify-center backdrop-blur-sm pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 20, className: "text-red-500 animate-pulse" }) }),
8400          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-7 bg-black/40 border-b border-zinc-950 flex flex-col items-center justify-center relative", children: [
8401            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 w-full h-[3px]", style: { backgroundColor: trackColor } }),
8402            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black text-zinc-400 tracking-wider", children: trackDisplay })
8403          ] }),
8404          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-2 mt-2", onClick: (e) => {
8405            e.stopPropagation();
8406            onSelectTrack(idx);
8407          }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
8408            InstrumentSelector,
8409            {
8410              currentProgram: instr.program !== void 0 ? instr.program : 0,
8411              channel: instr.midiChannel ?? instr.channel,
8412              onChange: (prog) => onInstrumentChange && onInstrumentChange(idx, prog),
8413              variant: "expand",
8414              theme
8415            }
8416          ) }),
8417          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-between gap-2 py-3", children: [
8418            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
8419              RotaryKnob,
8420              {
8421                value: trackPans && trackPans[idx] !== void 0 ? trackPans[idx] : 0,
8422                onChange: (val) => onPanChange && onPanChange(idx, val),
8423                label: "PAN",
8424                accentColor: "#2dd4bf"
8425              }
8426            ) }),
8427            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1.5 w-full px-2 shrink-0", children: [
8428              /* @__PURE__ */ jsxRuntimeExports.jsx(
8429                "button",
8430                {
8431                  onClick: (e) => {
8432                    e.stopPropagation();
8433                    toggleMute(idx);
8434                  },
8435                  title: isPlayAlongMute ? "Auto-muted for Play-Along" : isMuted ? t("mixer.unmute", "Unmute") : t("mixer.mute", "Mute"),
8436                  className: getTrackButtonClass(true, isMuted, isPlayAlongMute),
8437                  disabled: isGameMode || !isMixingSupported || isPlayAlongMute,
8438                  children: isPlayAlongMute ? /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { size: 13, className: "animate-pulse text-orange-400" }) : isMuted ? /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Volume2, { size: 13 })
8439                }
8440              ),
8441              /* @__PURE__ */ jsxRuntimeExports.jsx(
8442                "button",
8443                {
8444                  onClick: (e) => {
8445                    e.stopPropagation();
8446                    onSolo(idx);
8447                  },
8448                  title: isPlayAlongMute ? "Disabled during Play-Along" : t("mixer.solo", "Solo Track"),
8449                  className: getTrackButtonClass(false, soloedIndex === idx, isPlayAlongMute),
8450                  disabled: isGameMode || !isMixingSupported || isPlayAlongMute,
8451                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Headphones, { size: 13 })
8452                }
8453              ),
8454              /* @__PURE__ */ jsxRuntimeExports.jsx(
8455                "button",
8456                {
8457                  onClick: (e) => {
8458                    e.stopPropagation();
8459                    toggleVisibility(idx);
8460                  },
8461                  title: isMelodyMode ? "Cannot hide tracks in Melody Mode" : isHidden ? t("mixer.show", "Show") : t("mixer.hide", "Hide"),
8462                  className: `${getVisibilityButtonClass(isHidden)} ${isMelodyMode ? "opacity-30" : ""}`,
8463                  disabled: isMelodyMode,
8464                  children: isHidden ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 13 })
8465                }
8466              ),
8467              instr.stemPath && /* @__PURE__ */ jsxRuntimeExports.jsx(
8468                "button",
8469                {
8470                  onClick: (e) => handleStemDownload(idx, e),
8471                  disabled: downloadingIndices.has(idx),
8472                  title: t("mixer.downloadTrack", "Download Track Opus"),
8473                  className: getDownloadButtonClass(downloadingIndices.has(idx)),
8474                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { size: 13, className: downloadingIndices.has(idx) ? "animate-bounce" : "" })
8475                }
8476              )
8477            ] }),
8478            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex-1 w-full flex items-center justify-center gap-1.5 px-2 relative ${isGameMode || !isMixingSupported || isPlayAlongMute ? "opacity-30 cursor-not-allowed pointer-events-none" : ""}`, children: [
8479              /* @__PURE__ */ jsxRuntimeExports.jsx(SegmentedVuMeter, { idx, vuMeterRefs }),
8480              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-56 w-11 flex items-center justify-center", children: [
8481                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute w-[3px] h-[216px] bg-zinc-950 rounded border-l border-zinc-900 border-r border-zinc-800 shadow-[inset_0_1px_2px_rgba(0,0,0,0.9)]" }),
8482                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-y-2 left-0.5 flex flex-col justify-between text-[5px] font-mono text-zinc-500/80 pointer-events-none select-none", children: [
8483                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "+6" }),
8484                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "0" }),
8485                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-6" }),
8486                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-12" }),
8487                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-24" }),
8488                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-48" }),
8489                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-∞" })
8490                ] }),
8491                /* @__PURE__ */ jsxRuntimeExports.jsx(
8492                  "input",
8493                  {
8494                    type: "range",
8495                    min: "0",
8496                    max: "1.2",
8497                    step: "0.05",
8498                    value: currentVol,
8499                    onClick: (e) => e.stopPropagation(),
8500                    onDoubleClick: (e) => {
8501                      if (!isGameMode && isMixingSupported && !isPlayAlongMute) {
8502                        e.stopPropagation();
8503                        onVolumeChange(idx, 1);
8504                      }
8505                    },
8506                    onChange: (e) => onVolumeChange(idx, parseFloat(e.target.value)),
8507                    className: "absolute w-56 h-11 opacity-0 cursor-pointer z-20",
8508                    style: {
8509                      transform: "rotate(-90deg)",
8510                      transformOrigin: "center"
8511                    },
8512                    title: isPlayAlongMute ? "Auto-muted for Play-Along" : `${t("mixer.volume", "Volume")}: ${Math.round(currentVol * 100)}%`,
8513                    disabled: isGameMode || !isMixingSupported || isPlayAlongMute
8514                  }
8515                ),
8516                /* @__PURE__ */ jsxRuntimeExports.jsxs(
8517                  "div",
8518                  {
8519                    className: "absolute w-9 h-7 rounded shadow-xl pointer-events-none transition-all duration-75 z-10 flex flex-col items-center justify-center cursor-grab active:cursor-grabbing border-b-2 border-r border-t border-l",
8520                    style: {
8521                      bottom: `calc(${Math.min(100, currentVol / 1.2 * 100)}% - 14px)`,
8522                      background: isPlayAlongMute ? "linear-gradient(to bottom, #ea580c 0%, #c2410c 100%)" : "linear-gradient(to bottom, #e4e4e7 0%, #a1a1aa 100%)",
8523                      borderColor: isPlayAlongMute ? "#7c2d12" : "#52525b",
8524                      boxShadow: "0 4px 6px rgba(0,0,0,0.6), inset 0 1px 1px rgba(255,255,255,0.6)"
8525                    },
8526                    children: [
8527                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-[2px] bg-white shadow-[0_0_2px_rgba(0,0,0,0.3)] mb-0.5" }),
8528                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-px bg-zinc-950/20" })
8529                    ]
8530                  }
8531                )
8532              ] })
8533            ] })
8534          ] })
8535        ]
8536      },
8537      idx
8538    );
8539  }) }) }) });
8540  const renderList = () => {
8541    const informationalTracksCount2 = tracks.filter((instr, idx) => {
8542      let uniqueName = uniqueNames[idx] || instr.displayName;
8543      return isInformationalTrack(instr, uniqueName);
8544    }).length;
8545    const visibleTracksCount = tracks.length - (!showInformational ? informationalTracksCount2 : 0);
8546    const spaceYClass = visibleTracksCount >= 11 ? "space-y-0.5" : visibleTracksCount >= 9 ? "space-y-1" : visibleTracksCount >= 7 ? "space-y-1.5" : "space-y-2";
8547    const rowPaddingClass = visibleTracksCount >= 11 ? "py-0.5" : visibleTracksCount >= 10 ? "py-1" : visibleTracksCount >= 8 ? "py-1.5" : visibleTracksCount >= 7 ? "py-2" : "py-2.5";
8548    const indicatorHeightClass = visibleTracksCount >= 10 ? "h-4" : visibleTracksCount >= 8 ? "h-5" : visibleTracksCount >= 7 ? "h-6" : "h-8";
8549    const instrumentSize = visibleTracksCount >= 8 ? "sm" : "md";
8550    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `${spaceYClass} max-h-[600px] xl:max-h-none overflow-y-auto custom-scrollbar pr-1`, children: [
8551      tracks.map((instr, idx) => {
8552        let uniqueName = uniqueNames[idx] || instr.displayName;
8553        if (!uniqueName) {
8554          uniqueName = instr.name === "Informational Track" ? t("mixer.informationalTrack", "Informational Track") : getInstrumentName(instr.program) || `${t("mixer.track", "Track")} ${idx + 1}`;
8555        }
8556        const isInformational = isInformationalTrack(instr, uniqueName);
8557        if (isInformational && !showInformational) return null;
8558        const isMuted = mutedIndices.has(idx);
8559        const trackColor = colorMap[uniqueName] || getTrackColor(idx);
8560        const isHidden = hiddenTracks.has(uniqueName);
8561        const loadState = trackStates[idx];
8562        const isLoading = loadState && !loadState.ready && !loadState.error && instr.stemPath;
8563        const isSelected = selectedTrackIndex === idx;
8564        const isPlayAlongMute = playAlongMutedTracks.has(idx);
8565        const isNoteActive = isPlaying && isTrackActiveAtTime(notesByTrack[idx], currentTime);
8566        const unlitColor = theme === "light" ? "rgb(228, 228, 231)" : "rgb(39, 39, 42)";
8567        const ledStyle = {
8568          backgroundColor: isNoteActive ? trackColor : unlitColor,
8569          boxShadow: isNoteActive ? `0 0 10px ${trackColor}, 0 0 4px ${trackColor}` : "none",
8570          transition: isNoteActive ? "none" : "background-color 450ms cubic-bezier(0.1, 0.8, 0.25, 1), box-shadow 450ms cubic-bezier(0.1, 0.8, 0.25, 1)"
8571        };
8572        return /* @__PURE__ */ jsxRuntimeExports.jsxs(
8573          "div",
8574          {
8575            onClick: () => !isGameMode && isMixingSupported && onSelectTrack(idx),
8576            className: getListModeRowClass(isSelected, isPlayAlongMute, isMuted),
8577            children: [
8578              isLoading && !isInformational && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/60 z-30 flex items-center justify-center backdrop-blur-[2px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-3/4", children: [
8579                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-white font-bold mb-1 uppercase tracking-wider", children: [
8580                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Loading" }),
8581                  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
8582                    Math.round(loadState.percent),
8583                    "%"
8584                  ] })
8585                ] }),
8586                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-1 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full bg-indigo-500 transition-all duration-300", style: { width: `${loadState.percent}%` } }) })
8587              ] }) }),
8588              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center justify-between px-2.5 ${rowPaddingClass} min-w-0`, children: [
8589                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1 pr-1", children: [
8590                  /* @__PURE__ */ jsxRuntimeExports.jsx(
8591                    "div",
8592                    {
8593                      className: `w-1.5 ${indicatorHeightClass} rounded-full shrink-0 shadow-[0_0_8px_rgba(255,255,255,0.1)]`,
8594                      style: { backgroundColor: isInformational ? "#4b5563" : isPlayAlongMute ? "#f97316" : trackColor },
8595                      title: isInformational ? t("mixer.informationalTrack", "Informational Track") : `Track Color: ${trackColor}`
8596                    }
8597                  ),
8598                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 min-w-0 flex-1", children: [
8599                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-mono font-bold text-zinc-400 dark:text-zinc-500 whitespace-nowrap flex items-center gap-1 shrink-0", title: `${t("mixer.track", "Track")} ${idx + 1}`, children: [
8600                      "#",
8601                      idx + 1,
8602                      /* @__PURE__ */ jsxRuntimeExports.jsx(
8603                        "div",
8604                        {
8605                          className: "w-1.5 h-1.5 rounded-full shrink-0",
8606                          style: ledStyle,
8607                          title: "Signal activity indicator"
8608                        }
8609                      )
8610                    ] }),
8611                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center min-w-0 flex-1", children: isInformational ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[12px] font-medium text-zinc-500 truncate min-w-0 font-sans ml-1`, children: t("mixer.informationalTrack", "Informational Track") }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
8612                      InstrumentSelector,
8613                      {
8614                        currentProgram: instr.program !== void 0 ? instr.program : 0,
8615                        channel: instr.midiChannel,
8616                        onChange: (prog) => onInstrumentChange && onInstrumentChange(idx, prog),
8617                        variant: "list",
8618                        theme,
8619                        size: instrumentSize
8620                      }
8621                    ) })
8622                  ] }) })
8623                ] }),
8624                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-0.5 shrink-0 ml-1", children: [
8625                  !isInformational && /* @__PURE__ */ jsxRuntimeExports.jsx(
8626                    "button",
8627                    {
8628                      onClick: (e) => {
8629                        e.stopPropagation();
8630                        toggleVisibility(idx);
8631                      },
8632                      title: isMelodyMode ? "Cannot hide tracks in Melody Mode" : isHidden ? t("mixer.show", "Show") : t("mixer.hide", "Hide"),
8633                      className: `${getListModeVisibilityButtonClass(isHidden)} ${isMelodyMode ? "opacity-30 cursor-not-allowed" : ""}`,
8634                      disabled: isMelodyMode,
8635                      children: isHidden ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 14 })
8636                    }
8637                  ),
8638                  /* @__PURE__ */ jsxRuntimeExports.jsx(
8639                    "button",
8640                    {
8641                      onClick: (e) => {
8642                        e.stopPropagation();
8643                        onViewEvents(instr);
8644                      },
8645                      title: t("mixer.events", "View Events"),
8646                      className: getListModeEventsButtonClass(),
8647                      children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 14 })
8648                    }
8649                  ),
8650                  instr.stemPath && /* @__PURE__ */ jsxRuntimeExports.jsx(
8651                    "button",
8652                    {
8653                      onClick: (e) => handleStemDownload(idx, e),
8654                      disabled: downloadingIndices.has(idx),
8655                      title: t("mixer.downloadTrack", "Download Track Opus"),
8656                      className: getListModeDownloadButtonClass(downloadingIndices.has(idx)),
8657                      children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { size: 14, className: downloadingIndices.has(idx) ? "animate-bounce" : "" })
8658                    }
8659                  ),
8660                  !isInformational && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
8661                    /* @__PURE__ */ jsxRuntimeExports.jsx(
8662                      "button",
8663                      {
8664                        onClick: (e) => {
8665                          e.stopPropagation();
8666                          onSolo(idx);
8667                        },
8668                        title: isPlayAlongMute ? "Disabled during Play-Along" : t("mixer.solo", "Solo Track"),
8669                        className: getListModeSoloButtonClass(isPlayAlongMute, soloedIndex === idx),
8670                        disabled: isGameMode || !isMixingSupported || isPlayAlongMute,
8671                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Headphones, { size: 14 })
8672                      }
8673                    ),
8674                    /* @__PURE__ */ jsxRuntimeExports.jsx(
8675                      "button",
8676                      {
8677                        onClick: (e) => {
8678                          e.stopPropagation();
8679                          toggleMute(idx);
8680                        },
8681                        title: isPlayAlongMute ? "Auto-muted for Play-Along" : isMuted ? t("mixer.unmute", "Unmute") : t("mixer.mute", "Mute"),
8682                        className: getListModeMuteButtonClass(isPlayAlongMute, isMuted),
8683                        disabled: isGameMode || !isMixingSupported || isPlayAlongMute,
8684                        children: isPlayAlongMute ? /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { size: 14, className: "animate-pulse text-orange-400" }) : isMuted ? /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Volume2, { size: 14 })
8685                      }
8686                    )
8687                  ] })
8688                ] })
8689              ] })
8690            ]
8691          },
8692          idx
8693        );
8694      }),
8695      informationalTracksCount2 > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
8696        "button",
8697        {
8698          onClick: () => setShowInformational(!showInformational),
8699          className: getInformationalTracksBtnClass(),
8700          children: showInformational ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
8701            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 }),
8702            t("mixer.hideInformationalTracks", "Hide Informational Tracks")
8703          ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
8704            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 14 }),
8705            t("mixer.showInformationalTracks", { count: informationalTracksCount2, defaultValue: `Show ${informationalTracksCount2} Informational Track${informationalTracksCount2 !== 1 ? "s" : ""}` })
8706          ] })
8707        }
8708      )
8709    ] });
8710  };
8711  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
8712    isExpanded && mixerMode === "console" && typeof document !== "undefined" && reactDomExports.createPortal(
8713      /* @__PURE__ */ jsxRuntimeExports.jsxs(
8714        "div",
8715        {
8716          ref: containerRef,
8717          className: popoutWindow ? "fixed inset-0 z-[9999] flex flex-col text-white overflow-hidden" : `fixed top-0 left-0 right-0 ${isFullscreen ? "rounded-none" : "rounded-t-xl"} z-[9999] flex flex-col text-white animate-in fade-in zoom-in-95 overflow-hidden border border-zinc-800 shadow-2xl`,
8718          style: {
8719            background: "radial-gradient(ellipse at center, #424347 0%, #1e1f22 100%)",
8720            bottom: isFullscreen || popoutWindow ? "0px" : "5rem"
8721          },
8722          children: [
8723            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `h-10 bg-gradient-to-r from-zinc-800 to-zinc-900 border-b border-zinc-950 flex items-center justify-between px-4 select-none shrink-0 ${isFullscreen || popoutWindow ? "" : "rounded-t-xl"}`, children: [
8724              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 w-24", children: [
8725                /* @__PURE__ */ jsxRuntimeExports.jsx(
8726                  "button",
8727                  {
8728                    onClick: (e) => {
8729                      e.stopPropagation();
8730                      if (popoutWindow) {
8731                        closePopout();
8732                      } else {
8733                        if (document.fullscreenElement && document.exitFullscreen) {
8734                          document.exitFullscreen();
8735                        }
8736                        setIsExpanded(false);
8737                      }
8738                    },
8739                    className: "w-3 h-3 rounded-full bg-rose-500 border border-rose-600/50 hover:bg-rose-600 transition-colors flex items-center justify-center group relative shadow-[inset_0_1px_1px_rgba(255,255,255,0.2)] cursor-pointer",
8740                    title: popoutWindow ? t("mixer.closeWindow", "Close Window") : t("mixer.minimize", "Minimize"),
8741                    children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden group-hover:block text-[8px] text-rose-950 font-bold leading-none -mt-[1px]", children: "×" })
8742                  }
8743                ),
8744                /* @__PURE__ */ jsxRuntimeExports.jsx(
8745                  "button",
8746                  {
8747                    disabled: !isFullscreen && !popoutWindow,
8748                    onClick: (e) => {
8749                      e.stopPropagation();
8750                      if (popoutWindow) {
8751                        closePopout();
8752                      } else if (isFullscreen && document.exitFullscreen) {
8753                        document.exitFullscreen();
8754                      }
8755                    },
8756                    className: `w-3 h-3 rounded-full bg-amber-500 border border-amber-600/50 shadow-[inset_0_1px_1px_rgba(255,255,255,0.2)] transition-all ${isFullscreen || popoutWindow ? "cursor-pointer hover:bg-amber-600" : "opacity-40 cursor-not-allowed"}`,
8757                    title: popoutWindow ? t("mixer.reattach", "Reattach") : isFullscreen ? t("mixer.exitFullscreen", "Exit Fullscreen") : ""
8758                  }
8759                ),
8760                /* @__PURE__ */ jsxRuntimeExports.jsx(
8761                  "button",
8762                  {
8763                    onClick: (e) => {
8764                      var _a, _b, _c;
8765                      e.stopPropagation();
8766                      if (popoutWindow) {
8767                        const doc = popoutWindow.document;
8768                        const el = doc.body;
8769                        if (!doc.fullscreenElement) {
8770                          (_a = el.requestFullscreen) == null ? void 0 : _a.call(el);
8771                        } else {
8772                          (_b = doc.exitFullscreen) == null ? void 0 : _b.call(doc);
8773                        }
8774                      } else {
8775                        if (!isFullscreen) {
8776                          if ((_c = containerRef.current) == null ? void 0 : _c.requestFullscreen) {
8777                            containerRef.current.requestFullscreen();
8778                          }
8779                        } else {
8780                          if (document.exitFullscreen) {
8781                            document.exitFullscreen();
8782                          }
8783                        }
8784                      }
8785                    },
8786                    className: "w-3 h-3 rounded-full bg-emerald-500 border border-emerald-600/50 hover:bg-emerald-600 transition-colors flex items-center justify-center cursor-pointer shadow-[inset_0_1px_1px_rgba(255,255,255,0.2)]",
8787                    title: t("mixer.fullscreen", "Fullscreen")
8788                  }
8789                )
8790              ] }),
8791              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
8792                /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 14, className: "text-teal-400 animate-pulse" }),
8793                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-extrabold uppercase tracking-[0.25em] text-zinc-300 drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)]", children: t("watch.mixerConsole", "Studio Console Mixer") })
8794              ] }),
8795              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-2 justify-end items-center shrink-0", children: popoutWindow ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
8796                "button",
8797                {
8798                  onClick: (e) => {
8799                    e.stopPropagation();
8800                    closePopout();
8801                  },
8802                  className: "text-[9px] bg-zinc-950/40 border border-zinc-800 hover:border-zinc-700 px-2.5 py-1 rounded text-zinc-400 hover:text-white transition-all flex items-center gap-1 font-bold uppercase tracking-wider shadow-md",
8803                  children: [
8804                    /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize2, { size: 10 }),
8805                    t("mixer.reattachShort", "Reattach")
8806                  ]
8807                }
8808              ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
8809                /* @__PURE__ */ jsxRuntimeExports.jsxs(
8810                  "button",
8811                  {
8812                    onClick: (e) => {
8813                      e.stopPropagation();
8814                      openPopout();
8815                    },
8816                    className: "text-[9px] bg-zinc-950/40 border border-zinc-800 hover:border-zinc-700 px-2.5 py-1 rounded text-zinc-400 hover:text-white transition-all flex items-center gap-1 font-bold uppercase tracking-wider shadow-md",
8817                    title: t("mixer.popout", "Show in separate window"),
8818                    children: [
8819                      /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 10 }),
8820                      t("mixer.popoutShort", "Pop Out")
8821                    ]
8822                  }
8823                ),
8824                /* @__PURE__ */ jsxRuntimeExports.jsxs(
8825                  "button",
8826                  {
8827                    onClick: (e) => {
8828                      e.stopPropagation();
8829                      if (document.fullscreenElement && document.exitFullscreen) {
8830                        document.exitFullscreen();
8831                      }
8832                      setIsExpanded(false);
8833                    },
8834                    className: "text-[9px] bg-zinc-950/40 border border-zinc-800 hover:border-zinc-700 px-2.5 py-1 rounded text-zinc-400 hover:text-white transition-all flex items-center gap-1 font-bold uppercase tracking-wider shadow-md",
8835                    children: [
8836                      /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize2, { size: 10 }),
8837                      t("mixer.minimizeShort", "Minimize")
8838                    ]
8839                  }
8840                )
8841              ] }) })
8842            ] }),
8843            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex gap-0 min-h-0 overflow-hidden w-full items-stretch justify-start p-6", children: [
8844              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex gap-0 overflow-x-auto overflow-y-hidden custom-scrollbar pb-2 items-stretch bg-zinc-950/25 border border-zinc-900 rounded-lg shadow-[inset_0_2px_8px_rgba(0,0,0,0.8)]", children: tracks.map((instr, idx) => {
8845                let uniqueName = uniqueNames[idx] || instr.displayName;
8846                if (!uniqueName) {
8847                  uniqueName = instr.name === "Informational Track" ? t("mixer.informationalTrack", "Informational Track") : getInstrumentName(instr.program) || `${t("mixer.track", "Track")} ${idx + 1}`;
8848                }
8849                if (uniqueName === t("mixer.informationalTrack", "Informational Track") || instr.name === "Informational Track" || instr.midiChannel === -1 || instr.notesCount === 0) return null;
8850                const isMuted = mutedIndices.has(idx);
8851                const trackColor = colorMap[uniqueName] || getTrackColor(idx);
8852                const currentVol = trackVolumes[idx] !== void 0 ? trackVolumes[idx] : 1;
8853                const loadState = trackStates[idx];
8854                const isLoading = loadState && !loadState.ready && !loadState.error && instr.stemPath;
8855                const isError = loadState && loadState.error;
8856                const trackDisplay = `${t("mixer.trk", "TRK")} ${idx + 1}`;
8857                const isHidden = hiddenTracks.has(uniqueName);
8858                const isSelected = selectedTrackIndex === idx;
8859                const isPlayAlongMute = playAlongMutedTracks.has(idx);
8860                const faderBg = isPlayAlongMute ? "linear-gradient(to bottom, #f97316 0%, #ea580c 100%)" : "linear-gradient(to bottom, #2dd4bf 0%, #0d9488 100%)";
8861                return /* @__PURE__ */ jsxRuntimeExports.jsxs(
8862                  "div",
8863                  {
8864                    onClick: () => onSelectTrack(idx),
8865                    className: `w-[110px] shrink-0 flex flex-col h-full bg-gradient-to-b from-[#3a3b3f] to-[#242528] border-r border-zinc-950 relative select-none shadow-[inset_-1px_0_0_rgba(255,255,255,0.04)] ${isSelected ? "after:absolute after:inset-0 after:border-2 after:border-teal-500/30 after:pointer-events-none" : ""} ${isMuted ? "opacity-70" : ""}`,
8866                    children: [
8867                      isLoading && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 z-30 flex flex-col justify-end pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-teal-500/40 transition-all duration-300 w-full backdrop-blur-sm border-t border-teal-300 shadow-[0_0_10px_rgba(20,184,166,0.3)]", style: { height: `${loadState.percent}%` } }) }),
8868                      isError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-red-950/60 z-30 flex items-center justify-center backdrop-blur-sm pointer-events-none", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 20, className: "text-red-500 animate-pulse" }) }),
8869                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-7 bg-black/40 border-b border-zinc-950 flex flex-col items-center justify-center relative", children: [
8870                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 w-full h-[3px]", style: { backgroundColor: trackColor } }),
8871                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black text-zinc-400 tracking-wider", children: trackDisplay })
8872                      ] }),
8873                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-2 mt-2", onClick: (e) => {
8874                        e.stopPropagation();
8875                        onSelectTrack(idx);
8876                      }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
8877                        InstrumentSelector,
8878                        {
8879                          currentProgram: instr.program !== void 0 ? instr.program : 0,
8880                          channel: instr.midiChannel ?? instr.channel,
8881                          onChange: (prog) => onInstrumentChange && onInstrumentChange(idx, prog),
8882                          variant: "expand",
8883                          theme
8884                        }
8885                      ) }),
8886                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-between gap-2 py-3", children: [
8887                        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
8888                          RotaryKnob,
8889                          {
8890                            value: trackPans && trackPans[idx] !== void 0 ? trackPans[idx] : 0,
8891                            onChange: (val) => onPanChange && onPanChange(idx, val),
8892                            label: "PAN",
8893                            accentColor: "#2dd4bf"
8894                          }
8895                        ) }),
8896                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 w-full px-2 shrink-0", children: [
8897                          /* @__PURE__ */ jsxRuntimeExports.jsx(
8898                            LedButton,
8899                            {
8900                              label: "MUTE",
8901                              active: isMuted,
8902                              onClick: () => toggleMute(idx),
8903                              ledColor: "#ef4444"
8904                            }
8905                          ),
8906                          /* @__PURE__ */ jsxRuntimeExports.jsx(
8907                            LedButton,
8908                            {
8909                              label: "SOLO",
8910                              active: soloedIndex === idx,
8911                              onClick: () => onSolo(idx),
8912                              ledColor: "#fbbf24"
8913                            }
8914                          ),
8915                          /* @__PURE__ */ jsxRuntimeExports.jsx(
8916                            LedButton,
8917                            {
8918                              label: "BYP",
8919                              active: isHidden,
8920                              onClick: () => toggleVisibility(idx),
8921                              ledColor: "#3b82f6"
8922                            }
8923                          )
8924                        ] }),
8925                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex-1 w-full flex items-center justify-center gap-1.5 px-2 relative ${isGameMode || !isMixingSupported || isPlayAlongMute ? "opacity-30 cursor-not-allowed pointer-events-none" : ""}`, children: [
8926                          /* @__PURE__ */ jsxRuntimeExports.jsx(SegmentedVuMeter, { idx, vuMeterRefs }),
8927                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-56 w-11 flex items-center justify-center", children: [
8928                            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute w-[3px] h-[216px] bg-zinc-950 rounded border-l border-zinc-900 border-r border-zinc-800 shadow-[inset_0_1px_2px_rgba(0,0,0,0.9)]" }),
8929                            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-y-2 left-0.5 flex flex-col justify-between text-[5px] font-mono text-zinc-500/80 pointer-events-none select-none", children: [
8930                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "+6" }),
8931                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "0" }),
8932                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-6" }),
8933                              /* @__P
8933URE__ */ jsxRuntimeExports.jsx("span", { children: "-12" }),
8934                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-24" }),
8935                              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-oo" })
8936                            ] }),
8937                            /* @__PURE__ */ jsxRuntimeExports.jsx(
8938                              "input",
8939                              {
8940                                type: "range",
8941                                min: "0",
8942                                max: "1.2",
8943                                step: "0.02",
8944                                value: currentVol,
8945                                onClick: (e) => e.stopPropagation(),
8946                                onDoubleClick: (e) => {
8947                                  if (!isGameMode && isMixingSupported && !isPlayAlongMute) {
8948                                    e.stopPropagation();
8949                                    onVolumeChange(idx, 1);
8950                                  }
8951                                },
8952                                onChange: (e) => onVolumeChange(idx, parseFloat(e.target.value)),
8953                                className: "absolute w-56 h-10 opacity-0 cursor-pointer z-20",
8954                                style: {
8955                                  transform: "rotate(-90deg)",
8956                                  transformOrigin: "center"
8957                                },
8958                                title: isPlayAlongMute ? "Auto-muted for Play-Along" : `${t("mixer.volume", "Volume")}: ${Math.round(currentVol * 100)}%`,
8959                                disabled: isGameMode || !isMixingSupported || isPlayAlongMute
8960                              }
8961                            ),
8962                            /* @__PURE__ */ jsxRuntimeExports.jsxs(
8963                              "div",
8964                              {
8965                                className: "absolute w-[24px] h-[28px] border border-zinc-950 rounded flex flex-col justify-center items-center z-10 pointer-events-none transition-all duration-75",
8966                                style: {
8967                                  background: faderBg,
8968                                  bottom: `calc(${Math.min(100, currentVol / 1.2 * 100)}% - 14px)`,
8969                                  left: "50%",
8970                                  transform: "translateX(-50%)",
8971                                  boxShadow: "inset 0 1.5px 0 rgba(255,255,255,0.45), inset 0 -1.5px 3px rgba(0,0,0,0.6), 0 3px 6px rgba(0,0,0,0.65)"
8972                                },
8973                                children: [
8974                                  /* @__PURE__ */ jsxRuntimeExports.jsx(
8975                                    "div",
8976                                    {
8977                                      className: "absolute inset-0 rounded pointer-events-none",
8978                                      style: {
8979                                        background: "linear-gradient(to bottom, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 30%, rgba(0,0,0,0.25) 45%, transparent 50%, rgba(0,0,0,0.25) 55%, rgba(255,255,255,0) 70%, rgba(255,255,255,0.15) 100%)"
8980                                      }
8981                                    }
8982                                  ),
8983                                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-[3px] bg-black/95 border-y border-white/10 z-10 pointer-events-none" })
8984                                ]
8985                              }
8986                            )
8987                          ] })
8988                        ] })
8989                      ] }),
8990                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-10 border-t border-zinc-950 bg-black/30 flex flex-col items-center justify-center px-1.5 py-0.5 select-none text-center", children: [
8991                        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[8.5px] font-black text-zinc-400 uppercase tracking-wide truncate w-full", title: uniqueName, children: uniqueName }),
8992                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[6.5px] font-bold text-zinc-650 tracking-wider", children: instr.name === "Informational Track" ? "INFO" : `MIDI CH ${instr.midiChannel !== void 0 ? instr.midiChannel + 1 : idx + 1}` })
8993                      ] })
8994                    ]
8995                  },
8996                  idx
8997                );
8998              }) }),
8999              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 pl-6 flex items-stretch", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-32 shrink-0 flex flex-col h-full bg-gradient-to-b from-[#2e2f33] to-[#1a1b1d] border-l border-r border-zinc-950 shadow-[0_0_20px_rgba(0,0,0,0.5)] rounded-lg relative select-none", children: [
9000                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-7 bg-black/60 border-b border-zinc-950 flex flex-col items-center justify-center relative rounded-t-lg", children: [
9001                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 w-full h-[3px] bg-rose-500" }),
9002                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black text-rose-500 uppercase tracking-widest mt-0.5", children: "Master" })
9003                ] }),
9004                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-between gap-2 py-3 z-10", children: [
9005                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
9006                    RotaryKnob,
9007                    {
9008                      value: reverbLevel,
9009                      onChange: (val) => setReverbLevel(val),
9010                      min: 0,
9011                      max: 1,
9012                      label: "REVERB",
9013                      accentColor: "#3b82f6",
9014                      minLabel: "DRY",
9015                      maxLabel: "WET"
9016                    }
9017                  ) }),
9018                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full px-2 shrink-0", children: [
9019                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[7px] font-extrabold text-zinc-400 tracking-wider block text-center uppercase mb-0.5", children: "ROOM SIZE" }),
9020                    /* @__PURE__ */ jsxRuntimeExports.jsxs(
9021                      "div",
9022                      {
9023                        className: "bg-[#3b0b0b] border border-[#5f1313] rounded-[3px] px-1.5 py-0.5 flex items-center justify-between text-rose-400 font-mono text-[8.5px] shadow-[inset_0_1.5px_3px_rgba(0,0,0,0.8)] cursor-pointer hover:bg-[#4a0e0e] transition-colors",
9024                        onClick: (e) => {
9025                          e.stopPropagation();
9026                          setIsRoomSizeOpen(!isRoomSizeOpen);
9027                        },
9028                        children: [
9029                          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate uppercase font-bold tracking-wide text-center w-full", children: roomSize }),
9030                          /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 8, className: "text-rose-500 shrink-0 ml-1" })
9031                        ]
9032                      }
9033                    ),
9034                    isRoomSizeOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute z-50 left-2 right-2 mt-1 bg-[#230606] border border-[#5f1313] rounded shadow-2xl py-1 text-[8px] font-mono text-rose-400", children: ["Small Room", "Medium Hall", "Large Hall", "Cathedral", "Plate"].map((size) => /* @__PURE__ */ jsxRuntimeExports.jsx(
9035                      "button",
9036                      {
9037                        onClick: (e) => {
9038                          e.stopPropagation();
9039                          setRoomSize(size);
9040                          setIsRoomSizeOpen(false);
9041                        },
9042                        className: `w-full text-left px-2 py-1 hover:bg-[#3b0b0b] hover:text-white uppercase ${roomSize === size ? "bg-[#4a0e0e] font-bold text-white" : ""}`,
9043                        children: size
9044                      },
9045                      size
9046                    )) })
9047                  ] }),
9048                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 w-full px-2 shrink-0", children: [
9049                    /* @__PURE__ */ jsxRuntimeExports.jsx(
9050                      LedButton,
9051                      {
9052                        label: "MUTE ALL",
9053                        active: tracks.every((_, i) => mutedIndices.has(i)),
9054                        onClick: () => toggleMuteAll(),
9055                        ledColor: "#ef4444"
9056                      }
9057                    ),
9058                    /* @__PURE__ */ jsxRuntimeExports.jsx(
9059                      LedButton,
9060                      {
9061                        label: "RESET ALL",
9062                        active: false,
9063                        onClick: () => {
9064                          tracks.forEach((_, i) => {
9065                            onVolumeChange(i, 1);
9066                            if (onPanChange) onPanChange(i, 0);
9067                          });
9068                          setMasterVolume(1);
9069                          setMasterPan(0);
9070                          setReverbLevel(0.25);
9071                          setRoomSize("Medium Hall");
9072                        },
9073                        ledColor: "#fbbf24"
9074                      }
9075                    )
9076                  ] }),
9077                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 w-full flex items-center justify-center gap-1.5 px-2 relative", children: [
9078                    /* @__PURE__ */ jsxRuntimeExports.jsx(MasterSegmentedVuMeter, { masterVuRef }),
9079                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-56 w-11 flex items-center justify-center", children: [
9080                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute w-[3px] h-[216px] bg-zinc-950 rounded border-l border-zinc-900 border-r border-zinc-800 shadow-[inset_0_1px_2px_rgba(0,0,0,0.9)]" }),
9081                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-y-2 left-0.5 flex flex-col justify-between text-[5px] font-mono text-zinc-500/80 pointer-events-none select-none", children: [
9082                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "+6" }),
9083                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "0" }),
9084                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-6" }),
9085                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-12" }),
9086                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-24" }),
9087                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-oo" })
9088                      ] }),
9089                      /* @__PURE__ */ jsxRuntimeExports.jsx(
9090                        "input",
9091                        {
9092                          type: "range",
9093                          min: "0",
9094                          max: "1.2",
9095                          step: "0.02",
9096                          value: masterVolume,
9097                          onClick: (e) => e.stopPropagation(),
9098                          onDoubleClick: (e) => {
9099                            e.stopPropagation();
9100                            setMasterVolume(1);
9101                            tracks.forEach((_, i) => onVolumeChange(i, 1));
9102                          },
9103                          onChange: (e) => {
9104                            const val = parseFloat(e.target.value);
9105                            setMasterVolume(val);
9106                            tracks.forEach((_, i) => onVolumeChange(i, val));
9107                          },
9108                          className: "absolute w-56 h-10 opacity-0 cursor-pointer z-20",
9109                          style: {
9110                            transform: "rotate(-90deg)",
9111                            transformOrigin: "center"
9112                          },
9113                          title: `Master Volume: ${Math.round(masterVolume * 100)}%`
9114                        }
9115                      ),
9116                      /* @__PURE__ */ jsxRuntimeExports.jsxs(
9117                        "div",
9118                        {
9119                          className: "absolute w-[24px] h-[28px] border border-zinc-950 rounded flex flex-col justify-center items-center z-10 pointer-events-none transition-all duration-75",
9120                          style: {
9121                            background: "linear-gradient(to bottom, #ef4444 0%, #b91c1c 100%)",
9122                            bottom: `calc(${Math.min(100, masterVolume / 1.2 * 100)}% - 14px)`,
9123                            left: "50%",
9124                            transform: "translateX(-50%)",
9125                            boxShadow: "inset 0 1.5px 0 rgba(255,255,255,0.45), inset 0 -1.5px 3px rgba(0,0,0,0.6), 0 3px 6px rgba(0,0,0,0.65)"
9126                          },
9127                          children: [
9128                            /* @__PURE__ */ jsxRuntimeExports.jsx(
9129                              "div",
9130                              {
9131                                className: "absolute inset-0 rounded pointer-events-none",
9132                                style: {
9133                                  background: "linear-gradient(to bottom, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 30%, rgba(0,0,0,0.25) 45%, transparent 50%, rgba(0,0,0,0.25) 55%, rgba(255,255,255,0) 70%, rgba(255,255,255,0.15) 100%)"
9134                                }
9135                              }
9136                            ),
9137                            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-[3px] bg-black/95 border-y border-white/10 z-10 pointer-events-none" })
9138                          ]
9139                        }
9140                      )
9141                    ] })
9142                  ] })
9143                ] }),
9144                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-10 border-t border-zinc-950 bg-black/60 flex flex-col items-center justify-center px-1.5 py-0.5 select-none text-center rounded-b-lg", children: [
9145                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-black text-rose-500 uppercase tracking-widest drop-shadow-[0_1px_1px_rgba(0,0,0,0.8)]", children: "MIDI MASTER" }),
9146                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[6px] font-bold text-zinc-600 tracking-wider", children: "MAIN OUT" })
9147                ] })
9148              ] }) })
9149            ] })
9150          ]
9151        }
9152      ),
9153      popoutWindow ? popoutWindow.document.body : document.body
9154    ),
9155    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col h-full animate-in fade-in slide-in-from-right-2 ${getPanelBg()}`, children: [
9156      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: getHeaderBorder(), children: [
9157        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9158          /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: getHeaderTextClass(), children: [
9159            /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 16, className: theme === "fun" ? "text-pink-400 animate-pulse" : "text-indigo-400" }),
9160            t("watch.mixer", "Mixer")
9161          ] }),
9162          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: getSubheaderSeparator() }),
9163          /* @__PURE__ */ jsxRuntimeExports.jsxs(
9164            "button",
9165            {
9166              onClick: () => setMixerMode(mixerMode === "list" ? "console" : "list"),
9167              className: getHeaderButtonClass(),
9168              title: t("mixer.toggleView", "Toggle View"),
9169              children: [
9170                mixerMode === "list" ? /* @__PURE__ */ jsxRuntimeExports.jsx(PanelsTopLeft, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 12 }),
9171                mixerMode === "list" ? t("mixer.console", "Console") : t("mixer.list", "List")
9172              ]
9173            }
9174          ),
9175          mixerMode === "console" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
9176            "button",
9177            {
9178              onClick: () => setIsExpanded(!isExpanded),
9179              className: getHeaderButtonClass(),
9180              title: isExpanded ? t("mixer.minimize", "Minimize") : t("mixer.expand", "Expand"),
9181              children: [
9182                isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize2, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize2, { size: 12 }),
9183                isExpanded ? t("mixer.minimizeShort", "Minimize") : t("mixer.expandShort", "Expand")
9184              ]
9185            }
9186          )
9187        ] }),
9188        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9189          tracks.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
9190            "button",
9191            {
9192              onClick: onResetMixer,
9193              className: getResetButtonClass(),
9194              title: t("mixer.reset", "Reset All"),
9195              children: [
9196                /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 }),
9197                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden sm:inline", children: t("mixer.resetAction", "Reset") })
9198              ]
9199            }
9200          ),
9201          audioMode === "singleopus" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-[9px] font-bold uppercase text-amber-500 bg-amber-500/10 px-2 py-1 rounded border border-amber-500/20 cursor-help", title: t("mixer.opusMode", "Mixer is limited in Opus mode"), children: [
9202            /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 10 }),
9203            " No Stems"
9204          ] })
9205        ] })
9206      ] }),
9207      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden min-h-0 flex flex-col justify-stretch", children: popoutWindow ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center text-center p-6 bg-zinc-950/20 rounded-xl border border-dashed border-zinc-800 shadow-[inset_0_2px_4px_rgba(0,0,0,0.5)] my-2", children: [
9208        /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 36, className: "text-teal-400 mb-3 animate-pulse" }),
9209        /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-xs uppercase tracking-wider text-zinc-300 mb-1", children: "Pop-out Mode Active" }),
9210        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-500 mb-4 max-w-[220px] leading-relaxed", children: "The Studio Mixer Console is currently running in a separate window." }),
9211        /* @__PURE__ */ jsxRuntimeExports.jsx(
9212          "button",
9213          {
9214            onClick: closePopout,
9215            className: "text-[10px] bg-teal-500 hover:bg-teal-650 px-4 py-2 rounded text-white font-extrabold uppercase tracking-widest transition-all shadow-md shadow-teal-500/20 hover:scale-105 active:scale-95",
9216            children: "Reattach Mixer"
9217          }
9218        )
9219      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-h-0", children: [
9220        !isMixingSupported && renderBlockedMixer(),
9221        mixerMode === "console" ? renderConsole() : renderList()
9222      ] }) }),
9223      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: getSoundfontPickerContainer(), ref: sfRef, children: [
9224        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9225          /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 12, className: theme === "light" ? "text-zinc-400" : "text-zinc-500" }),
9226          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: theme === "light" ? "text-[10px] font-bold uppercase tracking-widest text-zinc-500" : "text-[10px] font-bold uppercase tracking-widest text-zinc-400", children: "MIDI Soundfont" })
9227        ] }) }),
9228        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
9229          /* @__PURE__ */ jsxRuntimeExports.jsxs(
9230            "button",
9231            {
9232              onClick: () => (audioMode === "synth" || audioMode === "wasmsynth" || audioMode === "opus") && !isGameMode && setIsSfOpen(!isSfOpen),
9233              disabled: !(audioMode === "synth" || audioMode === "wasmsynth" || audioMode === "opus") || isGameMode,
9234              className: getSoundfontButtonClass(),
9235              children: [
9236                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate pr-2", children: audioMode === "opus" ? t("watch.sfPrerendered", `${preRenderedSf ? preRenderedSf.replace(".sf2", "").replace(/_/g, " ") : "GeneralUser GS"} (Pre-rendered)`) : audioMode === "singleopus" ? "Pre-mixed Audio (N/A)" : audioMode === "wasmsynth" ? `${currentSf.name.replace(/\s*\(Pre-rendered\)/i, "")} (SF)` : audioMode === "fmsynth" ? "16-bit FM Synthesis (N/A)" : audioMode === "chiptune" ? "8-bit Chiptune (N/A)" : audioMode === "webmidi" ? "External Hardware MIDI (N/A)" : currentSf.name.replace(/\s*\(Pre-rendered\)/i, "") }),
9237                (audioMode === "synth" || audioMode === "wasmsynth" || audioMode === "opus") && /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 14, className: "text-zinc-500 transition-transform duration-200", style: { transform: isSfOpen ? "rotate(180deg)" : "none" } })
9238              ]
9239            }
9240          ),
9241          /* @__PURE__ */ jsxRuntimeExports.jsx(AnimatePresence, { children: isSfOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
9242            motion.div,
9243            {
9244              initial: { opacity: 0, y: -4, scale: 0.95 },
9245              animate: { opacity: 1, y: 0, scale: 1 },
9246              exit: { opacity: 0, y: -4, scale: 0.95 },
9247              className: getSoundfontDropdownBg(),
9248              children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-64 overflow-y-auto custom-scrollbar", children: modeLibraries.length > 0 ? modeLibraries.map((lib) => {
9249                const restricted = lib.access === "pro" && !isPro(currentUser);
9250                const isSelected = lib.id === soundfont;
9251                return /* @__PURE__ */ jsxRuntimeExports.jsxs(
9252                  "button",
9253                  {
9254                    disabled: restricted,
9255                    onClick: () => {
9256                      onSoundfontChange == null ? void 0 : onSoundfontChange(lib.id);
9257                      setIsSfOpen(false);
9258                    },
9259                    className: getSoundfontDropdownItemClass(isSelected, restricted),
9260                    children: [
9261                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between w-full gap-2", children: [
9262                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: getSoundfontDropdownItemTitleClass(isSelected), children: audioMode !== "opus" ? lib.name.replace(/\s*\(Pre-rendered\)/i, "") : lib.name }),
9263                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9264                          lib.access === "pro" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-bold bg-amber-500 text-white px-1.5 py-0.5 rounded-sm uppercase tracking-tighter leading-none shadow-sm", children: "GOLD+" }),
9265                          isSelected && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14, className: "text-indigo-400" })
9266                        ] })
9267                      ] }),
9268                      lib.desc && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-medium text-zinc-500 mt-0.5 leading-tight", children: audioMode !== "opus" && lib.isPreRendered ? "Standard quality live synthesis" : lib.desc })
9269                    ]
9270                  },
9271                  lib.id
9272                );
9273              }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "w-full px-4 py-3 text-left text-xs text-zinc-500 font-medium", children: "GeneralUser GS (Pre-rendered)" }) })
9274            }
9275          ) })
9276        ] })
9277      ] })
9278    ] })
9279  ] });
9280};
9281const GovernanceModal = ({ user, isOpen, onClose, onUpdate }) => {
9282  const [action, setAction] = reactExports.useState("");
9283  const [duration, setDuration] = reactExports.useState(24);
9284  const [reason, setReason] = reactExports.useState("");
9285  const [trustAdjustment, setTrustAdjustment] = reactExports.useState(0);
9286  const [isSubmitting, setIsSubmitting] = reactExports.useState(false);
9287  if (!isOpen) return null;
9288  const handleSubmit = async (e) => {
9289    e.preventDefault();
9290    if (!action && trustAdjustment === 0 && !reason) return;
9291    setIsSubmitting(true);
9292    try {
9293      await api.governUser(user.id, {
9294        action,
9295        duration: action === "suspend" ? duration : void 0,
9296        reason,
9297        trustAdjustment: trustAdjustment !== 0 ? trustAdjustment : void 0
9298      });
9299      onUpdate == null ? void 0 : onUpdate();
9300      onClose();
9301    } catch (e2) {
9302      console.error(e2);
9303      alert("Governance action failed");
9304    } finally {
9305      setIsSubmitting(false);
9306    }
9307  };
9308  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 w-full max-w-md rounded-2xl shadow-2xl overflow-hidden border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95 duration-200", children: [
9309    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-indigo-600 p-4 flex items-center justify-between text-white", children: [
9310      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9311        /* @__PURE__ */ jsxRuntimeExports.jsx(Shield, { size: 20 }),
9312        /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "font-bold", children: "USER GOVERNANCE" })
9313      ] }),
9314      /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "p-1 hover:bg-white/20 rounded-lg transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) })
9315    ] }),
9316    /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-6 space-y-6", children: [
9317      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 p-3 bg-zinc-100 dark:bg-zinc-800/50 rounded-xl", children: [
9318        /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: user.avatar || "/default-avatar.png", alt: user.username, className: "w-12 h-12 rounded-full border-2 border-white dark:border-zinc-700" }),
9319        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
9320          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-zinc-900 dark:text-white", children: user.username }),
9321          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500", children: [
9322            "ID: ",
9323            user.id,
9324            " • Trust: ",
9325            user.trustScore,
9326            "%"
9327          ] })
9328        ] })
9329      ] }),
9330      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
9331        /* @__PURE__ */ jsxRuntimeExports.jsxs(
9332          "button",
9333          {
9334            type: "button",
9335            onClick: () => setAction(user.isJailed ? "unjail" : "jail"),
9336            className: `flex flex-col items-center gap-2 p-3 rounded-xl border-2 transition-all ${action === "jail" || action === "unjail" ? "border-indigo-500 bg-indigo-500/5" : "border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700"}`,
9337            children: [
9338              user.isJailed ? /* @__PURE__ */ jsxRuntimeExports.jsx(UserCheck, { className: "text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(UserMinus, { className: "text-red-500" }),
9339              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold", children: user.isJailed ? "Remove Jail" : "Send to Jail" })
9340            ]
9341          }
9342        ),
9343        /* @__PURE__ */ jsxRuntimeExports.jsxs(
9344          "button",
9345          {
9346            type: "button",
9347            onClick: () => setAction(user.suspendedUntil ? "unsuspend" : "suspend"),
9348            className: `flex flex-col items-center gap-2 p-3 rounded-xl border-2 transition-all ${action === "suspend" || action === "unsuspend" ? "border-indigo-500 bg-indigo-500/5" : "border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700"}`,
9349            children: [
9350              /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "text-amber-500" }),
9351              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold", children: user.suspendedUntil ? "Unsuspend" : "Suspend (Mute)" })
9352            ]
9353          }
9354        ),
9355        /* @__PURE__ */ jsxRuntimeExports.jsxs(
9356          "button",
9357          {
9358            type: "button",
9359            onClick: () => setAction(user.isShadowBanned ? "unshadow-ban" : "shadow-ban"),
9360            className: `flex flex-col items-center gap-2 p-3 rounded-xl border-2 transition-all ${action === "shadow-ban" || action === "unshadow-ban" ? "border-indigo-500 bg-indigo-500/5" : "border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700"}`,
9361            children: [
9362              /* @__PURE__ */ jsxRuntimeExports.jsx(Ghost, { className: "text-zinc-500" }),
9363              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold", children: user.isShadowBanned ? "Un-Ghost" : "Shadow Ban" })
9364            ]
9365          }
9366        ),
9367        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-2 p-3 rounded-xl border-2 border-zinc-200 dark:border-zinc-800", children: [
9368          /* @__PURE__ */ jsxRuntimeExports.jsx(ChartColumn, { className: "text-blue-500" }),
9369          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
9370            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setTrustAdjustment((prev) => prev - 10), className: "text-zinc-400 hover:text-red-500", children: "-" }),
9371            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: `text-xs font-bold ${trustAdjustment > 0 ? "text-green-500" : trustAdjustment < 0 ? "text-red-500" : ""}`, children: [
9372              trustAdjustment > 0 ? "+" : "",
9373              trustAdjustment
9374            ] }),
9375            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setTrustAdjustment((prev) => prev + 10), className: "text-zinc-400 hover:text-green-500", children: "+" })
9376          ] }),
9377          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 uppercase font-bold tracking-wider", children: "Trust Score" })
9378        ] })
9379      ] }),
9380      action === "suspend" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 animate-in slide-in-from-top-2", children: [
9381        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider", children: "Suspension Duration (Hours)" }),
9382        /* @__PURE__ */ jsxRuntimeExports.jsx(
9383          "input",
9384          {
9385            type: "number",
9386            value: duration,
9387            onChange: (e) => setDuration(parseInt(e.target.value)),
9388            className: "w-full bg-zinc-100 dark:bg-zinc-800 border-none rounded-lg p-3 text-sm focus:ring-2 ring-indigo-500 outline-none"
9389          }
9390        )
9391      ] }),
9392      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
9393        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider", children: "Reason / Internal Note" }),
9394        /* @__PURE__ */ jsxRuntimeExports.jsx(
9395          "textarea",
9396          {
9397            value: reason,
9398            onChange: (e) => setReason(e.target.value),
9399            placeholder: "Why are you taking this action? (Internal only)",
9400            className: "w-full bg-zinc-100 dark:bg-zinc-800 border-none rounded-lg p-3 text-sm h-24 resize-none focus:ring-2 ring-indigo-500 outline-none"
9401          }
9402        )
9403      ] }),
9404      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 pt-2", children: [
9405        /* @__PURE__ */ jsxRuntimeExports.jsx(
9406          "button",
9407          {
9408            type: "button",
9409            onClick: onClose,
9410            className: "flex-1 px-4 py-3 rounded-xl font-bold text-sm text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors",
9411            children: "Cancel"
9412          }
9413        ),
9414        /* @__PURE__ */ jsxRuntimeExports.jsx(
9415          "button",
9416          {
9417            type: "submit",
9418            disabled: isSubmitting || !action && trustAdjustment === 0 && !reason,
9419            className: "flex-[2] bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 rounded-xl transition-all shadow-lg shadow-indigo-500/20 disabled:opacity-50",
9420            children: isSubmitting ? "Applying..." : "Apply Governance"
9421          }
9422        )
9423      ] })
9424    ] })
9425  ] }) });
9426};
9427const EducationalDisclaimerModal = ({ isOpen, onClose, onAccept }) => {
9428  const { t } = useTranslation();
9429  const [checked, setChecked] = reactExports.useState(false);
9430  if (!isOpen) return null;
9431  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white dark:bg-zinc-900 w-full max-w-lg max-h-[90vh] flex flex-col rounded-3xl shadow-2xl border border-zinc-200 dark:border-zinc-800 overflow-hidden animate-in zoom-in-95 duration-200", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-5 md:p-8 overflow-y-auto custom-scrollbar flex-1", children: [
9432    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-6", children: [
9433      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { size: 28 }) }),
9434      /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full transition-colors text-zinc-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) })
9435    ] }),
9436    /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-2xl font-black text-zinc-900 dark:text-white mb-4 tracking-tight", children: "Educational Use Acknowledgment" }),
9437    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 text-zinc-600 dark:text-zinc-400 text-sm leading-relaxed mb-8", children: [
9438      /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
9439        "HamieNET is a ",
9440        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-900 dark:text-zinc-200", children: "non-profit digital archive" }),
9441        " dedicated to the preservation and musicological study of MIDI metadata and transcriptions."
9442      ] }),
9443      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-2xl border border-zinc-100 dark:border-zinc-800 flex gap-3", children: [
9444        /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 18, className: "text-indigo-500 shrink-0 mt-0.5" }),
9445        /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
9446          "By downloading files from this archive, you acknowledge that the content is provided solely for",
9447          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-indigo-600 dark:text-indigo-400 mx-1", children: "educational, research, and non-commercial preservation purposes" }),
9448          "."
9449        ] })
9450      ] }),
9451      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "You agree not to use this data for any commercial redistribution or performance that would violate local copyright laws." })
9452    ] }),
9453    /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-3 p-4 bg-indigo-50/50 dark:bg-indigo-900/10 rounded-2xl border border-indigo-100/50 dark:border-indigo-900/20 cursor-pointer group transition-all hover:bg-indigo-50 dark:hover:bg-indigo-900/20 mb-8", children: [
9454      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex items-center mt-1", children: [
9455        /* @__PURE__ */ jsxRuntimeExports.jsx(
9456          "input",
9457          {
9458            type: "checkbox",
9459            checked,
9460            onChange: (e) => setChecked(e.target.checked),
9461            className: "peer h-5 w-5 cursor-pointer appearance-none rounded-md border border-zinc-300 dark:border-zinc-700 checked:bg-indigo-600 checked:border-transparent transition-all"
9462          }
9463        ),
9464        /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "absolute h-3.5 w-3.5 text-white opacity-0 peer-checked:opacity-100 left-0.5 top-0.5 pointer-events-none transition-opacity" })
9465      ] }),
9466      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-zinc-700 dark:text-zinc-300 select-none", children: "I agree that this download is for educational and research purposes only." })
9467    ] }),
9468    /* @__PURE__ */ jsxRuntimeExports.jsx(
9469      "button",
9470      {
9471        disabled: !checked,
9472        onClick: onAccept,
9473        className: "w-full py-4 bg-indigo-600 hover:bg-indigo-700 disabled:bg-zinc-200 dark:disabled:bg-zinc-800 disabled:text-zinc-400 dark:disabled:text-zinc-600 text-white font-black rounded-2xl transition-all shadow-lg shadow-indigo-600/20 disabled:shadow-none translate-y-0 active:translate-y-0.5",
9474        children: "Accept & Continue Download"
9475      }
9476    )
9477  ] }) }) });
9478};
9479const ShareModal = ({
9480  isOpen,
9481  onClose,
9482  currentTime,
9483  duration,
9484  trackTitle,
9485  trackId
9486}) => {
9487  const { t } = useTranslation();
9488  const [copied, setCopied] = reactExports.useState(false);
9489  const [gameMidiCopied, setGameMidiCopied] = reactExports.useState(false);
9490  const [startAtChecked, setStartAtChecked] = reactExports.useState(false);
9491  reactExports.useEffect(() => {
9492    if (copied) {
9493      const timer = setTimeout(() => setCopied(false), 2e3);
9494      return () => clearTimeout(timer);
9495    }
9496  }, [copied]);
9497  reactExports.useEffect(() => {
9498    if (gameMidiCopied) {
9499      const timer = setTimeout(() => setGameMidiCopied(false), 2e3);
9500      return () => clearTimeout(timer);
9501    }
9502  }, [gameMidiCopied]);
9503  if (!isOpen) return null;
9504  const formatTime = (seconds) => {
9505    const mins = Math.floor(seconds / 60);
9506    const secs = Math.floor(seconds % 60);
9507    return `${mins}:${secs.toString().padStart(2, "0")}`;
9508  };
9509  const getShareUrl = () => {
9510    if (typeof window === "undefined") return "";
9511    const url = new URL(window.location.href);
9512    url.searchParams.delete("t");
9513    url.searchParams.delete("time");
9514    if (startAtChecked) {
9515      url.searchParams.set("t", Math.floor(currentTime).toString());
9516    }
9517    return url.toString();
9518  };
9519  const shareUrl = getShareUrl();
9520  const shareTitle = `${t("watch.shareTextPrefix", "Check out this song on HamieNET:")} ${trackTitle}`;
9521  const getGameMidiUrl = () => {
9522    if (typeof window === "undefined") return "";
9523    const url = new URL(window.location.origin);
9524    url.pathname = `/media/midi/${trackId}.mid`;
9525    return url.toString();
9526  };
9527  const gameMidiUrl = getGameMidiUrl();
9528  const handleCopy = async () => {
9529    try {
9530      await navigator.clipboard.writeText(shareUrl);
9531      setCopied(true);
9532    } catch (err) {
9533      console.error("Failed to copy text: ", err);
9534    }
9535  };
9536  const handleCopyGameMidi = async () => {
9537    try {
9538      await navigator.clipboard.writeText(gameMidiUrl);
9539      setGameMidiCopied(true);
9540    } catch (err) {
9541      console.error("Failed to copy text: ", err);
9542    }
9543  };
9544  const socialNetworks = [
9545    {
9546      name: t("watch.shareFacebook", "Facebook"),
9547      url: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,
9548      icon: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-6 h-6 fill-current", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9 8H7v3h2v9h3v-9h3.6l.4-3H12V6c0-.9.2-1.2 1-1.2h2V2h-3c-2.5 0-4 1.2-4 3.8V8z" }) }),
9549      bgClass: "bg-[#1877f2] hover:bg-[#166fe5]"
9550    },
9551    {
9552      name: t("watch.shareTwitter", "X (Twitter)"),
9553      url: `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,
9554      icon: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-5 h-5 fill-current", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) }),
9555      bgClass: "bg-[#000000] hover:bg-[#1a1a1a] dark:bg-zinc-850 dar
9555k:hover:bg-zinc-700 border border-zinc-700/50"
9556    },
9557    {
9558      name: t("watch.shareWhatsApp", "WhatsApp"),
9559      url: `https://api.whatsapp.com/send?text=${encodeURIComponent(shareTitle + " " + shareUrl)}`,
9560      icon: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-6 h-6 fill-current", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M.057 24l1.687-6.163c-1.041-1.804-1.588-3.849-1.587-5.946C.06 5.348 5.397.01 12.008.01c3.202.001 6.212 1.246 8.477 3.514 2.266 2.268 3.507 5.28 3.505 8.484-.004 6.657-5.34 11.997-11.953 11.997-2.005-.001-3.973-.502-5.73-1.455L0 24zm6.59-4.846c1.6.95 3.188 1.449 4.825 1.451 5.436 0 9.86-4.37 9.864-9.799.002-2.63-1.023-5.101-2.885-6.968C16.59 1.97 14.12 1.01 11.49 1.01c-5.442 0-9.87 4.372-9.874 9.802-.001 1.782.485 3.524 1.407 5.11L1.932 20.17l4.715-1.016z" }) }),
9561      bgClass: "bg-[#25d366] hover:bg-[#20ba5a]"
9562    },
9563    {
9564      name: t("watch.shareReddit", "Reddit"),
9565      url: `https://www.reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(shareTitle)}`,
9566      icon: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-6 h-6 fill-current", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-.532 1.023c.319.49.52 1.068.567 1.693.992.115 1.737.954 1.737 1.97 0 1.102-.898 2-2 2-.178 0-.348-.03-.508-.073-.418 1.488-1.745 2.593-3.376 2.766.115.347.18.718.18 1.103a4.01 4.01 0 0 1-4.004 4.004c-1.815 0-3.32-1.2-3.793-2.846a3.99 3.99 0 0 1-1.267.206c-2.206 0-4-1.794-4-4 0-1.408.73-2.646 1.83-3.344.02-.63.21-1.22.52-1.724a1.25 1.25 0 0 1-.58-.934c0-.688.562-1.25 1.25-1.25.513 0 .95.31 1.14.75a7.1 7.1 0 0 1 3.86-1.127l.812-2.553 2.18.497c-.015.08-.023.163-.023.248 0 .688.562 1.25 1.25 1.25.688 0 1.25-.562 1.25-1.25s-.562-1.25-1.25-1.25c-.562 0-1.026.376-1.185.889l-2.433-.555-.916 2.885c-1.826.047-3.473.493-4.7 1.176.155-.38.53-.655.975-.655a1.25 1.25 0 0 1 1.25 1.25c0 .356-.153.674-.395.898.375.52.61 1.157.65 1.848.914.156 1.597.94 1.597 1.892 0 .613-.29 1.156-.74 1.503a4.01 4.01 0 0 1-2.906 1.257c-.12 0-.24-.007-.358-.02a3.99 3.99 0 0 1-3.08-3.076c-.534-.14-1.022-.44-1.39-.844zm-7.66 4.316a.91.91 0 1 0 0 1.82.91.91 0 0 0 0-1.82zm5.3 0a.91.91 0 1 0 0 1.82.91.91 0 0 0 0-1.82z" }) }),
9567      bgClass: "bg-[#ff4500] hover:bg-[#e03d00]"
9568    },
9569    {
9570      name: t("watch.shareEmail", "Email"),
9571      url: `mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(t("watch.shareEmailBody", "Check this out on HamieNET: ") + shareUrl)}`,
9572      icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Mail, { className: "w-5 h-5 text-white" }),
9573      bgClass: "bg-zinc-600 hover:bg-zinc-700"
9574    }
9575  ];
9576  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm 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-md overflow-hidden border border-zinc-200/50 dark:border-zinc-800/50 animate-in zoom-in-95 duration-200", children: [
9577    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-5 border-b border-zinc-100 dark:border-zinc-800", children: [
9578      /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-extrabold text-lg text-zinc-950 dark:text-white", children: t("watch.shareTitle", "Share") }),
9579      /* @__PURE__ */ jsxRuntimeExports.jsx(
9580        "button",
9581        {
9582          onClick: onClose,
9583          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",
9584          "aria-label": t("common.close", "Close"),
9585          children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 })
9586        }
9587      )
9588    ] }),
9589    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 flex flex-col gap-6", children: [
9590      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-4 overflow-x-auto py-2 px-2 -mx-2 -my-2 custom-scrollbar justify-start", children: socialNetworks.map((net) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
9591        "a",
9592        {
9593          href: net.url,
9594          target: "_blank",
9595          rel: "noopener noreferrer",
9596          className: "flex flex-col items-center gap-2 group shrink-0",
9597          children: [
9598            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-12 h-12 rounded-full flex items-center justify-center text-white transition-all duration-200 transform group-hover:scale-110 shadow-md ${net.bgClass}`, children: net.icon }),
9599            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-medium text-zinc-600 dark:text-zinc-400 group-hover:text-zinc-900 dark:group-hover:text-zinc-200 transition-colors", children: net.name })
9600          ]
9601        },
9602        net.name
9603      )) }),
9604      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
9605        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider", children: t("watch.shareLink", "Page Link") }),
9606        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-800 rounded-2xl p-1.5 pl-4 overflow-hidden", children: [
9607          /* @__PURE__ */ jsxRuntimeExports.jsx(
9608            "input",
9609            {
9610              type: "text",
9611              readOnly: true,
9612              value: shareUrl,
9613              onFocus: (e) => e.target.select(),
9614              className: "w-full bg-transparent border-none outline-none text-sm text-zinc-800 dark:text-zinc-200 font-medium"
9615            }
9616          ),
9617          /* @__PURE__ */ jsxRuntimeExports.jsx(
9618            "button",
9619            {
9620              onClick: handleCopy,
9621              className: `flex items-center gap-1.5 px-4 py-2 rounded-xl text-xs font-bold transition-all ${copied ? "bg-emerald-500 text-white shadow-lg shadow-emerald-500/20" : "bg-indigo-600 hover:bg-indigo-700 text-white shadow-lg shadow-indigo-600/20"}`,
9622              children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
9623                /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }),
9624                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.copied", "Copied") })
9625              ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
9626                /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 14 }),
9627                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.copy", "Copy") })
9628              ] })
9629            }
9630          )
9631        ] })
9632      ] }),
9633      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 pt-2 border-t border-zinc-100 dark:border-zinc-800", children: [
9634        /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-400 dark:text-zinc-500 uppercase tracking-wider", children: t("watch.midiLink", "MIDI Link") }),
9635        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-800 rounded-2xl p-1.5 pl-4 overflow-hidden", children: [
9636          /* @__PURE__ */ jsxRuntimeExports.jsx(
9637            "input",
9638            {
9639              type: "text",
9640              readOnly: true,
9641              value: gameMidiUrl,
9642              onFocus: (e) => e.target.select(),
9643              className: "w-full bg-transparent border-none outline-none text-sm text-zinc-800 dark:text-zinc-200 font-medium"
9644            }
9645          ),
9646          /* @__PURE__ */ jsxRuntimeExports.jsx(
9647            "button",
9648            {
9649              onClick: handleCopyGameMidi,
9650              className: `flex items-center gap-1.5 px-4 py-2 rounded-xl text-xs font-bold transition-all ${gameMidiCopied ? "bg-emerald-500 text-white shadow-lg shadow-emerald-500/20" : "bg-indigo-600 hover:bg-indigo-700 text-white shadow-lg shadow-indigo-600/20"}`,
9651              children: gameMidiCopied ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
9652                /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }),
9653                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.copied", "Copied") })
9654              ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
9655                /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 14 }),
9656                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.copy", "Copy") })
9657              ] })
9658            }
9659          )
9660        ] })
9661      ] }),
9662      duration > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3 pt-2 border-t border-zinc-100 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "relative flex items-center cursor-pointer select-none", children: [
9663        /* @__PURE__ */ jsxRuntimeExports.jsx(
9664          "input",
9665          {
9666            type: "checkbox",
9667            checked: startAtChecked,
9668            onChange: (e) => setStartAtChecked(e.target.checked),
9669            className: "sr-only peer"
9670          }
9671        ),
9672        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 h-5 bg-zinc-100 dark:bg-zinc-800 border-2 border-zinc-300 dark:border-zinc-700 rounded peer-checked:bg-indigo-600 peer-checked:border-indigo-600 transition-all flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 12, className: "text-white opacity-0 peer-checked:opacity-100 transition-opacity" }) }),
9673        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-3 text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: [
9674          t("watch.shareStartAt", "Start at"),
9675          " ",
9676          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-indigo-600 dark:text-indigo-400", children: formatTime(currentTime) })
9677        ] })
9678      ] }) })
9679    ] })
9680  ] }) });
9681};
9682const TRUST_LINK_THRESHOLD = 50;
9683const LinkifiedText = ({ text, trustScore = 0 }) => {
9684  if (!text) return null;
9685  if (trustScore < TRUST_LINK_THRESHOLD) {
9686    return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: text });
9687  }
9688  const urlRegex = /(https?:\/\/[^\s]+)/g;
9689  const parts = text.split(urlRegex);
9690  return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: parts.map((part, i) => {
9691    if (part.match(urlRegex)) {
9692      return /* @__PURE__ */ jsxRuntimeExports.jsx(
9693        "a",
9694        {
9695          href: part,
9696          target: "_blank",
9697          rel: "noopener noreferrer nofollow ugc",
9698          className: "text-indigo-600 dark:text-indigo-400 hover:underline break-all",
9699          children: part
9700        },
9701        i
9702      );
9703    }
9704    return part;
9705  }) });
9706};
9707const VideoMetadata = ({
9708  track,
9709  currentUser,
9710  isSubscribed,
9711  onSubscribe,
9712  onChannelSelect,
9713  onArtistSelect,
9714  likedMidis,
9715  toggleLike,
9716  tags,
9717  onTagSelect,
9718  onVoteTag,
9719  onOpenTagModal,
9720  onAddToPlaylist,
9721  onReport,
9722  onAuthRequested,
9723  onEditWiki,
9724  onCustomizeBackground,
9725  onRemoveBackground,
9726  currentTime = 0,
9727  duration = 0,
9728  isLiveSynthFallback = false,
9729  hasSynthError = false,
9730  onReuploadNeeded
9731}) => {
9732  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
9733  const { t } = useTranslation();
9734  const isCommunityContributor = !track.uploader || track.uploader.id === -1 || track.uploader.name === "Community Contributor";
9735  const { formatNumber } = useSettings();
9736  const { staffModeEnabled } = useAdmin();
9737  const [currentStatus, setCurrentStatus] = reactExports.useState(track.visibility);
9738  const [isMenuOpen, setIsMenuOpen] = reactExports.useState(false);
9739  const [isShareModalOpen, setIsShareModalOpen] = reactExports.useState(false);
9740  const [governanceUser, setGovernanceUser] = reactExports.useState(null);
9741  const [isGovernModalOpen, setIsGovernModalOpen] = reactExports.useState(false);
9742  const [isDownloading, setIsDownloading] = reactExports.useState(false);
9743  const [isDisclaimerOpen, setIsDisclaimerOpen] = reactExports.useState(false);
9744  const [isStatusExpanded, setIsStatusExpanded] = reactExports.useState(false);
9745  const [pendingDownloadType, setPendingDownloadType] = reactExports.useState(null);
9746  const [hasAcceptedTerms, setHasAcceptedTerms] = reactExports.useState(() => {
9747    if (typeof window === "undefined") return false;
9748    return localStorage.getItem("hamie_edu_accepted") === "true";
9749  });
9750  const menuRef = reactExports.useRef(null);
9751  reactExports.useEffect(() => {
9752    const handleClickOutside = (event) => {
9753      if (menuRef.current && !menuRef.current.contains(event.target)) {
9754        setIsMenuOpen(false);
9755      }
9756    };
9757    document.addEventListener("mousedown", handleClickOutside);
9758    return () => document.removeEventListener("mousedown", handleClickOutside);
9759  }, []);
9760  const handleSecureDownload = async (type, bypassTermsCheck = false) => {
9761    if (!bypassTermsCheck && !hasAcceptedTerms && (!currentUser || !currentUser.educationalAccepted)) {
9762      setPendingDownloadType(type);
9763      setIsDisclaimerOpen(true);
9764      return;
9765    }
9766    const userId = currentUser ? currentUser.id : -1;
9767    const displayType = type === "clean" ? "MIDI" : type.toUpperCase();
9768    if (currentUser) {
9769      if (!isSubscribed && currentUser.role !== "ADMIN") {
9770        const confirmMsg = t("watch.downloadConfirmSubBenefit", "Download {{type}}? This will deduct credits from your account. (Subscribers download for FREE!)", { type: displayType });
9771        if (!confirm(confirmMsg)) return;
9772      } else if (currentUser.role === "ADMIN") {
9773        const confirmMsg = t("watch.downloadConfirmAdmin", "Download {{type}}? (Admin: No credits deducted)", { type: displayType });
9774        if (!confirm(confirmMsg)) return;
9775      }
9776    } else {
9777      const confirmMsg = t("watch.downloadConfirmGuest", "Download {{type}}? Unregistered users have a daily limit.", { type: displayType });
9778      if (!confirm(confirmMsg)) return;
9779    }
9780    setIsDownloading(true);
9781    try {
9782      const response = await fetch(`/api/tracks/${track.id}/secure-download`, {
9783        method: "POST",
9784        headers: { "Content-Type": "application/json" },
9785        body: JSON.stringify({ userId, type })
9786      });
9787      if (response.ok) {
9788        const blob = await response.blob();
9789        const url = window.URL.createObjectURL(blob);
9790        const a = document.createElement("a");
9791        a.href = url;
9792        const contentDisposition = response.headers.get("content-disposition");
9793        let filename = `${track.title}.${type === "opus" ? "opus" : "mid"}`;
9794        if (contentDisposition) {
9795          const match = contentDisposition.match(/filename="(.+)"/);
9796          if (match) filename = match[1];
9797        }
9798        a.download = filename;
9799        document.body.appendChild(a);
9800        const clickEvent = new MouseEvent("click", {
9801          bubbles: false,
9802          cancelable: true,
9803          view: window
9804        });
9805        a.dispatchEvent(clickEvent);
9806        window.URL.revokeObjectURL(url);
9807        document.body.removeChild(a);
9808      } else {
9809        const err = await response.json();
9810        alert(`Download failed: ${err.error}`);
9811      }
9812    } catch (e) {
9813      console.error(e);
9814      api.logError(e, "VideoMetadata:handleSecureDownload");
9815      alert("Download error occurred.");
9816    } finally {
9817      setIsDownloading(false);
9818      setIsMenuOpen(false);
9819    }
9820  };
9821  const handleToggleVisibility = async () => {
9822    try {
9823      const result = await api.toggleTrackVisibility(track.id);
9824      setCurrentStatus(result.status);
9825    } catch (e) {
9826      console.error(e);
9827    }
9828  };
9829  const handleGovernUploader = async () => {
9830    try {
9831      const user = await api.getAdminUser(track.uploader.id);
9832      setGovernanceUser(user);
9833      setIsGovernModalOpen(true);
9834    } catch (e) {
9835      console.error(e);
9836    }
9837  };
9838  const handleReprocess = async () => {
9839    if (!confirm(t("watch.reprocessConfirm", "Are you sure you want to re-process MIDI ID #{{id}}? This will reset its processing state and re-queue it.", { id: track.id }))) return;
9840    try {
9841      await api.post("/admin/tracks/reprocess", { ids: [track.id] });
9842      alert(t("watch.reprocessSuccess", "Successfully queued MIDI ID #{{id}} for reprocessing.", { id: track.id }));
9843    } catch (e) {
9844      console.error(e);
9845      alert(t("watch.reprocessError", "Failed to queue MIDI: {{error}}", { error: e.message || e }));
9846    }
9847  };
9848  const [fitStage, setFitStage] = reactExports.useState(0);
9849  const containerRef = reactExports.useRef(null);
9850  const nameRef = reactExports.useRef(null);
9851  reactExports.useEffect(() => {
9852    const checkFit = () => {
9853      if (!nameRef.current || !containerRef.current) return;
9854      const nameEl = nameRef.current;
9855      const containerWidth = containerRef.current.clientWidth;
9856      const isOverflowing = nameEl.scrollWidth > containerWidth + 2;
9857      if (isOverflowing && fitStage < 3) {
9858        setFitStage((prev) => prev + 1);
9859      }
9860    };
9861    const observer = new ResizeObserver(() => {
9862      setFitStage(0);
9863    });
9864    if (containerRef.current) observer.observe(containerRef.current);
9865    checkFit();
9866    return () => observer.disconnect();
9867  }, [fitStage, track.uploader.name]);
9868  const getNameClasses = () => {
9869    const base = "font-bold text-zinc-900 dark:text-white cursor-pointer hover:underline transition-all duration-200";
9870    switch (fitStage) {
9871      case 0:
9872        return `${base} text-base md:text-lg whitespace-nowrap overflow-visible`;
9873      case 1:
9874        return `${base} text-sm md:text-base whitespace-nowrap overflow-visible`;
9875      case 2:
9876        return `${base} text-sm line-clamp-2 leading-snug break-words`;
9877      case 3:
9878        return `${base} text-sm truncate leading-snug break-words`;
9879      default:
9880        return base;
9881    }
9882  };
9883  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "", children: [
9884    track.artist && !["unknown", "unknown artist"].includes(track.artist.toLowerCase().trim()) && /* @__PURE__ */ jsxRuntimeExports.jsx(
9885      "a",
9886      {
9887        href: `/?q=${encodeURIComponent(track.artist)}`,
9888        onClick: (e) => {
9889          if (!e.metaKey && !e.ctrlKey && !e.shiftKey && e.button !== 1) {
9890            e.preventDefault();
9891            if (onArtistSelect) {
9892              onArtistSelect(track.artist);
9893            } else {
9894              window.location.href = `/?q=${encodeURIComponent(track.artist)}`;
9895            }
9896          }
9897        },
9898        className: "inline-block text-xs md:text-sm font-semibold text-zinc-500 dark:text-zinc-400 hover:text-indigo-600 dark:hover:text-indigo-400 hover:underline transition-colors cursor-pointer leading-tight mb-1",
9899        title: `Explore all tracks by ${track.artist}`,
9900        children: track.artist
9901      }
9902    ),
9903    /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-xl md:text-2xl font-bold text-zinc-900 dark:text-white line-clamp-2 leading-tight", children: track.title }),
9904    (track.status === "pending" || track.status === "processing" || track.visibility === "missing" || hasSynthError || isLiveSynthFallback) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 mb-2", children: [
9905      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap text-xs", children: [
9906        (track.status === "pending" || track.status === "processing") && /* @__PURE__ */ jsxRuntimeExports.jsxs(
9907          "button",
9908          {
9909            onClick: () => setIsStatusExpanded(!isStatusExpanded),
9910            className: "group relative flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-amber-500/10 hover:bg-amber-500/20 border border-amber-500/30 text-amber-700 dark:text-amber-300 font-medium transition-all shadow-xs cursor-pointer select-none",
9911            title: t("watch.status.hoverTooltip", "Click to view processing details & status"),
9912            children: [
9913              /* @__PURE__ */ jsxRuntimeExports.jsx(OctagonAlert, { size: 14, className: "text-amber-500 animate-pulse shrink-0" }),
9914              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: track.status === "pending" ? t("watch.status.pendingTitle", "MIDI Queued for Processing") : t("watch.status.processingTitle", "MIDI Processing in Progress") }),
9915              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-0.5 rounded-full hover:bg-amber-500/20 text-amber-600 dark:text-amber-400", children: isStatusExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 13 }) })
9916            ]
9917          }
9918        ),
9919        track.visibility === "missing" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1 rounded-full bg-red-500/10 border border-red-500/30 text-red-700 dark:text-red-300 font-medium shadow-xs", children: [
9920          /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 14, className: "text-red-500 animate-pulse shrink-0" }),
9921          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.status.missingTitle", "MIDI File Missing") }),
9922          onReuploadNeeded && /* @__PURE__ */ jsxRuntimeExports.jsx(
9923            "button",
9924            {
9925              onClick: onReuploadNeeded,
9926              className: "ml-1 px-2 py-0.5 bg-red-600 hover:bg-red-700 text-white rounded-md text-[11px] font-bold transition-all shadow-xs",
9927              children: t("watch.status.reuploadBtn", "Re-upload 
9927Now")
9928            }
9929          )
9930        ] }),
9931        hasSynthError && /* @__PURE__ */ jsxRuntimeExports.jsxs(
9932          "button",
9933          {
9934            onClick: () => setIsStatusExpanded(!isStatusExpanded),
9935            className: "flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-red-500/10 hover:bg-red-500/20 border border-red-500/30 text-red-700 dark:text-red-300 font-medium transition-all shadow-xs cursor-pointer select-none",
9936            title: t("watch.status.hoverTooltip", "Click to view processing details & status"),
9937            children: [
9938              /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 14, className: "text-red-500 shrink-0" }),
9939              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.status.synthErrorTitle", "Audio Synthesizer Failed to Load") }),
9940              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-0.5 rounded-full hover:bg-red-500/20 text-red-600 dark:text-red-400", children: isStatusExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown$1, { size: 13 }) })
9941            ]
9942          }
9943        ),
9944        isLiveSynthFallback && track.status !== "pending" && track.status !== "processing" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/30 text-indigo-700 dark:text-indigo-300 font-medium shadow-xs", title: t("watch.status.liveSynthDesc", "Playing directly from MIDI using a JS soundfont."), children: [
9945          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "🎹" }),
9946          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.status.liveSynthTitle", "Live Synthesis Active") })
9947        ] })
9948      ] }),
9949      isStatusExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 p-3.5 rounded-xl bg-amber-500/5 dark:bg-amber-950/20 border border-amber-500/20 text-zinc-800 dark:text-zinc-200 text-xs space-y-2 animate-in fade-in slide-in-from-top-1 duration-200 shadow-sm", children: [
9950        (track.status === "pending" || track.status === "processing") && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
9951          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 font-bold text-amber-700 dark:text-amber-300", children: [
9952            /* @__PURE__ */ jsxRuntimeExports.jsx(OctagonAlert, { size: 15 }),
9953            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: track.status === "pending" ? t("watch.status.pendingTitle", "MIDI Queued for Processing") : t("watch.status.processingTitle", "MIDI Processing in Progress") })
9954          ] }),
9955          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "opacity-90 leading-relaxed", children: track.status === "pending" ? t("watch.status.pendingDesc", "This MIDI is currently in queue waiting to be processed.") : t("watch.status.processingDesc", "This MIDI is currently being analyzed and rendered.") }),
9956          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-1.5 border-t border-amber-500/15", children: [
9957            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-amber-600 dark:text-amber-400 block mb-1", children: t("watch.status.comingSoonHeader", "The following advanced interactive features are coming soon:") }),
9958            /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "grid grid-cols-1 sm:grid-cols-2 gap-1 opacity-85 ml-1", children: [
9959              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-1.5", children: [
9960                "• ",
9961                t("watch.status.feature3d", "Interactive 3D Piano Roll View")
9962              ] }),
9963              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-1.5", children: [
9964                "• ",
9965                t("watch.status.featurePercussion", "Percussion Roll Channel Analyzer")
9966              ] }),
9967              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-1.5", children: [
9968                "• ",
9969                t("watch.status.featureSheet", "Interactive Sheet Music Score Viewer")
9970              ] }),
9971              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-1.5", children: [
9972                "• ",
9973                t("watch.status.featureSynth", "Arrangement Orchestra Synthesizer")
9974              ] }),
9975              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-1.5", children: [
9976                "• ",
9977                t("watch.status.featureGame", "Gamepad Play-Along Game Mode")
9978              ] }),
9979              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-center gap-1.5", children: [
9980                "• ",
9981                t("watch.status.featureStems", "High-fidelity Opus Audio Stems")
9982              ] })
9983            ] })
9984          ] })
9985        ] }),
9986        hasSynthError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-1", children: [
9987          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 font-bold text-red-600 dark:text-red-400 mb-1", children: [
9988            /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 15 }),
9989            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.status.synthErrorTitle", "Audio Synthesizer Failed to Load") })
9990          ] }),
9991          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "opacity-90 leading-relaxed", children: t("watch.status.synthErrorDesc", "Some audio tracks or the selected soundfont failed to load correctly. Please refresh the page or try switching to Pre-rendered Audio in the sidebar mixer panel.") })
9992        ] })
9993      ] })
9994    ] }),
9995    staffModeEnabled && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 mb-3 p-3 bg-indigo-500/10 border border-indigo-500/20 rounded-xl flex items-center justify-between animate-in slide-in-from-top-2", children: [
9996      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
9997        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-indigo-600 dark:text-indigo-400 font-bold text-xs", children: [
9998          /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { size: 16 }),
9999          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "ADMIN CONTROL" })
10000        ] }),
10001        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-4 w-px bg-indigo-500/20" }),
10002        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
10003          /* @__PURE__ */ jsxRuntimeExports.jsxs(
10004            "button",
10005            {
10006              onClick: handleToggleVisibility,
10007              className: `px-3 py-1.5 rounded-lg text-xs font-bold flex items-center gap-2 transition-all ${currentStatus === "suspended" ? "bg-red-600 text-white shadow-lg" : "bg-white dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 border border-zinc-200 dark:border-zinc-700"}`,
10008              children: [
10009                currentStatus === "suspended" ? /* @__P
10009URE__ */ jsxRuntimeExports.jsx(Eye, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 14 }),
10010                currentStatus === "suspended" ? "UNHIDE TRACK" : "HIDE TRACK"
10011              ]
10012            }
10013          ),
10014          /* @__PURE__ */ jsxRuntimeExports.jsxs(
10015            "a",
10016            {
10017              href: `${((_a = window.HAMIE_ENV) == null ? void 0 : _a.ADMIN_URL) || ""}/admin/media?id=${track.id}`,
10018              target: "_blank",
10019              className: "px-3 py-1.5 bg-white dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 border border-zinc-200 dark:border-zinc-700 rounded-lg text-xs font-bold flex items-center gap-2 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-all",
10020              children: [
10021                /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 14 }),
10022                "DATABASE RECORD"
10023              ]
10024            }
10025          ),
10026          /* @__PURE__ */ jsxRuntimeExports.jsxs(
10027            "button",
10028            {
10029              onClick: handleGovernUploader,
10030              className: "px-3 py-1.5 bg-white dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 border border-zinc-200 dark:border-zinc-700 rounded-lg text-xs font-bold flex items-center gap-2 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-all",
10031              children: [
10032                /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldAlert, { size: 14, className: "text-indigo-500" }),
10033                "GOVERN UPLOADER"
10034              ]
10035            }
10036          ),
10037          /* @__PURE__ */ jsxRuntimeExports.jsxs(
10038            "button",
10039            {
10040              onClick: handleReprocess,
10041              className: "px-3 py-1.5 bg-white dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 border border-zinc-200 dark:border-zinc-700 rounded-lg text-xs font-bold flex items-center gap-2 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-all",
10042              children: [
10043                /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 14, className: "text-indigo-500" }),
10044                "RE-PROCESS MIDI"
10045              ]
10046            }
10047          )
10048        ] })
10049      ] }),
10050      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-zinc-500 font-medium", children: [
10051        "Last Modified: ",
10052        new Date(track.updatedAt || track.uploadedAt).toLocaleString()
10053      ] })
10054    ] }),
10055    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between gap-2 mt-0.5 pb-2 border-b border-zinc-200 dark:border-zinc-800", children: [
10056      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 md:gap-4 min-w-0 flex-1", children: [
10057        !isCommunityContributor ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10058          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group cursor-pointer shrink-0", onClick: () => onChannelSelect(track.uploader.name), children: [
10059            /* @__PURE__ */ jsxRuntimeExports.jsx(UserAvatar, { src: track.uploader.avatar, name: track.uploader.name, size: 40, className: "bg-zinc-200 shadow-sm shrink-0" }),
10060            isPro(track.uploader) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute -bottom-1 -right-1 text-white text-[8px] font-bold px-1.5 rounded-full border border-white dark:border-zinc-900 uppercase tracking-tighter shadow-sm ${((_b = track.uploader.subscriptionTier) == null ? void 0 : _b.toLowerCase()) === "premium" ? "bg-indigo-600" : "bg-amber-500"}`, children: ((_c = track.uploader.subscriptionTier) == null ? void 0 : _c.toLowerCase()) === "premium" ? "PREMIUM" : ((_d = track.uploader.subscriptionTier) == null ? void 0 : _d.toUpperCase()) || "PRO" })
10061          ] }),
10062          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", ref: containerRef, children: [
10063            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 md:gap-2 min-w-0", children: [
10064              /* @__PURE__ */ jsxRuntimeExports.jsx(
10065                "h3",
10066                {
10067                  ref: nameRef,
10068                  className: getNameClasses(),
10069                  onClick: () => onChannelSelect(track.uploader.name),
10070                  title: track.uploader.name,
10071                  children: track.uploader.name
10072                }
10073              ),
10074              track.uploader.id === -1 && track.countryCode ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: track.countryCode, size: 16 }) }) : track.uploader.id !== -1 && track.uploader.showCountryBadge && track.uploader.country ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: track.uploader.country, size: 16 }) }) : null
10075            ] }),
10076            track.uploader.id !== -1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] md:text-xs text-zinc-500 leading-normal", children: [
10077              formatNumber(track.uploader.subscriberCount || 0),
10078              " ",
10079              t("common.subscribers", "subscribers")
10080            ] })
10081          ] })
10082        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1 flex items-center gap-1.5 py-2 relative group/tooltip", children: [
10083          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 cursor-help", children: [
10084            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs md:text-sm italic text-zinc-500 dark:text-zinc-400 font-medium select-none", children: t("watch.fromCommunityContributor", "from a community contributor") }),
10085            /* @__PURE__ */ jsxRuntimeExports.jsx(
10086              Info,
10087              {
10088                size: 14,
10089                className: "text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 transition-colors shrink-0"
10090              }
10091            )
10092          ] }),
10093          track.countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 ml-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: track.countryCode, size: 14 }) }),
10094          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute left-0 top-full mt-2 hidden group-hover/tooltip:flex flex-col w-72 md:w-80 p-4 rounded-xl bg-zinc-900/95 dark:bg-zinc-800/95 backdrop-blur-md text-white text-xs leading-relaxed shadow-xl border border-white/10 dark:border-zinc-700/80 z-50 transition-all duration-300 animate-in fade-in slide-in-from-top-1", children: [
10095            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1.5 font-semibold text-amber-400", children: [
10096              /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 14, className: "shrink-0" }),
10097              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Community Contributor Status" })
10098            ] }),
10099            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-300 font-normal leading-relaxed", children: t("watch.communityContributorTooltip", "Community Contributors are guest users who upload MIDI files without registering. These files are public and maintained by the community.") }),
10100            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-6 -mt-1.5 w-3 h-3 bg-zinc-900 dark:bg-zinc-800 border-t border-l border-white/10 dark:border-zinc-700/80 rotate-45" })
10101          ] })
10102        ] }),
10103        (!currentUser || currentUser.id !== track.uploader.id) && track.uploader.id !== -1 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onSubscribe, className: `ml-2 px-4 py-2 rounded-full font-bold text-sm transition-all shrink-0 ${isSubscribed ? "bg-zinc-100 dark:bg-zinc-800 text-zinc-900 dark:text-white" : "bg-zinc-900 dark:bg-white text-white dark:text-black"}`, children: isSubscribed ? t("watch.subscribed", "Subscribed") : t("watch.subscribe", "Subscribe") })
10104      ] }),
10105      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap justify-end", children: [
10106        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-100 dark:bg-zinc-800 rounded-full p-1", children: [
10107          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => toggleLike(track.id), className: `flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${likedMidis.has(track.id) ? "bg-zinc-200 dark:bg-zinc-700 text-black dark:text-white" : "hover:bg-zinc-200 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300"}`, children: [
10108            /* @__PURE__ */ jsxRuntimeExports.jsx(ThumbsUp, { size: 18, className: likedMidis.has(track.id) ? "fill-current" : "" }),
10109            formatNumber(track.likesCount + (likedMidis.has(track.id) ? 1 : 0))
10110          ] }),
10111          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-6 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
10112          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "px-3 py-1.5 rounded-full hover:bg-zinc-200 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ThumbsDown, { size: 18 }) })
10113        ] }),
10114        /* @__PURE__ */ jsxRuntimeExports.jsxs(
10115          "button",
10116          {
10117            onClick: () => setIsShareModalOpen(true),
10118            className: "flex items-center gap-2 px-4 py-2.5 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-full text-sm font-bold text-zinc-700 dark:text-zinc-300 transition-colors",
10119            children: [
10120              /* @__PURE__ */ jsxRuntimeExports.jsx(Share2, { size: 18 }),
10121              " ",
10122              t("watch.share", "Share")
10123            ]
10124          }
10125        ),
10126        /* @__PURE__ */ jsxRuntimeExports.jsx(
10127          ShareModal,
10128          {
10129            isOpen: isShareModalOpen,
10130            onClose: () => setIsShareModalOpen(false),
10131            currentTime,
10132            duration,
10133            trackTitle: track.title,
10134            trackId: track.id
10135          }
10136        ),
10137        /* @__PURE__ */ jsxRuntimeExports.jsxs(
10138          "a",
10139          {
10140            href: `/media/opus/${track.id}.opus`,
10141            onClick: (e) => {
10142              e.preventDefault();
10143              if (!isDownloading && track.opusPath) {
10144                e.stopPropagation();
10145                handleSecureDownload("opus");
10146              }
10147            },
10148            className: `flex items-center gap-2 px-4 py-2.5 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-full text-sm font-bold text-zinc-700 dark:text-zinc-300 transition-colors ${isDownloading || !track.opusPath ? "opacity-50 cursor-not-allowed pointer-events-none" : ""}`,
10149            title: !track.opusPath ? t("watch.opusNotAvailable", "Opus audio not available") : "",
10150            children: [
10151              /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { size: 18 }),
10152              " ",
10153              isDownloading ? "..." : "Opus"
10154            ]
10155          }
10156        ),
10157        /* @__PURE__ */ jsxRuntimeExports.jsxs(
10158          "a",
10159          {
10160            href: `/media/midi/${track.id}.mid`,
10161            onClick: (e) => {
10162              e.preventDefault();
10163              if (!isDownloading) {
10164                e.stopPropagation();
10165                handleSecureDownload("clean");
10166              }
10167            },
10168            className: `flex items-center gap-2 px-4 py-2.5 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-full text-sm font-bold text-zinc-700 dark:text-zinc-300 transition-colors ${isDownloading ? "opacity-50 cursor-not-allowed pointer-events-none" : ""}`,
10169            children: [
10170              /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { size: 18 }),
10171              " ",
10172              isDownloading ? "..." : t("common.midi", "MIDI")
10173            ]
10174          }
10175        ),
10176        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative ml-2", ref: menuRef, children: [
10177          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsMenuOpen(!isMenuOpen), className: "p-2.5 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-full text-zinc-700 dark:text-zinc-300 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Ellipsis, { size: 18 }) }),
10178          isMenuOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute right-0 top-12 w-48 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl shadow-xl z-50 overflow-hidden py-1", onClick: (e) => e.stopPropagation(), children: [
10179            /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: `/editor/${track.id}`, className: "w-full text-left px-4 py-2.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2", children: [
10180              /* @__PURE__ */ jsxRuntimeExports.jsx(SquarePen, { size: 16 }),
10181              " ",
10182              t("watch.editSong", "Edit this song")
10183            ] }),
10184            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10185            /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => {
10186              setIsMenuOpen(false);
10187              onAddToPlaylist();
10188            }, className: "w-full text-left px-4 py-2.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2", children: [
10189              /* @__PURE__ */ jsxRuntimeExports.jsx(FolderPlus, { size: 16 }),
10190              " ",
10191              t("playlists.saveTo", "Save to Playlist")
10192            ] }),
10193            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10194            /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => {
10195              setIsMenuOpen(false);
10196              onReport();
10197            }, className: "w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2", children: [
10198              /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 16 }),
10199              " ",
10200              t("watch.report", "Report")
10201            ] }),
10202            track.userId === -1 && currentUser && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10203              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10204              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10205                "button",
10206                {
10207                  onClick: () => {
10208                    setIsMenuOpen(false);
10209                    if (onEditWiki) onEditWiki();
10210                  },
10211                  className: "w-full text-left px-4 py-2.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2",
10212                  children: [
10213                    /* @__PURE__ */ jsxRuntimeExports.jsx(SquarePen, { size: 16 }),
10214                    " Edit Wiki Info"
10215                  ]
10216                }
10217              )
10218            ] }),
10219            currentUser && (currentUser.id === track.userId || currentUser.role === "ADMIN") && /* @__P
10219URE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10220              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10221              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10222                "button",
10223                {
10224                  onClick: () => {
10225                    setIsMenuOpen(false);
10226                    if (onCustomizeBackground) onCustomizeBackground();
10227                  },
10228                  className: "w-full text-left px-4 py-2.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2",
10229                  children: [
10230                    /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 16 }),
10231                    " Customize Background"
10232                  ]
10233                }
10234              ),
10235              track.backgroundImageUrl && /* @__PURE__ */ jsxRuntimeExports.jsxs(
10236                "button",
10237                {
10238                  onClick: () => {
10239                    setIsMenuOpen(false);
10240                    if (onRemoveBackground) onRemoveBackground();
10241                  },
10242                  className: "w-full text-left px-4 py-2.5 text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2",
10243                  children: [
10244                    /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 16 }),
10245                    " Remove Background"
10246                  ]
10247                }
10248              )
10249            ] }),
10250            currentUser && currentUser.id === track.userId && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10251              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10252              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10253                "button",
10254                {
10255                  onClick: async () => {
10256                    setIsMenuOpen(false);
10257                    if (confirm(t("watch.confirmDelete", "Are you sure you want to delete this MIDI? It will be removed from your profile and search lists."))) {
10258                      try {
10259                        await api.deleteTrackAsUser(track.id, currentUser.id);
10260                        alert(t("watch.deleteSuccess", "MIDI deleted successfully."));
10261                        window.location.href = "/";
10262                      } catch (err) {
10263                        alert(err.message || "Deletion failed");
10264                      }
10265                    }
10266                  },
10267                  className: "w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2 font-bold",
10268                  children: [
10269                    /* @__PURE__ */ jsxRuntimeExports.jsx(Trash, { size: 16 }),
10270                    " ",
10271                    t("watch.deleteTrack", "Delete MIDI")
10272                  ]
10273                }
10274              )
10275            ] }),
10276            (currentUser == null ? void 0 : currentUser.role) === "ADMIN" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10277              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10278              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10279                "a",
10280                {
10281                  href: `${((_e = window.HAMIE_ENV) == null ? void 0 : _e.ADMIN_URL) || ""}/admin/media?id=${track.id}`,
10282                  target: "_blank",
10283                  rel: "noopener noreferrer",
10284                  onClick: () => setIsMenuOpen(false),
10285                  className: "w-full text-left px-4 py-2.5 text-sm text-indigo-600 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2 font-bold",
10286                  children: [
10287                    /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 16 }),
10288                    " ",
10289                    t("watch.mediaLibrary", "Media Library")
10290                  ]
10291                }
10292              )
10293            ] })
10294          ] })
10295        ] })
10296      ] }),
10297      /* @__PURE__ */ jsxRuntimeExports.jsx(
10298        EducationalDisclaimerModal,
10299        {
10300          isOpen: isDisclaimerOpen,
10301          onClose: () => {
10302            setIsDisclaimerOpen(false);
10303            setPendingDownloadType(null);
10304          },
10305          onAccept: async () => {
10306            setIsDisclaimerOpen(false);
10307            localStorage.setItem("hamie_edu_accepted", "true");
10308            setHasAcceptedTerms(true);
10309            if (currentUser) {
10310              try {
10311                await fetch("/api/user/accept-educational", { method: "POST" });
10312              } catch (e) {
10313                console.warn("Could not sync educational acceptance to DB", e);
10314              }
10315            }
10316            if (pendingDownloadType) {
10317              handleSecureDownload(pendingDownloadType, true);
10318              setPendingDownloadType(null);
10319            }
10320          }
10321        }
10322      )
10323    ] }),
10324    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 bg-zinc-100 dark:bg-zinc-800/50 rounded-xl p-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors cursor-text group/desc", children: [
10325      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-2 text-xs font-bold text-zinc-900 dark:text-white mb-2 underline-offset-4", children: [
10326        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10327          formatNumber(track.views || 0),
10328          " ",
10329          t("common.views", "views")
10330        ] }),
10331        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "•" }),
10332        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10333          formatNumber(track.plays || 0),
10334          " ",
10335          t("common.plays", "plays")
10336        ] }),
10337        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "•" }),
10338        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10339          formatNumber((track.midiDownloads || 0) + (track.audioDownloads || 0)),
10340          " ",
10341          t("common.downloads", "downloads")
10342        ] }),
10343        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "•" }),
10344        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: timeAgo(track.uploadedAt) })
10345      ] }),
10346      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap font-medium", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LinkifiedText, { text: track.description, trustScore: track.uploader.trustScore || 0 }) }),
10347      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2.5 pt-2.5 border-t border-zinc-200/50 dark:border-zinc-700/50 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-zinc-500 dark:text-zinc-400", children: [
10348        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10349          /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 13, className: "opacity-70" }),
10350          " ",
10351          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10352            t("watch.duration", "Duration"),
10353            ": ",
10354            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: track.duration })
10355          ] })
10356        ] }),
10357        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10358        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10359          /* @__PURE__ */ jsxRuntimeExports.jsx(Music2, { size: 13, className: "opacity-70" }),
10360          " ",
10361          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10362            t("watch.tempo", "Tempo"),
10363            ": ",
10364            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: [
10365              ((_f = track.midiMetadata) == null ? void 0 : _f.tempo) || 120,
10366              " ",
10367              t("watch.bpm", "BPM")
10368            ] })
10369          ] })
10370        ] }),
10371        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10372        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10373          /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 13, className: "opacity-70" }),
10374          " ",
10375          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10376            t("watch.signature", "Signature"),
10377            ": ",
10378            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: ((_g = track.midiMetadata) == null ? void 0 : _g.timeSignature) || "4/4" })
10379          ] })
10380        ] }),
10381        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10382        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10383          /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 13, className: "opacity-70" }),
10384          " ",
10385          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10386            t("watch.key", "Key"),
10387            ": ",
10388            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: ((_h = track.midiMetadata) == null ? void 0 : _h.keySignature) || "C Major" })
10389          ] })
10390        ] }),
10391        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10392        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10393          /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 13, className: "opacity-70" }),
10394          " ",
10395          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10396            t("watch.tracks", "Tracks"),
10397            ": ",
10398            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: ((_i = track.midiMetadata) == null ? void 0 : _i.tracksCount) || 1 })
10399          ] })
10400        ] }),
10401        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10402        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10403          /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 13, className: "opacity-70" }),
10404          " ",
10405          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10406            t("watch.resolution", "Resolution"),
10407            ": ",
10408            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: [
10409              ((_j = track.midiMetadata) == null ? void 0 : _j.tickDiv) || 480,
10410              " PPQ"
10411            ] })
10412          ] })
10413        ] }),
10414        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10415        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10416          /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 13, className: "opacity-70" }),
10417          " ",
10418          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10419            t("watch.format", "Format"),
10420            ": ",
10421            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: [
10422              "SMF Type ",
10423              ((_k = track.midiMetadata) == null ? void 0 : _k.format) !== void 0 ? track.midiMetadata.format : 1
10424            ] })
10425          ] })
10426        ] }),
10427        ((_l = track.midiMetadata) == null ? void 0 : _l.karaokeFormat) && /* @__P
10427URE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10428          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 dark:text-zinc-700 select-none", children: "•" }),
10429          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10430            /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 13, className: "opacity-70" }),
10431            " ",
10432            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
10433              t("watch.karaoke", "Lyrics Format"),
10434              ": ",
10435              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-800 dark:text-zinc-200 font-semibold", children: track.midiMetadata.karaokeFormat === "kar" ? "KAR" : "Standard" })
10436            ] })
10437          ] })
10438        ] })
10439      ] }),
10440      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 flex flex-wrap gap-1.5 items-center", children: [
10441        tags.sort((a, b) => b.score - a.score).slice(0, 10).map((tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-white dark:bg-zinc-900/50 rounded-full border border-zinc-200 dark:border-zinc-700 pl-2.5 pr-0.5 py-0 transition-all hover:border-zinc-300 dark:hover:border-zinc-600", children: [
10442          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => onTagSelect(tag.name), className: "text-xs font-medium text-zinc-600 dark:text-zinc-400 hover:text-indigo-600 dark:hover:text-indigo-300 transition-colors mr-2", children: [
10443            "#",
10444            tag.name
10445          ] }),
10446          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-0", children: [
10447            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => onVoteTag(tag.name, 1), className: "p-0.5 text-zinc-400 hover:text-emerald-500 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 rounded transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowUp, { size: 11 }) }),
10448            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => onVoteTag(tag.name, -1), className: "p-0.5 text-zinc-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDown, { size: 11 }) })
10449          ] })
10450        ] }, tag.id)),
10451        /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: onOpenTagModal, className: "text-xs text-indigo-600 dark:text-indigo-400 hover:underline font-bold px-2 flex items-center gap-1", children: [
10452          /* @__PURE__ */ jsxRuntimeExports.jsx(Hash, { size: 10 }),
10453          " ",
10454          t("watch.addTags", "Add Tags")
10455        ] })
10456      ] })
10457    ] }),
10458    governanceUser && /* @__PURE__ */ jsxRuntimeExports.jsx(
10459      GovernanceModal,
10460      {
10461        user: governanceUser,
10462        isOpen: isGovernModalOpen,
10463        onClose: () => setIsGovernModalOpen(false)
10464      }
10465    )
10466  ] });
10467};
10468const CommentItem = ({ comment, currentUser, onReport, onVote, onReply, t, isReply = false, onHide, onGovern }) => {
10469  const { staffModeEnabled } = useAdmin();
10470  const [isMenuOpen, setIsMenuOpen] = reactExports.useState(false);
10471  const [isReplying, setIsReplying] = reactExports.useState(false);
10472  const [replyText, setReplyText] = reactExports.useState("");
10473  const menuRef = reactExports.useRef(null);
10474  reactExports.useEffect(() => {
10475    const handleClickOutside = (event) => {
10476      if (menuRef.current && !menuRef.current.contains(event.target)) {
10477        setIsMenuOpen(false);
10478      }
10479    };
10480    document.addEventListener("mousedown", handleClickOutside);
10481    return () => document.removeEventListener("mousedown", handleClickOutside);
10482  }, []);
10483  const handleVote = (value) => {
10484    if (!currentUser) return alert("Please sign in to vote");
10485    onVote(comment.id, value);
10486  };
10487  const handleReplySubmit = (e) => {
10488    e.preventDefault();
10489    if (!replyText.trim() || !onReply) return;
10490    onReply(comment.id, replyText);
10491    setReplyText("");
10492    setIsReplying(false);
10493  };
10494  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex gap-4 group relative ${isReply ? "ml-12 scale-95" : ""}`, children: [
10495    /* @__PURE__ */ jsxRuntimeExports.jsx(UserAvatar, { src: comment.avatar, name: comment.user, size: isReply ? 32 : 40 }),
10496    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
10497      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
10498        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-xs text-zinc-900 dark:text-white", children: comment.user }),
10499        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500", children: timeAgo(comment.timestamp) })
10500      ] }),
10501      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-800 dark:text-zinc-200 leading-relaxed break-words", children: comment.text }),
10502      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mt-2", children: [
10503        /* @__PURE__ */ jsxRuntimeExports.jsxs(
10504          "button",
10505          {
10506            onClick: () => handleVote(1),
10507            className: "flex items-center gap-1 text-xs text-zinc-500 hover:text-indigo-600 transition-colors",
10508            children: [
10509              /* @__PURE__ */ jsxRuntimeExports.jsx(ThumbsUp, { size: 14 }),
10510              " ",
10511              comment.likes || 0
10512            ]
10513          }
10514        ),
10515        /* @__PURE__ */ jsxRuntimeExports.jsx(
10516          "button",
10517          {
10518            onClick: () => handleVote(-1),
10519            className: "flex items-center gap-1 text-xs text-zinc-500 hover:text-red-600 transition-colors",
10520            children: /* @__PURE__ */ jsxRuntimeExports.jsx(ThumbsDown, { size: 14 })
10521          }
10522        ),
10523        !isReply && /* @__PURE__ */ jsxRuntimeExports.jsx(
10524          "button",
10525          {
10526            onClick: () => setIsReplying(!isReplying),
10527            className: "text-xs text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200 font-bold transition-colors",
10528            children: t("watch.reply", "Reply")
10529          }
10530        )
10531      ] }),
10532      isReplying && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 flex gap-3 animate-in fade-in slide-in-from-top-2", children: [
10533        /* @__PURE__ */ jsxRuntimeExports.jsx(UserAvatar, { src: currentUser == null ? void 0 : currentUser.avatar, name: (currentUser == null ? void 0 : currentUser.username) || "", size: 24 }),
10534        /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { className: "flex-1", onSubmit: handleReplySubmit, children: [
10535          /* @__PURE__ */ jsxRuntimeExports.jsx(
10536            "input",
10537            {
10538              autoFocus: true,
10539              type: "text",
10540              value: replyText,
10541              onChange: (e) => setReplyText(e.target.value),
10542              placeholder: t("watch.addReply", "Add a reply..."),
10543              className: "w-full bg-transparent border-b border-zinc-300 dark:border-zinc-700 py-1 text-sm focus:border-indigo-500 outline-none text-zinc-900 dark:text-white transition-colors"
10544            }
10545          ),
10546          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end mt-2 gap-2", children: [
10547            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setIsReplying(false), className: "px-3 py-1.5 rounded-full text-xs font-bold text-zinc-600 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800", children: t("common.cancel", "Cancel") }),
10548            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "submit", disabled: !replyText.trim(), className: "px-3 py-1.5 rounded-full text-xs font-bold bg-indigo-600 hover:bg-indigo-700 text-white disabled:opacity-50", children: t("watch.replyBtn", "Reply") })
10549          ] })
10550        ] })
10551      ] })
10552    ] }),
10553    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", ref: menuRef, children: [
10554      /* @__PURE__ */ jsxRuntimeExports.jsx(
10555        "button",
10556        {
10557          onClick: (e) => {
10558            e.stopPropagation();
10559            setIsMenuOpen(!isMenuOpen);
10560          },
10561          className: `p-2 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full transition-all self-start ${isMenuOpen ? "opacity-100 bg-zinc-100 dark:bg-zinc-800" : "opacity-0 group-hover:opacity-100"}`,
10562          children: /* @__PURE__ */ jsxRuntimeExports.jsx(EllipsisVertical, { size: 16 })
10563        }
10564      ),
10565      isMenuOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-0 top-8 w-40 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in zoom-in-95", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "py-1", children: [
10566        /* @__PURE__ */ jsxRuntimeExports.jsxs(
10567          "button",
10568          {
10569            onClick: () => {
10570              setIsMenuOpen(false);
10571              onReport(comment.id);
10572            },
10573            className: "w-full text-left px-4 py-2.5 text-sm text-zinc-600 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 flex items-center gap-2 transition-colors",
10574            children: [
10575              /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 14 }),
10576              " ",
10577              t("watch.report", "Report")
10578            ]
10579          }
10580        ),
10581        staffModeEnabled && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10582          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-px bg-zinc-200 dark:bg-zinc-800 my-1" }),
10583          /* @__PURE__ */ jsxRuntimeExports.jsxs(
10584            "button",
10585            {
10586              onClick: () => {
10587                setIsMenuOpen(false);
10588                onHide == null ? void 0 : onHide(comment.id);
10589              },
10590              className: "w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 flex items-center gap-2 transition-colors font-bold",
10591              children: [
10592                /* @__PURE__ */ jsxRuntimeExports.jsx(Flag, { size: 14, className: "text-red-500" }),
10593                " ",
10594                t("watch.hide", "Hide Comment")
10595              ]
10596            }
10597          ),
10598          /* @__PURE__ */ jsxRuntimeExports.jsxs(
10599            "button",
10600            {
10601              onClick: () => {
10602                setIsMenuOpen(false);
10603                onGovern == null ? void 0 : onGovern(comment.userId);
10604              },
10605              className: "w-full text-left px-4 py-2.5 text-sm text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 flex items-center gap-2 transition-colors font-bold",
10606              children: [
10607                /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldAlert, { size: 14, className: "text-indigo-500" }),
10608                " ",
10609                t("watch.governance", "Govern User")
10610              ]
10611            }
10612          )
10613        ] })
10614      ] }) })
10615    ] })
10616  ] });
10617};
10618const CommentSection = ({ trackId, comments, setComments, currentUser, onReport }) => {
10619  const { t } = useTranslation();
10620  const { staffModeEnabled } = useAdmin();
10621  const [newComment, setNewComment] = reactExports.useState("");
10622  const [governanceUser, setGovernanceUser] = reactExports.useState(null);
10623  const [isGovernModalOpen, setIsGovernModalOpen] = reactExports.useState(false);
10624  const handleGovern = async (userId) => {
10625    try {
10626      const user = await api.getAdminUser(userId);
10627      setGovernanceUser(user);
10628      setIsGovernModalOpen(true);
10629    } catch (e) {
10630      console.error(e);
10631      alert("Failed to load user details");
10632    }
10633  };
10634  const handlePostComment = async (e) => {
10635    e.preventDefault();
10636    if (!newComment.trim() || !currentUser) return;
10637    const comment = await api.postComment(trackId, currentUser.id, newComment);
10638    setComments([comment, ...comments]);
10639    setNewComment("");
10640  };
10641  const handleVote = async (commentId, value) => {
10642    if (!currentUser) return;
10643    try {
10644      const { likes } = await api.voteComment(commentId, currentUser.id, value);
10645      setComments(comments.map((c) => c.id === commentId ? { ...c, likes } : c));
10646    } catch (e) {
10647      console.error("Failed to vote", e);
10648    }
10649  };
10650  const handlePostReply = async (parentId, text) => {
10651    if (!currentUser) return;
10652    try {
10653      const reply = await api.postComment(trackId, currentUser.id, text, parentId);
10654      setComments([reply, ...comments]);
10655    } catch (e) {
10656      console.error("Failed to post reply", e);
10657    }
10658  };
10659  const handleHideComment = async (commentId) => {
10660    if (!staffModeEnabled) return;
10661    if (!confirm("Are you sure you want to hide this comment? This will also penalize the user's trust score.")) return;
10662    try {
10663      await api.toggleCommentVisibility(commentId);
10664      setComments(comments.filter((c) => c.id !== commentId));
10665    } catch (e) {
10666      console.error(e);
10667      alert("Failed to moderate comment");
10668    }
10669  };
10670  const parentComments = comments.filter((c) => !c.parentId);
10671  const getRepliesForParent = (parentId) => comments.filter((c) => c.parentId === parentId).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
10672  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-8 pt-6 border-t border-zinc-200 dark:border-zinc-800", children: [
10673    /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-xl font-bold text-zinc-900 dark:text-white mb-4", children: [
10674      comments.length,
10675      " ",
10676      t("watch.comments", "Comments")
10677    ] }),
10678    currentUser && currentUser.id !== -1 ? currentUser.isJailed || currentUser.suspendedUntil && new Date(currentUser.suspendedUntil) > /* @__PURE__ */ new Date() ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-amber-50 dark:bg-amber-900/10 rounded-lg mb-8 border border-amber-200 dark:border-amber-800/30", children: [
10679      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-amber-700 dark:text-amber-400 font-bold mb-1", children: "⚖️ Account Restricted" }),
10680      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-amber-600 dark:text-amber-500", children: "Your account is currently under review by the Hamie Tribunal. You can browse but cannot post comments." })
10681    ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mb-8", children: [
10682      /* @__PURE__ */ jsxRuntimeExports.jsx(UserAvatar, { src: currentUser.avatar, name: currentUser.username, size: 40 }),
10683      /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { className: "flex-1", onSubmit: handlePostComment, children: [
10684        /* @__PURE__ */ jsxRuntimeExports.jsx(
10685          "input",
10686          {
10687            type: "text",
10688            value: newComment,
10689            onChange: (e) => setNewComment(e.target.value),
10690            placeholder: t("watch.addComment", "Add a comment..."),
10691            className: "w-full bg-transparent border-b border-zinc-300 dark:border-zinc-700 py-2 focus:border-indigo-500 outline-none text-zinc-900 dark:text-white transition-colors"
10692          }
10693        ),
10694        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end mt-2 gap-2", children: [
10695          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setNewComment(""), className: "px-4 py-2 rounded-full text-sm font-bold text-zinc-600 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800", children: t("common.cancel", "Cancel") }),
10696          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "submit", disabled: !newComment, className: "px-4 py-2 rounded-full text-sm font-bold bg-indigo-600 hover:bg-indigo-700 text-white disabled:opacity-50", children: t("watch.commentBtn", "Comment") })
10697        ] })
10698      ] })
10699    ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-800/50 rounded-lg text-center mb-8 border border-zinc-200 dark:border-zinc-700 border-dashed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: t("watch.signInToComment", "Please sign in or register to join the conversation.") }) }),
10700    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-8", children: parentComments.map((comment) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
10701      /* @__PURE__ */ jsxRuntimeExports.jsx(
10702        CommentItem,
10703        {
10704          comment,
10705          currentUser,
10706          onReport,
10707          onVote: handleVote,
10708          onReply: handlePostReply,
10709          onHide: handleHideComment,
10710          onGovern: handleGovern,
10711          t
10712        }
10713      ),
10714      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4", children: getRepliesForParent(comment.id).map((reply) => /* @__PURE__ */ jsxRuntimeExports.jsx(
10715        CommentItem,
10716        {
10717          comment: reply,
10718          currentUser,
10719          onReport,
10720          onVote: handleVote,
10721          onHide: handleHideComment,
10722          onGovern: handleGovern,
10723          t,
10724          isReply: true
10725        },
10726        reply.id
10727      )) })
10728    ] }, comment.id)) }),
10729    governanceUser && /* @__PURE__ */ jsxRuntimeExports.jsx(
10730      GovernanceModal,
10731      {
10732        user: governanceUser,
10733        isOpen: isGovernModalOpen,
10734        onClose: () => setIsGovernModalOpen(false)
10735      }
10736    )
10737  ] });
10738};
10739const cleanGluedWords = (txt) => {
10740  return splitGluedWords(txt);
10741};
10742const shouldAddSpace = (prev, curr) => {
10743  if (!prev || !curr) return false;
10744  if (prev.endsWith(" ") || prev.endsWith(" ") || curr.startsWith(" ") || curr.startsWith(" ")) return false;
10745  if (shouldJoinSyllables(prev, curr)) return false;
10746  return true;
10747};
10748const isCapitalizedStart = (txt) => {
10749  const clean = txt.trim();
10750  if (!/^[A-Z]/.test(clean)) return false;
10751  if (clean.length === 1) return false;
10752  if (clean.length === 2 && /^[A-Z][\.,!\?;:]/.test(clean)) return false;
10753  return true;
10754};
10755const groupLyricsIntoLines = (lyrics) => {
10756  const preprocessed = [];
10757  for (let i = 0; i < lyrics.length; i++) {
10758    const syl = lyrics[i];
10759    const cleaned = cleanGluedWords(syl.txt);
10760    const parts = cleaned.split(" ");
10761    if (parts.length > 1) {
10762      parts.forEach((part, idx) => {
10763        preprocessed.push({
10764          t: syl.t + idx * 0.01,
10765          txt: part
10766        });
10767      });
10768    } else {
10769      preprocessed.push({
10770        t: syl.t,
10771        txt: cleaned
10772      });
10773    }
10774  }
10775  const lines = [];
10776  let currentLine = [];
10777  for (let i = 0; i < preprocessed.length; i++) {
10778    const syl = preprocessed[i];
10779    let txt = syl.txt;
10780    const isNewLine = txt.startsWith("/") || txt.startsWith("\\") || txt.includes("\n") || txt.includes("\r");
10781    if (isNewLine && currentLine.length > 0) {
10782      lines.push({
10783        startTime: currentLine[0].t,
10784        endTime: currentLine[currentLine.length - 1].t,
10785        syllables: currentLine
10786      });
10787      currentLine = [];
10788    }
10789    txt = txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ");
10790    if (currentLine.length > 0) {
10791      const prevSyl = currentLine[currentLine.length - 1];
10792      if (shouldAddSpace(prevSyl.txt, txt)) {
10793        txt = " " + txt;
10794      }
10795    }
10796    currentLine.push({ t: syl.t, txt });
10797    if (i < preprocessed.length - 1) {
10798      const nextSyl = preprocessed[i + 1];
10799      const cleanNextTxt = nextSyl.txt.replace(/^[\/\\]/, "").replace(/[\n\r]/g, "").replace(/`/g, " ");
10800      const hasGap = nextSyl.t - syl.t > 2.5;
10801      const isCapitalized = isCapitalizedStart(cleanNextTxt);
10802      const isIWord = /^[iI]$/.test(cleanNextTxt.trim());
10803      const shouldBreakOnCapital = isCapitalized && !isIWord && currentLine.length >= 2;
10804      const isWordBoundary = cleanNextTxt.startsWith(" ") || shouldAddSpace(txt, cleanNextTxt);
10805      const isTooLong = currentLine.length >= 28 && isWordBoundary;
10806      if ((hasGap || shouldBreakOnCapital || isTooLong) && currentLine.length > 0) {
10807        lines.push({
10808          startTime: currentLine[0].t,
10809          endTime: currentLine[currentLine.length - 1].t,
10810          syllables: currentLine
10811        });
10812        currentLine = [];
10813      }
10814    }
10815  }
10816  if (currentLine.length > 0) {
10817    lines.push({
10818      startTime: currentLine[0].t,
10819      endTime: currentLine[currentLine.length - 1].t,
10820      syllables: currentLine
10821    });
10822  }
10823  return lines;
10824};
10825const WatchPage = ({
10826  activeTrack,
10827  currentUser,
10828  isPlaying,
10829  setIsPlaying,
10830  toggleLike,
10831  likedMidis,
10832  subscribe,
10833  isSubscribed,
10834  handleChannelSelect,
10835  handleSelectTrack,
10836  toggleWatchLater,
10837  watchLater,
10838  allTracks,
10839  onTagSelect,
10840  onArtistSelect,
10841  currentTime,
10842  duration,
10843  onAddToQueue,
10844  onAddToPlaylist,
10845  onAuthRequested,
10846  audioMode,
10847  setAudioMode,
10848  autoPlayCountdown,
10849  onCancelAutoPlay,
10850  onPlayNext,
10851  nextTrack,
10852  isBuffering,
10853  setIsBuffering,
10854  onSeek,
10855  playbackRate,
10856  isGameMode,
10857  setIsGameMode,
10858  audioQuality,
10859  pitchSemitones,
10860  theme,
10861  onUserUpdate
10862}) => {
10863  var _a, _b, _c, _d, _e, _f;
10864  const { t, i18n } = useTranslation();
10865  const { country } = useSettings();
10866  const { staffModeEnabled } = useAdmin();
10867  const isAdmin = (currentUser == null ? void 0 : currentUser.role) === "ADMIN";
10868  const isEmbed = reactExports.useMemo(() => {
10869    if (typeof window !== "undefined") {
10870      return new URLSearchParams(window.location.search).get("embed") === "1";
10871    }
10872    return false;
10873  }, []);
10874  const adminLog = reactExports.useCallback((...args) => {
10875    if (isAdmin) console.log(...args);
10876  }, [isAdmin]);
10877  const [rawLibraries, setRawLibraries] = reactExports.useState([]);
10878  const resolveSfToLoad = reactExports.useCallback((sfToLoad) => {
10879    var _a2;
10880    let finalSf = sfToLoad;
10881    const libEntry = rawLibraries.find((l) => l.id === sfToLoad || l.baseId === sfToLoad);
10882    if (libEntry && libEntry.autoRoute) {
10883      const uniqueCount = new Set((_a2 = activeTrack.instruments) == null ? void 0 : _a2.map((i) => i.program)).size || 1;
10884      const threshold = libEntry.autoRouteThreshold || 15;
10885      const sfBaseName = finalSf.replace(/\.h?sf[23]$/i, "").replace(/_JS$/i, "");
10886      if (uniqueCount > threshold) {
10887        finalSf = sfBaseName + ".sf3";
10888        console.log(`[WatchPage] Smart Auto-Routing: ${uniqueCount} insts > ${threshold} -> Selected ${finalSf}`);
10889      } else {
10890        finalSf = sfBaseName + "_HSF3Bank";
10891        console.log(`[WatchPage] Smart Auto-Routing: ${uniqueCount} insts <= ${threshold} -> Selected ${finalSf}`);
10892      }
10893    }
10894    return finalSf;
10895  }, [rawLibraries, activeTrack.instruments]);
10896  const [eventsInstrument, setEventsInstrument] = reactExports.useState(null);
10897  const [instrumentOverrides, setInstrumentOverrides] = reactExports.useState({});
10898  const [fallbackInstruments, setFallbackInstruments] = reactExports.useState([]);
10899  reactExports.useEffect(() => {
10900    const handler = (e) => setFallbackInstruments(e.detail);
10901    window.addEventListener("liveFallbackTracksLoaded", handler);
10902    return () => window.removeEventListener("liveFallbackTracksLoaded", handler);
10903  }, []);
10904  reactExports.useEffect(() => {
10905    if (!activeTrack) return;
10906    const sourceInstruments = activeTrack.instruments || [];
10907    const isMissingPrograms = sourceInstruments.length > 0 && sourceInstruments.every((i) => i.program === null || i.name === "Instrument None");
10908    if (isMissingPrograms && fallbackInstruments.length === 0) {
10909      const eventsSources = [
10910        activeTrack.cleanMidiPath,
10911        activeTrack.midiPath
10912      ].filter(Boolean);
10913      if (eventsSources.length > 0) {
10914        parseMidiInstruments(eventsSources).then(({ tracks, duration: duration2 }) => {
10915          var _a2;
10916          setFallbackInstruments(tracks);
10917          if (duration2 > 0 && (!((_a2 = activeTrack.midiMetadata) == null ? void 0 : _a2.duration) || activeTrack.midiMetadata.duration === 0)) {
10918            window.dispatchEvent(new CustomEvent("setTrackDuration", { detail: duration2 }));
10919          }
10920        }).catch((err) => {
10921          console.error("[WatchPage] Eager parseMidiInstruments failed:", err);
10922        });
10923      }
10924    }
10925  }, [activeTrack, fallbackInstruments.length]);
10926  const [viewMode, setViewMode] = reactExports.useState(() => {
10927    var _a2, _b2, _c2;
10928    const hasPercussion = (_a2 = activeTrack == null ? void 0 : activeTrack.instruments) == null ? void 0 : _a2.some((inst) => inst.midiChannel === 9 && (inst.notesCount > 0 || inst.notesCount === void 0));
10929    const hasMelodicTracks = (_b2 = activeTrack == null ? void 0 : activeTrack.instruments) == null ? void 0 : _b2.some((inst) => inst.midiChannel !== 9 && (inst.notesCount > 0 || inst.notesCount === void 0));
10930    const isPercussionOnly = hasPercussion && !hasMelodicTracks;
10931    if (typeof window !== "undefined") {
10932      const params = new URLSearchParams(window.location.search);
10933      const mode = params.get("mode") || params.get("viewMode");
10934      if (mode === "score" || mode === "melody" || mode === "piano" || mode === "waveform" || mode === "karaoke" || mode === "eq" || mode === "orchestra" || mode === "percussion" || mode === "cinematic" || mode === "premium_orchestra" || mode === "taiko") {
10935        if (mode === "piano" && isPercussionOnly) {
10936          return "percussion";
10937        }
10938        const isStaff = staffModeEnabled || window.location.search.includes("ai_admin=1");
10939        if (!isStaff && (mode === "orchestra" || mode === "cinematic")) {
10940          return "premium_orchestra";
10941        }
10942        return mode;
10943      }
10944    }
10945    if (isPercussionOnly) return "percussion";
10946    if (isSlowMobile()) return "waveform";
10947    if (((_c2 = activeTrack == null ? void 0 : activeTrack.midiMetadata) == null ? void 0 : _c2.lyrics) && activeTrack.midiMetadata.lyrics.length > 0) return "karaoke";
10948    const hasPianoRoll = !!(activeTrack == null ? void 0 : activeTrack.pianoRollPath) || (activeTrack == null ? void 0 : activeTrack.isLight);
10949    if (hasPianoRoll) return "piano";
10950    return "waveform";
10951  });
10952  const [mixerMode, setMixerMode] = reactExports.useState("list");
10953  const [comments, setComments] = reactExports.useState([]);
10954  const [rawPianoRollData, setRawPianoRollData] = reactExports.useState(null);
10955  const [melodyData, setMelodyData] = reactExports.useState(void 0);
10956  const [melodySource, setMelodySource] = reactExports.useState("standard");
10957  const [hiddenTracks, setHiddenTracks] = reactExports.useState(/* @__PURE__ */ new Set());
10958  const [lyricsExpanded, setLyricsExpanded] = reactExports.useState(false);
10959  const [forceShowVisualizer, setForceShowVisualizer] = reactExports.useState(false);
10960  const [trackVolumes, setTrackVolumes] = reactExports.useState({});
10961  const [trackPans, setTrackPans] = reactExports.useState({});
10962  const [localMutedIndices, setLocalMutedIndices] = reactExports.useState(/* @__PURE__ */ new Set());
10963  const [playAlongMutedTracks, setPlayAlongMutedTracks] = reactExports.useState(/* @__PURE__ */ new Set());
10964  const [previousSoundfont, setPreviousSoundfont] = reactExports.useState(null);
10965  const [previousAudioMode, setPreviousAudioMode] = reactExports.useState(null);
10966  const [soloedIndex, setSoloedIndex] = reactExports.useState(null);
10967  const {
10968    setActiveTrack,
10969    trackStates,
10970    setTrackStates,
10971    isLiveSynthFallback,
10972    setIsLiveSynthFallback,
10973    isSynthReady,
10974    hasStartedLoading,
10975    setHasStartedLoading,
10976    soundfont,
10977    setSoundfont,
10978    engineOrder
10979  } = usePlayer();
10980  const isPlayingRef = reactExports.useRef(isPlaying);
10981  const currentTimeRef = reactExports.useRef(currentTime);
10982  const lastLoadedConfigRef = reactExports.useRef("");
10983  const lastSyncTrackIdRef = reactExports.useRef(null);
10984  reactExports.useEffect(() => {
10985    isPlayingRef.current = isPlaying;
10986  }, [isPlaying]);
10987  reactExports.useEffect(() => {
10988    currentTimeRef.current = currentTime;
10989  }, [currentTime]);
10990  const hasSynthError = reactExports.useMemo(() => {
10991    if (isLiveSynthFallback) {
10992      return Object.values(trackStates).some((status) => status && status.error);
10993    }
10994    if (!activeTrack.instruments || activeTrack.instruments.length === 0) return false;
10995    return activeTrack.instruments.some((inst, idx) => {
10996      if (!inst.stemPath) return false;
10997      const status = trackStates[idx];
10998      return status && status.error;
10999    });
11000  }, [trackStates, activeTrack.instruments, isLiveSynthFallback]);
11001  const [tags, setTags] = reactExports.useState([]);
11002  const [isTagModalOpen, setIsTagModalOpen] = reactExports.useState(false);
11003  const [isEditModalOpen, setIsEditModalOpen] = reactExports.useState(false);
11004  const [isReuploadModalOpen, setIsReuploadModalOpen] = reactExports.useState(false);
11005  const [isReportModalOpen, setIsReportModalOpen] = reactExports.useState(false);
11006  const [localBgUrl, setLocalBgUrl] = reactExports.useState(null);
11007  const [isBgModalOpen, setIsBgModalOpen] = reactExports.useState(false);
11008  const [bgUploadFile, setBgUploadFile] = reactExports.useState(null);
11009  const [bgUploadError, setBgUploadError] = reactExports.useState(null);
11010  const [bgUploadStatus, setBgUploadStatus] = reactExports.useState("idle");
11011  const [hasAcceptedCopyright, setHasAcceptedCopyright] = reactExports.useState(false);
11012  reactExports.useEffect(() => {
11013    setLocalBgUrl((activeTrack == null ? void 0 : activeTrack.backgroundImageUrl) || null);
11014    setBgUploadStatus("idle");
11015    setBgUploadFile(null);
11016    setBgUploadError(null);
11017    setHasAcceptedCopyright(false);
11018  }, [activeTrack]);
11019  const handleBackgroundUpload = async () => {
11020    if (!bgUploadFile) return;
11021    if (!hasAcceptedCopyright) {
11022      setBgUploadError("You must accept the copyright terms to upload.");
11023      return;
11024    }
11025    setBgUploadStatus("uploading");
11026    setBgUploadError(null);
11027    const formData = new FormData();
11028    formData.append("background", bgUploadFile);
11029    try {
11030      const response = await fetch(`/api/content/tracks/${activeTrack.id}/background`, {
11031        method: "POST",
11032        body: formData
11033      });
11034      if (!response.ok) {
11035        const errData = await response.json();
11036        throw new Error(errData.error || "Upload failed");
11037      }
11038      const data = await response.json();
11039      setLocalBgUrl(data.backgroundImageUrl);
11040      setBgUploadStatus("success");
11041      setTimeout(() => {
11042        setIsBgModalOpen(false);
11043      }, 1e3);
11044    } catch (err) {
11045      setBgUploadStatus("idle");
11046      setBgUploadError(err.message || "An error occurred during upload.");
11047    }
11048  };
11049  const handleBackgroundRemove = async () => {
11050    if (!confirm("Are you sure you want to remove the custom background and revert to defaults?")) return;
11051    try {
11052      const response = await fetch(`/api/content/tracks/${activeTrack.id}/background`, {
11053        method: "DELETE"
11054      });
11055      if (!response.ok) {
11056        const errData = await response.json();
11057        throw new Error(errData.error || "Failed to remove background");
11058      }
11059      setLocalBgUrl(null);
11060      alert("Custom background removed successfully.");
11061    } catch (err) {
11062      alert(err.message || "Failed to remove background.");
11063    }
11064  };
11065  const [isTheaterMode, setIsTheaterMode] = reactExports.useState(() => {
11066    if (typeof window === "undefined") return false;
11067    const pref = localStorage.getItem("theaterModePreference");
11068    if (pref === "theater") return true;
11069    if (pref === "default") return false;
11070    return window.innerWidth > 1400;
11071  });
11072  const handleSetTheaterMode = (isTheater) => {
11073    setIsTheaterMode(isTheater);
11074    localStorage.setItem("theaterModePreference", isTheater ? "theater" : "default");
11075  };
11076  const [selectedTrackIndex, setSelectedTrackIndex] = reactExports.useState("master");
11077  const [preRenderedSf, setPreRenderedSf] = reactExports.useState("GeneralUser-GS");
11078  const [defaultLiveSf, setDefaultLiveSf] = reactExports.useState("MusyngKite");
11079  const [soundfontMetadata, setSoundfontMetadata] = reactExports.useState({});
11080  reactExports.useEffect(() => {
11081    if (currentUser == null ? void 0 : currentUser.defaultSoundfont) {
11082      setSoundfont(currentUser.defaultSoundfont);
11083    } else {
11084      setSoundfont("GeneralUser-GS");
11085    }
11086  }, [currentUser, setSoundfont]);
11087  const displayedLibraries = reactExports.useMemo(() => {
11088    const isProcessed = activeTrack.status === "completed";
11089    const preSfBase = preRenderedSf.replace(/\.h?sf[23]$/, "");
11090    if (!isProcessed) {
11091      const preSfLib = rawLibraries.find((l) => l.id === preRenderedSf || l.baseId === preSfBase);
11092      const otherLibs = rawLibraries.filter((l) => l.id !== preRenderedSf && l.baseId !== preSfBase);
11093      const livePreSfLib = preSfLib ? {
11094        ...preSfLib,
11095        name: preSfBase.replace(/_/g, " "),
11096        desc: "Balanced live soundfont",
11097        isPreRendered: false,
11098        access: "free"
11099      } : {
11100        id: preRenderedSf,
11101        baseId: preSfBase,
11102        name: preSfBase.replace(/_/g, " "),
11103        desc: "Balanced live soundfont",
11104        isPreRendered: false,
11105        access: "free"
11106      };
11107      return [livePreSfLib, ...otherLibs];
11108    } else {
11109      const otherLibs = rawLibraries.filter((l) => l.id !== preRenderedSf && l.baseId !== preSfBase);
11110      return [
11111        {
11112          id: "pre-rendered",
11113          name: preSfBase.replace(/_/g, " ") + " (Pre-rendered)",
11114          desc: "High-performance pre-rendered Opus stems",
11115          isPreRendered: true,
11116          access: "free"
11117        },
11118        ...otherLibs
11119      ];
11120    }
11121  }, [rawLibraries, activeTrack.status, preRenderedSf]);
11122  const handleSoundfontChange = async (newSf) => {
11123    const selectedLib = displayedLibraries.find((l) => l.id === newSf);
11124    const isProLibrary = (selectedLib == null ? void 0 : selectedLib.access) === "pro";
11125    if (isProLibrary && !isPro(currentUser)) {
11126      console.warn("[HamieSecurity] Access Denied: This soundfont is for Pro users only.");
11127      return;
11128    }
11129    const isSfBinaryFile = newSf.toLowerCase().endsWith(".sf2") || newSf.toLowerCase().endsWith(".sf3") || newSf.toLowerCase().endsWith(".hsf3") || newSf.toLowerCase().endsWith("_hsf3bank");
11130    if (isSfBinaryFile && typeof AudioWorkletNode === "undefined") {
11131      alert(
11132        "⚠️ Browser Security Limitation:\n\nYour browser blocks WebAssembly SoundFonts (.sf2/.sf3) when accessing the site over unsecure HTTP.\n\nTo resolve this, please do one of the following:\n1. Select a JS soundfont option, which works perfectly on plain HTTP.\n2. Access the site via a secure HTTPS connection.\n3. Acce
11132ss via 'http://localhost:3002' if you are running/testing locally (browsers treat localhost as secure)."
11133      );
11134    }
11135    console.log("[WatchPage] handleSoundfontChange:", newSf);
11136    setSoundfont(newSf);
11137    if (audioMode !== "synth" && audioMode !== "wasmsynth") {
11138      setAudioMode(isSfBinaryFile ? "wasmsynth" : "synth");
11139    }
11140    try {
11141      if (currentUser && currentUser.id !== -1) {
11142        if (onUserUpdate) {
11143          await onUserUpdate({ defaultSoundfont: newSf });
11144        } else {
11145          await api.updateUser(currentUser.id, { defaultSoundfont: newSf });
11146        }
11147      }
11148    } catch (e) {
11149      console.warn("Failed to save personal soundfont default", e);
11150    }
11151  };
11152  reactExports.useEffect(() => {
11153    const loadDefaults = async () => {
11154      var _a2;
11155      try {
11156        const [sfData, preDef, metaSet, defaultLiveSet] = await Promise.all([
11157          api.getSoundfonts(),
11158          api.getSystemSetting("PRE_RENDERED_SOUNDFONT"),
11159          api.getSystemSetting("SOUNDFONT_METADATA"),
11160          api.getSystemSetting("DEFAULT_LIVE_SOUNDFONT")
11161        ]);
11162        const meta = sfData.metadata || (metaSet == null ? void 0 : metaSet.value) || {};
11163        setSoundfontMetadata(meta);
11164        const preSf = sfData.preRendered || (preDef == null ? void 0 : preDef.value) || "GeneralUser-GS";
11165        const preSfBase = preSf.replace(/\.h?sf[23]$/, "");
11166        setPreRenderedSf(preSf);
11167        let managerSf = ((_a2 = sfData.active) == null ? void 0 : _a2.filename) || sfData.active;
11168        if (managerSf && typeof managerSf === "string") {
11169          managerSf = managerSf.split("/").pop();
11170        }
11171        const activeSf = managerSf || (defaultLiveSet == null ? void 0 : defaultLiveSet.value) || "MusyngKite";
11172        setDefaultLiveSf(activeSf);
11173        const getBaseName = (name) => {
11174          return name.replace(/(_JS|_HSF3Bank|_SF3Bank)(?:_.*)?$/i, "").replace(/(?:_test_\d*)?\.h?sf[23]$/i, "");
11175        };
11176        const visibleLibs = (sfData.libraries || []).filter((lib) => {
11177          const libBase = getBaseName(lib.name);
11178          const formatMeta = meta[lib.name] || {};
11179          const folderMeta = meta[libBase] || {};
11180          const combinedMeta = { ...folderMeta, ...formatMeta };
11181          return combinedMeta.visible !== false;
11182        });
11183        const deduplicatedLibs = [];
11184        const seenAutoRoutes = /* @__PURE__ */ new Set();
11185        visibleLibs.forEach((lib) => {
11186          const libBase = getBaseName(lib.name);
11187          const folderMeta = meta[libBase] || {};
11188          if (folderMeta.autoRoute) {
11189            if (!seenAutoRoutes.has(libBase)) {
11190              seenAutoRoutes.add(libBase);
11191              deduplicatedLibs.push({ ...lib, isAutoRouted: true, idOverride: libBase });
11192            }
11193          } else {
11194            deduplicatedLibs.push(lib);
11195          }
11196        });
11197        const baseCounts = {};
11198        deduplicatedLibs.forEach((lib) => {
11199          const libBase = getBaseName(lib.name);
11200          baseCounts[libBase] = (baseCounts[libBase] || 0) + 1;
11201        });
11202        const processedLibs = deduplicatedLibs.map((lib) => {
11203          const libBase = getBaseName(lib.name);
11204          const isSf2 = lib.name.toLowerCase().endsWith(".sf2") || !!lib.isSf2;
11205          const isSf3 = lib.name.toLowerCase().endsWith(".sf3");
11206          const isHsf3 = lib.name.toLowerCase().endsWith("_hsf3bank") || lib.name.toLowerCase().endsWith(".hsf3");
11207          const isPre = lib.name === preSf || libBase === preSfBase;
11208          const formatMeta = meta[lib.name] || {};
11209          const folderMeta = meta[libBase] || {};
11210          const combinedMeta = { ...folderMeta, ...formatMeta };
11211          let displayName = folderMeta.name || combinedMeta.name || libBase.replace(/_/g, " ");
11212          if (baseCounts[libBase] > 1 && !lib.isAutoRouted) {
11213            if (isSf2) displayName += " (SF2)";
11214            else if (isSf3) displayName += " (SF3)";
11215            else if (isHsf3) displayName += " (HSF3)";
11216            else displayName += " (JS)";
11217          }
11218          let threshold = 15;
11219          let autoRouteEnabled = !!folderMeta.autoRoute;
11220          if (autoRouteEnabled) {
11221            if (folderMeta.autoRouteThresholdOverride != null) {
11222              threshold = folderMeta.autoRouteThresholdOverride;
11223            } else {
11224              const allFormats = sfData.libraries || [];
11225              const sf3Lib = allFormats.find((f) => f.name.toLowerCase() === libBase.toLowerCase() + ".sf3");
11226              const hsf3Lib = allFormats.find((f) => f.name.toLowerCase() === libBase.toLowerCase() + "_hsf3bank" || f.name.toLowerCase() === libBase.toLowerCase() + ".hsf3");
11227              const sf3Size = (sf3Lib == null ? void 0 : sf3Lib.size) || 0;
11228              const hsf3TotalSize = (hsf3Lib == null ? void 0 : hsf3Lib.size) || 0;
11229              if (sf3Size > 0 && hsf3TotalSize > 0) {
11230                threshold = Math.floor(sf3Size / (hsf3TotalSize / 128));
11231              }
11232            }
11233          }
11234          return {
11235            id: lib.idOverride || lib.name,
11236            baseId: libBase,
11237            name: displayName,
11238            desc: folderMeta.desc || combinedMeta.desc || (isPre ? "High-performance pre-rendered Opus stems" : ""),
11239            access: isPre ? "free" : folderMeta.access || combinedMeta.access || "free",
11240            isPreRendered: isPre,
11241            hasLive: !(isSf2 || isSf3 || isHsf3),
11242            autoRoute: autoRouteEnabled,
11243            autoRouteThreshold: threshold
11244          };
11245        });
11246        setRawLibraries(processedLibs);
11247        const hasStems = activeTrack.instruments && activeTrack.instruments.some((i) => i.stemPath);
11248        let initialSf = (currentUser == null ? void 0 : currentUser.defaultSoundfont) || (hasStems ? "pre-rendered" : activeSf);
11249        const preNorm = preSf.replace(/\.h?sf[23]$/, "");
11250        const initialNorm = initialSf.replace(/\.h?sf[23]$/, "");
11251        console.log("[WatchPage] Initial SF:", initialSf, "Pre-rendered SF:", preSf);
11252        if (initialNorm === preNorm || initialSf === "pre-rendered" || initialSf === preSf) {
11253          console.log("[WatchPage] Normalizing initial soundfont to 'pre-rendered' (Matches system pre-rendered SF:", preSf, ")");
11254          initialSf = "pre-rendered";
11255        }
11256        setSoundfont(initialSf);
11257      } catch (e) {
11258        console.warn("Failed to fetch soundfont defaults", e);
11259      }
11260    };
11261    loadDefaults();
11262  }, [currentUser]);
11263  reactExports.useEffect(() => {
11264    const resumeAudio = () => {
11265      const audioCtx = synth.getAudioContext();
11266      if (audioCtx && audioCtx.state === "suspended") {
11267        audioCtx.resume().then(() => {
11268          console.log("[WatchPage] AudioContext resumed via user gesture");
11269          window.removeEventListener("mousedown", resumeAudio);
11270          window.removeEventListener("keydown", resumeAudio);
11271          window.removeEventListener("touchstart", resumeAudio);
11272        }).catch(() => {
11273        });
11274      }
11275    };
11276    window.addEventListener("mousedown", resumeAudio);
11277    window.addEventListener("keydown", resumeAudio);
11278    window.addEventListener("touchstart", resumeAudio);
11279    return () => {
11280      window.removeEventListener("mousedown", resumeAudio);
11281      window.removeEventListener("keydown", resumeAudio);
11282      window.removeEventListener("touchstart", resumeAudio);
11283    };
11284  }, []);
11285  const isBlocked = isContentBlocked(country, (_a = activeTrack.midiMetadata) == null ? void 0 : _a.blockedRegions);
11286  const { processedInstruments, uniqueNames, colorMap: derivedColorMap } = reactExports.useMemo(() => {
11287    const map = {};
11288    let sourceInstruments = activeTrack.instruments || [];
11289    const isMissingPrograms = sourceInstruments.length > 0 && sourceInstruments.every((i) => i.program === null || i.name === "Instrument None");
11290    const hasOnlyInformational = sourceInstruments.length > 0 && sourceInstruments.every((i) => i.name === "Informational Track" || i.notesCount === 0);
11291    if (isMissingPrograms && fallbackInstruments.length > 0) {
11292      sourceInstruments = fallbackInstruments;
11293    } else if ((sourceInstruments.length === 0 || hasOnlyInformational) && rawPianoRollData && rawPianoRollData.length > 0) {
11294      const trackMap = /* @__PURE__ */ new Map();
11295      rawPianoRollData.forEach((note) => {
11296        const trackIdx = note.trackIdx !== void 0 ? note.trackIdx : note.i;
11297        if (!trackMap.has(trackIdx)) {
11298          trackMap.set(trackIdx, {
11299            trackId: trackIdx,
11300            name: typeof note.i === "string" ? note.i : `Track ${trackIdx}`,
11301            program: note.p || 0,
11302            midiChannel: note.isDrum ? 9 : 0,
11303            notesCount: 1,
11304            stemPath: null
11305          });
11306        } else {
11307          trackMap.get(trackIdx).notesCount++;
11308        }
11309      });
11310      sourceInstruments = Array.from(trackMap.values());
11311    }
11312    const processed = sourceInstruments.map((inst, idx) => {
11313      let name = inst.name;
11314      const overriddenProgram = instrumentOverrides[idx] !== void 0 ? instrumentOverrides[idx] : inst.program;
11315      if (name !== "Informational Track") {
11316        name = getInstrumentName(overriddenProgram, inst.midiChannel);
11317      }
11318      const origProg = inst.program !== null && inst.program !== void 0 ? inst.program : overriddenProgram;
11319      const origFam = inst.midiChannel === 9 ? -1 : Math.floor(origProg / 8);
11320      return {
11321        ...inst,
11322        originalProgram: origProg,
11323        originalFamilyIndex: origFam,
11324        program: overriddenProgram,
11325        displayName: String(name),
11326        originalName: inst.name,
11327        index: idx
11328      };
11329    });
11330    const nameCounts = {};
11331    const uNames = processed.map((inst, idx) => {
11332      const baseName = inst.displayName || getInstrumentName(inst.program, inst.midiChannel) || `Track ${idx + 1}`;
11333      nameCounts[baseName] = (nameCounts[baseName] || 0) + 1;
11334      const uniqueName = nameCounts[baseName] > 1 ? `${baseName} ${nameCounts[baseName]}` : baseName;
11335      return uniqueName;
11336    });
11337    uNames.forEach((uName, idx) => {
11338      map[uName] = getTrackColor(idx);
11339    });
11340    return { processedInstruments: processed, uniqueNames: uNames, colorMap: map };
11341  }, [activeTrack.instruments, i18n.language, rawPianoRollData, instrumentOverrides, fallbackInstruments]);
11342  const pianoRollData = reactExports.useMemo(() => {
11343    if (!rawPianoRollData || !Array.isArray(rawPianoRollData) || rawPianoRollData.length === 0) {
11344      return rawPianoRollData;
11345    }
11346    const musicalInstruments = processedInstruments.filter((inst) => inst.midiChannel !== -1);
11347    const updated = rawPianoRollData.map((note) => {
11348      let mappedIdx = -1;
11349      if (note.trackIdx !== void 0 && note.trackIdx >= 0 && note.trackIdx < musicalInstruments.length) {
11350        const candidate = musicalInstruments[note.trackIdx];
11351        if (candidate && candidate.midiChannel !== -1) {
11352          const rawName = (note.i || "").replace(/\s*\(\d+\)$/, "").trim().toLowerCase();
11353          const candName = (candidate.displayName || candidate.name || "").toLowerCase();
11354          if (musicalInstruments.length === 1 || !note.i || candName.includes(rawName) || rawName.includes(candName)) {
11355            mappedIdx = processedInstruments.findIndex((inst) => inst.trackId === candidate.trackId);
11356          }
11357        }
11358      }
11359      if (mappedIdx === -1 && note.trackIdx !== void 0) {
11360        const foundByTrackId = processedInstruments.findIndex(
11361          (inst) => inst.trackId === note.trackIdx && inst.midiChannel !== -1
11362        );
11363        if (foundByTrackId !== -1) {
11364          mappedIdx = foundByTrackId;
11365        }
11366      }
11367      if (mappedIdx === -1 && note.i) {
11368        const match = note.i.match(/^(.*) \((\d+)\)$/);
11369        if (match) {
11370          const targetTrackId = parseInt(match[2], 10) - 1;
11371          const baseName = match[1].trim().toLowerCase();
11372          const foundByTrackId = processedInstruments.findIndex(
11373            (inst) => inst.trackId === targetTrackId && inst.midiChannel !== -1
11374          );
11375          if (foundByTrackId !== -1) {
11376            mappedIdx = foundByTrackId;
11377          } else {
11378            const directTrackId = parseInt(match[2], 10);
11379            const foundDirect = processedInstruments.findIndex(
11380              (inst) => inst.trackId === directTrackId && inst.midiChannel !== -1
11381            );
11382            if (foundDirect !== -1) {
11383              mappedIdx = foundDirect;
11384            } else {
11385              const foundByName = musicalInstruments.findIndex(
11386                (inst) => (inst.displayName || inst.name || "").toLowerCase().includes(baseName)
11387              );
11388              if (foundByName !== -1) {
11389                mappedIdx = processedInstruments.findIndex((inst) => inst.trackId === musicalInstruments[foundByName].trackId);
11390              }
11391            }
11392          }
11393        }
11394      }
11395      if (mappedIdx === -1 && note.i) {
11396        const trackMatch = note.i.match(/^Track\s+(\d+)$/i);
11397        let targetIdx = -1;
11398        if (trackMatch) {
11399          targetIdx = parseInt(trackMatch[1], 10) - 1;
11400        } else if (!isNaN(parseInt(note.i, 10))) {
11401          targetIdx = parseInt(note.i, 10);
11402        }
11403        if (targetIdx >= 0) {
11404          const foundByTrackId = processedInstruments.findIndex(
11405            (inst) => inst.trackId === targetIdx && inst.midiChannel !== -1
11406          );
11407          if (foundByTrackId !== -1) {
11408            mappedIdx = foundByTrackId;
11409          } else if (targetIdx < musicalInstruments.length) {
11410            const matchedInst = musicalInstruments[targetIdx];
11411            mappedIdx = processedInstruments.findIndex((inst) => inst.trackId === matchedInst.trackId);
11412          }
11413        }
11414      }
11415      if (mappedIdx === -1 && note.i) {
11416        const instIdx = processedInstruments.findIndex(
11417          (inst) => inst.midiChannel !== -1 && (inst.originalName === note.i || inst.displayName === note.i || inst.name === note.i)
11418        );
11419        if (instIdx !== -1) {
11420          mappedIdx = instIdx;
11421        }
11422      }
11423      if (mappedIdx === -1 && note.trackIdx !== void 0 && note.trackIdx >= 0 && note.trackIdx < musicalInstruments.length) {
11424        const matchedInst = musicalInstruments[note.trackIdx];
11425        mappedIdx = processedInstruments.findIndex((inst) => inst.trackId === matchedInst.trackId);
11426      }
11427      if (mappedIdx !== -1) {
11428        const musicalIdx = musicalInstruments.findIndex(
11429          (inst) => inst.trackId === processedInstruments[mappedIdx].trackId
11430        );
11431        return {
11432          ...note,
11433          i: uniqueNames[mappedIdx] || note.i,
11434          trackIdx: musicalIdx !== -1 ? musicalIdx : mappedIdx !== -1 ? mappedIdx : note.trackIdx !== void 0 ? note.trackIdx : note.i
11435        };
11436      }
11437      return note;
11438    });
11439    return [...updated].sort((a, b) => (a.t || 0) - (b.t || 0));
11440  }, [rawPianoRollData, processedInstruments, uniqueNames]);
11441  const trackVisibility = reactExports.useMemo(() => {
11442    if (viewMode === "melody") {
11443      const melodyTrackNames = new Set((melodyData == null ? void 0 : melodyData.map((note) => note.i)) || []);
11444      if (!melodyData || melodyData.length === 0) return processedInstruments.map(() => true);
11445      return processedInstruments.map((inst) => melodyTrackNames.has(inst.originalName));
11446    }
11447    return uniqueNames.map((name) => !hiddenTracks.has(name));
11448  }, [viewMode, melodyData, processedInstruments, uniqueNames, hiddenTracks
11448]);
11449  reactExports.useEffect(() => {
11450    if (!activeTrack.id || uniqueNames.length === 0) return;
11451    const params = new URLSearchParams(window.location.search);
11452    const trackParam = params.get("track") || params.get("trackId") || params.get("trackNumb");
11453    if (trackParam) {
11454      const targetTrackId = parseInt(trackParam, 10);
11455      const foundInstr = processedInstruments.find(
11456        (inst) => inst.trackId === targetTrackId || inst.index === targetTrackId - 1 || inst.index === targetTrackId
11457      );
11458      if (foundInstr && foundInstr.index !== void 0) {
11459        const uniqueName = uniqueNames[foundInstr.index];
11460        const nextHidden = /* @__PURE__ */ new Set();
11461        uniqueNames.forEach((name) => {
11462          if (name !== uniqueName) {
11463            nextHidden.add(name);
11464          }
11465        });
11466        setHiddenTracks(nextHidden);
11467      }
11468    }
11469  }, [activeTrack.id, uniqueNames, processedInstruments]);
11470  const [relatedTracks, setRelatedTracks] = reactExports.useState([]);
11471  const [variations, setVariations] = reactExports.useState([]);
11472  const [hasCountedPlay, setHasCountedPlay] = reactExports.useState(false);
11473  reactExports.useEffect(() => {
11474    if (isPlaying && !hasCountedPlay) {
11475      api.recordPlay(activeTrack.id);
11476      setHasCountedPlay(true);
11477    }
11478  }, [isPlaying, hasCountedPlay, activeTrack.id]);
11479  const [hasProcessedClient, setHasProcessedClient] = reactExports.useState(false);
11480  reactExports.useEffect(() => {
11481    setHasProcessedClient(false);
11482  }, [activeTrack.id]);
11483  reactExports.useEffect(() => {
11484    if (activeTrack.status !== "pending" || hasProcessedClient) return;
11485    const runClientProcessing = async () => {
11486      try {
11487        console.log(`[WatchPage] Track #${activeTrack.id} is pending. Spawning client-side processor...`);
11488        setHasProcessedClient(true);
11489        const response = await fetch(`/api/tracks/${activeTrack.id}/midi`);
11490        if (!response.ok) return;
11491        const arrayBuffer = await response.arrayBuffer();
11492        const worker = new Worker(new URL(
11493          /* @vite-ignore */
11494          "/assets/midiProcessor.worker-DJEo_dd6.js",
11495          import.meta.url
11496        ), { type: "module" });
11497        worker.onmessage = async (e) => {
11498          const { status, data, trackId, error } = e.data;
11499          if (status === "success" && trackId === activeTrack.id) {
11500            console.log("[WatchPage] Client processing complete! Hydrating UI and submitting to API...");
11501            const updatedTrack = { ...activeTrack, instruments: data.instruments, duration: data.duration, midiMetadata: { ...activeTrack.midiMetadata, ...data } };
11502            handleSelectTrack(updatedTrack);
11503            fetch("/api/worker/submit-client-data", {
11504              method: "POST",
11505              headers: { "Content-Type": "application/json" },
11506              body: JSON.stringify({
11507                trackId: activeTrack.id,
11508                ...data
11509              })
11510            }).catch((err) => console.error("Failed to submit client data:", err));
11511          } else if (status === "error") {
11512            console.error("[WatchPage] Client worker error:", error);
11513          }
11514          worker.terminate();
11515        };
11516        worker.postMessage({ trackId: activeTrack.id, midiBuffer: arrayBuffer }, [arrayBuffer]);
11517      } catch (e) {
11518        console.error("[WatchPage] Failed to run client processing:", e);
11519      }
11520    };
11521    runClientProcessing();
11522  }, [activeTrack.id, activeTrack.status, hasProcessedClient, handleSelectTrack]);
11523  reactExports.useEffect(() => {
11524    const isUnprocessed = activeTrack.status !== "completed" && activeTrack.status !== "failed";
11525    if (!isUnprocessed) return;
11526    console.log(`[WatchPage] Track #${activeTrack.id} is unprocessed (status: ${activeTrack.status}). Starting polling for status updates...`);
11527    let timerId;
11528    const poll = () => {
11529      api.getTrack(String(activeTrack.id)).then((updated) => {
11530        var _a2, _b2;
11531        if (!updated) return;
11532        const hasNewInsts = updated.instruments && Array.isArray(updated.instruments) && updated.instruments.length > 0 && (!activeTrack.instruments || !Array.isArray(activeTrack.instruments) || activeTrack.instruments.length === 0);
11533        const hasNewPianoRoll = !!updated.pianoRollPath && !activeTrack.pianoRollPath;
11534        const hasNewMelody = !!updated.melodyPath && !activeTrack.melodyPath;
11535        const hasNewLyrics = ((_a2 = updated.midiMetadata) == null ? void 0 : _a2.lyrics) && updated.midiMetadata.lyrics.length > 0 && (!((_b2 = activeTrack.midiMetadata) == null ? void 0 : _b2.lyrics) || activeTrack.midiMetadata.lyrics.length === 0);
11536        const hasNewOpus = !!(updated.opusPath || updated.mp3PathHigh) && !(activeTrack.opusPath || activeTrack.mp3PathHigh);
11537        const statusChanged = updated.status !== activeTrack.status;
11538        if (hasNewInsts || hasNewPianoRoll || hasNewMelody || hasNewLyrics || hasNewOpus || statusChanged) {
11539          console.log(`[WatchPage] Track #${activeTrack.id} status/data updated (status: ${updated.status}). Updating track state...`);
11540          setActiveTrack((prev) => prev && prev.id === updated.id ? { ...prev, ...updated } : updated);
11541        }
11542        const isFinished = updated.status === "completed" || updated.status === "failed";
11543        if (!isFinished) {
11544          timerId = setTimeout(poll, 3e3);
11545        }
11546      }).catch((err) => {
11547        console.error("[WatchPage] Polling error:", err);
11548        timerId = setTimeout(poll, 5e3);
11549      });
11550    };
11551    timerId = setTimeout(poll, 3e3);
11552    return () => {
11553      if (timerId) clearTimeout(timerId);
11554    };
11555  }, [activeTrack.id, activeTrack.status, activeTrack.instruments, activeTrack.pianoRollPath, activeTrack.melodyPath, activeTrack.opusPath, activeTrack.mp3PathHigh, handleSelectTrack]);
11556  reactExports.useEffect(() => {
11557    var _a2, _b2, _c2;
11558    api.getComments(activeTrack.id).then(setComments);
11559    api.getRelatedTracks(activeTrack.id).then(setRelatedTracks);
11560    api.getVariations(activeTrack.id).then(setVariations);
11561    api.recordHistory(activeTrack.id, (currentUser == null ? void 0 : currentUser.id) || -1);
11562    const normalizedTags = activeTrack.tags.map(
11563      (t2) => typeof t2 === "string" ? { id: Math.random(), name: t2, slug: t2.toLowerCase(), type: "TAG", translations: {}, score: 10, source: "UPLOADER" } : t2
11564    );
11565    Promise.resolve().then(() => setTags(normalizedTags));
11566    setTrackStates({});
11567    setHasStartedLoading(false);
11568    lastLoadedConfigRef.current = "";
11569    setRawPianoRollData(null);
11570    synth.stop();
11571    const vols = {};
11572    (activeTrack.instruments || []).forEach((_, idx) => {
11573      vols[idx] = 1;
11574    });
11575    Promise.resolve().then(() => {
11576      setTrackVolumes(vols);
11577      setHiddenTracks(/* @__PURE__ */ new Set());
11578      setLocalMutedIndices(/* @__PURE__ */ new Set());
11579      setPlayAlongMutedTracks(/* @__PURE__ */ new Set());
11580      setHasCountedPlay(false);
11581    });
11582    const hasLyrics = ((_a2 = activeTrack == null ? void 0 : activeTrack.midiMetadata) == null ? void 0 : _a2.lyrics) && activeTrack.midiMetadata.lyrics.length > 0;
11583    const hasPercussion = (_b2 = activeTrack == null ? void 0 : activeTrack.instruments) == null ? void 0 : _b2.some((inst) => inst.midiChannel === 9 && (inst.notesCount > 0 || inst.notesCount === void 0));
11584    const hasMelodicTracks = (_c2 = activeTrack == null ? void 0 : activeTrack.instruments) == null ? void 0 : _c2.some((inst) => inst.midiChannel !== 9 && (inst.notesCount > 0 || inst.notesCount === void 0));
11585    const isPercussionOnly = hasPercussion && !hasMelodicTracks;
11586    let initialMode = null;
11587    if (typeof window !== "undefined") {
11588      const params = new URLSearchParams(window.location.search);
11589      const mode = params.get("mode") || params.get("viewMode");
11590      if (mode === "score" || mode === "melody" || mode === "piano" || mode === "waveform" || mode === "karaoke" || mode === "eq" || mode === "orchestra" || mode === "percussion" || mode === "cinematic" || mode === "premium_orchestra" || mode === "taiko") {
11591        const isStaff = staffModeEnabled || window.location.search.includes("ai_admin=1");
11592        if (mode === "piano" && isPercussionOnly) {
11593          initialMode = "percussion";
11594        } else if (!isStaff && (mode === "orchestra" || mode === "cinematic")) {
11595          initialMode = "premium_orchestra";
11596        } else {
11597          initialMode = mode;
11598        }
11599      }
11600    }
11601    if (initialMode) {
11602      setViewMode(initialMode);
11603    } else if (isPercussionOnly) {
11604      setViewMode("percussion");
11605    } else if (isSlowMobile()) {
11606      setViewMode("waveform");
11607    } else {
11608      const hasPianoRoll = !!(activeTrack == null ? void 0 : activeTrack.pianoRollPath) || (activeTrack == null ? void 0 : activeTrack.isLight);
11609      setViewMode(hasLyrics ? "karaoke" : hasPianoRoll ? "piano" : "waveform");
11610    }
11611    setForceShowVisualizer(false);
11612    return () => {
11613      synth.stop();
11614    };
11615  }, [activeTrack.id, staffModeEnabled]);
11616  reactExports.useEffect(() => {
11617    let isCurrent = true;
11618    const targetRollPath = activeTrack.pianoRollPath;
11619    if (targetRollPath) {
11620      fetch(targetRollPath).then((res) => {
11621        if (!res.ok) throw new Error("HTTP " + res.status);
11622        return res.json();
11623      }).then((data) => {
11624        if (!isCurrent) return;
11625        setRawPianoRollData(data);
11626      }).catch((err) => {
11627        console.error("Failed to load piano roll data", err);
11628        if (isCurrent) setRawPianoRollData([]);
11629      });
11630    } else if (!activeTrack.isLight) {
11631      Promise.resolve().then(() => {
11632        if (isCurrent) setRawPianoRollData([]);
11633      });
11634    }
11635    return () => {
11636      isCurrent = false;
11637    };
11638  }, [activeTrack.id, activeTrack.pianoRollPath, activeTrack.isLight]);
11639  reactExports.useEffect(() => {
11640    if (typeof window !== "undefined") {
11641      const params = new URLSearchParams(window.location.search);
11642      const currentMode = params.get("mode") || params.get("viewMode");
11643      if (currentMode !== viewMode) {
11644        params.set("mode", viewMode);
11645        const newSearch = params.toString();
11646        const newUrl = `${window.location.pathname}${newSearch ? `?${newSearch}` : ""}`;
11647        window.history.replaceState(window.history.state, "", newUrl);
11648      }
11649    }
11650  }, [viewMode]);
11651  reactExports.useEffect(() => {
11652    if (isPlayingRef.current && lastSyncTrackIdRef.current === activeTrack.id) {
11653      return;
11654    }
11655    lastSyncTrackIdRef.current = activeTrack.id;
11656    const hasWebMidiSupport = typeof navigator !== "undefined" && !!navigator.requestMIDIAccess;
11657    const midiOutputs = synth.getMidiOutputs() || [];
11658    const preferred = typeof window !== "undefined" ? localStorage.getItem("preferredAudioMode") : null;
11659    const best = getBestAudioMode(activeTrack, isSlowMobile(), hasWebMidiSupport, midiOutputs.length > 0, pitchSemitones, true, engineOrder, preferred);
11660    adminLog("%c[WatchPage] 🔊 Audio Mode Sync", "color:#a78bfa;font-weight:bold");
11661    adminLog("  trackId         :", activeTrack.id);
11662    adminLog("  → MODE:", best.mode, "| fallback:", best.isLiveSynthFallback);
11663    setIsLiveSynthFallback(best.isLiveSynthFallback);
11664    setAudioMode(best.mode);
11665  }, [activeTrack.id, activeTrack.status, activeTrack.opusPath, activeTrack.mp3PathHigh, pitchSemitones, setAudioMode, setIsLiveSynthFallback, adminLog, engineOrder]);
11666  reactExports.useEffect(() => {
11667    var _a2;
11668    let isCurrent = true;
11669    let melodyPathToFetch = melodySource === "studio" ? ((_a2 = activeTrack.quantizedScorePaths) == null ? void 0 : _a2.studio_melody) || activeTrack.melodyPath : activeTrack.melodyPath;
11670    if (melodyPathToFetch) {
11671      fetch(melodyPathToFetch).then((res) => {
11672        if (!res.ok) throw new Error("HTTP " + res.status);
11673        return res.json();
11674      }).then((data) => {
11675        if (!isCurrent) return;
11676        const updatedMelodyData = data.map((note) => {
11677          var _a3, _b2;
11678          const musicalInstruments = processedInstruments.filter((inst) => inst.midiChannel !== -1);
11679          let mappedIdx = -1;
11680          if (note.trackIdx !== void 0) {
11681            const targetIdx = note.trackIdx;
11682            if (targetIdx >= 0 && targetIdx < musicalInstruments.length) {
11683              const matchedInst = musicalInstruments[targetIdx];
11684              mappedIdx = processedInstruments.findIndex((inst) => inst.trackId === matchedInst.trackId);
11685            }
11686          }
11687          if (mappedIdx === -1) {
11688            let targetIdx = -1;
11689            const match = (_a3 = note.i) == null ? void 0 : _a3.match(/^(.*) \((\d+)\)$/);
11690            if (match) {
11691              targetIdx = parseInt(match[2]) - 1;
11692            } else {
11693              const trackMatch = (_b2 = note.i) == null ? void 0 : _b2.match(/^Track\s+(\d+)$/i);
11694              if (trackMatch) {
11695                targetIdx = parseInt(trackMatch[1]) - 1;
11696              } else if (!isNaN(parseInt(note.i))) {
11697                targetIdx = parseInt(note.i);
11698              }
11699            }
11700            if (targetIdx >= 0) {
11701              const foundByTrackId = processedInstruments.findIndex((inst) => inst.trackId === targetIdx && inst.midiChannel !== -1);
11702              if (foundByTrackId !== -1) {
11703                mappedIdx = foundByTrackId;
11704              } else if (targetIdx < musicalInstruments.length) {
11705                const matchedInst = musicalInstruments[targetIdx];
11706                mappedIdx = processedInstruments.findIndex((inst) => inst.trackId === matchedInst.trackId);
11707              }
11708            }
11709          }
11710          if (mappedIdx === -1) {
11711            const instIdx = processedInstruments.findIndex((inst) => inst.originalName === note.i);
11712            if (instIdx !== -1) {
11713              mappedIdx = instIdx;
11714            }
11715          }
11716          if (mappedIdx !== -1) {
11717            const musicalIdx = musicalInstruments.findIndex((inst) => inst.trackId === processedInstruments[mappedIdx].trackId);
11718            return {
11719              ...note,
11720              i: uniqueNames[mappedIdx],
11721              trackIdx: musicalIdx !== -1 ? musicalIdx : mappedIdx
11722            };
11723          }
11724          return note;
11725        });
11726        updatedMelodyData.sort((a, b) => a.t - b.t);
11727        setMelodyData(updatedMelodyData);
11728      }).catch((err) => {
11729        console.error("Failed to load melody data", err);
11730        if (isCurrent) setMelodyData([]);
11731      });
11732    } else {
11733      setMelodyData(void 0);
11734    }
11735    return () => {
11736      isCurrent = false;
11737    };
11738  }, [activeTrack.midiMetadata, melodySource]);
11739  reactExports.useEffect(() => {
11740    if (["synth", "wasmsynth", "chiptune", "fmsynth", "opus", "webmidi"].includes(audioMode)) {
11741      if (isPlaying && !isSynthReady) {
11742        setIsBuffering(true);
11743      } else {
11744        setIsBuffering(false);
11745      }
11746    } else if (audioMode !== "singleopus") {
11747      setIsBuffering(false);
11748    }
11749  }, [isPlaying, isSynthReady, audioMode, setIsBuffering]);
11750  reactExports.useEffect(() => {
11751    var _a2, _b2, _c2;
11752    const configKey = `${activeTrack.id}-${audioMode}-${soundfont}-${audioQuality}-${isGameMode}-${isLiveSynthFallback}`;
11753    const needsLoad = !hasStartedLoading || lastLoadedConfigRef.current !== configKey;
11754    const shouldStartLoad = (isPlaying || viewMode === "taiko" || isGameMode) && needsLoad;
11755    if (shouldStartLoad && (audioMode === "synth" || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth" || audioMode === "opus" || audioMode === "webmidi")) {
11756      console.log("[WatchPage] User requested Play or Config changed. Starting Download Sequence...");
11757      setHasStartedLoading(true);
11758      lastLoadedConfigRef.current = configKey;
11759      const isProUser = isPro(currentUser);
11760      ((_a2 = soundfontMetadata[soundfont]) == null ? void 0 : _a2.access) || "pro";
11761      const isPlayAlongActive = isGameMode || playAlongMutedTracks && playAlongMutedTracks.size > 0;
11762      adminLog("%c[WatchPage] ▶ Load-on-Play triggered", "color:#34d399;font-weight:bold");
11763      adminLog("  isLiveSynthFallback:", isLiveSynthFallback);
11764      adminLog("  soundfont          :", soundfont);
11765      adminLog("  audioQuality       :", audioQuality);
11766      adminLog("  isProUser          :", isProUser);
11767      adminLog("  isPlayAlongActive  :", isPlayAlongActive);
11768      if (isLiveSynthFallback || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth") {
11769        console.log("[WatchPage] Live synthesis fallback active. Synthesizing from MIDI file.");
11770        const eventsSources = [
11771          activeTrack.cleanMidiPath,
11772          activeTrack.midiPath
11773        ].filter(Boolean);
11774        adminLog("%c[WatchPage] 🎹 Live-synth fallback path resolution", "color:#f59e0b;font-weight:bold");
11775        adminLog("  pianoRollPath  :", activeTrack.pianoRollPath || "(missing)");
11776        adminLog("  cleanMidiPath  :", activeTrack.cleanMidiPath || "(missing)");
11777        adminLog("  midiPath       :", activeTrack.midiPath || "(missing)");
11778        adminLog("  → eventsSources:", eventsSources);
11779        adminLog("  defaultLiveSf  :", defaultLiveSf);
11780        if (eventsSources.length === 0) {
11781          console.error("[WatchPage] Live synthesis error: No MIDI source path available.");
11782          adminLog("%c[WatchPage] ❌ No MIDI source — aborting live synth", "color:red;font-weight:bold");
11783          setTrackStates({ 0: { ready: false, error: true, percent: 0 } });
11784          setIsPlaying(false);
11785          return;
11786        }
11787        synth.audioMode = audioMode;
11788        parseMidiInstruments(eventsSources).then(({ tracks: liveFallbackTracks, duration: fallbackDuration, tempo: fallbackTempo, tickDiv: fallbackTickDiv }) => {
11789          var _a3, _b3, _c3;
11790          if (fallbackDuration > 0 && (!((_a3 = activeTrack.midiMetadata) == null ? void 0 : _a3.duration) || activeTrack.midiMetadata.duration === 0)) {
11791            console.log(`[WatchPage] Dispatching live fallback duration: ${fallbackDuration.toFixed(2)}s`);
11792            window.dispatchEvent(new CustomEvent("setTrackDuration", { detail: fallbackDuration }));
11793          }
11794          const tickDivToUse = ((_b3 = activeTrack.midiMetadata) == null ? void 0 : _b3.tickDiv) || fallbackTickDiv;
11795          const tempoToUse = ((_c3 = activeTrack.midiMetadata) == null ? void 0 : _c3.tempo) || fallbackTempo;
11796          let sfToLoad2 = soundfont === "pre-rendered" ? preRenderedSf : soundfont;
11797          if (soundfont === "pre-rendered" && isSlowMobile()) {
11798            const heavySfs = ["MusyngKite", "GeneralUser-GS", "FatBoy", "FluidR3_GM", "MuseScore_General", "Aspirin"];
11799            if (heavySfs.includes(sfToLoad2.replace(/\.h?sf[23]$/i, "").replace(/_JS$/i, ""))) {
11800              sfToLoad2 = "SoundBlasterOld.sf2";
11801            }
11802          }
11803          sfToLoad2 = resolveSfToLoad(sfToLoad2);
11804          synth.loadTracks(
11805            liveFallbackTracks,
11806            tickDivToUse,
11807            tempoToUse,
11808            audioQuality === "high" && isProUser ? "pro" : "free",
11809            sfToLoad2,
11810            eventsSources,
11811            (idx, status) => {
11812              setTrackStates((prev) => ({ ...prev, [idx]: status }));
11813            },
11814            true,
11815            // forceLive
11816            defaultLiveSf,
11817            isAdmin
11818          ).then(() => {
11819            if (isPlayingRef.current) {
11820              synth.play(currentTimeRef.current);
11821            }
11822          }).catch((err) => {
11823            console.error("[WatchPage] Live synthesis load error:", err);
11824            setTrackStates({ 0: { ready: false, error: true, percent: 0 } });
11825            setIsPlaying(false);
11826          });
11827        }).catch((err) => {
11828          console.error("[WatchPage] Live synthesis parse error:", err);
11829          setTrackStates({ 0: { ready: false, error: true, percent: 0 } });
11830          setIsPlaying(false);
11831        });
11832        return;
11833      }
11834      const tracksToLoad = activeTrack.instruments && activeTrack.instruments.length > 0 ? activeTrack.instruments : [{
11835        name: "Master",
11836        program: 0,
11837        midiChannel: 0,
11838        notesCount: 0,
11839        stemPath: activeTrack.opusPath || activeTrack.mp3PathHigh || activeTrack.mp3Path,
11840        stemPathHigh: activeTrack.opusPath || activeTrack.mp3PathHigh,
11841        trackId: 0
11842      }];
11843      const isLive = audioMode === "synth" || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth" || isPlayAlongActive || isLiveSynthFallback;
11844      let sfToLoad = soundfont === "pre-rendered" ? preRenderedSf || defaultLiveSf || "MusyngKite" : soundfont;
11845      if (soundfont === "pre-rendered" && isSlowMobile()) {
11846        const heavySfs = ["MusyngKite", "GeneralUser-GS", "FatBoy", "FluidR3_GM", "MuseScore_General", "Aspirin"];
11847        if (heavySfs.includes(sfToLoad.replace(/\.h?sf[23]$/i, "").replace(/_JS$/i, ""))) {
11848          sfToLoad = "SoundBlasterOld.sf2";
11849        }
11850      }
11851      sfToLoad = resolveSfToLoad(sfToLoad);
11852      synth.audioMode = audioMode;
11853      synth.loadTracks(
11854        tracksToLoad,
11855        (_b2 = activeTrack.midiMetadata) == null ? void 0 : _b2.tickDiv,
11856        (_c2 = activeTrack.midiMetadata) == null ? void 0 : _c2.tempo,
11857        audioQuality === "high" && isProUser ? "pro" : "free",
11858        sfToLoad,
11859        activeTrack.pianoRollPath || activeTrack.cleanMidiPath || activeTrack.midiPath || void 0,
11860        (idx, status) => {
11861          setTrackStates((prev) => ({ ...prev, [idx]: status }));
11862        },
11863        isLive,
11864        defaultLiveSf,
11865        isAdmin
11866      ).then(() => {
11867        if (audioMode === "opus") {
11868          const stemDuration = synth.getDuration();
11869          if (stemDuration > 0) {
11870            console.log(`[WatchPage] Stems loaded. Dispatching real stem duration: ${stemDuration.toFixed(2)}s`);
11871            window.dispatchEvent(new CustomEvent("setTrackDuration", { detail: stemDuration }));
11872          }
11873        }
11874        if (isPlayingRef.current) {
11875          synth.play(currentTimeRef.current);
11876        }
11877      }).catch((err) => {
11878        console.error("Synth load error:", err);
11879      });
11880    }
11881  }, [isPlaying, hasStartedLoading, audioMode, activeTrack, audioQuality, soundfont, isGameMode, isLiveSynthFallback, viewMode]);
11882  reactExports.useEffect(() => {
11883    var _a2, _b2, _c2;
11884    const configKey = `${activeTrack.id}-${audioMode}-${soundfont}-${audioQuality}-${isGameMode}-${isLiveSynthFallback}`;
11885    if (hasStartedLoading && lastLoadedConfigRef.current && lastLoadedConfigRef.current !== configKey && (audioMode === "synth" || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth")) {
11886      lastLoadedConfigRef.current = configKey;
11887      console.log("[WatchPage] Audio config change detected. Re-loading tracks...");
11888      const isProUser = isPro(currentUser);
11889      ((_a2 = soundfontMetadata[soundfont]) == null ? void 0 : _a2.access) || "pro";
11890      const isPlayAlongActive = isGameMode || playAlongMutedTracks && playAlongMutedTracks.size > 0;
11891      if (isLiveSynthFallback || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth") {
11892        const eventsSources = [
11893          activeTrack.cleanMidiPath,
11894          activeTrack.midiPath
11895        ].filter(Boolean);
11896        if (eventsSources.length === 0) {
11897          console.error("[WatchPage] Live synthesis config change error: No MIDI source path available.");
11898          setTrackStates({ 0: { ready: false, error: true, percent: 0 } });
11899          setIsPlaying(false);
11900          return;
11901        }
11902        parseMidiInstruments(eventsSources).then(({ tracks: liveFallbackTracks, duration: fallbackDuration, tempo: fallbackTempo, tickDiv: fallbackTickDiv }) => {
11903          var _a3, _b3, _c3;
11904          if (fallbackDuration > 0 && (!((_a3 = activeTrack.midiMetadata) == null ? void 0 : _a3.duration) || activeTrack.midiMetadata.duration === 0)) {
11905            window.dispatchEvent(new CustomEvent("setTrackDuration", { detail: fallbackDuration }));
11906          }
11907          const tickDivToUse = ((_b3 = activeTrack.midiMetadata) == null ? void 0 : _b3.tickDiv) || fallbackTickDiv;
11908          const tempoToUse = ((_c3 = activeTrack.midiMetadata) == null ? void 0 : _c3.tempo) || fallbackTempo;
11909          let sfToLoad2 = soundfont === "pre-rendered" ? preRenderedSf : soundfont;
11910          if (soundfont === "pre-rendered" && isSlowMobile()) {
11911            const heavySfs = ["MusyngKite", "GeneralUser-GS", "FatBoy", "FluidR3_GM", "MuseScore_General", "Aspirin"];
11912            if (heavySfs.includes(sfToLoad2.replace(/\.h?sf[23]$/i, "").replace(/_JS$/i, ""))) {
11913              sfToLoad2 = "SoundBlasterOld.sf2";
11914            }
11915          }
11916          sfToLoad2 = resolveSfToLoad(sfToLoad2);
11917          synth.audioMode = audioMode;
11918          synth.loadTracks(
11919            liveFallbackTracks,
11920            tickDivToUse,
11921            tempoToUse,
11922            audioQuality === "high" && isProUser ? "pro" : "free",
11923            sfToLoad2,
11924            eventsSources,
11925            (idx, status) => {
11926              setTrackStates((prev) => ({ ...prev, [idx]: status }));
11927            },
11928            true,
11929            // forceLive
11930            defaultLiveSf,
11931            isAdmin
11932          ).then(() => {
11933            if (isPlayingRef.current) synth.play(currentTimeRef.current);
11934          });
11935        }).catch((err) => {
11936          console.error("[WatchPage] Live synthesis config-change error:", err);
11937          setTrackStates({ 0: { ready: false, error: true, percent: 0 } });
11938          setIsPlaying(false);
11939        });
11940        return;
11941      }
11942      const tracksToLoad = activeTrack.instruments && activeTrack.instruments.length > 0 ? activeTrack.instruments : [{
11943        name: "Master",
11944        program: 0,
11945        midiChannel: 0,
11946        notesCount: 0,
11947        stemPath: activeTrack.opusPath || activeTrack.mp3PathHigh || activeTrack.mp3Path,
11948        stemPathHigh: activeTrack.opusPath || activeTrack.mp3PathHigh,
11949        trackId: 0
11950      }];
11951      const isLive = audioMode === "synth" || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth" || isPlayAlongActive || isLiveSynthFallback;
11952      let sfToLoad = soundfont === "pre-rendered" ? preRenderedSf || defaultLiveSf || "MusyngKite" : soundfont;
11953      if (soundfont === "pre-rendered" && isSlowMobile()) {
11954        const heavySfs = ["MusyngKite", "GeneralUser-GS", "FatBoy", "FluidR3_GM", "MuseScore_General", "Aspirin"];
11955        if (heavySfs.includes(sfToLoad.replace(/\.h?sf[23]$/i, "").replace(/_JS$/i, ""))) {
11956          sfToLoad = "SoundBlasterOld.sf2";
11957        }
11958      }
11959      sfToLoad = resolveSfToLoad(sfToLoad);
11960      synth.audioMode = audioMode;
11961      synth.loadTracks(
11962        tracksToLoad,
11963        (_b2 = activeTrack.midiMetadata) == null ? void 0 : _b2.tickDiv,
11964        (_c2 = activeTrack.midiMetadata) == null ? void 0 : _c2.tempo,
11965        audioQuality === "high" && isProUser ? "pro" : "free",
11966        sfToLoad,
11967        activeTrack.pianoRollPath || activeTrack.cleanMidiPath || activeTrack.midiPath || void 0,
11968        (idx, status) => {
11969          setTrackStates((prev) => ({ ...prev, [idx]: status }));
11970        },
11971        isLive,
11972        defaultLiveSf,
11973        isAdmin
11974      ).then(() => {
11975        if (isPlayingRef.current) synth.play(currentTimeRef.current);
11976      });
11977    }
11978  }, [audioQuality, soundfont, isGameMode, isLiveSynthFallback, audioMode, playAlongMutedTracks, preRenderedSf, activeTrack, currentUser, defaultLiveSf, isAdmin]);
11979  reactExports.useEffect(() => {
11980    if (audioMode === "synth" || audioMode === "webmidi" || audioMode === "chiptune" || audioMode === "wasmsynth" || audioMode === "fmsynth" || audioMode === "opus") {
11981      if (isPlaying && isSynthReady) {
11982        console.log(`[WatchPage] Playback Control: Playing Synth (${audioMode}) at`, currentTime);
11983        synth.play(currentTime);
11984      } else if (!isPlaying) {
11985        console.log(`[WatchPage] Playback Control: Stopping Synth (${audioMode}) (isPlaying:`, isPlaying, "isSynthReady:", isSynthReady, ")");
11986        synth.stop();
11987      }
11988    } else if (!isPlaying) {
11989      console.log("[WatchPage] Playback Control: Stopping Synth (Mode is Opu
11989s)");
11990      synth.stop();
11991    }
11992  }, [isPlaying, audioMode, isSynthReady]);
11993  reactExports.useEffect(() => {
11994    if (audioMode === "opus" && hasSynthError && isPlaying) {
11995      console.error("[WatchPage] Synth loading failed. Halting playback.");
11996      setIsPlaying(false);
11997    }
11998  }, [audioMode, hasSynthError, isPlaying, setIsPlaying]);
11999  reactExports.useEffect(() => {
12000    const isPlayAlongActive = isGameMode || playAlongMutedTracks && playAlongMutedTracks.size > 0;
12001    if (isPlayAlongActive) {
12002      if (previousSoundfont === null) {
12003        setPreviousSoundfont(soundfont);
12004      }
12005      if (previousAudioMode === null) {
12006        setPreviousAudioMode(audioMode);
12007      }
12008      const targetSf = defaultLiveSf || "MusyngKite";
12009      const isNonLiveSf = soundfont === "pre-rendered" || soundfont === preRenderedSf && !isLiveSynthFallback;
12010      if (isNonLiveSf && soundfont !== targetSf) {
12011        console.log(`[WatchPage] Forcing live fallback soundfont (${targetSf}) for Play-Along Mode because current SF is non-live`);
12012        setSoundfont(targetSf);
12013      }
12014      if (audioMode === "opus" || audioMode === "singleopus") {
12015        const list = isSlowMobile() ? engineOrder == null ? void 0 : engineOrder.watchMobile : engineOrder == null ? void 0 : engineOrder.watchDesktop;
12016        const liveEngine = (list == null ? void 0 : list.find((e) => ["wasmsynth", "synth", "fmsynth", "chiptune"].includes(e))) || "wasmsynth";
12017        console.log(`[WatchPage] Forcing ${liveEngine} audioMode (live MIDI synthesis) for Play-Along Mode`);
12018        setAudioMode(liveEngine);
12019      }
12020    } else {
12021      if (previousSoundfont !== null) {
12022        console.log(`[WatchPage] Reverting soundfont back to ${previousSoundfont}`);
12023        setSoundfont(previousSoundfont);
12024        setPreviousSoundfont(null);
12025      }
12026      if (previousAudioMode !== null) {
12027        console.log(`[WatchPage] Reverting audioMode back to ${previousAudioMode}`);
12028        setAudioMode(previousAudioMode);
12029        setPreviousAudioMode(null);
12030      }
12031    }
12032  }, [isGameMode, playAlongMutedTracks, soundfont, audioMode, defaultLiveSf, preRenderedSf, previousSoundfont, previousAudioMode, setAudioMo
12032de, engineOrder]);
12033  reactExports.useEffect(() => {
12034    console.log("[WatchPage] Applying Mutes to Synth:", Array.from(localMutedIndices));
12035    processedInstruments.forEach((_, idx) => {
12036      synth.setMute(idx, localMutedIndices.has(idx));
12037    });
12038  }, [localMutedIndices, processedInstruments]);
12039  const handleInstrumentChange = reactExports.useCallback((trackIdx, newProgram) => {
12040    setInstrumentOverrides((prev) => ({ ...prev, [trackIdx]: newProgram }));
12041    synth.setTrackInstrument(trackIdx, newProgram);
12042    if (audioMode === "opus" || audioMode === "singleopus") {
12043      const list = isSlowMobile() ? engineOrder == null ? void 0 : engineOrder.watchMobile : engineOrder == null ? void 0 : engineOrder.watchDesktop;
12044      const liveEngine = (list == null ? void 0 : list.find((e) => ["wasmsynth", "synth", "fmsynth", "chiptune"].includes(e))) || "wasmsynth";
12045      setAudioMode(liveEngine);
12046    }
12047  }, [audioMode, setAudioMode, engineOrder]);
12048  const handleInstrumentMute = (index) => {
12049    if (isGameMode) return;
12050    if (soloedIndex !== null) {
12051      setSoloedIndex(null);
12052      synth.setSolo(null);
12053    }
12054    setLocalMutedIndices((prev) => {
12055      const next = new Set(prev);
12056      const isMuted = next.has(index);
12057      if (isMuted) {
12058        next.delete(index);
12059      } else {
12060        next.add(index);
12061      }
12062      return next;
12063    });
12064  };
12065  const handleGameModeMute = reactExports.useCallback((melodyIndices) => {
12066    if (soloedIndex !== null) {
12067      setSoloedIndex(null);
12068      synth.setSolo(null);
12069    }
12070    if (melodyIndices === null) {
12071      console.log("[WatchPage] Resetting Mutes (Play-Along Ended)");
12072      setLocalMutedIndices(/* @__PURE__ */ new Set());
12073      setPlayAlongMutedTracks(/* @__PURE__ */ new Set());
12074    } else {
12075      console.log("[WatchPage] Setting Play-Along Mutes:", melodyIndices);
12076      setLocalMutedIndices(new Set(melodyIndices));
12077      setPlayAlongMutedTracks(new Set(melodyIndices));
12078    }
12079  }, [soloedIndex]);
12080  const handleInstrumentSolo = (index) => {
12081    if (isGameMode) return;
12082    setSoloedIndex((prevSoloed) => {
12083      const newSoloed = prevSoloed === index ? null : index;
12084      synth.setSolo(newSoloed);
12085      return newSoloed;
12086    });
12087  };
12088  const handleVolumeChange = (index, val) => {
12089    setTrackVolumes((prev) => ({ ...prev, [index]: val }));
12090    synth.setTrackVolume(index, val);
12091  };
12092  const handlePanChange = (index, val) => {
12093    setTrackPans((prev) => ({ ...prev, [index]: val }));
12094    synth.setTrackPan(index, val);
12095  };
12096  const toggleTrackVisibility = (index) => {
12097    if (!activeTrack.instruments || !activeTrack.instruments[index] || !uniqueNames[index]) return;
12098    const uniqueName = uniqueNames[index];
12099    setHiddenTracks((prev) => {
12100      const next = new Set(prev);
12101      if (next.has(uniqueName)) next.delete(uniqueName);
12102      else next.add(uniqueName);
12103      return next;
12104    });
12105  };
12106  const handleVoteTag = async (tagName, value) => {
12107    const userId = currentUser ? currentUser.id : null;
12108    await api.voteTag(activeTrack.id, tagName, value, userId);
12109    setTags((prev) => prev.map((t2) => {
12110      if (t2.name === tagName) {
12111        return { ...t2, score: t2.score + value * ((currentUser == null ? void 0 : currentUser.trustScore) && currentUser.trustScore > 50 ? 5 : 1) };
12112      }
12113      return t2;
12114    }).sort((a, b) => b.score - a.score));
12115  };
12116  const handleProposeTag = async (tagName) => {
12117    if (!currentUser) {
12118      onAuthRequested();
12119      setIsTagModalOpen(false);
12120      return;
12121    }
12122    const res = await api.proposeTag(activeTrack.id, tagName, currentUser.id);
12123    if (res && res.tag) {
12124      setTags((prev) => {
12125        if (prev.find((t2) => t2.id === res.tag.id)) return prev;
12126        const newTag = { ...res.tag, score: res.tag.score || 0 };
12127        return [...prev, newTag].sort((a, b) => b.score - a.score);
12128      });
12129    }
12130  };
12131  const handleResetMixer = () => {
12132    setLocalMutedIndices(/* @__PURE__ */ new Set());
12133    setPlayAlongMutedTracks(/* @__PURE__ */ new Set());
12134    setTrackVolumes({});
12135    setTrackPans({});
12136    setHiddenTracks(/* @__PURE__ */ new Set());
12137    setSoloedIndex(null);
12138    if (audioMode === "synth" || audioMode === "wasmsynth") {
12139      synth.resetMixer();
12140    }
12141  };
12142  const openReportModal = (id) => {
12143    setIsReportModalOpen(true);
12144  };
12145  reactExports.useEffect(() => {
12146    if (activeTrack) {
12147      document.title = `${activeTrack.title} - ${activeTrack.artist} | HamieNet`;
12148    }
12149  }, [activeTrack]);
12150  if (isBlocked) {
12151    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-[60vh] text-center p-6", children: [
12152      /* @__PURE__ */ jsxRuntimeExports.jsx(OctagonAlert, { size: 48, className: "text-red-500 mb-4" }),
12153      /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-2xl font-bold text-zinc-900 dark:text-white mb-2", children: t("watch.contentUnavailable", "Content Unavailable") }),
12154      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-600 dark:text-zinc-400 max-w-md", children: t("watch.unavailableRegion", { region: country, defaultValue: `This content contains copyright-protected material that is not available in your region (${country}).` }) })
12155    ] });
12156  }
12157  const resolvedTrack = reactExports.useMemo(() => ({
12158    ...activeTrack,
12159    backgroundImageUrl: localBgUrl
12160  }), [activeTrack, localBgUrl]);
12161  const showLoadingOverlay = isPlaying && ["synth", "wasmsynth", "chiptune", "fmsynth", "opus", "webmidi"].includes(audioMode) && !isSynthReady;
12162  const visualizerComponent = audioMode === "webmidi" && !forceShowVisualizer ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full aspect-video rounded-xl bg-zinc-950 border border-zinc-800 p-6 flex flex-col justify-center items-center text-center shadow-2xl", children: [
12163    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 rounded-full bg-indigo-950 border border-indigo-500/30 text-indigo-400 mb-4 animate-pulse", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
12164      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2v20" }),
12165      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m17 5-5-3-5 3" }),
12166      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m7 19 5 3 5-3" }),
12167      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M2 12h20" }),
12168      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m5 7 3 5-3 5" }),
12169      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "m19 17-3-5 3-5" })
12170    ] }) }),
12171    /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-white mb-2", children: "Web MIDI Playback Active" }),
12172    /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-400 max-w-md mb-6 leading-relaxed", children: "To save battery and CPU cycles on your device, audio is playing directly through your device's operating system synthesizer. Music visualizers are disabled to keep your browser running fast." }),
12173    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-4 justify-center", children: [
12174      /* @__PURE__ */ jsxRuntimeExports.jsx(
12175        "button",
12176        {
12177          onClick: () => setForceShowVisualizer(true),
12178          className: "px-4 py-2 text-xs font-bold rounded-lg border border-zinc-700 hover:border-zinc-500 text-zinc-300 hover:text-white transition-colors",
12179          children: "Open Visualizer Anyway"
12180        }
12181      ),
12182      /* @__PURE__ */ jsxRuntimeExports.jsx(
12183        "button",
12184        {
12185          onClick: () => {
12186            const list = isSlowMobile() ? engineOrder == null ? void 0 : engineOrder.watchMobile : engineOrder == null ? void 0 : engineOrder.watchDesktop;
12187            const liveEngine = (list == null ? void 0 : list.find((e) => ["wasmsynth", "synth", "fmsynth", "chiptune"].includes(e))) || "wasmsynth";
12188            setAudioMode(liveEngine);
12189            setForceShowVisualizer(true);
12190          },
12191          className: "px-4 py-2 text-xs font-bold rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white transition-all shadow-md shadow-indigo-600/20",
12192          children: "Switch to Soundfont Synth"
12193        }
12194      )
12195    ] })
12196  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
12197    VisualizerWindow,
12198    {
12199      theme,
12200      viewMode,
12201      setViewMode,
12202      track: resolvedTrack,
12203      currentTime,
12204      duration,
12205      isPlaying,
12206      onPlayPause: (forcePlay) => {
12207        try {
12208          const ctx = synth.getAudioContext();
12209          if (ctx && ctx.state === "suspended") {
12210            ctx.resume();
12211          }
12212        } catch (err) {
12213          console.error("[WatchPage] Failed to resume AudioContext inside play click handler:", err);
12214        }
12215        if (forcePlay !== void 0) {
12216          setIsPlaying(forcePlay);
12217        } else {
12218          setIsPlaying(!isPlaying);
12219        }
12220      },
12221      autoPlayCountdown,
12222      onCancelAutoPlay,
12223      onPlayNext,
12224      nextTrack,
12225      isTheaterMode,
12226      setIsTheaterMode: handleSetTheaterMode,
12227      pianoRollData,
12228      melodyData,
12229      melodySource,
12230      pitchSemitones,
12231      setPitchSemitones: (val) => window.dispatchEvent(new CustomEvent("setPitchSemitones", { detail: val })),
12232      currentTempo: (((_b = activeTrack.midiMetadata) == null ? void 0 : _b.tempo) || 120) * playbackRate,
12233      setCurrentTempo: (val) => {
12234        var _a2;
12235        return window.dispatchEvent(new CustomEvent("setPlaybackRate", { detail: val / (((_a2 = activeTrack.midiMetadata) == null ? void 0 : _a2.tempo) || 120) }));
12236      },
12237      originalTempo: ((_c = activeTrack.midiMetadata) == null ? void 0 : _c.tempo) || 120,
12238      setMelodySource,
12239      hiddenTracks,
12240      trackVisibility,
12241      colorMap: derivedColorMap,
12242      isLoadingAudio: showLoadingOverlay,
12243      isAudioLoaded: ["synth", "wasmsynth", "chiptune", "fmsynth", "opus", "webmidi"].includes(audioMode) ? isSynthReady : true,
12244      isBuffering,
12245      selectedTrackIndex,
12246      onSelectTrack: setSelectedTrackIndex,
12247      processedInstruments,
12248      uniqueNames,
12249      trackId: activeTrack.id,
12250      userId: currentUser == null ? void 0 : currentUser.id,
12251      userName: currentUser == null ? void 0 : currentUser.username,
12252      uploaderName: activeTrack.uploader.name,
12253      isAdmin: (currentUser == null ? void 0 : currentUser.role) === "ADMIN",
12254      staffModeEnabled,
12255      audioMode,
12256      onSeek,
12257      playbackRate,
12258      isGameMode,
12259      setIsGameMode,
12260      onGameModeMute: handleGameModeMute,
12261      onAuthRequested,
12262      soloedIndex,
12263      onSolo: handleInstrumentSolo,
12264      mutedIndices: localMutedIndices,
12265      onMute: handleInstrumentMute,
12266      onPanChange: handlePanChange,
12267      soundfont,
12268      isAttached: true,
12269      onInstrumentChange: handleInstrumentChange
12270    }
12271  );
12272  if (isEmbed) {
12273    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-screen bg-black overflow-hidden flex flex-col justify-center items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full max-w-[1920px] aspect-video", children: visualizerComponent }) });
12274  }
12275  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
12276    "div",
12277    {
12278      className: `mx-auto p-0 sm:p-4 animate-in fade-in w-full ${isTheaterMode ? "max-w-full" : "max-w-[1750px]"}`,
12279      children: [
12280        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col xl:flex-row justify-center items-start xl:gap-8 gap-6 mb-8 w-full", children: [
12281          /* @__PURE__ */ jsxRuntimeExports.jsxs(
12282            "div",
12283            {
12284              className: "flex flex-col min-w-0 xl:min-w-[640px] w-full z-10",
12285              style: {
12286                flex: "1 1 auto",
12287                maxWidth: isTheaterMode ? "min(100%, calc((100vh - 190px) * 1.777))" : "min(100%, calc((100vh - 230px) * 1.777))"
12288              },
12289              children: [
12290                visualizerComponent,
12291                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 flex flex-col gap-6 px-4 sm:px-0 w-full", children: [
12292                  /* @__PURE__ */ jsxRuntimeExports.jsx(
12293                    VideoMetadata,
12294                    {
12295                      track: resolvedTrack,
12296                      currentUser,
12297                      isSubscribed,
12298                      onSubscribe: subscribe,
12299                      onChannelSelect: handleChannelSelect,
12300                      onCustomizeBackground: () => setIsBgModalOpen(true),
12301                      onRemoveBackground: handleBackgroundRemove,
12302                      likedMidis,
12303                      toggleLike: (id) => toggleLike(id),
12304                      tags,
12305                      onTagSelect,
12306                      onArtistSelect,
12307                      onVoteTag: handleVoteTag,
12308                      onOpenTagModal: () => setIsTagModalOpen(true),
12309                      onAddToPlaylist: () => onAddToPlaylist(activeTrack),
12310                      onReport: () => openReportModal(),
12311                      onAuthRequested,
12312                      onEditWiki: () => setIsEditModalOpen(true),
12313                      currentTime,
12314                      duration,
12315                      isLiveSynthFallback,
12316                      hasSynthError: (audioMode === "synth" || audioMode === "wasmsynth") && hasSynthError,
12317                      onReuploadNeeded: () => setIsReuploadModalOpen(true)
12318                    }
12319                  ),
12320                  /* @__P
12320URE__ */ jsxRuntimeExports.jsx(AdSlot, { type: "inArticleText", className: "!my-0 min-h-[90px]" }),
12321                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "xl:hidden w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
12322                    MixerPanel,
12323                    {
12324                      tracks: processedInstruments,
12325                      uniqueNames,
12326                      mutedIndices: localMutedIndices,
12327                      playAlongMutedTracks,
12328                      isMelodyMode: viewMode === "melody",
12329                      toggleMute: handleInstrumentMute,
12330                      trackVolumes,
12331                      onVolumeChange: handleVolumeChange,
12332                      trackPans,
12333                      onPanChange: handlePanChange,
12334                      onSolo: handleInstrumentSolo,
12335                      soloedIndex,
12336                      hiddenTracks,
12337                      toggleVisibility: toggleTrackVisibility,
12338                      audioMode,
12339                      toggleAudioMode: () => {
12340                        var _a2;
12341                        if (audioMode === "opus" || audioMode === "singleopus") {
12342                          const list = isSlowMobile() ? engineOrder == null ? void 0 : engineOrder.watchMobile : engineOrder == null ? void 0 : engineOrder.watchDesktop;
12343                          const liveEngine = (list == null ? void 0 : list.find((e) => ["wasmsynth", "synth", "fmsynth", "chiptune", "webmidi"].includes(e))) || "wasmsynth";
12344                          setAudioMode(liveEngine);
12345                        } else {
12346                          const hasStems = (_a2 = activeTrack.instruments) == null ? void 0 : _a2.some((i) => i.stemPath);
12347                          const hasAudio = !!activeTrack.opusPath;
12348                          if (hasStems) setAudioMode("opus");
12349                          else if (hasAudio) setAudioMode("singleopus");
12350                        }
12351                      },
12352                      mixerMode,
12353                      setMixerMode,
12354                      colorMap: derivedColorMap,
12355                      onViewEvents: setEventsInstrument,
12356                      isPlaying,
12357                      currentTime,
12358                      duration,
12359                      trackStates,
12360                      onResetMixer: handleResetMixer,
12361                      selectedTrackIndex,
12362                      onSelectTrack: (idx) => {
12363                        setSelectedTrackIndex(idx);
12364                      },
12365                      isGameMode,
12366                      midiTrack: activeTrack,
12367                      soundfont: soundfont === "pre-rendered" && activeTrack.status !== "completed" ? preRenderedSf : soundfont,
12368                      onSoundfontChange: handleSoundfontChange,
12369                      libraries: displayedLibraries,
12370                      currentUser,
12371                      preRenderedSf,
12372                      isAttached: true,
12373                      theme,
12374                      pianoRollData,
12375                      onInstrumentChange: handleInstrumentChange
12376                    }
12377                  ) }),
12378                  ((_d = activeTrack.midiMetadata) == null ? void 0 : _d.lyrics) && activeTrack.midiMetadata.lyrics.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-2xl p-5 shadow-sm mt-4", children: [
12379                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
12380                      /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
12381                        /* @__PURE__ */ jsxRuntimeExports.jsx(MicVocal, { size: 18, className: "text-indigo-500" }),
12382                        t("karaoke.lyrics")
12383                      ] }),
12384                      /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setLyricsExpanded(!lyricsExpanded), className: "text-xs font-bold text-indigo-500 hover:text-indigo-600 bg-indigo-500/10 px-3 py-1.5 rounded-full transition-colors", children: lyricsExpanded ? t("karaoke.showLess") : t("karaoke.showFullLyrics") })
12385                    ] }),
12386                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `relative overflow-hidden transition-all duration-500 ${lyricsExpanded ? "max-h-[5000px]" : "max-h-[120px]"}`, children: [
12387                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-600 dark:text-zinc-400 leading-snug text-xs sm:text-sm font-medium whitespace-pre-wrap", children: (() => {
12388                        const grouped = groupLyricsIntoLines(activeTrack.midiMetadata.lyrics);
12389                        return grouped.map((line, lineIdx) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1", children: line.syllables.map((syl, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: syl.txt.replace(/ /g, " ") }, i)) }, lineIdx));
12390                      })() }),
12391                      !lyricsExpanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-zinc-50 dark:from-zinc-900 to-transparent pointer-events-none" })
12392                    ] })
12393                  ] }),
12394                  /* @__PURE__ */ jsxRuntimeExports.jsx(
12395                    CommentSection,
12396                    {
12397                      trackId: activeTrack.id,
12398                      comments,
12399                      setComments,
12400                      currentUser,
12401                      onReport: openReportModal
12402                    }
12403                  )
12404                ] })
12405              ]
12406            }
12407          ),
12408          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full xl:w-[380px] shrink-0 min-w-0 flex flex-col gap-6 px-4 sm:px-0", children: [
12409            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "hidden xl:block w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
12410              MixerPanel,
12411              {
12412                tracks: processedInstruments,
12413                uniqueNames,
12414                mutedIndices: localMutedIndices,
12415                playAlongMutedTracks,
12416                isMelodyMode: viewMode === "melody",
12417                toggleMute: handleInstrumentMute,
12418                trackVolumes,
12419                onVolumeChange: handleVolumeChange,
12420                trackPans,
12421                onPanChange: handlePanChange,
12422                onSolo: handleInstrumentSolo,
12423                soloedIndex,
12424                hiddenTracks,
12425                toggleVisibility: toggleTrackVisibility,
12426                audioMode,
12427                toggleAudioMode: () => {
12428                  var _a2;
12429                  if (audioMode === "opus" || audioMode === "singleopus") {
12430                    const list = isSlowMobile() ? engineOrder == null ? void 0 : engineOrder.watchMobile : engineOrder == null ? void 0 : engineOrder.watchDesktop;
12431                    const liveEngine = (list == null ? void 0 : list.find((e) => ["wasmsynth", "synth", "fmsynth", "chiptune", "webmidi"].includes(e))) || "wasmsynth";
12432                    setAudioMode(liveEngine);
12433                  } else {
12434                    const hasStems = (_a2 = activeTrack.instruments) == null ? void 0 : _a2.some((i) => i.stemPath);
12435                    const hasAudio = !!activeTrack.opusPath;
12436                    if (hasStems) setAudioMode("opus");
12437                    else if (hasAudio) setAudioMode("singleopus");
12438                  }
12439                },
12440                mixerMode,
12441                setMixerMode,
12442                colorMap: derivedColorMap,
12443                onViewEvents: setEventsInstrument,
12444                isPlaying,
12445                currentTime,
12446                duration,
12447                trackStates,
12448                onResetMixer: handleResetMixer,
12449                selectedTrackIndex,
12450                onSelectTrack: (idx) => {
12451                  setSelectedTrackIndex(idx);
12452                },
12453                isGameMode,
12454                midiTrack: activeTrack,
12455                soundfont: soundfont === "pre-rendered" && activeTrack.status !== "completed" ? preRenderedSf : soundfont,
12456                onSoundfontChange: handleSoundfontChange,
12457                libraries: displayedLibraries,
12458                currentUser,
12459                preRenderedSf,
12460                isAttached: true,
12461                theme,
12462                pianoRollData,
12463                onInstrumentChange: handleInstrumentChange
12464              }
12465            ) }),
12466            /* @__PURE__ */ jsxRuntimeExports.jsx(AdSlot, { type: "watchSidebar" }),
12467            variations.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-gradient-to-br from-indigo-50/50 via-white to-white dark:from-indigo-950/20 dark:via-zinc-900 dark:to-zinc-900 border border-indigo-100/50 dark:border-indigo-900/30 rounded-2xl p-5 shadow-sm w-full", children: [
12468              /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white mb-4 flex items-center gap-2", children: [
12469                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "p-1.5 bg-indigo-500/10 rounded-lg text-indigo-500", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-git-fork", children: [
12470                  /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "18", r: "3" }),
12471                  /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6", cy: "6", r: "3" }),
12472                  /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "18", cy: "6", r: "3" }),
12473                  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" }),
12474                  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 12v3" })
12475                ] }) }),
12476                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
12477                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("watch.variations", "Other Arrangements") }),
12478                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 font-semibold px-2 py-0.5 rounded-full border border-indigo-500/20", children: variations.length })
12479                ] })
12480              ] }),
12481              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-3", children: variations.map((track) => /* @__PURE__ */ jsxRuntimeExports.jsx(
12482                VideoCard,
12483                {
12484                  track,
12485                  variant: "small",
12486                  onSelect: handleSelectTrack,
12487                  onToggleWatchLater: toggleWatchLater,
12488                  isWatchLater: watchLater.has(track.id),
12489                  onAddToQueue,
12490                  onAddToPlaylist
12491                },
12492                track.id
12493              )) })
12494            ] }),
12495            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full", children: [
12496              /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white mb-4 flex items-center gap-2", children: [
12497                /* @__PURE__ */ jsxRuntimeExports.jsx(ListVideo, { size: 18, className: "text-indigo-500" }),
12498                t("watch.upNext", "Up Next")
12499              ] }),
12500              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4", children: [
12501                nextTrack ? /* @__PURE__ */ jsxRuntimeExports.jsx(
12502                  VideoCard,
12503                  {
12504                    track: nextTrack,
12505                    onSelect: () => onPlayNext(),
12506                    variant: "grid",
12507                    isWatchLater: false,
12508                    onToggleWatchLater: () => {
12509                    }
12510                  }
12511                ) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "No more tracks in queue." }),
12512                relatedTracks.filter((t2) => t2.id !== activeTrack.id && t2.id !== (nextTrack == null ? void 0 : nextTrack.id)).map((track, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
12513                  VideoCard,
12514                  {
12515                    track,
12516                    variant: "grid",
12517                    onSelect: handleSelectTrack,
12518                    onToggleWatchLater: toggleWatchLater,
12519                    isWatchLater: watchLater.has(track.id),
12520                    onAddToQueue,
12521                    onAddToPlaylist
12522                  },
12523                  track.id + i
12524                )),
12525                relatedTracks.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Finding related tracks..." })
12526              ] })
12527            ] })
12528          ] })
12529        ] }),
12530        /* @__PURE__ */ jsxRuntimeExports.jsx(AdSlot, { type: "watchBottom" }),
12531        /* @__PURE__ */ jsxRuntimeExports.jsx(
12532          TagModal,
12533          {
12534            isOpen: isTagModalOpen,
12535            onClose: () => setIsTagModalOpen(false),
12536            onSubmit: handleProposeTag
12537          }
12538        ),
12539        isEditModalOpen && currentUser && /* @__PURE__ */ jsxRuntimeExports.jsx(
12540          EditTrackModal,
12541          {
12542            isOpen: isEditModalOpen,
12543            onClose: () => setIsEditModalOpen(false),
12544            track: activeTrack,
12545            currentUser,
12546            onSuccess: () => {
12547              window.location.reload();
12548            }
12549          }
12550        ),
12551        isReportModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
12552          ReportModal,
12553          {
12554            isOpen: isReportModalOpen,
12555            onClose: () => setIsReportModalOpen(false),
12556            trackId: activeTrack.id,
12557            trackTitle: activeTrack.title,
12558            isUnowned: !activeTrack.uploader || activeTrack.uploader.id === -1 || activeTrack.uploader.name === "Community Contributor",
12559            currentUser
12560          }
12561        ),
12562        /* @__PURE__ */ jsxRuntimeExports.jsx(
12563          TrackEventsModal,
12564          {
12565            isOpen: !!eventsInstrument,
12566            onClose: () => setEventsInstrument(null),
12567            instrument: eventsInstrument,
12568            currentTime,
12569            tempo: ((_e = activeTrack.midiMetadata) == null ? void 0 : _e.tempo) || 120,
12570            tickDiv: ((_f = activeTrack.midiMetadata) == null ? void 0 : _f.tickDiv) || 480
12571          }
12572        ),
12573        isReuploadModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
12574          ReuploadModal,
12575          {
12576            isOpen: isReuploadModalOpen,
12577            onClose: () => setIsReuploadModalOpen(false),
12578            trackId: activeTrack.id,
12579            trackTitle: activeTrack.title,
12580            onSuccess: () => {
12581              window.location.reload();
12582            }
12583          }
12584        ),
12585        isBgModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in duration-200", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-md bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-2xl p-6 shadow-2xl space-y-4", children: [
12586          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between pb-2 border-b border-zinc-100 dark:border-zinc-800", children: [
12587            /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
12588              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xl", children: "🎨" }),
12589              " Customize Orchestra Background"
12590            ] }),
12591            /* @__PURE__ */ jsxRuntimeExports.jsx(
12592              "button",
12593              {
12594                onClick: () => setIsBgModalOpen(false),
12595                className: "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 transition-colors text-xl font-bold",
12596                children: "×"
12597              }
12598            )
12599          ] }),
12600          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
12601            /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 leading-relaxed font-sans", children: [
12602              "Upload a custom landscape image (PNG, JPG, or WebP) to use as the background for this MIDI track in the Orchestra visualizer. Recommended size is ",
12603              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold", children: "1920x1080" }),
12604              "."
12605            ] }),
12606            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-900/50 rounded-xl text-amber-800 dark:text-amber-300 space-y-1", children: [
12607              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold uppercase tracking-wider block", children: "⚖️ Legal & Copyright Warning" }),
12608              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] leading-relaxed", children: "Please ensure you own the copyright or have the necessary legal rights/licenses to upload and distribute this image. By uploading, you agree to our Terms of Service and accept all legal liability for copyright infringement." })
12609            ] }),
12610            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-2 border-dashed border-zinc-200 dark:border-zinc-800 rounded-xl p-4 flex flex-col items-center justify-center gap-2 bg-zinc-50 dark:bg-zinc-950/50", children: [
12611              /* @__PURE__ */ jsxRuntimeExports.jsx(
12612                "input",
12613                {
12614                  type: "file",
12615                  accept: "image/png, image/jpeg, image/jpg, image/webp",
12616                  onChange: (e) => {
12617                    if (e.target.files && e.target.files[0]) {
12618                      setBgUploadFile(e.target.files[0]);
12619                      setBgUploadError(null);
12620                    }
12621                  },
12622                  className: "hidden",
12623                  id: "bg-file-input"
12624                }
12625              ),
12626              /* @__PURE__ */ jsxRuntimeExports.jsx(
12627                "label",
12628                {
12629                  htmlFor: "bg-file-input",
12630                  className: "px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-xs font-bold cursor-pointer transition-colors shadow-sm",
12631                  children: "Select Image File"
12632                }
12633              ),
12634              bgUploadFile && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300 max-w-full truncate font-mono", children: [
12635                "📄 ",
12636                bgUploadFile.name,
12637                " (",
12638                (bgUploadFile.size / 1024 / 1024).toFixed(2),
12639                " MB)"
12640              ] })
12641            ] }),
12642            /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-2.5 cursor-pointer p-1", children: [
12643              /* @__PURE__ */ jsxRuntimeExports.jsx(
12644                "input",
12645                {
12646                  type: "checkbox",
12647                  checked: hasAcceptedCopyright,
12648                  onChange: (e) => setHasAcceptedCopyright(e.target.checked),
12649                  className: "mt-0.5 w-4 h-4 rounded border-zinc-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer"
12650                }
12651              ),
12652              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-zinc-600 dark:text-zinc-400 select-none", children: "I confirm that I own this image or have full legal permission to upload and use it as a background." })
12653            ] }),
12654            bgUploadError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-red-500 font-medium", children: [
12655              "⚠️ ",
12656              bgUploadError
12657            ] })
12658          ] }),
12659          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end gap-2 pt-2 border-t border-zinc-100 dark:border-zinc-800", children: [
12660            /* @__PURE__ */ jsxRuntimeExports.jsx(
12661              "button",
12662              {
12663                onClick: () => setIsBgModalOpen(false),
12664                className: "px-4 py-2 text-xs font-semibold text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200 transition-colors",
12665                disabled: bgUploadStatus === "uploading",
12666                children: "Cancel"
12667              }
12668            ),
12669            /* @__PURE__ */ jsxRuntimeExports.jsx(
12670              "button",
12671              {
12672                onClick: handleBackgroundUpload,
12673                disabled: !bgUploadFile || !hasAcceptedCopyright || bgUploadStatus === "uploading",
12674                className: "px-4 py-2 text-xs font-bold text-white bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg shadow transition-colors flex items-center gap-1.5",
12675                children: bgUploadStatus === "uploading" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
12676                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" }),
12677                  "Uploading..."
12678                ] }) : bgUploadStatus === "success" ? "✅ Uploaded!" : "Upload Background"
12679              }
12680            )
12681          ] })
12682        ] }) })
12683      ]
12684    }
12685  );
12686};
12687export {
12688  WatchPage as default
12689};

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.