PageSourceSearch

https://www.hamienet.com/assets/EditorPage-8k3dswSw.js

js hamienet.com collected 2026-10-02 04:59:52 UTC 599,728 bytes, 10,788 lines download raw bytes

1const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ScoreEditorCanvas-Cg6gP8yn.js","assets/vendor-react-Bay9Y7Lb.js","assets/vendor-audio-D-wTggbx.js","assets/vendor-D0P4XgNj.js","assets/vendor-i18n-BC7ZMXWQ.js","assets/vendor-DqKRdbjz.css","assets/useMidiKeyboard-DlyEIN8T.js","assets/MidiEngine-2SYF-_aI.js","assets/vendor-spessasynth-DH8n6o_V.js"])))=>i.map(i=>d[i]);
2import { j as jsxRuntimeExports, r as reactExports, R as React, a as reactDomExports } from "./vendor-react-Bay9Y7Lb.js";
3import { u as useEditor, a as useScoreEditorEngine, g as getMidiNotesFromBase64, c as convertMidiToMEI, b as getMidiFromMEI, d as getMeiDurAttr, S as ScoreEditorCanvas, e as getBeats, f as getDurationInBeats, E as EditorProvider } from "./ScoreEditorCanvas-Cg6gP8yn.js";
4import { bG as CircleQuestionMark, b_ as Pause, bV as Play, di as Square, dj as Circle, ck as Layers, cf as Plus, b5 as Trash2, dk as Pencil, dl as Brush, dm as Eraser, bX as VolumeX, dn as Scissors, dp as Split, dq as MousePointer2, dr as Magnet, cg as Activity, ds as ZoomOut, dt as ZoomIn, bF as ChevronRight, cC as ChevronDown, c2 as Copy, dh as SlidersVertical, aW as X, du as Folder, d8 as SquarePen, dv as Wrench, bg as Music, bA as Eye, bM as Clock, b7 as Check, b9 as Settings, dw as GripHorizontal, bz as EyeOff, cc as Pin, cb as PinOff, dx as Ut, dy as Yt, dz as Qt, bI as Hash, dA as Grid3x3, dB as LayoutTemplate, dC as Columns2, cn as ArrowRight, aY as TriangleAlert, ba as Info, cG as Keyboard, dD as Paintbrush, dE as Undo2, dF as Redo2, dG as ClipboardPaste, cd as Save, d1 as Download, dH as LayoutDashboard, cX as Minimize, cY as Maximize, c$ as Minimize2, d0 as Maximize2, b4 as FolderOpen, dI as PanelLeftClose, dJ as PanelLeftOpen, dK as PanelRightClose, dL as PanelRightOpen, dM as Move, dN as MonitorUp, bR as ExternalLink } from "./vendor-D0P4XgNj.js";
5import { u as useTranslation, i as instance } from "./vendor-i18n-BC7ZMXWQ.js";
6import { I as INSTRUMENT_CATEGORIES, l as GM_INSTRUMENTS, G as GM_PERCUSSION_MAP, k as getTrackColor } from "./utils-CEVb7nN1.js";
7import "./index-w5lXls15.js";
8import { _ as __vitePreload } from "./vendor-react-three-BCww3P1x.js";
9import { P as ProjectList } from "./ProjectList-CcSw2aTd.js";
10import "./vendor-audio-D-wTggbx.js";
11import "./useMidiKeyboard-DlyEIN8T.js";
12import "./MidiEngine-2SYF-_aI.js";
13import "./vendor-spessasynth-DH8n6o_V.js";
14const HelpTip = ({
15  text,
16  title,
17  className = "",
18  iconSize = 14,
19  position = "top",
20  children
21}) => {
22  const positionClasses = {
23    top: "bottom-full mb-2 left-1/2 -translate-x-1/2",
24    bottom: "top-full mt-2 left-1/2 -translate-x-1/2",
25    left: "right-full mr-2 top-1/2 -translate-y-1/2",
26    right: "left-full ml-2 top-1/2 -translate-y-1/2"
27  };
28  const arrowClasses = {
29    top: "bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 w-2.5 h-2.5 border-b border-r",
30    bottom: "top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 w-2.5 h-2.5 border-t border-l",
31    left: "right-0 top-1/2 translate-x-1/2 -translate-y-1/2 w-2.5 h-2.5 border-t border-r",
32    right: "left-0 top-1/2 -translate-x-1/2 -translate-y-1/2 w-2.5 h-2.5 border-b border-l"
33  };
34  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `relative group/tooltip inline-flex items-center justify-center ${children ? "" : "mx-1 align-middle"} ${className}`, children: [
35    children || /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: iconSize, className: "text-zinc-400 hover:text-zinc-500 dark:text-zinc-500 dark:hover:text-zinc-400 cursor-help transition-colors" }),
36    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `absolute ${positionClasses[position]} hidden group-hover/tooltip:flex flex-col w-56 p-3 rounded-lg 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-[9999] transition-all duration-300 animate-in fade-in zoom-in-95`, children: [
37      title && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-amber-400 mb-1 text-left", children: title }),
38      /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-300 font-normal m-0 p-0 text-left", children: text }),
39      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute ${arrowClasses[position]} bg-zinc-900 dark:bg-zinc-800 border-white/10 dark:border-zinc-700/80 rotate-45` })
40    ] })
41  ] });
42};
43const TransportControls = () => {
44  const { t } = useTranslation();
45  const {
46    isEngineReady,
47    isPlaying,
48    play,
49    pause,
50    stop,
51    currentTime,
52    duration,
53    seek,
54    engine,
55    playbackMode,
56    setPlaybackMode,
57    isRecording,
58    setIsRecording,
59    isTypingKeyboardEnabled,
60    setIsTypingKeyboardEnabled,
61    isCountdownEnabled,
62    setIsCountdownEnabled,
63    isMetronomeEnabled,
64    setIsMetronomeEnabled,
65    isLoopRecordingEnabled,
66    setIsLoopRecordingEnabled,
67    masterVolume,
68    setMasterVolume,
69    masterPitch,
70    setMasterPitch,
71    tempo,
72    setTempo,
73    easyMode
74  } = useEditor();
75  const bpmRef = reactExports.useRef(null);
76  const [isBpmEditing, setIsBpmEditing] = reactExports.useState(false);
77  const [bpmInputValue, setBpmInputValue] = reactExports.useState(tempo.toString());
78  const dragStartY = reactExports.useRef(0);
79  const dragStartBpm = reactExports.useRef(0);
80  const [isDraggingBpm, setIsDraggingBpm] = reactExports.useState(false);
81  const canvasRef = reactExports.useRef(null);
82  const [voicesCount, setVoicesCount] = reactExports.useState(0);
83  const [memoryEstimation, setMemoryEstimation] = reactExports.useState(296);
84  const [cpuLoad, setCpuLoad] = reactExports.useState(4);
85  const formatBST = (timeSecs) => {
86    if (!engine) return "1:1:000";
87    const ticks = engine.secondsToTicks(timeSecs);
88    const ppq = engine.midi.header.ppq || 96;
89    const ticksPerStep = ppq / 4;
90    const ticksPerBar = ppq * 4;
91    const bar = Math.floor(ticks / ticksPerBar) + 1;
92    const step = Math.floor(ticks % ticksPerBar / ticksPerStep) + 1;
93    const tick = Math.round(ticks % ticksPerStep);
94    const stepStr = step.toString().padStart(2, "0");
95    const tickStr = tick.toString().padStart(3, "0");
96    return `${bar}:${stepStr}:${tickStr}`;
97  };
98  const handleBpmWheel = (e) => {
99    e.preventDefault();
100    const delta = e.deltaY < 0 ? 1 : -1;
101    const nextBpm = Math.max(10, Math.min(280, Math.round((tempo + delta) * 10) / 10));
102    setTempo(nextBpm);
103  };
104  const handleBpmMouseDown = (e) => {
105    if (e.button !== 0) return;
106    dragStartY.current = e.clientY;
107    dragStartBpm.current = tempo;
108    setIsDraggingBpm(true);
109    e.preventDefault();
110  };
111  reactExports.useEffect(() => {
112    const handleMouseMove = (e) => {
113      if (!isDraggingBpm) return;
114      const deltaY = dragStartY.current - e.clientY;
115      const bpmChange = Math.round(deltaY * 0.2 * 10) / 10;
116      const nextBpm = Math.max(10, Math.min(280, Math.round((dragStartBpm.current + bpmChange) * 10) / 10));
117      setTempo(nextBpm);
118    };
119    const handleMouseUp = () => {
120      setIsDraggingBpm(false);
121    };
122    if (isDraggingBpm) {
123      window.addEventListener("mousemove", handleMouseMove);
124      window.addEventListener("mouseup", handleMouseUp);
125    }
126    return () => {
127      window.removeEventListener("mousemove", handleMouseMove);
128      window.removeEventListener("mouseup", handleMouseUp);
129    };
130  }, [isDraggingBpm, setTempo]);
131  const handleBpmDoubleClick = () => {
132    setBpmInputValue(tempo.toFixed(3));
133    setIsBpmEditing(true);
134  };
135  const handleBpmInputBlur = () => {
136    const val = parseFloat(bpmInputValue);
137    if (!isNaN(val)) {
138      setTempo(Math.max(10, Math.min(280, val)));
139    }
140    setIsBpmEditing(false);
141  };
142  const handleBpmInputKeyDown = (e) => {
143    if (e.key === "Enter") {
144      handleBpmInputBlur();
145    } else if (e.key === "Escape") {
146      setIsBpmEditing(false);
147    }
148  };
149  reactExports.useEffect(() => {
150    let animationFrameId;
151    const drawOscilloscope = () => {
152      if (canvasRef.current && engine && engine.analyser) {
153        const canvas = canvasRef.current;
154        const ctx = canvas.getContext("2d");
155        if (ctx) {
156          const analyser = engine.analyser;
157          const bufferLength = analyser.frequencyBinCount;
158          const dataArray = new Uint8Array(bufferLength);
159          analyser.getByteTimeDomainData(dataArray);
160          ctx.fillStyle = "#202528";
161          ctx.fillRect(0, 0, canvas.width, canvas.height);
162          ctx.strokeStyle = "#2d3336";
163          ctx.lineWidth = 1;
164          ctx.beginPath();
165          ctx.moveTo(0, canvas.height / 2);
166          ctx.lineTo(canvas.width, canvas.height / 2);
167          ctx.stroke();
168          ctx.strokeStyle = isPlaying ? "#ff8c00" : "#4ade80";
169          ctx.lineWidth = 2;
170          ctx.shadowBlur = isPlaying ? 6 : 2;
171          ctx.shadowColor = isPlaying ? "#ff8c00" : "#4ade80";
172          ctx.beginPath();
173          const sliceWidth = canvas.width / bufferLength;
174          let x = 0;
175          for (let i = 0; i < bufferLength; i++) {
176            const v = dataArray[i] / 128;
177            const y = v * canvas.height / 2;
178            if (i === 0) {
179              ctx.moveTo(x, y);
180            } else {
181              ctx.lineTo(x, y);
182            }
183            x += sliceWidth;
184          }
185          ctx.lineTo(canvas.width, canvas.height / 2);
186          ctx.stroke();
187          ctx.shadowBlur = 0;
188        }
189      } else if (canvasRef.current) {
190        const canvas = canvasRef.current;
191        const ctx = canvas.getContext("2d");
192        if (ctx) {
193          ctx.fillStyle = "#202528";
194          ctx.fillRect(0, 0, canvas.width, canvas.height);
195          ctx.strokeStyle = "#4ade80";
196          ctx.lineWidth = 2;
197          ctx.beginPath();
198          ctx.moveTo(0, canvas.height / 2);
199          ctx.lineTo(canvas.width, canvas.height / 2);
200          ctx.stroke();
201        }
202      }
203    };
204    const updatePerformanceMetrics = () => {
205      if (engine) {
206        const voices = engine.getVoicesAmount();
207        setVoicesCount(voices);
208        const cpu = Math.min(99, Math.max(1, Math.round(voices * 0.8 + (isPlaying ? 4 : 1) + Math.random() * 1.5)));
209        setCpuLoad(cpu);
210        const memoryPerformance = performance.memory;
211        if (memoryPerformance) {
212          const jsHeap = Math.round(memoryPerformance.usedJSHeapSize / (1024 * 1024));
213          setMemoryEstimation(jsHeap);
214        } else {
215          const baseMemory = 296;
216          const noise = Math.round(Math.sin(Date.now() / 2e3) * 4);
217          setMemoryEstimation(baseMemory + noise + voices);
218        }
219      }
220    };
221    const tick = () => {
222      drawOscilloscope();
223      updatePerformanceMetrics();
224      animationFrameId = requestAnimationFrame(tick);
225    };
226    tick();
227    return () => {
228      cancelAnimationFrame(animationFrameId);
229    };
230  }, [engine, isPlaying]);
231  if (!isEngineReady) {
232    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 text-sm font-mono flex items-center h-full px-4 select-none", children: t("editor.initializingTransport", "Initializing Transport...") });
233  }
234  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
235    /* @__PURE__ */ jsxRuntimeExports.jsx("style", { children: `
236                @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Share+Tech+Mono&display=swap');
237                
238                .digital-clock {
239                    font-family: 'Share Tech Mono', 'Orbitron', monospace;
240                    text-shadow: 0 0 6px rgba(74, 222, 128, 0.4);
241                }
242                .digital-clock-playing {
243                    text-shadow: 0 0 8px rgba(255, 140, 0, 0.6);
244                }
245                .no-scrollbar::-webkit-scrollbar {
246                    display: none;
247                }
248                .no-scrollbar {
249                    -ms-overflow-style: none;
250                    scrollbar-width: none;
251                }
252            ` }),
253    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-[#25292c] border border-zinc-800 rounded px-2 py-1 gap-2.5 h-11 select-none overflow-x-auto no-scrollbar shadow-md", children: [
254      !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.playbackModeHelp", "Pattern / Playlist Song playback selector"), position: "bottom", className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col border border-zinc-700 bg-zinc-900 rounded overflow-hidden h-9 w-12 text-[10px] font-bold", children: [
255        /* @__PURE__ */ jsxRuntimeExports.jsx(
256          "button",
257          {
258            onClick: () => setPlaybackMode("pat"),
259            className: `flex-1 flex items-center justify-center transition-all ${playbackMode === "pat" ? "bg-gradient-to-b from-[#ff8c00] to-[#cc7000] text-black shadow-[0_0_10px_rgba(255,140,0,0.5)]" : "text-zinc-500 hover:text-zinc-300 bg-transparent"}`,
260            children: "PAT"
261          }
262        ),
263        /* @__PURE__ */ jsxRuntimeExports.jsx(
264          "button",
265          {
266            onClick: () => setPlaybackMode("song"),
267            className: `flex-1 flex items-center justify-center border-t border-zinc-800 transition-all ${playbackMode === "song" ? "bg-gradient-to-b from-[#ff8c00] to-[#cc7000] text-black shadow-[0_0_10px_rgba(255,140,0,0.5)]" : "text-zinc-500 hover:text-zinc-300 bg-transparent"}`,
268            children: "SONG"
269          }
270        )
271      ] }) }),
272      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-900 border border-zinc-700 rounded h-9 px-1 gap-0.5 shrink-0", children: [
273        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: isPlaying ? t("editor.pause", "Pause") : t("editor.play", "Play"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
274          "button",
275          {
276            onClick: isPlaying ? pause : play,
277            className: `w-7 h-7 rounded flex items-center justify-center transition-colors ${isPlaying ? "bg-gradient-to-b from-[#ff9f3b] to-[#e67300] text-black shadow-[0_0_8px_rgba(230,115,0,0.6)]" : "text-zinc-400 hover:text-white hover:bg-zinc-800"}`,
278            children: isPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { size: 13, fill: "currentColor", stroke: "none" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 13, fill: "currentColor", stroke: "none" })
279          }
280        ) }),
281        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.stopHelp", "Stop playback & Reset playhead"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
282          "button",
283          {
284            onClick: stop,
285            className: "w-7 h-7 rounded flex items-center justify-center text-zinc-400 hover:text-white hover:bg-zinc-800 transition-colors",
286            children: /* @__PURE__ */ jsxRuntimeExports.jsx(Square, { size: 12, fill: "currentColor", stroke: "none" })
287          }
288        ) }),
289        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.recordArmHelp", "Arm sequencer for recording MIDI input"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
290          "button",
291          {
292            onClick: () => setIsRecording(!isRecording),
293            className: `w-7 h-7 rounded flex items-center justify-center transition-colors ${isRecording ? "bg-red-600/30 text-red-500 border border-red-500/40 shadow-[0_0_8px_rgba(239,68,68,0.4)]" : "text-zinc-400 hover:text-red-500 hover:bg-zinc-800"}`,
294            children: /* @__PURE__ */ jsxRuntimeExports.jsx(Circle, { size: 10, fill: "currentColor", stroke: "none" })
295          }
296        ) })
297      ] }),
298      /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.bpmHelp", "BPM (Double-click to type, Drag up/down, Scroll wheel)"), position: "bottom", className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
299        "div",
300        {
301          ref: bpmRef,
302          onWheel: handleBpmWheel,
303          onMouseDown: handleBpmMouseDown,
304          onDoubleClick: handleBpmDoubleClick,
305          className: `bg-[#2c3236] border border-zinc-700 rounded h-9 w-[74px] flex items-center justify-center font-mono text-sm relative cursor-ns-resize ${isDraggingBpm ? "border-amber-500/80 bg-zinc-800" : "text-zinc-300"}`,
306          children: [
307            isBpmEditing ? /* @__PURE__ */ jsxRuntimeExports.jsx(
308              "input",
309              {
310                type: "text",
311                value: bpmInputValue,
312                onChange: (e) => setBpmInputValue(e.target.value),
313                onBlur: handleBpmInputBlur,
314                onKeyDown: handleBpmInputKeyDown,
315                autoFocus: true,
316                className: "bg-zinc-950 text-amber-500 font-bold outline-none border border-amber-600 text-center w-full h-full text-xs rounded"
317              }
318            ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center w-full select-none", children: [
319              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 leading-3 font-sans font-bold select-none", children: t("editor.tempoLabel", "TEMPO") }),
320              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold tracking-tight text-zinc-200", children: tempo.toFixed(easyMode ? 0 : 3) })
321            ] }),
322            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute right-0.5 top-0 bottom-0 flex flex-col justify-center gap-0.5 text-zinc-600 pointer-events-none", children: [
323              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px]", children: "▲" }),
324              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px]", children: "▼" })
325            ] })
326          ]
327        }
328      ) }),
329      /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.currentTimeHelp", "Current playback position (Bar:Step:Tick)"), position: "bottom", className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
330        "div",
331        {
332          onClick: () => seek(0),
333          className: "bg-zinc-950 border border-zinc-800 rounded h-9 px-3 min-w-[110px] flex items-center justify-between relative cursor-pointer group hover:border-zinc-700 transition-colors",
334          children: [
335            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `digital-clock text-xl font-bold tracking-widest leading-none ${isPlaying ? "text-[#ff8c00] digital-clock-playing" : "text-emerald-400"}`, children: formatBST(currentTime) }),
336            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col text-[8px] font-bold text-zinc-500 text-right font-sans pl-1.5 justify-center leading-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: isPlaying ? "text-[#ff8c00]" : "text-emerald-500", children: "B:S:T" }) })
337          ]
338        }
339      ) }),
340      !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-900 border border-zinc-700 rounded h-9 px-1 gap-1 shrink-0", children: [
341        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Typing keyboard to piano keyboard toggle (Use keys A,W,S,E,D,F,T,G,Y,H,U,J...)", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
342          "button",
343          {
344            onClick: () => setIsTypingKeyboardEnabled(!isTypingKeyboardEnabled),
345            className: `w-7 h-7 rounded flex items-center justify-center transition-colors ${isTypingKeyboardEnabled ? "bg-gradient-to-b from-[#ff8c00] to-[#cc7000] text-black shadow-[0_0_8px_rgba(255,140,0,0.5)]" : "text-zinc-400 hover:text-white hover:bg-zinc-800"}`,
346            children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v3h-2V8zm0 4h2v3h-2v-3zM8 8h2v3H8V8zm0 4h2v3H8v-3zM5 8h2v3H5V8zm0 4h2v3H5v-3zm14 3h-4v-3h4v3zm0-4h-2V8h2v3zm-3-3h2v3h-2V8z" }) })
347          }
348        ) }),
349        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "1-bar Countdown (3.2.1) before recording", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
350          "button",
351          {
352            onClick: () => setIsCountdownEnabled(!isCountdownEnabled),
353            className: `w-7 h-7 rounded flex items-center justify-center font-mono text-[10px] font-bold tracking-tighter leading-none transition-colors ${isCountdownEnabled ? "bg-gradient-to-b from-[#ff8c00] to-[#cc7000] text-black shadow-[0_0_8px_rgba(255,140,0,0.5)]" : "text-zinc-400 hover:text-white hover:bg-zinc-800"}`,
354            children: "3.2.1"
355          }
356        ) }),
357        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Audible Metronome click track", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
358          "button",
359          {
360            onClick: () => setIsMetronomeEnabled(!isMetronomeEnabled),
361            className: `w-7 h-7 rounded flex items-center justify-center transition-colors ${isMetronomeEnabled ? "bg-gradient-to-b from-[#ff8c00] to-[#cc7000] text-black shadow-[0_0_8px_rgba(255,140,0,0.5)]" : "text-zinc-400 hover:text-white hover:bg-zinc-800"}`,
362            children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2L5 22h14L12 2zm0 6v10M9 22h6" }) })
363          }
364        ) }),
365        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Loop recording / overdub mode", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
366          "button",
367          {
368            onClick: () => setIsLoopRecordingEnabled(!isLoopRecordingEnabled),
369            className: `w-7 h-7 rounded flex items-center justify-center transition-colors ${isLoopRecordingEnabled ? "bg-gradient-to-b from-[#ff8c00] to-[#cc7000] text-black shadow-[0_0_8px_rgba(255,140,0,0.5)]" : "text-zinc-400 hover:text-white hover:bg-zinc-800"}`,
370            children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21.5 2v6h-6M21.34 15.57a10 10 0 11-.57-8.38l.57-.57" }) })
371          }
372        ) })
373      ] }),
374      !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Real-time Master output Oscilloscope", position: "bottom", className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border border-zinc-700 bg-zinc-950 rounded overflow-hidden h-9 w-[100px] relative", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
375        "canvas",
376        {
377          ref: canvasRef,
378          width: "100",
379          height: "36",
380          className: "w-full h-full block"
381        }
382      ) }) }),
383      !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: `Master Volume: ${Math.round(masterVolume * 100)}% | Pitch: ${masterPitch > 0 ? `+${masterPitch}` : masterPitch} semitones`, position: "bottom", className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col justify-center border border-zinc-700 bg-zinc-900 rounded h-9 px-2 gap-1 min-w-[120px] shrink-0 text-[9px] font-sans font-bold", children: [
384        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 w-full", children: [
385          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 w-2.5", children: "V" }),
386          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 h-2.5 bg-zinc-950 rounded border border-zinc-800 relative overflow-hidden flex items-center", children: [
387            /* @__PURE__ */ jsxRuntimeExports.jsx(
388              "input",
389              {
390                type: "range",
391                min: "0",
392                max: "1.2",
393                step: "0.05",
394                value: masterVolume,
395                onChange: (e) => setMasterVolume(parseFloat(e.target.value)),
396                className: "absolute inset-0 opacity-0 cursor-pointer z-10 w-full",
397                title: `Volume: ${Math.round(masterVolume * 100)}%`
398              }
399            ),
400            /* @__PURE__ */ jsxRuntimeExports.jsx(
401              "div",
402              {
403                className: "h-full bg-gradient-to-r from-amber-500 to-[#ff8c00] shadow-[0_0_6px_rgba(255,140,0,0.6)]",
404                style: { width: `${Math.min(100, masterVolume / 1.2 * 100)}%` }
405              }
406            )
407          ] })
408        ] }),
409        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 w-full", children: [
410          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 w-2.5", children: "P" }),
411          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 h-2.5 bg-zinc-950 rounded border border-zinc-800 relative overflow-hidden flex items-center", children: [
412            /* @__PURE__ */ jsxRuntimeExports.jsx(
413              "input",
414              {
415                type: "range",
416                min: "-12",
417                max: "12",
418                step: "1",
419                value: masterPitch,
420                onChange: (e) => setMasterPitch(parseInt(e.target.value, 10)),
421                className: "absolute inset-0 opacity-0 cursor-pointer z-10 w-full",
422                title: `Master Pitch: ${masterPitch > 0 ? `+${masterPitch}` : masterPitch} semitones`
423              }
424            ),
425            /* @__PURE__ */ jsxRuntimeExports.jsx(
426              "div",
427              {
428                className: "h-full bg-gradient-to-r from-emerald-500 to-teal-400 shadow-[0_0_6px_rgba(52,211,153,0.6)] absolute transition-all duration-75",
429                style: {
430                  left: masterPitch >= 0 ? "50%" : `${50 + masterPitch / 12 * 50}%`,
431                  width: `${Math.abs(masterPitch / 12) * 50}%`
432                }
433              }
434            ),
435            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-1/2 -translate-x-1/2 top-0 bottom-0 w-0.5 bg-zinc-700" })
436          ] })
437        ] })
438      ] }) }),
439      !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "System telemetry: Polyphony count, memory footprint, CPU load", position: "bottom", className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-700 bg-zinc-900 rounded h-9 px-2 min-w-[130px] flex items-center justify-between gap-2.5 text-[9px] font-mono text-zinc-400", children: [
440        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col justify-center leading-3", children: [
441          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
442            /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "9", height: "9", fill: "currentColor", className: "text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0-2-.9-2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12z" }) }),
443            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
444              "VOICES: ",
445              /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-zinc-200", children: voicesCount })
446            ] })
447          ] }),
448          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
449            /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "9", height: "9", fill: "currentColor", className: "text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 6h-4v2h4v-2zm0 4h-4v2h4v-2z" }) }),
450            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
451              "MEM: ",
452              /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-zinc-200", children: [
453                memoryEstimation,
454                " MB"
455              ] })
456            ] })
457          ] })
458        ] }),
459        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col justify-center w-12 gap-0.5 leading-none", children: [
460          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] text-zinc-500 font-sans font-bold", children: "CPU LOAD" }),
461          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-1.5 bg-zinc-950 border border-zinc-800 rounded-sm relative overflow-hidden flex items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
462            "div",
463            {
464              className: `h-full transition-all duration-100 ${cpuLoad > 60 ? "bg-red-500" : cpuLoad > 30 ? "bg-amber-500" : "bg-emerald-500"}`,
465              style: { width: `${cpuLoad}%` }
466            }
467          ) }),
468          /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[8px] text-zinc-300 font-bold self-end", children: [
469            cpuLoad,
470            "%"
471          ] })
472        ] })
473      ] }) })
474    ] })
475  ] });
476};
477const FLHorizontalScrollbar = ({
478  containerRef,
479  zoomX,
480  setZoomX,
481  maxTicks,
482  ppq,
483  beatWidth,
484  mode = "piano-roll",
485  patterns = [],
486  playlistClips = [],
487  activePatternId = null,
488  playlistTracks = []
489}) => {
490  const trackRef = reactExports.useRef(null);
491  const thumbRef = reactExports.useRef(null);
492  const canvasRef = reactExports.useRef(null);
493  const drawMiniMap = reactExports.useCallback(() => {
494    if (!canvasRef.current || !trackRef.current) return;
495    const canvas = canvasRef.current;
496    const ctx = canvas.getContext("2d");
497    if (!ctx) return;
498    const rect = trackRef.current.getBoundingClientRect();
499    if (rect.width === 0 || rect.height === 0) return;
500    canvas.width = rect.width;
501    canvas.height = rect.height;
502    ctx.clearRect(0, 0, canvas.width, canvas.height);
503    if (mode === "timeline") {
504      if (!playlistClips || playlistClips.length === 0 || !patterns || !playlistTracks || playlistTracks.length === 0) return;
505      const N = playlistTracks.length;
506      playlistClips.forEach((clip) => {
507        const pattern = patterns.find((p) => p.id === clip.patternId);
508        if (!pattern) return;
509        const left = clip.startTick / maxTicks * canvas.width;
510        const width = clip.durationTicks / maxTicks * canvas.width;
511        const trackH = canvas.height / N;
512        const top = clip.trackIndex * trackH;
513        ctx.fillStyle = pattern.color + "25";
514        ctx.strokeStyle = pattern.color + "80";
515        ctx.lineWidth = 1;
516        ctx.fillRect(left, top + 1, width, trackH - 2);
517        ctx.strokeRect(left, top + 1, width, trackH - 2);
518        if (pattern.notes.length === 0) return;
519        const patternLength = pattern.notes.reduce((max, n) => Math.max(max, n.ticks + n.durationTicks), 0) || ppq * 4;
520        if (patternLength <= 0) return;
521        const midis = pattern.notes.map((n) => n.midi);
522        const minM = Math.min(...midis) - 1;
523        const maxM = Math.max(...midis) + 1;
524        const mDiff = maxM - minM || 1;
525        const numRepeats = Math.ceil(clip.durationTicks / patternLength);
526        ctx.fillStyle = pattern.color + "cc";
527        for (let r = 0; r < numRepeats; r++) {
528          const loopOffset = r * patternLength - clip.offsetTicks;
529          pattern.notes.forEach((note) => {
530            const noteGlobalTick = clip.startTick + loopOffset + note.ticks;
531            if (noteGlobalTick < clip.startTick || noteGlobalTick >= clip.startTick + clip.durationTicks) return;
532            const noteLeft = noteGlobalTick / maxTicks * canvas.width;
533            const noteWidth = note.durationTicks / maxTicks * canvas.width;
534            const noteYRatio = (note.midi - minM) / mDiff;
535            const noteTop = top + 2 + (1 - noteYRatio) * (trackH - 5);
536            ctx.fillRect(noteLeft, noteTop, Math.max(1.5, noteWidth), 1);
537          });
538        }
539      });
540    } else if (mode === "piano-roll") {
541      const activePattern = patterns == null ? void 0 : patterns.find((p) => p.id === activePatternId);
542      if (!activePattern || !activePattern.notes || activePattern.notes.length === 0) return;
543      const notes = activePattern.notes;
544      const midis = notes.map((n) => n.midi);
545      const minM = Math.min(...midis) - 2;
546      const maxM = Math.max(...midis) + 2;
547      const midiDiff = maxM - minM || 1;
548      ctx.fillStyle = activePattern.color || "#6366f1";
549      notes.forEach((note) => {
550        const left = note.ticks / maxTicks * canvas.width;
551        const width = note.durationTicks / maxTicks * canvas.width;
552        const noteYRatio = (note.midi - minM) / midiDiff;
553        const top = (1 - noteYRatio) * (canvas.height - 4) + 2;
554        ctx.fillRect(left, top, Math.max(2, width), 2);
555      });
556    }
557  }, [mode, patterns, playlistClips, playlistTracks, activePatternId, maxTicks, ppq]);
558  const syncThumb = reactExports.useCallback(() => {
559    if (!containerRef.current || !trackRef.current || !thumbRef.current) return;
560    const container = containerRef.current;
561    const trackWidth = trackRef.current.clientWidth;
562    const viewportWidth = container.clientWidth;
563    const scrollLeft = container.scrollLeft;
564    const pxPerTick = beatWidth * zoomX / ppq;
565    const contentWidth = maxTicks * pxPerTick;
566    let f_w = viewportWidth / contentWidth;
567    if (f_w > 1) f_w = 1;
568    const f_x = scrollLeft / contentWidth;
569    const thumbWidth = f_w * trackWidth;
570    const thumbLeft = f_x * trackWidth;
571    thumbRef.current.style.width = `${Math.max(thumbWidth, 24)}px`;
572    thumbRef.current.style.transform = `translateX(${thumbLeft}px)`;
573    drawMiniMap();
574  }, [zoomX, maxTicks, ppq, beatWidth, containerRef, drawMiniMap]);
575  reactExports.useEffect(() => {
576    const container = containerRef.current;
577    if (container) {
578      container.addEventListener("scroll", syncThumb);
579      window.addEventListener("resize", syncThumb);
580    }
581    syncThumb();
582    return () => {
583      if (container) container.removeEventListener("scroll", syncThumb);
584      window.removeEventListener("resize", syncThumb);
585    };
586  }, [syncThumb, containerRef]);
587  reactExports.useEffect(() => {
588    drawMiniMap();
589  }, [drawMiniMap, patterns, playlistClips, playlistTracks, activePatternId]);
590  const isDraggingRef = reactExports.useRef(false);
591  const dragModeRef = reactExports.useRef(null);
592  const dragStartXRef = reactExports.useRef(0);
593  const startScrollLeftRef = reactExports.useRef(0);
594  const startZoomXRef = reactExports.useRef(0);
595  const MIN_ZOOM = 0.1;
596  const MAX_ZOOM = 10;
597  const handlePointerDown = (e, mode2) => {
598    e.stopPropagation();
599    e.currentTarget.setPointerCapture(e.pointerId);
600    isDraggingRef.current = true;
601    dragModeRef.current = mode2;
602    dragStartXRef.current = e.clientX;
603    if (containerRef.current) {
604      startScrollLeftRef.current = containerRef.current.scrollLeft;
605    }
606    startZoomXRef.current = zoomX;
607  };
608  const handlePointerMove = (e) => {
609    if (!isDraggingRef.current || !containerRef.current || !trackRef.current) return;
610    const deltaX = e.clientX - dragStartXRef.current;
611    const trackWidth = trackRef.current.clientWidth;
612    const viewportWidth = containerRef.current.clientWidth;
613    const startPxPerTick = beatWidth * startZoomXRef.current / ppq;
614    const startContentWidth = maxTicks * startPxPerTick;
615    if (dragModeRef.current === "center") {
616      const scrollDelta = deltaX / trackWidth * startContentWidth;
617      containerRef.current.scrollLeft = startScrollLeftRef.current + scrollDelta;
618    } else if (dragModeRef.current === "right") {
619      const f_w_old = viewportWidth / startContentWidth;
620      const thumbWidthOld = f_w_old * trackWidth;
621      let thumbWidthNew = thumbWidthOld + deltaX;
622      thumbWidthNew = Math.max(24, Math.min(trackWidth, thumbWidthNew));
623      const newContentWidth = viewportWidth * trackWidth / thumbWidthNew;
624      const newPxPerTick = newContentWidth / maxTicks;
625      let newZoomX = newPxPerTick * ppq / beatWidth;
626      newZoomX = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoomX));
627      const actualNewPxPerTick = beatWidth * newZoomX / ppq;
628      const tickLeft = startScrollLeftRef.current / startPxPerTick;
629      containerRef.current.scrollLeft = tickLeft * actualNewPxPerTick;
630      setZoomX(newZoomX);
631    } else if (dragModeRef.current === "left") {
632      const f_w_old = viewportWidth / startContentWidth;
633      const thumbWidthOld = f_w_old * trackWidth;
634      let thumbWidthNew = thumbWidthOld - deltaX;
635      thumbWidthNew = Math.max(24, Math.min(trackWidth, thumbWidthNew));
636      const newContentWidth = viewportWidth * trackWidth / thumbWidthNew;
637      const newPxPerTick = newContentWidth / maxTicks;
638      let newZoomX = newPxPerTick * ppq / beatWidth;
639      newZoomX = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoomX));
640      const actualNewPxPerTick = beatWidth * newZoomX / ppq;
641      const tickRight = (startScrollLeftRef.current + viewportWidth) / startPxPerTick;
642      containerRef.current.scrollLeft = tickRight * actualNewPxPerTick - viewportWidth;
643      setZoomX(newZoomX);
644    }
645  };
646  const handlePointerUp = (e) => {
647    if (!isDraggingRef.current) return;
648    isDraggingRef.current = false;
649    dragModeRef.current = null;
650    e.currentTarget.releasePointerCapture(e.pointerId);
651  };
652  const handleTrackClick = (e) => {
653    if (isDraggingRef.current) return;
654    if (!containerRef.current || !trackRef.current) return;
655    if (e.target !== trackRef.current && e.target !== canvasRef.current) return;
656    const rect = trackRef.current.getBoundingClientRect();
657    const clickX = e.clientX - rect.left;
658    const trackWidth = trackRef.current.clientWidth;
659    const viewportWidth = containerRef.current.clientWidth;
660    const pxPerTick = beatWidth * zoomX / ppq;
661    const contentWidth = maxTicks * pxPerTick;
662    const f_w = viewportWidth / contentWidth;
663    const thumbWidth = f_w * trackWidth;
664    let newThumbLeft = clickX - thumbWidth / 2;
665    newThumbLeft = Math.max(0, Math.min(trackWidth - thumbWidth, newThumbLeft));
666    const newScrollLeft = newThumbLeft / trackWidth * contentWidth;
667    containerRef.current.scrollLeft = newScrollLeft;
668  };
669  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
670    "div",
671    {
672      className: "flex-1 relative h-full bg-zinc-300 dark:bg-zinc-800 shadow-inner overflow-hidden cursor-pointer",
673      ref: trackRef,
674      onPointerDown: (e) => {
675        if (e.target === trackRef.current || e.target === canvasRef.current) {
676          handleTrackClick(e);
677          handlePointerDown(e, "center");
678        }
679      },
680      onPointerMove: handlePointerMove,
681      onPointerUp: handlePointerUp,
682      onPointerCancel: handlePointerUp,
683      onWheel: (e) => {
684        if (e.ctrlKey || e.metaKey) {
685          e.preventDefault();
686          const zoomFactor = e.deltaY > 0 ? -0.25 : 0.25;
687          if (!containerRef.current || !trackRef.current) return;
688          const viewportWidth = containerRef.current.clientWidth;
689          const pxPerTick = beatWidth * zoomX / ppq;
690          const centerTick = (containerRef.current.scrollLeft + viewportWidth / 2) / pxPerTick;
691          const newZoomX = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoomX + zoomFactor));
692          const newPxPerTick = beatWidth * newZoomX / ppq;
693          containerRef.current.scrollLeft = centerTick * newPxPerTick - viewportWidth / 2;
694          setZoomX(newZoomX);
695        } else {
696          if (containerRef.current) {
697            containerRef.current.scrollLeft += e.deltaY > 0 ? 100 : -100;
698          }
699        }
700      },
701      children: [
702        /* @__PURE__ */ jsxRuntimeExports.jsx(
703          "canvas",
704          {
705            ref: canvasRef,
706            className: "absolute inset-0 w-full h-full pointer-events-none opacity-40 dark:opacity-60"
707          }
708        ),
709        /* @__PURE__ */ jsxRuntimeExports.jsxs(
710          "div",
711          {
712            ref: thumbRef,
713            className: "absolute top-0 h-full bg-zinc-400/20 dark:bg-zinc-500/20 hover:bg-zinc-400/30 dark:hover:bg-zinc-500/30 rounded-sm shadow-inner flex items-center justify-between group border-l border-r border-indigo-500/30 dark:border-indigo-400/30 backdrop-blur-[1px]",
714            style: { left: 0, width: 0, willChange: "transform, width" },
715            onPointerDown: (e) => handlePointerDown(e, "center"),
716            children: [
717              /* @__PURE__ */ jsxRuntimeExports.jsx(
718                "div",
719                {
720                  className: "w-2 h-full cursor-ew-resize bg-zinc-400/70 hover:bg-indigo-500 dark:bg-zinc-600/70 dark:hover:bg-indigo-400 transition-colors shrink-0 z-20",
721                  onPointerDown: (e) => handlePointerDown(e, "left")
722                }
723              ),
724              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-[2px] pointer-events-none opacity-40 select-none", children: [
725                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[1px] h-3 bg-zinc-900 dark:bg-zinc-200" }),
726                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[1px] h-3 bg-zinc-900 dark:bg-zinc-200" })
727              ] }),
728              /* @__PURE__ */ jsxRuntimeExports.jsx(
729                "div",
730                {
731                  className: "w-2 h-full cursor-ew-resize bg-zinc-400/70 hover:bg-indigo-500 dark:bg-zinc-600/70 dark:hover:bg-indigo-400 transition-colors shrink-0 z-20",
732                  onPointerDown: (e) => handlePointerDown(e, "right")
733                }
734              )
735            ]
736          }
737        )
738      ]
739    }
740  );
741};
742const BEAT_WIDTH$1 = 40;
743const ArrangementView = ({ isFloating = false, isPoppedOut = false }) => {
744  var _a, _b;
745  const {
746    engine,
747    activeTrackIndex,
748    setActiveTrackIndex,
749    midiVersion,
750    forceSync,
751    patterns,
752    setPatterns,
753    activePatternId,
754    setActivePatternId,
755    playlistTracks,
756    setPlaylistTracks,
757    playlistClips,
758    setPlaylistClips,
759    selectedClipIds,
760    setSelectedClipIds,
761    automationClips,
762    setAutomationClips,
763    timeMarkers,
764    setTimeMarkers,
765    loopRegion,
766    setLoopRegion,
767    gridSnap,
768    setGridSnap,
769    currentPlaylistTool,
770    setCurrentPlaylistTool,
771    easyMode,
772    makePatternUnique,
773    setHint,
774    currentTime,
775    playbackMode,
776    isPlaying
777  } = useEditor();
778  const [zoomX, setZoomX] = reactExports.useState(1);
779  const [zoomY, setZoomY] = reactExports.useState(1);
780  const [isAutomationVisible, setIsAutomationVisible] = reactExports.useState(true);
781  const [renamingPatternId, setRenamingPatternId] = reactExports.useState(null);
782  const [renamingPatternText, setRenamingPatternText] = reactExports.useState("");
783  const [renamingTrackId, setRenamingTrackId] = reactExports.useState(null);
784  const [renamingTrackText, setRenamingTrackText] = reactExports.useState("");
785  const [collapsedGroups, setCollapsedGroups] = reactExports.useState({});
786  const [patternColWidth, setPatternColWidth] = reactExports.useState(224);
787  const [trackColWidth, setTrackColWidth] = reactExports.useState(240);
788  const [isHelpOpen, setIsHelpOpen] = reactExports.useState(false);
789  const [activeHelpTab, setActiveHelpTab] = reactExports.useState("shortcuts");
790  const [activeDragClipId, setActiveDragClipId] = reactExports.useState(null);
791  const [activeDragAction, setActiveDragAction] = reactExports.useState(null);
792  const [dragStartTick, setDragStartTick] = reactExports.useState(0);
793  const [dragStartTrackIdx, setDragStartTrackIdx] = reactExports.useState(0);
794  const [dragStartOffsetTicks, setDragStartOffsetTicks] = reactExports.useState(0);
795  const [dragStartClipState, setDragStartClipState] = reactExports.useState(null);
796  const [dragStartMouseX, setDragStartMouseX] = reactExports.useState(0);
797  const [lassoStart, setLassoStart] = reactExports.useState(null);
798  const [lassoCurrent, setLassoCurrent] = reactExports.useState(null);
799  const [sliceStart, setSliceStart] = reactExports.useState(null);
800  const [sliceCurrent, setSliceCurrent] = reactExports.useState(null);
801  const [dragNodeId, setDragNodeId] = reactExports.useState(null);
802  const [dragNodeClipId, setDragNodeClipId] = reactExports.useState(null);
803  const [dragNodeStartProps, setDragNodeStartProps] = reactExports.useState(null);
804  const [dragTensionClipId, setDragTensionClipId] = reactExports.useState(null);
805  const [dragTensionNodeIdx, setDragTensionNodeIdx] = reactExports.useState(null);
806  const [activeColorPickerId, setActiveColorPickerId] = reactExports.useState(null);
807  const [isAltPressed, setIsAltPressed] = reactExports.useState(false);
808  const [isShiftPressed, setIsShiftPressed] = reactExports.useState(false);
809  const [isCtrlPressed, setIsCtrlPressed] = reactExports.useState(false);
810  const [contextMenu, setContextMenu] = reactExports.useState(null);
811  const arrangementPlayheadRef = reactExports.useRef(null);
812  const lastPaintedTickRef = reactExports.useRef(null);
813  const lastPaintedTrackIdxRef = reactExports.useRef(null);
814  const verticalScrollContainerRef = reactExports.useRef(null);
815  const panStartRef = reactExports.useRef(null);
816  const [isPanning, setIsPanning] = reactExports.useState(false);
817  const rulerDragActionRef = reactExports.useRef(null);
818  const rulerStartTickRef = reactExports.useRef(0);
819  const hasRulerDraggedRef = reactExports.useRef(false);
820  reactExports.useEffect(() => {
821    if (easyMode) {
822      setCurrentPlaylistTool("draw");
823      setGridSnap("beat");
824    }
825  }, [easyMode, setCurrentPlaylistTool, setGridSnap]);
826  const ppq = ((_b = (_a = engine == null ? void 0 : engine.midi) == null ? void 0 : _a.header) == null ? void 0 : _b.ppq) || 96;
827  const pxPerTick = BEAT_WIDTH$1 * zoomX / ppq;
828  reactExports.useEffect(() => {
829    if (!engine) return;
830    let rafId;
831    const updatePlayhead = () => {
832      const rawTime = engine.isPlaying() ? engine.getCurrentTime() : currentTime;
833      const rawTicks = engine.secondsToTicks(rawTime);
834      let displayTicks = rawTicks;
835      if (playbackMode === "pat") {
836        const activePattern = patterns.find((p) => p.id === activePatternId);
837        let patternLength = ppq * 4 * 4;
838        if (activePattern) {
839          const maxNoteTick = activePattern.notes.reduce((max, n) => Math.max(max, n.ticks + n.durationTicks), 0);
840          const ticksPerBar = ppq * 4;
841          patternLength = Math.max(ticksPerBar * 4, Math.ceil(maxNoteTick / ticksPerBar) * ticksPerBar);
842        }
843        displayTicks = patternLength > 0 ? rawTicks % patternLength : rawTicks;
844      }
845      const playheadX = displayTicks * pxPerTick;
846      if (arrangementPlayheadRef.current) {
847        arrangementPlayheadRef.current.style.left = `${playheadX}px`;
848      }
849      if (engine.isPlaying() && gridHorizontalScrollRef.current) {
850        const container = gridHorizontalScrollRef.current;
851        const scrollLeft = container.scrollLeft;
852        const width = container.clientWidth;
853        if (playheadX > scrollLeft + width - 50) {
854          container.scrollLeft = playheadX - width / 2;
855        } else if (playheadX < scrollLeft) {
856          container.scrollLeft = Math.max(0, playheadX - 50);
857        }
858      }
859      rafId = requestAnimationFrame(updatePlayhead);
860    };
861    rafId = requestAnimationFrame(updatePlayhead);
862    return () => cancelAnimationFrame(rafId);
863  }, [engine, isPlaying, playbackMode, pxPerTick, activePatternId, patterns, ppq]);
864  reactExports.useEffect(() => {
865    const handleKeyDown = (e) => {
866      if (e.key === "Alt") {
867        setIsAltPressed(true);
868      }
869      if (e.key === "Shift") {
870        setIsShiftPressed(true);
871      }
872      if (e.key === "Control") {
873        setIsCtrlPressed(true);
874      }
875    };
876    const handleKeyUp = (e) => {
877      if (e.key === "Alt") {
878        setIsAltPressed(false);
879      }
880      if (e.key === "Shift") {
881        setIsShiftPressed(false);
882      }
883      if (e.key === "Control") {
884        setIsCtrlPressed(false);
885      }
886    };
887    const handleBlur = () => {
888      setIsAltPressed(false);
889      setIsShiftPressed(false);
890      setIsCtrlPressed(false);
891    };
892    window.addEventListener("keydown", handleKeyDown);
893    window.addEventListener("keyup", handleKeyUp);
894    window.addEventListener("blur", handleBlur);
895    return () => {
896      window.removeEventListener("keydown", handleKeyDown);
897      window.removeEventListener("keyup", handleKeyUp);
898      window.removeEventListener("blur", handleBlur);
899    };
900  }, []);
901  reactExports.useEffect(() => {
902    const container = gridHorizontalScrollRef.current;
903    if (!container) return;
904    const handleWheelEvent = (e) => {
905      if (e.ctrlKey || e.metaKey) {
906        e.preventDefault();
907        const grid = gridRef.current;
908        if (!grid) return;
909        const gridRect = grid.getBoundingClientRect();
910        const containerRect = container.getBoundingClientRect();
911        const mouseXInGrid = e.clientX - gridRect.left;
912        const targetTick = mouseXInGrid / pxPerTick;
913        const zoomFactor = e.deltaY < 0 ? 1.15 : 0.85;
914        const nextZoomX = Math.max(0.2, Math.min(4, zoomX * zoomFactor));
915        setZoomX(nextZoomX);
916        const nextPxPerTick = BEAT_WIDTH$1 * nextZoomX / ppq;
917        const mouseXInContainer = e.clientX - containerRect.left;
918        const nextScrollLeft = targetTick * nextPxPerTick - mouseXInContainer;
919        container.scrollLeft = nextScrollLeft;
920      } else if (e.altKey) {
921        e.preventDefault();
922        const zoomFactor = e.deltaY < 0 ? 1.15 : 0.85;
923        setZoomY((y) => Math.max(0.5, Math.min(2.5, y * zoomFactor)));
924      }
925    };
926    container.addEventListener("wheel", handleWheelEvent, { passive: false });
927    return () => {
928      container.removeEventListener("wheel", handleWheelEvent);
929    };
930  }, [zoomX, pxPerTick, ppq]);
931  reactExports.useEffect(() => {
932    const closeMenu = () => setContextMenu(null);
933    if (contextMenu) {
934      window.addEventListener("pointerdown", closeMenu);
935      window.addEventListener("click", closeMenu);
936    }
937    return () => {
938      window.removeEventListener("pointerdown", closeMenu);
939      window.removeEventListener("click", closeMenu);
940    };
941  }, [contextMenu]);
942  const gridHorizontalScrollRef = reactExports.useRef(null);
943  const rulerScrollContainerRef = reactExports.useRef(null);
944  const gridRef = reactExports.useRef(null);
945  if (!engine || !engine.midi) return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-zinc-500", children: "Loading playlist..." });
946  engine.getDuration() || 1;
947  const maxTicks = Math.max(engine.midi.durationTicks + ppq * 4 * 16, ppq * 4 * 32);
948  const totalWidth = maxTicks * pxPerTick;
949  const getSnappedTick = (rawTick2) => {
950    if (easyMode) {
951      const snapInterval = gridSnap === "bar" ? ppq * 4 : ppq;
952      return Math.max(0, Math.round(rawTick2 / snapInterval) * snapInterval);
953    }
954    if (isAltPressed) {
955      return Math.max(0, Math.round(rawTick2));
956    }
957    let interval = 1;
958    switch (gridSnap) {
959      case "bar":
960        interval = ppq * 4;
961        break;
962      case "beat":
963        interval = ppq;
964        break;
965      case "1/2":
966        interval = ppq / 2;
967        break;
968      case "1/4":
969        interval = ppq / 4;
970        break;
971      case "1/6":
972        interval = ppq / 6;
973        break;
974      case "cell":
975        interval = ppq / 4;
976        break;
977      case "none":
978        return Math.max(0, Math.round(rawTick2));
979      default:
980        interval = ppq;
981    }
982    return Math.max(0, Math.round(rawTick2 / interval) * interval);
983  };
984  const handleHorizontalScroll = (e) => {
985    if (rulerScrollContainerRef.current) {
986      rulerScrollContainerRef.current.scrollLeft = e.currentTarget.scrollLeft;
987    }
988  };
989  const handleAddPattern = () => {
990    const nextId = `pat-${Date.now()}`;
991    const newPat = {
992      id: nextId,
993      name: `Pattern ${patterns.length + 1}`,
994      color: `#${Math.floor((0.3 + 0.6 * Math.random()) * 16777215).toString(16)}`,
995      notes: []
996    };
997    setPatterns((prev) => [...prev, newPat]);
998    setActivePatternId(nextId);
999  };
1000  const handleDeletePattern = (id, e) => {
1001    var _a2;
1002    e.stopPropagation();
1003    if (patterns.length <= 1) return;
1004    setPatterns((prev) => prev.filter((p) => p.id !== id));
1005    setPlaylistClips((prev) => prev.filter((c) => c.patternId !== id));
1006    if (activePatternId === id) {
1007      const remaining = patterns.filter((p) => p.id !== id);
1008      setActivePatternId(((_a2 = remaining[0]) == null ? void 0 : _a2.id) || null);
1009    }
1010    setTimeout(() => forceSync(), 0);
1011  };
1012  const handleAddPlaylistTrack = () => {
1013    const nextId = `track-${playlistTracks.length}-${Date.now()}`;
1014    const newTrack = {
1015      id: nextId,
1016      name: `Track ${playlistTracks.length + 1}`,
1017      color: `#${Math.floor((0.3 + 0.6 * Math.random()) * 16777215).toString(16)}`,
1018      icon: "synth",
1019      isMuted: false,
1020      isSoloed: false,
1021      volume: 0.8,
1022      panning: 0,
1023      height: 64,
1024      parentId: null
1025    };
1026    if (easyMode) {
1027      const patId = `pat-easy-${playlistTracks.length}-${Date.now()}`;
1028      const newPat = {
1029        id: patId,
1030        name: newTrack.name,
1031        color: newTrack.color,
1032        notes: []
1033      };
1034      const newClip = {
1035        id: `clip-easy-${playlistTracks.length}-${Date.now()}`,
1036        patternId: patId,
1037        trackIndex: playlistTracks.length,
1038        startTick: 0,
1039        durationTicks: 96 * 4 * 128,
1040        // 128 bars
1041        offsetTicks: 0,
1042        isMuted: false
1043      };
1044      setPatterns((prev) => [...prev, newPat]);
1045      setPlaylistClips((prev) => [...prev, newClip]);
1046      setActivePatternId(patId);
1047      setActiveTrackIndex(playlistTracks.length);
1048    }
1049    setPlaylistTracks((prev) => [...prev, newTrack]);
1050    engine.addTrack();
1051    setTimeout(() => forceSync(), 0);
1052  };
1053  const handleDeleteTrack = (idx) => {
1054    if (playlistTracks.length <= 1) return;
1055    playlistTracks[idx];
1056    setPlaylistTracks((prev) => prev.filter((_, i) => i !== idx));
1057    setPlaylistClips((prev) => prev.filter((c) => c.trackIndex !== idx).map((c) => {
1058      if (c.trackIndex > idx) return { ...c, trackIndex: c.trackIndex - 1 };
1059      return c;
1060    }));
1061    setAutomationClips((prev) => prev.filter((c) => c.trackIndex !== idx).map((c) => {
1062      if (c.trackIndex > idx) return { ...c, trackIndex: c.trackIndex - 1 };
1063      return c;
1064    }));
1065    engine.deleteTrack(idx);
1066    setTimeout(() => forceSync(), 0);
1067  };
1068  const toggleTrackMute = (idx) => {
1069    setPlaylistTracks((prev) => {
1070      const next = [...prev];
1071      next[idx].isMuted = !next[idx].isMuted;
1072      return next;
1073    });
1074    setTimeout(() => forceSync(), 0);
1075  };
1076  const toggleTrackSolo = (idx) => {
1077    setPlaylistTracks((prev) => {
1078      const next = [...prev];
1079      next.some((t, i) => i !== idx && t.isSoloed);
1080      const targetSolo = !next[idx].isSoloed;
1081      next[idx].isSoloed = targetSolo;
1082      return next;
1083    });
1084    setTimeout(() => forceSync(), 0);
1085  };
1086  const toggleTrackGroup = (idx) => {
1087    if (idx === 0) return;
1088    setPlaylistTracks((prev) => {
1089      const next = [...prev];
1090      const current = next[idx];
1091      const parent = next[idx - 1];
1092      if (current.parentId) {
1093        current.parentId = null;
1094      } else {
1095        current.parentId = parent.id;
1096      }
1097      return next;
1098    });
1099  };
1100  const getTrackTop = (trackIdx) => {
1101    let top = 0;
1102    for (let i = 0; i < trackIdx; i++) {
1103      const pt = playlistTracks[i];
1104      pt.parentId === null && collapsedGroups[pt.id];
1105      const parentOfI = pt.parentId;
1106      const parentTrack = playlistTracks.find((t) => t.id === parentOfI);
1107      const isChildHidden = parentOfI && parentTrack && collapsedGroups[parentOfI];
1108      if (!isChildHidden) {
1109        top += (pt.height || 64) * zoomY;
1110      }
1111    }
1112    return top;
1113  };
1114  const getTrackHeight = (trackIdx) => {
1115    const pt = playlistTracks[trackIdx];
1116    if (!pt) return 0;
1117    const parentOfI = pt.parentId;
1118    const parentTrack = playlistTracks.find((t) => t.id === parentOfI);
1119    if (parentOfI && parentTrack && collapsedGroups[parentOfI]) {
1120      return 0;
1121    }
1122    return (pt.height || 64) * zoomY;
1123  };
1124  const ticksToPixels = (ticks) => ticks * pxPerTick;
1125  const pixelsToTicks = (px) => px / pxPerTick;
1126  const getPatternLength = (pat) => {
1127    const maxNoteTick = pat.notes.reduce((max, n) => Math.max(max, n.ticks + n.durationTicks), 0);
1128    const ticksPerBar = ppq * 4;
1129    return Math.max(ticksPerBar, maxNoteTick);
1130  };
1131  const handleGridPointerDown = (e) => {
1132    var _a2, _b2;
1133    if (!gridRef.current) return;
1134    const rect = gridRef.current.getBoundingClientRect();
1135    const x = e.clientX - rect.left;
1136    const y = e.clientY - rect.top;
1137    const rawTick2 = pixelsToTicks(x);
1138    const snappedTick = getSnappedTick(rawTick2);
1139    if (e.button === 1) {
1140      e.preventDefault();
1141      panStartRef.current = {
1142        x: e.clientX,
1143        y: e.clientY,
1144        scrollLeft: ((_a2 = gridHorizontalScrollRef.current) == null ? void 0 : _a2.scrollLeft) || 0,
1145        scrollTop: ((_b2 = verticalScrollContainerRef.current) == null ? void 0 : _b2.scrollTop) || 0
1146      };
1147      setIsPanning(true);
1148      document.body.style.cursor = "grabbing";
1149      e.currentTarget.setPointerCapture(e.pointerId);
1150      return;
1151    }
1152    let trackIdx = -1;
1153    let cumulativeHeight = 0;
1154    for (let i = 0; i < playlistTracks.length; i++) {
1155      const h = getTrackHeight(i);
1156      if (h === 0) continue;
1157      if (y >= cumulativeHeight && y < cumulativeHeight + h) {
1158        trackIdx = i;
1159        break;
1160      }
1161      cumulativeHeight += h;
1162    }
1163    if (trackIdx === -1) return;
1164    if (easyMode) {
1165      setActiveTrackIndex(trackIdx);
1166      const trackClip = playlistClips.find((c) => c.trackIndex === trackIdx);
1167      if (trackClip) {
1168        setActivePatternId(trackClip.patternId);
1169      }
1170      return;
1171    }
1172    if (e.button === 2) {
1173      e.preventDefault();
1174      const existingClip = playlistClips.find(
1175        (c) => c.trackIndex === trackIdx && snappedTick >= c.startTick && snappedTick < c.startTick + c.durationTicks
1176      );
1177      if (existingClip) {
1178        setPlaylistClips((prev) => prev.filter((c) => c.id !== existingClip.id));
1179        setTimeout(() => forceSync(), 0);
1180      }
1181      setActiveDragAction("right-delete");
1182      return;
1183    }
1184    const isSelectAction = currentPlaylistTool === "select" || !easyMode && (e.ctrlKey || e.metaKey);
1185    if (isSelectAction) {
1186      setLassoStart({ x, y });
1187      setLassoCurrent({ x, y });
1188      setActiveDragAction("select");
1189      return;
1190    }
1191    if (currentPlaylistTool === "draw" || currentPlaylistTool === "paint") {
1192      if (!activePatternId) return;
1193      const pat = patterns.find((p) => p.id === activePatternId);
1194      if (!pat) return;
1195      const existingClip = playlistClips.find(
1196        (c) => c.trackIndex === trackIdx && c.patternId === activePatternId && snappedTick >= c.startTick && snappedTick < c.startTick + c.durationTicks
1197      ) ?? null;
1198      if (e.button === 0) {
1199        if (currentPlaylistTool === "paint") {
1200          setActiveDragAction("paint");
1201          const patLen = getPatternLength(pat);
1202          if (!existingClip) {
1203            const newClip = {
1204              id: `clip-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1205              patternId: activePatternId,
1206              trackIndex: trackIdx,
1207              startTick: snappedTick,
1208              durationTicks: patLen,
1209              offsetTicks: 0,
1210              isMuted: false
1211            };
1212            setPlaylistClips((prev) => [...prev, newClip]);
1213            lastPaintedTickRef.current = snappedTick;
1214            lastPaintedTrackIdxRef.current = trackIdx;
1215            setTimeout(() => forceSync(), 0);
1216          } else {
1217            lastPaintedTickRef.current = existingClip.startTick;
1218            lastPaintedTrackIdxRef.current = existingClip.trackIndex;
1219          }
1220        } else {
1221          if (!existingClip) {
1222            engine.stopAllNotePreviews();
1223            const patLen = getPatternLength(pat);
1224            const newClip = {
1225              id: `clip-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1226              patternId: activePatternId,
1227              trackIndex: trackIdx,
1228              startTick: snappedTick,
1229              durationTicks: patLen,
1230              offsetTicks: 0,
1231              isMuted: false
1232            };
1233            setPlaylistClips((prev) => [...prev, newClip]);
1234            setTimeout(() => forceSync(), 0);
1235            setActiveDragClipId(newClip.id);
1236            setActiveDragAction("move");
1237            setDragStartTick(snappedTick);
1238            setDragStartTrackIdx(trackIdx);
1239            setDragStartOffsetTicks(0);
1240            setDragStartClipState(newClip);
1241            setDragStartMouseX(e.clientX);
1242          } else {
1243            if (!easyMode && e.shiftKey) {
1244              const cloneId = `clip-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1245              const clonedClip = {
1246                ...existingClip,
1247                id: cloneId
1248              };
1249              setPlaylistClips((prev) => [...prev, clonedClip]);
1250              setActiveDragClipId(cloneId);
1251              setActiveDragAction("move");
1252              setDragStartTick(snappedTick);
1253              setDragStartTrackIdx(trackIdx);
1254              setDragStartOffsetTicks(snappedTick - existingClip.startTick);
1255              setDragStartClipState(clonedClip);
1256              setDragStartMouseX(e.clientX);
1257              setSelectedClipIds([cloneId]);
1258            } else {
1259              setActiveDragClipId(existingClip.id);
1260              setActiveDragAction("move");
1261              setDragStartTick(snappedTick);
1262              setDragStartTrackIdx(trackIdx);
1263              setDragStartOffsetTicks(snappedTick - existingClip.startTick);
1264              setDragStartClipState(existingClip);
1265              setDragStartMouseX(e.clientX);
1266            }
1267          }
1268        }
1269      }
1270    } else if (currentPlaylistTool === "delete") {
1271      const existingClip = playlistClips.find(
1272        (c) => c.trackIndex === trackIdx && snappedTick >= c.startTick && snappedTick < c.startTick + c.durationTicks
1273      );
1274      if (existingClip) {
1275        setPlaylistClips((prev) => prev.filter((c) => c.id !== existingClip.id));
1276        setTimeout(() => forceSync(), 0);
1277      }
1278    } else if (currentPlaylistTool === "mute") {
1279      const existingClip = playlistClips.find(
1280        (c) => c.trackIndex === trackIdx && snappedTick >= c.startTick && snappedTick < c.startTick + c.durationTicks
1281      );
1282      if (existingClip) {
1283        setPlaylistClips((prev) => prev.map(
1284          (c) => c.id === existingClip.id ? { ...c, isMuted: !c.isMuted } : c
1285        ));
1286        setTimeout(() => forceSync(), 0);
1287      }
1288    } else if (currentPlaylistTool === "slice") {
1289      if (easyMode) return;
1290      setSliceStart({ x: e.clientX, y: e.clientY });
1291      setSliceCurrent({ x: e.clientX, y: e.clientY });
1292      setActiveDragAction("slice");
1293      setDragStartTrackIdx(trackIdx);
1294    }
1295  };
1296  const handleGridPointerMove = (e) => {
1297    if (!gridRef.current || !activeDragAction) return;
1298    if (panStartRef.current) {
1299      const dx = e.clientX - panStartRef.current.x;
1300      const dy = e.clientY - panStartRef.current.y;
1301      if (gridHorizontalScrollRef.current) {
1302        gridHorizontalScrollRef.current.scrollLeft = panStartRef.current.scrollLeft - dx;
1303      }
1304      if (verticalScrollContainerRef.current) {
1305        verticalScrollContainerRef.current.scrollTop = panStartRef.current.scrollTop - dy;
1306      }
1307      return;
1308    }
1309    const rect = gridRef.current.getBoundingClientRect();
1310    const x = e.clientX - rect.left;
1311    const y = e.clientY - rect.top;
1312    const rawTick2 = pixelsToTicks(x);
1313    const snappedTick = getSnappedTick(rawTick2);
1314    let trackIdx = -1;
1315    let cumulativeHeight = 0;
1316    for (let i = 0; i < playlistTracks.length; i++) {
1317      const h = getTrackHeight(i);
1318      if (h === 0) continue;
1319      if (y >= cumulativeHeight && y < cumulativeHeight + h) {
1320        trackIdx = i;
1321        break;
1322      }
1323      cumulativeHeight += h;
1324    }
1325    if (activeDragAction === "right-delete" && trackIdx !== -1) {
1326      const existingClip = playlistClips.find(
1327        (c) => c.trackIndex === trackIdx && snappedTick >= c.startTick && snappedTick < c.startTick + c.durationTicks
1328      );
1329      if (existingClip) {
1330        setPlaylistClips((prev) => prev.filter((c) => c.id !== existingClip.id));
1331      }
1332      return;
1333    }
1334    if (activeDragAction === "paint" && activePatternId && trackIdx !== -1) {
1335      const pat = patterns.find((p) => p.id === activePatternId);
1336      if (pat) {
1337        const patLen = getPatternLength(pat);
1338        if (snappedTick !== lastPaintedTickRef.current || trackIdx !== lastPaintedTrackIdxRef.current) {
1339          const existingClip = playlistClips.find(
1340            (c) => c.trackIndex === trackIdx && snappedTick >= c.startTick && snappedTick < c.startTick + c.durationTicks
1341          );
1342          if (!existingClip) {
1343            const newClip = {
1344              id: `clip-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1345              patternId: activePatternId,
1346              trackIndex: trackIdx,
1347              startTick: snappedTick,
1348              durationTicks: patLen,
1349              offsetTicks: 0,
1350              isMuted: false
1351            };
1352            setPlaylistClips((prev) => [...prev, newClip]);
1353            lastPaintedTickRef.current = snappedTick;
1354            lastPaintedTrackIdxRef.current = trackIdx;
1355          }
1356        }
1357      }
1358      return;
1359    }
1360    if (activeDragAction === "move" && activeDragClipId && dragStartClipState) {
1361      const deltaTicks = snappedTick - dragStartTick;
1362      const newStartTick = Math.max(0, dragStartClipState.startTick + deltaTicks);
1363      const targetTrackIndex = trackIdx !== -1 ? trackIdx : dragStartClipState.trackIndex;
1364      setPlaylistClips((prev) => prev.map((c) => {
1365        if (c.id === activeDragClipId) {
1366          return {
1367            ...c,
1368            startTick: newStartTick,
1369            trackIndex: targetTrackIndex
1370          };
1371        }
1372        return c;
1373      }));
1374    } else if (activeDragAction === "trim-right" && activeDragClipId && dragStartClipState) {
1375      const newEndTick = Math.max(dragStartClipState.startTick + ppq / 4, snappedTick);
1376      const durationTicks = newEndTick - dragStartClipState.startTick;
1377      setPlaylistClips((prev) => prev.map((c) => {
1378        if (c.id === activeDragClipId) {
1379          return {
1380            ...c,
1381            durationTicks,
1382            isManualLength: true
1383          };
1384        }
1385        return c;
1386      }));
1387    } else if (activeDragAction === "trim-left" && activeDragClipId && dragStartClipState) {
1388      const origEnd = dragStartClipState.startTick + (dragStartClipState.isManualLength ? dragStartClipState.durationTicks : getPatternLength(patterns.find((p) => p.id === dragStartClipState.patternId)));
1389      const newStart = Math.min(origEnd - ppq / 4, snappedTick);
1390      const durationTicks = origEnd - newStart;
1391      const deltaTicks = newStart - dragStartClipState.startTick;
1392      const offsetTicks = Math.max(0, dragStartClipState.offsetTicks + deltaTicks);
1393      setPlaylistClips((prev) => prev.map((c) => {
1394        if (c.id === activeDragClipId) {
1395          return {
1396            ...c,
1397            startTick: newStart,
1398            durationTicks,
1399            offsetTicks,
1400            isManualLength: true
1401          };
1402        }
1403        return c;
1404      }));
1405    } else if (activeDragAction === "slip" && activeDragClipId && dragStartClipState) {
1406      if (easyMode) return;
1407      const deltaPx = e.clientX - dragStartMouseX;
1408      const deltaTicks = Math.round(pixelsToTicks(deltaPx));
1409      const offsetTicks = Math.max(0, dragStartClipState.offsetTicks - deltaTicks);
1410      setPlaylistClips((prev) => prev.map((c) => {
1411        if (c.id === activeDragClipId) {
1412          return {
1413            ...c,
1414            offsetTicks
1415          };
1416        }
1417        return c;
1418      }));
1419    } else if (activeDragAction === "slice") {
1420      setSliceCurrent({ x: e.clientX, y: e.clientY });
1421    } else if (activeDragAction === "select" && lassoStart) {
1422      setLassoCurrent({ x, y });
1423    } else if (activeDragAction === "node-drag" && dragNodeClipId && dragNodeId && dragNodeStartProps) {
1424      const clip = automationClips.find((c) => c.id === dragNodeClipId);
1425      if (!clip) return;
1426      const deltaTicks = Math.round(rawTick2 - dragStartTick);
1427      y - (getTrackTop(clip.trackIndex) + getTrackHeight(clip.trackIndex) / 2);
1428      const h = getTrackHeight(clip.trackIndex);
1429      const rawVal = 1 - (y - getTrackTop(clip.trackIndex)) / h;
1430      const value = Math.max(0, Math.min(1, rawVal));
1431      const newTick = Math.max(clip.startTick, Math.min(clip.startTick + clip.durationTicks, getSnappedTick(dragNodeStartProps.tick + deltaTicks)));
1432      setAutomationClips((prev) => prev.map((c) => {
1433        if (c.id === dragNodeClipId) {
1434          return {
1435            ...c,
1436            nodes: c.nodes.map((n) => n.id === dragNodeId ? { ...n, tick: newTick, value } : n)
1437          };
1438        }
1439        return c;
1440      }));
1441    } else if (activeDragAction === "tension-drag" && dragTensionClipId && dragTensionNodeIdx !== null) {
1442      const clip = automationClips.find((c) => c.id === dragTensionClipId);
1443      if (!clip) return;
1444      const node = clip.nodes[dragTensionNodeIdx];
1445      if (!node) return;
1446      const trackTop = getTrackTop(clip.trackIndex);
1447      const h = getTrackHeight(clip.trackIndex);
1448      const yOffset = y - trackTop;
1449      const relativeY = 1 - yOffset / h;
1450      const tension = Math.max(-0.99, Math.min(0.99, (relativeY - 0.5) * 2));
1451      setAutomationClips((prev) => prev.map((c) => {
1452        if (c.id === dragTensionClipId) {
1453          const nodes = [...c.nodes];
1454          nodes[dragTensionNodeIdx] = {
1455            ...nodes[dragTensionNodeIdx],
1456            tension
1457          };
1458          return { ...c, nodes };
1459        }
1460        return c;
1461      }));
1462    }
1463  };
1464  const handleGridPointerUp = (e) => {
1465    if (panStartRef.current) {
1466      e.currentTarget.releasePointerCapture(e.pointerId);
1467      panStartRef.current = null;
1468      setIsPanning(false);
1469      document.body.style.cursor = "";
1470    }
1471    if (activeDragAction === "slice" && sliceStart && sliceCurrent) {
1472      const rawCutX = sliceCurrent.x - gridRef.current.getBoundingClientRect().left;
1473      const cutTick = getSnappedTick(pixelsToTicks(rawCutX));
1474      const trackIdx = dragStartTrackIdx;
1475      setPlaylistClips((prevClips) => {
1476        const newClips = [];
1477        let modified = false;
1478        prevClips.forEach((c) => {
1479          if (c.trackIndex === trackIdx && cutTick > c.startTick && cutTick < c.startTick + c.durationTicks) {
1480            modified = true;
1481            const leftDuration = cutTick - c.startTick;
1482            const rightDuration = c.durationTicks - leftDuration;
1483            newClips.push({
1484              ...c,
1485              durationTicks: leftDuration
1486            });
1487            newClips.push({
1488              ...c,
1489              id: `clip-slice-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1490              startTick: cutTick,
1491              durationTicks: rightDuration,
1492              offsetTicks: c.offsetTicks + leftDuration
1493            });
1494          } else {
1495            newClips.push(c);
1496          }
1497        });
1498        if (modified) {
1499          setTimeout(() => forceSync(), 0);
1500        }
1501        return newClips;
1502      });
1503    } else if (activeDragAction === "select" && lassoStart && lassoCurrent) {
1504      const x1 = Math.min(lassoStart.x, lassoCurrent.x);
1505      const x2 = Math.max(lassoStart.x, lassoCurrent.x);
1506      const y1 = Math.min(lassoStart.y, lassoCurrent.y);
1507      const y2 = Math.max(lassoStart.y, lassoCurrent.y);
1508      const selectedIds = [];
1509      playlistClips.forEach((clip) => {
1510        const clipLeft = ticksToPixels(clip.startTick);
1511        const clipRight = clipLeft + ticksToPixels(clip.durationTicks);
1512        const clipTop = getTrackTop(clip.trackIndex);
1513        const clipBottom = clipTop + getTrackHeight(clip.trackIndex);
1514        if (clipRight >= x1 && clipLeft <= x2 && clipBottom >= y1 && clipTop <= y2) {
1515          selectedIds.push(clip.id);
1516        }
1517      });
1518      setSelectedClipIds(selectedIds);
1519    }
1520    setActiveDragAction(null);
1521    setActiveDragClipId(null);
1522    setDragStartClipState(null);
1523    setLassoStart(null);
1524    setLassoCurrent(null);
1525    setSliceStart(null);
1526    setSliceCurrent(null);
1527    setDragNodeId(null);
1528    setDragNodeClipId(null);
1529    setDragNodeStartProps(null);
1530    setDragTensionClipId(null);
1531    setDragTensionNodeIdx(null);
1532    lastPaintedTickRef.current = null;
1533    lastPaintedTrackIdxRef.current = null;
1534    setTimeout(() => forceSync(), 0);
1535  };
1536  const handleRulerPointerDown = (e) => {
1537    e.preventDefault();
1538    const rect = e.currentTarget.getBoundingClientRect();
1539    const x = e.clientX - rect.left;
1540    const tick = getSnappedTick(pixelsToTicks(x));
1541    rulerStartTickRef.current = tick;
1542    hasRulerDraggedRef.current = false;
1543    if (e.button === 0) {
1544      rulerDragActionRef.current = "seek";
1545      const clickedTime = engine.ticksToSeconds(tick);
1546      engine.seek(clickedTime);
1547      setTimeout(() => forceSync(), 0);
1548    } else if (e.button === 2) {
1549      rulerDragActionRef.current = "loop";
1550      setLoopRegion({ startTick: tick, endTick: tick });
1551    }
1552    e.currentTarget.setPointerCapture(e.pointerId);
1553  };
1554  const handleRulerPointerMove = (e) => {
1555    if (!rulerDragActionRef.current) return;
1556    const rect = e.currentTarget.getBoundingClientRect();
1557    const x = e.clientX - rect.left;
1558    const tick = getSnappedTick(pixelsToTicks(x));
1559    hasRulerDraggedRef.current = true;
1560    if (rulerDragActionRef.current === "seek") {
1561      const clickedTime = engine.ticksToSeconds(tick);
1562      engine.seek(clickedTime);
1563    } else if (rulerDragActionRef.current === "loop") {
1564      const start = rulerStartTickRef.current;
1565      const end = tick;
1566      setLoopRegion({
1567        startTick: Math.min(start, end),
1568        endTick: Math.max(start, end)
1569      });
1570    }
1571    setTimeout(() => forceSync(), 0);
1572  };
1573  const handleRulerPointerUp = (e) => {
1574    if (!rulerDragActionRef.current) return;
1575    e.currentTarget.releasePointerCapture(e.pointerId);
1576    if (rulerDragActionRef.current === "loop" && !hasRulerDraggedRef.current) {
1577      setLoopRegion(null);
1578      if (!easyMode) {
1579        const rect = e.currentTarget.getBoundingClientRect();
1580        const x = e.clientX - rect.left;
1581        const tick = getSnappedTick(pixelsToTicks(x));
1582        const label = window.prompt("Enter Marker Name:", "Chorus");
1583        if (label) {
1584          setTimeMarkers((prev) => [...prev, { id: `marker-${Date.now()}`, tick, label }]);
1585        }
1586      }
1587    }
1588    rulerDragActionRef.current = null;
1589    setTimeout(() => forceSync(), 0);
1590  };
1591  const handleSliceClipAtTick = (clipId, cutTick) => {
1592    setPlaylistClips((prevClips) => {
1593      const newClips = [];
1594      let modified = false;
1595      prevClips.forEach((c) => {
1596        if (c.id === clipId && cutTick > c.startTick && cutTick < c.startTick + c.durationTicks) {
1597          modified = true;
1598          const leftDuration = cutTick - c.startTick;
1599          const rightDuration = c.durationTicks - leftDuration;
1600          newClips.push({
1601            ...c,
1602            durationTicks: leftDuration
1603          });
1604          newClips.push({
1605            ...c,
1606            id: `clip-slice-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
1607            startTick: cutTick,
1608            durationTicks: rightDuration,
1609            offsetTicks: c.offsetTicks + leftDuration
1610          });
1611        } else {
1612          newClips.push(c);
1613        }
1614      });
1615      if (modified) {
1616        setTimeout(() => forceSync(), 0);
1617      }
1618      return newClips;
1619    });
1620  };
1621  const handleCreateAutomationClip = (trackIdx, ccNum) => {
1622    if (easyMode) return;
1623    const newClip = {
1624      id: `auto-${Date.now()}`,
1625      name: "Volume Automation",
1626      trackIndex: trackIdx,
1627      startTick: 0,
1628      durationTicks: ppq * 4 * 16,
1629      // 16 bars default
1630      ccNumber: ccNum,
1631      nodes: [
1632        { id: `n-0`, tick: 0, value: 0.8, tension: 0 },
1633        { id: `n-1`, tick: ppq * 4 * 16, value: 0.8, tension: 0 }
1634      ]
1635    };
1636    setAutomationClips((prev) => [...prev, newClip]);
1637    setTimeout(() => forceSync(), 0);
1638  };
1639  const handleSelectColor = (id, color, type) => {
1640    if (type === "pattern") {
1641      setPatterns((prev) => prev.map((p) => p.id === id ? { ...p, color } : p));
1642    } else {
1643      setPlaylistTracks((prev) => prev.map((t) => t.id === id ? { ...t, color } : t));
1644    }
1645    setActiveColorPickerId(null);
1646    setTimeout(() => forceSync(), 0);
1647  };
1648  const handleTrackHeaderResize = (trackIdx, e) => {
1649    e.stopPropagation();
1650    e.preventDefault();
1651    const startY = e.clientY;
1652    const origHeight = playlistTracks[trackIdx].height || 64;
1653    const handleMouseMove = (moveEvent) => {
1654      const deltaY = moveEvent.clientY - startY;
1655      const newHeight = Math.max(36, Math.min(200, origHeight + deltaY));
1656      setPlaylistTracks((prev) => prev.map(
1657        (t, idx) => idx === trackIdx ? { ...t, height: newHeight } : t
1658      ));
1659    };
1660    const handleMouseUp = () => {
1661      window.removeEventListener("mousemove", handleMouseMove);
1662      window.removeEventListener("mouseup", handleMouseUp);
1663    };
1664    window.addEventListener("mousemove", handleMouseMove);
1665    window.addEventListener("mouseup", handleMouseUp);
1666  };
1667  const handlePatternResizeMouseDown = (e) => {
1668    e.stopPropagation();
1669    e.preventDefault();
1670    const startX = e.clientX;
1671    const startWidth = patternColWidth;
1672    const handleMouseMove = (moveEvent) => {
1673      const deltaX = moveEvent.clientX - startX;
1674      const newWidth = Math.max(100, Math.min(500, startWidth + deltaX));
1675      setPatternColWidth(newWidth);
1676    };
1677    const handleMouseUp = () => {
1678      window.removeEventListener("mousemove", handleMouseMove);
1679      window.removeEventListener("mouseup", handleMouseUp);
1680    };
1681    window.addEventListener("mousemove", handleMouseMove);
1682    window.addEventListener("mouseup", handleMouseUp);
1683  };
1684  const handleTrackColResizeMouseDown = (e) => {
1685    e.stopPropagation();
1686    e.preventDefault();
1687    const startX = e.clientX;
1688    const startWidth = trackColWidth;
1689    const handleMouseMove = (moveEvent) => {
1690      const deltaX = moveEvent.clientX - startX;
1691      const newWidth = Math.max(100, Math.min(500, startWidth + deltaX));
1692      setTrackColWidth(newWidth);
1693    };
1694    const handleMouseUp = () => {
1695      window.removeEventListener("mousemove", handleMouseMove);
1696      window.removeEventListener("mouseup", handleMouseUp);
1697    };
1698    window.addEventListener("mousemove", handleMouseMove);
1699    window.addEventListener("mouseup", handleMouseUp);
1700  };
1701  const handleClipDoubleClick = (patternId) => {
1702    setActivePatternId(patternId);
1703  };
1704  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden bg-zinc-50 dark:bg-zinc-950 text-zinc-700 dark:text-zinc-300 select-none font-sans", children: [
1705    !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1706      "div",
1707      {
1708        style: { width: patternColWidth },
1709        className: "border-r border-zinc-200 dark:border-zinc-800 bg-zinc-100 dark:bg-zinc-900/60 flex flex-col shrink-0 relative",
1710        children: [
1711          /* @__PURE__ */ jsxRuntimeExports.jsx(
1712            "div",
1713            {
1714              onMouseDown: handlePatternResizeMouseDown,
1715              className: "absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors z-30"
1716            }
1717          ),
1718          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-10 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between px-3 bg-zinc-100 dark:bg-zinc-950 shrink-0", children: [
1719            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold text-zinc-500 uppercase tracking-widest flex items-center gap-1.5", children: [
1720              /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 12, className: "text-indigo-400" }),
1721              " Patterns"
1722            ] }),
1723            /* @__PURE__ */ jsxRuntimeExports.jsx(
1724              "button",
1725              {
1726                onClick: handleAddPattern,
1727                className: "p-1 hover:bg-zinc-800 text-indigo-400 hover:text-indigo-300 rounded transition-colors",
1728                title: "Add Pattern",
1729                children: /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 14 })
1730              }
1731            )
1732          ] }),
1733          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-2 flex flex-col gap-1 custom-scrollbar", children: patterns.map((pat) => {
1734            const isActive = pat.id === activePatternId;
1735            return /* @__PURE__ */ jsxRuntimeExports.jsx(
1736              "div",
1737              {
1738                onClick: () => setActivePatternId(pat.id),
1739                onDoubleClick: () => {
1740                  setRenamingPatternId(pat.id);
1741                  setRenamingPatternText(pat.name);
1742                },
1743                className: `group flex items-center gap-2 p-2 rounded cursor-pointer transition-all border ${isActive ? "bg-indigo-500/15 border-indigo-500/50 text-indigo-600 dark:text-white shadow-sm" : "bg-white dark:bg-zinc-800/20 border-zinc-200 dark:border-transparent hover:bg-zinc-50 dark:hover:bg-zinc-800/40 text-zinc-600 dark:text-zinc-400"}`,
1744                style: { borderLeftColor: pat.color, borderLeftWidth: "4px" },
1745                children: renamingPatternId === pat.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1746                  "input",
1747                  {
1748                    type: "text",
1749                    value: renamingPatternText,
1750                    onChange: (e) => setRenamingPatternText(e.target.value),
1751                    onBlur: () => {
1752                      setPatterns((prev) => prev.map((p) => p.id === pat.id ? { ...p, name: renamingPatternText || p.name } : p));
1753                      setRenamingPatternId(null);
1754                    },
1755                    onKeyDown: (e) => {
1756                      if (e.key === "Enter") {
1757                        setPatterns((prev) => prev.map((p) => p.id === pat.id ? { ...p, name: renamingPatternText || p.name } : p));
1758                        setRenamingPatternId(null);
1759                      }
1760                    },
1761                    autoFocus: true,
1762                    className: "bg-white dark:bg-zinc-850 border border-zinc-200 dark:border-zinc-700 rounded text-xs px-1 text-zinc-900 dark:text-white w-full outline-none"
1763                  }
1764                ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1765                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold truncate flex-1", children: pat.name }),
1766                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity", children: [
1767                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
1768                      /* @__PURE__ */ jsxRuntimeExports.jsx(
1769                        "button",
1770                        {
1771                          onClick: (e) => {
1772                            e.stopPropagation();
1773                            setActiveColorPickerId(activeColorPickerId === pat.id ? null : pat.id);
1774                          },
1775                          className: "w-3.5 h-3.5 rounded border border-white/20",
1776                          style: { backgroundColor: pat.color }
1777                        }
1778                      ),
1779                      activeColorPickerId === pat.id && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 mt-1 bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 p-1.5 rounded shadow-xl flex gap-1 z-50", children: ["#6366f1", "#e11d48", "#10b981", "#f59e0b", "#06b6d4", "#ec4899"].map((c) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1780                        "button",
1781                        {
1782                          onClick: () => handleSelectColor(pat.id, c, "pattern"),
1783                          className: "w-4 h-4 rounded border border-white/10",
1784                          style: { backgroundColor: c }
1785                        },
1786                        c
1787                      )) })
1788                    ] }),
1789                    /* @__PURE__ */ jsxRuntimeExports.jsx(
1790                      "button",
1791                      {
1792                        onClick: (e) => handleDeletePattern(pat.id, e),
1793                        className: "p-0.5 hover:text-red-500 rounded",
1794                        title: "Delete Pattern",
1795                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 })
1796                      }
1797                    )
1798                  ] })
1799                ] })
1800              },
1801              pat.id
1802            );
1803          }) })
1804        ]
1805      }
1806    ),
1807    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
1808      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-10 border-b border-zinc-200 dark:border-zinc-800 bg-zinc-100 dark:bg-zinc-900 flex justify-between items-center px-4 shrink-0", children: [
1809        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1810          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex bg-white dark:bg-zinc-950 p-0.5 rounded-lg border border-zinc-200 dark:border-zinc-800", children: [
1811            { id: "draw", icon: Pencil, title: "Draw Tool (Left-click grid cell)" },
1812            { id: "paint", icon: Brush, title: "Paint Tool (Draw contiguous clips)" },
1813            { id: "delete", icon: Eraser, title: "Delete Tool (Eraser)" },
1814            { id: "mute", icon: VolumeX, title: "Mute Tool (Click clips to toggle mute)" },
1815            { id: "slice", icon: Scissors, title: "Slice Knife (Drag line to cut clips)", lockPro: true },
1816            { id: "slip", icon: Split, title: "Slip Tool (Shift notes inside clip borders)", lockPro: true },
1817            { id: "select", icon: MousePointer2, title: "Select Box (Marquee)" }
1818          ].map((tool) => {
1819            const isLocked = easyMode && tool.lockPro;
1820            if (isLocked) return null;
1821            const isActive = currentPlaylistTool === tool.id;
1822            return /* @__PURE__ */ jsxRuntimeExports.jsx(
1823              "button",
1824              {
1825                onClick: () => setCurrentPlaylistTool(tool.id),
1826                className: `p-1.5 rounded transition-all ${isActive ? "bg-indigo-600 text-white shadow" : "text-zinc-500 hover:text-zinc-300"}`,
1827                title: tool.title,
1828                children: /* @__PURE__ */ jsxRuntimeExports.jsx(tool.icon, { size: 14 })
1829              },
1830              tool.id
1831            );
1832          }) }),
1833          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-800 mx-1" }),
1834          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-1 rounded-lg text-xs font-semibold", children: [
1835            /* @__PURE__ */ jsxRuntimeExports.jsx(Magnet, { size: 12, className: "text-zinc-500" }),
1836            /* @__PURE__ */ jsxRuntimeExports.jsxs(
1837              "select",
1838              {
1839                value: gridSnap,
1840                onChange: (e) => setGridSnap(e.target.value),
1841                className: "bg-transparent text-zinc-700 dark:text-zinc-300 outline-none cursor-pointer pr-1",
1842                children: [
1843                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "bar", children: "Snap to Bar" }),
1844                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "beat", children: "Snap to Beat" }),
1845                  !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1846                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "1/2", children: "Snap to 1/2 Beat" }),
1847                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "1/4", children: "Snap to 1/4 Beat" }),
1848                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "1/6", children: "Snap to 1/6 Beat" }),
1849                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "cell", children: "Snap to Cell" }),
1850                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None (Seamless)" })
1851                  ] })
1852                ]
1853              }
1854            )
1855          ] }),
1856          !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1857            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-800 mx-1" }),
1858            /* @__PURE__ */ jsxRuntimeExports.jsxs(
1859              "button",
1860              {
1861                onClick: () => setIsAutomationVisible(!isAutomationVisible),
1862                className: `flex items-center gap-1.5 px-2.5 py-1 rounded-lg border text-xs font-semibold transition-all ${isAutomationVisible ? "bg-white dark:bg-zinc-950 border-indigo-500/30 text-indigo-500 dark:text-indigo-400 shadow-sm" : "border-zinc-200 dark:border-zinc-800 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300"}`,
1863                children: [
1864                  /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 12 }),
1865                  "Automation"
1866                ]
1867              }
1868            )
1869          ] })
1870        ] }),
1871        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1872          /* @__PURE__ */ jsxRuntimeExports.jsx(
1873            "button",
1874            {
1875              onClick: () => setZoomX((z) => Math.max(0.2, z - 0.1)),
1876              className: "p-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 rounded transition-colors border border-zinc-200 dark:border-zinc-800/40 bg-white dark:bg-zinc-950/20",
1877              title: "Zoom X Out",
1878              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { size: 13 })
1879            }
1880          ),
1881          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-600 px-1 uppercase tracking-wider", children: "Zoom" }),
1882          /* @__PURE__ */ jsxRuntimeExports.jsx(
1883            "button",
1884            {
1885              onClick: () => setZoomX((z) => Math.min(3, z + 0.1)),
1886              className: "p-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 rounded transition-colors border border-zinc-200 dark:border-zinc-800/40 bg-white dark:bg-zinc-950/20",
1887              title: "Zoom X In",
1888              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 13 })
1889            }
1890          ),
1891          /* @__PURE__ */ jsxRuntimeExports.jsx(
1892            "button",
1893            {
1894              onClick: () => setIsHelpOpen(true),
1895              className: "p-1.5 ml-1 bg-indigo-50/50 hover:bg-indigo-100 dark:bg-indigo-950/30 dark:hover:bg-indigo-900/40 text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300 rounded-full transition-all border border-indigo-200 dark:border-indigo-800/50 shadow-sm",
1896              title: "Arrangement Help & Shortcuts",
1897              children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: 13 })
1898            }
1899          )
1900        ] })
1901      ] }),
1902      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `h-8 flex border-b border-zinc-200 dark:border-zinc-800 bg-zinc-150 dark:bg-zinc-900 shrink-0 relative ${isFloating ? "z-[50]" : "z-[5]"}`, children: [
1903        /* @__PURE__ */ jsxRuntimeExports.jsxs(
1904          "div",
1905          {
1906            style: { width: trackColWidth },
1907            className: "border-r border-zinc-200 dark:border-zinc-800 flex items-center px-4 font-bold text-[9px] text-zinc-500 dark:text-zinc-400 uppercase tracking-widest shrink-0 relative bg-zinc-100 dark:bg-zinc-950 select-none",
1908            children: [
1909              "Timeline Map",
1910              /* @__PURE__ */ jsxRuntimeExports.jsx(
1911                "div",
1912                {
1913                  onMouseDown: handleTrackColResizeMouseDown,
1914                  className: "absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors z-30"
1915                }
1916              )
1917            ]
1918          }
1919        ),
1920        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative h-full flex overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1921          FLHorizontalScrollbar,
1922          {
1923            containerRef: gridHorizontalScrollRef,
1924            zoomX,
1925            setZoomX,
1926            maxTicks,
1927            ppq,
1928            beatWidth: BEAT_WIDTH$1,
1929            mode: "timeline",
1930            patterns,
1931            playlistClips,
1932            activePatternId,
1933            playlistTracks
1934          }
1935        ) })
1936      ] }),
1937      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-10 flex border-b border-zinc-200 dark:border-zinc-800 bg-zinc-100 dark:bg-zinc-900 shrink-0", children: [
1938        /* @__PURE__ */ jsxRuntimeExports.jsxs(
1939          "div",
1940          {
1941            style: { width: trackColWidth },
1942            className: "border-r border-zinc-200 dark:border-zinc-800 flex items-center px-4 font-bold text-[10px] text-zinc-500 dark:text-zinc-400 uppercase tracking-widest shrink-0 relative",
1943            children: [
1944              "Track Lanes",
1945              /* @__PURE__ */ jsxRuntimeExports.jsx(
1946                "div",
1947                {
1948                  onMouseDown: handleTrackColResizeMouseDown,
1949                  className: "absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors z-30"
1950                }
1951              )
1952            ]
1953          }
1954        ),
1955        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: rulerScrollContainerRef, className: "flex-1 overflow-hidden pointer-events-none relative", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1956          "div",
1957          {
1958            className: "h-full relative bg-zinc-50 dark:bg-zinc-950 pointer-events-auto cursor-col-resize",
1959            style: { width: totalWidth },
1960            onPointerDown: handleRulerPointerDown,
1961            onPointerMove: handleRulerPointerMove,
1962            onPointerUp: handleRulerPointerUp,
1963            onContextMenu: (e) => e.preventDefault(),
1964            children: [
1965              Array.from({ length: Math.ceil(maxTicks / (ppq * 4)) }).map((_, barIdx) => {
1966                const left = barIdx * ppq * 4 * pxPerTick;
1967                return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1968                  "div",
1969                  {
1970                    className: "absolute h-full border-l border-zinc-200 dark:border-zinc-800 text-[9px] font-bold text-zinc-500 pl-1 pt-0.5 flex flex-col justify-between",
1971                    style: { left },
1972                    children: [
1973                      /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1974                        "Bar ",
1975                        barIdx + 1
1976                      ] }),
1977                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-[3px] text-[7px] font-semibold text-zinc-700 leading-none pb-0.5", children: [
1978                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "." }),
1979                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "." }),
1980                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "." })
1981                      ] })
1982                    ]
1983                  },
1984                  barIdx
1985                );
1986              }),
1987              loopRegion && /* @__PURE__ */ jsxRuntimeExports.jsx(
1988                "div",
1989                {
1990                  className: "absolute top-0 h-full bg-red-500/10 border-l border-r border-red-500/40 pointer-events-none",
1991                  style: {
1992                    left: ticksToPixels(loopRegion.startTick),
1993                    width: ticksToPixels(loopRegion.endTick - loopRegion.startTick)
1994                  }
1995                }
1996              ),
1997              timeMarkers.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1998                "div",
1999                {
2000                  className: "absolute top-1 bg-teal-500/20 text-teal-300 text-[8px] font-bold px-1 py-0.5 rounded border border-teal-500/40 z-30",
2001                  style: { left: ticksToPixels(m.tick), transform: "translateX(-50%)" },
2002                  title: "Right-click to delete marker",
2003                  onContextMenu: (e) => {
2004                    e.preventDefault();
2005                    e.stopPropagation();
2006                    setTimeMarkers((prev) => prev.filter((x) => x.id !== m.id));
2007                  },
2008                  children: [
2009                    "🚩 ",
2010                    m.label
2011                  ]
2012                },
2013                m.id
2014              ))
2015            ]
2016          }
2017        ) })
2018      ] }),
2019      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: verticalScrollContainerRef, className: "flex-1 overflow-y-auto bg-white dark:bg-zinc-950 flex flex-col relative custom-scrollbar", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-full", children: [
2020        /* @__PURE__ */ jsxRuntimeExports.jsxs(
2021          "div",
2022          {
2023            style: { width: trackColWidth },
2024            className: "border-r border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/40 shrink-0 flex flex-col relative overflow-hidden",
2025            children: [
2026              /* @__PURE__ */ jsxRuntimeExports.jsx(
2027                "div",
2028                {
2029                  onMouseDown: handleTrackColResizeMouseDown,
2030                  className: "absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors z-30"
2031                }
2032              ),
2033              playlistTracks.map((pt, idx) => {
2034                const h = getTrackHeight(idx);
2035                if (h === 0) return null;
2036                const isCollapsed = collapsedGroups[pt.id];
2037                const hasChildren = playlistTracks.some((t) => t.parentId === pt.id);
2038                return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2039                  "div",
2040                  {
2041                    style: { height: h },
2042                    onClick: () => setActiveTrackIndex(idx),
2043                    className: `border-b border-zinc-200 dark:border-zinc-800/80 px-2 py-1.5 flex flex-col justify-between hover:bg-zinc-200/40 dark:hover:bg-zinc-800/25 relative group overflow-hidden cursor-pointer ${activeTrackIndex === idx ? "bg-indigo-500/10 dark:bg-indigo-500/15 border-l-[3px] border-l-indigo-500" : "bg-zinc-100/20 dark:bg-zinc-900/20 border-l-[3px] border-l-transparent"} ${pt.parentId ? "pl-6" : ""}`,
2044                    children: [
2045                      pt.parentId && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-2.5 top-0 bottom-1/2 w-2.5 border-l border-b border-zinc-700 rounded-bl-sm" }),
2046                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 z-10", children: [
2047                        hasChildren && /* @__PURE__ */ jsxRuntimeExports.jsx(
2048                          "button",
2049                          {
2050                            onClick: (e) => {
2051                              e.stopPropagation();
2052                              setCollapsedGroups((prev) => ({ ...prev, [pt.id]: !prev[pt.id] }));
2053                            },
2054                            className: "p-0.5 text-zinc-500 hover:text-zinc-300 rounded",
2055                            children: isCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 12 })
2056                          }
2057                        ),
2058                        renamingTrackId === pt.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2059                          "input",
2060                          {
2061                            type: "text",
2062                            value: renamingTrackText,
2063                            onChange: (e) => setRenamingTrackText(e.target.value),
2064                            onBlur: () => {
2065                              setPlaylistTracks((prev) => prev.map((t) => t.id === pt.id ? { ...t, name: renamingTrackText || t.name } : t));
2066                              setRenamingTrackId(null);
2067                            },
2068                            onKeyDown: (e) => {
2069                              if (e.key === "Enter") {
2070                                setPlaylistTracks((prev) => prev.map((t) => t.id === pt.id ? { ...t, name: renamingTrackText || t.name } : t));
2071                                setRenamingTrackId(null);
2072                              }
2073                            },
2074                            onFocus: (e) => e.currentTarget.select(),
2075                            onDoubleClick: (e) => e.currentTarget.select(),
2076                            autoFocus: true,
2077                            className: "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded text-xs px-1 text-zinc-900 dark:text-white w-full outline-none"
2078                          }
2079                        ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2080                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2081                            "span",
2082                            {
2083                              onDoubleClick: (e) => {
2084                                e.stopPropagation();
2085                                setRenamingTrackId(pt.id);
2086                                setRenamingTrackText(pt.name);
2087                              },
2088                              className: "text-xs font-semibold text-zinc-700 dark:text-zinc-200 truncate flex-1 cursor-default select-none",
2089                              children: pt.name
2090                            }
2091                          ),
2092                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2093                            "button",
2094                            {
2095                              onClick: (e) => {
2096                                e.stopPropagation();
2097                                setRenamingTrackId(pt.id);
2098                                setRenamingTrackText(pt.name);
2099                              },
2100                              className: "opacity-0 group-hover:opacity-100 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 p-0.5 rounded transition-opacity shrink-0",
2101                              title: "Rename Track",
2102                              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { size: 11 })
2103                            }
2104                          )
2105                        ] }),
2106                        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-500/80 shrink-0 font-medium", children: [
2107                          "#",
2108                          idx + 1
2109                        ] })
2110                      ] }),
2111                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2 z-10", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), children: [
2112                        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
2113                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2114                            "button",
2115                            {
2116                              onClick: () => toggleTrackMute(idx),
2117                              className: `w-5 h-5 rounded text-[10px] font-bold transition-colors ${pt.isMuted ? "bg-yellow-600 text-black shadow-inner" : "bg-zinc-200 dark:bg-zinc-800 text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-300"}`,
2118                              title: "Mute Track",
2119                              children: "M"
2120                            }
2121                          ),
2122                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2123                            "button",
2124                            {
2125                              onClick: () => toggleTrackSolo(idx),
2126                              className: `w-5 h-5 rounded text-[10px] font-bold transition-colors ${pt.isSoloed ? "bg-red-600 text-white shadow" : "bg-zinc-200 dark:bg-zinc-800 text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-300"}`,
2127                              title: "Solo Track",
2128                              children: "S"
2129                            }
2130                          ),
2131                          !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(
2132                            "button",
2133                            {
2134                              onClick: () => toggleTrackGroup(idx),
2135                              className: "p-1 text-zinc-500 hover:text-zinc-300 rounded hover:bg-zinc-800/40",
2136                              title: "Indent / Outdent Track",
2137                              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Split, { size: 10 })
2138                            }
2139                          )
2140                        ] }),
2141                        !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
2142                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", title: `Volume: ${Math.round(pt.volume * 100)}%`, children: [
2143                            /* @__PURE__ */ jsxRuntimeExports.jsx(
2144                              "input",
2145                              {
2146                                type: "range",
2147                                min: "0",
2148                                max: "1",
2149                                step: "0.05",
2150                                value: pt.volume,
2151                                onChange: (e) => {
2152                                  const val = parseFloat(e.target.value);
2153                                  setPlaylistTracks((prev) => prev.map((t, i) => i === idx ? { ...t, volume: val } : t));
2154                                  setTimeout(() => forceSync(), 0);
2155                                },
2156                                className: "w-10 h-1 accent-indigo-500 cursor-pointer"
2157                              }
2158                            ),
2159                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[7px] text-zinc-500 font-bold uppercase mt-0.5", children: "Vol" })
2160                          ] }),
2161                          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", title: `Panning: ${Math.round(pt.panning * 100)}`, children: [
2162                            /* @__PURE__ */ jsxRuntimeExports.jsx(
2163                              "input",
2164                              {
2165                                type: "range",
2166                                min: "-1",
2167                                max: "1",
2168                                step: "0.1",
2169                                value: pt.panning,
2170                                onChange: (e) => {
2171                                  const val = parseFloat(e.target.value);
2172                                  setPlaylistTracks((prev) => prev.map((t, i) => i === idx ? { ...t, panning: val } : t));
2173                                  setTimeout(() => forceSync(), 0);
2174                                },
2175                                className: "w-10 h-1 accent-indigo-500 cursor-pointer"
2176                              }
2177                            ),
2178                            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[7px] text-zinc-500 font-bold uppercase mt-0.5", children: "Pan" })
2179                          ] }),
2180                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2181                            "button",
2182                            {
2183                              onClick: () => handleCreateAutomationClip(idx, 7),
2184                              className: "p-1 hover:bg-zinc-850 hover:text-indigo-400 text-zinc-600 rounded transition-all",
2185                              title: "Add Volume Automation Envelope",
2186                              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 11 })
2187                            }
2188                          )
2189                        ] })
2190                      ] }),
2191                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity z-10 mt-1", children: [
2192                        /* @__P
2192URE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
2193                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2194                            "button",
2195                            {
2196                              onClick: () => setActiveColorPickerId(activeColorPickerId === pt.id ? null : pt.id),
2197                              className: "w-3.5 h-3.5 rounded border border-white/20",
2198                              style: { backgroundColor: pt.color }
2199                            }
2200                          ),
2201                          activeColorPickerId === pt.id && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 mt-1 bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 p-1 rounded flex gap-1 z-50", children: ["#6366f1", "#e11d48", "#10b981", "#f59e0b", "#06b6d4", "#ec4899"].map((c) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2202                            "button",
2203                            {
2204                              onClick: () => handleSelectColor(pt.id, c, "track"),
2205                              className: "w-4.5 h-4.5 rounded border border-white/10",
2206                              style: { backgroundColor: c }
2207                            },
2208                            c
2209                          )) })
2210                        ] }),
2211                        /* @__PURE__ */ jsxRuntimeExports.jsx(
2212                          "button",
2213                          {
2214                            onClick: () => handleDeleteTrack(idx),
2215                            className: "p-0.5 text-zinc-500 hover:text-red-500",
2216                            title: "Delete Track",
2217                            children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 11 })
2218                          }
2219                        )
2220                      ] }),
2221                      !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(
2222                        "div",
2223                        {
2224                          onMouseDown: (e) => handleTrackHeaderResize(idx, e),
2225                          className: "absolute bottom-0 left-0 w-full h-1 cursor-ns-resize hover:bg-indigo-500/50 transition-colors z-20"
2226                        }
2227                      )
2228                    ]
2229                  },
2230                  pt.id
2231                );
2232              }),
2233              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-12 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-center p-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2234                "button",
2235                {
2236                  onClick: handleAddPlaylistTrack,
2237                  className: "flex items-center gap-1 px-3 py-1.5 text-[10px] font-bold text-zinc-500 dark:text-zinc-400 hover:text-indigo-500 dark:hover:text-indigo-400 hover:bg-indigo-500/10 rounded border border-dashed border-zinc-300 dark:border-zinc-800 hover:border-indigo-500/30 transition-all w-full justify-center",
2238                  children: [
2239                    /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }),
2240                    " Add track lane"
2241                  ]
2242                }
2243              ) })
2244            ]
2245          }
2246        ),
2247        /* @__PURE__ */ jsxRuntimeExports.jsx(
2248          "div",
2249          {
2250            ref: gridHorizontalScrollRef,
2251            onScroll: handleHorizontalScroll,
2252            className: "flex-1 overflow-x-auto relative custom-scrollbar select-none",
2253            children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2254              "div",
2255              {
2256                ref: gridRef,
2257                onPointerDown: handleGridPointerDown,
2258                onPointerMove: handleGridPointerMove,
2259                onPointerUp: handleGridPointerUp,
2260                className: "h-full relative overflow-hidden bg-white dark:bg-zinc-950 bg-grid-pattern",
2261                style: {
2262                  width: totalWidth,
2263                  // CSS Grid beat/bar visual separator lines using linear gradients
2264                  backgroundImage: `
2265                                          linear-gradient(to right, rgba(128,128,128,0.12) 1px, transparent 1px),
2266                                          linear-gradient(to right, rgba(128,128,128,0.04) 1px, transparent 1px)
2267                                      `,
2268                  backgroundSize: `${ppq * 4 * pxPerTick}px 100%, ${ppq * pxPerTick}px 100%`,
2269                  minHeight: playlistTracks.reduce((sum, _, i) => sum + getTrackHeight(i), 0) + 100
2270                },
2271                onContextMenu: (e) => e.preventDefault(),
2272                children: [
2273                  playlistTracks.map((pt, idx) => {
2274                    const h = getTrackHeight(idx);
2275                    if (h === 0) return null;
2276                    const top = getTrackTop(idx);
2277                    return /* @__PURE__ */ jsxRuntimeExports.jsx(
2278                      "div",
2279                      {
2280                        className: "absolute left-0 w-full border-b border-zinc-200 dark:border-zinc-900 pointer-events-none",
2281                        style: { top: top + h - 1, height: 1 }
2282                      },
2283                      `sep-${pt.id}`
2284                    );
2285                  }),
2286                  playlistClips.map((clip) => {
2287                    const trackTop = getTrackTop(clip.trackIndex);
2288                    const trackHeight = getTrackHeight(clip.trackIndex);
2289                    if (trackHeight === 0) return null;
2290                    const pattern = patterns.find((p) => p.id === clip.patternId);
2291                    if (!pattern) return null;
2292                    const patternLength = getPatternLength(pattern);
2293                    const actualDurationTicks = clip.isManualLength ? clip.durationTicks : patternLength;
2294                    const clipLeft = ticksToPixels(clip.startTick);
2295                    const clipWidth = ticksToPixels(actualDurationTicks);
2296                    const clipHeight = trackHeight - 12;
2297                    const isSelected = selectedClipIds.includes(clip.id);
2298                    return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2299                      "div",
2300                      {
2301                        onDoubleClick: () => !easyMode && handleClipDoubleClick(clip.patternId),
2302                        className: `absolute rounded transition-shadow flex flex-col overflow-hidden ${clip.isMuted ? "opacity-40" : ""} ${!easyMode && isSelected ? "ring-2 ring-indigo-500 border border-white z-40" : "border border-zinc-300/50 dark:border-zinc-700/40 z-10"}`,
2303                        style: {
2304                          left: clipLeft,
2305                          width: clipWidth,
2306                          top: trackTop + 6,
2307                          height: clipHeight,
2308                          backgroundColor: easyMode ? pattern.color + "18" : pattern.color + "25",
2309                          borderColor: pattern.color
2310                        },
2311                        onPointerDown: (e) => {
2312                          var _a2, _b2, _c;
2313                          e.stopPropagation();
2314                          if (e.button === 1) {
2315                            e.preventDefault();
2316                            panStartRef.current = {
2317                              x: e.clientX,
2318                              y: e.clientY,
2319                              scrollLeft: ((_a2 = gridHorizontalScrollRef.current) == null ? void 0 : _a2.scrollLeft) || 0,
2320                              scrollTop: ((_b2 = verticalScrollContainerRef.current) == null ? void 0 : _b2.scrollTop) || 0
2321                            };
2322                            setIsPanning(true);
2323                            document.body.style.cursor = "grabbing";
2324                            (_c = gridRef.current) == null ? void 0 : _c.setPointerCapture(e.pointerId);
2325                            return;
2326                          }
2327                          if (easyMode) {
2328                            if (e.button === 0) {
2329                              setActiveTrackIndex(clip.trackIndex);
2330                              setActivePatternId(clip.patternId);
2331                            }
2332                            return;
2333                          }
2334                          if (e.button === 0) {
2335                            const isRightEdge = e.clientX - e.currentTarget.getBoundingClientRect().right > -8;
2336                            const isLeftEdge = e.clientX - e.currentTarget.getBoundingClientRect().left < 8;
2337                            if (!easyMode && e.shiftKey && !isRightEdge && !isLeftEdge) {
2338                              const cloneId = `clip-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
2339                              const clonedClip = {
2340                                ...clip,
2341                                id: cloneId
2342                              };
2343                              setPlaylistClips((prev) => [...prev, clonedClip]);
2344                              setActiveDragClipId(cloneId);
2345                              setDragStartClipState(clonedClip);
2346                              setDragStartMouseX(e.clientX);
2347                              setActiveDragAction("move");
2348                              setDragStartTick(clip.startTick);
2349                              setDragStartTrackIdx(clip.trackIndex);
2350                              setSelectedClipIds([cloneId]);
2351                            } else {
2352                              setActiveDragClipId(clip.id);
2353                              setDragStartClipState(clip);
2354                              setDragStartMouseX(e.clientX);
2355                              if (isRightEdge) {
2356                                setActiveDragAction("trim-right");
2357                              } else if (isLeftEdge) {
2358                                setActiveDragAction("trim-left");
2359                              } else {
2360                                if (currentPlaylistTool === "slip") {
2361                                  setActiveDragAction("slip");
2362                                } else if (currentPlaylistTool === "paint") {
2363                                  setActiveDragAction("paint");
2364                                  lastPaintedTickRef.current = clip.startTick;
2365                                  lastPaintedTrackIdxRef.current = clip.trackIndex;
2366                                } else {
2367                                  setActiveDragAction("move");
2368                                  setDragStartTick(clip.startTick);
2369                                  setDragStartTrackIdx(clip.trackIndex);
2370                                }
2371                              }
2372                              if (e.shiftKey) {
2373                                setSelectedClipIds((prev) => [...prev, clip.id]);
2374                              } else if (!isSelected) {
2375                                setSelectedClipIds([clip.id]);
2376                              }
2377                            }
2378                          } else if (e.button === 2) {
2379                            e.preventDefault();
2380                            e.stopPropagation();
2381                            if (!easyMode && e.altKey) {
2382                              const rect = gridRef.current.getBoundingClientRect();
2383                              const clickX = e.clientX - rect.left;
2384                              const clickTick = getSnappedTick(pixelsToTicks(clickX));
2385                              setContextMenu({
2386                                x: e.clientX,
2387                                y: e.clientY,
2388                                clipId: clip.id,
2389                                clickTick
2390                              });
2391                            } else {
2392                              setPlaylistClips((prev) => prev.filter((c) => c.id !== clip.id));
2393                              setActiveDragAction("right-delete");
2394                              setTimeout(() => forceSync(), 0);
2395                            }
2396                          }
2397                        },
2398                        children: [
2399                          !easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2400                            "div",
2401                            {
2402                              className: "h-4 px-1.5 text-[9px] font-bold text-white flex items-center justify-between border-b border-black/10 select-none",
2403                              style: { backgroundColor: pattern.color + "40" },
2404                              children: [
2405                                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate flex-1", children: pattern.name }),
2406                                clip.isMuted && /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { size: 8, className: "text-red-400" })
2407                              ]
2408                            }
2409                          ),
2410                          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative overflow-hidden pointer-events-none", children: Array.from({ length: Math.ceil(actualDurationTicks / patternLength) }).map((_, loopIdx) => {
2411                            const loopOffset = loopIdx * patternLength - clip.offsetTicks;
2412                            const loopLeft = ticksToPixels(loopOffset);
2413                            const loopWidth = ticksToPixels(patternLength);
2414                            return /* @__PURE__ */ jsxRuntimeExports.jsx(
2415                              "div",
2416                              {
2417                                className: "absolute h-full border-r border-dashed border-white/10",
2418                                style: { left: loopLeft, width: loopWidth },
2419                                children: pattern.notes.map((note, nIdx) => {
2420                                  const noteLeft = ticksToPixels(note.ticks);
2421                                  const noteWidth = Math.max(ticksToPixels(note.durationTicks), 2);
2422                                  const noteMidis = pattern.notes.map((n) => n.midi);
2423                                  const minN = noteMidis.length > 0 ? Math.min(...noteMidis) - 2 : 36;
2424                                  const maxN = noteMidis.length > 0 ? Math.max(...noteMidis) + 2 : 96;
2425                                  const noteRange = maxN - minN || 1;
2426                                  const noteY = 100 - (note.midi - minN) / noteRange * 100;
2427                                  return /* @__PURE__ */ jsxRuntimeExports.jsx(
2428                                    "div",
2429                                    {
2430                                      className: "absolute rounded-sm",
2431                                      style: {
2432                                        left: noteLeft,
2433                                        width: noteWidth,
2434                                        top: `${Math.max(5, Math.min(95, noteY))}%`,
2435                                        backgroundColor: pattern.color,
2436                                        height: "2px",
2437                                        opacity: 0.85
2438                                      }
2439                                    },
2440                                    nIdx
2441                                  );
2442                                })
2443                              },
2444                              loopIdx
2445                            );
2446                          }) }),
2447                          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 top-0 w-2 h-full cursor-col-resize z-30" }),
2448                          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-0 top-0 w-2 h-full cursor-col-resize z-30" })
2449                        ]
2450                      },
2451                      clip.id
2452                    );
2453                  }),
2454                  isAutomationVisible && !easyMode && automationClips.map((clip) => {
2455                    const trackTop = getTrackTop(clip.trackIndex);
2456                    const trackHeight = getTrackHeight(clip.trackIndex);
2457                    if (trackHeight === 0) return null;
2458                    const clipLeft = ticksToPixels(clip.startTick);
2459                    const clipWidth = ticksToPixels(clip.durationTicks);
2460                    const sortedNodes = [...clip.nodes].sort((a, b) => a.tick - b.tick);
2461                    const points = [];
2462                    sortedNodes.forEach((node, nodeIdx) => {
2463                      const x = ticksToPixels(node.tick);
2464                      const y = trackTop + (1 - node.value) * trackHeight;
2465                      if (nodeIdx === 0) {
2466                        points.push(`M ${x} ${y}`);
2467                      } else {
2468                        const prevNode = sortedNodes[nodeIdx - 1];
2469                        const prevX = ticksToPixels(prevNode.tick);
2470                        const prevY = trackTop + (1 - prevNode.value) * trackHeight;
2471                        if (Math.abs(prevNode.tension || 0) < 0.01) {
2472                          points.push(`L ${x} ${y}`);
2473                        } else {
2474                          const k = (prevNode.tension || 0) * 5;
2475                          for (let step = 1; step <= 20; step++) {
2476                            const t = step / 20;
2477                            const curveT = (Math.exp(t * k) - 1) / (Math.exp(k) - 1);
2478                            const currX = prevX + t * (x - prevX);
2479                            const currY = prevY + curveT * (y - prevY);
2480                            points.push(`L ${currX} ${currY}`);
2481                          }
2482                        }
2483                      }
2484                    });
2485                    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 pointer-events-none", children: [
2486                      /* @__PURE__ */ jsxRuntimeExports.jsx(
2487                        "div",
2488                        {
2489                          className: "absolute bg-teal-500/5 border border-dashed border-teal-500/25 rounded z-0",
2490                          style: {
2491                            left: clipLeft,
2492                            width: clipWidth,
2493                            top: trackTop + 3,
2494                            height: trackHeight - 6
2495                          }
2496                        }
2497                      ),
2498                      /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "absolute inset-0 w-full h-full z-20", children: sortedNodes.length > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx(
2499                        "path",
2500                        {
2501                          d: points.join(" "),
2502                          fill: "none",
2503                          stroke: "#14b8a6",
2504                          strokeWidth: "2",
2505                          strokeDasharray: "4 2"
2506                        }
2507                      ) }),
2508                      sortedNodes.map((node, nodeIdx) => {
2509                        const nodeX = ticksToPixels(node.tick);
2510                        const nodeY = trackTop + (1 - node.value) * trackHeight;
2511                        let tensionHandle = null;
2512                        if (nodeIdx < sortedNodes.length - 1) {
2513                          const nextNode = sortedNodes[nodeIdx + 1];
2514                          const nextX = ticksToPixels(nextNode.tick);
2515                          const nextY = trackTop + (1 - nextNode.value) * trackHeight;
2516                          const midX = (nodeX + nextX) / 2;
2517                          const k = (node.tension || 0) * 5;
2518                          const curveT = Math.abs(k) < 0.01 ? 0.5 : (Math.exp(0.5 * k) - 1) / (Math.exp(k) - 1);
2519                          const midY = nodeY + curveT * (nextY - nodeY);
2520                          tensionHandle = /* @__PURE__ */ jsxRuntimeExports.jsx(
2521                            "div",
2522                            {
2523                              onPointerDown: (e) => {
2524                                e.stopPropagation();
2525                                e.preventDefault();
2526                                setActiveDragAction("tension-drag");
2527                                setDragTensionClipId(clip.id);
2528                                setDragTensionNodeIdx(nodeIdx);
2529                              },
2530                              className: "absolute w-2 h-2 bg-indigo-400 border border-white rounded-full cursor-ns-resize pointer-events-auto transform -translate-x-1/2 -translate-y-1/2 hover:scale-125 transition-transform z-35",
2531                              style: { left: midX, top: midY },
2532                              title: "Drag to curve envelope"
2533                            },
2534                            `tension-${node.id}`
2535                          );
2536                        }
2537                        return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
2538                          /* @__PURE__ */ jsxRuntimeExports.jsx(
2539                            "div",
2540                            {
2541                              onPointerDown: (e) => {
2542                                e.stopPropagation();
2543                                e.preventDefault();
2544                                if (e.button === 0) {
2545                                  setActiveDragAction("node-drag");
2546                                  setDragNodeClipId(clip.id);
2547                                  setDragNodeId(node.id);
2548                                  setDragNodeStartProps({ tick: node.tick, value: node.value });
2549                                  setDragStartTick(rawTick);
2550                                } else if (e.button === 2) {
2551                                  if (clip.nodes.length > 2) {
2552                                    setAutomationClips((prev) => prev.map(
2553                                      (c) => c.id === clip.id ? { ...c, nodes: c.nodes.filter((n) => n.id !== node.id) } : c
2554                                    ));
2555                                    setTimeout(() => forceSync(), 0);
2556                                  }
2557                                }
2558                              },
2559                              onDoubleClick: (e) => {
2560                                e.stopPropagation();
2561                                const newValText = window.prompt("Enter node value (0.0 to 1.0):", node.value.toFixed(2));
2562                                if (newValText) {
2563                                  const value = Math.max(0, Math.min(1, parseFloat(newValText)));
2564                                  setAutomationClips((prev) => prev.map(
2565                                    (c) => c.id === clip.id ? { ...c, nodes: c.nodes.map((n) => n.id === node.id ? { ...n, value } : n) } : c
2566                                  ));
2567                                  setTimeout(() => forceSync(), 0);
2568                                }
2569                              },
2570                              className: "absolute w-3.5 h-3.5 bg-teal-400 border-2 border-white dark:border-zinc-950 rounded-full cursor-move pointer-events-auto transform -translate-x-1/2 -translate-y-1/2 hover:scale-125 transition-transform shadow-md z-30",
2571                              style: { left: nodeX, top: nodeY },
2572                              title: "Drag to edit node. Double click to type value. Right click to delete node."
2573                            }
2574                          ),
2575                          tensionHandle
2576                        ] }, node.id);
2577                      }),
2578                      /* @__PURE__ */ jsxRuntimeExports.jsx(
2579                        "div",
2580                        {
2581                          className: "absolute pointer-events-auto z-10 cursor-crosshair opacity-0 hover:opacity-10",
2582                          style: {
2583                            left: clipLeft,
2584                            width: clipWidth,
2585                            top: trackTop + 3,
2586                            height: trackHeight - 6
2587                          },
2588                          onPointerDown: (e) => {
2589                            if (e.button === 0 && e.ctrlKey) {
2590                              e.stopPropagation();
2591                              const bounding = e.currentTarget.getBoundingClientRect();
2592                              const xPos = e.clientX - bounding.left;
2593                              const nodeTick = getSnappedTick(pixelsToTicks(xPos));
2594                              const yPos = e.clientY - bounding.top;
2595                              const nodeVal = 1 - yPos / (trackHeight - 6);
2596                              const newNode = {
2597                                id: `n-${Date.now()}`,
2598                                tick: nodeTick,
2599                                value: Math.max(0, Math.min(1, nodeVal)),
2600                                tension: 0
2601                              };
2602                              setAutomationClips((prev) => prev.map(
2603                                (c) => c.id === clip.id ? { ...c, nodes: [...c.nodes, newNode] } : c
2604                              ));
2605                              setTimeout(() => forceSync(), 0);
2606                            }
2607                          },
2608                          title: "Ctrl+Left Click to place automation node"
2609                        }
2610                      )
2611                    ] }, clip.id);
2612                  }),
2613                  /* @__PURE__ */ jsxRuntimeExports.jsx(
2614                    "div",
2615                    {
2616                      ref: arrangementPlayheadRef,
2617                      className: "absolute top-0 h-full w-[2px] bg-amber-500 z-50 pointer-events-none shadow",
2618                      style: { left: ticksToPixels(engine ? engine.secondsToTicks(currentTime) : 0) }
2619                    }
2620                  ),
2621                  activeDragAction === "select" && lassoStart && lassoCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx(
2622                    "div",
2623                    {
2624                      className: "absolute border border-indigo-400 bg-indigo-500/10 z-40 pointer-events-none",
2625                      style: {
2626                        left: Math.min(lassoStart.x, lassoCurrent.x),
2627                        top: Math.min(lassoStart.y, lassoCurrent.y),
2628                        width: Math.abs(lassoStart.x - lassoCurrent.x),
2629                        height: Math.abs(lassoStart.y - lassoCurrent.y)
2630                      }
2631                    }
2632                  ),
2633                  activeDragAction === "slice" && sliceStart && sliceCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx(
2634                    "div",
2635                    {
2636                      className: "absolute border-l-2 border-dashed border-red-500 z-40 pointer-events-none",
2637                      style: {
2638                        left: sliceCurrent.x - gridRef.current.getBoundingClientRect().left,
2639                        top: getTrackTop(dragStartTrackIdx),
2640                        height: getTrackHeight(dragStartTrackIdx)
2641                      }
2642                    }
2643                  )
2644                ]
2645              }
2646            )
2647          }
2648        )
2649      ] }) })
2650    ] }),
2651    contextMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2652      "div",
2653      {
2654        className: "fixed bg-white/95 dark:bg-zinc-900/95 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-xl py-1 z-[100] w-42 backdrop-blur-md text-xs font-semibold text-zinc-700 dark:text-zinc-300",
2655        style: { left: contextMenu.x, top: contextMenu.y },
2656        onClick: (e) => e.stopPropagation(),
2657        children: [
2658          /* @__PURE__ */ jsxRuntimeExports.jsxs(
2659            "button",
2660            {
2661              onClick: () => {
2662                makePatternUnique(contextMenu.clipId);
2663                setContextMenu(null);
2664              },
2665              className: "w-full text-left px-3 py-1.5 hover:bg-indigo-600 hover:text-white transition-colors flex items-center gap-2",
2666              children: [
2667                /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 13 }),
2668                " Make Unique"
2669              ]
2670            }
2671          ),
2672          /* @__PURE__ */ jsxRuntimeExports.jsxs(
2673            "button",
2674            {
2675              onClick: () => {
2676                const clip = playlistClips.find((c) => c.id === contextMenu.clipId);
2677                const pattern = patterns.find((p) => p.id === (clip == null ? void 0 : clip.patternId));
2678                if (pattern) {
2679                  const newName = window.prompt("Rename Pattern:", pattern.name);
2680                  if (newName) {
2681                    setPatterns((prev) => prev.map((p) => p.id === pattern.id ? { ...p, name: newName } : p));
2682                  }
2683                  const newColor = window.prompt("Enter Color Hex (e.g. #ff0055):", pattern.color);
2684                  if (newColor) {
2685                    setPatterns((prev) => prev.map((p) => p.id === pattern.id ? { ...p, color: newColor } : p));
2686                  }
2687                }
2688                setContextMenu(null);
2689              },
2690              className: "w-full text-left px-3 py-1.5 hover:bg-indigo-600 hover:text-white transition-colors flex items-center gap-2",
2691              children: [
2692                /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { size: 13 }),
2693                " Rename/Color"
2694              ]
2695            }
2696          ),
2697          /* @__PURE__ */ jsxRuntimeExports.jsxs(
2698            "button",
2699            {
2700              onClick: () => {
2701                handleSliceClipAtTick(contextMenu.clipId, contextMenu.clickTick);
2702                setContextMenu(null);
2703              },
2704              className: "w-full text-left px-3 py-1.5 hover:bg-indigo-600 hover:text-white transition-colors flex items-center gap-2",
2705              children: [
2706                /* @__PURE__ */ jsxRuntimeExports.jsx(Scissors, { size: 13 }),
2707                " Chop/Slice"
2708              ]
2709            }
2710          ),
2711          /* @__PURE__ */ jsxRuntimeExports.jsxs(
2712            "button",
2713            {
2714              onClick: () => {
2715                setPlaylistClips((prev) => prev.map((c) => c.id === contextMenu.clipId ? { ...c, isManualLength: false, offsetTicks: 0 } : c));
2716                setContextMenu(null);
2717                setTimeout(() => forceSync(), 0);
2718              },
2719              className: "w-full text-left px-3 py-1.5 hover:bg-indigo-600 hover:text-white transition-colors flex items-center gap-2",
2720              children: [
2721                /* @__PURE__ */ jsxRuntimeExports.jsx(SlidersVertical, { size: 13 }),
2722                " Reset Clip Length"
2723              ]
2724            }
2725          )
2726        ]
2727      }
2728    ),
2729    isHelpOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(
2730      "div",
2731      {
2732        className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm z-[200] flex items-center justify-center p-4 animate-fade-in",
2733        onClick: () => setIsHelpOpen(false),
2734        children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2735          "div",
2736          {
2737            className: "bg-white/90 dark:bg-zinc-900/90 border border-zinc-200 dark:border-zinc-800 w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden backdrop-blur-md flex flex-col h-[520px] max-h-[90vh] transition-transform scale-100 transform duration-300 hover:shadow-indigo-500/10",
2738            onClick: (e) => e.stopPropagation(),
2739            children: [
2740              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between bg-zinc-50/50 dark:bg-zinc-950/40", children: [
2741                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
2742                  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { className: "text-indigo-500 dark:text-indigo-400", size: 18 }),
2743                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm text-zinc-800 dark:text-zinc-100 uppercase tracking-wider", children: "Arrangement View Guide" })
2744                ] }),
2745                /* @__PURE__ */ jsxRuntimeExports.jsx(
2746                  "button",
2747                  {
2748                    onClick: () => setIsHelpOpen(false),
2749                    className: "p-1 hover:bg-zinc-200 dark:hover:bg-zinc-800 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 rounded-full transition-colors",
2750                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 16 })
2751                  }
2752                )
2753              ] }),
2754              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-zinc-100 dark:border-zinc-800 bg-zinc-50/20 dark:bg-zinc-950/10 p-1.5 gap-1 shrink-0", children: [
2755                /* @__PURE__ */ jsxRuntimeExports.jsxs(
2756                  "button",
2757                  {
2758                    onClick: () => setActiveHelpTab("shortcuts"),
2759                    className: `flex-1 py-2 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 ${activeHelpTab === "shortcuts" ? "bg-indigo-600 text-white shadow-md shadow-indigo-600/20" : "text-zinc-600 dark:text-zinc-400 hover:text-zinc-800 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800/40"}`,
2760                    children: [
2761                      /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointer2, { size: 13 }),
2762                      "Controls & Shortcuts"
2763                    ]
2764                  }
2765                ),
2766                /* @__PURE__ */ jsxRuntimeExports.jsxs(
2767                  "button",
2768                  {
2769                    onClick: () => setActiveHelpTab("about"),
2770                    className: `flex-1 py-2 px-4 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 ${activeHelpTab === "about" ? "bg-indigo-600 text-white shadow-md shadow-indigo-600/20" : "text-zinc-600 dark:text-zinc-400 hover:text-zinc-800 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800/40"}`,
2771                    children: [
2772                      /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 13 }),
2773                      "About Arrangement"
2774                    ]
2775                  }
2776                )
2777              ] }),
2778              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-5 text-xs text-zinc-600 dark:text-zinc-300 leading-relaxed scrollbar-thin scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700", children: activeHelpTab === "shortcuts" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
2779                easyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 bg-amber-50 dark:bg-amber-950/20 border border-amber-200/50 dark:border-amber-800/30 rounded-xl text-amber-800 dark:text-amber-300 font-semibold mb-3 flex items-start gap-2", children: [
2780                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-500", children: "⚠️" }),
2781                  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
2782                    /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Easy Mode Active:" }),
2783                    " Some advanced tools and shortcuts (e.g. Duplication, Copy/Paste, Nudging) are disabled. Switch to Pro Mode in settings for full DAW functionality."
2784                  ] })
2785                ] }),
2786                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2787                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-800 dark:text-zinc-100 mb-2 border-b border-zinc-100 dark:border-zinc-800 pb-1 uppercase tracking-wider text-[10px]", children: "🖱️ Mouse Controls & Actions" }),
2788                  /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left border-collapse", children: [
2789                    /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-zinc-100 dark:border-zinc-800 text-zinc-400 text-[9px] uppercase", children: [
2790                      /* @__P
2790URE__ */ jsxRuntimeExports.jsx("th", { className: "py-1 font-semibold w-1/3", children: "Action" }),
2791                      /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "py-1 font-semibold", children: "Behavior" })
2792                    ] }) }),
2793                    /* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { className: "divide-y divide-zinc-100/50 dark:divide-zinc-800/50", children: [
2794                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2795                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Left-Click (Draw/Paint)" }),
2796                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Place active pattern on snapped grid timeline. Click & drag to move existing clip." })
2797                      ] }),
2798                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2799                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Left/Right Edge Drag" }),
2800                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Trim clip boundaries left or right (adjusts duration)." })
2801                      ] }),
2802                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2803                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Shift + Drag Clip" }),
2804                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2805                          "Clone/duplicate the clip to a new position. ",
2806                          !easyMode ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500 font-medium", children: "(Pro Only)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-medium", children: "(Locked)" })
2807                        ] })
2808                      ] }),
2809                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2810                        /* @__P
2810URE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Right-Click Clip" }),
2811                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Quickly delete a clip from the playlist grid." })
2812                      ] }),
2813                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2814                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Alt + Right-Click Clip" }),
2815                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2816                          "Open Context Menu (Make Unique, Rename/Color, Chop/Slice). ",
2817                          !easyMode ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500 font-medium", children: "(Pro Only)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-medium", children: "(Locked)" })
2818                        ] })
2819                      ] }),
2820                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2821                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Middle Mouse Button (MMB)" }),
2822                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Press and drag anywhere on the grid canvas to pan the view." })
2823                      ] }),
2824                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2825                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Ctrl + Scroll Wheel" }),
2826                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Zoom view horizontally, centered around the mouse cursor." })
2827                      ] }),
2828                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2829                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 font-bold text-zinc-700 dark:text-zinc-300", children: "Alt + Scroll Wheel" }),
2830                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Zoom track lane heights vertically." })
2831                      ] })
2832                    ] })
2833                  ] })
2834                ] }),
2835                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2", children: [
2836                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-800 dark:text-zinc-100 mb-2 border-b border-zinc-100 dark:border-zinc-800 pb-1 uppercase tracking-wider text-[10px]", children: "⌨️ Keyboard Hotkeys" }),
2837                  /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left border-collapse", children: [
2838                    /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-zinc-100 dark:border-zinc-800 text-zinc-400 text-[9px] uppercase", children: [
2839                      /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "py-1 font-semibold w-1/3", children: "Shortcut" }),
2840                      /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "py-1 font-semibold", children: "Command" })
2841                    ] }) }),
2842                    /* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { className: "divide-y divide-zinc-100/50 dark:divide-zinc-800/50", children: [
2843                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2844                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Spacebar" }) }),
2845                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Play / Pause playback." })
2846                      ] }),
2847                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2848                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2849                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Ctrl + Space" }),
2850                          " or ",
2851                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Numpad 0" })
2852                        ] }),
2853                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Stop playback and return playhead." })
2854                      ] }),
2855                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2856                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Escape" }) }),
2857                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Deselect all selected clips and notes." })
2858                      ] }),
2859                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2860                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Ctrl + A" }) }),
2861                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Select all clips on the grid canvas." })
2862                      ] }),
2863                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2864                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2865                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Del" }),
2866                          " or ",
2867                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Backspace" })
2868                        ] }),
2869                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Delete selected clips (or selected notes)." })
2870                      ] }),
2871                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2872                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2873                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Ctrl + C" }),
2874                          " / ",
2875                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "X" }),
2876                          " / ",
2877                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "V" })
2878                        ] }),
2879                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2880                          "Copy / Cut / Paste selected clips relative to playhead. ",
2881                          !easyMode ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500 font-medium", children: "(Pro Only)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-medium", children: "(Locked)" })
2882                        ] })
2883                      ] }),
2884                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2885                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Ctrl + B" }) }),
2886                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2887                          "Contiguously duplicate selected clips down the timeline. ",
2888                          !easyMode ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500 font-medium", children: "(Pro Only)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-medium", children: "(Locked)" })
2889                        ] })
2890                      ] }),
2891                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2892                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Ctrl + ← / →" }) }),
2893                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2894                          "Nudge selected clips horizontally by snapped grid interval. ",
2895                          !easyMode ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500 font-medium", children: "(Pro Only)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-medium", children: "(Locked)" })
2896                        ] })
2897                      ] }),
2898                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2899                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Shift + ↑ / ↓" }) }),
2900                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2901                          "Move selected clips vertically across track lanes. ",
2902                          !easyMode ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-green-500 font-medium", children: "(Pro Only)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-medium", children: "(Locked)" })
2903                        ] })
2904                      ] }),
2905                      /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
2906                        /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-1.5", children: [
2907                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Ctrl + Z" }),
2908                          " / ",
2909                          /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Y" })
2910                        ] }),
2911                        /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5", children: "Undo / Redo editor history states." })
2912                      ] })
2913                    ] })
2914                  ] })
2915                ] })
2916              ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
2917                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2918                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-800 dark:text-zinc-100 uppercase tracking-wider text-[10px] border-b border-zinc-100 dark:border-zinc-800 pb-1", children: "🎵 What is the Arrangement View?" }),
2919                  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
2920                    "The ",
2921                    /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Arrangement View" }),
2922                    " (inspired by FL Studio's Playlist) serves as the structural orchestrator for your entire project. Unlike the Piano Roll, which focuses on editing notes inside a single pattern, the Arrangement View allows you to build the overarching flow, verse-chorus transitions, and automated filter sweeps of your song."
2923                  ] })
2924                ] }),
2925                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2926                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-800 dark:text-zinc-100 uppercase tracking-wider text-[10px] border-b border-zinc-100 dark:border-zinc-800 pb-1", children: "🛣️ Multi-Pattern Track Lanes & Grouping" }),
2927                  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Track lanes in this playlist are fully generic. A single track can contain clips from different MIDI Patterns (e.g. lead melody, basslines, and drum beats)." }),
2928                  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Each track lane has dynamic controls:" }),
2929                  /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-disc pl-5 space-y-1", children: [
2930                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
2931                      /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Mute / Solo:" }),
2932                      " Toggle audible states to solo a specific line or mute distractions."
2933                    ] }),
2934                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
2935                      /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Volume & Pan Sliders:" }),
2936                      " Customize audio mix settings directly for each track lane."
2937                    ] }),
2938                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
2939                      /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Sub-tracks & Grouping:" }),
2940                      " Drag or toggle tracks to form collapsible parent/child groups. Click the collapse arrow to clean up large projects and hide secondary automation lines."
2941                    ] })
2942                  ] })
2943                ] }),
2944                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2945                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-800 dark:text-zinc-100 uppercase tracking-wider text-[10px] border-b border-zinc-100 dark:border-zinc-800 pb-1", children: "🎹 Integration with the Piano Roll" }),
2946                  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
2947                    "Any clip you place in the grid points to a parent ",
2948                    /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Pattern" }),
2949                    ". Double-clicking any pattern clip immediately opens its MIDI data inside the Piano Roll editor below, letting you fine-tune its notes."
2950                  ] }),
2951                  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
2952                    "If you want to edit a clip independently without changing other clips of the same pattern, use ",
2953                    /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "px-1.5 py-0.5 rounded bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 text-[10px] font-mono", children: "Alt + Right-Click" }),
2954                    " (in Pro Mode) and select ",
2955                    /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Make Unique" }),
2956                    ". This duplicates the pattern under a new name, letting you edit its notes separately."
2957                  ] })
2958                ] })
2959              ] }) }),
2960              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950 flex items-center justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2961                "button",
2962                {
2963                  onClick: () => setIsHelpOpen(false),
2964                  className: "px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-100 rounded-xl text-xs font-bold transition-all shadow-sm",
2965                  children: "Got it"
2966                }
2967              ) })
2968            ]
2969          }
2970        )
2971      }
2972    )
2973  ] });
2974};
2975const TrackInspector = () => {
2976  const { t } = useTranslation();
2977  const { engine, activeTrackIndex, forceSync, selectedNoteIds, updateNote, selectedTempoIndex, selectedTimeSigIndex } = useEditor();
2978  const track = engine && engine.midi && activeTrackIndex !== null ? engine.midi.tracks[activeTrackIndex] : null;
2979  const ppq = engine && engine.midi ? engine.midi.header.ppq : 480;
2980  const [selectedCategory, setSelectedCategory] = reactExports.useState("Piano");
2981  reactExports.useEffect(() => {
2982    if (!track) return;
2983    const isPerc = track.channel === 9;
2984    if (isPerc) {
2985      setSelectedCategory("Percussion");
2986    } else {
2987      const cat = INSTRUMENT_CATEGORIES.find((c) => track.instrument.number >= c.start && track.instrument.number <= c.end);
2988      if (cat) setSelectedCategory(cat.name);
2989    }
2990  }, [track == null ? void 0 : track.channel, track == null ? void 0 : track.instrument.number, activeTrackIndex]);
2991  if (!engine || !engine.midi || activeTrackIndex === null || !track) {
2992    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-sm text-zinc-500", children: t("editor.inspector.selectTrack", "Select a track to edit properties.") });
2993  }
2994  const handleCategoryChange = (e) => {
2995    const newCat = e.target.value;
2996    setSelectedCategory(newCat);
2997    if (newCat === "Percussion") {
2998      engine.updateTrackChannel(activeTrackIndex, 9);
2999      forceSync();
3000    } else {
3001      if (track.channel === 9) {
3002        engine.updateTrackChannel(activeTrackIndex, 0);
3003      }
3004      const catInfo = INSTRUMENT_CATEGORIES.find((c) => c.name === newCat);
3005      if (catInfo) {
3006        engine.updateTrackInstrument(activeTrackIndex, catInfo.start);
3007        forceSync();
3008      }
3009    }
3010  };
3011  const handleInstrumentChange = (e) => {
3012    engine.updateTrackInstrument(activeTrackIndex, parseInt(e.target.value, 10));
3013    forceSync();
3014  };
3015  const handleNameChange = (e) => {
3016    engine.updateTrackName(activeTrackIndex, e.target.value);
3017    forceSync();
3018  };
3019  const handleChannelChange = (e) => {
3020    engine.updateTrackChannel(activeTrackIndex, parseInt(e.target.value, 10));
3021    forceSync();
3022  };
3023  const handleNoteChange = (field, val) => {
3024    if (selectedNoteIds.length > 0 && !isNaN(val)) {
3025      selectedNoteIds.forEach((idx) => updateNote(activeTrackIndex, idx, { [field]: val }));
3026      forceSync();
3027    }
3028  };
3029  const handleNoteMBBChange = (val) => {
3030    if (engine && selectedNoteIds.length > 0) {
3031      const ticks = engine.mbbToTicks(val);
3032      selectedNoteIds.forEach((idx) => updateNote(activeTrackIndex, idx, { ticks }));
3033      forceSync();
3034    }
3035  };
3036  const handleTempoChange = (field, val) => {
3037    if (engine && engine.midi && selectedTempoIndex !== null) {
3038      const t2 = engine.midi.header.tempos[selectedTempoIndex];
3039      if (!t2) return;
3040      const newTicks = field === "ticks" ? val : t2.ticks;
3041      const newBpm = field === "bpm" ? val : t2.bpm;
3042      engine.updateTempo(t2.ticks, newTicks, newBpm);
3043      forceSync();
3044    }
3045  };
3046  const handleTempoMBBChange = (val) => {
3047    if (engine && engine.midi && selectedTempoIndex !== null) {
3048      const t2 = engine.midi.header.tempos[selectedTempoIndex];
3049      if (!t2) return;
3050      const newTicks = engine.mbbToTicks(val);
3051      engine.updateTempo(t2.ticks, newTicks, t2.bpm);
3052      forceSync();
3053    }
3054  };
3055  const handleTimeSigChange = (field, val) => {
3056    if (engine && engine.midi && selectedTimeSigIndex !== null) {
3057      const ts = engine.midi.header.timeSignatures[selectedTimeSigIndex];
3058      if (!ts) return;
3059      const newTicks = field === "ticks" ? val : ts.ticks;
3060      const num = field === "numerator" ? val : ts.timeSignature[0];
3061      const den = field === "denominator" ? val : ts.timeSignature[1];
3062      engine.updateTimeSignature(ts.ticks, newTicks, num, den);
3063      forceSync();
3064    }
3065  };
3066  const handleTimeSigMBBChange = (val) => {
3067    if (engine && engine.midi && selectedTimeSigIndex !== null) {
3068      const ts = engine.midi.header.timeSignatures[selectedTimeSigIndex];
3069      if (!ts) return;
3070      const newTicks = engine.mbbToTicks(val);
3071      engine.updateTimeSignature(ts.ticks, newTicks, ts.timeSignature[0], ts.timeSignature[1]);
3072      forceSync();
3073    }
3074  };
3075  const categoryInfo = INSTRUMENT_CATEGORIES.find((c) => c.name === selectedCategory);
3076  let instrumentOptions = [];
3077  if (categoryInfo) {
3078    for (let i = categoryInfo.start; i <= categoryInfo.end; i++) {
3079      instrumentOptions.push({ val: i, label: `${instance.t(`instruments.${i}`, { defaultValue: GM_INSTRUMENTS[i] })} (${i})` });
3080    }
3081  }
3082  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 pb-24 flex flex-col gap-4", children: [
3083    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3084      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center mb-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.inspector.trackNameHelp", "The display name for this track."), position: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 cursor-help", children: t("editor.inspector.trackName", "Track Name") }) }) }),
3085      /* @__PURE__ */ jsxRuntimeExports.jsx(
3086        "input",
3087        {
3088          type: "text",
3089          value: track.name,
3090          onChange: handleNameChange,
3091          onFocus: (e) => e.currentTarget.select(),
3092          onDoubleClick: (e) => e.currentTarget.select(),
3093          className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1.5 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors"
3094        }
3095      )
3096    ] }),
3097    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-900/50 border border-zinc-200 dark:border-zinc-800 rounded-md p-3", children: [
3098      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center mb-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.inspector.categoryHelp", "The instrument group for this track. Selecting Percussion switches the track to Channel 10."), position: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 cursor-help", children: t("editor.inspector.category", "Category") }) }) }),
3099      /* @__PURE__ */ jsxRuntimeExports.jsxs(
3100        "select",
3101        {
3102          value: selectedCategory,
3103          onChange: handleCategoryChange,
3104          className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1.5 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3105          children: [
3106            INSTRUMENT_CATEGORIES.map((c) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: c.name, children: [
3107              c.name,
3108              " (",
3109              c.start,
3110              "-",
3111              c.end,
3112              ")"
3113            ] }, c.name)),
3114            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "Percussion", children: t("editor.inspector.drumKitPercussion", "Drum Kit / Percussion") })
3115          ]
3116        }
3117      ),
3118      selectedCategory !== "Percussion" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3119        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center mb-1 mt-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.inspector.instrumentHelp", "The specific General MIDI instrument used for playback."), position: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 cursor-help", children: t("editor.inspector.instrument", "Instrument") }) }) }),
3120        /* @__PURE__ */ jsxRuntimeExports.jsx(
3121          "select",
3122          {
3123            value: track.instrument.number,
3124            onChange: handleInstrumentChange,
3125            className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1.5 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3126            children: instrumentOptions.map((opt) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: opt.val, children: opt.label }, opt.val))
3127          }
3128        )
3129      ] }),
3130      selectedCategory === "Percussion" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500 italic mt-2", children: t("editor.inspector.drumKitInfo", "Drum Kits use Channel 10. Draw notes in the Piano Roll to trigger different drum sounds!") })
3131    ] }),
3132    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3133      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center mb-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.inspector.channelHelp", "MIDI Channel. Channel 10 (index 9) is reserved for Percussion."), position: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 cursor-help", children: t("editor.inspector.channel", "Channel (0-15)") }) }) }),
3134      /* @__PURE__ */ jsxRuntimeExports.jsx(
3135        "input",
3136        {
3137          type: "number",
3138          value: track.channel,
3139          onChange: handleChannelChange,
3140          onFocus: (e) => e.currentTarget.select(),
3141          onDoubleClick: (e) => e.currentTarget.select(),
3142          className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1.5 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3143          min: "0",
3144          max: "15"
3145        }
3146      )
3147    ] }),
3148    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 border-t border-zinc-200 dark:border-zinc-800 pt-4", children: [
3149      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-2", children: [
3150        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.inspector.eventPropertiesHelp", "Properties of the currently selected notes or events."), position: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold cursor-help", children: t("editor.inspector.eventProperties", "Event Properties") }) }) }),
3151        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs bg-zinc-200 dark:bg-zinc-800 px-2 py-1 rounded-full text-zinc-600 dark:text-zinc-400", children: selectedTempoIndex !== null ? t("editor.inspector.tempo", "Tempo") : selectedTimeSigIndex !== null ? t("editor.inspector.timeSignature", "Time Signature") : `${track.notes.length} ${t("editor.inspector.total", "Total")}` })
3152      ] }),
3153      selectedTempoIndex !== null && engine.midi.header.tempos[selectedTempoIndex] ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 mt-3", children: [
3154        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [
3155          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.bpm", "BPM") }),
3156          /* @__PURE__ */ jsxRuntimeExports.jsx(
3157            "input",
3158            {
3159              type: "number",
3160              value: Math.round(engine.midi.header.tempos[selectedTempoIndex].bpm),
3161              onChange: (e) => handleTempoChange("bpm", parseInt(e.target.value, 10)),
3162              onFocus: (e) => e.currentTarget.select(),
3163              onDoubleClick: (e) => e.currentTarget.select(),
3164              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3165              min: "10",
3166              max: "300"
3167            }
3168          )
3169        ] }),
3170        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3171          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.startTicks", "Start (Ticks)") }),
3172          /* @__PURE__ */ jsxRuntimeExports.jsx(
3173            "input",
3174            {
3175              type: "number",
3176              value: engine.midi.header.tempos[selectedTempoIndex].ticks,
3177              onChange: (e) => handleTempoChange("ticks", parseInt(e.target.value, 10)),
3178              onFocus: (e) => e.currentTarget.select(),
3179              onDoubleClick: (e) => e.currentTarget.select(),
3180              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3181              min: "0"
3182            }
3183          )
3184        ] }),
3185        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3186          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.startMBT", "Start (M:B:T)") }),
3187          /* @__PURE__ */ jsxRuntimeExports.jsx(
3188            "input",
3189            {
3190              type: "text",
3191              value: engine.ticksToMBB(engine.midi.header.tempos[selectedTempoIndex].ticks),
3192              onBlur: (e) => handleTempoMBBChange(e.target.value),
3193              onKeyDown: (e) => e.key === "Enter" && handleTempoMBBChange(e.currentTarget.value),
3194              onFocus: (e) => e.currentTarget.select(),
3195              onDoubleClick: (e) => e.currentTarget.select(),
3196              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors"
3197            }
3198          )
3199        ] })
3200      ] }) : selectedTimeSigIndex !== null && engine.midi.header.timeSignatures[selectedTimeSigIndex] ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 mt-3", children: [
3201        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3202          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.numerator", "Numerator") }),
3203          /* @__PURE__ */ jsxRuntimeExports.jsx(
3204            "input",
3205            {
3206              type: "number",
3207              value: engine.midi.header.timeSignatures[selectedTimeSigIndex].timeSignature[0],
3208              onChange: (e) => handleTimeSigChange("numerator", parseInt(e.target.value, 10)),
3209              onFocus: (e) => e.currentTarget.select(),
3210              onDoubleClick: (e) => e.currentTarget.select(),
3211              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3212              min: "1",
3213              max: "32"
3214            }
3215          )
3216        ] }),
3217        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3218          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.denominator", "Denominator") }),
3219          /* @__PURE__ */ jsxRuntimeExports.jsxs(
3220            "select",
3221            {
3222              value: engine.midi.header.timeSignatures[selectedTimeSigIndex].timeSignature[1],
3223              onChange: (e) => handleTimeSigChange("denominator", parseInt(e.target.value, 10)),
3224              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3225              children: [
3226                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "2", children: "2" }),
3227                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "4", children: "4" }),
3228                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "8", children: "8" }),
3229                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "16", children: "16" })
3230              ]
3231            }
3232          )
3233        ] }),
3234        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3235          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.startTicks", "Start (Ticks)") }),
3236          /* @__PURE__ */ jsxRuntimeExports.jsx(
3237            "input",
3238            {
3239              type: "number",
3240              value: engine.midi.header.timeSignatures[selectedTimeSigIndex].ticks,
3241              onChange: (e) => handleTimeSigChange("ticks", parseInt(e.target.value, 10)),
3242              onFocus: (e) => e.currentTarget.select(),
3243              onDoubleClick: (e) => e.currentTarget.select(),
3244              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3245              min: "0"
3246            }
3247          )
3248        ] }),
3249        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3250          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.startMBT", "Start (M:B:T)") }),
3251          /* @__PURE__ */ jsxRuntimeExports.jsx(
3252            "input",
3253            {
3254              type: "text",
3255              value: engine.ticksToMBB(engine.midi.header.timeSignatures[selectedTimeSigIndex].ticks),
3256              onBlur: (e) => handleTimeSigMBBChange(e.target.value),
3257              onKeyDown: (e) => e.key === "Enter" && handleTimeSigMBBChange(e.currentTarget.value),
3258              onFocus: (e) => e.currentTarget.select(),
3259              onDoubleClick: (e) => e.currentTarget.select(),
3260              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors"
3261            }
3262          )
3263        ] })
3264      ] }) : selectedNoteIds.length > 0 && track.notes[selectedNoteIds[0]] ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 mt-3", children: [
3265        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3266          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.pitch", "Pit
3266ch (0-127)") }),
3267          /* @__PURE__ */ jsxRuntimeExports.jsx(
3268            "input",
3269            {
3270              type: "number",
3271              value: track.notes[selectedNoteIds[0]].midi,
3272              onChange: (e) => handleNoteChange("midi", parseInt(e.target.value, 10)),
3273              onFocus: (e) => e.currentTarget.select(),
3274              onDoubleClick: (e) => e.currentTarget.select(),
3275              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors"
3276            }
3277          )
3278        ] }),
3279        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3280          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.velocity", "Velocity (0-127)") }),
3281          /* @__PURE__ */ jsxRuntimeExports.jsx(
3282            "input",
3283            {
3284              type: "number",
3285              value: Math.round(track.notes[selectedNoteIds[0]].velocity * 127),
3286              onChange: (e) => handleNoteChange("velocity", parseInt(e.target.value, 10) / 127),
3287              onFocus: (e) => e.currentTarget.select(),
3288              onDoubleClick: (e) => e.currentTarget.select(),
3289              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3290              min: "0",
3291              max: "127"
3292            }
3293          )
3294        ] }),
3295        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3296          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.startTicks", "Start (Ticks)") }),
3297          /* @__PURE__ */ jsxRuntimeExports.jsx(
3298            "input",
3299            {
3300              type: "number",
3301              value: track.notes[selectedNoteIds[0]].ticks,
3302              onChange: (e) => handleNoteChange("ticks", parseInt(e.target.value, 10)),
3303              onFocus: (e) => e.currentTarget.select(),
3304              onDoubleClick: (e) => e.currentTarget.select(),
3305              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3306              min: "0"
3307            }
3308          )
3309        ] }),
3310        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3311          /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] uppercase font-bold text-zinc-500 mb-1", children: t("editor.inspector.startMBT", "Start (M:B:T)") }),
3312          /* @__PURE__ */ jsxRuntimeExports.jsx(
3313            "input",
3314            {
3315              type: "text",
3316              value: engine.ticksToMBB(track.notes[selectedNoteIds[0]].ticks),
3317              onBlur: (e) => handleNoteMBBChange(e.target.value),
3318              onKeyDown: (e) => e.key === "Enter" && handleNoteMBBChange(e.currentTarget.value),
3319              onFocus: (e) => e.currentTarget.select(),
3320              onDoubleClick: (e) => e.currentTarget.select(),
3321              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors"
3322            }
3323          )
3324        ] }),
3325        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [
3326          /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex justify-between items-center text-[10px] uppercase font-bold text-zinc-500 mb-1", children: [
3327            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("editor.inspector.durationTicks", "Duration (Ticks)") }),
3328            /* @__PURE__ */ jsxRuntimeExports.jsxs(
3329              "select",
3330              {
3331                className: "bg-transparent border-none text-[10px] text-zinc-400 hover:text-indigo-500 cursor-pointer focus:outline-none",
3332                onChange: (e) => {
3333                  if (e.target.value) {
3334                    handleNoteChange("durationTicks", ppq * (4 / Number(e.target.value)));
3335                    e.target.value = "";
3336                  }
3337                },
3338                value: "",
3339                children: [
3340                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", disabled: true, children: t("editor.inspector.snapTo", "Snap To...") }),
3341                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "1", children: "1/1" }),
3342                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "2", children: "1/2" }),
3343                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "4", children: "1/4" }),
3344                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "8", children: "1/8" }),
3345                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "16", children: "1/16" }),
3346                  /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "32", children: "1/32" })
3347                ]
3348              }
3349            )
3350          ] }),
3351          /* @__PURE__ */ jsxRuntimeExports.jsx(
3352            "input",
3353            {
3354              type: "number",
3355              value: track.notes[selectedNoteIds[0]].durationTicks,
3356              onChange: (e) => handleNoteChange("durationTicks", parseInt(e.target.value, 10)),
3357              onFocus: (e) => e.currentTarget.select(),
3358              onDoubleClick: (e) => e.currentTarget.select(),
3359              className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-sm text-zinc-900 dark:text-white focus:outline-none focus:border-indigo-500 transition-colors",
3360              min: "1"
3361            }
3362          )
3363        ] })
3364      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500 italic mt-2", children: t("editor.inspector.selectEvent", "Select an event (Note, Tempo, Time Sig) to edit its properties.") })
3365    ] })
3366  ] });
3367};
3368const TrackMeter = ({ channel, isPlaying, engine }) => {
3369  const [levelL, setLevelL] = React.useState(0);
3370  const [levelR, setLevelR] = React.useState(0);
3371  const lastLevelLRef = React.useRef(0);
3372  const lastLevelRRef = React.useRef(0);
3373  React.useEffect(() => {
3374    let animId;
3375    const update = () => {
3376      let targetL = 0;
3377      let targetR = 0;
3378      if (isPlaying && engine) {
3379        try {
3380          const activeNotes = engine.getActiveNotes(channel);
3381          if (activeNotes && activeNotes.size > 0) {
3382            targetL = 0.35 + Math.random() * 0.45;
3383            targetR = targetL * (0.95 + Math.random() * 0.1);
3384          }
3385        } catch (e) {
3386        }
3387      }
3388      const curL = lastLevelLRef.current;
3389      const curR = lastLevelRRef.current;
3390      let nextL = targetL > curL ? curL + (targetL - curL) * 0.4 : curL - 0.06;
3391      let nextR = targetR > curR ? curR + (targetR - curR) * 0.4 : curR - 0.06;
3392      nextL = Math.max(0, Math.min(1, nextL));
3393      nextR = Math.max(0, Math.min(1, nextR));
3394      lastLevelLRef.current = nextL;
3395      lastLevelRRef.current = nextR;
3396      setLevelL(nextL);
3397      setLevelR(nextR);
3398      animId = requestAnimationFrame(update);
3399    };
3400    animId = requestAnimationFrame(update);
3401    return () => cancelAnimationFrame(animId);
3402  }, [channel, isPlaying, engine]);
3403  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-[1px] h-32 items-center opacity-85", children: [
3404    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[3px] h-full bg-zinc-900 dark:bg-black rounded-sm overflow-hidden relative", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3405      "div",
3406      {
3407        className: "absolute bottom-0 w-full bg-gradient-to-t from-emerald-500 via-yellow-400 to-red-500 transition-all duration-75",
3408        style: { height: `${levelL * 100}%` }
3409      }
3410    ) }),
3411    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[3px] h-full bg-zinc-900 dark:bg-black rounded-sm overflow-hidden relative", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3412      "div",
3413      {
3414        className: "absolute bottom-0 w-full bg-gradient-to-t from-emerald-500 via-yellow-400 to-red-500 transition-all duration-75",
3415        style: { height: `${levelR * 100}%` }
3416      }
3417    ) })
3418  ] });
3419};
3420const MasterMeter = ({ isPlaying, engine }) => {
3421  const [levelL, setLevelL] = React.useState(0);
3422  const [levelR, setLevelR] = React.useState(0);
3423  const analyserRef = React.useRef(null);
3424  const dataArrayRef = React.useRef(null);
3425  React.useEffect(() => {
3426    if (engine && engine.analyser) {
3427      analyserRef.current = engine.analyser;
3428      const bufferLength = engine.analyser.frequencyBinCount;
3429      dataArrayRef.current = new Uint8Array(bufferLength);
3430    }
3431  }, [engine]);
3432  React.useEffect(() => {
3433    let animId;
3434    const update = () => {
3435      let targetL = 0;
3436      let targetR = 0;
3437      if (isPlaying && analyserRef.current && dataArrayRef.current) {
3438        analyserRef.current.getByteTimeDomainData(dataArrayRef.current);
3439        let sum = 0;
3440        for (let i = 0; i < dataArrayRef.current.length; i++) {
3441          const val = (dataArrayRef.current[i] - 128) / 128;
3442          sum += val * val;
3443        }
3444        const rms = Math.sqrt(sum / dataArrayRef.current.length);
3445        targetL = Math.min(1, rms * 4.5);
3446        targetR = targetL * (0.95 + Math.random() * 0.1);
3447        targetR = Math.min(1, targetR);
3448      }
3449      setLevelL((prev) =>
3449 prev + (targetL - prev) * 0.45 - (targetL < prev ? 0.05 : 0));
3450      setLevelR((prev) => prev + (targetR - prev) * 0.45 - (targetR < prev ? 0.05 : 0));
3451      animId = requestAnimationFrame(update);
3452    };
3453    animId = requestAnimationFrame(update);
3454    return () => cancelAnimationFrame(animId);
3455  }, [isPlaying]);
3456  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-[2px] h-36 items-center", children: [
3457    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[5px] h-full bg-zinc-900 rounded overflow-hidden relative border border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3458      "div",
3459      {
3460        className: "absolute bottom-0 w-full bg-gradient-to-t from-emerald-500 via-yellow-400 to-red-500 transition-all duration-75",
3461        style: { height: `${Math.max(0, Math.min(100, levelL * 100))}%` }
3462      }
3463    ) }),
3464    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-[5px] h-full bg-zinc-900 rounded overflow-hidden relative border border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3465      "div",
3466      {
3467        className: "absolute bottom-0 w-full bg-gradient-to-t from-emerald-500 via-yellow-400 to-red-500 transition-all duration-75",
3468        style: { height: `${Math.max(0, Math.min(100, levelR * 100))}%` }
3469      }
3470    ) })
3471  ] });
3472};
3473const PanKnob = ({ value, onChange }) => {
3474  const { t } = useTranslation();
3475  const handleMouseDown = (e) => {
3476    e.preventDefault();
3477    const startY = e.clientY;
3478    const startVal = value;
3479    const handleMouseMove = (moveEvent) => {
3480      const deltaY = startY - moveEvent.clientY;
3481      const sensitivity = 0.015;
3482      let newVal = startVal + deltaY * sensitivity;
3483      newVal = Math.max(-1, Math.min(1, newVal));
3484      onChange(newVal);
3485    };
3486    const handleMouseUp = () => {
3487      document.removeEventListener("mousemove", handleMouseMove);
3488      document.removeEventListener("mouseup", handleMouseUp);
3489    };
3490    document.addEventListener("mousemove", handleMouseMove);
3491    document.addEventListener("mouseup", handleMouseUp);
3492  };
3493  const angle = value * 135;
3494  let label = "C";
3495  if (value < -0.05) {
3496    label = `L${Math.round(Math.abs(value) * 100)}`;
3497  } else if (value > 0.05) {
3498    label = `R${Math.round(value * 100)}`;
3499  }
3500  return /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.mixer.panHelp", "Pan. Drag up/down to adjust."), position: "top", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3501    "div",
3502    {
3503      className: "flex flex-col items-center cursor-ns-resize group relative",
3504      onMouseDown: handleMouseDown,
3505      onDoubleClick: () => onChange(0),
3506      children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-8 h-8 rounded-full bg-zinc-100 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 shadow flex items-center justify-center relative select-none", children: [
3507        /* @__PURE__ */ jsxRuntimeExports.jsx(
3508          "div",
3509          {
3510            className: "w-1.5 h-1.5 bg-indigo-500 rounded-full absolute top-1 transition-transform origin-[center_12px]",
3511            style: { transform: `rotate(${angle}deg)` }
3512          }
3513        ),
3514        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[7px] text-zinc-500 dark:text-zinc-400 group-hover:text-indigo-500 dark:group-hover:text-indigo-400 font-mono font-bold mt-2", children: label })
3515      ] })
3516    }
3517  ) });
3518};
3519const VolumeFader = ({ value, onChange }) => {
3520  const { t } = useTranslation();
3521  const handleMouseDown = (e) => {
3522    e.preventDefault();
3523    const startY = e.clientY;
3524    const startVal = value;
3525    const handleMouseMove = (moveEvent) => {
3526      const deltaY = startY - moveEvent.clientY;
3527      const sensitivity = 8e-3;
3528      let newVal = startVal + deltaY * sensitivity;
3529      newVal = Math.max(0, Math.min(1.2, newVal));
3530      onChange(newVal);
3531    };
3532    const handleMouseUp = () => {
3533      document.removeEventListener("mousemove", handleMouseMove);
3534      document.removeEventListener("mouseup", handleMouseUp);
3535    };
3536    document.addEventListener("mousemove", handleMouseMove);
3537    document.addEventListener("mouseup", handleMouseUp);
3538  };
3539  const percentage = Math.min(100, value / 1.2 * 100);
3540  value > 0 ? (20 * Math.log10(value)).toFixed(1) : "-inf";
3541  return /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.mixer.volumeHelp", "Volume. Drag to adjust."), position: "right", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
3542    "div",
3543    {
3544      className: "w-6 h-32 bg-zinc-200 dark:bg-zinc-950 rounded relative border border-zinc-300 dark:border-zinc-800 shadow-inner flex justify-center cursor-ns-resize",
3545      onMouseDown: handleMouseDown,
3546      onDoubleClick: () => onChange(0.8),
3547      children: [
3548        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 bottom-0 w-[2px] bg-zinc-300 dark:bg-zinc-800" }),
3549        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 top-1/4 h-[1px] bg-zinc-300 dark:bg-zinc-800" }),
3550        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 top-2/4 h-[1px] bg-zinc-300 dark:bg-zinc-800" }),
3551        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 top-3/4 h-[1px] bg-zinc-300 dark:bg-zinc-800" }),
3552        /* @__PURE__ */ jsxRuntimeExports.jsx(
3553          "div",
3554          {
3555            className: "absolute w-5 h-5 bg-zinc-100 dark:bg-zinc-700 hover:bg-white dark:hover:bg-zinc-600 rounded shadow-md border border-zinc-400 dark:border-zinc-800 flex flex-col justify-center items-center z-10",
3556            style: { bottom: `calc(${percentage}% - 10px)` },
3557            children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-[2px] bg-indigo-500" })
3558          }
3559        )
3560      ]
3561    }
3562  ) });
3563};
3564const Mixer = () => {
3565  const { t } = useTranslation();
3566  const {
3567    engine,
3568    activeTrackIndex,
3569    setActiveTrackIndex,
3570    playlistTracks,
3571    setTrackVolume,
3572    setTrackPanning,
3573    toggleTrackMute,
3574    toggleTrackSolo,
3575    masterVolume,
3576    setMasterVolume,
3577    isPlaying
3578  } = useEditor();
3579  if (!engine || !engine.midi) return null;
3580  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex gap-1.5 p-2 bg-zinc-50 dark:bg-zinc-900 border-t border-zinc-200 dark:border-zinc-800 overflow-x-auto select-none", children: [
3581    playlistTracks.map((track, idx) => {
3582      var _a;
3583      const midiTrack = engine.midi.tracks[idx];
3584      const instName = ((_a = midiTrack == null ? void 0 : midiTrack.instrument) == null ? void 0 : _a.name) || "Synth";
3585      const channel = (midiTrack == null ? void 0 : midiTrack.channel) ?? idx % 16;
3586      return /* @__PURE__ */ jsxRuntimeExports.jsxs(
3587        "div",
3588        {
3589          className: `w-24 flex flex-col border rounded-lg overflow-hidden transition-all duration-150 cursor-pointer ${activeTrackIndex === idx ? "bg-indigo-50/20 dark:bg-indigo-950/10 border-indigo-500/50 shadow-md shadow-indigo-500/5" : "bg-white dark:bg-zinc-950 border-zinc-200 dark:border-zinc-800/80 hover:border-zinc-300 dark:hover:border-zinc-700"}`,
3590          onClick: () => setActiveTrackIndex(idx),
3591          children: [
3592            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `h-7 border-b flex items-center justify-between px-2 text-[10px] font-bold truncate transition-colors ${activeTrackIndex === idx ? "bg-indigo-500 text-white" : "bg-zinc-100 dark:bg-zinc-900 text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-800"}`, children: [
3593              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: track.name || t("editor.mixer.trackNumber", "Track {{number}}", { number: idx + 1 }) }),
3594              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[8px] opacity-60 font-mono", children: [
3595                "Ch",
3596                channel + 1
3597              ] })
3598            ] }),
3599            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center pt-3 pb-1 border-b border-zinc-100 dark:border-zinc-900/50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3600              PanKnob,
3601              {
3602                value: track.panning,
3603                onChange: (val) => setTrackPanning(idx, val)
3604              }
3605            ) }),
3606            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex justify-center items-center gap-2 py-4 px-2 bg-zinc-50/50 dark:bg-zinc-950/30", children: [
3607              /* @__PURE__ */ jsxRuntimeExports.jsx(
3608                VolumeFader,
3609                {
3610                  value: track.volume,
3611                  onChange: (val) => setTrackVolume(idx, val)
3612                }
3613              ),
3614              /* @__PURE__ */ jsxRuntimeExports.jsx(
3615                TrackMeter,
3616                {
3617                  channel,
3618                  isPlaying,
3619                  engine
3620                }
3621              )
3622            ] }),
3623            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-20 border-t border-zinc-200 dark:border-zinc-800 flex flex-col p-1.5 gap-1.5 bg-zinc-50 dark:bg-zinc-900/40", children: [
3624              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 justify-center items-center", children: [
3625                /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.mixer.muteHelp", "Mute: Silence this track"), position: "top", className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3626                  "button",
3627                  {
3628                    className: `w-full py-1 text-[10px] font-bold rounded border transition-all duration-150 ${track.isMuted ? "bg-amber-500 border-amber-600 text-white font-black shadow-inner shadow-amber-700/30" : "bg-zinc-200 dark:bg-zinc-800 border-zinc-300 dark:border-zinc-700 text-zinc-500 hover:bg-zinc-300 dark:hover:bg-zinc-700"}`,
3629                    onClick: (e) => {
3630                      e.stopPropagation();
3631                      toggleTrackMute(idx);
3632                    },
3633                    children: "M"
3634                  }
3635                ) }),
3636                /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.mixer.soloHelp", "Solo: Silence all other tracks"), position: "top", className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3637                  "button",
3638                  {
3639                    className: `w-full py-1 text-[10px] font-bold rounded border transition-all duration-150 ${track.isSoloed ? "bg-rose-500 border-rose-600 text-white font-black shadow-inner shadow-rose-700/30 animate-pulse" : "bg-zinc-200 dark:bg-zinc-800 border-zinc-300 dark:border-zinc-700 text-zinc-500 hover:bg-zinc-300 dark:hover:bg-zinc-700"}`,
3640                    onClick: (e) => {
3641                      e.stopPropagation();
3642                      toggleTrackSolo(idx);
3643                    },
3644                    children: "S"
3645                  }
3646                ) })
3647              ] }),
3648              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center text-[9px] font-medium text-zinc-400 dark:text-zinc-500 truncate select-none border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 py-0.5 rounded px-1", title: instName, children: instName })
3649            ] })
3650          ]
3651        },
3652        track.id || idx
3653      );
3654    }),
3655    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-28 ml-3 flex flex-col border border-rose-200 dark:border-rose-950 rounded-lg overflow-hidden bg-rose-50/10 dark:bg-zinc-950/60 shadow-lg shadow-rose-500/5", children: [
3656      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-7 bg-rose-500 text-white flex items-center justify-center text-[10px] font-bold uppercase tracking-wider", children: t("editor.mixer.master", "Master") }),
3657      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex justify-center items-center gap-3 py-4 px-2", children: [
3658        /* @__PURE__ */ jsxRuntimeExports.jsx(
3659          VolumeFader,
3660          {
3661            value: masterVolume,
3662            onChange: (val) => setMasterVolume(val)
3663          }
3664        ),
3665        /* @__PURE__ */ jsxRuntimeExports.jsx(
3666          MasterMeter,
3667          {
3668            isPlaying,
3669            engine
3670          }
3671        )
3672      ] }),
3673      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-20 flex flex-col items-center justify-center p-2 bg-rose-500/5 border-t border-rose-200/50 dark:border-rose-950/50", children: [
3674        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-mono uppercase text-rose-500 font-bold tracking-widest", children: t("editor.mixer.stereoOut", "Stereo Out") }),
3675        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-400 font-medium mt-1", children: [
3676          Math.round(masterVolume * 100),
3677          "%"
3678        ] })
3679      ] })
3680    ] })
3681  ] });
3682};
3683const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
3684const ContextMenu = ({
3685  x,
3686  y,
3687  onClose,
3688  toolMode,
3689  setToolMode,
3690  scaleRoot,
3691  setScaleRoot,
3692  scaleType,
3693  setScaleType,
3694  chordStamp,
3695  setChordStamp,
3696  gridSnap,
3697  setGridSnap,
3698  isDrumMode
3699}) => {
3700  const menuRef = reactExports.useRef(null);
3701  const [activeSubmenu, setActiveSubmenu] = reactExports.useState(null);
3702  const [submenuHoverTimeout, setSubmenuHoverTimeout] = reactExports.useState(null);
3703  const [coords, setCoords] = reactExports.useState({ left: x, top: y });
3704  const [submenuDirection, setSubmenuDirection] = reactExports.useState("right");
3705  reactExports.useEffect(() => {
3706    const handleClickOutside = (e) => {
3707      if (menuRef.current && !menuRef.current.contains(e.target)) {
3708        onClose();
3709      }
3710    };
3711    document.addEventListener("mousedown", handleClickOutside);
3712    return () => document.removeEventListener("mousedown", handleClickOutside);
3713  }, [onClose]);
3714  reactExports.useEffect(() => {
3715    if (menuRef.current) {
3716      const rect = menuRef.current.getBoundingClientRect();
3717      const winWidth = window.innerWidth;
3718      const winHeight = window.innerHeight;
3719      let adjustedX = x;
3720      let adjustedY = y;
3721      if (x + rect.width > winWidth) {
3722        adjustedX = winWidth - rect.width - 10;
3723      }
3724      if (y + rect.height > winHeight) {
3725        adjustedY = winHeight - rect.height - 10;
3726      }
3727      if (adjustedX < 10) adjustedX = 10;
3728      if (adjustedY < 10) adjustedY = 10;
3729      setCoords({ left: adjustedX, top: adjustedY });
3730      if (adjustedX + rect.width + 192 > winWidth) {
3731        setSubmenuDirection("left");
3732      } else {
3733        setSubmenuDirection("right");
3734      }
3735    }
3736  }, [x, y]);
3737  const handleMouseEnterSubmenu = (menuName) => {
3738    if (submenuHoverTimeout) clearTimeout(submenuHoverTimeout);
3739    setActiveSubmenu(menuName);
3740  };
3741  const handleMouseLeaveSubmenu = () => {
3742    const timeout = setTimeout(() => {
3743      setActiveSubmenu(null);
3744    }, 100);
3745    setSubmenuHoverTimeout(timeout);
3746  };
3747  const handleToolSelect = (mode, e) => {
3748    e.stopPropagation();
3749    setToolMode(mode);
3750    onClose();
3751  };
3752  const renderSnapSubmenu = () => /* @__PURE__ */ jsxRuntimeExports.jsx(
3753    "div",
3754    {
3755      className: `absolute top-0 ${submenuDirection === "left" ? "right-full mr-1" : "left-full ml-1"} w-48 bg-zinc-100 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 shadow-xl py-1 text-sm text-zinc-800 dark:text-zinc-200`,
3756      onMouseEnter: () => handleMouseEnterSubmenu("snap"),
3757      onMouseLeave: handleMouseLeaveSubmenu,
3758      children: [
3759        { label: "Off", val: 0 },
3760        { label: "1 Bar (1/1)", val: 1 },
3761        { label: "1/2", val: 2 },
3762        { label: "1/4", val: 4 },
3763        { label: "1/8", val: 8 },
3764        { label: "1/16", val: 16 },
3765        { label: "1/32", val: 32 }
3766      ].map((snap) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3767        "button",
3768        {
3769          className: "w-full text-left px-6 py-1 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center gap-2",
3770          onClick: (e) => {
3771            e.stopPropagation();
3772            setGridSnap(snap.val);
3773            onClose();
3774          },
3775          children: [
3776            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-4", children: gridSnap === snap.val && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }) }),
3777            snap.label
3778          ]
3779        },
3780        snap.val
3781      ))
3782    }
3783  );
3784  const renderStampSubmenu = () => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3785    "div",
3786    {
3787      className: `absolute top-0 ${submenuDirection === "left" ? "right-full mr-1" : "left-full ml-1"} w-48 bg-zinc-100 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 shadow-xl py-1 text-sm text-zinc-800 dark:text-zinc-200 max-h-[80vh] overflow-y-auto`,
3788      onMouseEnter: () => handleMouseEnterSubmenu("stamp"),
3789      onMouseLeave: handleMouseLeaveSubmenu,
3790      children: [
3791        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1 text-xs font-semibold text-zinc-500 uppercase", children: "Chords" }),
3792        [
3793          { label: "None", val: "none" },
3794          { label: "Major", val: "major" },
3795          { label: "Minor", val: "minor" },
3796          { label: "Major 7th", val: "maj7" },
3797          { label: "Minor 7th", val: "min7" },
3798          { label: "Dominant 7th", val: "dom7" },
3799          { label: "Diminished", val: "dim" },
3800          { label: "Augmented", val: "aug" }
3801        ].map((stamp) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3802          "button",
3803          {
3804            disabled: isDrumMode,
3805            className: "w-full text-left px-6 py-1 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center gap-2 disabled:opacity-50",
3806            onClick: (e) => {
3807              e.stopPropagation();
3808              setChordStamp(stamp.val);
3809              onClose();
3810            },
3811            children: [
3812              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-4", children: chordStamp === stamp.val && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }) }),
3813              stamp.label
3814            ]
3815          },
3816          stamp.val
3817        )),
3818        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-300 dark:border-zinc-700 my-1" }),
3819        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1 text-xs font-semibold text-zinc-500 uppercase", children: "Scale Helpers" }),
3820        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 py-1 flex items-center gap-2", children: [
3821          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 text-xs w-8", children: "Root:" }),
3822          /* @__PURE__ */ jsxRuntimeExports.jsxs(
3823            "select",
3824            {
3825              value: scaleRoot === null ? "none" : scaleRoot,
3826              onChange: (e) => setScaleRoot(e.target.value === "none" ? null : Number(e.target.value)),
3827              className: "flex-1 bg-zinc-200 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded px-1 py-0.5 text-xs focus:outline-none",
3828              onClick: (e) => e.stopPropagation(),
3829              children: [
3830                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "Off" }),
3831                NOTE_NAMES.map((n, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: i, children: n }, i))
3832              ]
3833            }
3834          )
3835        ] }),
3836        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 py-1 pb-2 flex items-center gap-2", children: [
3837          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 text-xs w-8", children: "Type:" }),
3838          /* @__PURE__ */ jsxRuntimeExports.jsxs(
3839            "select",
3840            {
3841              value: scaleType,
3842              onChange: (e) => setScaleType(e.target.value),
3843              disabled: scaleRoot === null,
3844              className: "flex-1 bg-zinc-200 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-600 rounded px-1 py-0.5 text-xs focus:outline-none disabled:opacity-50",
3845              onClick: (e) => e.stopPropagation(),
3846              children: [
3847                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "major", children: "Major" }),
3848                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "minor", children: "Minor" }),
3849                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "harmonic_minor", children: "Harmonic Minor" }),
3850                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "melodic_minor", children: "Melodic Minor" }),
3851                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pentatonic_major", children: "Pentatonic Major" }),
3852                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pentatonic_minor", children: "Pentatonic Minor" }),
3853                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "blues", children: "Blues" })
3854              ]
3855            }
3856          )
3857        ] })
3858      ]
3859    }
3860  );
3861  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
3862    "div",
3863    {
3864      ref: menuRef,
3865      className: "fixed z-[100] flex bg-zinc-100 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 shadow-2xl select-none",
3866      style: { left: coords.left, top: coords.top },
3867      onContextMenu: (e) => e.preventDefault(),
3868      children: [
3869        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col bg-zinc-200 dark:bg-zinc-900 border-r border-zinc-300 dark:border-zinc-700 p-1", children: [
3870          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 uppercase font-semibold text-center mb-1", children: "Mode" }),
3871          /* @__PURE__ */ jsxRuntimeExports.jsx(
3872            "button",
3873            {
3874              onClick: (e) => handleToolSelect("draw", e),
3875              className: `p-2 rounded mb-1 ${toolMode === "draw" ? "bg-zinc-300 dark:bg-zinc-700" : "hover:bg-zinc-300 dark:hover:bg-zinc-800"}`,
3876              title: "Draw",
3877              children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3878                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-1.5 h-1.5 rounded-full ${toolMode === "draw" ? "bg-indigo-500" : "bg-transparent"}` }),
3879                /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3880              ] })
3881            }
3882          ),
3883          /* @__PURE__ */ jsxRuntimeExports.jsx(
3884            "button",
3885            {
3886              onClick: (e) => handleToolSelect("paint", e),
3887              className: `p-2 rounded mb-1 ${toolMode === "paint" ? "bg-zinc-300 dark:bg-zinc-700" : "hover:bg-zinc-300 dark:hover:bg-zinc-800"}`,
3888              title: "Paint",
3889              children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3890                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-1.5 h-1.5 rounded-full ${toolMode === "paint" ? "bg-indigo-500" : "bg-transparent"}` }),
3891                /* @__PURE__ */ jsxRuntimeExports.jsx(Brush, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3892              ] })
3893            }
3894          ),
3895          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "p-2 rounded mb-1 opacity-40", title: "Paint (Drum Sequencer Mode) - Not Implemented", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3896            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-transparent" }),
3897            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
3898              /* @__PURE__ */ jsxRuntimeExports.jsx(Brush, { size: 18, className: "text-zinc-700 dark:text-zinc-300" }),
3899              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute -top-1 -right-1 text-[10px] font-bold", children: "+" })
3900            ] })
3901          ] }) }),
3902          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "p-2 rounded mb-1 opacity-40", title: "Delete - Not Implemented", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3903            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-transparent" }),
3904            /* @__PURE__ */ jsxRuntimeExports.jsx(VolumeX, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3905          ] }) }),
3906          /* @__P
3906URE__ */ jsxRuntimeExports.jsx(
3907            "button",
3908            {
3909              onClick: (e) => handleToolSelect("slice", e),
3910              className: `p-2 rounded mb-1 ${toolMode === "slice" ? "bg-zinc-300 dark:bg-zinc-700" : "hover:bg-zinc-300 dark:hover:bg-zinc-800"}`,
3911              title: "Slice",
3912              children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3913                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-1.5 h-1.5 rounded-full ${toolMode === "slice" ? "bg-indigo-500" : "bg-transparent"}` }),
3914                /* @__PURE__ */ jsxRuntimeExports.jsx(Scissors, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3915              ] })
3916            }
3917          ),
3918          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "p-2 rounded mb-1 opacity-40", title: "Select - Not Implemented", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3919            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-transparent" }),
3920            /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointer2, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3921          ] }) }),
3922          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "p-2 rounded mb-1 opacity-40", title: "Zoom - Not Implemented", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3923            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-transparent" }),
3924            /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3925          ] }) }),
3926          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "p-2 rounded opacity-40", title: "Playback - Not Implemented", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3927            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-transparent" }),
3928            /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 18, className: "text-zinc-700 dark:text-zinc-300" })
3929          ] }) })
3930        ] }),
3931        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col py-1 min-w-[220px] text-sm text-zinc-800 dark:text-zinc-200", children: [
3932          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
3933            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3934              /* @__PURE__ */ jsxRuntimeExports.jsx(Folder, { size: 15, className: "text-zinc-400" }),
3935              "File"
3936            ] }),
3937            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
3938          ] }),
3939          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
3940            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3941              /* @__PURE__ */ jsxRuntimeExports.jsx(SquarePen, { size: 15, className: "text-zinc-400" }),
3942              "Edit"
3943            ] }),
3944            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
3945          ] }),
3946          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
3947            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3948              /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { size: 15, className: "text-zinc-400" }),
3949              "Tools"
3950            ] }),
3951            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
3952          ] }),
3953          /* @__PURE__ */ jsxRuntimeExports.jsxs(
3954            "div",
3955            {
3956              className: "relative w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between cursor-pointer",
3957              onMouseEnter: () => handleMouseEnterSubmenu("stamp"),
3958              onMouseLeave: handleMouseLeaveSubmenu,
3959              children: [
3960                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3961                  /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 15, className: "text-indigo-400" }),
3962                  "Stamp"
3963                ] }),
3964                /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 }),
3965                activeSubmenu === "stamp" && renderStampSubmenu()
3966              ]
3967            }
3968          ),
3969          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
3970            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3971              /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 15, className: "text-zinc-400" }),
3972              "View"
3973            ] }),
3974            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
3975          ] }),
3976          /* @__PURE__ */ jsxRuntimeExports.jsxs(
3977            "div",
3978            {
3979              className: "relative w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between cursor-pointer",
3980              onMouseEnter: () => handleMouseEnterSubmenu("snap"),
3981              onMouseLeave: handleMouseLeaveSubmenu,
3982              children: [
3983                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3984                  /* @__PURE__ */ jsxRuntimeExports.jsx(Magnet, { size: 15, className: "text-indigo-400" }),
3985                  "Snap"
3986                ] }),
3987                /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 }),
3988                activeSubmenu === "snap" && renderSnapSubmenu()
3989              ]
3990            }
3991          ),
3992          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
3993            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
3994              /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointer2, { size: 15, className: "text-zinc-400" }),
3995              "Select"
3996            ] }),
3997            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
3998          ] }),
3999          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
4000            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4001              /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 15, className: "text-zinc-400" }),
4002              "Group"
4003            ] }),
4004            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
4005          ] }),
4006          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
4007            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4008              /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 15, className: "text-zinc-400" }),
4009              "Zoom"
4010            ] }),
4011            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
4012          ] }),
4013          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
4014            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4015              /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 15, className: "text-zinc-400" }),
4016              "Time markers"
4017            ] }),
4018            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
4019          ] }),
4020          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-300 dark:border-zinc-700 my-1" }),
4021          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
4022            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4023              /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 15, className: "text-zinc-400" }),
4024              "Target channel"
4025            ] }),
4026            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
4027          ] }),
4028          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
4029            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4030              /* @__PURE__ */ jsxRuntimeExports.jsx(SlidersVertical, { size: 15, className: "text-zinc-400" }),
4031              "Target control"
4032            ] }),
4033            /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
4034          ] }),
4035          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-300 dark:border-zinc-700 my-1" }),
4036          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4037            /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }),
4038            " Auto smoothing"
4039          ] }) }),
4040          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4041            /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }),
4042            " Preview notes during playback"
4043          ] }) }),
4044          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "w-full text-left px-10 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 opacity-50", children: "Collapse all grouped tracks" }),
4045          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: true, className: "w-full text-left px-10 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: [
4046            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Center" }),
4047            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 text-xs italic", children: "Shift+0" })
4048          ] }),
4049          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-300 dark:border-zinc-700 my-1" }),
4050          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { disabled: true, className: "w-full text-left px-4 py-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-700 flex items-center justify-between opacity-50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
4051            /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 14 }),
4052            /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 15, className: "text-zinc-400 ml-1 inline" }),
4053            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1", children: "Detached" })
4054          ] }) })
4055        ] })
4056      ]
4057    }
4058  );
4059};
4060const MIN_NOTE = 0;
4061const MAX_NOTE = 127;
4062const KEY_HEIGHT = 16;
4063const BEAT_WIDTH = 40;
4064const isBlackKey = (midiNote) => {
4065  const noteClass = midiNote % 12;
4066  return [1, 3, 6, 8, 10].includes(noteClass);
4067};
4068const noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
4069const getNoteName = (midiNote) => {
4070  const octave = Math.floor(midiNote / 12) - 1;
4071  const name = noteNames[midiNote % 12];
4072  return `${name}${octave}`;
4073};
4074const formatTicksToFlTime = (ticks, ppqVal = 96) => {
4075  const beatsPerBar = 4;
4076  const ticksPerBeat = ppqVal;
4077  const ticksPerStep = ppqVal / 4;
4078  const ticksPerBar = beatsPerBar * ticksPerBeat;
4079  const bar = Math.floor(ticks / ticksPerBar) + 1;
4080  const remainingTicksInBar = ticks % ticksPerBar;
4081  const step = Math.floor(remainingTicksInBar / ticksPerStep) + 1;
4082  const tick = Math.floor(remainingTicksInBar % ticksPerStep);
4083  const stepStr = String(step).padStart(2, "0");
4084  const tickStr = String(tick).padStart(2, "0");
4085  return `${bar}:${stepStr}:${tickStr}`;
4086};
4087const formatDurationTicksToFlTime = (ticks, ppqVal = 96) => {
4088  const beatsPerBar = 4;
4089  const ticksPerBeat = ppqVal;
4090  const ticksPerStep = ppqVal / 4;
4091  const ticksPerBar = beatsPerBar * ticksPerBeat;
4092  const bar = Math.floor(ticks / ticksPerBar);
4093  const remainingTicksInBar = ticks % ticksPerBar;
4094  const step = Math.floor(remainingTicksInBar / ticksPerStep);
4095  const tick = Math.floor(remainingTicksInBar % ticksPerStep);
4096  const stepStr = String(step).padStart(2, "0");
4097  const tickStr = String(tick).padStart(2, "0");
4098  return `${bar}:${stepStr}:${tickStr}`;
4099};
4100const PianoRoll = () => {
4101  var _a, _b;
4102  const { engine, activeTrackIndex, setActiveTrackIndex, setActivePatternId, midiVersion, activePattern, addNote, addNotes, updateNote, updateNotes, deleteNote, forceSync, selectedNoteIds, setSelectedNoteIds, deleteSelection, splitNote, setHint, currentTime, loopMarkerTick, setLoopMarkerTick, playbackMode, playlistClips, activePatternId, isPlaying, patterns, activeMidiKeys } = useEditor();
4103  const [zoomX, setZoomX] = reactExports.useState(1);
4104  const [gridSnap, setGridSnap] = reactExports.useState(16);
4105  const containerRef = reactExports.useRef(null);
4106  const gridRef = reactExports.useRef(null);
4107  const keyboardRef = reactExports.useRef(null);
4108  const [metaModal, setMetaModal] = reactExports.useState(null);
4109  const [metaType, setMetaType] = reactExports.useState("tempo");
4110  const [metaBpm, setMetaBpm] = reactExports.useState("120");
4111  const [metaNum, setMetaNum] = reactExports.useState("4");
4112  const [metaDen, setMetaDen] = reactExports.useState("4");
4113  const [draggingNoteIdx, setDraggingNoteIdx] = reactExports.useState(null);
4114  const [dragStartPos, setDragStartPos] = reactExports.useState(null);
4115  const [dragStartProps, setDragStartProps] = reactExports.useState(null);
4116  const [dragType, setDragType] = reactExports.useState(null);
4117  const [isShiftDrag, setIsShiftDrag] = reactExports.useState(false);
4118  const [dragConstraint, setDragConstraint] = reactExports.useState("none");
4119  const dragSelectedNotesRef = reactExports.useRef([]);
4120  const [draggingMeta, setDraggingMeta] = reactExports.useState(null);
4121  const [metaDragTicks, setMetaDragTicks] = reactExports.useState(null);
4122  const [lassoStart, setLassoStart] = reactExports.useState(null);
4123  const [lassoCurrent, setLassoCurrent] = reactExports.useState(null);
4124  const [contextMenuPos, setContextMenuPos] = reactExports.useState(null);
4125  const draggedRef = reactExports.useRef(false);
4126  const [sliceStart, setSliceStart] = reactExports.useState(null);
4127  const [sliceCurrent, setSliceCurrent] = reactExports.useState(null);
4128  const [panStart, setPanStart] = reactExports.useState(null);
4129  const [showHelp, setShowHelp] = reactExports.useState(false);
4130  const [toolMode, setToolMode] = reactExports.useState("draw");
4131  const [isPainting, setIsPainting] = reactExports.useState(false);
4132  const [lastPaintedTick, setLastPaintedTick] = reactExports.useState(null);
4133  const auditionPitchRef = reactExports.useRef(null);
4134  const lastNoteDurationRef = reactExports.useRef(null);
4135  const playheadElRef = reactExports.useRef(null);
4136  const currentTimeRef = reactExports.useRef(currentTime);
4137  reactExports.useEffect(() => {
4138    currentTimeRef.current = currentTime;
4139  }, [currentTime]);
4140  const [activePreviewMidi, setActivePreviewMidi] = reactExports.useState(null);
4141  const [gridPreviewMidi, setGridPreviewMidi] = reactExports.useState(null);
4142  const bottomPaneRef = reactExports.useRef(null);
4143  const [bottomPaneMode, setBottomPaneMode] = reactExports.useState("velocity");
4144  const [draggingVelocityIdx, setDraggingVelocityIdx] = reactExports.useState(null);
4145  const dragStartValRef = reactExports.useRef(null);
4146  const dragSelectedNoteValsRef = reactExports.useRef([]);
4147  const [activeVoice, setActiveVoice] = reactExports.useState(1);
4148  const [showShortcutsModal, setShowShortcutsModal] = reactExports.useState(false);
4149  const [showGhostNotes, setShowGhostNotes] = reactExports.useState(true);
4150  const [ghostTrackVisibility, setGhostTrackVisibility] = reactExports.useState({});
4151  const [showLayersDropdown, setShowLayersDropdown] = reactExports.useState(false);
4152  const layersDropdownRef = reactExports.useRef(null);
4153  const [scaleRoot, setScaleRoot] = reactExports.useState(null);
4154  const [scaleType, setScaleType] = reactExports.useState("major");
4155  const SCALES = {
4156    "major": [0, 2, 4, 5, 7, 9, 11],
4157    "minor": [0, 2, 3, 5, 7, 8, 10],
4158    "harmonic_minor": [0, 2, 3, 5, 7, 8, 11],
4159    "melodic_minor": [0, 2, 3, 5, 7, 9, 11],
4160    "pentatonic_major": [0, 2, 4, 7, 9],
4161    "pentatonic_minor": [0, 3, 5, 7, 10],
4162    "blues": [0, 3, 5, 6, 7, 10]
4163  };
4164  const [chordStamp, setChordStamp] = reactExports.useState("none");
4165  const CHORDS = {
4166    "major": [0, 4, 7],
4167    "minor": [0, 3, 7],
4168    "maj7": [0, 4, 7, 11],
4169    "min7": [0, 3, 7, 10],
4170    "dom7": [0, 4, 7, 10],
4171    "dim": [0, 3, 6],
4172    "aug": [0, 4, 8]
4173  };
4174  const [isToolbarFloating, setIsToolbarFloating] = reactExports.useState(false);
4175  const [toolbarPos, setToolbarPos] = reactExports.useState({ x: 300, y: 16 });
4176  const [isDraggingToolbar, setIsDraggingToolbar] = reactExports.useState(false);
4177  const [toolbarDragStart, setToolbarDragStart] = reactExports.useState({ x: 0, y: 0, startPosX: 0, startPosY: 0 });
4178  const handleToolbarPointerDown = (e) => {
4179    if (!isToolbarFloating) return;
4180    if (e.target.closest("button, select, input")) return;
4181    e.stopPropagation();
4182    e.currentTarget.setPointerCapture(e.pointerId);
4183    setIsDraggingToolbar(true);
4184    setToolbarDragStart({
4185      x: e.clientX,
4186      y: e.clientY,
4187      startPosX: toolbarPos.x,
4188      startPosY: toolbarPos.y
4189    });
4190  };
4191  const handleToolbarPointerMove = (e) => {
4192    if (!isDraggingToolbar) return;
4193    const dx = e.clientX - toolbarDragStart.x;
4194    const dy = e.clientY - toolbarDragStart.y;
4195    setToolbarPos({
4196      x: Math.max(0, toolbarDragStart.startPosX + dx),
4197      y: Math.max(0, toolbarDragStart.startPosY + dy)
4198    });
4199  };
4200  const handleToolbarPointerUp = (e) => {
4201    if (!isDraggingToolbar) return;
4202    setIsDraggingToolbar(false);
4203    e.currentTarget.releasePointerCapture(e.pointerId);
4204  };
4205  reactExports.useEffect(() => {
4206    if (containerRef.current && activeTrackIndex !== null && engine && engine.midi) {
4207      const tr = engine.midi.tracks[activeTrackIndex];
4208      const isDrum = tr.channel === 9;
4209      const maxN = isDrum ? 81 : MAX_NOTE;
4210      const targetNote = isDrum ? 38 : 60;
4211      const targetIndex = maxN - targetNote;
4212      if (targetIndex >= 0) {
4213        containerRef.current.scrollTop = targetIndex * KEY_HEIGHT - containerRef.current.clientHeight / 2;
4214      }
4215    }
4216  }, [activeTrackIndex, engine]);
4217  reactExports.useEffect(() => {
4218    const handleClickOutside = (e) => {
4219      if (layersDropdownRef.current && !layersDropdownRef.current.contains(e.target)) {
4220        setShowLayersDropdown(false);
4221      }
4222    };
4223    document.addEventListener("mousedown", handleClickOutside);
4224    return () => document.removeEventListener("mousedown", handleClickOutside);
4225  }, []);
4226  if (!engine || !engine.midi || activeTrackIndex === null) {
4227    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-zinc-500 h-full flex items-center justify-center", children: "Select a track to edit in the Piano Roll." });
4228  }
4229  const track = engine.midi.tracks[activeTrackIndex];
4230  const notes = activePattern ? activePattern.notes : [];
4231  const isDrumMode = track.channel === 9;
4232  const getNotePropertyValue = (n, mode) => {
4233    switch (mode) {
4234      case "velocity":
4235        return n.velocity ?? 0.78;
4236      case "release":
4237        return n.release ?? 0.5;
4238      case "pan":
4239        return n.pan ?? 0.5;
4240      case "pitch":
4241        return n.pitch ?? 0.5;
4242      case "modX":
4243        return n.modX ?? 0.5;
4244      case "modY":
4245        return n.modY ?? 0.5;
4246      case "shift":
4247        return n.shift ?? 0.5;
4248      default:
4249        return 0.5;
4250    }
4251  };
4252  const getPropertyUpdateObject = (mode, val) => {
4253    switch (mode) {
4254      case "velocity":
4255        return { velocity: val };
4256      case "release":
4257        return { release: val };
4258      case "pan":
4259        return { pan: val };
4260      case "pitch":
4261        return { pitch: val };
4262      case "modX":
4263        return { modX: val };
4264      case "modY":
4265        return { modY: val };
4266      case "shift":
4267        return { shift: val };
4268      default:
4269        return {};
4270    }
4271  };
4272  const formatTooltipValue = (mode, val) => {
4273    switch (mode) {
4274      case "velocity":
4275        return `Velocity: ${Math.round(val * 100)}%`;
4276      case "release":
4277        return `Release: ${Math.round(val * 100)}%`;
4278      case "pan": {
4279        if (Math.abs(val - 0.5) < 0.02) return "Panning: Center";
4280        return val < 0.5 ? `Panning: ${Math.round((0.5 - val) * 200)}
4280% Left` : `Panning: ${Math.round((val - 0.5) * 200)}% Right`;
4281      }
4282      case "pitch": {
4283        const cents = Math.round((val - 0.5) * 2400);
4284        if (cents === 0) return "Fine Pitch: Center";
4285        return cents > 0 ? `Fine Pitch: +${cents} cents` : `Fine Pitch: ${cents} cents`;
4286      }
4287      case "modX":
4288        return `Filter Cutoff (Mod X): ${Math.round(val * 100)}%`;
4289      case "modY":
4290        return `Filter Resonance (Mod Y): ${Math.round(val * 100)}%`;
4291      case "shift": {
4292        const ticksOffset = Math.round((val - 0.5) * 480);
4293        if (ticksOffset === 0) return "Start Time Shift: None";
4294        return ticksOffset > 0 ? `Start Time Shift: +${ticksOffset} ticks` : `Start Time Shift: ${ticksOffset} ticks`;
4295      }
4296      default:
4297        return "";
4298    }
4299  };
4300  const findNoteAtX = (x) => {
4301    let closestIdx = -1;
4302    let minDistance = Infinity;
4303    notes.forEach((n, idx) => {
4304      const left = n.ticks * pxPerTick;
4305      const w = Math.max(n.durationTicks * pxPerTick, 4);
4306      const right = left + w;
4307      if (x >= left && x <= right) {
4308        closestIdx = idx;
4309        minDistance = 0;
4310      } else if (minDistance > 0) {
4311        const dist = Math.abs(x - left);
4312        if (dist < minDistance) {
4313          minDistance = dist;
4314          closestIdx = idx;
4315        }
4316      }
4317    });
4318    return minDistance < 50 ? closestIdx : -1;
4319  };
4320  const activePlaybackMidis = React.useMemo(() => {
4321    if (!engine) return /* @__PURE__ */ new Set();
4322    const activeMidis = /* @__PURE__ */ new Set();
4323    if (engine.isPlaying()) {
4324      const currentTicks2 = engine.secondsToTicks(currentTime);
4325      let patternLength2 = 0;
4326      if (activePattern) {
4327        const maxNoteTick = activePattern.notes.reduce((max, n) => Math.max(max, n.ticks + n.durationTicks), 0);
4328        const ppq2 = engine.midi.header.ppq || 96;
4329        const ticksPerBar = ppq2 * 4;
4330        patternLength2 = activePattern.durationTicks || Math.max(ticksPerBar * 4, Math.ceil(maxNoteTick / ticksPerBar) * ticksPerBar);
4331      }
4332      let showPlayhead2 = false;
4333      let playheadTick2 = 0;
4334      if (playbackMode === "pat") {
4335        showPlayhead2 = true;
4336        playheadTick2 = patternLength2 > 0 ? currentTicks2 % patternLength2 : currentTicks2;
4337      } else {
4338        const playingClip = playlistClips.find(
4339          (clip) => clip.patternId === activePatternId && !clip.isMuted && currentTicks2 >= clip.startTick && currentTicks2 < clip.startTick + clip.durationTicks
4340        );
4341        if (playingClip) {
4342          showPlayhead2 = true;
4343          playheadTick2 = patternLength2 > 0 ? (currentTicks2 - playingClip.startTick + playingClip.offsetTicks) % patternLength2 : currentTicks2 - playingClip.startTick + playingClip.offsetTicks;
4344        }
4345      }
4346      if (showPlayhead2) {
4347        notes.forEach((note) => {
4348          if (playheadTick2 >= note.ticks && playheadTick2 < note.ticks + note.durationTicks) {
4349            activeMidis.add(note.midi);
4350          }
4351        });
4352      }
4353      console.log(`[Keyboard Debug] currentTime: ${currentTime.toFixed(3)}, currentTicks: ${currentTicks2}, playheadTick: ${playheadTick2}, activeMidis:`, Array.from(activeMidis));
4354    }
4355    if (track && track.channel !== void 0) {
4356      try {
4357        const liveNotes = engine.getActiveNotes(track.channel);
4358        liveNotes.forEach((m) => activeMidis.add(m));
4359        if (liveNotes.size > 0) {
4360          console.log(`[Keyboard Debug] Live synthesizer notes on channel ${track.channel}:`, Array.from(liveNotes));
4361        }
4362      } catch (e) {
4363      }
4364    }
4365    return activeMidis;
4366  }, [currentTime, engine, notes, track, playbackMode, playlistClips, activePatternId, activePattern]);
4367  const isNoteActive = (m) => {
4368    if (activePreviewMidi === m) return true;
4369    if (gridPreviewMidi === m) return true;
4370    if (activePlaybackMidis.has(m)) return true;
4371    if (activeMidiKeys == null ? void 0 : activeMidiKeys.has(m)) return true;
4372    return false;
4373  };
4374  const getWhiteKeyBg = (m) => {
4375    if (isNoteActive(m)) {
4376      return "linear-gradient(to right, #f97316 0%, #ffa043 30%, #ffb066 100%)";
4377    }
4378    let inScale = false;
4379    if (scaleRoot !== null) {
4380      const noteClass = m % 12;
4381      const intervals = SCALES[scaleType] || [];
4382      inScale = intervals.some((interval) => (scaleRoot + interval) % 12 === noteClass);
4383    }
4384    if (m % 12 === 0) {
4385      return inScale ? "#dbeafe" : "#e3e4e6";
4386    } else {
4387      return inScale ? "linear-gradient(to right, #dbeafe 0%, #eff6ff 35%, #f8fafc 100%)" : "linear-gradient(to right, #e2e2e2 0%, #f2f2f2 35%, #ffffff 100%)";
4388    }
4389  };
4390  const getBlackKeyBg = (m) => {
4391    if (isNoteActive(m)) {
4392      return "linear-gradient(to right, #d35400 0%, #e67e22 80%, #ffb066 100%)";
4393    }
4394    return "linear-gradient(to right, #242424 0%, #363636 80%, #4c4c4c 100%)";
4395  };
4396  const handleKeyMouseDown = (e, m) => {
4397    if (!engine) return;
4398    const rect = e.currentTarget.getBoundingClientRect();
4399    const x = e.clientX - rect.left;
4400    const width = rect.width;
4401    const isBlack = !isDrumMode && isBlackKey(m);
4402    let playMidi = m;
4403    if (isBlack && x > width * 0.6) {
4404      const y = e.clientY - rect.top;
4405      if (y < rect.height / 2) {
4406        playMidi = m + 1;
4407      } else {
4408        playMidi = m - 1;
4409      }
4410    }
4411    setActivePreviewMidi(playMidi);
4412    engine.startNotePreview(playMidi, track.channel);
4413  };
4414  const handleKeyMouseUp = (e, m) => {
4415    if (activePreviewMidi !== null && engine) {
4416      engine.stopNotePreview(activePreviewMidi, track.channel);
4417    }
4418    setActivePreviewMidi(null);
4419  };
4420  const handleKeyMouseMove = (e, m) => {
4421    const rect = e.currentTarget.getBoundingClientRect();
4422    const x = e.clientX - rect.left;
4423    const width = rect.width;
4424    const isBlack = !isDrumMode && isBlackKey(m);
4425    let hoverMidi = m;
4426    if (isBlack && x > width * 0.6) {
4427      const y = e.clientY - rect.top;
4428      if (y < rect.height / 2) {
4429        hoverMidi = m + 1;
4430      } else {
4431        hoverMidi = m - 1;
4432      }
4433    }
4434    if (e.buttons === 1 && hoverMidi !== activePreviewMidi && engine) {
4435      if (activePreviewMidi !== null) {
4436        engine.stopNotePreview(activePreviewMidi, track.channel);
4437      }
4438      setActivePreviewMidi(hoverMidi);
4439      engine.startNotePreview(hoverMidi, track.channel);
4440    }
4441    const noteNameStr = isDrumMode ? GM_PERCUSSION_MAP[hoverMidi] || `Drum ${hoverMidi}` : getNoteName(hoverMidi);
4442    setHint({
4443      label: isDrumMode ? "Percussion Key" : "Piano Keyboard",
4444      positionText: "",
4445      noteText: `${noteNameStr} / ${hoverMidi}`,
4446      iconType: "keyboard"
4447    });
4448  };
4449  const handleKeyMouseLeave = (e, m) => {
4450    if (activePreviewMidi !== null && engine) {
4451      engine.stopNotePreview(activePreviewMidi, track.channel);
4452    }
4453    setActivePreviewMidi(null);
4454    setHint(null);
4455  };
4456  const handleKeyMouseEnter = (e, m) => {
4457    if (e.buttons === 1 && engine) {
4458      const rect = e.currentTarget.getBoundingClientRect();
4459      const x = e.clientX - rect.left;
4460      const width = rect.width;
4461      const isBlack = !isDrumMode && isBlackKey(m);
4462      let playMidi = m;
4463      if (isBlack && x > width * 0.6) {
4464        const y = e.clientY - rect.top;
4465        if (y < rect.height / 2) {
4466          playMidi = m + 1;
4467        } else {
4468          playMidi = m - 1;
4469        }
4470      }
4471      setActivePreviewMidi(playMidi);
4472      engine.startNotePreview(playMidi, track.channel);
4473    }
4474  };
4475  const ppq = engine.midi.header.ppq;
4476  const maxTicks = engine.midi.durationTicks + ppq * 4 * 8;
4477  const pxPerTick = BEAT_WIDTH * zoomX / ppq;
4478  const totalWidth = maxTicks * pxPerTick;
4479  const actualMinNote = isDrumMode ? 35 : MIN_NOTE;
4480  const actualMaxNote = isDrumMode ? 81 : MAX_NOTE;
4481  const actualNumKeys = actualMaxNote - actualMinNote + 1;
4482  const handleMouseMove = (e) => {
4483    if (isPainting && toolMode === "paint" && gridRef.current) {
4484      const rect = gridRef.current.getBoundingClientRect();
4485      const x = e.clientX - rect.left;
4486      const y = e.clientY - rect.top;
4487      const snapTicks2 = gridSnap === 0 ? 1 : ppq * (4 / gridSnap);
4488      const rawTicks = Math.max(0, Math.round(x / pxPerTick));
4489      const ticks = gridSnap === 0 ? rawTicks : Math.round(rawTicks / snapTicks2) * snapTicks2;
4490      const pitchIdx = Math.floor(y / KEY_HEIGHT);
4491      const midiNote = actualMaxNote - pitchIdx;
4492      if (midiNote >= actualMinNote && midiNote <= actualMaxNote) {
4493        if (lastPaintedTick !== ticks) {
4494          const defaultDuration = gridSnap === 0 ? ppq : snapTicks2;
4495          const existingNote = notes.find((n) => n.midi === midiNote && n.ticks === ticks);
4496          if (!existingNote) {
4497            addNote(activeTrackIndex, midiNote, ticks, defaultDuration, 0.8, "pianoroll", activeVoice);
4498            setLastPaintedTick(ticks);
4499            if (engine) {
4500              engine.startNotePreview(midiNote, track.channel);
4501              setGridPreviewMidi(midiNote);
4502              setTimeout(() => {
4503                engine.stopNotePreview(midiNote, track.channel);
4504                setGridPreviewMidi((prev) => prev === midiNote ? null : prev);
4505              }, 100);
4506            }
4507          }
4508        }
4509      }
4510      return;
4511    }
4512    if (panStart && containerRef.current) {
4513      const dx2 = e.clientX - panStart.x;
4514      const dy2 = e.clientY - panStart.y;
4515      containerRef.current.scrollLeft = panStart.scrollX - dx2;
4516      containerRef.current.scrollTop = panStart.scrollY - dy2;
4517      return;
4518    }
4519    if (draggingMeta && dragStartPos) {
4520      const dx2 = e.clientX - dragStartPos.x;
4521      const dticks2 = Math.round(dx2 / pxPerTick);
4522      const newTicks = Math.max(0, draggingMeta.originalTick + dticks2);
4523      setMetaDragTicks(newTicks);
4524      return;
4525    }
4526    if (lassoStart && gridRef.current && containerRef.current) {
4527      draggedRef.current = true;
4528      const rect = gridRef.current.getBoundingClientRect();
4529      const x = e.clientX - rect.left + (containerRef.current.scrollLeft - lassoStart.scrollX);
4530      const y = e.clientY - rect.top + (containerRef.current.scrollTop - lassoStart.scrollY);
4531      setLassoCurrent({ x, y });
4532      return;
4533    }
4534    if (sliceStart && gridRef.current && containerRef.current) {
4535      draggedRef.current = true;
4536      const rect = gridRef.current.getBoundingClientRect();
4537      const x = e.clientX - rect.left + (containerRef.current.scrollLeft - sliceStart.scrollX);
4538      const y = e.clientY - rect.top + (containerRef.current.scrollTop - sliceStart.scrollY);
4539      setSliceCurrent({ x, y });
4540      return;
4541    }
4542    if (draggingNoteIdx === null || !dragStartPos || !dragStartProps || !gridRef.current) {
4543      if (gridRef.current) {
4544        const rect = gridRef.current.getBoundingClientRect();
4545        const x = e.clientX - rect.left;
4546        const y = e.clientY - rect.top;
4547        const rawTicks = Math.max(0, x / pxPerTick);
4548        const snapTicks2 = gridSnap === 0 ? 1 : ppq * (4 / gridSnap);
4549        const ticks = gridSnap === 0 ? Math.round(rawTicks) : Math.round(rawTicks / snapTicks2) * snapTicks2;
4550        const pitchIdx = Math.floor(y / KEY_HEIGHT);
4551        const midiNote = actualMaxNote - pitchIdx;
4552        if (midiNote >= actualMinNote && midiNote <= actualMaxNote) {
4553          const noteNameStr = isDrumMode ? GM_PERCUSSION_MAP[midiNote] || `Drum ${midiNote}` : getNoteName(midiNote);
4554          const posStr = formatTicksToFlTime(ticks, ppq);
4555          setHint({
4556            label: isDrumMode ? "Percussion Grid" : `Draw Notes: ${toolMode}`,
4557            positionText: posStr,
4558            noteText: `${noteNameStr} / ${midiNote}`,
4559            iconType: toolMode
4560          });
4561        }
4562      }
4563      return;
4564    }
4565    draggedRef.current = true;
4566    const dx = e.clientX - dragStartPos.x;
4567    const dy = e.clientY - dragStartPos.y;
4568    let dticks = Math.round(dx / pxPerTick);
4569    let dPitch = -Math.round(dy / KEY_HEIGHT);
4570    if (isShiftDrag) {
4571      let activeConstraint = dragConstraint;
4572      if (activeConstraint === "none") {
4573        if (Math.abs(dx) > 10 || Math.abs(dy) > 10) {
4574          if (Math.abs(dx) >= Math.abs(dy)) {
4575            activeConstraint = "horizontal";
4576            setDragConstraint("horizontal");
4577          } else {
4578            activeConstraint = "vertical";
4579            setDragConstraint("vertical");
4580          }
4581        }
4582      }
4583      if (activeConstraint === "horizontal") {
4584        dPitch = 0;
4585      } else if (activeConstraint === "vertical") {
4586        dticks = 0;
4587      }
4588    }
4589    const snapTicks = gridSnap === 0 ? 1 : ppq * (4 / gridSnap);
4590    if (dragType === "move") {
4591      const updates = dragSelectedNotesRef.current.map((item) => {
4592        const rawTicks = item.ticks + dticks;
4593        const newTicks = Math.max(0, gridSnap === 0 ? rawTicks : Math.round(rawTicks / snapTicks) * snapTicks);
4594        const newPitch = Math.max(actualMinNote, Math.min(actualMaxNote, item.midi + dPitch));
4595        if (item.idx === draggingNoteIdx && newPitch !== auditionPitchRef.current) {
4596          if (auditionPitchRef.current !== null && engine) {
4597            engine.stopNotePreview(auditionPitchRef.current, track.channel);
4598          }
4599          if (engine) {
4600            engine.startNotePreview(newPitch, track.channel);
4601            setGridPreviewMidi(newPitch);
4602          }
4603          auditionPitchRef.current = newPitch;
4604        }
4605        return {
4606          noteIndex: item.idx,
4607          newProps: {
4608            ticks: newTicks,
4609            midi: newPitch
4610          }
4611        };
4612      });
4613      updateNotes(activeTrackIndex, updates, "pianoroll");
4614    } else if (dragType === "resize-right") {
4615      const rawDuration = dragStartProps.durationTicks + dticks;
4616      const snappedDuration = gridSnap === 0 ? rawDuration : Math.round(rawDuration / snapTicks) * snapTicks;
4617      const newDuration = Math.max(1, snappedDuration);
4618      updateNote(activeTrackIndex, draggingNoteIdx, { durationTicks: newDuration }, "pianoroll");
4619    } else if (dragType === "resize-left") {
4620      const rawTicks = dragStartProps.ticks + dticks;
4621      const snappedTicks = gridSnap === 0 ? rawTicks : Math.round(rawTicks / snapTicks) * snapTicks;
4622      const newTicks = Math.max(0, snappedTicks);
4623      const originalEndTicks = dragStartProps.ticks + dragStartProps.durationTicks;
4624      const newDuration = Math.max(1, originalEndTicks - newTicks);
4625      if (originalEndTicks - newTicks > 0) {
4626        updateNote(activeTrackIndex, draggingNoteIdx, { ticks: newTicks, durationTicks: newDuration }, "pianoroll");
4627      }
4628    }
4629  };
4630  const handleMouseUp = (e) => {
4631    if (draggingMeta && metaDragTicks !== null) {
4632      if (draggingMeta.type === "tempo") {
4633        engine.updateTempo(draggingMeta.originalTick, metaDragTicks, draggingMeta.bpm);
4634      } else {
4635        engine.updateTimeSignature(draggingMeta.originalTick, metaDragTicks, draggingMeta.num, draggingMeta.den);
4636      }
4637      forceSync();
4638    }
4639    if (auditionPitchRef.current !== null && engine) {
4640      engine.stopNotePreview(auditionPitchRef.current, track.channel);
4641      auditionPitchRef.current = null;
4642    }
4643    setGridPreviewMidi(null);
4644    if (draggingNoteIdx !== null && (dragType === "resize-left" || dragType === "resize-right")) {
4645      const resizedNote = notes[draggingNoteIdx];
4646      if (resizedNote) {
4647        lastNoteDurationRef.current = resizedNote.durationTicks;
4648      }
4649    }
4650    if (lassoStart && lassoCurrent && engine && engine.midi) {
4651      const x1 = Math.min(lassoStart.x, lassoCurrent.x);
4652      const x2 = Math.max(lassoStart.x, lassoCurrent.x);
4653      const y1 = Math.min(lassoStart.y, lassoCurrent.y);
4654      const y2 = Math.max(lassoStart.y, lassoCurrent.y);
4655      engine.midi.tracks[activeTrackIndex];
4656      const notes2 = activePattern ? activePattern.notes : [];
4657      const newSelectedIds = [];
4658      notes2.forEach((note, idx) => {
4659        const noteLeft = note.ticks * pxPerTick;
4660        const noteRight = noteLeft + note.durationTicks * pxPerTick;
4661        const noteTop = (actualMaxNote - note.midi) * KEY_HEIGHT;
4662        const noteBottom = noteTop + KEY_HEIGHT;
4663        if (noteRight >= x1 && noteLeft <= x2 && noteBottom >= y1 && noteTop <= y2) {
4664          newSelectedIds.push(idx);
4665        }
4666      });
4667      if (e == null ? void 0 : e.shiftKey) {
4668        const combined = Array.from(/* @__PURE__ */ new Set([...selectedNoteIds, ...newSelectedIds]));
4669        setSelectedNoteIds(combined);
4670      } else {
4671        setSelectedNoteIds(newSelectedIds);
4672      }
4673    }
4674    if (sliceStart && sliceCurrent && engine && engine.midi) {
4675      engine.midi.tracks[activeTrackIndex];
4676      const notes2 = activePattern ? activePattern.notes : [];
4677      const x1 = sliceStart.x;
4678      const y1 = sliceStart.y;
4679      const x2 = sliceCurrent.x;
4680      const y2 = sliceCurrent.y;
4681      const splitsToApply = [];
4682      notes2.forEach((note, idx) => {
4683        const noteLeft = note.ticks * pxPerTick;
4684        const noteRight = noteLeft + note.durationTicks * pxPerTick;
4685        const noteTop = (actualMaxNote - note.midi) * KEY_HEIGHT;
4686        const noteCenterY = noteTop + KEY_HEIGHT / 2;
4687        const minY = Math.min(y1, y2);
4688        const maxY = Math.max(y1, y2);
4689        if (noteCenterY >= minY && noteCenterY <= Math.max(maxY, minY + 1)) {
4690          let intersectX;
4691          if (y1 === y2) {
4692            intersectX = x1;
4693          } else {
4694            intersectX = x1 + (noteCenterY - y1) * (x2 - x1) / (y2 - y1);
4695          }
4696          if (intersectX > noteLeft && intersectX < noteRight) {
4697            const splitTick = Math.round(intersectX / pxPerTick);
4698            splitsToApply.push({ noteIdx: idx, splitTick });
4699          }
4700        }
4701      });
4702      splitsToApply.sort((a, b) =>
4702 b.noteIdx - a.noteIdx).forEach((split) => {
4703        splitNote(activeTrackIndex, split.noteIdx, split.splitTick, "pianoroll");
4704      });
4705    }
4706    setLassoStart(null);
4707    setLassoCurrent(null);
4708    setSliceStart(null);
4709    setSliceCurrent(null);
4710    setTimeout(() => {
4711      draggedRef.current = false;
4712    }, 50);
4713    setDraggingNoteIdx(null);
4714    setDragStartPos(null);
4715    setDragStartProps(null);
4716    setDragType(null);
4717    setDraggingMeta(null);
4718    setMetaDragTicks(null);
4719    setIsPainting(false);
4720    setLastPaintedTick(null);
4721    setPanStart(null);
4722  };
4723  const keys = [];
4724  for (let midi = actualMaxNote; midi >= actualMinNote; midi--) {
4725    const isBlack = !isDrumMode && isBlackKey(midi);
4726    const name = isDrumMode ? GM_PERCUSSION_MAP[midi] || `Drum ${midi}` : getNoteName(midi);
4727    const octave = Math.floor(midi / 12) - 1;
4728    let isInScale = false;
4729    if (!isDrumMode && scaleRoot !== null) {
4730      const noteClass = midi % 12;
4731      const intervals = SCALES[scaleType] || [];
4732      isInScale = intervals.some((interval) => (scaleRoot + interval) % 12 === noteClass);
4733    }
4734    if (isDrumMode) {
4735      const bgClass = isInScale ? "bg-indigo-100 dark:bg-indigo-900/30" : "bg-white dark:bg-zinc-800";
4736      const textClass = isNoteActive(midi) ? "text-white font-bold" : isInScale ? "text-indigo-800 dark:text-indigo-300" : "text-zinc-600 dark:text-zinc-400";
4737      const activeBg = isNoteActive(midi) ? "#ffb066" : void 0;
4738      keys.push(
4739        /* @__PURE__ */ jsxRuntimeExports.jsx(
4740          "div",
4741          {
4742            className: `flex items-center justify-end px-2 border-b select-none cursor-pointer relative w-full border-zinc-200 dark:border-zinc-800 hover:brightness-110 ${bgClass} ${textClass}`,
4743            style: { height: KEY_HEIGHT, fontSize: "9px", backgroundColor: activeBg },
4744            onMouseDown: (e) => handleKeyMouseDown(e, midi),
4745            onMouseUp: (e) => handleKeyMouseUp(),
4746            onMouseMove: (e) => handleKeyMouseMove(e, midi),
4747            onMouseLeave: (e) => handleKeyMouseLeave(),
4748            onMouseEnter: (e) => handleKeyMouseEnter(e, midi),
4749            children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold pointer-events-none truncate z-20", title: name, children: name })
4750          },
4751          midi
4752        )
4753      );
4754    } else {
4755      if (isBlack) {
4756        keys.push(
4757          /* @__PURE__ */ jsxRuntimeExports.jsxs(
4758            "div",
4759            {
4760              className: "select-none cursor-pointer relative w-full hover:brightness-110 overflow-hidden",
4761              style: { height: KEY_HEIGHT },
4762              onMouseDown: (e) => handleKeyMouseDown(e, midi),
4763              onMouseUp: (e) => handleKeyMouseUp(),
4764              onMouseMove: (e) => handleKeyMouseMove(e, midi),
4765              onMouseLeave: (e) => handleKeyMouseLeave(),
4766              onMouseEnter: (e) => handleKeyMouseEnter(e, midi),
4767              children: [
4768                /* @__PURE__ */ jsxRuntimeExports.jsx(
4769                  "div",
4770                  {
4771                    className: "absolute right-0 top-0 w-[40%] h-[50%] z-10 pointer-events-none transition-all duration-75",
4772                    style: { background: getWhiteKeyBg(midi + 1) }
4773                  }
4774                ),
4775                /* @__PURE__ */ jsxRuntimeExports.jsx(
4776                  "div",
4777                  {
4778                    className: "absolute right-0 bottom-0 w-[40%] h-[50%] z-10 pointer-events-none transition-all duration-75",
4779                    style: { background: getWhiteKeyBg(midi - 1) }
4780                  }
4781                ),
4782                /* @__PURE__ */ jsxRuntimeExports.jsx(
4783                  "div",
4784                  {
4785                    className: "absolute right-0 top-1/2 w-[40%] h-px bg-zinc-300 dark:bg-zinc-700 pointer-events-none z-15",
4786                    style: { transform: "translateY(-50%)" }
4787                  }
4788                ),
4789                /* @__PURE__ */ jsxRuntimeExports.jsx(
4790                  "div",
4791                  {
4792                    className: "absolute left-0 top-0 w-[60%] h-full z-20 pointer-events-none transition-all duration-75 shadow-sm",
4793                    style: {
4794                      background: getBlackKeyBg(midi),
4795                      borderRight: "1px solid #181a1b",
4796                      borderBottom: "1px solid #181a1b"
4797                    },
4798                    title: name
4799                  }
4800                )
4801              ]
4802            },
4803            midi
4804          )
4805        );
4806      } else {
4807        const isEOrB = midi % 12 === 4 || midi % 12 === 11;
4808        const borderClass = isEOrB ? "border-b border-zinc-300 dark:border-zinc-700" : "";
4809        const isC = midi % 12 === 0;
4810        keys.push(
4811          /* @__PURE__ */ jsxRuntimeExports.jsxs(
4812            "div",
4813            {
4814              className: `flex items-center justify-end select-none cursor-pointer relative w-full hover:brightness-110 transition-all duration-75 ${borderClass}`,
4815              style: {
4816                height: KEY_HEIGHT,
4817                fontSize: "9px",
4818                background: getWhiteKeyBg(midi)
4819              },
4820              onMouseDown: (e) => handleKeyMouseDown(e, midi),
4821              onMouseUp: (e) => handleKeyMouseUp(),
4822              onMouseMove: (e) => handleKeyMouseMove(e, midi),
4823              onMouseLeave: (e) => handleKeyMouseLeave(),
4824              onMouseEnter: (e) => handleKeyMouseEnter(e, midi),
4825              children: [
4826                isC && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "absolute right-2 font-bold text-[#4c566a] dark:text-[#8fbcbb] pointer-events-none select-none text-[10px] z-20", children: [
4827                  "C",
4828                  octave
4829                ] }),
4830                !isC && isInScale && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-2 w-1.5 h-1.5 rounded-full bg-indigo-500/40 pointer-events-none z-20" })
4831              ]
4832            },
4833            midi
4834          )
4835        );
4836      }
4837    }
4838  }
4839  const verticalLines = [];
4840  const timeSignatures = engine.midi.header.timeSignatures;
4841  if (timeSignatures.length === 0) {
4842    timeSignatures.push({ ticks: 0, timeSignature: [4, 4], measures: 0 });
4843  }
4844  const measureLabels = [];
4845  {
4846    let curMeasureTick = 0;
4847    let tsIndex = 0;
4848    let measureNumber = 1;
4849    while (curMeasureTick <= maxTicks) {
4850      const ts = timeSignatures[tsIndex] || { timeSignature: [4, 4] };
4851      const nextTs = timeSignatures[tsIndex + 1];
4852      const num = ts.timeSignature[0];
4853      const den = ts.timeSignature[1];
4854      const ticksPerBeat = ppq * (4 / den);
4855      const ticksPerMeasure = ticksPerBeat * num;
4856      const limitTick = nextTs ? nextTs.ticks : maxTicks;
4857      while (curMeasureTick < limitTick) {
4858        measureLabels.push(
4859          /* @__PURE__ */ jsxRuntimeExports.jsx(
4860            "div",
4861            {
4862              className: "absolute bottom-0.5 text-[10px] font-bold text-zinc-400 dark:text-zinc-500 pointer-events-none select-none pl-1",
4863              style: { left: curMeasureTick * pxPerTick },
4864              children: measureNumber
4865            },
4866            `m-lbl-${curMeasureTick}`
4867          )
4868        );
4869        measureNumber++;
4870        curMeasureTick += ticksPerMeasure;
4871        if (ticksPerMeasure <= 0) break;
4872      }
4873      tsIndex++;
4874      if (tsIndex >= timeSignatures.length) break;
4875    }
4876  }
4877  let currentTick = 0;
4878  let tsIdx = 0;
4879  while (currentTick <= maxTicks) {
4880    const ts = timeSignatures[tsIdx];
4881    const nextTs = timeSignatures[tsIdx + 1];
4882    const num = ts.timeSignature[0];
4883    const den = ts.timeSignature[1];
4884    const ticksPerBeat = ppq * (4 / den);
4885    const ticksPerMeasure = ticksPerBeat * num;
4886    const limitTick = nextTs ? nextTs.ticks : maxTicks;
4887    while (currentTick < limitTick) {
4888      verticalLines.push(
4889        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute h-full w-[2px] bg-zinc-400 dark:bg-zinc-600 z-0", style: { left: currentTick * pxPerTick } }, `m-${currentTick}`)
4890      );
4891      for (let b = 1; b < num; b++) {
4892        const beatTick = currentTick + b * ticksPerBeat;
4893        if (beatTick < limitTick) {
4894          verticalLines.push(
4895            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute h-full w-px bg-zinc-200/70 dark:bg-zinc-800/50 z-0", style: { left: beatTick * pxPerTick } }, `b-${beatTick}`)
4896          );
4897        }
4898      }
4899      if (gridSnap > den) {
4900        const subsPerBeat = gridSnap / den;
4901        const ticksPerSub = ticksPerBeat / subsPerBeat;
4902        for (let b = 0; b < num; b++) {
4903          for (let s = 1; s < subsPerBeat; s++) {
4904            const subTick = currentTick + b * ticksPerBeat + s * ticksPerSub;
4905            if (subTick < limitTick) {
4906              verticalLines.push(
4907                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute h-full w-px bg-zinc-200/30 dark:bg-zinc-800/30 z-0", style: { left: subTick * pxPerTick } }, `s-${subTick}`)
4908              );
4909            }
4910          }
4911        }
4912      }
4913      currentTick += ticksPerMeasure;
4914      if (ticksPerMeasure <= 0) break;
4915    }
4916    tsIdx++;
4917    if (tsIdx >= timeSignatures.length) break;
4918  }
4919  let patternLength = 0;
4920  if (activePattern) {
4921    const maxNoteTick = activePattern.notes.reduce((max, n) => Math.max(max, n.ticks + n.durationTicks), 0);
4922    const ppq2 = ((_b = (_a = engine == null ? void 0 : engine.midi) == null ? void 0 : _a.header) == null ? void 0 : _b.ppq) || 96;
4923    const ticksPerBar = ppq2 * 4;
4924    patternLength = activePattern.durationTicks || Math.max(ticksPerBar * 4, Math.ceil(maxNoteTick / ticksPerBar) * ticksPerBar);
4925  }
4926  const currentTicks = engine ? engine.secondsToTicks(currentTime) : 0;
4927  let showPlayhead = false;
4928  let playheadTick = 0;
4929  if (playbackMode === "pat") {
4930    showPlayhead = true;
4931    playheadTick = patternLength > 0 ? currentTicks % patternLength : currentTicks;
4932  } else {
4933    const playingClip = playlistClips.find(
4934      (clip) => clip.patternId === activePatternId && !clip.isMuted && currentTicks >= clip.startTick && currentTicks < clip.startTick + clip.durationTicks
4935    );
4936    if (playingClip) {
4937      showPlayhead = true;
4938      playheadTick = patternLength > 0 ? (currentTicks - playingClip.startTick + playingClip.offsetTicks) % patternLength : currentTicks - playingClip.startTick + playingClip.offsetTicks;
4939    }
4940  }
4941  const playheadX = playheadTick * pxPerTick;
4942  reactExports.useEffect(() => {
4943    console.log(`[Playhead Debug] useEffect triggered. isPlaying: ${isPlaying}, engine.isPlaying: ${engine == null ? void 0 : engine.isPlaying()}`);
4944    if (!engine) return;
4945    let rafId;
4946    const loop = () => {
4947      const rawTime = engine.isPlaying() ? engine.getCurrentTime() : currentTimeRef.current;
4948      const rawTicks = engine.secondsToTicks(rawTime);
4949      let pxOffset = 0;
4950      if (playbackMode === "pat") {
4951        const tick = patternLength > 0 ? rawTicks % patternLength : rawTicks;
4952        pxOffset = tick * pxPerTick;
4953      } else {
4954        const playingClip = playlistClips.find(
4955          (c) => c.patternId === activePatternId && !c.isMuted && rawTicks >= c.startTick && rawTicks < c.startTick + c.durationTicks
4956        );
4957        if (playingClip) {
4958          const tick = patternLength > 0 ? (rawTicks - playingClip.startTick + playingClip.offsetTicks) % patternLength : rawTicks - playingClip.startTick + playingClip.offsetTicks;
4959          pxOffset = tick * pxPerTick;
4960        } else {
4961          const tick = patternLength > 0 ? rawTicks % patternLength : rawTicks;
4962          pxOffset = tick * pxPerTick;
4963        }
4964      }
4965      if (playheadElRef.current) {
4966        playheadElRef.current.style.left = `${pxOffset}px`;
4967        playheadElRef.current.style.display = "block";
4968        if (isPlaying && containerRef.current) {
4969          const container = containerRef.current;
4970          const scrollLeft = container.scrollLeft;
4971          const width = container.clientWidth;
4972          if (pxOffset > scrollLeft + width - 50) {
4973            container.scrollLeft = pxOffset - width / 2;
4974          } else if (pxOffset < scrollLeft) {
4975            container.scrollLeft = Math.max(0, pxOffset - 50);
4976          }
4977        }
4978      }
4979      rafId = requestAnimationFrame(loop);
4980    };
4981    rafId = requestAnimationFrame(loop);
4982    return () => {
4983      console.log("[Playhead Debug] useEffect cleanup called.");
4984      cancelAnimationFrame(rafId);
4985    };
4986  }, [engine, isPlaying, playbackMode, patternLength, pxPerTick, playlistClips, activePatternId]);
4987  const toolbarJSX = /* @__PURE__ */ jsxRuntimeExports.jsxs(
4988    "div",
4989    {
4990      className: isToolbarFloating ? "absolute z-[60] flex items-center bg-white dark:bg-zinc-800 p-2 rounded-lg shadow-lg border border-zinc-200 dark:border-zinc-700" : "shrink-0 flex items-center bg-zinc-100 dark:bg-zinc-900 px-4 py-2 border-b border-zinc-300 dark:border-zinc-700 w-full z-[60]",
4991      style: isToolbarFloating ? { right: "auto", bottom: "auto", left: toolbarPos.x, top: toolbarPos.y } : {},
4992      onPointerDown: handleToolbarPointerDown,
4993      onPointerMove: handleToolbarPointerMove,
4994      onPointerUp: handleToolbarPointerUp,
4995      onPointerCancel: handleToolbarPointerUp,
4996      children: [
4997        isToolbarFloating && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mr-2 cursor-move text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GripHorizontal, { size: 18 }) }),
4998        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Draw, paint, or slice notes on the piano roll.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 mr-2 border-r border-zinc-200 dark:border-zinc-700 pr-4", children: [
4999          /* @__PURE__ */ jsxRuntimeExports.jsx(
5000            "button",
5001            {
5002              onClick: () => setToolMode("draw"),
5003              className: `p-1.5 rounded transition-colors ${toolMode === "draw" ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-900/50 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-700"}`,
5004              title: "Draw Tool (Left-click to add, drag to resize)",
5005              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { size: 18 })
5006            }
5007          ),
5008          /* @__PURE__ */ jsxRuntimeExports.jsx(
5009            "button",
5010            {
5011              onClick: () => setToolMode("paint"),
5012              className: `p-1.5 rounded transition-colors ${toolMode === "paint" ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-900/50 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-700"}`,
5013              title: "Paint Tool (Click and drag to draw continuously)",
5014              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Brush, { size: 18 })
5015            }
5016          ),
5017          /* @__PURE__ */ jsxRuntimeExports.jsx(
5018            "button",
5019            {
5020              onClick: () => setToolMode("slice"),
5021              className: `p-1.5 rounded transition-colors ${toolMode === "slice" ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-900/50 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-700"}`,
5022              title: "Slice Tool (Click and drag to cut notes)",
5023              children: /* @__PURE__ */ jsxRuntimeExports.jsx(Scissors, { size: 18 })
5024            }
5025          )
5026        ] }) }),
5027        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Switch voices. Different voices are rendered in different colors.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1 mr-2 border-r border-zinc-200 dark:border-zinc-700 pr-4", children: [1, 2, 3, 4].map((v) => /* @__PURE__ */ jsxRuntimeExports.jsx(
5028          "button",
5029          {
5030            onClick: () => setActiveVoice(v),
5031            className: `w-6 h-6 rounded flex items-center justify-center text-xs font-bold transition-colors ${activeVoice === v ? v === 1 ? "bg-[#0072B2] text-white" : v === 2 ? "bg-[#009E73] text-white" : v === 3 ? "bg-[#D55E00] text-white" : "bg-[#CC79A7] text-white" : "bg-zinc-100 dark:bg-zinc-700 text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-600"}`,
5032            children: v
5033          },
5034          v
5035        )) }) }),
5036        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Grid snapping resolution for drawing and moving notes.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mr-2 border-r border-zinc-200 dark:border-zinc-700 pr-4 relative", children: [
5037          /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded transition-colors", title: "Snap to Grid", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Magnet, { size: 18 }) }),
5038          /* @__PURE__ */ jsxRuntimeExports.jsxs(
5039            "select",
5040            {
5041              value: gridSnap,
5042              onChange: (e) => setGridSnap(Number(e.target.value)),
5043              className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer",
5044              title: "Snap to Grid",
5045              children: [
5046                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 0, children: "Off" }),
5047                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 1, children: "1 Bar (1/1)" }),
5048                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 2, children: "1/2" }),
5049                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 4, children: "1/4" }),
5050                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 8, children: "1/8" }),
5051                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 16, children: "1/16" }),
5052                /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 32, children: "1/32" })
5053              ]
5054            }
5055          )
5056        ] }) }),
5057        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Zoom in or out horizontally.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
5058          /* @__PURE__ */ jsxRuntimeExports.jsx(
5059            "button",
5060            {
5061              onClick: () => setZoomX((z) => Math.max(0.25, z - 0.25)),
5062              className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded text-zinc-600 dark:text-zinc-300",
5063              title: "Zoom Out",
5064              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { size: 20 })
5065            }
5066          ),
5067          /* @__PURE__ */ jsxRuntimeExports.jsx(
5068            "button",
5069            {
5070              onClick: () => setZoomX((z) => Math.min(4, z + 0.25)),
5071              className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded text-zinc-600 dark:text-zinc-300",
5072              title: "Zoom In",
5073              children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 20 })
5074            }
5075          )
5076        ] }) }),
5077        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-6 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
5078        /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Toggle and color-code background track ghost notes.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", ref: layersDropdownRef, children: [
5079          /* @__PURE__ */ jsxRuntimeExports.jsxs(
5080            "button",
5081            {
5082              onClick: () => setShowLayersDropdown(!showLayersDropdown),
5083              className: `p-1.5 rounded flex items-center gap-1.5 transition-colors ${showLayersDropdown ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-900/50 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-700"}`,
5084              title: "Layer Visibility & Ghost Notes",
5085              children: [
5086                /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 18 }),
5087                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold select-none", children: "Layers" })
5088              ]
5089            }
5090          ),
5091          showLayersDropdown && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute right-0 mt-2 w-64 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg shadow-xl z-[90] p-3 text-sm flex flex-col gap-2", children: [
5092            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center pb-2 border-b border-zinc-100 dark:border-zinc-700", children: [
5093              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-900 dark:text-zinc-100", children: "Ghost Channels" }),
5094              /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
5095                /* @__PURE__ */ jsxRuntimeExports.jsx(
5096                  "input",
5097                  {
5098                    type: "checkbox",
5099                    checked: showGhostNotes,
5100                    onChange: (e) => setShowGhostNotes(e.target.checked),
5101                    className: "rounded text-indigo-600 focus:ring-indigo-500 cursor-pointer"
5102                  }
5103                ),
5104                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-medium text-zinc-600 dark:text-zinc-400 select-none", children: "Enabled" })
5105              ] })
5106            ] }),
5107            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-48 overflow-y-auto pr-1 flex flex-col gap-1.5 custom-scrollbar", children: engine.midi.tracks.map((t, idx) => {
5108              const isCurrent = idx === activeTrackIndex;
5109              const isVisible = ghostTrackVisibility[idx] !== false;
5110              const color = t.color || getTrackColor(idx);
5111              return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5112                "div",
5113                {
5114                  className: `flex items-center justify-between p-1 rounded hover:bg-zinc-50 dark:hover:bg-zinc-750/30 ${isCurrent ? "bg-zinc-50 dark:bg-zinc-750/20" : ""}`,
5115                  children: [
5116                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 truncate", children: [
5117                      /* @__P
5117URE__ */ jsxRuntimeExports.jsx(
5118                        "div",
5119                        {
5120                          className: "w-3 h-3 rounded-full shrink-0",
5121                          style: { backgroundColor: color }
5122                        }
5123                      ),
5124                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `truncate font-medium text-xs ${isCurrent ? "text-zinc-900 dark:text-zinc-100 font-bold" : "text-zinc-600 dark:text-zinc-400"}`, children: t.name || `Track ${idx + 1}` }),
5125                      isCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] bg-indigo-100 text-indigo-700 dark:bg-indigo-900/60 dark:text-indigo-300 px-1 rounded-sm", children: "Active" })
5126                    ] }),
5127                    !isCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx(
5128                      "button",
5129                      {
5130                        onClick: () => {
5131                          setGhostTrackVisibility((prev) => ({
5132                            ...prev,
5133                            [idx]: !isVisible
5134                          }));
5135                        },
5136                        className: "text-zinc-400 hover:text-zinc-650 dark:hover:text-zinc-200 transition-colors p-0.5",
5137                        title: isVisible ? "Hide ghost notes" : "Show ghost notes",
5138                        children: isVisible ? /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 14 })
5139                      }
5140                    )
5141                  ]
5142                },
5143                `layer-${idx}`
5144              );
5145            }) }),
5146            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center pt-2 border-t border-zinc-100 dark:border-zinc-700 text-xs", children: [
5147              /* @__PURE__ */ jsxRuntimeExports.jsx(
5148                "button",
5149                {
5150                  onClick: () => {
5151                    const newVis = {};
5152                    engine.midi.tracks.forEach((_, i) => {
5153                      newVis[i] = true;
5154                    });
5155                    setGhostTrackVisibility(newVis);
5156                  },
5157                  className: "text-indigo-600 dark:text-indigo-400 hover:underline font-semibold",
5158                  children: "Show All"
5159                }
5160              ),
5161              /* @__PURE__ */ jsxRuntimeExports.jsx(
5162                "button",
5163                {
5164                  onClick: () => {
5165                    const newVis = {};
5166                    engine.midi.tracks.forEach((_, i) => {
5167                      newVis[i] = false;
5168                    });
5169                    setGhostTrackVisibility(newVis);
5170                  },
5171                  className: "text-zinc-500 hover:text-zinc-750 dark:hover:text-zinc-300 hover:underline font-semibold",
5172                  children: "Hide All"
5173                }
5174              )
5175            ] })
5176          ] })
5177        ] }) }),
5178        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-6 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
5179        /* @__PURE__ */ jsxRuntimeExports.jsx(
5180          "button",
5181          {
5182            onClick: () => setShowShortcutsModal(true),
5183            className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded text-zinc-600 dark:text-zinc-300 ml-1",
5184            title: "Keyboard Shortcuts & Tips",
5185            children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: 20 })
5186          }
5187        ),
5188        /* @__PURE__ */ jsxRuntimeExports.jsx(
5189          "button",
5190          {
5191            onClick: () => setIsToolbarFloating(!isToolbarFloating),
5192            className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-700 rounded text-zinc-500 hover:text-indigo-600 dark:hover:text-indigo-400 ml-auto",
5193            title: isToolbarFloating ? "Dock Toolbar" : "Float Toolbar",
5194            children: isToolbarFloating ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pin, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PinOff, { size: 18 })
5195          }
5196        )
5197      ]
5198    }
5199  );
5200  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Ut, { direction: "vertical", className: "flex-1 w-full h-full bg-zinc-50 dark:bg-zinc-900", children: [
5201    /* @__PURE__ */ jsxRuntimeExports.jsxs(Yt, { className: "flex relative min-h-[200px] flex-col", children: [
5202      !isToolbarFloating && toolbarJSX,
5203      /* @__PURE__ */ jsxRuntimeExports.jsxs(
5204        "div",
5205        {
5206          className: "flex-1 flex overflow-hidden relative bg-zinc-50 dark:bg-zinc-900 select-none",
5207          onMouseMove: handleMouseMove,
5208          onMouseUp: handleMouseUp,
5209          onMouseLeave: (e) => {
5210            handleMouseUp(e);
5211            setHint(null);
5212          },
5213          onContextMenu: (e) => {
5214            e.preventDefault();
5215            setContextMenuPos({ x: e.clientX, y: e.clientY });
5216          },
5217          children: [
5218            isToolbarFloating && toolbarJSX,
5219            showShortcutsModal && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm", onClick: () => setShowShortcutsModal(false), onContextMenu: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 p-6 rounded-lg shadow-2xl border border-zinc-200 dark:border-zinc-800 max-w-lg w-full max-h-[80vh] overflow-y-auto custom-scrollbar", onClick: (e) => e.stopPropagation(), children: [
5220              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-6 border-b border-zinc-200 dark:border-zinc-800 pb-4", children: [
5221                /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-xl text-zinc-900 dark:text-zinc-100 flex items-center gap-2", children: [
5222                  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: 24, className: "text-indigo-500" }),
5223                  "Piano Roll Shortcuts"
5224                ] }),
5225                /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setShowShortcutsModal(false), className: "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200", children: "✕" })
5226              ] }),
5227              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
5228                /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
5229                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-700 dark:text-zinc-300 mb-2 uppercase text-xs tracking-wider", children: "Navigation" }),
5230                  /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "space-y-2 text-sm text-zinc-600 dark:text-zinc-400", children: [
5231                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5232                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Middle Click + Drag" }),
5233                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Pan around the Piano Roll" })
5234                    ] }),
5235                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5236                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Scroll Wheel" }),
5237                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Scroll Vertically" })
5238                    ] }),
5239                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5240                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Shift + Scroll Wheel" }),
5241                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Scroll Horizontally" })
5242                    ] })
5243                  ] })
5244                ] }),
5245                /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
5246                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-700 dark:text-zinc-300 mb-2 uppercase text-xs tracking-wider", children: "Note Editing" }),
5247                  /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "space-y-2 text-sm text-zinc-600 dark:text-zinc-400", children: [
5248                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5249                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Left Click (Grid)" }),
5250                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Add a new note / Drag to resize" })
5251                    ] }),
5252                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5253                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Right Click (Note)" }),
5254                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Delete the note" })
5255                    ] }),
5256                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5257                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Alt + Click (Note)" }),
5258                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Mute / Unmute the note" })
5259                    ] }),
5260                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5261                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Drag Note Ends" }),
5262                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Resize note duration" })
5263                    ] }),
5264                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5265                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Ctrl + Click (Note)" }),
5266                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Toggle note selection" })
5267                    ] }),
5268                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5269                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Shift + Drag (Note)" }),
5270                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Duplicate selected note(s)" })
5271                    ] }),
5272                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5273                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Ctrl + Drag (Grid)" }),
5274                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Lasso select notes (Ctrl+Shift to append)" })
5275                    ] })
5276                  ] })
5277                ] }),
5278                /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
5279                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-700 dark:text-zinc-300 mb-2 uppercase text-xs tracking-wider", children: "Tools" }),
5280                  /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "space-y-2 text-sm text-zinc-600 dark:text-zinc-400", children: [
5281                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5282                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Draw Tool" }),
5283                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Default mode. Click to select, drag to move." })
5284                    ] }),
5285                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5286                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Paint Tool" }),
5287                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Click and drag to draw a continuous line of notes." })
5288                    ] }),
5289                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5290                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Delete / Backspace" }),
5291                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Delete currently selected notes" })
5292                    ] })
5293                  ] })
5294                ] }),
5295                /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
5296                  /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-700 dark:text-zinc-300 mb-2 uppercase text-xs tracking-wider", children: "Timeline" }),
5297                  /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "space-y-2 text-sm text-zinc-600 dark:text-zinc-400", children: [
5298                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5299                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Click on Timeline Ruler" }),
5300                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Add Tempo / Time Signature change" })
5301                    ] }),
5302                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5303                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Right Click Marker" }),
5304                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Edit existing event" })
5305                    ] }),
5306                    /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex justify-between", children: [
5307                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: "Drag Marker" }),
5308                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Move event along the timeline" })
5309                    ] })
5310                  ] })
5311                ] })
5312              ] }),
5313              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-8 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5314                "button",
5315                {
5316                  onClick: () => setShowShortcutsModal(false),
5317                  className: "px-6 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded font-bold transition-colors",
5318                  children: "Got it!"
5319                }
5320              ) })
5321            ] }) }),
5322            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `shrink-0 bg-zinc-100 dark:bg-zinc-950 overflow-hidden relative z-20 shadow-[2px_0_10px_rgba(0,0,0,0.1)] dark:shadow-[2px_0_10px_rgba(0,0,0,0.5)] border-r border-zinc-200 dark:border-zinc-800 flex flex-col ${isDrumMode ? "w-32" : "w-24"}`, children: [
5323              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-8 shrink-0 border-b border-transparent w-full" }),
5324              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-6 shrink-0 sticky top-0 bg-zinc-100/95 dark:bg-zinc-800/95 border-b border-zinc-300 dark:border-zinc-700 z-[60] backdrop-blur" }),
5325              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5326                "div",
5327                {
5328                  ref: keyboardRef,
5329                  className: "absolute w-full top-0 left-0",
5330                  children: keys
5331                }
5332              ) })
5333            ] }),
5334            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0 flex flex-col focus:outline-none overflow-hidden", children: [
5335              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-8 shrink-0 border-b border-zinc-300 dark:border-zinc-700 w-full z-[50]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5336                FLHorizontalScrollbar,
5337                {
5338                  containerRef,
5339                  zoomX,
5340                  setZoomX,
5341                  maxTicks,
5342                  ppq,
5343                  beatWidth: BEAT_WIDTH,
5344                  mode: "piano-roll",
5345                  patterns,
5346                  activePatternId
5347                }
5348              ) }),
5349              /* @__PURE__ */ jsxRuntimeExports.jsxs(
5350                "div",
5351                {
5352                  ref: containerRef,
5353                  className: "flex-1 min-w-0 overflow-auto relative custom-scrollbar flex flex-col focus:outline-none",
5354                  tabIndex: 0,
5355                  onKeyDown: (e) => {
5356                    if (e.key === "Delete" || e.key === "Backspace") {
5357                      e.preventDefault();
5358                      deleteSelection();
5359                    } else if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "d") {
5360                      e.preventDefault();
5361                      const selectedNotes = selectedNoteIds.map((idx) => notes[idx]).filter(Boolean);
5362                      if (selectedNotes.length > 0) {
5363                        const minTick = Math.min(...selectedNotes.map((n) => n.ticks));
5364                        const maxEndTick = Math.max(...selectedNotes.map((n) => n.ticks + n.durationTicks));
5365                        const blockDuration = maxEndTick - minTick;
5366                        const duplicates = selectedNotes.map((n) => ({
5367                          midi: n.midi,
5368                          ticks: n.ticks + blockDuration,
5369                          durationTicks: n.durationTicks,
5370                          velocity: n.velocity ?? 0.8,
5371                          voice: n.voice || 1
5372                        }));
5373                        addNotes(activeTrackIndex, duplicates, "pianoroll");
5374                        setSelectedNoteIds(duplicates.map((_, i) => notes.length + i));
5375                        setTimeout(() => forceSync(), 0);
5376                      }
5377                    }
5378                  },
5379                  onScroll: (e) => {
5380                    const target = e.target;
5381                    if (keyboardRef.current) {
5382                      keyboardRef.current.style.transform = `translateY(-${target.scrollTop}px)`;
5383                    }
5384                    if (bottomPaneRef.current) {
5385                      bottomPaneRef.current.scrollLeft = target.scrollLeft;
5386                    }
5387                  },
5388                  onWheel: (e) => {
5389                    if (e.ctrlKey || e.metaKey) {
5390                      const zoomFactor = e.deltaY > 0 ? -0.25 : 0.25;
5391                      setZoomX((z) => Math.max(0.25, Math.min(4, z + zoomFactor)));
5392                    } else if (e.shiftKey) {
5393                      if (containerRef.current) {
5394                        containerRef.current.scrollLeft += e.deltaY;
5395                      }
5396                    }
5397                  },
5398                  children: [
5399                    /* @__PURE__ */ jsxRuntimeExports.jsxs(
5400                      "div",
5401                      {
5402                        className: "sticky top-0 h-6 shrink-0 bg-zinc-100/95 dark:bg-zinc-800/95 border-b border-zinc-300 dark:border-zinc-700 z-[45] cursor-pointer shadow-sm backdrop-blur",
5403                        style: { width: totalWidth },
5404                        onClick: (e) => {
5405                          if (e.target.closest(".meta-marker")) return;
5406                          const rect = e.currentTarget.getBoundingClientRect();
5407                          const x = e.clientX - rect.left;
5408                          const tick = Math.round(x / pxPerTick);
5409                          setMetaModal({ tick });
5410                        },
5411                        onContextMenu: (e) => {
5412                          if (e.target.closest(".meta-marker")) return;
5413                          e.preventDefault();
5414                          e.stopPropagation();
5415                          const rect = e.currentTarget.getBoundingClientRect();
5416                          const x = e.clientX - rect.left;
5417                          const tick = Math.round(x / pxPerTick);
5418                          const actionsMenu = confirm("Add Loop Marker at this position?");
5419                          if (actionsMenu) {
5420                            setLoopMarkerTick(tick);
5421                          } else if (loopMarkerTick !== null) {
5422                            const remove = confirm("Remove current Loop Marker?");
5423                            if (remove) {
5424                              setLoopMarkerTick(null);
5425                            }
5426                          }
5427                        },
5428                        children: [
5429                          measureLabels,
5430                          engine.midi.header.tempos.map((t, i) => {
5431                            const isDragging = (draggingMeta == null ? void 0 : draggingMeta.type) === "tempo" && draggingMeta.originalTick === t.ticks;
5432                            const tickToRender = isDragging && metaDragTicks !== null ? metaDragTicks : t.ticks;
5433                            return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5434                              "div",
5435                              {
5436                                className: `meta-marker absolute top-0 flex flex-col items-center hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-sm px-1 cursor-ew-resize ${isDragging ? "z-50 opacity-80" : "z-10"}`,
5437                                style: {
5438                                  left: tickToRender * pxPerTick,
5439                                  transform: tickToRender === 0 ? "translateX(0)" : "translateX(-50%)"
5440                                },
5441                                onMouseDown: (e) => {
5442                                  e.stopPropagation();
5443                                  setDragStartPos({ x: e.clientX, y: e.clientY });
5444                                  setDraggingMeta({ type: "tempo", originalTick: t.ticks, bpm: t.bpm });
5445                                  setMetaDragTicks(t.ticks);
5446                                },
5447                                onContextMenu: (e) => {
5448                                  e.preventDefault();
5449                                  e.stopPropagation();
5450                                  setMetaType("tempo");
5451                                  setMetaBpm(Math.round(t.bpm).toString());
5452                                  setMetaModal({ tick: t.ticks, isEdit: true, originalTick: t.ticks });
5453                                },
5454                                children: [
5455                                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0 h-0 border-l-[4px] border-r-[4px] border-t-[6px] border-l-transparent border-r-transparent border-t-rose-500 mb-0.5" }),
5456                                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-rose-600 dark:text-rose-400 bg-zinc-100/80 dark:bg-zinc-800/80 px-0.5 rounded leading-none", children: Math.round(t.bpm) })
5457                                ]
5458                              },
5459                              `t-${i}`
5460                            );
5461                          }),
5462                          engine.midi.header.timeSignatures.map((ts, i) => {
5463                            const isDragging = (draggingMeta == null ? void 0 : draggingMeta.type) === "timesig" && draggingMeta.originalTick === ts.ticks;
5464                            const tickToRender = isDragging && metaDragTicks !== null ? metaDragTicks : ts.ticks;
5465                            return /* @__PURE__ */ jsxRuntimeExports.jsx(
5466                              "div",
5467                              {
5468                                className: `meta-marker absolute top-0 flex flex-col items-center hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-sm px-1 cursor-ew-resize ${isDragging ? "z-50 opacity-80" : "z-10"}`,
5469                                style: {
5470                                  left: tickToRender * pxPerTick,
5471                                  transform: tickToRender === 0 ? "translateX(0)" : "translateX(-50%)",
5472                                  marginTop: "12px"
5473                                },
5474                                onMouseDown: (e) => {
5475                                  e.stopPropagation();
5476                                  setDragStartPos({ x: e.clientX, y: e.clientY });
5477                                  setDraggingMeta({ type: "timesig", originalTick: ts.ticks, num: ts.timeSignature[0], den: ts.timeSignature[1] });
5478                                  setMetaDragTicks(ts.ticks);
5479                                },
5480                                onContextMenu: (e) => {
5481                                  e.preventDefault();
5482                                  e.stopPropagation();
5483                                  setMetaType("timesig");
5484                                  setMetaNum(ts.timeSignature[0].toString());
5485                                  setMetaDen(ts.timeSignature[1].toString());
5486                                  setMetaModal({ tick: ts.ticks, isEdit: true, originalTick: ts.ticks });
5487                                },
5488                                children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold text-blue-600 dark:text-blue-400 bg-zinc-100/80 dark:bg-zinc-800/80 px-0.5 rounded leading-none", children: [
5489                                  ts.timeSignature[0],
5490                                  "/",
5491                                  ts.timeSignature[1]
5492                                ] })
5493                              },
5494                              `ts-${i}`
5495                            );
5496                          }),
5497                          loopMarkerTick !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
5498                            "div",
5499                            {
5500                              className: "meta-marker absolute top-0 flex flex-col items-center z-[50]",
5501                              style: {
5502                                left: loopMarkerTick * pxPerTick,
5503                                transform: "translateX(-50%)"
5504                              },
5505                              onContextMenu: (e) => {
5506                                e.preventDefault();
5507                                e.stopPropagation();
5508                                const remove = confirm("Remove Loop Marker?");
5509                                if (remove) {
5510                                  setLoopMarkerTick(null);
5511                                }
5512                              },
5513                              children: [
5514                                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-0 h-0 border-l-[6px] border-r-[6px] border-t-[8px] border-l-transparent border-r-transparent border-t-emerald-500 mb-0.5" }),
5515                                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-bold text-emerald-600 dark:text-emerald-400 bg-zinc-100/90 dark:bg-zinc-800/90 px-1 rounded leading-none", children: "LOOP" })
5516                              ]
5517                            }
5518                          )
5519                        ]
5520                      }
5521                    ),
5522                    /* @__PURE__ */ jsxRuntimeExports.jsxs(
5523                      "div",
5524                      {
5525                        ref: gridRef,
5526                        className: "relative bg-zinc-50 dark:bg-zinc-900 cursor-crosshair shrink-0",
5527                        style: { width: totalWidth, height: actualNumKeys * KEY_HEIGHT },
5528                        onMouseDown: (e) => {
5529                          if (!gridRef.current || !containerRef.current) return;
5530                          containerRef.current.focus();
5531                          const rect = gridRef.current.getBoundingClientRect();
5532                          const x = e.clientX - rect.left;
5533                          const y = e.clientY - rect.top;
5534                          if (e.button === 1) {
5535                            e.preventDefault();
5536                            setPanStart({ x: e.clientX, y: e.clientY, scrollX: containerRef.current.scrollLeft, scrollY: containerRef.current.scrollTop });
5537                            return;
5538                          }
5539                          if (toolMode === "paint" && e.button === 0) {
5540                            setIsPainting(true);
5541                            const snapTicks = gridSnap === 0 ? 1 : ppq * (4 / gridSnap);
5542                            const rawTicks = Math.max(0, Math.round(x / pxPerTick));
5543                            const ticks = gridSnap === 0 ? rawTicks : Math.round(rawTicks / snapTicks) * snapTicks;
5544                            const pitchIdx = Math.floor(y / KEY_HEIGHT);
5545                            const midiNote = actualMaxNote - pitchIdx;
5546                            if (midiNote >= actualMinNote && midiNote <= actualMaxNote) {
5547                              const defaultDuration = gridSnap === 0 ? ppq : snapTicks;
5548                              const existingNote = notes.find((n) => n.midi === midiNote && n.ticks === ticks);
5549                              if (!existingNote) {
5550                                const intervals = chordStamp !== "none" && !isDrumMode ? CHORDS[chordStamp] : [0];
5551                                const notesToAdd = [];
5552                                intervals.forEach((interval) => {
5553                                  const chordMidi = midiNote + interval;
5554                                  if (chordMidi >= actualMinNote && chordMidi <= actualMaxNote) {
5555                                    notesToAdd.push({
5556                                      midi: chordMidi,
5557                                      ticks,
5558                                      durationTicks: defaultDuration,
5559                                      velocity: 0.8,
5560                                      voice: activeVoice
5561                                    });
5562                                  }
5563                                });
5564                                addNotes(activeTrackIndex, notesToAdd, "pianoroll");
5565                                setLastPaintedTick(ticks);
5566                                if (engine) {
5567                                  engine.startNotePreview(midiNote, track.channel);
5568                                  setGridPreviewMidi(midiNote);
5569                                  setTimeout(() => {
5570                                    engine.stopNotePreview(midiNote, track.channel);
5571                                    setGridPreviewMidi((prev) => prev === midiNote ? null : prev);
5572                                  }, 100);
5573                                }
5574                              }
5575                            }
5576                            return;
5577                          }
5578                          if (toolMode === "slice" && e.button === 0) {
5579                            setSliceStart({ x, y, scrollX: containerRef.current.scrollLeft, scrollY: containerRef.current.scrollTop });
5580                            setSliceCurrent({ x, y });
5581                            return;
5582                          }
5583                          if (toolMode === "draw" && e.button === 0) {
5584                            if (e.ctrlKey) {
5585                              setLassoStart({ x, y, scrollX: containerRef.current.scrollLeft, scrollY: containerRef.current.scrollTop });
5586                              setLassoCurrent({ x, y });
5587                              if (!e.shiftKey) {
5588                                setSelectedNoteIds([]);
5589                              }
5590                              return;
5591                            }
5592                            const snapTicks = gridSnap === 0 ? 1 : ppq * (4 / gridSnap);
5593                            const rawTicks = Math.max(0, Math.round(x / pxPerTick));
5594                            const ticks = gridSnap === 0 ? rawTicks : Math.round(rawTicks / snapTicks) * snapTicks;
5595                            const pitchIdx = Math.floor(y / KEY_HEIGHT);
5596                            const midiNote = actualMaxNote - pitchIdx;
5597                            if (midiNote >= actualMinNote && midiNote <= actualMaxNote) {
5598                              const defaultDuration = lastNoteDurationRef.current ?? ppq;
5599                              const intervals = chordStamp !== "none" && !isDrumMode ? CHORDS[chordStamp] : [0];
5600                              const notesToAdd = [];
5601                              intervals.forEach((interval) => {
5602                                const chordMidi = midiNote + interval;
5603                                if (chordMidi >= actualMinNote && chordMidi <= actualMaxNote) {
5604                                  notesToAdd.push({
5605                                    midi: chordMidi,
5606                                    ticks,
5607                                    durationTicks: defaultDuration,
5608                                    velocity: 0.8,
5609                                    voice: activeVoice
5610                                  });
5611                                }
5612                              });
5613                              addNotes(activeTrackIndex, notesToAdd, "pianoroll");
5614                              if (engine) {
5615                                engine.startNotePreview(midiNote, track.channel);
5616                                auditionPitchRef.current = midiNote;
5617                                setGridPreviewMidi(midiNote);
5618                              }
5619                              const newNoteIndices = [];
5620                              notesToAdd.forEach((n) => {
5621                                const foundIdx = notes.findIndex(
5622                                  (note) => note.midi === n.midi && note.ticks === n.ticks && note.durationTicks === n.durationTicks
5623                                );
5624                                if (foundIdx !== -1) {
5625                                  newNoteIndices.push(foundIdx);
5626                                }
5627                              });
5628                              if (newNoteIndices.length > 0) {
5629                                setSelectedNoteIds(newNoteIndices);
5630                                const primaryNoteIdx = newNoteIndices[0];
5631                                const primaryNote = notes[primaryNoteIdx];
5632                                if (primaryNote) {
5633                                  setDraggingNoteIdx(primaryNoteIdx);
5634                                  setDragStartPos({ x: e.clientX, y: e.clientY });
5635                                  setDragStartProps({
5636                                    midi: primaryNote.midi,
5637                                    ticks: primaryNote.ticks,
5638                                    durationTicks: primaryNote.durationTicks
5639                                  });
5640                                  setDragType("resize-right");
5641                                }
5642                              }
5643                            }
5644                            return;
5645                          }
5646                        },
5647                        children: [
5648                          Array.from({ length: actualNumKeys }).map((_, i) => {
5649                            const midi = actualMaxNote - i;
5650                            const isC = !isDrumMode && midi % 12 === 0;
5651                            const isBlack = isBlackKey(midi);
5652                            let isInScale = false;
5653                            if (!isDrumMode && scaleRoot !== null) {
5654                              const noteClass = midi % 12;
5655                              const intervals = SCALES[scaleType] || [];
5656                              isInScale = intervals.some((interval) => (scaleRoot + interval) % 12 === noteClass);
5657                            }
5658                            const bgClass = isInScale ? isBlack ? "bg-indigo-500/5 dark:bg-indigo-500/10" : "bg-indigo-500/10 dark:bg-indigo-500/20" : isBlack ? "bg-zinc-100/50 dark:bg-zinc-800/20" : "transparent";
5659                            return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
5660                              bgClass !== "transparent" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute w-full pointer-events-none ${bgClass}`, style: { top: i * KEY_HEIGHT, height: KEY_HEIGHT } }),
5661                              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute w-full h-px ${isC ? "bg-zinc-300 dark:bg-zinc-700" : "bg-zinc-200/60 dark:bg-zinc-800/40"}`, style: { top: i * KEY_HEIGHT } })
5662                            ] }, i);
5663                          }),
5664                          verticalLines,
5665                          /* @__PURE__ */ jsxRuntimeExports.jsx(
5666                            "div",
5667                            {
5668                              ref: playheadElRef,
5669                              className: "absolute top-0 h-full w-[2px] bg-red-500 z-40 pointer-events-none",
5670                              style: { left: playheadX, display: showPlayhead ? "block" : "none" }
5671                            }
5672                          ),
5673                          loopMarkerTick !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
5674                            "div",
5675                            {
5676                              className: "absolute top-0 h-full w-px bg-emerald-500/60 dark:bg-emerald-500/40 z-30 pointer-events-none border-l border-dashed border-emerald-400",
5677                              style: { left: loopMarkerTick * pxPerTick }
5678                            }
5679                          ),
5680                          showGhostNotes && engine.midi.tracks.map((t, tIdx) => {
5681                            if (tIdx === activeTrackIndex) return null;
5682                            if (ghostTrackVisibility[tIdx] === false) return null;
5683                            return t.notes.map((note, nIdx) => {
5684                              if (note.midi > actualMaxNote || note.midi < actualMinNote) return null;
5685                              const top = (actualMaxNote - note.midi) * KEY_HEIGHT;
5686                              const left = note.ticks * pxPerTick;
5687                              const width = note.durationTicks * pxPerTick;
5688                              const trackColor = t.color || getTrackColor(tIdx);
5689                              return /* @__PURE__ */ jsxRuntimeExports.jsx(
5690                                "div",
5691                                {
5692                                  className: "absolute h-[14px] rounded-sm opacity-20 hover:opacity-50 cursor-pointer border transition-all z-0",
5693                                  style: {
5694                                    top: top + 1,
5695                                    left,
5696                                    width: Math.max(width, 4),
5697                                    backgroundColor: trackColor,
5698                                    borderColor: trackColor
5699                                  },
5700                                  onMouseMove: (e) => {
5701                                    const noteStartText = formatTicksToFlTime(note.ticks, ppq);
5702                                    const noteDurText = formatDurationTicksToFlTime(note.durationTicks, ppq);
5703                                    const noteNameStr = isDrumMode ? GM_PERCUSSION_MAP[note.midi] || `Drum ${note.midi}` : getNoteName(note.midi);
5704                                    setHint({
5705                                      label: `${t.name || `Track ${tIdx + 1}`}: ${noteNameStr}`,
5706                                      positionText: `${noteStartText} for ${noteDurText}`,
5707                                      noteText: `${noteNameStr} / ${note.midi}`,
5708                                      iconType: "note"
5709                                    });
5710                                  },
5711                                  onMouseLeave: () => {
5712                                    setHint(null);
5713                                  },
5714                                  onDoubleClick: (e) => {
5715                                    e.stopPropagation();
5716                                    e.preventDefault();
5717                                    setActiveTrackIndex(tIdx);
5718                                    const targetPattern = patterns[tIdx];
5719                                    if (targetPattern) {
5720                                      setActivePatternId(targetPattern.id);
5721                                      const noteIdx = targetPattern.notes.findIndex(
5722                                        (n) => n.midi === note.midi && Math.abs(n.ticks - note.ticks) < 2 && Math.abs(n.durationTicks - note.durationTicks) < 2
5723                                      );
5724                                      if (noteIdx !== -1) {
5725                                        setSelectedNoteIds([noteIdx]);
5726                                      } else {
5727                                        setSelectedNoteIds([]);
5728                                      }
5729                                    }
5730                                  }
5731                                },
5732                                `ghost-${tIdx}-${nIdx}`
5733                              );
5734                            });
5735                          }),
5736                          notes.map((note, idx) => {
5737                            if (note.midi > actualMaxNote || note.midi < actualMinNote) return null;
5738                            const top = (actualMaxNote - note.midi) * KEY_HEIGHT;
5739                            const left = note.ticks * pxPerTick;
5740                            const width = note.durationTicks * pxPerTick;
5741                            const isSelected = selectedNoteIds.includes(idx);
5742                            const noteVoice = note.voice || 1;
5743                            const isActiveVoice = noteVoice === activeVoice;
5744                            const bgClass = isSelected ? "bg-[#ff5252] border-[#9e2a2a]" : noteVoice === 1 ? "bg-[#0072B2] border-[#005f9e]" : noteVoice === 2 ? "bg-[#009E73] border-[#00825f]" : noteVoice === 3 ? "bg-[#D55E00] border-[#b85100]" : "bg-[#CC79A7] border-[#b36a92]";
5745                            const hoverClass = isSelected ? "hover:bg-[#ff3333]" : noteVoice === 1 ? "hover:bg-[#005f9e]" : noteVoice === 2 ? "hover:bg-[#00825f]" : noteVoice === 3 ? "hover:bg-[#b85100]" : "hover:bg-[#b36a92]";
5746                            const shadowClass = isSelected ? "shadow-[0_0_10px_rgba(255,82,82,0.8)]" : noteVoice === 1 ? "shadow-[0_0_10px_rgba(0,114,178,0.8)]" : noteVoice === 2 ? "shadow-[0_0_10px_rgba(0,158,115,0.8)]" : noteVoice === 3 ? "shadow-[0_0_10px_rgba(213,94,0,0.8)]" : "shadow-[0_0_10px_rgba(204,121,167,0.8)]";
5747                            const isMuted = note.isMuted;
5748                            const muteClass = isMuted ? "opacity-30 grayscale" : "";
5749                            return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5750                              "div",
5751                              {
5752                                className: `absolute h-[14px] rounded border shadow cursor-move group ${bgClass} ${hoverClass} ${muteClass} ${draggingNoteIdx === idx ? `z-50 ${shadowClass}` : isSelected ? `z-40 ring-1 ring-red-500 shadow-[0_0_8px_rgba(255,82,82,0.6)]` : "z-10"} ${!isActiveVoice ? "opacity-30 pointer-events-none" : ""} flex items-center px-1 overflow-hidden`,
5753                                style: { top: top + 1, left, width: Math.max(width, 4) },
5754                                onMouseMove: (e) => {
5755                                  e.stopPropagation();
5756                                  const noteStartText = formatTicksToFlTime(note.ticks, ppq);
5757                                  const noteDurText = formatDurationTicksToFlTime(note.durationTicks, ppq);
5758                                  const noteNameStr = isDrumMode ? GM_PERCUSSION_MAP[note.midi] || `Drum ${note.midi}` : getNoteName(note.midi);
5759                                  setHint({
5760                                    label: `Note: ${noteNameStr}`,
5761                                    positionText: `${noteStartText} for ${noteDurText}`,
5762                                    noteText: `${noteNameStr} / ${note.midi}`,
5763                                    iconType: "note"
5764                                  });
5765                                },
5766                                onMouseLeave: () => {
5767                                  setHint(null);
5768                                },
5769                                onMouseDown: (e) => {
5770                                  var _a2;
5771                                  e.stopPropagation();
5772                                  (_a2 = containerRef.current) == null ? void 0 : _a2.focus();
5773                                  if (e.altKey && e.shiftKey) {
5774                                    updateNote(activeTrackIndex, idx, { isMuted: !isMuted }, "pianoroll");
5775                                    return;
5776                                  }
5777                                  if (e.altKey || e.shiftKey) {
5778                                    const notesToDuplicate = selectedNoteIds.includes(idx) ? selectedNoteIds : [idx];
5779                                    const clickedOriginal = notes[idx];
5780                                    const duplicates = notesToDuplicate.map((id) => {
5781                                      const original = notes[id];
5782                                      return {
5783                                        midi: original.midi,
5784                                        ticks: original.ticks,
5785                                        durationTicks: original.durationTicks,
5786                                        velocity: original.velocity ?? 0.8,
5787                                        voice: original.voice || 1
5788                                      };
5789                                    });
5790                                    addNotes(activeTrackIndex, duplicates, "pianoroll");
5791                                    const newDuplicateIndices = duplicates.map((_, dIdx) => notes.length + dIdx);
5792                                    const clickedOriginalIndexInSelection = notesToDuplicate.indexOf(idx);
5793                                    const dragTargetIdx = clickedOriginalIndexInSelection !== -1 ? newDuplicateIndices[clickedOriginalIndexInSelection] : newDuplicateIndices[0];
5794                                    if (newDuplicateIndices.length > 0) {
5795                                      setSelectedNoteIds(newDuplicateIndices);
5796                                    }
5797                                    if (dragTargetIdx !== void 0 && dragTargetIdx !== -1) {
5798                                      setDraggingNoteIdx(dragTargetIdx);
5799                                      setDragStartPos({ x: e.clientX, y: e.clientY });
5800                                      setDragStartProps({
5801                                        midi: clickedOriginal.midi,
5802                                        ticks: clickedOriginal.ticks,
5803                                        durationTicks: clickedOriginal.durationTicks
5804                                      });
5805                                      setDragType("move");
5806                                      setIsShiftDrag(e.shiftKey);
5807                                      setDragConstraint("none");
5808                                      dragSelectedNotesRef.current = newDuplicateIndices.map((newIdx, dIdx) => {
5809                                        const origIdx = notesToDuplicate[dIdx];
5810                                        const original = notes[origIdx];
5811                                        return {
5812                                          idx: newIdx,
5813                                          ticks: original.ticks,
5814                                          midi: original.midi,
5815                                          durationTicks: original.durationTicks
5816                                        };
5817                                      });
5818                                      if (engine && !isMuted) {
5819                                        engine.startNotePreview(cl
5819ickedOriginal.midi, track.channel);
5820                                        auditionPitchRef.current = clickedOriginal.midi;
5821                                        setGridPreviewMidi(clickedOriginal.midi);
5822                                      }
5823                                    }
5824                                  } else {
5825                                    if (e.ctrlKey) {
5826                                      if (selectedNoteIds.includes(idx)) {
5827                                        setSelectedNoteIds(selectedNoteIds.filter((id) => id !== idx));
5828                                      } else {
5829                                        setSelectedNoteIds([...selectedNoteIds, idx]);
5830                                      }
5831                                    } else if (!selectedNoteIds.includes(idx)) {
5832                                      setSelectedNoteIds([idx]);
5833                                    }
5834                                    setDraggingNoteIdx(idx);
5835                                    setDragStartPos({ x: e.clientX, y: e.clientY });
5836                                    setDragStartProps({ midi: note.midi, ticks: note.ticks, durationTicks: note.durationTicks });
5837                                    setDragType("move");
5838                                    setIsShiftDrag(false);
5839                                    setDragConstraint("none");
5840                                    const selectedIds = selectedNoteIds.includes(idx) ? selectedNoteIds : [idx];
5841                                    dragSelectedNotesRef.current = selectedIds.map((sIdx) => ({
5842                                      idx: sIdx,
5843                                      ticks: notes[sIdx].ticks,
5844                                      midi: notes[sIdx].midi,
5845                                      durationTicks: notes[sIdx].durationTicks
5846                                    }));
5847                                    if (engine && !isMuted) {
5848                                      engine.startNotePreview(note.midi, track.channel);
5849                                      auditionPitchRef.current = note.midi;
5850                                      setGridPreviewMidi(note.midi);
5851                                    }
5852                                  }
5853                                },
5854                                onMouseUp: () => {
5855                                  if (engine) {
5856                                    engine.stopNotePreview(note.midi, track.channel);
5857                                  }
5858                                  auditionPitchRef.current = null;
5859                                  setGridPreviewMidi(null);
5860                                },
5861                                onContextMenu: (e) => {
5862                                  e.preventDefault();
5863                                  e.stopPropagation();
5864                                  deleteNote(activeTrackIndex, idx, "pianoroll");
5865                                  forceSync();
5866                                },
5867                                onMouseEnter: (e) => {
5868                                  if ((e.buttons & 2) !== 0) {
5869                                    deleteNote(activeTrackIndex, idx, "pianoroll");
5870                                    forceSync();
5871                                  }
5872                                },
5873                                children: [
5874                                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-bold text-white/95 select-none pointer-events-none truncate z-20", children: isDrumMode ? GM_PERCUSSION_MAP[note.midi] || `Drum ${note.midi}` : getNoteName(note.midi) }),
5875                                  /* @__PURE__ */ jsxRuntimeExports.jsx(
5876                                    "div",
5877                                    {
5878                                      className: "absolute left-0 top-0 w-2 h-full cursor-ew-resize opacity-0 group-hover:opacity-100 bg-white/20 z-30",
5879                                      onMouseDown: (e) => {
5880                                        e.stopPropagation();
5881                                        setDraggingNoteIdx(idx);
5882                                        setDragStartPos({ x: e.clientX, y: e.clientY });
5883                                        setDragStartProps({ midi: note.midi, ticks: note.ticks, durationTicks: note.durationTicks });
5884                                        setDragType("resize-left");
5885                                      }
5886                                    }
5887                                  ),
5888                                  /* @__PURE__ */ jsxRuntimeExports.jsx(
5889                                    "div",
5890                                    {
5891                                      className: "absolute right-0 top-0 w-2 h-full cursor-ew-resize opacity-0 group-hover:opacity-100 bg-white/20 z-30",
5892                                      onMouseDown: (e) => {
5893                                        e.stopPropagation();
5894                                        setDraggingNoteIdx(idx);
5895                                        setDragStartPos({ x: e.clientX, y: e.clientY });
5896                                        setDragStartProps({ midi: note.midi, ticks: note.ticks, durationTicks: note.durationTicks });
5897                                        setDragType("resize-right");
5898                                      }
5899                                    }
5900                                  )
5901                                ]
5902                              },
5903                              `${midiVersion}-${idx}`
5904                            );
5905                          }),
5906                          lassoStart && lassoCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx(
5907                            "div",
5908                            {
5909                              className: "absolute border border-indigo-500 bg-indigo-500/20 z-50 pointer-events-none",
5910                              style: {
5911                                left: Math.min(lassoStart.x, lassoCurrent.x),
5912                                top: Math.min(lassoStart.y, lassoCurrent.y),
5913                                width: Math.abs(lassoCurrent.x - lassoStart.x),
5914                                height: Math.abs(lassoCurrent.y - lassoStart.y)
5915                              }
5916                            }
5917                          ),
5918                          sliceStart && sliceCurrent && /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "absolute inset-0 pointer-events-none z-[100]", style: { width: totalWidth, height: actualNumKeys * KEY_HEIGHT }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
5919                            "line",
5920                            {
5921                              x1: sliceStart.x,
5922                              y1: sliceStart.y,
5923                              x2: sliceCurrent.x,
5924                              y2: sliceCurrent.y,
5925                              stroke: "#f43f5e",
5926                              strokeWidth: "2",
5927                              strokeDasharray: "4 2"
5928                            }
5929                          ) })
5930                        ]
5931                      }
5932                    )
5933                  ]
5934                }
5935              ),
5936              contextMenuPos && /* @__PURE__ */ jsxRuntimeExports.jsx(
5937                ContextMenu,
5938                {
5939                  x: contextMenuPos.x,
5940                  y: contextMenuPos.y,
5941                  onClose: () => setContextMenuPos(null),
5942                  toolMode,
5943                  setToolMode,
5944                  scaleRoot,
5945                  setScaleRoot,
5946                  scaleType,
5947                  setScaleType,
5948                  chordStamp,
5949                  setChordStamp,
5950                  gridSnap,
5951                  setGridSnap,
5952                  isDrumMode
5953                }
5954              ),
5955              metaModal && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm", onClick: () => setMetaModal(null), onContextMenu: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 p-5 rounded-lg shadow-2xl border border-zinc-200 dark:border-zinc-800 w-80", onClick: (e) => e.stopPropagation(), children: [
5956                /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-lg mb-4 text-zinc-900 dark:text-zinc-100", children: metaModal.isEdit ? "Edit Timeline Event" : "Add Timeline Event" }),
5957                !metaModal.isEdit && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mb-4", children: [
5958                  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm text-zinc-700 dark:text-zinc-300", children: [
5959                    /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "radio", checked: metaType === "tempo", onChange: () => setMetaType("tempo"), className: "accent-indigo-500" }),
5960                    " Tempo"
5961                  ] }),
5962                  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm text-zinc-700 dark:text-zinc-300", children: [
5963                    /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "radio", checked: metaType === "timesig", onChange: () => setMetaType("timesig"), className: "accent-indigo-500" }),
5964                    " Time Signature"
5965                  ] })
5966                ] }),
5967                metaType === "tempo" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
5968                  /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 mb-1", children: "BPM" }),
5969                  /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: metaBpm, onChange: (e) => setMetaBpm(e.target.value), className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded px-3 py-2 text-zinc-900 dark:text-zinc-100 outline-none focus:border-indigo-500" })
5970                ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 flex gap-2", children: [
5971                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
5972                    /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 mb-1", children: "Numerator" }),
5973                    /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: metaNum, onChange: (e) => setMetaNum(e.target.value), className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded px-3 py-2 text-zinc-900 dark:text-zinc-100 outline-none focus:border-indigo-500" })
5974                  ] }),
5975                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
5976                    /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 mb-1", children: "Denominator" }),
5977                    /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: metaDen, onChange: (e) => setMetaDen(e.target.value), className: "w-full bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded px-3 py-2 text-zinc-900 dark:text-zinc-100 outline-none focus:border-indigo-500" })
5978                  ] })
5979                ] }),
5980                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mt-6", children: [
5981                  metaModal.isEdit ? /* @__PURE__ */ jsxRuntimeExports.jsx(
5982                    "button",
5983                    {
5984                      onClick: () => {
5985                        if (metaType === "tempo") engine.deleteTempo(metaModal.originalTick);
5986                        else engine.deleteTimeSignature(metaModal.originalTick);
5987                        forceSync();
5988                        setMetaModal(null);
5989                      },
5990                      className: "px-3 py-2 text-red-500 hover:text-red-600 text-sm font-bold transition-colors",
5991                      children: "Delete"
5992                    }
5993                  ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}),
5994                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
5995                    /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setMetaModal(null), className: "px-4 py-2 text-sm font-bold text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100", children: "Cancel" }),
5996                    /* @__PURE__ */ jsxRuntimeExports.jsx(
5997                      "button",
5998                      {
5999                        onClick: () => {
6000                          if (metaType === "tempo") {
6001                            const bpm = parseFloat(metaBpm);
6002                            if (!isNaN(bpm) && bpm > 0) {
6003                              if (metaModal.isEdit) engine.updateTempo(metaModal.originalTick, metaModal.tick, bpm);
6004                              else engine.addTempo(metaModal.tick, bpm);
6005                              forceSync();
6006                            }
6007                          } else {
6008                            const num = parseInt(metaNum);
6009                            const den = parseInt(metaDen);
6010                            if (!isNaN(num) && !isNaN(den) && num > 0 && den > 0) {
6011                              if (metaModal.isEdit) engine.updateTimeSignature(metaModal.originalTick, metaModal.tick, num, den);
6012                              else engine.addTimeSignature(metaModal.tick, num, den);
6013                              forceSync();
6014                            }
6015                          }
6016                          setMetaModal(null);
6017                        },
6018                        className: "px-4 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded text-sm font-bold shadow-sm transition-colors",
6019                        children: metaModal.isEdit ? "Save Changes" : "Save Event"
6020                      }
6021                    )
6022                  ] })
6023                ] })
6024              ] }) })
6025            ] })
6026          ]
6027        }
6028      )
6029    ] }),
6030    /* @__PURE__ */ jsxRuntimeExports.jsx(Qt, { className: "h-2 bg-zinc-200 dark:bg-zinc-800 cursor-row-resize flex items-center justify-center z-50", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-1 rounded-full bg-zinc-300 dark:bg-zinc-700" }) }),
6031    /* @__PURE__ */ jsxRuntimeExports.jsxs(Yt, { defaultSize: 20, minSize: 10, maxSize: 50, className: "bg-white dark:bg-zinc-900 border-t border-zinc-200 dark:border-zinc-800 relative select-none z-10 flex", children: [
6032      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `h-full shrink-0 border-r border-zinc-200 dark:border-zinc-800 bg-zinc-100 dark:bg-zinc-950 p-2 flex flex-col gap-2 z-20 ${isDrumMode ? "w-32" : "w-24"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
6033        "select",
6034        {
6035          value: bottomPaneMode,
6036          onChange: (e) => setBottomPaneMode(e.target.value),
6037          className: "w-full bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded text-[10px] p-1 font-bold text-zinc-600 dark:text-zinc-300 outline-none cursor-pointer",
6038          children: [
6039            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "velocity", children: "Note velocity" }),
6040            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "release", children: "Note release" }),
6041            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pan", children: "Note panning" }),
6042            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pitch", children: "Note pitch" }),
6043            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "modX", children: "Note mod X" }),
6044            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "modY", children: "Note mod Y" }),
6045            /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "shift", children: "Note shift" })
6046          ]
6047        }
6048      ) }),
6049      /* @__PURE__ */ jsxRuntimeExports.jsxs(
6050        "div",
6051        {
6052          ref: bottomPaneRef,
6053          className: "flex-1 min-w-0 overflow-x-hidden overflow-y-hidden relative custom-scrollbar bg-zinc-50 dark:bg-zinc-900/40",
6054          onMouseDown: (e) => {
6055            const rect = e.currentTarget.getBoundingClientRect();
6056            const x = e.clientX - rect.left;
6057            const y = e.clientY - rect.top;
6058            const val = Math.max(0, Math.min(1, 1 - y / rect.height));
6059            const idx = findNoteAtX(x);
6060            if (idx !== -1) {
6061              setDraggingVelocityIdx(idx);
6062              dragStartValRef.current = val;
6063              const isSelected = selectedNoteIds.includes(idx);
6064              if (isSelected) {
6065                dragSelectedNoteValsRef.current = selectedNoteIds.map((sIdx) => ({
6066                  idx: sIdx,
6067                  startVal: getNotePropertyValue(notes[sIdx], bottomPaneMode)
6068                }));
6069              } else {
6070                dragSelectedNoteValsRef.current = [{
6071                  idx,
6072                  startVal: getNotePropertyValue(notes[idx], bottomPaneMode)
6073                }];
6074              }
6075              updateNote(activeTrackIndex, idx, getPropertyUpdateObject(bottomPaneMode, val), "pianoroll");
6076            }
6077          },
6078          onMouseMove: (e) => {
6079            const rect = e.currentTarget.getBoundingClientRect();
6080            const x = e.clientX - rect.left;
6081            const y = e.clientY - rect.top;
6082            const val = Math.max(0, Math.min(1, 1 - y / rect.height));
6083            if (draggingVelocityIdx !== null) {
6084              if (dragStartValRef.current !== null && dragSelectedNoteValsRef.current.length > 0) {
6085                const delta = val - dragStartValRef.current;
6086                dragSelectedNoteValsRef.current.forEach((item) => {
6087                  const newVal = Math.max(0, Math.min(1, item.startVal + delta));
6088                  updateNote(activeTrackIndex, item.idx, getPropertyUpdateObject(bottomPaneMode, newVal), "pianoroll");
6089                });
6090              } else {
6091                updateNote(activeTrackIndex, draggingVelocityIdx, getPropertyUpdateObject(bottomPaneMode, val), "pianoroll");
6092              }
6093              setHint({
6094                label: formatTooltipValue(bottomPaneMode, val),
6095                positionText: "",
6096                noteText: "",
6097                iconType: "velocity"
6098              });
6099            } else {
6100              const idx = findNoteAtX(x);
6101              if (idx !== -1) {
6102                const noteVal = getNotePropertyValue(notes[idx], bottomPaneMode);
6103                const noteNameStr = isDrumMode ? GM_PERCUSSION_MAP[notes[idx].midi] || `Drum ${notes[idx].midi}` : getNoteName(notes[idx].midi);
6104                setHint({
6105                  label: `${noteNameStr}: ${formatTooltipValue(bottomPaneMode, noteVal)}`,
6106                  positionText: `Start: ${formatTicksToFlTime(notes[idx].ticks, ppq)}`,
6107                  noteText: `${noteNameStr} / ${notes[idx].midi}`,
6108                  iconType: "velocity"
6109                });
6110              }
6111            }
6112          },
6113          onMouseUp: () => {
6114            setDraggingVelocityIdx(null);
6115            dragStartValRef.current = null;
6116            dragSelectedNoteValsRef.current = [];
6117          },
6118          onMouseLeave: () => {
6119            setDraggingVelocityIdx(null);
6120            dragStartValRef.current = null;
6121            dragSelectedNoteValsRef.current = [];
6122            setHint(null);
6123          },
6124          children: [
6125            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 h-full flex pointer-events-none", style: { width: totalWidth }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-full h-1/2 border-b ${bottomPaneMode === "pan" || bottomPaneMode === "pitch" || bottomPaneMode === "shift" ? "border-zinc-400 dark:border-zinc-500 border-dashed z-10" : "border-zinc-200/50 dark:border-zinc-800/50"}` }) }),
6126            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 h-full", style: { width: totalWidth }, children: notes.map((note, idx) => {
6127              const left = note.ticks * pxPerTick;
6128              const isSelected = selectedNoteIds.includes(idx);
6129              const val = getNotePropertyValue(note, bottomPaneMode);
6130              const isBipolar = bottomPaneMode === "pan" || bottomPaneMode === "pitch" || bottomPaneMode === "shift";
6131              const noteVoice = note.voice || 1;
6132              const stalkBgClass = isSelected ? "bg-[#ff5252]" : noteVoice === 1 ? "bg-[#0072B2]" : noteVoice === 2 ? "bg-[#009E73]" : noteVoice === 3 ? "bg-[#D55E00]" : "bg-[#CC79A7]";
6133              const handleBgClass = isSelected ? "bg-[#ff5252] border-white shadow-[0_0_6px_rgba(255,82,82,0.6)]" : noteVoice === 1 ? "bg-[#0072B2] border-white" : noteVoice === 2 ? "bg-[#009E73] border-white" : noteVoice === 3 ? "bg-[#D55E00] border-white" : "bg-[#CC79A7] border-white";
6134              let stalkStyle = {};
6135              if (isBipolar) {
6136                if (val >= 0.5) {
6137                  stalkStyle = {
6138                    bottom: "50%",
6139                    height: `${(val - 0.5) * 100}%`
6140                  };
6141                } else {
6142                  stalkStyle = {
6143                    top: "50%",
6144                    height: `${(0.5 - val) * 100}%`
6145                  };
6146                }
6147              } else {
6148                stalkStyle = {
6149                  bottom: 0,
6150                  height: `${val * 100}%`
6151                };
6152              }
6153              return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6154                "div",
6155                {
6156                  className: "absolute bottom-0 w-3 flex justify-center group cursor-ns-resize z-20",
6157                  style: { left: left - 2, height: "100%" },
6158                  onMouseDown: (e) => {
6159                    var _a2;
6160                    e.stopPropagation();
6161                    setDraggingVelocityIdx(idx);
6162                    const rect = (_a2 = bottomPaneRef.current) == null ? void 0 : _a2.getBoundingClientRect();
6163                    if (rect) {
6164                      const y = e.clientY - rect.top;
6165                      const v = Math.max(0, Math.min(1, 1 - y / rect.height));
6166                      dragStartValRef.current = v;
6167                    } else {
6168                      dragStartValRef.current = val;
6169                    }
6170                    const isSel = selectedNoteIds.includes(idx);
6171                    if (isSel) {
6172                      dragSelectedNoteValsRef.current = selectedNoteIds.map((sIdx) => ({
6173                        idx: sIdx,
6174                        startVal: getNotePropertyValue(notes[sIdx], bottomPaneMode)
6175                      }));
6176                    } else {
6177                      dragSelectedNoteValsRef.current = [{
6178                        idx,
6179                        startVal: val
6180                      }];
6181                    }
6182                  },
6183                  children: [
6184                    /* @__PURE__ */ jsxRuntimeExports.jsx(
6185                      "div",
6186                      {
6187                        className: `absolute w-[2px] transition-colors ${stalkBgClass}`,
6188                        style: stalkStyle
6189                      }
6190                    ),
6191                    /* @__PURE__ */ jsxRuntimeExports.jsx(
6192                      "div",
6193                      {
6194                        className: `absolute w-2.5 h-2.5 rounded-full border shadow-sm transition-colors ${handleBgClass}`,
6195                        style: { bottom: `calc(${val * 100}% - 5px)` }
6196                      }
6197                    ),
6198                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute -top-6 bg-zinc-800 text-white text-[9px] px-1.5 py-0.5 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity z-50 whitespace-nowrap shadow-md", children: formatTooltipValue(bottomPaneMode, val) })
6199                  ]
6200                },
6201                `vel-${idx}`
6202              );
6203            }) })
6204          ]
6205        }
6206      )
6207    ] })
6208  ] });
6209};
6210const ScoreRibbon = ({
6211  currentTool,
6212  onSelectTool,
6213  isPlaying,
6214  onTogglePlay,
6215  onStop,
6216  onZoomIn,
6217  onZoomOut,
6218  showMeasureNumbers,
6219  onToggleMeasureNumbers,
6220  currentAccidental,
6221  onSelectAccidental,
6222  isPercussion,
6223  showKeypad,
6224  onToggleKeypad,
6225  showTextEditor,
6226  onToggleTextEditor,
6227  entryMode,
6228  onSelectEntryMode,
6229  isDotted,
6230  onToggleDotted,
6231  isTieActive,
6232  onToggleTieActive,
6233  isTriplet,
6234  onToggleTriplet,
6235  currentArticulation,
6236  onSelectArticulation,
6237  showGrid = true,
6238  onToggleGrid,
6239  onStartTour
6240}) => {
6241  const [activeTab, setActiveTab] = reactExports.useState("Home");
6242  const svgPaths = {
6243    whole: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "12", cy: "12", rx: "7", ry: "4", fill: "none", stroke: "currentColor", strokeWidth: "2", transform: "rotate(-15 12 12)" }) }) }),
6244    half: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "14", height: "20", viewBox: "0 0 24 36", children: [
6245      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "none", stroke: "currentColor", strokeWidth: "2", transform: "rotate(-15 9 24)" }),
6246      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" })
6247    ] }) }),
6248    quarter: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "14", height: "20", viewBox: "0 0 24 36", children: [
6249      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6250      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" })
6251    ] }) }),
6252    eighth: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "14", height: "20", viewBox: "0 0 24 36", children: [
6253      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6254      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" }),
6255      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14 4 C 20 8, 18 16, 16 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
6256    ] }) }),
6257    sixteenth: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "14", height: "20", viewBox: "0 0 24 36", children: [
6258      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6259      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" }),
6260      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14 4 C 20 8, 18 16, 16 20 M14 12 C 20 16, 18 24, 16 28", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
6261    ] }) }),
6262    sharp: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base font-serif leading-none", children: "♯" }),
6263    flat: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base font-serif leading-none", children: "♭" }),
6264    natural: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-base font-serif leading-none", children: "♮" }),
6265    tie: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "20", height: "10", viewBox: "0 0 32 16", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 12 C 12 0, 20 0, 28 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }),
6266    dot: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "6", height: "6", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "6", fill: "currentColor" }) }),
6267    staccato: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "6", height: "6", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "6", fill: "currentColor" }) }) }),
6268    tenuto: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "12", height: "6", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "4", y: "10", width: "16", height: "4", fill: "currentColor" }) }) }),
6269    accent: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 6 L20 12 L4 18 L6 12 Z", fill: "currentColor" }) }) }),
6270    fermata: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", children: [
6271      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 14 C 4 4, 20 4, 20 14", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
6272      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "14", r: "2", fill: "currentColor" })
6273    ] }) })
6274  };
6275  const RibbonTab = ({ label }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6276    "button",
6277    {
6278      onClick: () => setActiveTab(label),
6279      className: `px-3 py-1 text-xs font-medium transition-colors ${activeTab === label ? "bg-[#f0f0f0] dark:bg-[#1e1e1e] border-t-2 border-indigo-500 text-indigo-600 dark:text-indigo-400" : "text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-200"}`,
6280      children: label
6281    }
6282  );
6283  const Group = ({ title, children }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full border-r border-zinc-300 dark:border-zinc-700 px-2 min-w-min justify-between py-1", children: [
6284    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center gap-1", children }),
6285    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] text-zinc-500 text-center flex items-center justify-center uppercase tracking-wider bg-zinc-100 dark:bg-zinc-800/50 rounded-sm w-full", children: title })
6286  ] });
6287  const RibbonBtn = ({ icon, label, onClick, isActive, large = false }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
6288    "button",
6289    {
6290      onClick,
6291      className: `flex ${large ? "flex-col justify-center px-2" : "items-center px-1.5 py-0.5"} gap-0.5 rounded hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors ${isActive ? "bg-indigo-100 dark:bg-indigo-500/30 text-indigo-600 dark:text-indigo-400" : "text-zinc-700 dark:text-zinc-300"} ${large ? "h-10" : "h-6"}`,
6292      children: [
6293        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: icon }),
6294        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] ${large ? "text-center leading-tight" : ""}`, children: label })
6295      ]
6296    }
6297  );
6298  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col w-full bg-[#e5e5e5] dark:bg-[#2d2d2d] border-b border-zinc-300 dark:border-zinc-800 select-none shrink-0", children: [
6299    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex px-2 pt-1 gap-1", children: [
6300      /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonTab, { label: "Home" }),
6301      /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonTab, { label: "Note Input" }),
6302      /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonTab, { label: "Play" }),
6303      /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonTab, { label: "View" })
6304    ] }),
6305    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-16 bg-[#f0f0f0] dark:bg-[#1e1e1e] flex items-stretch px-1 overflow-x-auto", children: [
6306      activeTab === "Home" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6307        /* @__PURE__ */ jsxRuntimeExports.jsx(Group, { title: "Select", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointer2, { size: 24 }), label: "Select", onClick: () => onSelectTool("select"), isActive: currentTool === "select" }) }),
6308        /* @__PURE__ */ jsxRuntimeExports.jsx(Group, { title: "Edit", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Eraser, { size: 24 }), label: "Erase", onClick: () => onSelectTool("erase"), isActive: currentTool === "erase" }) }),
6309        onStartTour && /* @__PURE__ */ jsxRuntimeExports.jsx(Group, { title: "Help", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6310          RibbonBtn,
6311          {
6312            large: true,
6313            icon: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-indigo-400", children: [
6314              /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "10" }),
6315              /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
6316              /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
6317            ] }),
6318            label: "Tour",
6319            onClick: onStartTour
6320          }
6321        ) })
6322      ] }),
6323      activeTab === "Note Input" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6324        /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Mode", children: [
6325          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-lg", children: "♩" }), label: "Note", onClick: () => onSelectEntryMode("note"), isActive: entryMode === "note" }),
6326          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-lg", children: "𝄾" }), label: "Rest", onClick: () => onSelectEntryMode("rest"), isActive: entryMode === "rest" })
6327        ] }),
6328        /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Notes/Rests", children: [
6329          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.whole, label: "Whole", onClick: () => onSelectTool("note_whole"), isActive: currentTool === "note_whole" }),
6330          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.half, label: "Half", onClick: () => onSelectTool("note_half"), isActive: currentTool === "note_half" }),
6331          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.quarter, label: "Quarter", onClick: () => onSelectTool("note_quarter"), isActive: currentTool === "note_quarter" }),
6332          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.eighth, label: "Eighth", onClick: () => onSelectTool("note_eighth"), isActive: currentTool === "note_eighth" }),
6333          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.sixteenth, label: "16th", onClick: () => onSelectTool("note_sixteenth"), isActive: currentTool === "note_sixteenth" })
6334        ] }),
6335        /* @__PURE__ */ jsxRuntimeExports.jsx(Group, { title: "Accidentals", children: !isPercussion ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6336          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.natural, label: "Natural", onClick: () => onSelectAccidental(currentAccidental === "natural" ? null : "natural"), isActive: currentAccidental === "natural" }),
6337          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.sharp, label: "Sharp", onClick: () => onSelectAccidental(currentAccidental === "sharp" ? null : "sharp"), isActive: currentAccidental === "sharp" }),
6338          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.flat, label: "Flat", onClick: () => onSelectAccidental(currentAccidental === "flat" ? null : "flat"), isActive: currentAccidental === "flat" })
6339        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6340          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold", children: "KICK" }), label: "Kick", onClick: () => onSelectAccidental(currentAccidental === "kick" ? null : "kick"), isActive: currentAccidental === "kick" }),
6341          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold", children: "SNARE" }), label: "Snare", onClick: () => onSelectAccidental(currentAccidental === "snare" ? null : "snare"), isActive: currentAccidental === "snare" }),
6342          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold", children: "HI-HAT" }), label: "Hi-Hat", onClick: () => onSelectAccidental(currentAccidental === "hihat" ? null : "hihat"), isActive: currentAccidental === "hihat" }),
6343          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold", children: "CYMBAL" }), label: "Cymbal", onClick: () => onSelectAccidental(currentAccidental === "cymbal" ? null : "cymbal"), isActive: currentAccidental === "cymbal" })
6344        ] }) }),
6345        /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Modifiers", children: [
6346          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", children: svgPaths.dot }) }), label: "Dot", onClick: onToggleDotted, isActive: isDotted }),
6347          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.tie, label: "Tie", onClick: onToggleTieActive, isActive: isTieActive }),
6348          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-lg", children: "3" }), label: "Triplet", onClick: onToggleTriplet, isActive: isTriplet })
6349        ] }),
6350        /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Articulations", children: [
6351          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.staccato, label: "Staccato", onClick: () => onSelectArticulation(currentArticulation === "staccato" ? null : "staccato"), isActive: currentArticulation === "staccato" }),
6352          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.accent, label: "Accent", onClick: () => onSelectArticulation(currentArticulation === "accent" ? null : "accent"), isActive: currentArticulation === "accent" }),
6353          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.tenuto, label: "Tenuto", onClick: () => onSelectArticulation(currentArticulation === "tenuto" ? null : "tenuto"), isActive: currentArticulation === "tenuto" }),
6354          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: svgPaths.fermata, label: "Fermata", onClick: () => onSelectArticulation(currentArticulation === "fermata" ? null : "fermata"), isActive: currentArticulation === "fermata" })
6355        ] }),
6356        /* @__PURE__ */ jsxRuntimeExports.jsx(Group, { title: "Keypad", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6357          RibbonBtn,
6358          {
6359            large: true,
6360            icon: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
6361              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
6362              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "7", width: "2", height: "2" }),
6363              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "11", y: "7", width: "2", height: "2" }),
6364              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "15", y: "7", width: "2", height: "2" }),
6365              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "11", width: "2", height: "2" }),
6366              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "11", y: "11", width: "2", height: "2" }),
6367              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "15", y: "11", width: "2", height: "2" }),
6368              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "7", y: "15", width: "2", height: "2" }),
6369              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "11", y: "15", width: "2", height: "2" }),
6370              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "15", y: "15", width: "2", height: "2" })
6371            ] }),
6372            label: showKeypad ? "Hide Keypad" : "Show Keypad",
6373            onClick: () => onToggleKeypad(!showKeypad),
6374            isActive: showKeypad
6375          }
6376        ) })
6377      ] }),
6378      activeTab === "Play" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Transport", children: [
6379        /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: isPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { size: 24 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 24 }), label: isPlaying ? "Pause" : "Play", onClick: onTogglePlay, isActive: isPlaying }),
6380        /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Square, { size: 24 }), label: "Stop", onClick: onStop || onTogglePlay, isActive: !isPlaying })
6381      ] }) }),
6382      activeTab === "View" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6383        /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Zoom", children: [
6384          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 24 }), label: "Zoom In", onClick: onZoomIn }),
6385          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { size: 24 }), label: "Zoom Out", onClick: onZoomOut })
6386        ] }),
6387        /* @__PURE__ */ jsxRuntimeExports.jsxs(Group, { title: "Visibility", children: [
6388          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Hash, { size: 24 }), label: "Measure Nos", onClick: onToggleMeasureNumbers, isActive: showMeasureNumbers }),
6389          /* @__PURE__ */ jsxRuntimeExports.jsx(RibbonBtn, { large: true, icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Grid3x3, { size: 24 }), label: "Grid Lines", onClick: onToggleGrid || (() => {
6390          }), isActive: showGrid }),
6391          /* @__PURE__ */ jsxRuntimeExports.jsx(
6392            RibbonBtn,
6393            {
6394              large: true,
6395              icon: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
6396                /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "16 18 22 12 16 6" }),
6397                /* @__PURE__ */ jsxRuntimeExports.jsx("polyline", { points: "8 6 2 12 8 18" })
6398              ] }),
6399              label: showTextEditor ? "Hide Code" : "Show Code",
6400              onClick: onToggleTextEditor,
6401              isActive: showTextEditor
6402            }
6403          )
6404        ] })
6405      ] })
6406    ] })
6407  ] });
6408};
6409const ScoreDocumentTabs = () => {
6410  const { engine, activeTrackIndex, setActiveTrackIndex, midiVersion } = useEditor();
6411  if (!engine || !engine.midi) return null;
6412  const tracks = engine.midi.tracks;
6413  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 bg-zinc-200 dark:bg-[#1a1a1a] border-b border-zinc-300 dark:border-zinc-800 flex items-end px-2 gap-1 overflow-x-auto shrink-0 select-none", children: [
6414    /* @__PURE__ */ jsxRuntimeExports.jsx(
6415      "button",
6416      {
6417        onClick: () => setActiveTrackIndex(null),
6418        className: `px-4 py-1.5 text-xs font-medium rounded-t-lg border border-b-0 transition-colors ${activeTrackIndex === null ? "bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-800 text-zinc-900 dark:text-zinc-100" : "bg-zinc-100 dark:bg-zinc-800/50 border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-700"}`,
6419        children: "Transposed score"
6420      }
6421    ),
6422    tracks.map((track, idx) => {
6423      const isDrums = track.instrument.percussion;
6424      const instName = track.instrument.name || (isDrums ? "Drums" : `Track ${idx + 1}`);
6425      const name = track.name ? track.name : instName;
6426      return /* @__PURE__ */ jsxRuntimeExports.jsx(
6427        "button",
6428        {
6429          onClick: () => setActiveTrackIndex(idx),
6430          className: `px-4 py-1.5 text-xs font-medium rounded-t-lg border border-b-0 transition-colors whitespace-nowrap ${activeTrackIndex === idx ? "bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-800 text-zinc-900 dark:text-zinc-100" : "bg-zinc-100 dark:bg-zinc-800/50 border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-700"}`,
6431          title: name,
6432          children: name
6433        },
6434        idx
6435      );
6436    })
6437  ] });
6438};
6439const ScoreKeypad = ({
6440  currentTool,
6441  onSelectTool,
6442  currentAccidental,
6443  onSelectAccidental,
6444  currentVoice = 1,
6445  onSelectVoice,
6446  isPercussion,
6447  onClose,
6448  currentArticulation,
6449  onSelectArticulation,
6450  isDotted = false,
6451  onToggleDotted,
6452  isTieActive = false,
6453  onToggleTieActive,
6454  entryMode = "note",
6455  onSelectEntryMode
6456}) => {
6457  const { t } = useTranslation();
6458  const [activeTab, setActiveTab] = reactExports.useState(1);
6459  const [isMinimized, setIsMinimized] = reactExports.useState(false);
6460  const [scale, setScale] = reactExports.useState(0.75);
6461  const keypadRef = reactExports.useRef(null);
6462  const [position, setPosition] = reactExports.useState({ x: 0, y: 0 });
6463  const dragRef = reactExports.useRef({
6464    isDragging: false,
6465    startX: 0,
6466    startY: 0,
6467    initialX: 0,
6468    initialY: 0
6469  });
6470  const handleMouseDown = (e) => {
6471    if (e.button !== 0) return;
6472    const target = e.target;
6473    if (target.closest("button") || target.closest("input") || target.closest("select") || target.closest("textarea") || target.classList.contains("resize-handle")) {
6474      return;
6475    }
6476    dragRef.current = {
6477      isDragging: true,
6478      startX: e.clientX,
6479      startY: e.clientY,
6480      initialX: position.x,
6481      initialY: position.y
6482    };
6483  };
6484  reactExports.useEffect(() => {
6485    const handleMouseMove = (e) => {
6486      if (!dragRef.current.isDragging) return;
6487      const dx = e.clientX - dragRef.current.startX;
6488      const dy = e.clientY - dragRef.current.startY;
6489      let newX = dragRef.current.initialX + dx;
6490      let newY = dragRef.current.initialY + dy;
6491      if (keypadRef.current && keypadRef.current.parentElement) {
6492        const parent = keypadRef.current.parentElement;
6493        const parentRect = parent.getBoundingClientRect();
6494        const keypadWidth = 240;
6495        const keypadHeight = keypadRef.current.offsetHeight || 380;
6496        const scaledWidth = keypadWidth * scale;
6497        const scaledHeight = keypadHeight * scale;
6498        const minX = 32 + scaledWidth - parentRect.width;
6499        const maxX = 32;
6500        const minY = 32 + scaledHeight - parentRect.height;
6501        const maxY = 32;
6502        newX = Math.max(minX, Math.min(maxX, newX));
6503        newY = Math.max(minY, Math.min(maxY, newY));
6504      }
6505      setPosition({ x: newX, y: newY });
6506    };
6507    const handleMouseUp = () => {
6508      dragRef.current.isDragging = false;
6509    };
6510    window.addEventListener("mousemove", handleMouseMove);
6511    window.addEventListener("mouseup", handleMouseUp);
6512    return () => {
6513      window.removeEventListener("mousemove", handleMouseMove);
6514      window.removeEventListener("mouseup", handleMouseUp);
6515    };
6516  }, [scale]);
6517  reactExports.useEffect(() => {
6518    const clampPosition = () => {
6519      if (!keypadRef.current) return;
6520      const parent = keypadRef.current.parentElement;
6521      if (!parent) return;
6522      const parentRect = parent.getBoundingClientRect();
6523      const keypadWidth = 240;
6524      const keypadHeight = keypadRef.current.offsetHeight || 380;
6525      const scaledWidth = keypadWidth * scale;
6526      const scaledHeight = keypadHeight * scale;
6527      setPosition((prev) => {
6528        const minX = 32 + scaledWidth - parentRect.width;
6529        const maxX = 32;
6530        const minY = 32 + scaledHeight - parentRect.height;
6531        const maxY = 32;
6532        return {
6533          x: Math.max(minX, Math.min(maxX, prev.x)),
6534          y: Math.max(minY, Math.min(maxY, prev.y))
6535        };
6536      });
6537    };
6538    clampPosition();
6539    window.addEventListener("resize", clampPosition);
6540    return () => window.removeEventListener("resize", clampPosition);
6541  }, [scale]);
6542  const handleResizeMouseDown = (corner) => (e) => {
6543    e.stopPropagation();
6544    e.preventDefault();
6545    const startX = e.clientX;
6546    const startY = e.clientY;
6547    const startScale = scale;
6548    const handleMouseMove = (moveEvent) => {
6549      const dx = moveEvent.clientX - startX;
6550      const dy = moveEvent.clientY - startY;
6551      let factor = 0;
6552      if (corner === "tl") {
6553        factor = -(dx + dy);
6554      } else if (corner === "tr") {
6555        factor = dx - dy;
6556      } else if (corner === "bl") {
6557        factor = -dx + dy;
6558      } else if (corner === "br") {
6559        factor = dx + dy;
6560      }
6561      const distance = factor * 2e-3;
6562      setScale(Math.min(1.5, Math.max(0.5, startScale + distance)));
6563    };
6564    const handleMouseUp = () => {
6565      window.removeEventListener("mousemove", handleMouseMove);
6566      window.removeEventListener("mouseup", handleMouseUp);
6567    };
6568    window.addEventListener("mousemove", handleMouseMove);
6569    window.addEventListener("mouseup", handleMouseUp);
6570  };
6571  const KeypadTab = ({ num }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6572    "button",
6573    {
6574      onClick: () => onSelectVoice == null ? void 0 : onSelectVoice(num),
6575      className: `flex-1 py-1.5 text-xs font-bold transition-colors ${currentVoice === num ? "bg-[#6366f1] text-white rounded" : "bg-[#2a2a35] text-zinc-300 hover:bg-[#3b3b4f]"}`,
6576      children: num === "All" ? t("editor.scoreKeypad.voiceAll", "All") : num
6577    }
6578  );
6579  const KeypadBtn = ({ active, onClick, svg, label, rowSpan = 1 }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6580    "button",
6581    {
6582      onClick,
6583      className: `rounded flex items-center justify-center transition-all ${active ? "bg-[#6366f1] text-white shadow-[inset_0_2px_4px_rgba(0,0,0,0.5)]" : "bg-[#363640] hover:bg-[#4a4a58] text-zinc-200 shadow-[inset_1px_1px_0px_rgba(255,255,255,0.05),1px_1px_3px_rgba(0,0,0,0.4)]"}`,
6584      style: { gridRow: `span ${rowSpan}`, minHeight: rowSpan === 2 ? "100px" : "48px" },
6585      title: label,
6586      children: svg
6587    }
6588  );
6589  const svgPaths = {
6590    whole: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "12", cy: "12", rx: "7", ry: "4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", transform: "rotate(-15 12 12)" }) }),
6591    half: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "32", viewBox: "0 0 24 36", children: [
6592      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "none", stroke: "currentColor", strokeWidth: "2.5", transform: "rotate(-15 9 24)" }),
6593      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" })
6594    ] }),
6595    quarter: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "32", viewBox: "0 0 24 36", children: [
6596      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6597      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" })
6598    ] }),
6599    eighth: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "32", viewBox: "0 0 24 36", children: [
6600      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6601      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" }),
6602      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14 4 C 20 8, 18 16, 16 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
6603    ] }),
6604    sixteenth: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "32", viewBox: "0 0 24 36", children: [
6605      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6606      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" }),
6607      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14 4 C 20 8, 18 16, 16 20 M14 12 C 20 16, 18 24, 16 28", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
6608    ] }),
6609    thirtysecond: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "32", viewBox: "0 0 24 36", children: [
6610      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "9", cy: "24", rx: "6", ry: "4", fill: "currentColor", transform: "rotate(-15 9 24)" }),
6611      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "14", y1: "24", x2: "14", y2: "4", stroke: "currentColor", strokeWidth: "2" }),
6612      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M14 4 C 20 8, 18 16, 16 20 M14 10 C 20 14, 18 22, 16 26 M14 16 C 20 20, 18 28, 16 32", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
6613    ] }),
6614    sharp: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl leading-none font-serif", children: "♯" }),
6615    flat: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl leading-none font-serif", children: "♭" }),
6616    natural: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl leading-none font-serif", children: "♮" }),
6617    tie: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "32", height: "16", viewBox: "0 0 32 16", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 12 C 12 0, 20 0, 28 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }),
6618    dot: /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "2.5", fill: "currentColor" }),
6619    restQuarter: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "24", viewBox: "0 0 24 36", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M10 2 L14 10 L8 18 L16 26 C16 30 10 34 6 30 C10 32 12 28 8 24 L14 16 L10 8 Z", fill: "currentColor" }) }),
6620    staccato: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor" }) }),
6621    tenuto: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "6", y: "10", width: "12", height: "2", fill: "currentColor" }) }),
6622    accent: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 6 L20 12 L4 18 L6 12 Z", fill: "currentColor" }) }),
6623    fermata: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", children: [
6624      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M4 14 C 4 4, 20 4, 20 14", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
6625      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "14", r: "2", fill: "currentColor" })
6626    ] })
6627  };
6628  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
6629    "div",
6630    {
6631      id: "score-keypad-panel",
6632      ref: keypadRef,
6633      className: "absolute z-50 rounded-xl shadow-[0_20px_40px_rgba(0,0,0,0.6)] border border-[#444] flex flex-col select-none overflow-hidden",
6634      style: {
6635        right: "32px",
6636        bottom: "32px",
6637        transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
6638        transformOrigin: "bottom right",
6639        backgroundColor: "#2b2b36",
6640        width: "240px"
6641      },
6642      onMouseDown: handleMouseDown,
6643      children: [
6644        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 left-0 w-3 h-3 cursor-nwse-resize z-[60] resize-handle", onMouseDown: handleResizeMouseDown("tl") }),
6645        /* @__P
6645URE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-0 right-0 w-3 h-3 cursor-nesw-resize z-[60] resize-handle", onMouseDown: handleResizeMouseDown("tr") }),
6646        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 left-0 w-3 h-3 cursor-nesw-resize z-[60] resize-handle", onMouseDown: handleResizeMouseDown("bl") }),
6647        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 right-0 w-3 h-3 cursor-nwse-resize z-[60] resize-handle", onMouseDown: handleResizeMouseDown("br") }),
6648        /* @__PURE__ */ jsxRuntimeExports.jsxs(
6649          "div",
6650          {
6651            className: "h-7 bg-[#343442] flex items-center justify-center px-3 cursor-move border-b border-[#111] relative",
6652            onMouseDown: handleMouseDown,
6653            children: [
6654              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute left-3 flex gap-1.5 ml-2", children: [
6655                /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "w-3 h-3 rounded-full bg-[#ff5f56] hover:bg-[#ff5f56]/80", title: t("editor.scoreKeypad.close", "Close") }),
6656                /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsMinimized(!isMinimized), className: "w-3 h-3 rounded-full bg-[#ffbd2e] hover:bg-[#ffbd2e]/80", title: t("editor.scoreKeypad.minimize", "Minimize") }),
6657                /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "w-3 h-3 rounded-full bg-[#27c93f] hover:bg-[#27c93f]/80", title: t("editor.scoreKeypad.maximize", "Maximize") })
6658              ] }),
6659              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[12px] text-zinc-400 font-medium pointer-events-none tracking-wide", children: t("editor.scoreKeypad.title", "Keypad") })
6660            ]
6661          }
6662        ),
6663        !isMinimized && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2.5 flex flex-col gap-2", children: [
6664          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-5 gap-1", children: [
6665            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab(1), className: `col-span-1 rounded flex items-center justify-center py-1 transition-colors ${activeTab === 1 ? "bg-[#6366f1] text-white" : "bg-[#363640] hover:bg-[#4a4a58] text-zinc-300"}`, children: svgPaths.whole }),
6666            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab(2), className: `col-span-1 rounded flex items-center justify-center py-1 transition-colors ${activeTab === 2 ? "bg-[#6366f1] text-white" : "bg-[#363640] hover:bg-[#4a4a58] text-zinc-300"}`, children: svgPaths.fermata }),
6667            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab(3), className: `col-span-1 rounded flex items-center justify-center py-1 transition-colors ${activeTab === 3 ? "bg-[#6366f1] text-white" : "bg-[#363640] hover:bg-[#4a4a58] text-zinc-300"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-serif", children: "𝄽" }) }),
6668            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab(4), className: `col-span-1 rounded flex items-center justify-center py-1 transition-colors ${activeTab === 4 ? "bg-[#6366f1] text-white" : "bg-[#363640] hover:bg-[#4a4a58] text-zinc-300"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-serif", children: "%" }) }),
6669            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab(5), className: `col-span-1 rounded flex items-center justify-center py-1 transition-colors ${activeTab === 5 ? "bg-[#6366f1] text-white" : "bg-[#363640] hover:bg-[#4a4a58] text-zinc-300"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-serif text-xs", children: "♭♭" }) })
6670          ] }),
6671          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-4 gap-1.5 mt-1", children: [
6672            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentArticulation === "fermata", onClick: () => onSelectArticulation == null ? void 0 : onSelectArticulation(currentArticulation === "fermata" ? null : "fermata"), label: t("editor.scoreKeypad.fermata", "Fermata"), svg: svgPaths.fermata }),
6673            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentArticulation === "accent", onClick: () => onSelectArticulation == null ? void 0 : onSelectArticulation(currentArticulation === "accent" ? null : "accent"), label: t("editor.scoreKeypad.accent", "Accent"), svg: svgPaths.accent }),
6674            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: isDotted, onClick: () => onToggleDotted == null ? void 0 : onToggleDotted(), label: t("editor.scoreKeypad.dot", "Dot"), svg: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", children: svgPaths.dot }) }),
6675            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentArticulation === "tenuto", onClick: () => onSelectArticulation == null ? void 0 : onSelectArticulation(currentArticulation === "tenuto" ? null : "tenuto"), label: t("editor.scoreKeypad.tenuto", "Tenuto"), svg: svgPaths.tenuto }),
6676            !isPercussion ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6677              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "natural", onClick: () => onSelectAccidental(currentAccidental === "natural" ? null : "natural"), label: t("editor.scoreKeypad.natural", "Natural"), svg: svgPaths.natural }),
6678              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "sharp", onClick: () => onSelectAccidental(currentAccidental === "sharp" ? null : "sharp"), label: t("editor.scoreKeypad.sharp", "Sharp"), svg: svgPaths.sharp }),
6679              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "flat", onClick: () => onSelectAccidental(currentAccidental === "flat" ? null : "flat"), label: t("editor.scoreKeypad.flat", "Flat"), svg: svgPaths.flat }),
6680              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentArticulation === "staccato", onClick: () => onSelectArticulation == null ? void 0 : onSelectArticulation(currentArticulation === "staccato" ? null : "staccato"), label: t("editor.scoreKeypad.staccato", "Staccato"), svg: svgPaths.staccato })
6681            ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6682              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "kick", onClick: () => onSelectAccidental(currentAccidental === "kick" ? null : "kick"), label: t("editor.scoreKeypad.kickDrum", "Kick Drum"), svg: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold uppercase tracking-tighter", children: "KICK" }) }),
6683              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "snare", onClick: () => onSelectAccidental(currentAccidental === "snare" ? null : "snare"), label: t("editor.scoreKeypad.snareDrum", "Snare Drum"), svg: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold uppercase tracking-tighter", children: "SNARE" }) }),
6684              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "hihat", onClick: () => onSelectAccidental(currentAccidental === "hihat" ? null : "hihat"), label: t("editor.scoreKeypad.hiHat", "Hi-Hat"), svg: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold uppercase tracking-tighter text-center leading-none", children: [
6685                "HI",
6686                /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
6687                "HAT"
6688              ] }) }),
6689              /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentAccidental === "cymbal", onClick: () => onSelectAccidental(currentAccidental === "cymbal" ? null : "cymbal"), label: t("editor.scoreKeypad.cymbal", "Cymbal"), svg: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold uppercase tracking-tighter", children: "CYMB" }) })
6690            ] }),
6691            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentTool === "note_quarter", onClick: () => onSelectTool("note_quarter"), label: t("editor.scoreKeypad.quarterNote", "Quarter Note"), svg: svgPaths.quarter }),
6692            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentTool === "note_half", onClick: () => onSelectTool("note_half"), label: t("editor.scoreKeypad.halfNote", "Half Note"), svg: svgPaths.half }),
6693            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentTool === "note_whole", onClick: () => onSelectTool("note_whole"), label: t("editor.scoreKeypad.wholeNote", "Whole Note"), svg: svgPaths.whole }),
6694            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: isTieActive, onClick: () => onToggleTieActive == null ? void 0 : onToggleTieActive(), label: t("editor.scoreKeypad.tie", "Tie"), svg: svgPaths.tie, rowSpan: 2 }),
6695            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentTool === "note_eighth", onClick: () => onSelectTool("note_eighth"), label: t("editor.scoreKeypad.eighthNote", "Eighth Note"), svg: svgPaths.eighth }),
6696            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentTool === "note_sixteenth", onClick: () => onSelectTool("note_sixteenth"), label: t("editor.scoreKeypad.sixteenthNote", "16th Note"), svg: svgPaths.sixteenth }),
6697            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: currentTool === "note_thirtysecond", onClick: () => onSelectTool("note_thirtysecond"), label: t("editor.scoreKeypad.thirtysecondNote", "32nd Note"), svg: svgPaths.thirtysecond }),
6698            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: entryMode === "rest" && currentTool === "note_quarter", onClick: () => {
6699              onSelectEntryMode == null ? void 0 : onSelectEntryMode("rest");
6700              onSelectTool == null ? void 0 : onSelectTool("note_quarter");
6701            }, label: t("editor.scoreKeypad.quarterRest", "Quarter Rest"), svg: svgPaths.restQuarter }),
6702            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: isDotted && currentTool === "note_half", onClick: () => {
6703              if (!isDotted) onToggleDotted == null ? void 0 : onToggleDotted();
6704              onSelectTool == null ? void 0 : onSelectTool("note_half");
6705              onSelectEntryMode == null ? void 0 : onSelectEntryMode("note");
6706            }, label: t("editor.scoreKeypad.dottedHalf", "Dotted Half"), svg: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex", children: [
6707              svgPaths.half,
6708              /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "12", height: "32", viewBox: "0 0 12 32", children: /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "6", cy: "24", r: "3", fill: "currentColor" }) })
6709            ] }) }),
6710            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: false, onClick: () => {
6711            }, label: t("editor.scoreKeypad.blank", "Blank"), svg: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-20 text-xs", children: "..." }) }),
6712            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadBtn, { active: false, onClick: () => {
6713            }, label: t("editor.scoreKeypad.blank", "Blank"), svg: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-20 text-xs", children: "..." }) })
6714          ] }),
6715          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 mt-1 bg-[#1c1c24] p-1 rounded-lg", children: [
6716            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadTab, { num: 1 }),
6717            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadTab, { num: 2 }),
6718            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadTab, { num: 3 }),
6719            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadTab, { num: 4 }),
6720            /* @__PURE__ */ jsxRuntimeExports.jsx(KeypadTab, { num: "All" })
6721          ] })
6722        ] })
6723      ]
6724    }
6725  );
6726};
6727const ScoreStatusBar = ({
6728  zoom,
6729  onZoomChange,
6730  layout,
6731  onLayoutChange,
6732  syntaxError
6733}) => {
6734  const { t } = useTranslation();
6735  const { activeTrackIndex, engine } = useEditor();
6736  const getActiveTrackName = () => {
6737    if (activeTrackIndex === null) return t("editor.scoreStatusBar.transposedScore", "Transposed score");
6738    if (!engine || !engine.midi) return "";
6739    const track = engine.midi.tracks[activeTrackIndex];
6740    return track.name || track.instrument.name || t("editor.scoreStatusBar.trackNumber", "Track {{number}}", { number: activeTrackIndex + 1 });
6741  };
6742  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-6 bg-[#2d2d2d] border-t border-zinc-800 flex items-center justify-between px-2 text-[10px] text-zinc-400 select-none shrink-0 z-50", children: [
6743    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
6744      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("editor.scoreStatusBar.page", "Page {{current}} of {{total}}", { current: 1, total: 1 }) }),
6745      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-3 bg-zinc-600" }),
6746      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("editor.scoreStatusBar.bars", "Bars: -") }),
6747      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-3 bg-zinc-600" }),
6748      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("editor.scoreStatusBar.noSelection", "No Selection") }),
6749      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-3 bg-zinc-600" }),
6750      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-zinc-300", children: getActiveTrackName() }),
6751      syntaxError !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6752        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-3 bg-zinc-600" }),
6753        syntaxError ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-semibold flex items-center gap-1", children: [
6754          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" }),
6755          syntaxError
6756        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-400 font-semibold flex items-center gap-1", children: [
6757          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-400" }),
6758          t("editor.scoreStatusBar.syncedWithPianoRoll", "Synced with Piano Roll")
6759        ] })
6760      ] })
6761    ] }),
6762    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
6763      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 bg-[#1a1a1a] rounded px-1 border border-[#333]", children: [
6764        /* @__PURE__ */ jsxRuntimeExports.jsx(
6765          "button",
6766          {
6767            onClick: () => onLayoutChange("panorama"),
6768            className: `p-0.5 rounded transition-colors ${layout === "panorama" ? "bg-[#444] text-white" : "hover:text-zinc-200"}`,
6769            title: t("editor.scoreStatusBar.panoramaView", "Panorama View"),
6770            children: /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutTemplate, { size: 12 })
6771          }
6772        ),
6773        /* @__PURE__ */ jsxRuntimeExports.jsx(
6774          "button",
6775          {
6776            onClick: () => onLayoutChange("pages"),
6777            className: `p-0.5 rounded transition-colors ${layout === "pages" ? "bg-[#444] text-white" : "hover:text-zinc-200"}`,
6778            title: t("editor.scoreStatusBar.pagesView", "Pages View"),
6779            children: /* @__PURE__ */ jsxRuntimeExports.jsx(Columns2, { size: 12 })
6780          }
6781        )
6782      ] }),
6783      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
6784        /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-8 text-right", children: [
6785          (zoom * 100).toFixed(0),
6786          "%"
6787        ] }),
6788        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => onZoomChange(Math.max(0.1, zoom - 0.1)), className: "hover:text-white", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { size: 12 }) }),
6789        /* @__PURE__ */ jsxRuntimeExports.jsx(
6790          "input",
6791          {
6792            type: "range",
6793            min: "0.1",
6794            max: "3",
6795            step: "0.05",
6796            value: zoom,
6797            onChange: (e) => onZoomChange(parseFloat(e.target.value)),
6798            className: "w-20 h-1 accent-indigo-500 bg-zinc-700 rounded-full appearance-none outline-none cursor-pointer",
6799            style: { WebkitAppearance: "none" }
6800          }
6801        ),
6802        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => onZoomChange(Math.min(3, zoom + 0.1)), className: "hover:text-white", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 12 }) })
6803      ] })
6804    ] })
6805  ] });
6806};
6807const LiveFeedbackTour = ({
6808  onComplete,
6809  humdrumText,
6810  showTextEditor,
6811  setShowTextEditor,
6812  showKeypad,
6813  setShowKeypad
6814}) => {
6815  const [currentStep, setCurrentStep] = reactExports.useState(0);
6816  const [bounds, setBounds] = reactExports.useState(null);
6817  const [tooltipStyle, setTooltipStyle] = reactExports.useState({});
6818  const tooltipRef = reactExports.useRef(null);
6819  const initialHumdrumRef = reactExports.useRef(null);
6820  const steps = [
6821    {
6822      targetSelector: null,
6823      title: "MIDI Editor",
6824      content: "Welcome to Hamie Lab's MIDI Editor! Let's see how Live Feedback works. Click 'Next' to begin.",
6825      preferredPlacement: "center"
6826    },
6827    {
6828      targetSelector: ".humdrum-ace-editor",
6829      title: "The Humdrum Code Editor",
6830      content: "This is where your music notation code lives. Try typing or altering a note representation (e.g., '4c') or modify the text to trigger a live re-render.",
6831      preferredPlacement: "right"
6832    },
6833    {
6834      targetSelector: "#score-canvas-container",
6835      title: "Sheet Music Renderer",
6836      content: "Look here! As you type or use the keypad, your sheet music is instantly engraved and updated in real-time.",
6837      preferredPlacement: "left"
6838    },
6839    {
6840      targetSelector: "#score-keypad-panel",
6841      title: "Notation Keypad",
6842      content: "You can also use this Keypad to quickly input note durations, accidentals, and articulations directly onto the staff.",
6843      preferredPlacement: "left"
6844    }
6845  ];
6846  reactExports.useEffect(() => {
6847    if (currentStep === 1 && !showTextEditor) {
6848      setShowTextEditor(true);
6849    }
6850    if (currentStep === 3 && !showKeypad) {
6851      setShowKeypad(true);
6852    }
6853  }, [currentStep, showTextEditor, showKeypad, setShowTextEditor, setShowKeypad]);
6854  reactExports.useEffect(() => {
6855    if (currentStep === 1) {
6856      if (initialHumdrumRef.current === null) {
6857        initialHumdrumRef.current = humdrumText;
6858      } else if (humdrumText !== initialHumdrumRef.current) {
6859        setTimeout(() => {
6860          handleNext();
6861        }, 400);
6862      }
6863    } else {
6864      initialHumdrumRef.current = null;
6865    }
6866  }, [currentStep, humdrumText]);
6867  reactExports.useEffect(() => {
6868    const step = steps[currentStep];
6869    if (!step || !step.targetSelector) {
6870      setBounds(null);
6871      return;
6872    }
6873    const updateBounds = () => {
6874      const el = document.querySelector(step.targetSelector);
6875      if (el) {
6876        setBounds(el.getBoundingClientRect());
6877      } else {
6878        setBounds(null);
6879      }
6880    };
6881    updateBounds();
6882    const interval = setInterval(updateBounds, 150);
6883    window.addEventListener("resize", updateBounds);
6884    window.addEventListener("scroll", updateBounds, true);
6885    return () => {
6886      clearInterval(interval);
6887      window.removeEventListener("resize", updateBounds);
6888      window.removeEventListener("scroll", updateBounds, true);
6889    };
6890  }, [currentStep]);
6891  reactExports.useEffect(() => {
6892    const step = steps[currentStep];
6893    const tooltip = tooltipRef.current;
6894    const computePlacement = () => {
6895      const vw = window.innerWidth;
6896      const vh = window.innerHeight;
6897      const isMobile = vw < 768;
6898      if (step.preferredPlacement === "center" || !bounds || isMobile) {
6899        setTooltipStyle({
6900          position: "fixed",
6901          left: "50%",
6902          top: step.preferredPlacement === "center" ? "40%" : "auto",
6903          bottom: step.preferredPlacement === "center" ? "auto" : "24px",
6904          transform: step.preferredPlacement === "center" ? "translate(-50%, -50%)" : "translateX(-50%)",
6905          width: "calc(100% - 32px)",
6906          maxWidth: "420px",
6907          zIndex: 10000010
6908        });
6909        return;
6910      }
6911      const tWidth = tooltip ? tooltip.offsetWidth : 360;
6912      const tHeight = tooltip ? tooltip.offsetHeight : 180;
6913      const gap = 20;
6914      let placement = step.preferredPlacement;
6915      if (placement === "right") {
6916        if (bounds.right + tWidth + gap > vw) {
6917          placement = "bottom";
6918        }
6919      } else if (placement === "left") {
6920        if (bounds.left - tWidth - gap < 0) {
6921          placement = "bottom";
6922        }
6923      }
6924      let left = 0;
6925      let top = 0;
6926      if (placement === "right") {
6927        left = bounds.right + gap;
6928        top = bounds.top + (bounds.height - tHeight) / 2;
6929        top = Math.max(16, Math.min(vh - tHeight - 16, top));
6930      } else if (placement === "left") {
6931        left = bounds.left - tWidth - gap;
6932        top = bounds.top + (bounds.height - tHeight) / 2;
6933        top = Math.max(16, Math.min(vh - tHeight - 16, top));
6934      } else {
6935        left = bounds.left + (bounds.width - tWidth) / 2;
6936        top = bounds.bottom + gap;
6937        left = Math.max(16, Math.min(vw - tWidth - 16, left));
6938        if (top + tHeight > vh) {
6939          top = bounds.top - tHeight - gap;
6940        }
6941      }
6942      setTooltipStyle({
6943        position: "fixed",
6944        left: `${left}px`,
6945        top: `${top}px`,
6946        width: `${tWidth}px`,
6947        zIndex: 10000010
6948      });
6949    };
6950    computePlacement();
6951    const timeout = setTimeout(computePlacement, 50);
6952    window.addEventListener("resize", computePlacement);
6953    return () => {
6954      clearTimeout(timeout);
6955      window.removeEventListener("resize", computePlacement);
6956    };
6957  }, [currentStep, bounds]);
6958  const handleNext = () => {
6959    if (currentStep < steps.length - 1) {
6960      setCurrentStep(currentStep + 1);
6961    } else {
6962      handleComplete();
6963    }
6964  };
6965  const handlePrev = () => {
6966    if (currentStep > 0) {
6967      setCurrentStep(currentStep - 1);
6968    }
6969  };
6970  const handleComplete = () => {
6971    localStorage.setItem("hamienet_live_tour_done", "true");
6972    onComplete();
6973  };
6974  const handleSkip = () => {
6975    handleComplete();
6976  };
6977  const getClipPath = () => {
6978    if (!bounds) return "none";
6979    const top = bounds.top;
6980    const left = bounds.left;
6981    const right = bounds.right;
6982    const bottom = bounds.bottom;
6983    return `polygon(
6984            0% 0%, 
6985            0% 100%, 
6986            ${left}px 100%, 
6987            ${left}px ${top}px, 
6988            ${right}px ${top}px, 
6989            ${right}px ${bottom}px, 
6990            ${left}px ${bottom}px, 
6991            ${left}px 100%, 
6992            100% 100%, 
6993            100% 0%
6994        )`;
6995  };
6996  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[10000000] pointer-events-none font-sans", children: [
6997    /* @__PURE__ */ jsxRuntimeExports.jsx(
6998      "div",
6999      {
7000        className: "absolute inset-0 bg-black/60 transition-all duration-300 pointer-events-auto",
7001        style: { clipPath: getClipPath() },
7002        onClick: handleSkip
7003      }
7004    ),
7005    bounds && /* @__PURE__ */ jsxRuntimeExports.jsx(
7006      "div",
7007      {
7008        className: "absolute border border-indigo-500 rounded-lg shadow-[0_0_15px_rgba(99,102,241,0.6)] animate-pulse pointer-events-none transition-all duration-300",
7009        style: {
7010          left: bounds.left - 2,
7011          top: bounds.top - 2,
7012          width: bounds.width + 4,
7013          height: bounds.height + 4
7014        }
7015      }
7016    ),
7017    /* @__PURE__ */ jsxRuntimeExports.jsxs(
7018      "div",
7019      {
7020        ref: tooltipRef,
7021        style: tooltipStyle,
7022        className: "absolute pointer-events-auto bg-[#181824]/95 border border-[#3c3c54]/60 backdrop-blur-md rounded-xl p-4 shadow-2xl text-zinc-100 flex flex-col gap-3 transition-all duration-300",
7023        children: [
7024          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between border-b border-[#3c3c54]/40 pb-2", children: [
7025            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-indigo-400 font-bold text-xs uppercase tracking-wider", children: [
7026              /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: 14 }),
7027              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
7028                "Step ",
7029                currentStep + 1,
7030                " of ",
7031                steps.length
7032              ] })
7033            ] }),
7034            /* @__PURE__ */ jsxRuntimeExports.jsx(
7035              "button",
7036              {
7037                onClick: handleSkip,
7038                className: "text-zinc-550 hover:text-zinc-200 transition",
7039                title: "Skip Tour",
7040                children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 14 })
7041              }
7042            )
7043          ] }),
7044          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
7045            /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-sm text-white", children: steps[currentStep].title }),
7046            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-300 leading-relaxed", children: steps[currentStep].content })
7047          ] }),
7048          currentStep === 1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-amber-400/90 font-medium flex items-center gap-1.5 bg-amber-500/10 border border-amber-500/20 px-2.5 py-1 rounded", children: [
7049            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-amber-400 animate-ping" }),
7050            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Try typing inside the code editor to auto-advance." })
7051          ] }),
7052          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mt-2 pt-2 border-t border-[#3c3c54]/30", children: [
7053            /* @__PURE__ */ jsxRuntimeExports.jsx(
7054              "button",
7055              {
7056                onClick: handleSkip,
7057                className: "text-[10px] text-zinc-450 hover:text-zinc-255 font-bold uppercase transition",
7058                children: "Skip Tour"
7059              }
7060            ),
7061            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
7062              currentStep > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
7063                "button",
7064                {
7065                  onClick: handlePrev,
7066                  className: "px-2.5 py-1 text-[10px] font-bold rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-300 transition",
7067                  children: "Back"
7068                }
7069              ),
7070              /* @__PURE__ */ jsxRuntimeExports.jsxs(
7071                "button",
7072                {
7073                  onClick: handleNext,
7074                  className: "px-3 py-1 text-[10px] font-bold rounded bg-indigo-600 hover:bg-indigo-500 text-white transition flex items-center gap-1",
7075                  children: [
7076                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: currentStep === steps.length - 1 ? "Finish" : "Next" }),
7077                    /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { size: 10 })
7078                  ]
7079                }
7080              )
7081            ] })
7082          ] })
7083        ]
7084      }
7085    )
7086  ] });
7087};
7088class ScoreErrorBoundary extends React.Component {
7089  constructor(props) {
7090    super(props);
7091    this.state = { hasError: false };
7092  }
7093  static getDerivedStateFromError(error) {
7094    return { hasError: true };
7095  }
7096  componentDidCatch(error, errorInfo) {
7097    console.error("ScoreErrorBoundary caught rendering error:", error, errorInfo);
7098  }
7099  componentDidUpdate(prevProps) {
7100    if (this.state.hasError && prevProps.svg !== this.props.svg) {
7101      this.setState({ hasError: false });
7102    }
7103  }
7104  render() {
7105    if (this.state.hasError) {
7106      return this.props.fallback;
7107    }
7108    return this.props.children;
7109  }
7110}
7111const getBeatsPerMeasureFromDoc = (doc) => {
7112  if (!doc) return 4;
7113  try {
7114    const scoreDef = doc.querySelector("scoreDef");
7115    let count = 4;
7116    let unit = 4;
7117    if (scoreDef) {
7118      const meterCount = scoreDef.getAttribute("meter.count");
7119      const meterUnit = scoreDef.getAttribute("meter.unit");
7120      if (meterCount) count = parseInt(meterCount, 10) || 4;
7121      if (meterUnit) unit = parseInt(meterUnit, 10) || 4;
7122    } else {
7123      const staffDef = doc.querySelector("staffDef");
7124      if (staffDef) {
7125        const meterCount = staffDef.getAttribute("meter.count");
7126        const meterUnit = staffDef.getAttribute("meter.unit");
7127        if (meterCount) count = parseInt(meterCount, 10) || 4;
7128        if (meterUnit) unit = parseInt(meterUnit, 10) || 4;
7129      }
7130    }
7131    return count * (4 / unit);
7132  } catch (e) {
7133    console.error("Failed to parse meter from MEI Doc", e);
7134  }
7135  return 4;
7136};
7137const getBeatsPerMeasureFromMei = (meiString) => {
7138  if (!meiString) return 4;
7139  try {
7140    const parser = new DOMParser();
7141    const doc = parser.parseFromString(meiString, "application/xml");
7142    return getBeatsPerMeasureFromDoc(doc);
7143  } catch (e) {
7144    console.error("Failed to parse MEI", e);
7145  }
7146  return 4;
7147};
7148const getStartTimeInLayer = (layer, targetEl) => {
7149  let eventEl = targetEl;
7150  if (targetEl.parentElement && targetEl.parentElement.tagName === "chord") {
7151    eventEl = targetEl.parentElement;
7152  }
7153  let elapsed = 0;
7154  const children = Array.from(layer.children);
7155  for (const child of children) {
7156    if (child === eventEl) {
7157      break;
7158    }
7159    if (child.tagName === "note" || child.tagName === "chord" || child.tagName === "rest" || child.tagName === "mRest") {
7160      const durAttr = child.getAttribute("dur") || "4";
7161      const dotsAttr = child.getAttribute("dots") || null;
7162      elapsed += getBeats(durAttr, dotsAttr);
7163    }
7164  }
7165  return elapsed;
7166};
7167const checkOverlapInLayer = (layer, snappedBeat, beatDur) => {
7168  const beatsPerMeasure = getBeatsPerMeasureFromDoc(layer.ownerDocument);
7169  let currentBeat = 0;
7170  const children = Array.from(layer.children);
7171  for (const child of children) {
7172    if (child.tagName === "note" || child.tagName === "chord") {
7173      const durAttr = child.getAttribute("dur") || "4";
7174      const dotsAttr = child.getAttribute("dots") || null;
7175      const duration = getBeats(durAttr, dotsAttr);
7176      const start = currentBeat;
7177      const end = start + duration;
7178      if (snappedBeat < end - 1e-3 && start < snappedBeat + beatDur - 1e-3) {
7179        return true;
7180      }
7181      currentBeat += duration;
7182    } else if (child.tagName === "rest") {
7183      const durAttr = child.getAttribute("dur") || "4";
7184      const dotsAttr = child.getAttribute("dots") || null;
7185      currentBeat += getBeats(durAttr, dotsAttr);
7186    } else if (child.tagName === "mRest") {
7187      currentBeat += beatsPerMeasure;
7188    }
7189  }
7190  return false;
7191};
7192const getActualNoteStartTime = (noteEl, measureNum, elapsedBeats) => {
7193  const doc = noteEl.ownerDocument;
7194  const beatsPerMeasure = getBeatsPerMeasureFromDoc(doc);
7195  const tie = noteEl.getAttribute("tie");
7196  const startBeat = (measureNum - 1) * beatsPerMeasure + elapsedBeats;
7197  let startSeconds = startBeat * 0.5;
7198  if (tie === "e") {
7199    const pname = noteEl.getAttribute("pname") || "c";
7200    const oct = noteEl.getAttribute("oct") || "4";
7201    const measure = noteEl.closest("measure");
7202    let prevM = measure == null ? void 0 : measure.previousElementSibling;
7203    while (prevM && prevM.tagName !== "measure") {
7204      prevM = prevM.previousElementSibling;
7205    }
7206    if (prevM) {
7207      const prevNote = Array.from(prevM.querySelectorAll("note")).find(
7208        (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "s"
7209      );
7210      if (prevNote) {
7211        const prevLayer = prevNote.closest("layer");
7212        if (prevLayer) {
7213          let prevEventEl = prevNote;
7214          if (prevNote.parentElement && prevNote.parentElement.tagName === "chord") {
7215            prevEventEl = prevNote.parentElement;
7216          }
7217          const prevElapsedBeats = getStartTimeInLayer(prevLayer, prevEventEl);
7218          let prevMeasureNum = measureNum - 1;
7219          const prevMeasure = prevLayer.closest("measure");
7220          if (prevMeasure) {
7221            const prevNAttr = prevMeasure.getAttribute("n");
7222            if (prevNAttr) prevMeasureNum = parseInt(prevNAttr);
7223          }
7224          const prevStartBeat = (prevMeasureNum - 1) * beatsPerMeasure + prevElapsedBeats;
7225          startSeconds = prevStartBeat * 0.5;
7226        }
7227      }
7228    }
7229  }
7230  return startSeconds;
7231};
7232const cleanTiesForNote = (doc, noteId) => {
7233  const ties = Array.from(doc.querySelectorAll("tie"));
7234  ties.forEach((tie) => {
7235    const startid = tie.getAttribute("startid");
7236    const endid = tie.getAttribute("endid");
7237    if (startid === `#${noteId}` || endid === `#${noteId}`) {
7238      tie.remove();
7239    }
7240  });
7241};
7242const getSnappingOffset = (clientY, staffNode) => {
7243  const staffLines = Array.from(staffNode.querySelectorAll("path"));
7244  if (staffLines.length < 5) return 0;
7245  const pathsWithWidth = staffLines.map((line) => {
7246    const rect = line.getBoundingClientRect();
7247    return { line, width: rect.width, rect };
7248  }).sort((a, b) => b.width - a.width);
7249  const rects = pathsWithWidth.slice(0, 5).map((item) => item.rect).sort((a, b) => a.top - b.top);
7250  if (rects.length === 5) {
7251    const topLineY = rects[0].top;
7252    const bottomLineY = rects[4].top;
7253    const avgGap = (bottomLineY - topLineY) / 4;
7254    const stepSize = avgGap / 2;
7255    const relativeY = clientY - topLineY;
7256    return Math.round(relativeY / stepSize);
7257  }
7258  return 0;
7259};
7260const getXmlElById = (xmlDoc, id) => {
7261  if (!id) return null;
7262  return xmlDoc.getElementById(id) || xmlDoc.querySelector(`[id="${id}"]`) || xmlDoc.querySelector(`[xml\\:id="${id}"]`) || xmlDoc.querySelector(`[*|id="${id}"]`) || Array.from(xmlDoc.getElementsByTagName("*")).find((el) => el.getAttribute("xml:id") === id) || null;
7263};
7264const ScoreEditor = () => {
7265  var _a, _b;
7266  const { activeTrackIndex, addNote, updateNote, deleteNote, isPlaying, play, pause, stop, engine, meiDocs, setMeiDoc, selectedNoteIds, setSelectedNoteIds, patterns, setPatterns, activePatternId, forceSync, tempo } = useEditor();
7267  const [currentTool, setCurrentTool] = reactExports.useState("select");
7268  const [currentAccidental, setCurrentAccidental] = reactExports.useState(null);
7269  const [currentVoice, setCurrentVoice] = reactExports.useState(1);
7270  const [entryMode, setEntryMode] = reactExports.useState("note");
7271  const [isDotted, setIsDotted] = reactExports.useState(false);
7272  const [isTieActive, setIsTieActive] = reactExports.useState(false);
7273  const [isTriplet, setIsTriplet] = reactExports.useState(false);
7274  const [currentArticulation, setCurrentArticulation] = reactExports.useState(null);
7275  const [showKeypad, setShowKeypad] = reactExports.useState(true);
7276  const containerRef = reactExports.useRef(null);
7277  const [dimensions, setDimensions] = reactExports.useState({ width: 800, height: 600 });
7278  const [selectedElementId, setSelectedElementId] = reactExports.useState(null);
7279  const [zoom, setZoom] = reactExports.useState(0.42);
7280  const [layout, setLayout] = reactExports.useState("pages");
7281  const [showGrid, setShowGrid] = reactExports.useState(true);
7282  const [showTextEditor, setShowTextEditor] = reactExports.useState(true);
7283  const [humdrumText, setHumdrumText] = reactExports.useState("");
7284  const [syntaxError, setSyntaxError] = reactExports.useState(null);
7285  const [aceReady, setAceReady] = reactExports.useState(false);
7286  const [forceRerender, setForceRerender] = reactExports.useState(0);
7287  const aceEditorRef = reactExports.useRef(null);
7288  const aceContainerRef = reactExports.useRef(null);
7289  const editingFromScoreRef = reactExports.useRef(false);
7290  const [lastSyncedNotesStr, setLastSyncedNotesStr] = reactExports.useState("");
7291  const [showLiveTour, setShowLiveTour] = reactExports.useState(false);
7292  const debounceTimerRef = reactExports.useRef(null);
7293  const audioCtxRef = reactExports.useRef(null);
7294  const playNotePreview = reactExports.useCallback((pitch) => {
7295    try {
7296      if (!audioCtxRef.current) {
7297        audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)();
7298      }
7299      const ctx = audioCtxRef.current;
7300      if (ctx.state === "suspended") {
7301        ctx.resume();
7302      }
7303      const now = ctx.currentTime;
7304      const osc = ctx.createOscillator();
7305      const gain = ctx.createGain();
7306      const freq = 440 * Math.pow(2, (pitch - 69) / 12);
7307      osc.frequency.value = freq;
7308      osc.type = "triangle";
7309      gain.gain.setValueAtTime(0, now);
7310      gain.gain.linearRampToValueAtTime(0.12, now + 0.01);
7311      gain.gain.exponentialRampToValueAtTime(1e-3, now + 0.35);
7312      osc.connect(gain);
7313      gain.connect(ctx.destination);
7314      osc.start(now);
7315      osc.stop(now + 0.35);
7316    } catch (e) {
7317      console.error("Failed to play audio preview:", e);
7318    }
7319  }, []);
7320  const isPercussion = activeTrackIndex !== null && ((_b = (_a = engine == null ? void 0 : engine.midi) == null ? void 0 : _a.tracks[activeTrackIndex]) == null ? void 0 : _b.channel) === 9;
7321  const [showMeasureNumbers, setShowMeasureNumbers] = reactExports.useState(true);
7322  const {
7323    isReady,
7324    svg,
7325    insertNote,
7326    deleteElement,
7327    updateMei,
7328    toolkit,
7329    undo,
7330    redo,
7331    canUndo,
7332    canRedo
7333  } = useScoreEditorEngine({
7334    dimensions,
7335    zoom,
7336    isPercussion,
7337    initialMei: activeTrackIndex !== null ? meiDocs[activeTrackIndex] : void 0,
7338    showMeasureNumbers,
7339    onMeiUpdate: (newMei) => {
7340      if (activeTrackIndex !== null) {
7341        setMeiDoc(activeTrackIndex, newMei);
7342        if (toolkit) {
7343          try {
7344            toolkit.loadData(newMei);
7345            const hum = toolkit.getHumdrum ? toolkit.getHumdrum() : newMei;
7346            setHumdrumText(hum);
7347            setSyntaxError(null);
7348          } catch (e) {
7349            console.error("Failed to convert MEI to Humdrum:", e);
7350          }
7351        }
7352      }
7353    }
7354  });
7355  reactExports.useEffect(() => {
7356    if (isReady && !localStorage.getItem("hamienet_live_tour_done")) {
7357      localStorage.setItem("hamienet_live_tour_done", "true");
7358      const timer = setTimeout(() => {
7359        setShowLiveTour(true);
7360      }, 1e3);
7361      return () => clearTimeout(timer);
7362    }
7363  }, [isReady]);
7364  reactExports.useEffect(() => {
7365    const handleKeyDown = (e) => {
7366      if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
7367        if (e.key === "z" || e.key === "Z") {
7368          e.preventDefault();
7369          undo();
7370        } else if (e.key === "y" || e.key === "Y") {
7371          e.preventDefault();
7372          redo();
7373        }
7374      }
7375    };
7376    window.addEventListener("keydown", handleKeyDown);
7377    return () => {
7378      window.removeEventListener("keydown", handleKeyDown);
7379    };
7380  }, [undo, redo]);
7381  reactExports.useEffect(() => {
7382    if (!showTextEditor) return;
7383    const injectAce = async () => {
7384      if (window.ace) {
7385        setAceReady(true);
7386        return;
7387      }
7388      try {
7389        await new Promise((resolve, reject) => {
7390          const script = document.createElement("script");
7391          script.src = "https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js";
7392          script.async = true;
7393          script.onload = () => resolve();
7394          script.onerror = () => reject(new Error("Failed to load Ace Editor"));
7395          document.head.appendChild(script);
7396        });
7397        setAceReady(true);
7398      } catch (err) {
7399        console.error(err);
7400      }
7401    };
7402    injectAce();
7403  }, [showTextEditor]);
7404  const handleHumdrumEditorChange = reactExports.useCallback((newHumdrum) => {
7405    if (!toolkit || activeTrackIndex === null || !engine) return;
7406    editingFromScoreRef.current = true;
7407    try {
7408      const success = toolkit.loadData(newHumdrum);
7409      if (success === true || success === 1) {
7410        setSyntaxError(null);
7411        const render = toolkit.renderToSVG(1, {});
7412        const newMei = toolkit.getMEI();
7413        setMeiDoc(activeTrackIndex, newMei);
7414        const base64Midi = toolkit.renderToMIDI();
7415        if (base64Midi) {
7416          const newNotes = getMidiNotesFromBase64(base64Midi, engine.midi.header.ppq);
7417          const newNotesStr = JSON.stringify(newNotes);
7418          setLastSyncedNotesStr(newNotesStr);
7419          setPatterns((prev) => prev.map((p) => {
7420            if (p.id === activePatternId) {
7421              return {
7422                ...p,
7423                notes: newNotes
7424              };
7425            }
7426            return p;
7427          }));
7428          setTimeout(() => forceSync(), 0);
7429        }
7430      } else {
7431        setSyntaxError("Invalid Humdrum/Notation syntax");
7432      }
7433    } catch (err) {
7434      console.error("Verovio processing failed:", err);
7435      setSyntaxError((err == null ? void 0 : err.message) || "Invalid Humdrum/Notation syntax");
7436    }
7437  }, [toolkit, activeTrackIndex, engine, setMeiDoc, activePatternId, setPatterns, forceSync]);
7438  reactExports.useEffect(() => {
7439    if (!aceReady || !showTextEditor || !aceContainerRef.current) return;
7440    const ace = window.ace;
7441    if (!ace) return;
7442    const editor = ace.edit(aceContainerRef.current);
7443    const isDark = document.documentElement.classList.contains("dark");
7444    editor.setTheme(isDark ? "ace/theme/monokai" : "ace/theme/chrome");
7445    editor.session.setMode("ace/mode/text");
7446    editor.setShowPrintMargin(false);
7447    editor.setOption("fontSize", "13px");
7448    editor.setValue(humdrumText, -1);
7449    const onChange = () => {
7450      const val = editor.getValue();
7451      if (val !== humdrumText) {
7452        if (debounceTimerRef.current) {
7453          clearTimeout(debounceTimerRef.current);
7454        }
7455        debounceTimerRef.current = setTimeout(() => {
7456          handleHumdrumEditorChange(val);
7457        }, 280);
7458      }
7459    };
7460    editor.on("change", onChange);
7461    aceEditorRef.current = editor;
7462    return () => {
7463      if (debounceTimerRef.current) {
7464        clearTimeout(debounceTimerRef.current);
7465      }
7466      if (aceEditorRef.current) {
7467        aceEditorRef.current.off("change", onChange);
7468        aceEditorRef.current.destroy();
7469        aceEditorRef.current = null;
7470      }
7471    };
7472  }, [aceReady, showTextEditor, handleHumdrumEditorChange]);
7473  reactExports.useEffect(() => {
7474    if (aceEditorRef.current && humdrumText !== aceEditorRef.current.getValue()) {
7475      const cursor = aceEditorRef.current.getCursorPosition();
7476      aceEditorRef.current.setValue(humdrumText, -1);
7477      aceEditorRef.current.moveCursorToPosition(cursor);
7478    }
7479  }, [humdrumText]);
7480  reactExports.useEffect(() => {
7481    if (!aceEditorRef.current) return;
7482    const isDark = document.documentElement.classList.contains("dark");
7483    aceEditorRef.current.setTheme(isDark ? "ace/theme/monokai" : "ace/theme/chrome");
7484  }, [aceReady, showTextEditor]);
7485  reactExports.useEffect(() => {
7486    if (activeTrackIndex === null || !engine || !engine.midi || !engine.midi.tracks[activeTrackIndex]) return;
7487    const track = engine.midi.tracks[activeTrackIndex];
7488    const currentNotesStr = JSON.stringify(track.notes.map((n) => ({
7489      midi: n.midi,
7490      ticks: n.ticks,
7491      durationTicks: n.durationTicks,
7492      velocity: n.velocity,
7493      voice: n.voice || 1
7494    })));
7495    if (currentNotesStr !== lastSyncedNotesStr) {
7496      if (editingFromScoreRef.current) {
7497        editingFromScoreRef.current = false;
7498        setLastSyncedNotesStr(currentNotesStr);
7499        return;
7500      }
7501      setLastSyncedNotesStr(currentNotesStr);
7502      const ppq = engine.midi.header.ppq;
7503      const newMei = convertMidiToMEI(track, ppq);
7504      setMeiDoc(activeTrackIndex, newMei);
7505      if (toolkit) {
7506        try {
7507          toolkit.loadData(newMei);
7508          const hum = toolkit.getHumdrum ? toolkit.getHumdrum() : newMei;
7509          setHumdrumText(hum);
7510          setSyntaxError(null);
7511        } catch (e) {
7512          console.error("Error loading regenerated MEI:", e);
7513        }
7514      } else {
7515        setHumdrumText(newMei);
7516      }
7517    } else {
7518      const isCurrentlyMeiXml = humdrumText.trim().startsWith("<");
7519      if (toolkit && isCurrentlyMeiXml && activeTrackIndex !== null && meiDocs[activeTrackIndex]) {
7520        try {
7521          const currentMei = meiDocs[activeTrackIndex];
7522          toolkit.loadData(currentMei);
7523          const hum = toolkit.getHumdrum ? toolkit.getHumdrum() : currentMei;
7524          setHumdrumText(hum);
7525          setSyntaxError(null);
7526        } catch (e) {
7527          console.error("Error on initial conversion:", e);
7528        }
7529      }
7530    }
7531  }, [activeTrackIndex, engine, patterns, toolkit, setMeiDoc, lastSyncedNotesStr, humdrumText, meiDocs]);
7532  reactExports.useEffect(() => {
7533    if (!containerRef.current) return;
7534    const observer = new ResizeObserver((entries) => {
7535      for (const entry of entries) {
7536        if (entry.contentRect.width > 0) {
7537          setDimensions({ width: entry.contentRect.width, height: entry.contentRect.height });
7538        }
7539      }
7540    });
7541    observer.observe(containerRef.current);
7542    return () => observer.disconnect();
7543  }, []);
7544  reactExports.useEffect(() => {
7545    const handleWheel = (e) => {
7546      if (e.shiftKey) {
7547        e.preventDefault();
7548        const delta = e.deltaY;
7549        setZoom((prevZoom) => {
7550          const newZoom = prevZoom - delta * 1e-3;
7551          return Math.max(0.1, Math.min(3, newZoom));
7552        });
7553      }
7554    };
7555    const el = containerRef.current;
7556    if (el) {
7557      el.addEventListener("wheel", handleWheel, { passive: false });
7558    }
7559    return () => {
7560      if (el) {
7561        el.removeEventListener("wheel", handleWheel);
7562      }
7563    };
7564  }, []);
7565  reactExports.useEffect(() => {
7566    var _a2;
7567    if (selectedNoteIds.length > 0 && activeTrackIndex !== null && meiDocs[activeTrackIndex] && ((_a2 = engine == null ? void 0 : engine.midi) == null ? void 0 : _a2.tracks[activeTrackIndex])) {
7568      const track = engine.midi.tracks[activeTrackIndex];
7569      const note = track.notes[selectedNoteIds[0]];
7570      if (!note) return;
7571      const parser = new DOMParser();
7572      const doc = parser.parseFromString(meiDocs[activeTrackIndex], "application/xml");
7573      const notes = Array.from(doc.querySelectorAll("note"));
7574      let foundId = null;
7575      for (const noteEl of notes) {
7576        const pname = noteEl.getAttribute("pname") || "c";
7577        const oct = noteEl.getAttribute("oct") || "4";
7578        const originalAccid = noteEl.getAttribute("accid") || void 0;
7579        const oldMidi = getMidiFromMEI(pname, parseInt(oct), originalAccid);
7580        if (oldMidi === note.midi) {
7581          const layer = noteEl.closest("layer");
7582          if (layer) {
7583            let eventEl = noteEl;
7584            if (noteEl.parentElement && noteEl.parentElement.tagName === "chord") {
7585              eventEl = noteEl.parentElement;
7586            }
7587            const elapsedBeats = getStartTimeInLayer(layer, eventEl);
7588            const measure = layer.closest("measure");
7589            let measureNum = 1;
7590            if (measure) {
7591              const nAttr = measure.getAttribute("n");
7592              if (nAttr) measureNum = parseInt(nAttr);
7593            }
7594            const actualStartSeconds = getActualNoteStartTime(noteEl, measureNum, elapsedBeats);
7595            if (Math.abs(note.time - actualStartSeconds) < 0.05) {
7596              foundId = noteEl.getAttribute("xml:id");
7597              break;
7598            }
7599          }
7600        }
7601      }
7602      if (foundId && foundId !== selectedElementId) {
7603        setSelectedElementId(foundId);
7604      }
7605    } else if (selectedNoteIds.length === 0 && selectedElementId) {
7606      setSelectedElementId(null);
7607    }
7608  }, [selectedNoteIds, activeTrackIndex, meiDocs, engine]);
7609  const applyAccidentalToNote = reactExports.useCallback((noteId, accidName) => {
7610    editingFromScoreRef.current = true;
7611    updateMei((doc) => {
7612      var _a2;
7613      const noteEl = getXmlElById(doc, noteId);
7614      if (noteEl && noteEl.tagName === "note") {
7615        const pname = noteEl.getAttribute("pname") || "c";
7616        const oct = noteEl.getAttribute("oct") || "4";
7617        const originalAccid = noteEl.getAttribute("accid") || void 0;
7618        const oldMidi = getMidiFromMEI(pname, parseInt(oct), originalAccid);
7619        const tie = noteEl.getAttribute("tie");
7620        const layer = noteEl.closest("layer");
7621        if (layer) {
7622          let eventEl = noteEl;
7623          if (noteEl.parentElement && noteEl.parentElement.tagName === "chord") {
7624            eventEl = noteEl.parentElement;
7625          }
7626          const elapsedBeats = getStartTimeInLayer(layer, eventEl);
7627          const measure = layer.closest("measure");
7628          let measureNum = 1;
7629          if (measure) {
7630            const nAttr = measure.getAttribute("n");
7631            if (nAttr) measureNum = parseInt(nAttr);
7632          }
7633          getBeatsPerMeasureFromDoc(doc);
7634          const actualStartSeconds = getActualNoteStartTime(noteEl, measureNum, elapsedBeats);
7635          const newAccid = accidName === "sharp" ? "s" : accidName === "flat" ? "f" : accidName === "natural" ? "n" : void 0;
7636          if (newAccid) {
7637            noteEl.setAttribute("accid", newAccid);
7638          } else {
7639            noteEl.removeAttribute("accid");
7640          }
7641          const newMidi = getMidiFromMEI(pname, parseInt(oct), newAccid);
7642          playNotePreview(newMidi);
7643          if (activeTrackIndex !== null && ((_a2 = engine == null ? void 0 : engine.midi) == null ? void 0 : _a2.tracks[activeTrackIndex])) {
7644            const track = engine.midi.tracks[activeTrackIndex];
7645            const noteIndex = track.notes.findIndex((n) => n.midi === oldMidi && Math.abs(n.time - actualStartSeconds) < 0.05);
7646            if (noteIndex !== -1) {
7647              updateNote(activeTrackIndex, noteIndex, { midi: newMidi });
7648            }
7649          }
7650          if (tie === "s") {
7651            const nextMeasure = measure == null ? void 0 : measure.nextElementSibling;
7652            let nextM = nextMeasure;
7653            while (nextM && nextM.tagName !== "measure") {
7654              nextM = nextM.nextElementSibling;
7655            }
7656            if (nextM) {
7657              const nextNote = Array.from(nextM.querySelectorAll("note")).find(
7658                (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "e"
7659              );
7660              if (nextNote) {
7661                if (newAccid) nextNote.setAttribute("accid", newAccid);
7662                else nextNote.removeAttribute("accid");
7663              }
7664            }
7665          } else if (tie === "e") {
7666            let prevM = measure == null ? void 0 : measure.previousElementSibling;
7667            while (prevM && prevM.tagName !== "measure") {
7668              prevM = prevM.previousElementSibling;
7669            }
7670            if (prevM) {
7671              const prevNote = Array.from(prevM.querySelectorAll("note")).find(
7672                (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "s"
7673              );
7674              if (prevNote) {
7675                if (newAccid) prevNote.setAttribute("accid", newAccid);
7676                else prevNote.removeAttribute("accid");
7677              }
7678            }
7679          }
7680        }
7681      }
7682    });
7683  }, [activeTrackIndex, engine, updateNote, updateMei]);
7684  const applyDurationToNote = reactExports.useCallback((noteId, tool) => {
7685    editingFromScoreRef.current = true;
7686    const durMap = {
7687      "note_whole": "1",
7688      "note_half": "2",
7689      "note_quarter": "4",
7690      "note_eighth": "8",
7691      "note_sixteenth": "16"
7692    };
7693    const dur = durMap[tool];
7694    if (!dur) return;
7695    updateMei((doc) => {
7696      var _a2;
7697      const noteEl = getXmlElById(doc, noteId);
7698      if (noteEl && noteEl.tagName === "note") {
7699        const pname = noteEl.getAttribute("pname") || "c";
7700        const oct = noteEl.getAttribute("oct") || "4";
7701        const accid = noteEl.getAttribute("accid") || void 0;
7702        const midiPitch = getMidiFromMEI(pname, parseInt(oct), accid);
7703        playNotePreview(midiPitch);
7704        const tie = noteEl.getAttribute("tie");
7705        const layer = noteEl.closest("layer");
7706        if (layer) {
7707          let eventEl = noteEl;
7708          if (noteEl.parentElement && noteEl.parentElement.tagName === "chord") {
7709            eventEl = noteEl.parentElement;
7710          }
7711          const elapsedBeats = getStartTimeInLayer(layer, eventEl);
7712          const measure = layer.closest("measure");
7713          let measureNum = 1;
7714          if (measure) {
7715            const nAttr = measure.getAttribute("n");
7716            if (nAttr) measureNum = parseInt(nAttr);
7717          }
7718          getBeatsPerMeasureFromDoc(doc);
7719          const actualStartSeconds = getActualNoteStartTime(noteEl, measureNum, elapsedBeats);
7720          const newBeatDur = getDurationInBeats(dur);
7721          if (activeTrackIndex !== null && ((_a2 = engine == null ? void 0 : engine.midi) == null ? void 0 : _a2.tracks[activeTrackIndex])) {
7722            const track = engine.midi.tracks[activeTrackIndex];
7723            const noteIndex = track.notes.findIndex((n) => n.midi === midiPitch && Math.abs(n.time - actualStartSeconds) < 0.05);
7724            if (noteIndex !== -1) {
7725              updateNote(activeTrackIndex, noteIndex, { duration: newBeatDur * 0.5 });
7726            }
7727          }
7728          cleanTiesForNote(doc, noteId);
7729          if (tie === "s") {
7730            const nextMeasure = measure == null ? void 0 : measure.nextElementSibling;
7731            let nextM = nextMeasure;
7732            while (nextM && nextM.tagName !== "measure") {
7733              nextM = nextM.nextElementSibling;
7734            }
7735            if (nextM) {
7736              const nextNote = Array.from(nextM.querySelectorAll("note")).find(
7737                (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "e"
7738              );
7739              if (nextNote) {
7740                const nextNoteId = nextNote.getAttribute("xml:id") || nextNote.getAttribute("id");
7741                if (nextNoteId) cleanTiesForNote(doc, nextNoteId);
7742                nextNote.remove();
7743              }
7744            }
7745          } else if (tie === "e") {
7746            let prevM = measure == null ? void 0 : measure.previousElementSibling;
7747            while (prevM && prevM.tagName !== "measure") {
7748              prevM = prevM.previousElementSibling;
7749            }
7750            if (prevM) {
7751              const prevNote = Array.from(prevM.querySelectorAll("note")).find(
7752                (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "s"
7753              );
7754              if (prevNote) {
7755                prevNote.removeAttribute("tie");
7756                const prevNoteId = prevNote.getAttribute("xml:id") || prevNote.getAttribute("id");
7757                if (prevNoteId) cleanTiesForNote(doc, prevNoteId);
7758              }
7759            }
7760          }
7761          noteEl.removeAttribute("dots");
7762          if (elapsedBeats + newBeatDur > 4) {
7763            const ns = "http://www.music-encoding.org/ns/mei";
7764            const dur1 = 4 - elapsedBeats;
7765            const dur2 = newBeatDur - dur1;
7766            const { dur: dur1Attr, dots: dots1Attr } = getMeiDurAttr(dur1);
7767            noteEl.setAttribute("dur", dur1Attr);
7768            if (dots1Attr) noteEl.setAttribute("dots", dots1Attr);
7769            noteEl.setAttribute("tie", "s");
7770            noteEl.setAttribute("tmp-start-beat", elapsedBeats.toString());
7771            let nextMeasure = measure == null ? void 0 : measure.nextElementSibling;
7772            let nextM = nextMeasure;
7773            while (nextM && nextM.tagName !== "measure") {
7774              nextM = nextM.nextElementSibling;
7775            }
7776            if (!nextM && measure && measure.parentNode) {
7777              const parentSection = measure.parentNode;
7778              const nextN = measureNum + 1;
7779              const newMeasure = doc.createElementNS(ns, "measure");
7780              newMeasure.setAttribute("n", nextN.toString());
7781              newMeasure.setAttribute("xml:id", `m-${nextN}`);
7782              const staff = doc.createElementNS(ns, "staff");
7783              staff.setAttribute("n", "1");
7784              staff.setAttribute("xml:id", `s-${nextN}`);
7785              const newLayer = doc.createElementNS(ns, "layer");
7786              newLayer.setAttribute("n", "1");
7787              newLayer.setAttribute("xml:id", `l-${nextN}`);
7788              const mRest = doc.createElementNS(ns, "mRest");
7789              mRest.setAttribute("xml:id", `mr-${nextN}`);
7790              newLayer.appendChild(mRest);
7791              staff.appendChild(newLayer);
7792              newMeasure.appendChild(staff);
7793              parentSection.appendChild(newMeasure);
7794              nextM = newMeasure;
7795            }
7796            if (nextM) {
7797              const currentLayerN = layer.getAttribute("n") || "1";
7798              const nextStaff = nextM.querySelector("staff");
7799              if (nextStaff) {
7800                let nextLayer = nextStaff.querySelector(`layer[n="${currentLayerN}"]`);
7801                if (!nextLayer) {
7802                  nextLayer = doc.createElementNS(ns, "layer");
7803                  nextLayer.setAttribute("n", currentLayerN);
7804                  const nextN = measureNum + 1;
7805                  nextLayer.setAttribute("xml:id", `l-${nextN}-${currentLayerN}`);
7806                  nextStaff.appendChild(nextLayer);
7807                }
7808                const nextMRest = nextLayer.querySelector("mRest");
7809                if (nextMRest) nextMRest.remove();
7810                const tiedNote = doc.createElementNS(ns, "note");
7811                const tiedNoteId = "note-" + Math.random().toString(36).substr(2, 9);
7812                tiedNote.setAttribute("xml:id", tiedNoteId);
7813                tiedNote.setAttribute("pname", pname);
7814                tiedNote.setAttribute("oct", oct);
7815                if (accid) tiedNote.setAttribute("accid", accid);
7816                const { dur: dur2Attr, dots: dots2Attr } = getMeiDurAttr(dur2);
7817                tiedNote.setAttribute("dur", dur2Attr);
7818                if (dots2Attr) tiedNote.setAttribute("dots", dots2Attr);
7819                tiedNote.setAttribute("tie", "e");
7820                tiedNote.setAttribute("tmp-start-beat", "0");
7821                if (nextLayer.firstChild) {
7822                  nextLayer.insertBefore(tiedNote, nextLayer.firstChild);
7823                } else {
7824                  nextLayer.appendChild(tiedNote);
7825                }
7826                const tieEl = doc.createElementNS(ns, "tie");
7827                tieEl.setAttribute("startid", `#${noteId}`);
7828                tieEl.setAttribute("endid", `#${tiedNoteId}`);
7829                tieEl.setAttribute("xml:id", "tie-" + Math.random().toString(36).substr(2, 9));
7830                measure.appendChild(tieEl);
7831              }
7832            }
7833          } else {
7834            noteEl.setAttribute("dur", dur);
7835            noteEl.removeAttribute("tie");
7836            noteEl.setAttribute("tmp-start-beat", elapsedBeats.toString());
7837          }
7838        }
7839      }
7840    });
7841  }, [activeTrackIndex, engine, updateNote, updateMei]);
7842  const applyArticulationToNote = reactExports.useCallback((noteId, articulationType) => {
7843    editingFromScoreRef.current = true;
7844    updateMei((doc) => {
7845      const noteEl = getXmlElById(doc, noteId);
7846      if (noteEl && noteEl.tagName === "note") {
7847        if (articulationType) {
7848          if (articulationType === "staccato") {
7849            noteEl.setAttribute("artic", "stacc");
7850            noteEl.removeAttribute("fermata");
7851          } else if (articulationType === "accent") {
7852            noteEl.setAttribute("artic", "acc");
7853            noteEl.removeAttribute("fermata");
7854          } else if (articulationType === "tenuto") {
7855            noteEl.setAttribute("artic", "ten");
7856            noteEl.removeAttribute("fermata");
7857          } else if (articulationType === "fermata") {
7858            noteEl.setAttribute("fermata", "above");
7859            noteEl.removeAttribute("artic");
7860          }
7861        } else {
7862          noteEl.removeAttribute("artic");
7863          noteEl.removeAttribute("fermata");
7864        }
7865      }
7866    });
7867  }, [updateMei]);
7868  reactExports.useEffect(() => {
7869    if (!selectedElementId || !isReady || activeTrackIndex === null) return;
7870    const parser = new DOMParser();
7871    const doc = parser.parseFromString(meiDocs[activeTrackIndex] || "", "application/xml");
7872    const noteEl = getXmlElById(doc, selectedElementId);
7873    if (noteEl && noteEl.tagName === "note") {
7874      const accid = noteEl.getAttribute("accid");
7875      const dur = noteEl.getAttribute("dur");
7876      const artic = noteEl.getAttribute("artic");
7877      const fermata = noteEl.getAttribute("fermata");
7878      if (accid === "s") setCurrentAccidental("sharp");
7879      else if (accid === "f") setCurrentAccidental("flat");
7880      else if (accid === "n") setCurrentAccidental("natural");
7881      else setCurrentAccidental(null);
7882      if (artic === "stacc") setCurrentArticulation("staccato");
7883      else if (artic === "acc") setCurrentArticulation("accent");
7884      else if (artic === "ten") setCurrentArticulation("tenuto");
7885      else if (fermata === "above") setCurrentArticulation("fermata");
7886      else setCurrentArticulation(null);
7887      const toolMap = {
7888        "1": "note_whole",
7889        "2": "note_half",
7890        "4": "note_quarter",
7891        "8": "note_eighth",
7892        "16": "note_sixteenth"
7893      };
7894      if (dur && toolMap[dur]) {
7895        setCurrentTool(toolMap[dur]);
7896      }
7897    }
7898  }, [selectedElementId, meiDocs, activeTrackIndex, isReady]);
7899  const handleSelectTool = reactExports.useCallback((tool) => {
7900    setCurrentTool(tool);
7901    if (selectedElementId && tool.startsWith("note_")) {
7902      applyDurationToNote(selectedElementId, tool);
7903    } else if (tool === "select" || tool === "erase") {
7904      setSelectedElementId(null);
7905    }
7906  }, [selectedElementId, applyDurationToNote]);
7907  const handleSelectAccidental = reactExports.useCallback((accid) => {
7908    setCurrentAccidental(accid);
7909    if (selectedElementId && (accid === null || accid === "sharp" || accid === "flat" || accid === "natural")) {
7910      applyAccidentalToNote(selectedElementId, accid);
7911    }
7912  }, [selectedElementId, applyAccidentalToNote]);
7913  const handleSelectArticulation = reactExports.useCallback((artic) => {
7914    setCurrentArticulation(artic);
7915    if (selectedElementId) {
7916      applyArticulationToNote(selectedElementId, artic);
7917    }
7918  }, [selectedElementId, applyArticulationToNote]);
7919  const handleDeleteElement = reactExports.useCallback((elementId) => {
7920    editingFromScoreRef.current = true;
7921    updateMei((doc) => {
7922      const noteEl = getXmlElById(doc, elementId);
7923      if (noteEl && noteEl.tagName === "note") {
7924        const pname = noteEl.getAttribute("pname") || "";
7925        const oct = noteEl.getAttribute("oct") || "";
7926        const accid = noteEl.getAttribute("accid") || void 0;
7927        const midiPitch = getMidiFromMEI(pname, parseInt(oct), accid);
7928        const tie = noteEl.getAttribute("tie");
7929        const layer = noteEl.closest("layer");
7930        if (layer) {
7931          let eventEl = noteEl;
7932          if (noteEl.parentElement && noteEl.parentElement.tagName === "chord") {
7933            eventEl = noteEl.parentElement;
7934          }
7935          const elapsedBeats = getStartTimeInLayer(layer, eventEl);
7936          const measure = layer.closest("measure");
7937          let measureNum = 1;
7938          if (measure) {
7939            const nAttr = measure.getAttribute("n");
7940            if (nAttr) measureNum = parseInt(nAttr);
7941          }
7942          getBeatsPerMeasureFromDoc(doc);
7943          const actualStartSeconds = getActualNoteStartTime(noteEl, measureNum, elapsedBeats);
7944          if (activeTrackIndex !== null && engine && engine.midi && engine.midi.tracks[activeTrackIndex]) {
7945            const track = engine.midi.tracks[activeTrackIndex];
7946            const idx = track.notes.findIndex((n) => n.midi === midiPitch && Math.abs(n.time - actualStartSeconds) < 0.05);
7947            if (idx !== -1) {
7948              deleteNote(activeTrackIndex, idx, "score");
7949            }
7950          }
7951          cleanTiesForNote(doc, elementId);
7952          if (tie === "s") {
7953            const nextMeasure = measure == null ? void 0 : measure.nextElementSibling;
7954            let nextM = nextMeasure;
7955            while (nextM && nextM.tagName !== "measure") {
7956              nextM = nextM.nextElementSibling;
7957            }
7958            if (nextM) {
7959              const nextNote = Array.from(nextM.querySelectorAll("note")).find(
7960                (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "e"
7961              );
7962              if (nextNote) {
7963                const nextNoteId = nextNote.getAttribute("xml:id") || nextNote.getAttribute("id");
7964                if (nextNoteId) cleanTiesForNote(doc, nextNoteId);
7965                nextNote.remove();
7966              }
7967            }
7968          } else if (tie === "e") {
7969            let prevM = measure == null ? void 0 : measure.previousElementSibling;
7970            while (prevM && prevM.tagName !== "measure") {
7971              prevM = prevM.previousElementSibling;
7972            }
7973            if (prevM) {
7974              const prevNote = Array.from(prevM.querySelectorAll("note")).find(
7975                (n) => n.getAttribute("pname") === pname && n.getAttribute("oct") === oct && n.getAttribute("tie") === "s"
7976              );
7977              if (prevNote) {
7978                const prevNoteId = prevNote.getAttribute("xml:id") || prevNote.getAttribute("id");
7979                if (prevNoteId) cleanTiesForNote(doc, prevNoteId);
7980                prevNote.remove();
7981              }
7982            }
7983          }
7984        }
7985        const parent = noteEl.parentNode;
7986        noteEl.remove();
7987        if (parent && parent.tagName === "chord") {
7988          const remainingNotes = Array.from(parent.querySelectorAll("note"));
7989          if (remainingNotes.length === 1) {
7990            const singleNote = remainingNotes[0];
7991            const chordDur = parent.getAttribute("dur");
7992            const chordDots = parent.getAttribute("dots");
7993            if (chordDur) singleNote.setAttribute("dur", chordDur);
7994            if (chordDots) singleNote.setAttribute("dots", chordDots);
7995            const chordParent = parent.parentNode;
7996            if (chordParent) {
7997              chordParent.insertBefore(singleNote, parent);
7998              parent.remove();
7999            }
8000          } else if (remainingNotes.length === 0) {
8001            parent.remove();
8002          }
8003        }
8004      } else if (noteEl) {
8005        noteEl.remove();
8006      }
8007    });
8008    if (elementId === selectedElementId) {
8009      setSelectedElementId(null);
8010    }
8011  }, [activeTrackIndex, engine, updateMei, deleteNote, selectedElementId]);
8012  const handleElementClick = reactExports.useCallback((elementId, e, yOffset, passedSnappedBeat) => {
8013    var _a2;
8014    if (!isReady || !containerRef.current) return;
8015    let gNode = containerRef.current.querySelector(`[id="${elementId}"]`);
8016    if (!gNode) return;
8017    let className = gNode.getAttribute("class") || "";
8018    if (className.includes("bounding-box") || gNode.id.startsWith("bbox-")) {
8019      gNode = gNode.parentElement;
8020      if (!gNode) return;
8021      elementId = gNode.getAttribute("id") || elementId;
8022      className = gNode.getAttribute("class") || "";
8023    }
8024    if (currentTool === "select") {
8025      if (className.includes("note") || className.includes("chord")) {
8026        const target = e.target;
8027        const closestNote = target.closest("g.note");
8028        const noteId = (closestNote == null ? void 0 : closestNote.getAttribute("id")) || elementId;
8029        setSelectedElementId(noteId);
8030        if (activeTrackIndex !== null && meiDocs[activeTrackIndex] && ((_a2 = engine == null ? void 0 : engine.midi) == null ? void 0 : _a2.tracks[activeTrackIndex])) {
8031          const parser = new DOMParser();
8032          const doc = parser.parseFromString(meiDocs[activeTrackIndex], "application/xml");
8033          const noteEl = getXmlElById(doc, noteId);
8034          if (noteEl && noteEl.tagName === "note") {
8035            const pname = noteEl.getAttribute("pname") || "c";
8036            const oct = noteEl.getAttribute("oct") || "4";
8037            const originalAccid = noteEl.getAttribute("accid") || void 0;
8038            const oldMidi = getMidiFromMEI(pname, parseInt(oct), originalAccid);
8039            const layer = noteEl.closest("layer");
8040            if (layer) {
8041              let eventEl = noteEl;
8042              if (noteEl.parentElement && noteEl.parentElement.tagName === "chord") {
8043                eventEl = noteEl.parentElement;
8044              }
8045              const elapsedBeats = getStartTimeInLayer(layer, eventEl);
8046              const measure = layer.closest("measure");
8047              let measureNum = 1;
8048              if (measure) {
8049                const nAttr = measure.getAttribute("n");
8050                if (nAttr) measureNum = parseInt(nAttr);
8051              }
8052              const actualStartSeconds = getActualNoteStartTime(noteEl, measureNum, elapsedBeats);
8053              const track = engine.midi.tracks[activeTrackIndex];
8054              const noteIndex = track.notes.findIndex((n) => n.midi === oldMidi && Math.abs(n.time - actualStartSeconds) < 0.05);
8055              if (noteIndex !== -1) {
8056                playNotePreview(oldMidi);
8057                if (e && e.shiftKey) {
8058                  setSelectedNoteIds(selectedNoteIds.includes(noteIndex) ? selectedNoteIds.filter((id) => id !== noteIndex) : [...selectedNoteIds, noteIndex]);
8059                } else {
8060                  setSelectedNoteIds([noteIndex]);
8061                }
8062              } else {
8063                setSelectedNoteIds([]);
8064              }
8065            }
8066          }
8067        }
8068      } else {
8069        setSelectedElementId(null);
8070        setSelectedNoteIds([]);
8071      }
8072      return;
8073    }
8074    if (currentTool === "erase" && className.includes("note")) {
8075      handleDeleteElement(elementId);
8076      return;
8077    }
8078    if (currentTool.startsWith("note_")) {
8079      editingFromScoreRef.current = true;
8080      let mRestIdToRemove = null;
8081      let layerIdToInsert = null;
8082      let layerNode = null;
8083      if (className.includes("mRest")) {
8084        mRestIdToRemove = elementId;
8085      } else {
8086        const measureNode2 = gNode.closest("g.measure") || (className.includes("measure") ? gNode : null);
8087        if (measureNode2) {
8088          const mRestNode = measureNode2.querySelector(".mRest");
8089          if (mRestNode) {
8090            mRestIdToRemove = mRestNode.getAttribute("id");
8091          }
8092        }
8093      }
8094      if (className.includes("layer")) {
8095        layerNode = gNode;
8096        layerIdToInsert = elementId;
8097      } else {
8098        layerNode = gNode.closest("g.layer") || gNode.querySelector("g.layer") || gNode.closest(".layer") || gNode.querySelector(".layer");
8099        if (layerNode) {
8100          layerIdToInsert = layerNode.getAttribute("id") || layerNode.getAttribute("xml:id");
8101        }
8102      }
8103      if (layerNode) {
8104        const mRestNode = layerNode.querySelector(".mRest[id]");
8105        if (mRestNode) {
8106          mRestIdToRemove = mRestNode.getAttribute("id");
8107        }
8108        if (mRestIdToRemove) ;
8109        else {
8110          const noteRests = Array.from(layerNode.querySelectorAll(".note[id], .rest[id]"));
8111          if (noteRests.length > 0) {
8112            const sortedElements = noteRests.map((el) => ({
8113              id: el.getAttribute("id"),
8114              rect: el.getBoundingClientRect()
8115            })).sort((a, b) => a.rect.left - b.rect.left);
8116            const clickX = e.clientX;
8117            const nextElement = sortedElements.find((item) => {
8118              const centerX = item.rect.left + item.rect.width / 2;
8119              return centerX > clickX;
8120            });
8121            if (nextElement) {
8122              nextElement.id;
8123            }
8124          }
8125        }
8126      }
8127      const beatsPerMeasure = getBeatsPerMeasureFromMei(activeTrackIndex !== null ? meiDocs[activeTrackIndex] : void 0);
8128      const durMap = {
8129        "note_whole": "1",
8130        "note_half": "2",
8131        "note_quarter": "4",
8132        "note_eighth": "8",
8133        "note_sixteenth": "16"
8134      };
8135      const dur = durMap[currentTool];
8136      let beatDur = getDurationInBeats(dur);
8137      if (isTriplet) {
8138        beatDur /= 1.5;
8139      }
8140      if (isDotted) {
8141        beatDur *= 1.5;
8142      }
8143      let pitch = "c";
8144      let oct = "4";
8145      if (yOffset !== void 0) {
8146        const step = 38 - yOffset;
8147        const pitchMap = ["c", "d", "e", "f", "g", "a", "b"];
8148        pitch = pitchMap[(step % 7 + 7) % 7];
8149        oct = Math.floor(step / 7).toString();
8150      }
8151      let accid = currentAccidental === "sharp" ? "s" : currentAccidental === "flat" ? "f" : currentAccidental === "natural" ? "n" : void 0;
8152      getMidiFromMEI(pitch, parseInt(oct), accid);
8153      let snappedBeat = passedSnappedBeat;
8154      const measureNode = gNode.closest("g.measure") || (className.includes("measure") ? gNode : null);
8155      if (snappedBeat === void 0 && measureNode) {
8156        const rect = measureNode.getBoundingClientRect();
8157        let startX = rect.left + 50;
8158        const elements = Array.from(measureNode.querySelectorAll(".note, .rest, .mRest, .chord"));
8159        if (elements.length > 0) {
8160          let minLeft = Infinity;
8161          elements.forEach((el) => {
8162            const elRect = el.getBoundingClientRect();
8163            if (elRect.left < minLeft && elRect.width > 0) minLeft = elRect.left + elRect.width / 2;
8164          });
8165          if (minLeft !== Infinity) startX = minLeft;
8166        }
8167        const endX = rect.right - 10;
8168        const usableWidth = endX - startX;
8169        let beat = 0;
8170        if (usableWidth > 0 && e.clientX > startX) {
8171          beat = (e.clientX - startX) / usableWidth * beatsPerMeasure;
8172        }
8173        const gridStep = beatDur >= 1 ? 1 : beatDur;
8174        const roundedBeat = Math.round(beat / gridStep) * gridStep;
8175        snappedBeat = Math.max(0, Math.min(beatsPerMeasure - gridStep, roundedBeat));
8176      } else if (snappedBeat === void 0) {
8177        snappedBeat = 0;
8178      }
8179      const finishNotePlacement = (midiPitch2) => {
8180        const isRest = entryMode === "rest";
8181        if (!isRest) {
8182          playNotePreview(midiPitch2);
8183        }
8184        updateMei((doc) => {
8185          const ns = "http://www.music-encoding.org/ns/mei";
8186          let targetLayer = null;
8187          const clickedEl = getXmlElById(doc, elementId);
8188          if (clickedEl) {
8189            targetLayer = clickedEl.tagName === "layer" ? clickedEl : clickedEl.closest("layer");
8190            if (!targetLayer) {
8191              targetLayer = clickedEl.querySelector("layer");
8192            }
8193          }
8194          if (!targetLayer) {
8195            targetLayer = getXmlElById(doc, layerIdToInsert) || doc.querySelector(`layer`);
8196          }
8197          if (targetLayer) {
8198            const measure = targetLayer.closest("measure");
8199            let measureNum = 1;
8200            if (measure) {
8201              const nAttr = measure.getAttribute("n");
8202              if (nAttr) measureNum = parseInt(nAttr);
8203            }
8204            let staff = targetLayer.closest("staff");
8205            if (!staff && measure) {
8206              staff = measure.querySelector("staff");
8207            }
8208            let targetLayerN = 1;
8209            if (staff) {
8210              if (currentVoice === "All" || currentVoice === 1) {
8211                let layer1 = staff.querySelector('layer[n="1"]');
8212                if (layer1) {
8213                  const overlaps = checkOverlapInLayer(layer1, snappedBeat, beatDur);
8214                  if (overlaps) {
8215                    targetLayerN = 2;
8216                  }
8217                }
8218              } else {
8219                targetLayerN = currentVoice;
8220              }
8221              let layerNode2 = staff.querySelector(`layer[n="${targetLayerN}"]`);
8222              if (!layerNode2) {
8223                layerNode2 = doc.createElementNS(ns, "layer");
8224                layerNode2.setAttribute("n", targetLayerN.toString());
8225                layerNode2.setAttribute("xml:id", `l-${measureNum}-${targetLayerN}`);
8226                staff.appendChild(layerNode2);
8227              }
8228              targetLayer = layerNode2;
8229            }
8230            if (targetLayerN !== 1) {
8231              const targetMRest = targetLayer.querySelector("mRest");
8232              if (targetMRest) targetMRest.remove();
8233            } else {
8234              if (mRestIdToRemove) {
8235                const mRest = getXmlElById(doc, mRestIdToRemove);
8236                if (mRest) mRest.remove();
8237              }
8238              const targetMRest = targetLayer.querySelector("mRest");
8239              if (targetMRest) targetMRest.remove();
8240            }
8241            const note = doc.createElementNS(ns, isRest ? "rest" : "note");
8242            const newNoteId = (isRest ? "rest-" : "note-") + Math.random().toString(36).substr(2, 9);
8243            note.setAttribute("xml:id", newNoteId);
8244            if (!isRest) {
8245              if (!isPercussion) {
8246                note.setAttribute("pname", pitch);
8247                note.setAttribute("oct", oct);
8248                if (accid) note.setAttribute("accid", accid);
8249              } else {
8250                note.setAttribute("pname", "c");
8251                note.setAttribute("oct", "5");
8252              }
8253              if (currentArticulation) {
8254                if (currentArticulation === "staccato") note.setAttribute("artic", "stacc");
8255                else if (currentArticulation === "accent") note.setAttribute("artic", "acc");
8256                else if (currentArticulation === "tenuto") note.setAttribute("artic", "ten");
8257                else if (currentArticulation === "fermata") note.setAttribute("fermata", "above");
8258              }
8259            }
8260            if (isDotted) {
8261              note.setAttribute("dots", "1");
8262            }
8263            if (isTriplet) {
8264              note.setAttribute("num", "3");
8265              note.setAttribute("numbase", "2");
8266            }
8267            if (snappedBeat + beatDur > beatsPerMeasure) {
8268              const dur1 = beatsPerMeasure - snappedBeat;
8269              const dur2 = beatDur - dur1;
8270              const { dur: dur1Attr, dots: dots1Attr } = getMeiDurAttr(dur1);
8271              note.setAttribute("dur", dur1Attr);
8272              if (dots1Attr) note.setAttribute("dots", dots1Attr);
8273              if (!isRest) {
8274                note.setAttribute("tie", "s");
8275              }
8276              note.setAttribute("tmp-start-beat", snappedBeat.toString());
8277              targetLayer.appendChild(note);
8278              let nextMeasure = measure == null ? void 0 : measure.nextElementSibling;
8279              let nextM = nextMeasure;
8280              while (nextM && nextM.tagName !== "measure") {
8281                nextM = nextM.nextElementSibling;
8282              }
8283              if (!nextM && measure && measure.parentNode) {
8284                const parentSection = measure.parentNode;
8285                const nextN = measureNum + 1;
8286                const newMeasure = doc.createElementNS(ns, "measure");
8287                newMeasure.setAttribute("n", nextN.toString());
8288                newMeasure.setAttribute("xml:id", `m-${nextN}`);
8289                const staff2 = doc.createElementNS(ns, "staff");
8290                staff2.setAttribute("n", "1");
8291                staff2.setAttribute("xml:id", `s-${nextN}`);
8292                const newLayer = doc.createElementNS(ns, "layer");
8293                newLayer.setAttribute("n", "1");
8294                newLayer.setAttribute("xml:id", `l-${nextN}`);
8295                const mRest = doc.createElementNS(ns, "mRest");
8296                mRest.setAttribute("xml:id", `mr-${nextN}`);
8297                newLayer.appendChild(mRest);
8298                staff2.appendChild(newLayer);
8299                newMeasure.appendChild(staff2);
8300                parentSection.appendChild(newMeasure);
8301                nextM = newMeasure;
8302              }
8303              if (nextM) {
8304                const targetLayerN2 = targetLayer.getAttribute("n") || "1";
8305                const nextStaff = nextM.querySelector("staff");
8306                if (nextStaff) {
8307                  let nextLayer = nextStaff.querySelector(`layer[n="${targetLayerN2}"]`);
8308                  if (!nextLayer) {
8309                    nextLayer = doc.createElementNS(ns, "layer");
8310                    nextLayer.setAttribute("n", targetLayerN2);
8311                    const nextN = measureNum + 1;
8312                    nextLayer.setAttribute("xml:id", `l-${nextN}-${targetLayerN2}`);
8313                    nextStaff.appendChild(nextLayer);
8314                  }
8315                  const nextMRest = nextLayer.querySelector("mRest");
8316                  if (nextMRest) nextMRest.remove();
8317                  const tiedEl = doc.createElementNS(ns, isRest ? "rest" : "note");
8318                  const tiedElId = (isRest ? "rest-" : "note-") + Math.random().toString(36).substr(2, 9);
8319                  tiedEl.setAttribute("xml:id", tiedElId);
8320                  if (!isRest) {
8321                    if (!isPercussion) {
8322                      tiedEl.setAttribute("pname", pitch);
8323                      tiedEl.setAttribute("oct", oct);
8324                      if (accid) tiedEl.setAttribute("accid", accid);
8325                    } else {
8326                      tiedEl.setAttribute("pname", "c");
8327                      tiedEl.setAttribute("oct", "5");
8328                    }
8329                    tiedEl.setAttribute("tie", "e");
8330                  }
8331                  const { dur: dur2Attr, dots: dots2Attr } = getMeiDurAttr(dur2);
8332                  tiedEl.setAttribute("dur", dur2Attr);
8333                  if (dots2Attr) tiedEl.setAttribute("dots", dots2Attr);
8334                  tiedEl.setAttribute("tmp-start-beat", "0");
8335                  if (nextLayer.firstChild) {
8336                    nextLayer.insertBefore(tiedEl, nextLayer.firstChild);
8337                  } else {
8338                    nextLayer.appendChild(tiedEl);
8339                  }
8340                  if (!isRest) {
8341                    const tieEl = doc.createElementNS(ns, "tie");
8342                    tieEl.setAttribute("startid", `#${newNoteId}`);
8343                    tieEl.setAttribute("endid", `#${tiedElId}`);
8344                    tieEl.setAttribute("xml:id", "tie-" + Math.random().toString(36).substr(2, 9));
8345                    if (measure) measure.appendChild(tieEl);
8346                  }
8347                }
8348              }
8349            } else {
8350              note.setAttribute("dur", dur);
8351              note.setAttribute("tmp-start-beat", snappedBeat.toString());
8352              if (isTieActive && !isRest) {
8353                const sisterNotes = Array.from(targetLayer.querySelectorAll("note"));
8354                if (sisterNotes.length > 0) {
8355                  const prevNote = sisterNotes[sisterNotes.length - 1];
8356                  if (prevNote && prevNote.getAttribute("pname") === pitch && prevNote.getAttribute("oct") === oct) {
8357                    prevNote.setAttribute("tie", "s");
8358                    note.setAttribute("tie", "e");
8359                    const tieEl = doc.createElementNS(ns, "tie");
8360                    tieEl.setAttribute("startid", `#${prevNote.getAttribute("xml:id")}`);
8361                    tieEl.setAttribute("endid", `#${newNoteId}`);
8362                    tieEl.setAttribute("xml:id", "tie-" + Math.random().toString(36).substr(2, 9));
8363                    const measure2 = targetLayer.closest("measure");
8364                    if (measure2) measure2.appendChild(tieEl);
8365                  }
8366                }
8367              }
8368              targetLayer.appendChild(note);
8369            }
8370            if (!isRest && activeTrackIndex !== null && engine) {
8371              const bpm = tempo || 120;
8372              const totalStartBeat = (measureNum - 1) * beatsPerMeasure + snappedBeat;
8373              const totalStartSeconds = totalStartBeat / (bpm / 60);
8374              const durationSeconds = beatDur / (bpm / 60);
8375              addNote(activeTrackIndex, midiPitch2, engine.secondsToTicks(totalStartSeconds), engine.secondsToTicks(durationSeconds), 0.8, "score");
8376            }
8377          }
8378        });
8379        if (!isRest && engine) {
8380          engine.startNotePreview(midiPitch2);
8381        }
8382      };
8383      if (isPercussion) {
8384        if (currentAccidental && ["kick", "snare", "hihat", "cymbal"].includes(currentAccidental)) {
8385          let drumPitch = 38;
8386          if (currentAccidental === "kick") drumPitch = 36;
8387          else if (currentAccidental === "snare") drumPitch = 38;
8388          else if (currentAccidental === "hihat") drumPitch = 42;
8389          else if (currentAccidental === "cymbal") drumPitch = 49;
8390          finishNotePlacement(drumPitch);
8391          return;
8392        }
8393        __vitePreload(async () => {
8394          const { REVERSE_DRUM_MAP, getPitchFromY } = await import("./ScoreEditorCanvas-Cg6gP8yn.js").then((n) => n.h);
8395          return { REVERSE_DRUM_MAP, getPitchFromY };
8396        }, true ? __vite__mapDeps([0,1,2,3,4,5,6,7,8]) : void 0).then(({ REVERSE_DRUM_MAP, getPitchFromY }) => {
8397          const verticalPitchStr = getPitchFromY(meiDocs[activeTrackIndex], yOffset, snappedBeat);
8398          const pitch2 = verticalPitchStr && REVERSE_DRUM_MAP[verticalPitchStr.toLowerCase()] || 38;
8399          finishNotePlacement(pitch2);
8400        });
8401        return;
8402      }
8403      if (yOffset !== void 0) {
8404        __vitePreload(async () => {
8405          const { getPitchFromY } = await import("./ScoreEditorCanvas-Cg6gP8yn.js").then((n) => n.h);
8406          return { getPitchFromY };
8407        }, true ? __vite__mapDeps([0,1,2,3,4,5,6,7,8]) : void 0).then(({ getPitchFromY }) => {
8408          const verticalPitchStr = getPitchFromY(meiDocs[activeTrackIndex], yOffset, snappedBeat);
8409          if (verticalPitchStr) {
8410            const match = verticalPitchStr.match(/^([a-g])(-?\d+)$/i);
8411            if (match) {
8412              const pitch2 = match[1].toLowerCase();
8413              const oct2 = parseInt(match[2]);
8414              const accid2 = currentAccidental === "sharp" ? "s" : currentAccidental === "flat" ? "f" : currentAccidental === "natural" ? "n" : void 0;
8415              finishNotePlacement(getMidiFromMEI(pitch2, oct2, accid2));
8416            } else {
8417              finishNotePlacement(60);
8418            }
8419          } else {
8420            const step = 38 - yOffset;
8421            const pitchMap = ["c", "d", "e", "f", "g", "a", "b"];
8422            const pitch2 = pitchMap[(step % 7 + 7) % 7];
8423            const oct2 = Math.floor(step / 7).toString();
8424            const accid2 = currentAccidental === "sharp" ? "s" : currentAccidental === "flat" ? "f" : currentAccidental === "natural" ? "n" : void 0;
8425            finishNotePlacement(getMidiFromMEI(pitch2, parseInt(oct2), accid2));
8426          }
8427        });
8428      } else {
8429        finishNotePlacement(60);
8430      }
8431    }
8432  }, [isReady, containerRef, currentTool, currentAccidental, activeTrackIndex, updateMei, addNote, handleDeleteElement, engine, currentVoice, isPercussion, meiDocs]);
8433  const handleElementRightClick = (elementId, className) => {
8434    if (!isReady) return;
8435    if (className.includes("note") || className.includes("rest")) {
8436      handleDeleteElement(elementId);
8437    }
8438  };
8439  const handleElementDrag = reactExports.useCallback((elementId, dy, clientY) => {
8440    if (!isReady || activeTrackIndex === null || !containerRef.current || !meiDocs[activeTrackIndex]) return;
8441    const noteSVG = containerRef.current.querySelector(`[id="${elementId}"]`);
8442    if (!noteSVG) return;
8443    const staffSVG = noteSVG.closest("g.staff");
8444    if (!staffSVG) return;
8445    const newOffset = getSnappingOffset(clientY, staffSVG);
8446    editingFromScoreRef.current = true;
8447    updateMei((doc) => {
8448      var _a2;
8449      const noteEl = getXmlElById
8449(doc, elementId);
8450      if (noteEl && noteEl.tagName === "note") {
8451        const pname = noteEl.getAttribute("pname") || "c";
8452        const oct = noteEl.getAttribute("oct") || "4";
8453        const originalAccid = noteEl.getAttribute("accid") || void 0;
8454        const oldMidi = getMidiFromMEI(pname, parseInt(oct), originalAccid);
8455        const layer = noteEl.closest("layer");
8456        if (layer) {
8457          let eventEl = noteEl;
8458          if (noteEl.parentElement && noteEl.parentElement.tagName === "chord") {
8459            eventEl = noteEl.parentElement;
8460          }
8461          const elapsedBeats = getStartTimeInLayer(layer, eventEl);
8462          const measure = noteEl.closest("measure");
8463          let measureNum = 1;
8464          if (measure) {
8465            const nAttr = measure.getAttribute("n");
8466            if (nAttr) measureNum = parseInt(nAttr);
8467          }
8468          const actualStartSeconds = getActualNoteStartTime(noteEl, measureNum, elapsedBeats);
8469          const step = 38 - newOffset;
8470          const pitchMap = ["c", "d", "e", "f", "g", "a", "b"];
8471          const newPitch = pitchMap[(step % 7 + 7) % 7];
8472          const newOct = Math.floor(step / 7).toString();
8473          const accid = noteEl.getAttribute("accid") || void 0;
8474          noteEl.setAttribute("pname", newPitch);
8475          noteEl.setAttribute("oct", newOct);
8476          const newMidi = getMidiFromMEI(newPitch, parseInt(newOct), accid);
8477          if (activeTrackIndex !== null && ((_a2 = engine == null ? void 0 : engine.midi) == null ? void 0 : _a2.tracks[activeTrackIndex])) {
8478            const track = engine.midi.tracks[activeTrackIndex];
8479            const noteIndex = track.notes.findIndex((n) => n.midi === oldMidi && Math.abs(n.time - actualStartSeconds) < 0.05);
8480            if (noteIndex !== -1) {
8481              updateNote(activeTrackIndex, noteIndex, { midi: newMidi });
8482            }
8483          }
8484        }
8485      }
8486    });
8487  }, [isReady, activeTrackIndex, meiDocs, updateMei, updateNote, engine]);
8488  const handleTogglePlay = () => {
8489    if (isPlaying) {
8490      pause();
8491    } else {
8492      play();
8493    }
8494  };
8495  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden relative bg-[#8e8e93] dark:bg-[#202020]", children: [
8496    /* @__PURE__ */ jsxRuntimeExports.jsx(
8497      ScoreRibbon,
8498      {
8499        currentTool,
8500        onSelectTool: handleSelectTool,
8501        isPlaying,
8502        onTogglePlay: handleTogglePlay,
8503        onStop: stop,
8504        onZoomIn: () => setZoom((z) => Math.min(3, z + 0.1)),
8505        onZoomOut: () => setZoom((z) => Math.max(0.1, z - 0.1)),
8506        showMeasureNumbers,
8507        onToggleMeasureNumbers: () => setShowMeasureNumbers(!showMeasureNumbers),
8508        currentAccidental,
8509        onSelectAccidental: handleSelectAccidental,
8510        isPercussion,
8511        showKeypad,
8512        onToggleKeypad: setShowKeypad,
8513        showTextEditor,
8514        onToggleTextEditor: () => setShowTextEditor(!showTextEditor),
8515        entryMode,
8516        onSelectEntryMode: setEntryMode,
8517        isDotted,
8518        onToggleDotted: () => setIsDotted(!isDotted),
8519        isTieActive,
8520        onToggleTieActive: () => setIsTieActive(!isTieActive),
8521        isTriplet,
8522        onToggleTriplet: () => setIsTriplet(!isTriplet),
8523        currentArticulation,
8524        onSelectArticulation: handleSelectArticulation,
8525        showGrid,
8526        onToggleGrid: () => setShowGrid(!showGrid),
8527        onStartTour: () => setShowLiveTour(true)
8528      }
8529    ),
8530    /* @__PURE__ */ jsxRuntimeExports.jsx(ScoreDocumentTabs, {}),
8531    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden relative", ref: containerRef, children: [
8532      showTextEditor && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-[35%] border-r border-zinc-300 dark:border-zinc-800 flex flex-col bg-white dark:bg-[#1a1a1a]", children: [
8533        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 bg-zinc-100 dark:bg-zinc-800 px-3 flex items-center justify-between border-b border-zinc-200 dark:border-zinc-700 shrink-0", children: [
8534          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-semibold text-zinc-600 dark:text-zinc-400 font-mono tracking-wider", children: "HUMDRUM CODE EDITOR" }),
8535          /* @__PURE__ */ jsxRuntimeExports.jsx(
8536            "button",
8537            {
8538              onClick: () => setShowTextEditor(false),
8539              className: "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200 text-[10px] font-semibold uppercase tracking-wider transition-colors",
8540              children: "Close"
8541            }
8542          )
8543        ] }),
8544        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: aceContainerRef, className: "humdrum-ace-editor flex-1 font-mono text-sm leading-relaxed", style: { width: "100%", height: "100%" } })
8545      ] }),
8546      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { id: "score-canvas-container", className: "flex-1 overflow-auto bg-[#8e8e93] dark:bg-[#323232] relative p-8", children: [
8547        syntaxError && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-4 right-4 z-10 flex items-center gap-1.5 bg-amber-500/15 border border-amber-500/30 px-3 py-1.5 rounded-lg text-amber-500 text-[11px] font-semibold backdrop-blur shadow-lg animate-fade-in", children: [
8548          /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 14, className: "animate-pulse" }),
8549          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Invalid Notation Code (Showing last valid state)" })
8550        ] }),
8551        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { zoom, margin: "0 auto", width: "fit-content" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
8552          ScoreErrorBoundary,
8553          {
8554            svg,
8555            fallback: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center p-8 bg-zinc-800 border border-zinc-700 rounded-lg text-zinc-300 gap-4 shadow-xl max-w-md mx-auto my-12", children: [
8556              /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 48, className: "text-amber-500 animate-pulse" }),
8557              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
8558                /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-lg text-white", children: "Rendering Error" }),
8559                /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-400 mt-1", children: "The current notation could not be drawn. Showing the last successful render." })
8560              ] })
8561            ] }),
8562            children: /* @__PURE__ */ jsxRuntimeExports.jsx(
8563              ScoreEditorCanvas,
8564              {
8565                svg,
8566                currentTool,
8567                zoom,
8568                mei: activeTrackIndex !== null ? meiDocs[activeTrackIndex] : void 0,
8569                isPlaying,
8570                engine,
8571                currentVoice,
8572                onElementClick: handleElementClick,
8573                onElementRightClick: handleElementRightClick,
8574                onElementDrag: handleElementDrag,
8575                onMouseUp: () => {
8576                  if (engine) {
8577                    engine.stopAllNotePreviews();
8578                  }
8579                },
8580                currentAccidental,
8581                isPercussion,
8582                entryMode,
8583                isDotted,
8584                isTriplet,
8585                currentArticulation,
8586                selectedNoteId: selectedElementId,
8587                showGrid
8588              }
8589            )
8590          }
8591        ) })
8592      ] }),
8593      showKeypad && /* @__PURE__ */ jsxRuntimeExports.jsx(
8594        ScoreKeypad,
8595        {
8596          currentTool,
8597          onSelectTool: setCurrentTool,
8598          currentAccidental,
8599          onSelectAccidental: handleSelectAccidental,
8600          currentVoice,
8601          onSelectVoice: setCurrentVoice,
8602          isPercussion,
8603          onClose: () => setShowKeypad(false),
8604          currentArticulation,
8605          onSelectArticulation: handleSelectArticulation,
8606          isDotted,
8607          onToggleDotted: () => setIsDotted(!isDotted),
8608          isTieActive,
8609          onToggleTieActive: () => setIsTieActive(!isTieActive),
8610          entryMode,
8611          onSelectEntryMode: setEntryMode
8612        }
8613      )
8614    ] }),
8615    /* @__PURE__ */ jsxRuntimeExports.jsx(
8616      ScoreStatusBar,
8617      {
8618        zoom,
8619        onZoomChange: setZoom,
8620        layout,
8621        onLayoutChange: setLayout,
8622        syntaxError
8623      }
8624    ),
8625    showLiveTour && /* @__PURE__ */ jsxRuntimeExports.jsx(
8626      LiveFeedbackTour,
8627      {
8628        onComplete: () => setShowLiveTour(false),
8629        humdrumText,
8630        showTextEditor,
8631        setShowTextEditor,
8632        showKeypad,
8633        setShowKeypad
8634      }
8635    )
8636  ] });
8637};
8638const DEFAULT_LAYOUT = {
8639  inspector: { state: "docked" },
8640  arrangement: { state: "docked" },
8641  pianoRoll: { state: "docked" },
8642  scoreEditor: { state: "docked" },
8643  mixer: { state: "docked" }
8644};
8645const STORAGE_KEY = "hamienet-editor-workspace-layout";
8646function useWorkspaceLayout() {
8647  const [layout, setLayout] = reactExports.useState(() => {
8648    try {
8649      const saved = localStorage.getItem(STORAGE_KEY);
8650      if (saved) {
8651        const parsed = JSON.parse(saved);
8652        if (parsed && typeof parsed === "object" && "inspector" in parsed) {
8653          return parsed;
8654        }
8655      }
8656    } catch (e) {
8657      console.error("Failed to load workspace layout", e);
8658    }
8659    return DEFAULT_LAYOUT;
8660  });
8661  const saveTimeoutRef = reactExports.useRef(null);
8662  const updatePanelLayout = reactExports.useCallback((panel, updates) => {
8663    setLayout((prev) => {
8664      const newLayout = {
8665        ...prev,
8666        [panel]: {
8667          ...prev[panel],
8668          ...updates
8669        }
8670      };
8671      if (saveTimeoutRef.current) {
8672        clearTimeout(saveTimeoutRef.current);
8673      }
8674      saveTimeoutRef.current = setTimeout(() => {
8675        try {
8676          localStorage.setItem(STORAGE_KEY, JSON.stringify(newLayout));
8677        } catch (e) {
8678          console.error("Failed to save workspace layout", e);
8679        }
8680      }, 500);
8681      return newLayout;
8682    });
8683  }, []);
8684  const resetLayout = reactExports.useCallback(() => {
8685    setLayout(DEFAULT_LAYOUT);
8686    try {
8687      localStorage.removeItem(STORAGE_KEY);
8688      const keysToRemove = [];
8689      for (let i = 0; i < localStorage.length; i++) {
8690        const key = localStorage.key(i);
8691        if (key == null ? void 0 : key.startsWith("editor-popout-")) {
8692          keysToRemove.push(key);
8693        }
8694      }
8695      keysToRemove.forEach((k) => localStorage.removeItem(k));
8696    } catch (e) {
8697      console.error("Failed to reset workspace layout", e);
8698    }
8699  }, []);
8700  const setPanelState = reactExports.useCallback((panel, state) => {
8701    updatePanelLayout(panel, { state });
8702  }, [updatePanelLayout]);
8703  return {
8704    layout,
8705    updatePanelLayout,
8706    setPanelState,
8707    resetLayout
8708  };
8709}
8710function copyStyles(sourceDoc, targetDoc) {
8711  Array.from(sourceDoc.styleSheets).forEach((styleSheet) => {
8712    try {
8713      if (styleSheet.href) {
8714        const newLinkEl = sourceDoc.createElement("link");
8715        newLinkEl.rel = "stylesheet";
8716        newLinkEl.href = styleSheet.href;
8717        targetDoc.head.appendChild(newLinkEl);
8718      } else if (styleSheet.ownerNode) {
8719        const newStyleEl = sourceDoc.createElement("style");
8720        newStyleEl.textContent = styleSheet.ownerNode.textContent;
8721        targetDoc.head.appendChild(newStyleEl);
8722      }
8723    } catch (e) {
8724      console.warn("Could not copy stylesheet", e);
8725    }
8726  });
8727  const tailwindStyles = sourceDoc.querySelectorAll("style[data-vite-dev-id]");
8728  tailwindStyles.forEach((style) => {
8729    const newStyleEl = sourceDoc.createElement("style");
8730    newStyleEl.textContent = style.textContent;
8731    targetDoc.head.appendChild(newStyleEl);
8732  });
8733}
8734function EditorWindowPortal({
8735  children,
8736  onClose,
8737  title = "External Window",
8738  panelId,
8739  defaultWidth = 800,
8740  defaultHeight = 600
8741}) {
8742  const [container, setContainer] = reactExports.useState(null);
8743  const externalWindow = reactExports.useRef(null);
8744  const geometrySaveTimer = reactExports.useRef(null);
8745  const { undo, redo, copy, paste, engine } = useEditor();
8746  const onCloseRef = reactExports.useRef(onClose);
8747  reactExports.useEffect(() => {
8748    onCloseRef.current = onClose;
8749  }, [onClose]);
8750  reactExports.useEffect(() => {
8751    let w = defaultWidth;
8752    let h = defaultHeight;
8753    let x = 200;
8754    let y = 200;
8755    const storageKey = `editor-popout-${panelId}`;
8756    try {
8757      const savedGeo = localStorage.getItem(storageKey);
8758      if (savedGeo) {
8759        const parsed = JSON.parse(savedGeo);
8760        if (parsed && typeof parsed.w === "number") {
8761          w = parsed.w;
8762          h = parsed.h;
8763          x = parsed.x;
8764          y = parsed.y;
8765        }
8766      }
8767    } catch (e) {
8768      console.error("Failed to parse saved geometry", e);
8769    }
8770    const features = `popup=yes,width=${w},height=${h},left=${x},top=${y}`;
8771    externalWindow.current = window.open("", panelId, features);
8772    if (!externalWindow.current) {
8773      console.error("Failed to open external window. Popup blocker?");
8774      alert(`Popup blocker prevented opening the ${title} panel. Please allow popups for this site.`);
8775      if (onCloseRef.current) onCloseRef.current();
8776      return;
8777    }
8778    const doc = externalWindow.current.document;
8779    doc.title = title;
8780    const el = doc.createElement("div");
8781    el.style.height = "100vh";
8782    el.style.width = "100vw";
8783    el.style.display = "flex";
8784    el.style.flexDirection = "column";
8785    doc.body.appendChild(el);
8786    doc.body.style.margin = "0";
8787    doc.body.style.padding = "0";
8788    doc.body.style.overflow = "hidden";
8789    copyStyles(document, doc);
8790    const syncDarkMode = () => {
8791      if (document.documentElement.classList.contains("dark")) {
8792        doc.documentElement.classList.add("dark");
8793        doc.body.style.backgroundColor = "#18181b";
8794      } else {
8795        doc.documentElement.classList.remove("dark");
8796        doc.body.style.backgroundColor = "#ffffff";
8797      }
8798    };
8799    syncDarkMode();
8800    const observer = new MutationObserver((mutations) => {
8801      mutations.forEach((mutation) => {
8802        if (mutation.attributeName === "class") {
8803          syncDarkMode();
8804        }
8805      });
8806    });
8807    observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
8808    setContainer(el);
8809    geometrySaveTimer.current = setInterval(() => {
8810      if (externalWindow.current) {
8811        if (externalWindow.current.closed) {
8812          if (onCloseRef.current) onCloseRef.current();
8813        } else {
8814          try {
8815            const geo = {
8816              w: externalWindow.current.outerWidth,
8817              h: externalWindow.current.outerHeight,
8818              x: externalWindow.current.screenX,
8819              y: externalWindow.current.screenY
8820            };
8821            localStorage.setItem(storageKey, JSON.stringify(geo));
8822          } catch (e) {
8823          }
8824        }
8825      }
8826    }, 1e3);
8827    const handleKeyDown = (e) => {
8828      const activeEl = doc.activeElement;
8829      if (activeEl && (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA" || activeEl.getAttribute("contenteditable") === "true")) {
8830        return;
8831      }
8832      const isCmdOrCtrl = e.ctrlKey || e.metaKey;
8833      const isShift = e.shiftKey;
8834      if (isCmdOrCtrl && !isShift && e.key.toLowerCase() === "z") {
8835        e.preventDefault();
8836        undo();
8837      } else if (isCmdOrCtrl && isShift && e.key.toLowerCase() === "z" || isCmdOrCtrl && e.key.toLowerCase() === "y") {
8838        e.preventDefault();
8839        redo();
8840      } else if (isCmdOrCtrl && e.key.toLowerCase() === "c") {
8841        copy();
8842      } else if (isCmdOrCtrl && e.key.toLowerCase() === "v") {
8843        paste();
8844      } else if (e.code === "Space") {
8845        e.preventDefault();
8846        if (engine && engine.isReady) {
8847          engine.togglePlayback();
8848        }
8849      } else if (isCmdOrCtrl && isShift) {
8850        const event = new window.KeyboardEvent("keydown", {
8851          key: e.key,
8852          code: e.code,
8853          ctrlKey: e.ctrlKey,
8854          metaKey: e.metaKey,
8855          shiftKey: e.shiftKey,
8856          altKey: e.altKey,
8857          bubbles: true
8858        });
8859        window.dispatchEvent(event);
8860      }
8861    };
8862    doc.addEventListener("keydown", handleKeyDown);
8863    return () => {
8864      observer.disconnect();
8865      if (geometrySaveTimer.current) clearInterval(geometrySaveTimer.current);
8866      if (externalWindow.current && !externalWindow.current.closed) {
8867        externalWindow.current.close();
8868      }
8869    };
8870  }, [title, panelId, defaultWidth, defaultHeight]);
8871  if (!container) return null;
8872  return reactDomExports.createPortal(children, container);
8873}
8874var clipClipboard = [];
8875const useGlobalShortcuts = () => {
8876  const {
8877    undo,
8878    redo,
8879    copy,
8880    paste,
8881    canUndo,
8882    canRedo,
8883    deleteSelection,
8884    selectedClipIds,
8885    setSelectedClipIds,
8886    setSelectedNoteIds,
8887    playlistClips,
8888    setPlaylistClips,
8889    activeTrackIndex,
8890    playlistTracks,
8891    gridSnap,
8892    isPlaying,
8893    play,
8894    pause,
8895    stop,
8896    currentTime,
8897    easyMode,
8898    engine,
8899    forceSync
8900  } = useEditor();
8901  reactExports.useEffect(() => {
8902    const getSnappedTick = (rawTick2) => {
8903      var _a, _b;
8904      const ppq = ((_b = (_a = engine == null ? void 0 : engine.midi) == null ? void 0 : _a.header) == null ? void 0 : _b.ppq) || 96;
8905      let interval = ppq;
8906      switch (gridSnap) {
8907        case "bar":
8908          interval = ppq * 4;
8909          break;
8910        case "beat":
8911          interval = ppq;
8912          break;
8913        case "1/2":
8914          interval = ppq / 2;
8915          break;
8916        case "1/4":
8917          interval = ppq / 4;
8918          break;
8919        case "1/6":
8920          interval = ppq / 6;
8921          break;
8922        case "cell":
8923          interval = ppq / 4;
8924          break;
8925        case "none":
8926          return Math.max(0, Math.round(rawTick2));
8927        default:
8928          interval = ppq;
8929      }
8930      return Math.max(0, Math.round(rawTick2 / interval) * interval);
8931    };
8932    const handleKeyDown = (e) => {
8933      var _a, _b;
8934      const target = e.target;
8935      if (target.tagName === "TEXTAREA" || target.isContentEditable) {
8936        return;
8937      }
8938      if (target.tagName === "INPUT") {
8939        const type = target.type;
8940        if (type !== "range" && type !== "checkbox" && type !== "radio" && type !== "button" && type !== "submit") return;
8941      }
8942      const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
8943      const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
8944      if (e.key === "Escape") {
8945        e.preventDefault();
8946        setSelectedClipIds([]);
8947        setSelectedNoteIds([]);
8948        return;
8949      }
8950      if (cmdOrCtrl && (e.key === "a" || e.key === "A")) {
8951        e.preventDefault();
8952        setSelectedClipIds(playlistClips.map((c) => c.id));
8953        return;
8954      }
8955      if (e.key === "Delete" || e.key === "Backspace") {
8956        e.preventDefault();
8957        if (selectedClipIds.length > 0) {
8958          setPlaylistClips((prev) => prev.filter((c) => !selectedClipIds.includes(c.id)));
8959          setSelectedClipIds([]);
8960          setTimeout(() => forceSync(), 0);
8961        } else {
8962          deleteSelection();
8963        }
8964        return;
8965      }
8966      if (e.key === " " && !cmdOrCtrl) {
8967        e.preventDefault();
8968        if (isPlaying) {
8969          pause();
8970        } else {
8971          play();
8972        }
8973        return;
8974      }
8975      if (cmdOrCtrl && e.key === " " || e.code === "Numpad0") {
8976        e.preventDefault();
8977        stop();
8978        return;
8979      }
8980      if (easyMode) {
8981        if (cmdOrCtrl) {
8982          if (e.key === "z" || e.key === "Z") {
8983            if (e.shiftKey) {
8984              e.preventDefault();
8985              if (canRedo) redo();
8986            } else {
8987              e.preventDefault();
8988              if (canUndo) undo();
8989            }
8990          } else if (e.key === "y" || e.key === "Y") {
8991            e.preventDefault();
8992            if (canRedo) redo();
8993          }
8994        }
8995        return;
8996      }
8997      if (cmdOrCtrl) {
8998        if (e.key === "c" || e.key === "C") {
8999          e.preventDefault();
9000          if (selectedClipIds.length > 0) {
9001            const selectedClips = playlistClips.filter((c) => selectedClipIds.includes(c.id));
9002            clipClipboard = selectedClips.map((c) => ({ ...c }));
9003          } else {
9004            copy();
9005          }
9006          return;
9007        }
9008        if (e.key === "x" || e.key === "X") {
9009          e.preventDefault();
9010          if (selectedClipIds.length > 0) {
9011            const selectedClips = playlistClips.filter((c) => selectedClipIds.includes(c.id));
9012            clipClipboard = selectedClips.map((c) => ({ ...c }));
9013            setPlaylistClips((prev) => prev.filter((c) => !selectedClipIds.includes(c.id)));
9014            setSelectedClipIds([]);
9015            setTimeout(() => forceSync(), 0);
9016          } else {
9017            copy();
9018            deleteSelection();
9019          }
9020          return;
9021        }
9022        if (e.key === "v" || e.key === "V") {
9023          e.preventDefault();
9024          if (clipClipboard.length > 0) {
9025            const minStartTick = Math.min(...clipClipboard.map((c) => c.startTick));
9026            const minTrackIndex = Math.min(...clipClipboard.map((c) => c.trackIndex));
9027            const playheadTick = engine ? engine.secondsToTicks(currentTime) : 0;
9028            const snappedPlayheadTick = getSnappedTick(playheadTick);
9029            const tickOffset = snappedPlayheadTick - minStartTick;
9030            const trackOffset = (activeTrackIndex !== null ? activeTrackIndex : 0) - minTrackIndex;
9031            const pastedClips = clipClipboard.map((clip) => {
9032              const newStart = Math.max(0, clip.startTick + tickOffset);
9033              const newTrack = Math.max(0, Math.min(playlistTracks.length - 1, clip.trackIndex + trackOffset));
9034              return {
9035                ...clip,
9036                id: `clip-${Date.now()}-${Math.random()}`,
9037                startTick: newStart,
9038                trackIndex: newTrack
9039              };
9040            });
9041            setPlaylistClips((prev) => [...prev, ...pastedClips]);
9042            setSelectedClipIds(pastedClips.map((c) => c.id));
9043            setTimeout(() => forceSync(), 0);
9044          } else {
9045            paste();
9046          }
9047          return;
9048        }
9049        if (e.key === "b" || e.key === "B") {
9050          e.preventDefault();
9051          if (selectedClipIds.length > 0) {
9052            const selectedClips = playlistClips.filter((clip) => selectedClipIds.includes(clip.id));
9053            const minStart = Math.min(...selectedClips.map((c) => c.startTick));
9054            const maxEnd = Math.max(...selectedClips.map((c) => c.startTick + c.durationTicks));
9055            const span = maxEnd - minStart;
9056            const duplicatedClips = selectedClips.map((clip) => ({
9057              ...clip,
9058              id: `clip-${Date.now()}-${Math.random()}`,
9059              startTick: clip.startTick + span
9060            }));
9061            setPlaylistClips((prev) => [...prev, ...duplicatedClips]);
9062            setSelectedClipIds(duplicatedClips.map((c) => c.id));
9063            setTimeout(() => forceSync(), 0);
9064          }
9065          return;
9066        }
9067        if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
9068          e.preventDefault();
9069          if (selectedClipIds.length > 0) {
9070            const selectedClips = playlistClips.filter((clip) => selectedClipIds.includes(clip.id));
9071            const ppq = ((_b = (_a = engine == null ? void 0 : engine.midi) == null ? void 0 : _a.header) == null ? void 0 : _b.ppq) || 96;
9072            let snapInterval = ppq;
9073            switch (gridSnap) {
9074              case "bar":
9075                snapInterval = ppq * 4;
9076                break;
9077              case "beat":
9078                snapInterval = ppq;
9079                break;
9080              case "1/2":
9081                snapInterval = ppq / 2;
9082                break;
9083              case "1/4":
9084                snapInterval = ppq / 4;
9085                break;
9086              case "1/6":
9087                snapInterval = ppq / 6;
9088                break;
9089              case "cell":
9090                snapInterval = ppq / 4;
9091                break;
9092              case "none":
9093                snapInterval = 24;
9094                break;
9095            }
9096            const direction = e.key === "ArrowLeft" ? -1 : 1;
9097            const shift = direction * snapInterval;
9098            const canMove = selectedClips.every((clip) => clip.startTick + shift >= 0);
9099            if (canMove) {
9100              setPlaylistClips((prev) => prev.map((clip) => {
9101                if (selectedClipIds.includes(clip.id)) {
9102                  return {
9103                    ...clip,
9104                    startTick: clip.startTick + shift
9105                  };
9106                }
9107                return clip;
9108              }));
9109              setTimeout(() => forceSync(), 0);
9110            }
9111          }
9112          return;
9113        }
9114        if (e.key === "z" || e.key === "Z") {
9115          if (e.shiftKey) {
9116            e.preventDefault();
9117            if (canRedo) redo();
9118          } else {
9119            e.preventDefault();
9120            if (canUndo) undo();
9121          }
9122          return;
9123        }
9124        if (e.key === "y" || e.key === "Y") {
9125          e.preventDefault();
9126          if (canRedo) redo();
9127          return;
9128        }
9129      }
9130      if (e.shiftKey && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
9131        e.preventDefault();
9132        if (selectedClipIds.length > 0) {
9133          const direction = e.key === "ArrowUp" ? -1 : 1;
9134          setPlaylistClips((prev) => prev.map((clip) => {
9135            if (selectedClipIds.includes(clip.id)) {
9136              const nextTrack = Math.max(0, Math.min(playlistTracks.length - 1, clip.trackIndex + direction));
9137              return {
9138                ...clip,
9139                trackIndex: nextTrack
9140              };
9141            }
9142            return clip;
9143          }));
9144          setTimeout(() => forceSync(), 0);
9145        }
9146        return;
9147      }
9148    };
9149    window.addEventListener("keydown", handleKeyDown);
9150    return () => window.removeEventListener("keydown", handleKeyDown);
9151  }, [
9152    undo,
9153    redo,
9154    copy,
9155    paste,
9156    canUndo,
9157    canRedo,
9158    deleteSelection,
9159    selectedClipIds,
9160    setSelectedClipIds,
9161    setSelectedNoteIds,
9162    playlistClips,
9163    setPlaylistClips,
9164    activeTrackIndex,
9165    playlistTracks,
9166    gridSnap,
9167    isPlaying,
9168    play,
9169    pause,
9170    stop,
9171    currentTime,
9172    easyMode,
9173    engine,
9174    forceSync
9175  ]);
9176};
9177const FlHintPanel = () => {
9178  var _a, _b;
9179  const { hint, currentTime, engine } = useEditor();
9180  const ppq = ((_b = (_a = engine == null ? void 0 : engine.midi) == null ? void 0 : _a.header) == null ? void 0 : _b.ppq) || 96;
9181  const formatTicksToFlTime2 = (ticks, ppqVal = 96) => {
9182    const beatsPerBar = 4;
9183    const ticksPerBeat = ppqVal;
9184    const ticksPerStep = ppqVal / 4;
9185    const ticksPerBar = beatsPerBar * ticksPerBeat;
9186    const bar = Math.floor(ticks / ticksPerBar) + 1;
9187    const remainingTicksInBar = ticks % ticksPerBar;
9188    const step = Math.floor(remainingTicksInBar / ticksPerStep) + 1;
9189    const tick = Math.floor(remainingTicksInBar % ticksPerStep);
9190    const stepStr = String(step).padStart(2, "0");
9191    const tickStr = String(tick).padStart(2, "0");
9192    return `${bar}:${stepStr}:${tickStr}`;
9193  };
9194  const defaultPositionText = React.useMemo(() => {
9195    if (!engine) return "1:01:00";
9196    const currentTicks = engine.secondsToTicks(currentTime);
9197    return formatTicksToFlTime2(currentTicks, ppq);
9198  }, [currentTime, engine, ppq]);
9199  const activeLabel = (hint == null ? void 0 : hint.label) || "(HamieNET)";
9200  const activePosition = (hint == null ? void 0 : hint.positionText) !== void 0 ? hint.positionText : defaultPositionText;
9201  const activeNote = (hint == null ? void 0 : hint.noteText) || "";
9202  const activeIcon = (hint == null ? void 0 : hint.iconType) || "info";
9203  const renderIcon = (type) => {
9204    const size = 14;
9205    const className = "text-indigo-500 dark:text-indigo-400 animate-pulse";
9206    switch (type.toLowerCase()) {
9207      case "draw":
9208        return /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { size, className });
9209      case "paint":
9210        return /* @__PURE__ */ jsxRuntimeExports.jsx(Paintbrush, { size, className });
9211      case "slice":
9212      case "cut":
9213        return /* @__PURE__ */ jsxRuntimeExports.jsx(Scissors, { size, className });
9214      case "select":
9215        return /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointer2, { size, className });
9216      case "erase":
9217      case "delete":
9218        return /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size, className });
9219      case "keyboard":
9220        return /* @__PURE__ */ jsxRuntimeExports.jsx(Keyboard, { size, className });
9221      case "note":
9222      case "note_whole":
9223      case "note_half":
9224      case "note_quarter":
9225      case "note_eighth":
9226      case "note_sixteenth":
9227        return /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size, className });
9228      default:
9229        return /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size, className: "text-zinc-400 dark:text-zinc-500" });
9230    }
9231  };
9232  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
9233    "div",
9234    {
9235      className: "flex items-center justify-between px-3 py-1.5 h-10 w-72 bg-zinc-100 dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-sm select-none shrink-0",
9236      title: "HamieNET Hint Panel (Shows hover details or playhead position)",
9237      children: [
9238        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col justify-center flex-1 min-w-0", children: [
9239          /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] font-bold text-indigo-600 dark:text-indigo-400 uppercase tracking-widest truncate leading-tight", children: activeLabel }),
9240          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline gap-2 mt-0.5 min-w-0", children: [
9241            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-800 dark:text-zinc-200 font-bold shrink-0 leading-none", children: activePosition }),
9242            activeNote && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-500 dark:text-zinc-400 font-medium truncate leading-none", children: activeNote })
9243          ] })
9244        ] }),
9245        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center shrink-0 ml-2 bg-zinc-200/50 dark:bg-zinc-900/50 p-1 rounded-md w-6 h-6", children: renderIcon(activeIcon) })
9246      ]
9247    }
9248  );
9249};
9250const EasyToProMergeModal = ({ mergeState, onClose }) => {
9251  const [resolutions, setResolutions] = reactExports.useState(() => {
9252    const initial = {};
9253    mergeState.tracks.forEach((t) => {
9254      if (t.availablePatterns.length > 0) {
9255        initial[t.trackIndex] = { action: "merge", targetPatternId: t.availablePatterns[0].id };
9256      } else {
9257        initial[t.trackIndex] = { action: "new-track" };
9258      }
9259    });
9260    return initial;
9261  });
9262  const setResolution = (trackIndex, res) => {
9263    setResolutions((prev) => ({ ...prev, [trackIndex]: res }));
9264  };
9265  const handleConfirm = () => {
9266    mergeState.resolve(resolutions);
9267  };
9268  return /* @__PURE__ */ jsxRuntimeExports.jsx(
9269    "div",
9270    {
9271      className: "fixed inset-0 z-50 flex items-center justify-center",
9272      style: { background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" },
9273      children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
9274        "div",
9275        {
9276          className: "relative bg-zinc-900 border border-zinc-700/60 rounded-xl shadow-2xl max-w-lg w-full mx-4 overflow-hidden",
9277          style: { boxShadow: "0 25px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05)" },
9278          children: [
9279            /* @__PURE__ */ jsxRuntimeExports.jsxs(
9280              "div",
9281              {
9282                className: "px-6 pt-6 pb-4 border-b border-zinc-700/50",
9283                style: { background: "linear-gradient(135deg, rgba(99,102,241,0.08) 0%, rgba(168,85,247,0.05) 100%)" },
9284                children: [
9285                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-1", children: [
9286                    /* @__PURE__ */ jsxRuntimeExports.jsx(
9287                      "div",
9288                      {
9289                        className: "w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0",
9290                        style: { background: "linear-gradient(135deg, #6366f1, #a855f7)" },
9291                        children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "16", height: "16", fill: "none", viewBox: "0 0 24 24", stroke: "white", strokeWidth: 2, children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 9l4-4 4 4m0 6l-4 4-4-4" }) })
9292                      }
9293                    ),
9294                    /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-base font-semibold text-white", children: "Switching Back to Pro Mode" })
9295                  ] }),
9296                  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 ml-11", children: "You added new notes while in Easy Mode. Choose what to do with them on each track." })
9297                ]
9298              }
9299            ),
9300            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-6 py-4 space-y-4 max-h-96 overflow-y-auto", children: mergeState.tracks.map((track) => {
9301              const res = resolutions[track.trackIndex] || { action: "discard" };
9302              return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg bg-zinc-800/50 border border-zinc-700/40 overflow-hidden", children: [
9303                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2.5 flex items-center gap-2 border-b border-zinc-700/30 bg-zinc-800/80", children: [
9304                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-2 h-2 rounded-full bg-indigo-400 flex-shrink-0" }),
9305                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium text-zinc-200", children: track.trackName }),
9306                  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-auto text-xs text-zinc-500", children: [
9307                    track.addedNotes.length,
9308                    " new note",
9309                    track.addedNotes.length !== 1 ? "s" : ""
9310                  ] })
9311                ] }),
9312                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-2.5", children: [
9313                  track.availablePatterns.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-2.5 cursor-pointer group", children: [
9314                    /* @__PURE__ */ jsxRuntimeExports.jsx(
9315                      "input",
9316                      {
9317                        type: "radio",
9318                        name: `res-${track.trackIndex}`,
9319                        checked: res.action === "merge",
9320                        onChange: () => setResolution(track.trackIndex, {
9321                          action: "merge",
9322                          targetPatternId: res.targetPatternId || track.availablePatterns[0].id
9323                        }),
9324                        className: "mt-0.5 accent-indigo-500 flex-shrink-0"
9325                      }
9326                    ),
9327                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
9328                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-300 group-hover:text-white transition-colors", children: "Add to existing pattern:" }),
9329                      /* @__PURE__ */ jsxRuntimeExports.jsx(
9330                        "select",
9331                        {
9332                          value: res.action === "merge" ? res.targetPatternId || track.availablePatterns[0].id : track.availablePatterns[0].id,
9333                          onChange: (e) => setResolution(track.trackIndex, { action: "merge", targetPatternId: e.target.value }),
9334                          onClick: () => setResolution(track.trackIndex, {
9335                            action: "merge",
9336                            targetPatternId: res.targetPatternId || track.availablePatterns[0].id
9337                          }),
9338                          className: "mt-1 w-full bg-zinc-700 border border-zinc-600 rounded-md text-xs text-zinc-200 px-2 py-1.5 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30",
9339                          children: track.availablePatterns.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.name }, p.id))
9340                        }
9341                      )
9342                    ] })
9343                  ] }),
9344                  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2.5 cursor-pointer group", children: [
9345                    /* @__PURE__ */ jsxRuntimeExports.jsx(
9346                      "input",
9347                      {
9348                        type: "radio",
9349                        name: `res-${track.trackIndex}`,
9350                        checked: res.action === "new-track",
9351                        onChange: () => setResolution(track.trackIndex, { action: "new-track" }),
9352                        className: "accent-indigo-500 flex-shrink-0"
9353                      }
9354                    ),
9355                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-300 group-hover:text-white transition-colors", children: "Put in a new track" })
9356                  ] }),
9357                  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2.5 cursor-pointer group", children: [
9358                    /* @__PURE__ */ jsxRuntimeExports.jsx(
9359                      "input",
9360                      {
9361                        type: "radio",
9362                        name: `res-${track.trackIndex}`,
9363                        checked: res.action === "discard",
9364                        onChange: () => setResolution(track.trackIndex, { action: "discard" }),
9365                        className: "accent-indigo-500 flex-shrink-0"
9366                      }
9367                    ),
9368                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400 group-hover:text-zinc-300 transition-colors", children: "Discard new notes" })
9369                  ] })
9370                ] })
9371              ] }, track.trackIndex);
9372            }) }),
9373            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 py-4 border-t border-zinc-700/50 flex items-center justify-between gap-3 bg-zinc-900/80", children: [
9374              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500", children: "Deleted notes will be removed from their original patterns." }),
9375              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
9376                /* @__PURE__ */ jsxRuntimeExports.jsx(
9377                  "button",
9378                  {
9379                    onClick: onClose,
9380                    className: "px-4 py-2 rounded-lg text-xs font-medium text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700/50 transition-colors",
9381                    children: "Cancel"
9382                  }
9383                ),
9384                /* @__PURE__ */ jsxRuntimeExports.jsx(
9385                  "button",
9386                  {
9387                    onClick: handleConfirm,
9388                    className: "px-4 py-2 rounded-lg text-xs font-semibold text-white transition-all",
9389                    style: { background: "linear-gradient(135deg, #6366f1, #a855f7)" },
9390                    children: "Switch to Pro Mode"
9391                  }
9392                )
9393              ] })
9394            ] })
9395          ]
9396        }
9397      )
9398    }
9399  );
9400};
9401const uint8ArrayToBase64 = (arr) => {
9402  let binary = "";
9403  const len = arr.byteLength;
9404  for (let i = 0; i < len; i++) {
9405    binary += String.fromCharCode(arr[i]);
9406  }
9407  return btoa(binary);
9408};
9409const EditorLayout = ({ currentUser, onAuthRequested, projectId, onNavigate }) => {
9410  useGlobalShortcuts();
9411  const { t } = useTranslation();
9412  const {
9413    engine,
9414    isEngineReady,
9415    engineError,
9416    loadingStatus,
9417    midiVersion,
9418    loadMidiData,
9419    loadProjectData,
9420    forceSync,
9421    undo,
9422    redo,
9423    canUndo,
9424    canRedo,
9425    copy,
9426    paste,
9427    clipboard,
9428    selectedNoteIds,
9429    patterns,
9430    playlistTracks,
9431    playlistClips,
9432    automationClips,
9433    timeMarkers,
9434    loopRegion,
9435    easyMode,
9436    gridSnap,
9437    activePatternId,
9438    setEasyMode,
9439    easyToProMergeState,
9440    setEasyToProMergeState,
9441    isMidiSupported,
9442    isMidiConnected,
9443    connectMidi,
9444    midiError,
9445    meiDocs
9446  } = useEditor();
9447  const [bottomPane, setBottomPane] = React.useState(() => {
9448    if (typeof window !== "undefined") {
9449      const params = new URLSearchParams(window.location.search);
9450      const mode = params.get("mode");
9451      if (mode === "score" || mode && mode.includes("score")) {
9452        return "score-editor";
9453      }
9454    }
9455    return "piano-roll";
9456  });
9457  const [isSaving, setIsSaving] = React.useState(false);
9458  const [isProjectModalOpen, setIsProjectModalOpen] = React.useState(false);
9459  const [isSettingsOpen, setIsSettingsOpen] = React.useState(false);
9460  const [activeSettingsTab, setActiveSettingsTab] = React.useState("general");
9461  const { layout, setPanelState, resetLayout, updatePanelLayout } = useWorkspaceLayout();
9462  const inspectorState = layout.inspector.state;
9463  const arrangementState = layout.arrangement.state;
9464  const pianoRollState = layout.pianoRoll.state;
9465  const scoreEditorState = layout.scoreEditor.state;
9466  const mixerState = layout.mixer.state;
9467  const isInspectorFloating = inspectorState === "floating";
9468  const isArrangementFloating = arrangementState === "floating";
9469  const dockedRightTabs = [
9470    pianoRollState !== "popped-out" && pianoRollState !== "hidden" ? "piano-roll" : null,
9471    scoreEditorState !== "popped-out" && scoreEditorState !== "hidden" ? "score-editor" : null,
9472    mixerState !== "popped-out" && mixerState !== "hidden" ? "mixer" : null
9473  ].filter(Boolean);
9474  const hasDockedRightPane = dockedRightTabs.length > 0;
9475  React.useEffect(() => {
9476    if (!hasDockedRightPane) return;
9477    if (bottomPane === "piano-roll" && pianoRollState === "popped-out" || bottomPane === "score-editor" && scoreEditorState === "popped-out" || bottomPane === "mixer" && mixerState === "popped-out") {
9478      setBottomPane(dockedRightTabs[0]);
9479    }
9480  }, [bottomPane, pianoRollState, scoreEditorState, mixerState, hasDockedRightPane]);
9481  const [isFullscreen, setIsFullscreen] = React.useState(false);
9482  const [isFocusMode, setIsFocusMode] = React.useState(false);
9483  const editorRef = React.useRef(null);
9484  const fileInputRef = React.useRef(null);
9485  const [isExportDropdownOpen, setIsExportDropdownOpen] = React.useState(false);
9486  const exportDropdownRef = React.useRef(null);
9487  const [draggedPanel, setDraggedPanel] = reactExports.useState(null);
9488  const [dragStart, setDragStart] = reactExports.useState({ x: 0, y: 0 });
9489  const [resizingPanel, setResizingPanel] = reactExports.useState(null);
9490  const [resizeStart, setResizeStart] = reactExports.useState({ x: 0, y: 0, w: 0, h: 0 });
9491  const inspectorPos = layout.inspector.floatPos || { x: 50, y: 150 };
9492  const inspectorSize = layout.inspector.floatSize || { width: 280, height: 450 };
9493  const arrangementPos = layout.arrangement.floatPos || { x: 300, y: 150 };
9494  const arrangementSize = layout.arrangement.floatSize || { width: 600, height: 400 };
9495  const bottomPanePos = layout.pianoRoll.floatPos || { x: 600, y: 150 };
9496  const bottomPaneSize = layout.pianoRoll.floatSize || { width: 650, height: 480 };
9497  const isRightPaneFloating = pianoRollState === "floating" || scoreEditorState === "floating" || mi
9497xerState === "floating";
9498  const setInspectorPos = (pos) => updatePanelLayout("inspector", { floatPos: pos });
9499  const setArrangementPos = (pos) => updatePanelLayout("arrangement", { floatPos: pos });
9500  const setBottomPanePos = (pos) => {
9501    updatePanelLayout("pianoRoll", { floatPos: pos });
9502    updatePanelLayout("scoreEditor", { floatPos: pos });
9503    updatePanelLayout("mixer", { floatPos: pos });
9504  };
9505  const setInspectorSize = (size) => updatePanelLayout("inspector", { floatSize: size });
9506  const setArrangementSize = (size) => updatePanelLayout("arrangement", { floatSize: size });
9507  const setBottomPaneSize = (size) => {
9508    updatePanelLayout("pianoRoll", { floatSize: size });
9509    updatePanelLayout("scoreEditor", { floatSize: size });
9510    updatePanelLayout("mixer", { floatSize: size });
9511  };
9512  const setShowInspector = (show) => setPanelState("inspector", show ? "docked" : "hidden");
9513  const setShowArrangement = (show) => setPanelState("arrangement", show ? "docked" : "hidden");
9514  const setShowBottomPane = (show) => {
9515    if (show) {
9516      if (bottomPane === "piano-roll") setPanelState("pianoRoll", "docked");
9517      else if (bottomPane === "score-editor") setPanelState("scoreEditor", "docked");
9518      else setPanelState("mixer", "docked");
9519    } else {
9520      setPanelState("pianoRoll", "hidden");
9521      setPanelState("scoreEditor", "hidden");
9522      setPanelState("mixer", "hidden");
9523    }
9524  };
9525  const handlePanelDragStart = (panel, e) => {
9526    setDraggedPanel(panel);
9527    const currentPos = panel === "inspector" ? inspectorPos : panel === "arrangement" ? arrangementPos : bottomPanePos;
9528    setDragStart({ x: e.clientX - currentPos.x, y: e.clientY - currentPos.y });
9529    e.preventDefault();
9530  };
9531  const handlePanelResizeStart = (panel, e) => {
9532    e.stopPropagation();
9533    e.preventDefault();
9534    setResizingPanel(panel);
9535    const currentSize = panel === "inspector" ? inspectorSize : panel === "arrangement" ? arrangementSize : bottomPaneSize;
9536    setResizeStart({
9537      x: e.clientX,
9538      y: e.clientY,
9539      w: currentSize.width,
9540      h: currentSize.height
9541    });
9542  };
9543  const handleGlobalDragMove = (e) => {
9544    if (draggedPanel) {
9545      const newX = e.clientX - dragStart.x;
9546      const newY = e.clientY - dragStart.y;
9547      if (draggedPanel === "inspector") {
9548        setInspectorPos({ x: newX, y: newY });
9549      } else if (draggedPanel === "arrangement") {
9550        setArrangementPos({ x: newX, y: newY });
9551      } else if (draggedPanel === "bottom-pane") {
9552        setBottomPanePos({ x: newX, y: newY });
9553      }
9554    } else if (resizingPanel) {
9555      const deltaX = e.clientX - resizeStart.x;
9556      const deltaY = e.clientY - resizeStart.y;
9557      const newW = Math.max(200, resizeStart.w + deltaX);
9558      const newH = Math.max(150, resizeStart.h + deltaY);
9559      if (resizingPanel === "inspector") {
9560        setInspectorSize({ width: newW, height: newH });
9561      } else if (resizingPanel === "arrangement") {
9562        setArrangementSize({ width: newW, height: newH });
9563      } else if (resizingPanel === "bottom-pane") {
9564        setBottomPaneSize({ width: newW, height: newH });
9565      }
9566    }
9567  };
9568  const handleGlobalDragEnd = () => {
9569    setDraggedPanel(null);
9570    setResizingPanel(null);
9571  };
9572  React.useEffect(() => {
9573    const handleClickOutside = (e) => {
9574      if (exportDropdownRef.current && !exportDropdownRef.current.contains(e.target)) {
9575        setIsExportDropdownOpen(false);
9576      }
9577    };
9578    document.addEventListener("mousedown", handleClickOutside);
9579    return () => document.removeEventListener("mousedown", handleClickOutside);
9580  }, []);
9581  const toggleFullscreen = () => {
9582    var _a;
9583    if (!document.fullscreenElement) {
9584      (_a = editorRef.current) == null ? void 0 : _a.requestFullscreen().catch((err) => {
9585        console.error(`Error attempting to enable fullscreen: ${err.message}`);
9586      });
9587    } else {
9588      document.exitFullscreen();
9589    }
9590  };
9591  React.useEffect(() => {
9592    const handleFullscreenChange = () => {
9593      setIsFullscreen(!!document.fullscreenElement);
9594    };
9595    document.addEventListener("fullscreenchange", handleFullscreenChange);
9596    return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
9597  }, []);
9598  const [songName, setSongName] = React.useState("");
9599  React.useEffect(() => {
9600    if (engine == null ? void 0 : engine.midi) {
9601      setSongName(engine.midi.name || "Untitled MIDI Project");
9602    }
9603  }, [engine == null ? void 0 : engine.midi, midiVersion]);
9604  React.useEffect(() => {
9605    const handleFocusPianoRoll = () => {
9606      setBottomPane("piano-roll");
9607      setShowBottomPane(true);
9608    };
9609    window.addEventListener("focus-piano-roll", handleFocusPianoRoll);
9610    return () => window.removeEventListener("focus-piano-roll", handleFocusPianoRoll);
9611  }, []);
9612  React.useEffect(() => {
9613    const handleKeyDown = (e) => {
9614      const activeEl = document.activeElement;
9615      if (activeEl && (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA" || activeEl.getAttribute("contenteditable") === "true")) {
9616        return;
9617      }
9618      const isCmdOrCtrl = e.ctrlKey || e.metaKey;
9619      const isShift = e.shiftKey;
9620      if (isCmdOrCtrl && isShift) {
9621        if (e.key.toLowerCase() === "i") {
9622          e.preventDefault();
9623          setShowInspector((prev) => !prev);
9624        } else if (e.key.toLowerCase() === "a") {
9625          e.preventDefault();
9626          setShowArrangement((prev) => !prev);
9627        } else if (e.key.toLowerCase() === "b") {
9628          e.preventDefault();
9629          setShowBottomPane(!hasDockedRightPane);
9630        }
9631      }
9632    };
9633    window.addEventListener("keydown", handleKeyDown);
9634    return () => window.removeEventListener("keydown", handleKeyDown);
9635  }, []);
9636  const handleNameChange = (e) => {
9637    setSongName(e.target.value);
9638    if (engine && engine.midi) {
9639      engine.midi.name = e.target.value;
9640    }
9641  };
9642  const handleSave = async () => {
9643    var _a;
9644    if (!currentUser) {
9645      onAuthRequested();
9646      return;
9647    }
9648    if (!engine) return;
9649    setIsSaving(true);
9650    try {
9651      const midiData = engine.exportMidi();
9652      const base64Data = uint8ArrayToBase64(midiData);
9653      const payload = {
9654        title: songName || ((_a = engine.midi) == null ? void 0 : _a.name) || "Untitled MIDI Project",
9655        projectData: {
9656          midi: base64Data,
9657          patterns,
9658          playlistTracks,
9659          playlistClips,
9660          automationClips,
9661          timeMarkers,
9662          loopRegion,
9663          easyMode,
9664          gridSnap,
9665          activePatternId,
9666          meiDocs
9667        },
9668        originalMidiId: projectId && !isNaN(Number(projectId)) ? projectId : null
9669        // if we loaded a track id to start
9670      };
9671      const isSavedProject = projectId && isNaN(Number(projectId));
9672      const method = isSavedProject ? "PUT" : "POST";
9673      const url = method === "PUT" ? `/api/projects/${projectId}` : "/api/projects";
9674      const res = await fetch(url, {
9675        method,
9676        headers: { "Content-Type": "application/json" },
9677        body: JSON.stringify(payload)
9678      });
9679      if (!res.ok) {
9680        const errData = await res.json();
9681        throw new Error(errData.error || "Failed to save project");
9682      }
9683      const savedProject = await res.json();
9684      if (method === "POST" && onNavigate) {
9685        onNavigate("editor", savedProject.id);
9686      }
9687      alert("Project saved successfully!");
9688    } catch (err) {
9689      console.error("Save error:", err);
9690      alert(err.message || "Failed to save project. Please make sure you are logged in.");
9691    } finally {
9692      setIsSaving(false);
9693    }
9694  };
9695  const [isImporting, setIsImporting] = React.useState(false);
9696  const [importProgress, setImportProgress] = React.useState("");
9697  const { setMeiDoc, setActiveTrackIndex, pushHistoryState } = useEditor();
9698  const handleImportClick = () => {
9699    var _a;
9700    if (engine && engine.midi && engine.midi.tracks.some((t2) => t2.notes.length > 0)) {
9701      if (!window.confirm("Importing a new project file will replace your current project. Do you want to continue?")) {
9702        return;
9703      }
9704    }
9705    (_a = fileInputRef.current) == null ? void 0 : _a.click();
9706  };
9707  const handleFileChange = async (e) => {
9708    var _a, _b;
9709    const file = (_a = e.target.files) == null ? void 0 : _a[0];
9710    if (!file) return;
9711    const isProjectFile = file.name.toLowerCase().endsWith(".ham") || file.name.toLowerCase().endsWith(".json");
9712    if (isProjectFile) {
9713      try {
9714        setIsImporting(true);
9715        setImportProgress("Reading project file...");
9716        const reader = new FileReader();
9717        const textPromise = new Promise((resolve, reject) => {
9718          reader.onload = () => resolve(reader.result);
9719          reader.onerror = () => reject(reader.error);
9720          reader.readAsText(file);
9721        });
9722        const text = await textPromise;
9723        const data = JSON.parse(text);
9724        const projectData = data.projectData || (data.patterns ? data : null);
9725        if (!projectData || !projectData.patterns) {
9726          throw new Error("Invalid project file: missing patterns or project data");
9727        }
9728        setImportProgress("Loading project...");
9729        const name = data.title || projectData.title || file.name.replace(/\.[^/.]+$/, "");
9730        if (engine && engine.midi) {
9731          engine.midi.name = name;
9732        }
9733        setSongName(name);
9734        loadProjectData(projectData);
9735        setIsImporting(false);
9736        setImportProgress("");
9737      } catch (err) {
9738        console.error("Project import error:", err);
9739        alert("Failed to load project file: " + (err.message || err));
9740        setIsImporting(false);
9741        setImportProgress("");
9742      } finally {
9743        if (fileInputRef.current) fileInputRef.current.value = "";
9744      }
9745      return;
9746    }
9747    try {
9748      setIsImporting(true);
9749      setImportProgress("Uploading MIDI... (This uses 1 conversion credit)");
9750      const formData = new FormData();
9751      formData.append("midiFile", file);
9752      formData.append("generateScore", "true");
9753      const uploadRes = await fetch("/api/convert", {
9754        method: "POST",
9755        body: formData
9756      });
9757      if (!uploadRes.ok) {
9758        const errData = await uploadRes.json();
9759        if (errData.limitReached) {
9760          alert(errData.error);
9761        } else {
9762          throw new Error(errData.error || "Failed to upload MIDI");
9763        }
9764        const buffer2 = await file.arrayBuffer();
9765        pushHistoryState();
9766        loadMidiData(buffer2);
9767        setIsImporting(false);
9768        return;
9769      }
9770      const { track } = await uploadRes.json();
9771      const jobId = track.id;
9772      setImportProgress("Processing Sheet Music...");
9773      let isComplete = false;
9774      let jobData = null;
9775      let attempts = 0;
9776      while (!isComplete && attempts < 120) {
9777        await new Promise((r) => setTimeout(r, 2e3));
9778        attempts++;
9779        const statusRes = await fetch(`/api/convert/status/${jobId}`);
9780        if (statusRes.ok) {
9781          jobData = await statusRes.json();
9782          if (jobData.status === "completed") {
9783            isComplete = true;
9784          } else if (jobData.status === "error" || jobData.status === "failed") {
9785            throw new Error("Worker failed to process MIDI");
9786          } else {
9787            const percentStr = typeof jobData.percent === "number" ? ` (${jobData.percent}%)` : "";
9788            setImportProgress(`Processing: ${jobData.status}${percentStr}`);
9789          }
9790        }
9791      }
9792      if (!isComplete) throw new Error("Processing timed out");
9793      const buffer = await file.arrayBuffer();
9794      pushHistoryState();
9795      loadMidiData(buffer);
9796      const meiPath = jobData.studioPath || ((_b = jobData.midiMetadata) == null ? void 0 : _b.studioPath);
9797      if (meiPath) {
9798        setImportProgress("Loading Sheet Music...");
9799        const meiRes = await fetch(meiPath);
9800        if (meiRes.ok) {
9801          const meiText = await meiRes.text();
9802          setMeiDoc(0, meiText);
9803          setActiveTrackIndex(0);
9804        }
9805      }
9806    } catch (err) {
9807      console.error("Import error:", err);
9808      alert("Failed to generate sheet music. Loading audio only.");
9809      try {
9810        const buffer = await file.arrayBuffer();
9811        pushHistoryState();
9812        loadMidiData(buffer);
9813      } catch (e2) {
9814      }
9815    } finally {
9816      setIsImporting(false);
9817      setImportProgress("");
9818      if (fileInputRef.current) fileInputRef.current.value = "";
9819    }
9820  };
9821  const handleExportHAM = () => {
9822    if (!engine) return;
9823    try {
9824      const midiData = engine.exportMidi();
9825      const base64Data = uint8ArrayToBase64(midiData);
9826      const payload = {
9827        version: 1,
9828        type: "hamienet_project",
9829        title: songName || "Untitled MIDI Project",
9830        projectData: {
9831          midi: base64Data,
9832          patterns,
9833          playlistTracks,
9834          playlistClips,
9835          automationClips,
9836          timeMarkers,
9837          loopRegion,
9838          easyMode,
9839          gridSnap,
9840          activePatternId,
9841          meiDocs
9842        }
9843      };
9844      const jsonString = JSON.stringify(payload, null, 2);
9845      const blob = new Blob([jsonString], { type: "application/json" });
9846      const url = URL.createObjectURL(blob);
9847      const a = document.createElement("a");
9848      a.href = url;
9849      const fileName = (songName || "Untitled MIDI Project").trim().replace(/[^a-z0-9_-]/gi, "_") + ".ham";
9850      a.download = fileName;
9851      a.click();
9852      URL.revokeObjectURL(url);
9853    } catch (err) {
9854      console.error("Export HAM error:", err);
9855      alert("Failed to export project: " + err);
9856    }
9857  };
9858  const handleExportMIDI = async () => {
9859    if (!engine) return;
9860    if (!currentUser) {
9861      onAuthRequested();
9862      return;
9863    }
9864    if (currentUser.role !== "ADMIN") {
9865      const confirmMsg = "Exporting this MIDI will deduct 1 credit from your account. Do you want to proceed?";
9866      if (!confirm(confirmMsg)) return;
9867    } else {
9868      const confirmMsg = "Export MIDI? (Admin: No credits deducted)";
9869      if (!confirm(confirmMsg)) return;
9870    }
9871    try {
9872      const res = await fetch("/api/projects/charge-export", {
9873        method: "POST",
9874        headers: { "Content-Type": "application/json" }
9875      });
9876      if (res.status === 401) {
9877        localStorage.removeItem("hamie_session");
9878        alert("Your session has expired. Please log in again.");
9879        onAuthRequested();
9880        return;
9881      }
9882      const data = await res.json();
9883      if (!res.ok) {
9884        alert(data.error || "Failed to deduct credits for MIDI export");
9885        return;
9886      }
9887      if (currentUser && typeof data.newCredits === "number") {
9888        currentUser.credits = data.newCredits;
9889      }
9890      const midiData = engine.exportMidi();
9891      const blob = new Blob([midiData], { type: "audio/midi" });
9892      const url = URL.createObjectURL(blob);
9893      const a = document.createElement("a");
9894      a.href = url;
9895      const fileName = (songName || "Untitled MIDI Project").trim().replace(/[^a-z0-9_-]/gi, "_") + ".mid";
9896      a.download = fileName;
9897      a.click();
9898      URL.revokeObjectURL(url);
9899      alert(`Exported successfully! Remaining credits: ${data.newCredits}`);
9900    } catch (err) {
9901      console.error("Export MIDI error:", err);
9902      alert("Failed to export MIDI: " + err.message);
9903    }
9904  };
9905  const isExpandedScoreView = bottomPane === "score-editor" && isFocusMode;
9906  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
9907    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: editorRef, onMouseMove: handleGlobalDragMove, onMouseUp: handleGlobalDragEnd, onContextMenu: (e) => e.preventDefault(), className: `flex flex-col w-full bg-white dark:bg-zinc-900 text-zinc-900 dark:text-white animate-in fade-in ${isFullscreen ? "h-screen" : "h-[calc(100vh-64px)]"}`, children: [
9908      isExpandedScoreView ? (
9909        /* Single Compact Toolbar for Expanded Score View */
9910        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-12 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center bg-zinc-50 dark:bg-zinc-950 px-4 shrink-0 gap-3", children: [
9911          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 min-w-0 flex-1", children: [
9912            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
9913              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "p-1 bg-indigo-500/20 text-indigo-400 rounded text-xs", children: "🎹" }),
9914              /* @__PURE__ */ jsxRuntimeExports.jsx(
9915                "input",
9916                {
9917                  type: "text",
9918                  value: songName,
9919                  onChange: handleNameChange,
9920                  onBlur: () => forceSync(),
9921                  className: "bg-transparent border-b border-transparent hover:border-zinc-300 dark:hover:border-zinc-700 focus:border-indigo-500 outline-none w-32 sm:w-44 transition-colors font-bold text-xs",
9922                  placeholder: t("editor.untitledProject", "Untitled MIDI Project")
9923                }
9924              )
9925            ] }),
9926            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-200 dark:bg-zinc-800 shrink-0" }),
9927            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "scale-90 origin-left shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TransportControls, {}) }),
9928            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-200 dark:bg-zinc-800 shrink-0" }),
9929            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-0.5 shrink-0", children: [
9930              /* @__PURE__ */ jsxRuntimeExports.jsx(
9931                "button",
9932                {
9933                  onClick: undo,
9934                  disabled: !canUndo,
9935                  title: "Undo (Ctrl+Z)",
9936                  className: `p-1.5 rounded transition-colors ${canUndo ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
9937                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Undo2, { size: 16 })
9938                }
9939              ),
9940              /* @__PURE__ */ jsxRuntimeExports.jsx(
9941                "button",
9942                {
9943                  onClick: redo,
9944                  disabled: !canRedo,
9945                  title: "Redo (Ctrl+Y)",
9946                  className: `p-1.5 rounded transition-colors ${canRedo ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
9947                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Redo2, { size: 16 })
9948                }
9949              ),
9950              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-1.5" }),
9951              /* @__PURE__ */ jsxRuntimeExports.jsx(
9952                "button",
9953                {
9954                  onClick: copy,
9955                  disabled: selectedNoteIds.length === 0,
9956                  title: "Copy (Ctrl+C)",
9957                  className: `p-1.5 rounded transition-colors ${selectedNoteIds.length > 0 ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
9958                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 16 })
9959                }
9960              ),
9961              /* @__PURE__ */ jsxRuntimeExports.jsx(
9962                "button",
9963                {
9964                  onClick: paste,
9965                  disabled: !clipboard || clipboard.notes.length === 0,
9966                  title: "Paste (Ctrl+V)",
9967                  className: `p-1.5 rounded transition-colors ${clipboard && clipboard.notes.length > 0 ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
9968                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(ClipboardPaste, { size: 16 })
9969                }
9970              )
9971            ] })
9972          ] }),
9973          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 shrink-0", children: [
9974            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
9975              /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.saveHelp", "Save project"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
9976                "button",
9977                {
9978                  onClick: handleSave,
9979                  disabled: isSaving || !isEngineReady,
9980                  title: t("editor.save", "Save"),
9981                  className: "p-1.5 text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded disabled:opacity-50 transition-colors",
9982                  children: isSaving ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-4 h-4 border-2 border-zinc-500 border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 16 })
9983                }
9984              ) }),
9985              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", ref: exportDropdownRef, children: [
9986                /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.exportHelp", "Export"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
9987                  "button",
9988                  {
9989                    onClick: () => setIsExportDropdownOpen((prev) => !prev),
9990                    disabled: !isEngineReady,
9991                    title: t("editor.export", "Export"),
9992                    className: "p-1.5 text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded disabled:opacity-50 transition-colors flex items-center",
9993                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { size: 16 })
9994                  }
9995                ) }),
9996                isExportDropdownOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute right-0 mt-1 w-56 rounded-md shadow-lg bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 z-50 py-1", children: [
9997                  /* @__PURE__ */ jsxRuntimeExports.jsxs(
9998                    "button",
9999                    {
10000                      onClick: () => {
10001                        setIsExportDropdownOpen(false);
10002                        handleExportHAM();
10003                      },
10004                      className: "w-full text-left px-4 py-2 text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors flex flex-col gap-0.5",
10005                      children: [
10006                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-zinc-900 dark:text-white", children: t("editor.exportHam", "Export Project (.ham)") }),
10007                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400", children: t("editor.exportHamDesc", "Save arrangement, patterns & sheet music") })
10008                      ]
10009                    }
10010                  ),
10011                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-200 dark:border-zinc-700 my-1" }),
10012                  /* @__PURE__ */ jsxRuntimeExports.jsxs(
10013                    "button",
10014                    {
10015                      onClick: () => {
10016                        setIsExportDropdownOpen(false);
10017                        handleExportMIDI();
10018                      },
10019                      className: "w-full text-left px-4 py-2 text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors flex flex-col gap-0.5",
10020                      children: [
10021                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-zinc-900 dark:text-white", children: t("editor.exportMidi", "Export MIDI (.mid)") }),
10022                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400", children: t("editor.exportMidiDesc", "Standard MIDI notes only") })
10023                      ]
10024                    }
10025                  )
10026                ] })
10027              ] }),
10028              /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.settingsHelp", "Settings"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10029                "button",
10030                {
10031                  onClick: () => setIsSettingsOpen(true),
10032                  title: t("editor.settings", "Settings"),
10033                  className: "p-1.5 text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded transition-colors",
10034                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 16 })
10035                }
10036              ) })
10037            ] }),
10038            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-200 dark:bg-zinc-800" }),
10039            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
10040              /* @__PURE__ */ jsxRuntimeExports.jsx(
10041                "button",
10042                {
10043                  onClick: resetLayout,
10044                  title: "Reset Workspace",
10045                  className: "p-1.5 rounded transition-colors ml-1 text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800",
10046                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutDashboard, { size: 18 })
10047                }
10048              ),
10049              /* @__PURE__ */ jsxRuntimeExports.jsx(
10050                "button",
10051                {
10052                  onClick: toggleFullscreen,
10053                  title: isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen",
10054                  className: `p-1.5 rounded transition-colors ${isFullscreen ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10055                  children: isFullscreen ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize, { size: 16 })
10056                }
10057              ),
10058              /* @__PURE__ */ jsxRuntimeExports.jsx(
10059                "button",
10060                {
10061                  onClick: () => setIsFocusMode(!isFocusMode),
10062                  title: isFocusMode ? "Exit Focus Mode" : "Focus Mode",
10063                  className: `p-1.5 rounded transition-colors ${isFocusMode ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10064                  children: isFocusMode ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize2, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize2, { size: 16 })
10065                }
10066              )
10067            ] })
10068          ] })
10069        ] })
10070      ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10071        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-h-14 py-2 border-b border-zinc-200 dark:border-zinc-800 flex flex-wrap justify-between items-center bg-zinc-50 dark:bg-zinc-950 px-4 shrink-0 gap-3", children: [
10072          /* @__P
10072URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 lg:gap-6 min-w-0 flex-wrap", children: [
10073            /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-xl font-bold flex items-center gap-2 shrink-0", children: [
10074              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "p-1.5 bg-indigo-500/20 text-indigo-400 rounded-lg", children: "🎹" }),
10075              /* @__PURE__ */ jsxRuntimeExports.jsx(
10076                "input",
10077                {
10078                  type: "text",
10079                  value: songName,
10080                  onChange: handleNameChange,
10081                  onBlur: () => forceSync(),
10082                  className: "bg-transparent border-b border-transparent hover:border-zinc-300 dark:hover:border-zinc-700 focus:border-indigo-500 outline-none w-48 sm:w-64 transition-colors hidden md:inline font-bold",
10083                  placeholder: t("editor.untitledProject", "Untitled MIDI Project")
10084                }
10085              )
10086            ] }),
10087            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-6 bg-zinc-200 dark:bg-zinc-800 hidden sm:block mx-1" }),
10088            /* @__PURE__ */ jsxRuntimeExports.jsx(TransportControls, {})
10089          ] }),
10090          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 items-center flex-wrap shrink-0", children: [
10091            isImporting && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-3 py-1 text-xs text-indigo-400 self-center animate-pulse truncate max-w-[120px] sm:max-w-[200px]", children: importProgress }),
10092            projectId && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-3 py-1 bg-zinc-800 rounded-full text-xs sm:text-sm text-zinc-400 self-center truncate max-w-[100px] sm:max-w-[150px]", children: [
10093              "ID: ",
10094              projectId
10095            ] }),
10096            /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", accept: ".mid,.midi,.ham,.json", ref: fileInputRef, className: "hidden", onChange: handleFileChange }),
10097            /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.importHelp", "Import a MIDI (.mid) or Project (.ham) file from your computer. Note: this will replace your current project."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
10098              "button",
10099              {
10100                onClick: handleImportClick,
10101                disabled: isImporting,
10102                className: "px-3 sm:px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-white rounded font-bold text-xs sm:text-sm transition-colors border border-zinc-700 shadow-sm flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
10103                children: [
10104                  isImporting ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" }) : null,
10105                  isImporting ? t("editor.importing", "Importing...") : t("editor.import", "Import")
10106                ]
10107              }
10108            ) }),
10109            /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.projectsHelp", "Open your saved HamieNET MIDI projects."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
10110              "button",
10111              {
10112                onClick: () => setIsProjectModalOpen(true),
10113                disabled: !isEngineReady,
10114                className: "px-3 sm:px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-white rounded font-bold text-xs sm:text-sm transition-colors border border-zinc-700 flex items-center gap-2 shadow-sm disabled:opacity-50 disabled:cursor-not-allowed",
10115                children: [
10116                  /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { size: 14 }),
10117                  t("editor.projects", "Projects")
10118                ]
10119              }
10120            ) }),
10121            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", ref: exportDropdownRef, children: [
10122              /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.exportHelp", "Export the current project as a .ham or standard .mid file."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
10123                "button",
10124                {
10125                  onClick: () => setIsExportDropdownOpen((prev) => !prev),
10126                  disabled: !isEngineReady,
10127                  className: "px-3 sm:px-4 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded font-bold text-xs sm:text-sm transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1.5",
10128                  children: [
10129                    t("editor.export", "Export"),
10130                    /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 })
10131                  ]
10132                }
10133              ) }),
10134              isExportDropdownOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute right-0 mt-1 w-56 rounded-md shadow-lg bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 z-50 py-1", children: [
10135                /* @__PURE__ */ jsxRuntimeExports.jsxs(
10136                  "button",
10137                  {
10138                    onClick: () => {
10139                      setIsExportDropdownOpen(false);
10140                      handleExportHAM();
10141                    },
10142                    className: "w-full text-left px-4 py-2 text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors flex flex-col gap-0.5",
10143                    children: [
10144                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-zinc-900 dark:text-white", children: t("editor.exportHam", "Export Project (.ham)") }),
10145                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400", children: t("editor.exportHamDesc", "Save arrangement, patterns & sheet music") })
10146                    ]
10147                  }
10148                ),
10149                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-200 dark:border-zinc-700 my-1" }),
10150                /* @__PURE__ */ jsxRuntimeExports.jsxs(
10151                  "button",
10152                  {
10153                    onClick: () => {
10154                      setIsExportDropdownOpen(false);
10155                      handleExportMIDI();
10156                    },
10157                    className: "w-full text-left px-4 py-2 text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors flex flex-col gap-0.5",
10158                    children: [
10159                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-zinc-900 dark:text-white", children: t("editor.exportMidi", "Export MIDI (.mid)") }),
10160                      /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400", children: t("editor.exportMidiDesc", "Standard MIDI notes only") })
10161                    ]
10162                  }
10163                )
10164              ] })
10165            ] }),
10166            /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.saveHelp", "Save your project to the cloud so you can access it later."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
10167              "button",
10168              {
10169                onClick: handleSave,
10170                disabled: isSaving || !isEngineReady,
10171                className: "px-3 sm:px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-white rounded font-bold text-xs sm:text-sm transition-colors border border-zinc-700 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
10172                children: [
10173                  isSaving ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" }) : null,
10174                  t("editor.save", "Save")
10175                ]
10176              }
10177            ) }),
10178            /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.settingsHelp", "Settings & MIDI Configuration"), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
10179              "button",
10180              {
10181                onClick: () => setIsSettingsOpen(true),
10182                className: "px-3 sm:px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-white rounded font-bold text-xs sm:text-sm transition-colors border border-zinc-700 flex items-center gap-2 shadow-sm",
10183                children: [
10184                  /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 14 }),
10185                  t("editor.settings", "Settings")
10186                ]
10187              }
10188            ) })
10189          ] })
10190        ] }),
10191        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-12 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center bg-zinc-50/80 dark:bg-zinc-950/80 px-4 shrink-0", children: [
10192          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-6", children: [
10193            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Undo, redo, copy, or paste notes in the editor.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
10194              /* @__PURE__ */ jsxRuntimeExports.jsx(
10195                "button",
10196                {
10197                  onClick: undo,
10198                  disabled: !canUndo,
10199                  title: "Undo (Ctrl+Z)",
10200                  className: `p-1.5 rounded transition-colors ${canUndo ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
10201                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Undo2, { size: 18 })
10202                }
10203              ),
10204              /* @__PURE__ */ jsxRuntimeExports.jsx(
10205                "button",
10206                {
10207                  onClick: redo,
10208                  disabled: !canRedo,
10209                  title: "Redo (Ctrl+Y)",
10210                  className: `p-1.5 rounded transition-colors ${canRedo ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
10211                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Redo2, { size: 18 })
10212                }
10213              ),
10214              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-2" }),
10215              /* @__PURE__ */ jsxRuntimeExports.jsx(
10216                "button",
10217                {
10218                  onClick: copy,
10219                  disabled: selectedNoteIds.length === 0,
10220                  title: "Copy (Ctrl+C)",
10221                  className: `p-1.5 rounded transition-colors ${selectedNoteIds.length > 0 ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
10222                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 18 })
10223                }
10224              ),
10225              /* @__PURE__ */ jsxRuntimeExports.jsx(
10226                "button",
10227                {
10228                  onClick: paste,
10229                  disabled: !clipboard || clipboard.notes.length === 0,
10230                  title: "Paste (Ctrl+V)",
10231                  className: `p-1.5 rounded transition-colors ${clipboard && clipboard.notes.length > 0 ? "text-zinc-700 hover:bg-zinc-200 dark:text-zinc-200 dark:hover:bg-zinc-800" : "text-zinc-400 dark:text-zinc-600 opacity-40 cursor-not-allowed"}`,
10232                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(ClipboardPaste, { size: 18 })
10233                }
10234              )
10235            ] }) }) }),
10236            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-200 dark:bg-zinc-800" }),
10237            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: "Toggle editor panels, fullscreen, or focus mode.", position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
10238              /* @__PURE__ */ jsxRuntimeExports.jsx(
10239                "button",
10240                {
10241                  onClick: () => setShowInspector(inspectorState === "hidden"),
10242                  title: inspectorState !== "hidden" ? "Hide Inspector" : "Show Inspector",
10243                  className: `p-1.5 rounded transition-colors ${inspectorState !== "hidden" ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10244                  children: inspectorState !== "hidden" ? /* @__PURE__ */ jsxRuntimeExports.jsx(PanelLeftClose, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PanelLeftOpen, { size: 18 })
10245                }
10246              ),
10247              /* @__PURE__ */ jsxRuntimeExports.jsx(
10248                "button",
10249                {
10250                  onClick: () => setShowArrangement(arrangementState === "hidden"),
10251                  title: arrangementState !== "hidden" ? "Hide Track Lanes" : "Show Track Lanes",
10252                  className: `p-1.5 rounded transition-colors ${arrangementState !== "hidden" ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10253                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 18 })
10254                }
10255              ),
10256              /* @__PURE__ */ jsxRuntimeExports.jsx(
10257                "button",
10258                {
10259                  onClick: () => setShowBottomPane(!hasDockedRightPane),
10260                  title: hasDockedRightPane ? "Hide Right Pane" : "Show Right Pane",
10261                  className: `p-1.5 rounded transition-colors ${hasDockedRightPane ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10262                  children: hasDockedRightPane ? /* @__PURE__ */ jsxRuntimeExports.jsx(PanelRightClose, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PanelRightOpen, { size: 18 })
10263                }
10264              ),
10265              /* @__PURE__ */ jsxRuntimeExports.jsx(
10266                "button",
10267                {
10268                  onClick: toggleFullscreen,
10269                  title: isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen",
10270                  className: `p-1.5 rounded transition-colors ml-1 ${isFullscreen ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10271                  children: isFullscreen ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize, { size: 18 })
10272                }
10273              ),
10274              /* @__PURE__ */ jsxRuntimeExports.jsx(
10275                "button",
10276                {
10277                  onClick: () => setIsFocusMode(!isFocusMode),
10278                  title: isFocusMode ? "Exit Focus Mode" : "Enter Focus Mode",
10279                  className: `p-1.5 rounded transition-colors ml-1 ${isFocusMode ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10280                  children: isFocusMode ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize2, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize2, { size: 18 })
10281                }
10282              )
10283            ] }) }) }),
10284            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-5 bg-zinc-200 dark:bg-zinc-800" }),
10285            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 bg-zinc-200/50 dark:bg-zinc-800/50 rounded-lg p-0.5 shadow-inner", children: [
10286              /* @__PURE__ */ jsxRuntimeExports.jsx(
10287                "button",
10288                {
10289                  onClick: () => setEasyMode(true),
10290                  className: `px-2.5 py-1 rounded text-xs font-bold transition-all ${easyMode ? "bg-indigo-600 text-white shadow-sm" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
10291                  children: "Easy Mode"
10292                }
10293              ),
10294              /* @__PURE__ */ jsxRuntimeExports.jsx(
10295                "button",
10296                {
10297                  onClick: () => setEasyMode(false),
10298                  className: `px-2.5 py-1 rounded text-xs font-bold transition-all ${!easyMode ? "bg-indigo-600 text-white shadow-sm" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"}`,
10299                  children: "Pro Mode"
10300                }
10301              )
10302            ] })
10303          ] }),
10304          /* @__PURE__ */ jsxRuntimeExports.jsx(FlHintPanel, {})
10305        ] })
10306      ] }),
10307      engineError ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-center justify-center h-full w-full bg-zinc-50 dark:bg-zinc-900 text-zinc-900 dark:text-white p-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-xl p-8 max-w-lg w-full text-center", children: [
10308        /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-red-400 mb-4", children: "DAW Initialization Failed" }),
10309        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-300 text-sm mb-6", children: engineError }),
10310        /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-zinc-400 text-xs italic", children: [
10311          "Note: For security reasons, the audio synthesis engine (AudioWorklet) requires a secure context. Please ensure you are accessing the site via ",
10312          /* @__P
10312URE__ */ jsxRuntimeExports.jsx("strong", { children: "HTTPS" }),
10313          " or ",
10314          /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "localhost" }),
10315          "."
10316        ] })
10317      ] }) }) : !isEngineReady ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full w-full bg-zinc-50 dark:bg-zinc-900 text-zinc-900 dark:text-white gap-4", children: [
10318        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin" }),
10319        /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 font-medium", children: loadingStatus || "Loading DAW Engine..." })
10320      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden relative", children: [
10321        inspectorState === "floating" && (isInspectorFloating ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
10322          "div",
10323          {
10324            style: { left: inspectorPos.x, top: inspectorPos.y, width: `${inspectorSize.width}px`, height: `${inspectorSize.height}px` },
10325            className: "fixed bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-2xl flex flex-col z-50 overflow-hidden",
10326            children: [
10327              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10328                "div",
10329                {
10330                  onMouseDown: (e) => handlePanelDragStart("inspector", e),
10331                  className: "h-9 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between px-3 bg-zinc-50 dark:bg-zinc-950 text-xs font-bold text-zinc-500 uppercase tracking-widest cursor-move select-none shrink-0",
10332                  children: [
10333                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [
10334                      /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 12 }),
10335                      " ",
10336                      t("editor.inspector", "Inspector")
10337                    ] }),
10338                    /* @__PURE__ */ jsxRuntimeExports.jsx(
10339                      "button",
10340                      {
10341                        onClick: () => setPanelState("inspector", "docked"),
10342                        className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10343                        title: t("editor.dock", "Dock"),
10344                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pin, { size: 14 })
10345                      }
10346                    ),
10347                    /* @__PURE__ */ jsxRuntimeExports.jsx(
10348                      "button",
10349                      {
10350                        onClick: () => {
10351                          setPanelState("inspector", "popped-out");
10352                        },
10353                        className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10354                        title: "Pop Out",
10355                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(MonitorUp, { size: 14 })
10356                      }
10357                    )
10358                  ]
10359                }
10360              ),
10361              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrackInspector, {}) }),
10362              /* @__PURE__ */ jsxRuntimeExports.jsx(
10363                "div",
10364                {
10365                  onMouseDown: (e) => handlePanelResizeStart("inspector", e),
10366                  className: "absolute bottom-0 right-0 w-4 h-4 cursor-se-resize flex items-end justify-end p-0.5 z-[100]",
10367                  children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", className: "text-zinc-400 dark:text-zinc-600 fill-current", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 0 L8 0 L8 8 L0 8 L0 6 L4 6 L4 4 L6 4 Z" }) })
10368                }
10369              )
10370            ]
10371          }
10372        ) : null),
10373        inspectorState === "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(EditorWindowPortal, { panelId: "inspector", title: "HamieNET Editor - Inspector", onClose: () => setPanelState("inspector", "docked"), defaultWidth: 300, defaultHeight: 600, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrackInspector, {}) }),
10374        arrangementState === "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(EditorWindowPortal, { panelId: "arrangement", title: "HamieNET Editor - Arrangement", onClose: () => setPanelState("arrangement", "docked"), defaultWidth: 800, defaultHeight: 400, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrangementView, { isFloating: true, isPoppedOut: true }) }),
10375        pianoRollState === "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(EditorWindowPortal, { panelId: "pianoRoll", title: "HamieNET Editor - Piano Roll", onClose: () => setPanelState("pianoRoll", "docked"), defaultWidth: 1e3, defaultHeight: 500, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PianoRoll, {}) }),
10376        scoreEditorState === "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(EditorWindowPortal, { panelId: "scoreEditor", title: "HamieNET Editor - Score Editor", onClose: () => setPanelState("scoreEditor", "docked"), defaultWidth: 1e3, defaultHeight: 500, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScoreEditor, {}) }),
10377        mixerState === "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(EditorWindowPortal, { panelId: "mixer", title: "HamieNET Editor - Mixer", onClose: () => setPanelState("mixer", "docked"), defaultWidth: 800, defaultHeight: 400, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Mixer, {}) }),
10378        !isFocusMode && isArrangementFloating ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
10379          "div",
10380          {
10381            style: { left: arrangementPos.x, top: arrangementPos.y, width: `${arrangementSize.width}px`, height: `${arrangementSize.height}px` },
10382            className: "fixed bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-2xl flex flex-col z-50 overflow-hidden",
10383            children: [
10384              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10385                "div",
10386                {
10387                  onMouseDown: (e) => handlePanelDragStart("arrangement", e),
10388                  className: "h-9 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between px-3 bg-zinc-50 dark:bg-zinc-950 text-xs font-bold text-zinc-500 uppercase tracking-widest cursor-move select-none shrink-0",
10389                  children: [
10390                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5", children: [
10391                      /* @__P
10391URE__ */ jsxRuntimeExports.jsx(Move, { size: 12 }),
10392                      " ",
10393                      t("editor.arrangement", "Arrangement")
10394                    ] }),
10395                    /* @__PURE__ */ jsxRuntimeExports.jsx(
10396                      "button",
10397                      {
10398                        onClick: () => setPanelState("arrangement", "docked"),
10399                        className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10400                        title: t("editor.dock", "Dock"),
10401                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pin, { size: 14 })
10402                      }
10403                    ),
10404                    /* @__PURE__ */ jsxRuntimeExports.jsx(
10405                      "button",
10406                      {
10407                        onClick: () => {
10408                          setPanelState("arrangement", "popped-out");
10409                        },
10410                        className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10411                        title: "Pop Out",
10412                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(MonitorUp, { size: 14 })
10413                      }
10414                    )
10415                  ]
10416                }
10417              ),
10418              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden relative", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrangementView, { isFloating: true }) }),
10419              /* @__PURE__ */ jsxRuntimeExports.jsx(
10420                "div",
10421                {
10422                  onMouseDown: (e) => handlePanelResizeStart("arrangement", e),
10423                  className: "absolute bottom-0 right-0 w-4 h-4 cursor-se-resize flex items-end justify-end p-0.5 z-[100]",
10424                  children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", className: "text-zinc-400 dark:text-zinc-600 fill-current", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 0 L8 0 L8 8 L0 8 L0 6 L4 6 L4 4 L6 4 Z" }) })
10425                }
10426              )
10427            ]
10428          }
10429        ) : null,
10430        isRightPaneFloating ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
10431          "div",
10432          {
10433            style: { left: bottomPanePos.x, top: bottomPanePos.y, width: `${bottomPaneSize.width}px`, height: `${bottomPaneSize.height}px` },
10434            className: "fixed bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-2xl flex flex-col z-50 overflow-hidden",
10435            children: [
10436              /* @__PURE__ */ jsxRuntimeExports.jsxs(
10437                "div",
10438                {
10439                  onMouseDown: (e) => handlePanelDragStart("bottom-pane", e),
10440                  className: "h-9 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center px-2 bg-zinc-50 dark:bg-zinc-950 shrink-0 cursor-move select-none",
10441                  children: [
10442                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 h-full items-center", children: [
10443                      pianoRollState !== "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(
10444                        "button",
10445                        {
10446                          onClick: () => setBottomPane("piano-roll"),
10447                          className: `px-3 py-1 text-[10px] sm:text-xs font-bold transition-colors border-t-2 ${bottomPane === "piano-roll" ? "border-indigo-500 bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-700"}`,
10448                          children: t("editor.pianoRoll", "PIANO ROLL")
10449                        }
10450                      ),
10451                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
10452                      scoreEditorState !== "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(
10453                        "button",
10454                        {
10455                          onClick: () => setBottomPane("score-editor"),
10456                          className: `px-3 py-1 text-[10px] sm:text-xs font-bold transition-colors border-t-2 ${bottomPane === "score-editor" ? "border-indigo-500 bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-700"}`,
10457                          children: t("editor.scoreEditor", "SCORE EDITOR")
10458                        }
10459                      ),
10460                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
10461                      mixerState !== "popped-out" && /* @__PURE__ */ jsxRuntimeExports.jsx(
10462                        "button",
10463                        {
10464                          onClick: () => setBottomPane("mixer"),
10465                          className: `px-3 py-1 text-[10px] sm:text-xs font-bold transition-colors border-t-2 ${bottomPane === "mixer" ? "border-indigo-500 bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-700"}`,
10466                          children: t("editor.mixer", "MIXER")
10467                        }
10468                      )
10469                    ] }),
10470                    /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1.5 px-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10471                      "button",
10472                      {
10473                        onClick: () => {
10474                          setPanelState("pianoRoll", "docked");
10475                          setPanelState("scoreEditor", "docked");
10476                          setPanelState("mixer", "docked");
10477                        },
10478                        className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10479                        title: t("editor.dock", "Dock"),
10480                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pin, { size: 14 })
10481                      }
10482                    ) })
10483                  ]
10484                }
10485              ),
10486              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex flex-col relative bg-zinc-50 dark:bg-zinc-950", children: bottomPane === "piano-roll" ? /* @__PURE__ */ jsxRuntimeExports.jsx(PianoRoll, {}) : bottomPane === "score-editor" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScoreEditor, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(Mixer, {}) }),
10487              /* @__PURE__ */ jsxRuntimeExports.jsx(
10488                "div",
10489                {
10490                  onMouseDown: (e) => handlePanelResizeStart("bottom-pane", e),
10491                  className: "absolute bottom-0 right-0 w-4 h-4 cursor-se-resize flex items-end justify-end p-0.5 z-[100]",
10492                  children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", className: "text-zinc-400 dark:text-zinc-600 fill-current", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M6 0 L8 0 L8 8 L0 8 L0 6 L4 6 L4 4 L6 4 Z" }) })
10493                }
10494              )
10495            ]
10496          }
10497        ) : null,
10498        /* @__PURE__ */ jsxRuntimeExports.jsxs(Ut, { direction: "horizontal", className: "relative", children: [
10499          !inspectorState === "docked" && !isFocusMode && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 top-1/2 -translate-y-1/2 z-[45]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10500            "button",
10501            {
10502              onClick: () => setShowInspector(true),
10503              className: "w-4 h-12 bg-zinc-250 dark:bg-zinc-800 border border-l-0 border-zinc-300 dark:border-zinc-700 hover:bg-indigo-500 dark:hover:bg-indigo-600 hover:text-white rounded-r shadow flex items-center justify-center cursor-pointer transition-colors",
10504              title: "Expand Inspector",
10505              children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold select-none", children: ">" })
10506            }
10507          ) }),
10508          inspectorState === "docked" && !isFocusMode && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10509            /* @__PURE__ */ jsxRuntimeExports.jsxs(Yt, { defaultSize: "200px", minSize: "200px", onResize: () => {
10510            }, className: "bg-white dark:bg-zinc-900 flex flex-col z-[51] shadow-sm border-r border-zinc-200 dark:border-zinc-800", style: { overflow: "visible" }, children: [
10511              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between px-4 bg-zinc-50 dark:bg-zinc-950 text-xs font-bold text-zinc-500 uppercase tracking-widest shrink-0", children: [
10512                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("editor.inspector", "Inspector") }),
10513                /* @__PURE__ */ jsxRuntimeExports.jsx(
10514                  "button",
10515                  {
10516                    onClick: () => setPanelState("inspector", "floating"),
10517                    className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10518                    title: t("editor.float", "Float"),
10519                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 14 })
10520                  }
10521                ),
10522                /* @__PURE__ */ jsxRuntimeExports.jsx(
10523                  "button",
10524                  {
10525                    onClick: () => {
10526                      setPanelState("inspector", "popped-out");
10527                    },
10528                    className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all ml-1",
10529                    title: "Pop Out",
10530                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(MonitorUp, { size: 14 })
10531                  }
10532                )
10533              ] }),
10534              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrackInspector, {}) })
10535            ] }),
10536            (arrangementState === "docked" || hasDockedRightPane) && /* @__PURE__ */ jsxRuntimeExports.jsx(Qt, { className: "w-1.5 bg-zinc-200 dark:bg-zinc-800 hover:bg-indigo-500/50 active:bg-indigo-500 cursor-col-resize transition-colors shrink-0 z-20 flex items-center justify-center relative group", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10537              "button",
10538              {
10539                onClick: (e) => {
10540                  e.stopPropagation();
10541                  setShowInspector(false);
10542                },
10543                className: "absolute w-4 h-8 bg-zinc-300 dark:bg-zinc-700 hover:bg-indigo-500 dark:hover:bg-indigo-600 hover:text-white text-zinc-650 dark:text-zinc-300 rounded-r shadow flex items-center justify-center cursor-pointer left-0 z-50 border border-l-0 border-zinc-200 dark:border-zinc-600 opacity-0 group-hover:opacity-100 transition-opacity",
10544                title: "Collapse Inspector",
10545                children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold select-none", children: "<" })
10546              }
10547            ) })
10548          ] }),
10549          !isFocusMode && arrangementState === "docked" && /* @__PURE__ */ jsxRuntimeExports.jsxs(Yt, { defaultSize: "40%", minSize: "150px", onResize: () => {
10550          }, className: "bg-zinc-50 dark:bg-zinc-950 flex flex-col relative overflow-hidden", children: [
10551            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute right-3 top-2 z-[45] flex items-center", children: [
10552              /* @__PURE__ */ jsxRuntimeExports.jsx(
10553                "button",
10554                {
10555                  onClick: () => setPanelState("arrangement", "floating"),
10556                  className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800/80 backdrop-blur-sm transition-all shadow",
10557                  title: t("editor.floatArrangement", "Float Arrangement"),
10558                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 14 })
10559                }
10560              ),
10561              /* @__PURE__ */ jsxRuntimeExports.jsx(
10562                "button",
10563                {
10564                  onClick: () => {
10565                    setPanelState("arrangement", "popped-out");
10566                  },
10567                  className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800/80 backdrop-blur-sm transition-all shadow ml-1",
10568                  title: "Pop Out",
10569                  children: /* @__PURE__ */ jsxRuntimeExports.jsx(MonitorUp, { size: 14 })
10570                }
10571              )
10572            ] }),
10573            /* @__PURE__ */ jsxRuntimeExports.jsx(ArrangementView, { isFloating: false })
10574          ] }),
10575          hasDockedRightPane && !isFocusMode && !isRightPaneFloating && (inspectorState === "docked" && !isInspectorFloating || arrangementState === "docked") && /* @__PURE__ */ jsxRuntimeExports.jsx(Qt, { className: "w-1.5 bg-zinc-200 dark:bg-zinc-800 hover:bg-indigo-500/50 active:bg-indigo-500 cursor-col-resize transition-colors shrink-0 z-30 border-l border-zinc-200 dark:border-zinc-850 flex items-center justify-center relative group", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10576            "button",
10577            {
10578              onClick: (e) => {
10579                e.stopPropagation();
10580                setShowArrangement(false);
10581              },
10582              className: "absolute w-4 h-8 bg-zinc-300 dark:bg-zinc-700 hover:bg-indigo-500 dark:hover:bg-indigo-600 hover:text-white text-zinc-650 dark:text-zinc-300 rounded-r shadow flex items-center justify-center cursor-pointer left-0 z-50 border border-l-0 border-zinc-200 dark:border-zinc-600 opacity-0 group-hover:opacity-100 transition-opacity",
10583              title: "Collapse Arrangement",
10584              children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold select-none", children: "<" })
10585            }
10586          ) }),
10587          (hasDockedRightPane || isFocusMode) && !isRightPaneFloating && /* @__PURE__ */ jsxRuntimeExports.jsxs(Yt, { defaultSize: isFocusMode ? "100%" : "45%", minSize: isFocusMode ? "100%" : "350px", onResize: () => {
10588          }, className: "bg-white dark:bg-zinc-900 flex flex-col relative z-[60] shadow-[-5px_0_15px_rgba(0,0,0,0.05)] dark:shadow-[-5px_0_25px_rgba(0,0,0,0.5)]", style: { overflow: "visible" }, children: [
10589            arrangementState === "hidden" && !isFocusMode && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 top-1/2 -translate-y-1/2 z-[45]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10590              "button",
10591              {
10592                onClick: () =>
10592 setShowArrangement(true),
10593                className: "w-4 h-12 bg-zinc-250 dark:bg-zinc-800 border border-l-0 border-zinc-300 dark:border-zinc-700 hover:bg-indigo-500 dark:hover:bg-indigo-600 hover:text-white rounded-r shadow flex items-center justify-center cursor-pointer transition-colors",
10594                title: "Expand Arrangement",
10595                children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold select-none", children: ">" })
10596              }
10597            ) }),
10598            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center px-2 bg-zinc-50 dark:bg-zinc-950 shrink-0", children: [
10599              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 h-full items-center", children: [
10600                dockedRightTabs.includes("piano-roll") && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.pianoRollHelp", "Detailed view of MIDI notes and velocities."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10601                  "button",
10602                  {
10603                    onClick: () => setBottomPane("piano-roll"),
10604                    className: `px-4 py-1 text-xs font-bold transition-colors border-t-2 ${bottomPane === "piano-roll" ? "border-indigo-500 bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10605                    children: t("editor.pianoRollTab", "PIANO ROLL")
10606                  }
10607                ) }),
10608                dockedRightTabs.includes("piano-roll") && dockedRightTabs.includes("score-editor") && /* @__P
10608URE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
10609                dockedRightTabs.includes("score-editor") && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.scoreEditorHelp", "Traditional sheet music view and tools."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10610                  "button",
10611                  {
10612                    onClick: () => setBottomPane("score-editor"),
10613                    className: `px-4 py-1 text-xs font-bold transition-colors border-t-2 ${bottomPane === "score-editor" ? "border-indigo-500 bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10614                    children: t("editor.scoreEditorTab", "SCORE EDITOR")
10615                  }
10616                ) }),
10617                (dockedRightTabs.includes("piano-roll") || dockedRightTabs.includes("score-editor")) && dockedRightTabs.includes("mixer") && /* @__P
10617URE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
10618                dockedRightTabs.includes("mixer") && /* @__PURE__ */ jsxRuntimeExports.jsx(HelpTip, { text: t("editor.mixerHelp", "Adjust track volumes, panning, and effects."), position: "bottom", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10619                  "button",
10620                  {
10621                    onClick: () => setBottomPane("mixer"),
10622                    className: `px-4 py-1 text-xs font-bold transition-colors border-t-2 ${bottomPane === "mixer" ? "border-indigo-500 bg-white dark:bg-zinc-900 text-indigo-600 dark:text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10623                    children: t("editor.mixerTab", "MIXER")
10624                  }
10625                ) })
10626              ] }),
10627              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 px-2", children: [
10628                /* @__P
10628URE__ */ jsxRuntimeExports.jsx(
10629                  "button",
10630                  {
10631                    onClick: () => setPanelState(bottomPane === "piano-roll" ? "pianoRoll" : bottomPane === "score-editor" ? "scoreEditor" : "mixer", "floating"),
10632                    className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10633                    title: t("editor.float", "Float"),
10634                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 14 })
10635                  }
10636                ),
10637                /* @__PURE__ */ jsxRuntimeExports.jsx(
10638                  "button",
10639                  {
10640                    onClick: () => {
10641                      const pid = bottomPane === "piano-roll" ? "pianoRoll" : bottomPane === "score-editor" ? "scoreEditor" : "mixer";
10642                      setPanelState(pid, "popped-out");
10643                    },
10644                    className: "text-zinc-400 hover:text-zinc-200 p-1 rounded hover:bg-zinc-800 transition-all",
10645                    title: "Pop Out",
10646                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(MonitorUp, { size: 14 })
10647                  }
10648                ),
10649                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-4 bg-zinc-300 dark:bg-zinc-700 mx-1" }),
10650                /* @__PURE__ */ jsxRuntimeExports.jsx(
10651                  "button",
10652                  {
10653                    onClick: () => setIsFocusMode(!isFocusMode),
10654                    title: isFocusMode ? "Exit Focus Mode" : "Focus Mode",
10655                    className: `p-1.5 rounded transition-colors ${isFocusMode ? "bg-indigo-100 text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-800"}`,
10656                    children: isFocusMode ? /* @__PURE__ */ jsxRuntimeExports.jsx(Minimize2, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize2, { size: 16 })
10657                  }
10658                )
10659              ] })
10660            ] }),
10661            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex flex-col relative bg-zinc-50 dark:bg-zinc-950", children: bottomPane === "piano-roll" ? /* @__PURE__ */ jsxRuntimeExports.jsx(PianoRoll, {}) : bottomPane === "score-editor" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScoreEditor, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(Mixer, {}) })
10662          ] })
10663        ] })
10664      ] }),
10665      isProjectModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 text-zinc-950 dark:text-white", children: [
10666        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/85 backdrop-blur-sm", onClick: () => setIsProjectModalOpen(false) }),
10667        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-4xl bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95 max-h-[90vh] overflow-y-auto custom-scrollbar", children: [
10668          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-6", children: [
10669            /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
10670              /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { className: "text-indigo-500" }),
10671              "My Saved Projects"
10672            ] }),
10673            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsProjectModalOpen(false), children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20, className: "text-zinc-500 hover:text-zinc-900 dark:hover:text-white" }) })
10674          ] }),
10675          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10676            ProjectList,
10677            {
10678              onSelectProject: (project) => {
10679                setIsProjectModalOpen(false);
10680                if (onNavigate) {
10681                  onNavigate("editor", project.id);
10682                }
10683              },
10684              layout: "grid"
10685            }
10686          ) })
10687        ] })
10688      ] }),
10689      isSettingsOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4 text-zinc-950 dark:text-white", children: [
10690        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/85 backdrop-blur-sm", onClick: () => setIsSettingsOpen(false) }),
10691        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-lg bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95 max-h-[90vh] overflow-y-auto custom-scrollbar", children: [
10692          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-6", children: [
10693            /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
10694              /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "text-indigo-500 animate-spin-slow" }),
10695              "Editor Settings"
10696            ] }),
10697            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsSettingsOpen(false), children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20, className: "text-zinc-500 hover:text-zinc-900 dark:hover:text-white" }) })
10698          ] }),
10699          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-zinc-200 dark:border-zinc-800 mb-6", children: [
10700            /* @__PURE__ */ jsxRuntimeExports.jsx(
10701              "button",
10702              {
10703                onClick: () => setActiveSettingsTab("general"),
10704                className: `px-4 py-2 font-bold text-sm border-b-2 transition-colors ${activeSettingsTab === "general" ? "border-indigo-500 text-indigo-500" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-white"}`,
10705                children: "General"
10706              }
10707            ),
10708            /* @__PURE__ */ jsxRuntimeExports.jsx(
10709              "button",
10710              {
10711                onClick: () => setActiveSettingsTab("midi"),
10712                className: `px-4 py-2 font-bold text-sm border-b-2 transition-colors ${activeSettingsTab === "midi" ? "border-indigo-500 text-indigo-500" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-white"}`,
10713                children: "MIDI Setup"
10714              }
10715            )
10716          ] }),
10717          activeSettingsTab === "general" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-950 rounded-lg", children: [
10718            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
10719              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm", children: "Easy Mode" }),
10720              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500", children: "Simplify the DAW interface for beginner users." })
10721            ] }),
10722            /* @__PURE__ */ jsxRuntimeExports.jsx(
10723              "button",
10724              {
10725                onClick: () => setEasyMode(!easyMode),
10726                className: `px-3 py-1 text-xs font-bold rounded-lg transition-colors ${easyMode ? "bg-indigo-600 text-white" : "bg-zinc-200 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300"}`,
10727                children: easyMode ? "Enabled" : "Disabled"
10728              }
10729            )
10730          ] }) }),
10731          activeSettingsTab === "midi" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
10732            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-950 rounded-lg", children: [
10733              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
10734                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm", children: "Web MIDI Support" }),
10735                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500", children: "Required browser API for hardware MIDI input." })
10736              ] }),
10737              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-0.5 text-xs font-bold rounded ${isMidiSupported ? "bg-emerald-500/20 text-emerald-500" : "bg-rose-500/20 text-rose-500"}`, children: isMidiSupported ? "Supported" : "Unsupported" })
10738            ] }),
10739            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-950 rounded-lg", children: [
10740              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
10741                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm", children: "MIDI Keyboard Connection" }),
10742                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500", children: "Status of connected external hardware MIDI controllers." })
10743              ] }),
10744              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-0.5 text-xs font-bold rounded ${isMidiConnected ? "bg-emerald-500/20 text-emerald-500" : "bg-zinc-500/20 text-zinc-500"}`, children: isMidiConnected ? "Connected" : "Disconnected" })
10745            ] }),
10746            midiError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-rose-500/10 border border-rose-500/20 rounded-lg text-xs text-rose-500", children: midiError }),
10747            /* @__PURE__ */ jsxRuntimeExports.jsx(
10748              "button",
10749              {
10750                onClick: async () => {
10751                  const success = await connectMidi();
10752                  if (success) {
10753                    alert("MIDI devices successfully scanned and connected!");
10754                  } else {
10755                    alert("Failed to connect MIDI devices. Check permissions or browser support.");
10756                  }
10757                },
10758                className: "w-full py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg font-bold text-sm transition-colors shadow-lg",
10759                children: "Scan for MIDI Device"
10760              }
10761            )
10762          ] })
10763        ] })
10764      ] })
10765    ] }),
10766    easyToProMergeState && /* @__PURE__ */ jsxRuntimeExports.jsx(
10767      EasyToProMergeModal,
10768      {
10769        mergeState: easyToProMergeState,
10770        onClose: () => setEasyToProMergeState(null)
10771      }
10772    )
10773  ] });
10774};
10775const EditorPage = ({ currentUser, onAuthRequested, id, onNavigate }) => {
10776  return /* @__PURE__ */ jsxRuntimeExports.jsx(EditorProvider, { projectId: id, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
10777    EditorLayout,
10778    {
10779      currentUser,
10780      onAuthRequested,
10781      projectId: id,
10782      onNavigate
10783    }
10784  ) });
10785};
10786export {
10787  EditorPage as default
10788};

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.